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 directory declared used to return an undefined index, so a relative path
246 // that made the caller read or write 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-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-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-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-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 // Fix old names (map to new names)
533 $arrayconv = MODULE_MAPPING;
534 $arrayconvbis = array_flip(MODULE_MAPPING);
535
536 if (!getDolGlobalString('MAIN_USE_NEW_SUPPLIERMOD')) {
537 // Special cases: both use the same module.
538 $arrayconv['supplier_order'] = 'fournisseur';
539 $arrayconv['supplier_invoice'] = 'fournisseur';
540 }
541
542 $module_alt = $module;
543 if (!empty($arrayconv[$module])) {
544 $module_alt = $arrayconv[$module];
545 }
546 $module_bis = $module;
547 if (!empty($arrayconvbis[$module])) {
548 $module_bis = $arrayconvbis[$module];
549 }
550
551 return !empty($conf->modules[$module]) || !empty($conf->modules[$module_alt]) || !empty($conf->modules[$module_bis]);
552}
553
564function getWarningDelay($module, $parmlevel1, $parmlevel2 = '')
565{
566 global $conf;
567
568 // For compatibility with bad naming on module
569 $moduletomoduletouse = array(
570 'invoice' => 'facture',
571 );
572 $moduleParmsMapping = array(
573 'product' => 'produit',
574 );
575
576 if (!empty($moduletomoduletouse[$module])) {
577 $module = $moduletomoduletouse[$module];
578 }
579
580 $warningDelayPath = $parmlevel1;
581 if (!empty($moduleParmsMapping[$warningDelayPath])) {
582 $warningDelayPath = $moduleParmsMapping[$warningDelayPath];
583 }
584
585 if ($parmlevel2) {
586 if (!empty($conf->$module) && !empty($conf->$module->$warningDelayPath) && !empty($conf->$module->$warningDelayPath->$parmlevel2) && !empty($conf->$module->$warningDelayPath->$parmlevel2->warning_delay)) {
587 return (int) $conf->$module->$warningDelayPath->$parmlevel2->warning_delay;
588 }
589 } else {
590 if (!empty($conf->$module) && !empty($conf->$module->$warningDelayPath) && !empty($conf->$module->$warningDelayPath->warning_delay)) {
591 return (int) $conf->$module->$warningDelayPath->warning_delay;
592 }
593 }
594
595 return 0;
596}
597
604function isDolTms($timestamp)
605{
606 if ($timestamp === '') {
607 dol_syslog('Using empty string for a timestamp is deprecated, prefer use of null when calling page ' . $_SERVER["PHP_SELF"] . getCallerInfoString(), LOG_NOTICE);
608 return false;
609 }
610 if (is_null($timestamp) || !is_numeric($timestamp)) {
611 return false;
612 }
613
614 return true;
615}
616
628function getDoliDBInstance($type, $host, $user, $pass, $name, $port)
629{
630 require_once DOL_DOCUMENT_ROOT . "/core/db/" . $type . '.class.php';
631
632 $class = 'DoliDB' . ucfirst($type);
633 $db = new $class($type, $host, $user, $pass, $name, $port);
634 return $db;
635}
636
654function getEntity($element, $shared = 1, $currentobject = null)
655{
656 global $conf, $mc, $hookmanager, $object, $action, $db;
657
658 if (!is_object($hookmanager)) {
659 include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
660 $hookmanager = new HookManager($db);
661 }
662
663 // fix different element names (France to English)
664 switch ($element) {
665 case 'projet':
666 $element = 'project';
667 break;
668 case 'contrat':
669 $element = 'contract';
670 break; // "/contrat/class/contrat.class.php"
671 case 'order_supplier':
672 $element = 'supplier_order';
673 break; // "/fourn/class/fournisseur.commande.class.php"
674 case 'invoice_supplier':
675 $element = 'supplier_invoice';
676 break; // "/fourn/class/fournisseur.facture.class.php"
677 }
678
679 if (is_object($mc)) {
680 $out = $mc->getEntity($element, $shared, $currentobject);
681 } else {
682 $out = '';
683 $addzero = array('user', 'usergroup', 'cronjob', 'c_email_templates', 'email_template', 'default_values', 'overwrite_trans');
684 if (getDolGlobalString('HOLIDAY_ALLOW_ZERO_IN_DIC')) { // this constant break the dictionary admin without Multicompany
685 $addzero[] = 'c_holiday_types';
686 }
687 if (in_array($element, $addzero)) {
688 $out .= '0,';
689 }
690 $out .= ((int) $conf->entity);
691 }
692
693 // Manipulate entities to query on the fly
694 $parameters = array(
695 'element' => $element,
696 'shared' => $shared,
697 'object' => $object,
698 'currentobject' => $currentobject,
699 'out' => $out
700 );
701 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
702 $reshook = $hookmanager->executeHooks('hookGetEntity', $parameters, $currentobject, $action); // Note that $action and $object may have been modified by some hooks
703
704 if (is_numeric($reshook)) {
705 if ($reshook == 0 && !empty($hookmanager->resPrint)) {
706 $out .= ',' . $hookmanager->resPrint; // add
707 } elseif ($reshook == 1) {
708 $out = $hookmanager->resPrint; // replace
709 }
710 }
711
712 return $out;
713}
714
721function setEntity($currentobject)
722{
723 global $conf, $mc;
724
725 if (is_object($mc) && method_exists($mc, 'setEntity')) {
726 return $mc->setEntity($currentobject);
727 } else {
728 return ((is_object($currentobject) && $currentobject->id > 0 && ((int) $currentobject->entity) > 0) ? (int) $currentobject->entity : $conf->entity);
729 }
730}
731
738function isASecretKey($keyname)
739{
740 return preg_match('/(_pass|password|_pw|_key|securekey|serverkey|secret\d?|p12key|exportkey|_PW_[a-z]+|token)$/i', $keyname);
741}
742
743
750function num2Alpha($n)
751{
752 $r = '';
753 for ($r = ""; $n >= 0; $n = intval($n / 26) - 1) {
754 $r = chr($n % 26 + 0x41) . $r;
755 }
756 return $r;
757}
758
759
776function getBrowserInfo($user_agent)
777{
778 include_once DOL_DOCUMENT_ROOT . '/includes/mobiledetect/mobiledetectlib/Mobile_Detect.php';
779
780 $name = 'unknown';
781 $version = '';
782 $os = 'unknown';
783 $phone = '';
784
785 $user_agent = substr($user_agent, 0, 512); // Avoid to process too large user agent
786
787 // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal Bad definition of Mobile_Detect function
788 $detectmobile = new Mobile_Detect(null, $user_agent);
789 $tablet = $detectmobile->isTablet();
790
791 if ($detectmobile->isMobile()) {
792 $phone = 'unknown';
793
794 // If phone/smartphone, we set phone os name.
795 if ($detectmobile->is('AndroidOS')) {
796 $os = $phone = 'android';
797 } elseif ($detectmobile->is('BlackBerryOS')) {
798 $os = $phone = 'blackberry';
799 } elseif ($detectmobile->is('iOS')) {
800 $os = 'ios';
801 $phone = 'iphone';
802 } elseif ($detectmobile->is('PalmOS')) {
803 $os = $phone = 'palm';
804 } elseif ($detectmobile->is('SymbianOS')) {
805 $os = 'symbian';
806 } elseif ($detectmobile->is('webOS')) {
807 $os = 'webos';
808 } elseif ($detectmobile->is('MaemoOS')) {
809 $os = 'maemo';
810 } elseif ($detectmobile->is('WindowsMobileOS') || $detectmobile->is('WindowsPhoneOS')) {
811 $os = 'windows';
812 }
813 }
814
815 // OS
816 if (preg_match('/linux/i', $user_agent)) {
817 $os = 'linux';
818 } elseif (preg_match('/macintosh/i', $user_agent)) {
819 $os = 'macintosh';
820 } elseif (preg_match('/windows/i', $user_agent)) {
821 $os = 'windows';
822 }
823
824 // Name
825 $reg = array();
826 if (preg_match('/firefox(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
827 $name = 'firefox';
828 $version = empty($reg[2]) ? '' : $reg[2];
829 } elseif (preg_match('/edge(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
830 $name = 'edge';
831 $version = empty($reg[2]) ? '' : $reg[2];
832 } elseif (preg_match('/chrome(\/|\s)([\d\.]+)/i', $user_agent, $reg)) {
833 $name = 'chrome';
834 $version = empty($reg[2]) ? '' : $reg[2];
835 } elseif (preg_match('/chrome/i', $user_agent, $reg)) {
836 // we can have 'chrome (Mozilla...) chrome x.y' in one string
837 $name = 'chrome';
838 } elseif (preg_match('/iceweasel/i', $user_agent)) {
839 $name = 'iceweasel';
840 } elseif (preg_match('/epiphany/i', $user_agent)) {
841 $name = 'epiphany';
842 } elseif (preg_match('/safari(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
843 $name = 'safari';
844 $version = empty($reg[2]) ? '' : $reg[2];
845 } elseif (preg_match('/opera(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
846 // Safari is often present in string for mobile but its not.
847 $name = 'opera';
848 $version = empty($reg[2]) ? '' : $reg[2];
849 } elseif (preg_match('/(MSIE\s([0-9]+\.[0-9]))|.*(Trident\/[0-9]+.[0-9];.*rv:([0-9]+\.[0-9]+))/i', $user_agent, $reg)) {
850 $name = 'ie';
851 $version = end($reg);
852 } elseif (preg_match('/(Windows NT\s([0-9]+\.[0-9])).*(Trident\/[0-9]+.[0-9];.*rv:([0-9]+\.[0-9]+))/i', $user_agent, $reg)) {
853 // MS products at end
854 $name = 'ie';
855 $version = end($reg);
856 } elseif (preg_match('/l[iy]n(x|ks)(\‍(|\/|\s)*([\d\.]+)/i', $user_agent, $reg)) {
857 // MS products at end
858 $name = 'textbrowser';
859 $version = empty($reg[3]) ? '' : $reg[3];
860 } elseif (preg_match('/w3m\/([\d\.]+)/i', $user_agent, $reg)) {
861 // MS products at end
862 $name = 'textbrowser';
863 $version = empty($reg[1]) ? '' : $reg[1];
864 }
865
866 if ($tablet) {
867 $layout = 'tablet';
868 } elseif ($phone) {
869 $layout = 'phone';
870 } else {
871 $layout = 'classic';
872 }
873
874 return array(
875 'browsername' => $name,
876 'browserversion' => $version,
877 'browseros' => $os,
878 'browserua' => $user_agent,
879 'layout' => $layout, // tablet, phone, classic
880 'phone' => $phone, // deprecated
881 'tablet' => $tablet // deprecated
882 );
883}
884
890function dol_shutdown()
891{
892 global $db;
893 $disconnectdone = false;
894 $depth = 0;
895 if (is_object($db) && !empty($db->connected)) {
896 $depth = $db->transaction_opened;
897 $disconnectdone = $db->close();
898 }
899 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));
900}
901
911function GETPOSTISSET($paramname)
912{
913 $isset = false;
914
915 $relativepathstring = $_SERVER["PHP_SELF"];
916 // Clean $relativepathstring
917 if (constant('DOL_URL_ROOT')) {
918 $relativepathstring = preg_replace('/^' . preg_quote(constant('DOL_URL_ROOT'), '/') . '/', '', $relativepathstring);
919 }
920 $relativepathstring = ltrim($relativepathstring, '/');
921 $relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
922
923 // Code for search criteria persistence.
924 // Retrieve values if restore_lastsearch_values
925 if (!empty($_GET['restore_lastsearch_values'])) { // Use $_GET here and not GETPOST
926 if (!empty($_SESSION['lastsearch_values_' . $relativepathstring])) { // If there is saved values
927 $tmp = json_decode($_SESSION['lastsearch_values_' . $relativepathstring], true);
928 if (is_array($tmp)) {
929 foreach ($tmp as $key => $val) {
930 if ($key == $paramname) { // We are on the requested parameter
931 $isset = true;
932 break;
933 }
934 }
935 }
936 }
937 // If there is saved contextpage, limit, page or mode
938 if ($paramname == 'contextpage' && !empty($_SESSION['lastsearch_contextpage_' . $relativepathstring])) {
939 $isset = true;
940 } elseif ($paramname == 'limit' && !empty($_SESSION['lastsearch_limit_' . $relativepathstring])) {
941 $isset = true;
942 } elseif ($paramname == 'page' && !empty($_SESSION['lastsearch_page_' . $relativepathstring])) {
943 $isset = true;
944 } elseif ($paramname == 'mode' && !empty($_SESSION['lastsearch_mode_' . $relativepathstring])) {
945 $isset = true;
946 }
947 } else {
948 $isset = (isset($_POST[$paramname]) || isset($_GET[$paramname])); // We must keep $_POST and $_GET here
949 }
950
951 return $isset;
952}
953
962function GETPOSTISARRAY($paramname, $method = 0)
963{
964 // for $method test need return the same $val as GETPOST
965 if (empty($method)) {
966 $val = isset($_GET[$paramname]) ? $_GET[$paramname] : (isset($_POST[$paramname]) ? $_POST[$paramname] : '');
967 } elseif ($method == 1) {
968 $val = isset($_GET[$paramname]) ? $_GET[$paramname] : '';
969 } elseif ($method == 2) {
970 $val = isset($_POST[$paramname]) ? $_POST[$paramname] : '';
971 } elseif ($method == 3) {
972 $val = isset($_POST[$paramname]) ? $_POST[$paramname] : (isset($_GET[$paramname]) ? $_GET[$paramname] : '');
973 } else {
974 $val = 'BadFirstParameterForGETPOST';
975 }
976
977 return is_array($val);
978}
979
980
991function GETPOSTINT($paramname, $method = 0, $nodefault = 0)
992{
993 return (int) GETPOST($paramname, 'int', $method, null, null, 0, $nodefault);
994}
995
1009function GETPOSTFLOAT($paramname, $rounding = '', $option = 2)
1010{
1011 // 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.)
1012 return (float) price2num(GETPOST($paramname), $rounding, $option);
1013}
1014
1030function GETPOSTDATE($prefix, $hourTime = '', $gm = 'auto', $saverestore = '')
1031{
1032 $m = array();
1033 if ($hourTime === 'getpost' || $hourTime === 'getpostend') {
1034 $hour = (GETPOSTISSET($prefix . 'hour') && GETPOSTINT($prefix . 'hour') >= 0) ? GETPOSTINT($prefix . 'hour') : ($hourTime === 'getpostend' ? 23 : 0);
1035 $minute = (GETPOSTISSET($prefix . 'min') && GETPOSTINT($prefix . 'min') >= 0) ? GETPOSTINT($prefix . 'min') : ($hourTime === 'getpostend' ? 59 : 0);
1036 $second = (GETPOSTISSET($prefix . 'sec') && GETPOSTINT($prefix . 'sec') >= 0) ? GETPOSTINT($prefix . 'sec') : ($hourTime === 'getpostend' ? 59 : 0);
1037 } elseif (preg_match('/^(\d\d):(\d\d):(\d\d)$/', $hourTime, $m)) {
1038 $hour = intval($m[1]);
1039 $minute = intval($m[2]);
1040 $second = intval($m[3]);
1041 } elseif ($hourTime === 'end') {
1042 $hour = 23;
1043 $minute = 59;
1044 $second = 59;
1045 } else {
1046 $hour = $minute = $second = 0;
1047 }
1048
1049 if (
1050 $saverestore
1051 && !GETPOSTISSET($prefix . 'day')
1052 && !GETPOSTISSET($prefix . 'month')
1053 && !GETPOSTISSET($prefix . 'year')
1054 && isset($_SESSION['DOLDATE_' . $saverestore . '_day'])
1055 && isset($_SESSION['DOLDATE_' . $saverestore . '_month'])
1056 && isset($_SESSION['DOLDATE_' . $saverestore . '_year'])
1057 ) {
1058 $day = $_SESSION['DOLDATE_' . $saverestore . '_day'];
1059 $month = $_SESSION['DOLDATE_' . $saverestore . '_month'];
1060 $year = $_SESSION['DOLDATE_' . $saverestore . '_year'];
1061 } else {
1062 $month = GETPOSTINT($prefix . 'month');
1063 $day = GETPOSTINT($prefix . 'day');
1064 $year = GETPOSTINT($prefix . 'year');
1065 }
1066
1067 // normalize out of range values
1068 $hour = (int) min($hour, 23);
1069 $minute = (int) min($minute, 59);
1070 $second = (int) min($second, 59);
1071
1072 if ($saverestore) {
1073 $_SESSION['DOLDATE_' . $saverestore . '_day'] = $day;
1074 $_SESSION['DOLDATE_' . $saverestore . '_month'] = $month;
1075 $_SESSION['DOLDATE_' . $saverestore . '_year'] = $year;
1076 }
1077
1078 //print "$hour, $minute, $second, $month, $day, $year, $gm<br>";
1079 return dol_mktime($hour, $minute, $second, $month, $day, $year, $gm);
1080}
1081
1123function GETPOST($paramname, $check = 'alphanohtml', $method = 0, $filter = null, $options = null, $noreplace = 0, $nodefault = 0)
1124{
1125 global $langs, $user;
1126
1127 if (empty($paramname)) { // Explicit test for null for phan.
1128 return 'BadFirstParameterForGETPOST';
1129 }
1130 if (empty($check)) {
1131 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);
1132 // Enable this line to know who call the GETPOST with '' $check parameter.
1133 //var_dump(getCallerInfoString());
1134 }
1135 if (in_array($paramname, array('sortfield', 'sortorder'))) { // Force the $check to a more appropriated value
1136 $check = 'aZ09comma';
1137 }
1138
1139 if (empty($method)) {
1140 $out = isset($_GET[$paramname]) ? $_GET[$paramname] : (isset($_POST[$paramname]) ? $_POST[$paramname] : '');
1141 } elseif ($method == 1) {
1142 $out = isset($_GET[$paramname]) ? $_GET[$paramname] : '';
1143 } elseif ($method == 2) {
1144 $out = isset($_POST[$paramname]) ? $_POST[$paramname] : '';
1145 } elseif ($method == 3) {
1146 $out = isset($_POST[$paramname]) ? $_POST[$paramname] : (isset($_GET[$paramname]) ? $_GET[$paramname] : '');
1147 } else {
1148 return 'BadThirdParameterForGETPOST';
1149 }
1150
1151 $relativepathstring = ''; // For static analysis - looks possibly undefined if not set.
1152
1153 if (empty($method) || $method == 3 || $method == 4) {
1154 $relativepathstring = (empty($_SERVER["PHP_SELF"]) ? '' : $_SERVER["PHP_SELF"]);
1155 // Clean $relativepathstring
1156 if (constant('DOL_URL_ROOT')) {
1157 $relativepathstring = preg_replace('/^' . preg_quote(constant('DOL_URL_ROOT'), '/') . '/', '', $relativepathstring);
1158 }
1159 $relativepathstring = ltrim($relativepathstring, '/');
1160 $relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
1161
1162 // Code for search criteria persistence.
1163 // Retrieve saved values if restore_lastsearch_values is set
1164 if (!empty($_GET['restore_lastsearch_values'])) { // Use $_GET here and not GETPOST
1165 if (!empty($_SESSION['lastsearch_values_' . $relativepathstring])) { // If there is saved values
1166 $tmp = json_decode($_SESSION['lastsearch_values_' . $relativepathstring], true);
1167 if (is_array($tmp)) {
1168 foreach ($tmp as $key => $val) {
1169 if ($key == $paramname) { // We are on the requested parameter
1170 $out = $val;
1171 break;
1172 }
1173 }
1174 }
1175 }
1176 // If there is saved contextpage, page or limit
1177 if ($paramname == 'contextpage' && !empty($_SESSION['lastsearch_contextpage_' . $relativepathstring])) {
1178 $out = $_SESSION['lastsearch_contextpage_' . $relativepathstring];
1179 } elseif ($paramname == 'limit' && !empty($_SESSION['lastsearch_limit_' . $relativepathstring])) {
1180 $out = $_SESSION['lastsearch_limit_' . $relativepathstring];
1181 } elseif ($paramname == 'page' && !empty($_SESSION['lastsearch_page_' . $relativepathstring])) {
1182 $out = $_SESSION['lastsearch_page_' . $relativepathstring];
1183 } elseif ($paramname == 'mode' && !empty($_SESSION['lastsearch_mode_' . $relativepathstring])) {
1184 $out = $_SESSION['lastsearch_mode_' . $relativepathstring];
1185 }
1186 } elseif (!isset($_GET['sortfield'])) {
1187 // Else, retrieve default values if we are not doing a sort
1188 // 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
1189 if (!empty($_GET['action']) && $_GET['action'] == 'create' && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) {
1190 // Search default value from $object->field
1191 global $object;
1192 '@phan-var-force CommonObject $object'; // Suppose it's a CommonObject for analysis, but other objects have the $fields field as well
1193 if (is_object($object) && isset($object->fields[$paramname]['default'])) {
1194 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
1195 $out = $object->fields[$paramname]['default'];
1196 }
1197 }
1198 if (getDolGlobalString('MAIN_ENABLE_DEFAULT_VALUES')) {
1199 if (!empty($_GET['action']) && (preg_match('/^create/', $_GET['action']) || preg_match('/^presend/', $_GET['action'])) && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) {
1200 // Now search in setup to overwrite default values
1201 if (!empty($user->default_values)) { // $user->default_values defined from menu 'Setup - Default values'
1202 if (isset($user->default_values[$relativepathstring]['createform'])) {
1203 foreach ($user->default_values[$relativepathstring]['createform'] as $defkey => $defval) {
1204 $qualified = 0;
1205 if ($defkey != '_noquery_') {
1206 $tmpqueryarraytohave = explode('&', $defkey);
1207 $tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
1208 $foundintru = 0;
1209 foreach ($tmpqueryarraytohave as $tmpquerytohave) {
1210 if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) {
1211 $foundintru = 1;
1212 }
1213 }
1214 if (!$foundintru) {
1215 $qualified = 1;
1216 }
1217 } else {
1218 $qualified = 1;
1219 }
1220
1221 if ($qualified) {
1222 if (isset($user->default_values[$relativepathstring]['createform'][$defkey][$paramname])) {
1223 $out = $user->default_values[$relativepathstring]['createform'][$defkey][$paramname];
1224 break;
1225 }
1226 }
1227 }
1228 }
1229 }
1230 } elseif (!empty($paramname) && !isset($_GET[$paramname]) && !isset($_POST[$paramname]) && empty($nodefault)) {
1231 // Management of default search_filters and sort order
1232 if (!empty($user->default_values)) {
1233 // $user->default_values defined from menu 'Setup - Default values'
1234 //var_dump($user->default_values[$relativepathstring]);
1235 if ($paramname == 'sortfield' || $paramname == 'sortorder') {
1236 // Sorted on which fields ? ASC or DESC ?
1237 if (isset($user->default_values[$relativepathstring]['sortorder'])) {
1238 // Even if paramname is sortfield, data are stored into ['sortorder...']
1239 foreach ($user->default_values[$relativepathstring]['sortorder'] as $defkey => $defval) {
1240 $qualified = 0;
1241 if ($defkey != '_noquery_') {
1242 $tmpqueryarraytohave = explode('&', $defkey);
1243 $tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
1244 $foundintru = 0;
1245 foreach ($tmpqueryarraytohave as $tmpquerytohave) {
1246 if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) {
1247 $foundintru = 1;
1248 }
1249 }
1250 if (!$foundintru) {
1251 $qualified = 1;
1252 }
1253 } else {
1254 $qualified = 1;
1255 }
1256
1257 if ($qualified) {
1258 $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
1259 foreach ($user->default_values[$relativepathstring]['sortorder'][$defkey] as $key => $val) {
1260 if ($out) {
1261 $out .= ', ';
1262 }
1263 if ($paramname == 'sortfield') {
1264 $out .= dol_string_nospecial($key, '', $forbidden_chars_to_replace);
1265 }
1266 if ($paramname == 'sortorder') {
1267 $out .= dol_string_nospecial($val, '', $forbidden_chars_to_replace);
1268 }
1269 }
1270 //break; // No break for sortfield and sortorder so we can cumulate fields (is it really useful ?)
1271 }
1272 }
1273 }
1274 } elseif (isset($user->default_values[$relativepathstring]['filters'])) {
1275 foreach ($user->default_values[$relativepathstring]['filters'] as $defkey => $defval) { // $defkey is a querystring like 'a=b&c=d', $defval is key of user
1276 if (!empty($_GET['disabledefaultvalues'])) { // If set of default values has been disabled by a request parameter
1277 continue;
1278 }
1279 $qualified = 0;
1280 if ($defkey != '_noquery_') {
1281 $tmpqueryarraytohave = explode('&', $defkey);
1282 $tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
1283 $foundintru = 0;
1284 foreach ($tmpqueryarraytohave as $tmpquerytohave) {
1285 if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) {
1286 $foundintru = 1;
1287 }
1288 }
1289 if (!$foundintru) {
1290 $qualified = 1;
1291 }
1292 } else {
1293 $qualified = 1;
1294 }
1295
1296 if ($qualified && isset($user->default_values[$relativepathstring]['filters'][$defkey][$paramname])) {
1297 // We must keep $_POST and $_GET here
1298 if (isset($_POST['search_all']) || isset($_GET['search_all'])) {
1299 // We made a search from quick search menu, do we still use default filter ?
1300 if (!getDolGlobalString('MAIN_DISABLE_DEFAULT_FILTER_FOR_QUICK_SEARCH')) {
1301 $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
1302 $out = dol_string_nospecial($user->default_values[$relativepathstring]['filters'][$defkey][$paramname], '', $forbidden_chars_to_replace);
1303 }
1304 } else {
1305 $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
1306 $out = dol_string_nospecial($user->default_values[$relativepathstring]['filters'][$defkey][$paramname], '', $forbidden_chars_to_replace);
1307 }
1308 break;
1309 }
1310 }
1311 }
1312 }
1313 }
1314 }
1315 }
1316 }
1317
1318 // 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)
1319 // Example of variables: __DAY__, __MONTH__, __YEAR__, __MYCOMPANY_COUNTRY_ID__, __USER_ID__, ...
1320 // 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.
1321 '@phan-var-force string $paramname';
1322 if (!is_array($out) && empty($_POST[$paramname]) && empty($noreplace)) {
1323 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
1324 $substitutionarray = getCommonSubstitutionArray($langs, 0, null, $user, array('mycompany', 'date', 'system', 'user'));
1325 complete_substitutions_array($substitutionarray, $langs, $user);
1326
1327 $out = make_substitutions($out, $substitutionarray, $langs);
1328 }
1329 }
1330
1331 // Check type of variable and make sanitization according to this
1332 if (preg_match('/^array/', $check)) { // If 'array' or 'array:restricthtml' or 'array:aZ09' or 'array:int'
1333 $tmpcheck = 'alphanohtml';
1334 if ($out === null || $out === '') {
1335 $out = array();
1336 } elseif (!is_array($out)) {
1337 $out = explode(',', $out);
1338 } else {
1339 $tmparray = explode(':', $check);
1340 if (!empty($tmparray[1])) {
1341 $tmpcheck = $tmparray[1];
1342 }
1343 }
1344 foreach ($out as $outkey => $outval) {
1345 $out[$outkey] = sanitizeVal($outval, $tmpcheck, $filter, $options);
1346 }
1347 } else {
1348 // If field name is 'search_xxx' then we force the add of space after each < and > (when following char is numeric) because it means
1349 // 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
1350 if (strpos($paramname, 'search_') === 0) {
1351 $out = preg_replace('/([<>])([-+]?\d)/', '\1 \2', $out);
1352 }
1353
1354 // @phan-suppress-next-line UnknownSanitizeType
1355 $out = sanitizeVal($out, $check, $filter, $options);
1356 }
1357
1358 // Sanitizing for special parameters.
1359 // 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.
1360 // @TODO Merge backtopage with backtourl
1361 // @TODO Rename backtolist into backtopagelist
1362 // @TODO Merge urlfrom into backtourl
1363 if (preg_match('/^backto/i', $paramname) || preg_match('/^urlfrom/i', $paramname)) {
1364 $out = str_replace('\\', '/', $out); // Can be before the loop because only 1 char is replaced. No risk to get it after other replacements.
1365 $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.
1366 do {
1367 $oldstringtoclean = $out;
1368 $out = str_ireplace(array('javascript', 'vbscript', '&colon', '&#'), '', $out);
1369 $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'
1370 $out = preg_replace(array('/^[a-z]*\/\s*\/+/i'), '', $out); // We remove schema*// to remove external URL
1371 } while ($oldstringtoclean != $out);
1372 }
1373
1374 // Code for search criteria persistence.
1375 // Save data into session if key start with 'search_'
1376 if (empty($method) || $method == 3 || $method == 4) {
1377 if (preg_match('/^search_/', $paramname) || in_array($paramname, array('sortorder', 'sortfield'))) {
1378 //var_dump($paramname.' - '.$out.' '.$user->default_values[$relativepathstring]['filters'][$paramname]);
1379
1380 // We save search key only if $out not empty that means:
1381 // - posted value not empty, or
1382 // - 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).
1383
1384 if ($out != '' && isset($user)) { // $out = '0' or 'abc', it is a search criteria to keep
1385 $user->lastsearch_values_tmp[$relativepathstring][$paramname] = $out;
1386 }
1387 }
1388 }
1389
1390 if ($paramname == 'hashp' && $out == 'shared') {
1391 $out = ''; // We refuse to have hashp=shared as a parameter
1392 }
1393
1394 return $out;
1395}
1396
1406function sanitizeVal($out = '', $check = 'alphanohtml', $filter = null, $options = null)
1407{
1408 // TODO : use class "Validate" to perform tests (and add missing tests) if needed for factorize
1409 // Check is done after replacement
1410 if ($out === null) {
1411 $out = '';
1412 }
1413 switch ($check) {
1414 case 'none':
1415 case 'password':
1416 break;
1417 case 'int': // Check param is a numeric value (integer but also float or hexadecimal)
1418 if (!is_numeric($out)) {
1419 $out = '';
1420 }
1421 break;
1422 case 'intcomma':
1423 if (is_array($out)) {
1424 $out = implode(',', $out);
1425 }
1426 if (preg_match('/[^0-9,-]+/i', $out)) {
1427 $out = '';
1428 }
1429 break;
1430 case 'san_alpha':
1431 dol_syslog("Use of parameter value 'san_alpha' in GETPOST is deprecated. Use 'alphanohtml', 'aZ09comma', ...", LOG_WARNING);
1432 $out = filter_var($out, FILTER_SANITIZE_STRING);
1433 break;
1434 case 'email':
1435 $out = filter_var($out, FILTER_SANITIZE_EMAIL);
1436 break;
1437 case 'url':
1438 //$out = filter_var($out, FILTER_SANITIZE_URL); // Not reliable, replaced with FILTER_VALIDATE_URL
1439 $out = preg_replace('/[^:\/\[\]a-z0-9@\$\'\*\~\.\-_,;\?\!=%&+#]+/i', '', $out);
1440 // TODO Allow ( ) but only into password of https://login:password@domain...
1441 break;
1442 case 'aZ':
1443 if (!is_array($out)) {
1444 $out = trim($out);
1445 if (preg_match('/[^a-z]+/i', $out)) {
1446 $out = '';
1447 }
1448 }
1449 break;
1450 case 'aZ09':
1451 if (!is_array($out)) {
1452 $out = trim($out);
1453 if (preg_match('/[^a-z0-9_\-\.]+/i', $out)) {
1454 $out = '';
1455 }
1456 }
1457 break;
1458 case 'aZ09arobase': // great to sanitize $objecttype parameter
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 'aZ09comma': // great to sanitize $sortfield or $sortorder params that can be 't.abc,t.def_gh'
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 'alpha': // No html and no ../ and "
1475 case 'alphanohtml': // Recommended for most scalar parameters and search parameters. Not valid for json string.
1476 if (!is_array($out)) {
1477 $out = trim($out);
1478 do {
1479 $oldstringtoclean = $out;
1480 // Remove html tags
1481 $out = dol_string_nohtmltag($out, 0);
1482 // 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).
1483 $out = preg_replace('/\\\‍([0-9xu])/', '/\1', $out);
1484 // Remove also other dangerous string sequences
1485 // '../' or '..\' is dangerous because it allows dir transversals
1486 // '&#38', '&#0000038', '&#x26'... is a the char '&' alone but there is no reason to accept such way to encode input char
1487 // '"' = '&#34' = '&#0000034' = '&#x22' is dangerous because param in url can close the href= or src= and add javascript functions.
1488 // '&#47', '&#0000047', '&#x2F' is the char '/' but there is no reason to accept such way to encode this input char
1489 // '&#92' = '&#0000092' = '&#x5C' is the char '\' but there is no reason to accept such way to encode this input char
1490 $out = str_ireplace(array('../', '..\\', '&#38', '&#0000038', '&#x26', '&quot', '"', '&#34', '&#0000034', '&#x22', '&#47', '&#0000047', '&#x2F', '&#92', '&#0000092', '&#x5C'), '', $out);
1491 } while ($oldstringtoclean != $out);
1492 // keep lines feed
1493 }
1494 break;
1495 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'
1496 if (!is_array($out)) {
1497 $out = trim($out);
1498 do {
1499 $oldstringtoclean = $out;
1500 // Decode html entities
1501 $out = dol_html_entity_decode($out, ENT_COMPAT | ENT_HTML5, 'UTF-8');
1502 // 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).
1503 $out = preg_replace('/\\\‍([0-9xu])/', '/\1', $out);
1504 // Remove also other dangerous string sequences
1505 // '../' or '..\' is dangerous because it allows dir transversals
1506 // '&#38', '&#0000038', '&#x26'... is a the char '&' alone but there is no reason to accept such way to encode input char
1507 // '"' = '&#34' = '&#0000034' = '&#x22' is dangerous because param in url can close the href= or src= and add javascript functions.
1508 // '&#47', '&#0000047', '&#x2F' is the char '/' but there is no reason to accept such way to encode this input char
1509 // '&#92' = '&#0000092' = '&#x5C' is the char '\' but there is no reason to accept such way to encode this input char
1510 $out = str_ireplace(array('../', '..\\', '&#38', '&#0000038', '&#x26', '&quot', '"', '&#34', '&#0000034', '&#x22', '&#47', '&#0000047', '&#x2F', '&#92', '&#0000092', '&#x5C'), '', $out);
1511 } while ($oldstringtoclean != $out);
1512 }
1513 break;
1514 case 'nohtml': // No html. Valid for JSON strings.
1515 $out = dol_string_nohtmltag($out, 0);
1516 break;
1517 case 'restricthtmlnolink':
1518 case 'restricthtml': // Recommended for most html textarea
1519 case 'restricthtmlallowclass':
1520 case 'restricthtmlallowiframe':
1521 case 'restricthtmlallowlinkscript': // Allow link and script tag for head section.
1522 case 'restricthtmlallowunvalid':
1523 $out = dol_htmlwithnojs($out, 1, $check);
1524 break;
1525 case 'custom':
1526 if (!empty($out)) {
1527 if (empty($filter)) {
1528 return 'BadParameterForGETPOST - Param 3 of sanitizeVal()';
1529 }
1530 if (is_null($options)) {
1531 $options = 0;
1532 }
1533 $out = filter_var($out, $filter, $options);
1534 }
1535 break;
1536 default:
1537 dol_syslog("Error, you call sanitizeVal() with a bad value for the check type. Data will be sanitized with alphanohtml.", LOG_ERR);
1538 $out = GETPOST($out, 'alphanohtml');
1539 break;
1540 }
1541
1542 return $out;
1543}
1544
1553function dolSetCookie(string $cookiename, string $cookievalue, int $expire = -1)
1554{
1555 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/securitycore.lib.php';
1556
1557 global $dolibarr_main_force_https;
1558
1559 if ($expire == -1) {
1560 $expire = (time() + (86400 * 354)); // keep cookie 1 year.
1561 }
1562
1563 if (PHP_VERSION_ID < 70300) {
1564 setcookie($cookiename, empty($cookievalue) ? '' : $cookievalue, empty($cookievalue) ? 0 : $expire, '/', '', !(empty($dolibarr_main_force_https) && isHTTPS() === false), true); // add tag httponly
1565 } else {
1566 // Only available for php >= 7.3
1567 $cookieparams = array(
1568 'expires' => empty($cookievalue) ? 0 : $expire,
1569 'path' => '/',
1570 //'domain' => '.mywebsite.com', // the dot at the beginning allows compatibility with subdomains
1571 'secure' => !(empty($dolibarr_main_force_https) && isHTTPS() === false),
1572 'httponly' => true,
1573 'samesite' => 'Lax' // None || Lax || Strict
1574 );
1575 setcookie($cookiename, empty($cookievalue) ? '' : $cookievalue, $cookieparams);
1576 }
1577 if (empty($cookievalue)) {
1578 unset($_COOKIE[$cookiename]);
1579 }
1580}
1581
1582if (!function_exists('dol_getprefix')) {
1593 function dol_getprefix($mode = '')
1594 {
1595 // If prefix is for email (we need to have $conf already loaded for this case)
1596 if ($mode == 'email') {
1597 global $conf;
1598
1599 if (getDolGlobalString('MAIL_PREFIX_FOR_EMAIL_ID')) { // If MAIL_PREFIX_FOR_EMAIL_ID is set
1600 if (getDolGlobalString('MAIL_PREFIX_FOR_EMAIL_ID') != 'SERVER_NAME') {
1601 return getDolGlobalString('MAIL_PREFIX_FOR_EMAIL_ID');
1602 } elseif (isset($_SERVER["SERVER_NAME"])) { // If MAIL_PREFIX_FOR_EMAIL_ID is set to 'SERVER_NAME'
1603 return $_SERVER["SERVER_NAME"];
1604 }
1605 }
1606
1607 // The recommended value if MAIL_PREFIX_FOR_EMAIL_ID is not defined (may be not defined for old versions)
1608 if (!empty($conf->file->instance_unique_id)) {
1609 return sha1('dolibarr' . $conf->file->instance_unique_id);
1610 }
1611
1612 // For backward compatibility when instance_unique_id is not set
1613 return sha1(DOL_DOCUMENT_ROOT . DOL_URL_ROOT);
1614 }
1615
1616 // If prefix is for session (no need to have $conf loaded)
1617 global $dolibarr_main_instance_unique_id, $dolibarr_main_cookie_cryptkey; // This is loaded by filefunc.inc.php
1618 $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
1619
1620 // The recommended value (may be not defined for old versions)
1621 if (!empty($tmp_instance_unique_id)) {
1622 return sha1('dolibarr' . $tmp_instance_unique_id);
1623 }
1624
1625 // For backward compatibility when instance_unique_id is not set
1626 if (isset($_SERVER["SERVER_NAME"]) && isset($_SERVER["DOCUMENT_ROOT"])) {
1627 return sha1($_SERVER["SERVER_NAME"] . $_SERVER["DOCUMENT_ROOT"] . DOL_DOCUMENT_ROOT . DOL_URL_ROOT);
1628 } else {
1629 return sha1(DOL_DOCUMENT_ROOT . DOL_URL_ROOT);
1630 }
1631 }
1632}
1633
1644function dol_include_once($relpath, $classname = '')
1645{
1646 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']
1647
1648 if (strpos($relpath, '..') !== false) {
1649 // Found a not valid path
1650 dol_syslog('functions::dol_include_once Tried to load a file with a path including a forbidden sequence ".." : ' . $relpath, LOG_WARNING);
1651 return false;
1652 }
1653 if (!preg_match('/\.php$/', $relpath)) {
1654 // Found a not valid path
1655 dol_syslog('functions::dol_include_once Tried to load a file that is not a PHP file : ' . $relpath, LOG_WARNING);
1656 return false;
1657 }
1658
1659 $fullpath = dol_buildpath($relpath);
1660
1661 if (!file_exists($fullpath)) {
1662 dol_syslog('functions::dol_include_once Tried to load unexisting file: ' . $relpath, LOG_WARNING);
1663 return false;
1664 }
1665 if (!empty($classname) && !class_exists($classname)) {
1666 return include $fullpath;
1667 } else {
1668 return include_once $fullpath;
1669 }
1670}
1671
1672
1686function dol_buildpath($path, $type = 0, $returnemptyifnotfound = 0)
1687{
1688 global $conf;
1689
1690 $path = preg_replace('/^\//', '', $path);
1691
1692 if (empty($type)) { // For a filesystem path
1693 $res = DOL_DOCUMENT_ROOT . '/' . $path; // Standard default path
1694 if (is_array($conf->file->dol_document_root)) {
1695 foreach ($conf->file->dol_document_root as $key => $dirroot) { // ex: array("main"=>"/home/main/htdocs", "alt0"=>"/home/dirmod/htdocs", ...)
1696 if ($key == 'main') {
1697 continue;
1698 }
1699 // if (@file_exists($dirroot.'/'.$path)) {
1700 if (@file_exists($dirroot . '/' . $path)) { // avoid [php:warn]
1701 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/...'
1702 continue;
1703 }
1704 $res = $dirroot . '/' . $path;
1705 return $res;
1706 }
1707 }
1708 }
1709 if ($returnemptyifnotfound) {
1710 // Not found into alternate dir
1711 if ($returnemptyifnotfound == 1 || !file_exists($res)) {
1712 return '';
1713 }
1714 }
1715 } else {
1716 // For an url path
1717 // We try to get local path of file on filesystem from url
1718 // Note that trying to know if a file on disk exist by forging path on disk from url
1719 // works only for some web server and some setup. This is bugged when
1720 // using proxy, rewriting, virtual path, etc...
1721 $res = '';
1722 if ($type == 1) {
1723 $res = DOL_URL_ROOT . '/' . $path; // Standard value
1724 }
1725 if ($type == 2) {
1726 $res = DOL_MAIN_URL_ROOT . '/' . $path; // Standard value
1727 }
1728 if ($type == 3) {
1729 $res = DOL_URL_ROOT . '/' . $path;
1730 }
1731
1732 foreach ($conf->file->dol_document_root as $key => $dirroot) { // ex: array(["main"]=>"/home/main/htdocs", ["alt0"]=>"/home/dirmod/htdocs", ...)
1733 if ($key == 'main') {
1734 if ($type == 3) {
1735 /*global $dolibarr_main_url_root;*/
1736
1737 // Define $urlwithroot
1738 $urlwithouturlroot = preg_replace('/' . preg_quote(DOL_URL_ROOT, '/') . '$/i', '', trim($conf->file->dol_main_url_root));
1739 $urlwithroot = $urlwithouturlroot . DOL_URL_ROOT; // This is to use external domain name found into config file
1740 //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current
1741
1742 $res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : $urlwithroot) . '/' . $path; // Test on start with http is for old conf syntax
1743 }
1744 continue;
1745 }
1746 $regs = array();
1747 preg_match('/^([^\?]+(\.css\.php|\.css|\.js\.php|\.js|\.png|\.jpg|\.php)?)/i', $path, $regs); // Take part before '?'
1748 if (!empty($regs[1])) {
1749 //print $key.'-'.$dirroot.'/'.$path.'-'.$conf->file->dol_url_root[$type].'<br>'."\n";
1750 //if (file_exists($dirroot.'/'.$regs[1])) {
1751 if (@file_exists($dirroot . '/' . $regs[1])) { // avoid [php:warn]
1752 if ($type == 1) {
1753 $res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : DOL_URL_ROOT) . $conf->file->dol_url_root[$key] . '/' . $path;
1754 } elseif ($type == 2) {
1755 $res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : DOL_MAIN_URL_ROOT) . $conf->file->dol_url_root[$key] . '/' . $path;
1756 } elseif ($type == 3) {
1757 /*global $dolibarr_main_url_root;*/
1758
1759 // Define $urlwithroot
1760 $urlwithouturlroot = preg_replace('/' . preg_quote(DOL_URL_ROOT, '/') . '$/i', '', trim($conf->file->dol_main_url_root));
1761 $urlwithroot = $urlwithouturlroot . DOL_URL_ROOT; // This is to use external domain name found into config file
1762 //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current
1763
1764 $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
1765 }
1766 break;
1767 }
1768 }
1769 }
1770 }
1771
1772 return $res;
1773}
1774
1784function dolBuildUrl($url, $params = [], $addtoken = false, $anchor = '')
1785{
1786 global $db, $hookmanager;
1787
1788 if (!is_object($hookmanager)) {
1789 include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
1790 $hookmanager = new HookManager($db);
1791 }
1792 if ((!isset($params['mainmenu']) || empty($params['mainmenu'])) && GETPOSTISSET('mainmenu')) {
1793 $params = array_merge($params, ['mainmenu' => (GETPOST('mainmenu', 'restricthtml'))]);
1794 }
1795 if ((!isset($params['leftmenu'])/* || empty($params['leftmenu']) */) && GETPOSTISSET('leftmenu')) { // do not fill leftmenu if we have leftmenu=
1796 $params = array_merge($params, ['leftmenu' => (GETPOST('leftmenu', 'restricthtml'))]);
1797 }
1798 $parameters = [
1799 'path' => &$url,
1800 'params' => &$params,
1801 'addtoken' => &$addtoken,
1802 ];
1803 $hookmanager->executeHooks('buildurl', $parameters);
1804 if ($addtoken) {
1805 $params = array_merge($params, ['token' => newToken()]);
1806 }
1807 if ($params) {
1808 $url .= '?' . http_build_query($params);
1809 }
1810 if ($anchor) {
1811 $url .= '#' . preg_replace('/[^a-z]/i', '', $anchor);
1812 }
1813
1814 return $url;
1815}
1816
1827function dol_get_object_properties($obj, $properties = [])
1828{
1829 // Get real properties using get_object_vars() if $properties is empty
1830 if (empty($properties)) {
1831 return get_object_vars($obj);
1832 }
1833
1834 $existingProperties = [];
1835 $realProperties = get_object_vars($obj);
1836
1837 // Get the real or magic property values
1838 foreach ($properties as $property) {
1839 if (array_key_exists($property, $realProperties)) {
1840 // Real property, add the value
1841 $existingProperties[$property] = $obj->{$property};
1842 } elseif (property_exists($obj, $property)) {
1843 // Magic property
1844 $existingProperties[$property] = $obj->{$property};
1845 }
1846 }
1847
1848 return $existingProperties;
1849}
1850
1851
1869function dol_clone($srcobject, $native = 2)
1870{
1871 if ($native == 0) {
1872 // deprecated method, use the method with native = 2 instead
1873 dol_syslog("Warning, call to dol_clone() with the deprecated parameter native=0, use 2 instead", LOG_WARNING);
1874
1875 $tmpsavdb = null;
1876 if (isset($srcobject->db) && isset($srcobject->db->db) && is_object($srcobject->db->db) && get_class($srcobject->db->db) == 'PgSql\Connection') {
1877 $tmpsavdb = $srcobject->db;
1878 unset($srcobject->db); // Such property can not be serialized with pgsl (when object->db->db = 'PgSql\Connection')
1879 }
1880
1881 $myclone = unserialize(serialize($srcobject)); // serialize then unserialize is a hack to be sure to have a new object for all fields
1882
1883 if (!empty($tmpsavdb)) {
1884 $srcobject->db = $tmpsavdb;
1885 }
1886 } elseif ($native == 2) {
1887 // recommended method to have a full secured isolated cloned object
1888 $myclone = new stdClass();
1889 $tmparray = get_object_vars($srcobject); // return only public properties
1890
1891 if (is_array($tmparray)) {
1892 foreach ($tmparray as $propertykey => $propertyval) {
1893 if (is_scalar($propertyval) || is_array($propertyval)) {
1894 $myclone->$propertykey = $propertyval;
1895 }
1896 }
1897 }
1898 } else {
1899 $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)
1900 }
1901
1902 return $myclone;
1903}
1904
1905
1914function dol_clone_in_array($srcobject, $startlevel = 0)
1915{
1916 if (is_object($srcobject)) {
1917 $srcobject = get_object_vars($srcobject); // exclude private/protected properties
1918 }
1919
1920 if (is_array($srcobject)) {
1921 $result = [];
1922 foreach ($srcobject as $key => $value) {
1923 if (in_array($key, array('db', 'fields', 'error', 'errorhidden', 'errors', 'oldcopy', 'linkedObjects', 'linked_objects'))) {
1924 continue;
1925 }
1926 $result[$key] = dol_clone_in_array($value, $startlevel + 1);
1927 }
1928 return $result;
1929 }
1930
1931 return $srcobject;
1932}
1933
1934
1944function dol_size($size, $type = '')
1945{
1946 global $conf;
1947 if (empty($conf->dol_optimize_smallscreen)) {
1948 return $size;
1949 }
1950 if ($type == 'width' && $size > 250) {
1951 return 250;
1952 } else {
1953 return 10;
1954 }
1955}
1956
1957
1971function dol_sanitizeFileName($str, $newstr = '_', $unaccent = 1, $includequotes = 0, $allowdash = 0)
1972{
1973 $str = (string) $str;
1974
1975 // List of special chars for filenames in windows are defined on page https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
1976 // Char '>' '<' '|' '$' and ';' are special chars for shells.
1977 // Char '/' and '\' are file delimiters.
1978 // 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
1979 $filesystem_forbidden_chars = array('<', '>', '/', '\\', '?', '*', '|', '"', ':', '°', '$', ';', '`');
1980 if ($includequotes) {
1981 $filesystem_forbidden_chars[] = "'";
1982 }
1983 $tmp = dol_string_nospecial($unaccent ? dol_string_unaccent($str) : $str, $newstr, $filesystem_forbidden_chars);
1984 $tmp = preg_replace('/\-\-+/', '_', $tmp);
1985 if (empty($allowdash)) {
1986 $tmp = preg_replace('/\s+\-([^\s])/', ' _$1', $tmp);
1987 $tmp = preg_replace('/\s+\-$/', '', $tmp);
1988 }
1989 $tmp = str_replace('..', '', $tmp);
1990 $tmp = str_replace('~', $newstr, $tmp);
1991 $tmp = preg_replace('/\s{2,}/', ' ', $tmp);
1992
1993 return $tmp;
1994}
1995
1996
2009function dol_sanitizePathName($str, $newstr = '_', $unaccent = 0, $allowdash = 0)
2010{
2011 // List of special chars for filenames in windows are defined on page https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
2012 // Char '>' '<' '|' '$' ';' and '`' are special chars for shells.
2013 // Char '?' and '*' are for wild card chars.
2014 // Char '"' is dangerous.
2015 // Char '°' is just not expected.
2016 // 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
2017 // Chars '--' and '~' can be used for path transversal
2018 $filesystem_forbidden_chars = array('<', '>', '?', '*', '|', '"', '°', '$', ';', '`');
2019
2020 $tmp = $str;
2021 if ($unaccent) {
2022 $tmp = dol_string_unaccent($tmp);
2023 }
2024 $tmp = dol_string_nospecial($tmp, $newstr, $filesystem_forbidden_chars);
2025 $tmp = preg_replace('/\-\-+/', $newstr, $tmp);
2026 if (empty($allowdash)) {
2027 $tmp = preg_replace('/\s+\-([^\s])/', ' '.$newstr.'$1', $tmp);
2028 $tmp = preg_replace('/\s+\-$/', '', $tmp);
2029 }
2030 $tmp = str_replace('..', $newstr, $tmp);
2031 $tmp = str_replace('~', $newstr, $tmp);
2032 $tmp = preg_replace('/\s{2,}/', ' ', $tmp);
2033
2034 return $tmp;
2035}
2036
2044function dol_sanitizeUrl($stringtoclean, $type = 1)
2045{
2046 // 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)
2047 // We should use dol_string_nounprintableascii but function may not be yet loaded/available
2048 $stringtoclean = preg_replace('/[\x00-\x1F\x7F]/u', '', $stringtoclean); // /u operator makes UTF8 valid characters being ignored so are not included into the replace
2049 // We clean html comments because some hacks try to obfuscate evil strings by inserting HTML comments. Example: on<!-- -->error=alert(1)
2050 $stringtoclean = preg_replace('/<!--[^>]*-->/', '', $stringtoclean);
2051
2052 $stringtoclean = str_replace('\\', '/', $stringtoclean);
2053 if ($type == 1) {
2054 // removing : should disable links to external url like http:aaa)
2055 // removing ';' should disable "named" html entities encode into an url (we should not have this into an url)
2056 $stringtoclean = str_replace(array(':', ';', '@'), '', $stringtoclean);
2057 }
2058
2059 do {
2060 $oldstringtoclean = $stringtoclean;
2061 // removing '&colon' should disable links to external url like http:aaa)
2062 // removing '&#' should disable "numeric" html entities encode into an url (we should not have this into an url)
2063 $stringtoclean = str_ireplace(array('javascript', 'vbscript', '&colon', '&#'), '', $stringtoclean);
2064 } while ($oldstringtoclean != $stringtoclean);
2065
2066 if ($type == 1) {
2067 // removing '//' should disable links to external url like //aaa or http//)
2068 $stringtoclean = preg_replace(array('/^[a-z]*\/\/+/i'), '', $stringtoclean);
2069 }
2070
2071 return $stringtoclean;
2072}
2073
2080function dol_sanitizeEmail($stringtoclean)
2081{
2082 do {
2083 $oldstringtoclean = $stringtoclean;
2084 $stringtoclean = str_ireplace(array('"', ':', '[', ']', "\n", "\r", '\\', '\/'), '', $stringtoclean);
2085 } while ($oldstringtoclean != $stringtoclean);
2086
2087 return $stringtoclean;
2088}
2089
2098function dol_sanitizeKeyCode($str)
2099{
2100 return preg_replace('/[^\w]+/', '', $str);
2101}
2102
2103
2112function dol_string_unaccent($str)
2113{
2114 if (is_null($str)) {
2115 return '';
2116 }
2117
2118 if (utf8_check($str)) {
2119 if (extension_loaded('intl') && getDolGlobalString('MAIN_UNACCENT_USE_TRANSLITERATOR')) {
2120 $transliterator = Transliterator::createFromRules(':: Any-Latin; :: Latin-ASCII; :: NFD; :: [:Nonspacing Mark:] Remove; :: NFC;', Transliterator::FORWARD);
2121 return $transliterator->transliterate($str);
2122 }
2123 // See http://www.utf8-chartable.de/
2124 $string = rawurlencode($str);
2125 $replacements = array(
2126 '%C3%80' => 'A', '%C3%81' => 'A', '%C3%82' => 'A', '%C3%83' => 'A', '%C3%84' => 'A', '%C3%85' => 'A',
2127 '%C3%87' => 'C',
2128 '%C3%88' => 'E', '%C3%89' => 'E', '%C3%8A' => 'E', '%C3%8B' => 'E',
2129 '%C3%8C' => 'I', '%C3%8D' => 'I', '%C3%8E' => 'I', '%C3%8F' => 'I',
2130 '%C3%91' => 'N',
2131 '%C3%92' => 'O', '%C3%93' => 'O', '%C3%94' => 'O', '%C3%95' => 'O', '%C3%96' => 'O', '%C5%90' => 'O',
2132 '%C5%A0' => 'S',
2133 '%C3%99' => 'U', '%C3%9A' => 'U', '%C3%9B' => 'U', '%C3%9C' => 'U', '%C5%B0' => 'U',
2134 '%C3%9D' => 'Y', '%C5%B8' => 'y',
2135 '%C3%A0' => 'a', '%C3%A1' => 'a', '%C3%A2' => 'a', '%C3%A3' => 'a', '%C3%A4' => 'a', '%C3%A5' => 'a',
2136 '%C3%A7' => 'c',
2137 '%C3%A8' => 'e', '%C3%A9' => 'e', '%C3%AA' => 'e', '%C3%AB' => 'e',
2138 '%C3%AC' => 'i', '%C3%AD' => 'i', '%C3%AE' => 'i', '%C3%AF' => 'i',
2139 '%C3%B1' => 'n',
2140 '%C3%B2' => 'o', '%C3%B3' => 'o', '%C3%B4' => 'o', '%C3%B5' => 'o', '%C3%B6' => 'o', '%C5%91' => 'o',
2141 '%C5%A1' => 's',
2142 '%C3%B9' => 'u', '%C3%BA' => 'u', '%C3%BB' => 'u', '%C3%BC' => 'u', '%C5%B1' => 'u',
2143 '%C3%BD' => 'y', '%C3%BF' => 'y',
2144 '%CC%80' => '',
2145 '%CC%81' => '',
2146 '%CC%82' => '',
2147 '%CC%83' => '',
2148 '%CC%84' => '',
2149 '%CC%85' => '',
2150 '%CC%86' => '',
2151 '%CC%87' => '',
2152 '%CC%88' => '',
2153 '%CC%89' => '',
2154 '%CC%8A' => '',
2155 '%CC%8B' => '',
2156 '%CC%8C' => '',
2157 '%CC%8D' => '',
2158 '%CC%8E' => '',
2159 '%CC%8F' => '',
2160 '%CC%90' => '',
2161 '%CC%91' => '',
2162 '%CC%A7' => ''
2163 );
2164 $string = strtr($string, $replacements);
2165 return rawurldecode($string);
2166 } else {
2167 // See http://www.ascii-code.com/
2168 $string = strtr(
2169 $str,
2170 "\xC0\xC1\xC2\xC3\xC4\xC5\xC7
2171 \xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1
2172 \xD2\xD3\xD4\xD5\xD8\xD9\xDA\xDB\xDD
2173 \xE0\xE1\xE2\xE3\xE4\xE5\xE7\xE8\xE9\xEA\xEB
2174 \xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF8
2175 \xF9\xFA\xFB\xFC\xFD\xFF",
2176 "AAAAAAC
2177 EEEEIIIIDN
2178 OOOOOUUUY
2179 aaaaaaceeee
2180 iiiidnooooo
2181 uuuuyy"
2182 );
2183 $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"));
2184 return $string;
2185 }
2186}
2187
2201function dol_string_nospecial($str, $newstr = '_', $badcharstoreplace = '', $badcharstoremove = '', $keepspaces = 0)
2202{
2203 $forbidden_chars_to_replace = array("'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ",", ";", "=", '°', '$', ';'); // more complete than dol_sanitizeFileName
2204 if (empty($keepspaces)) {
2205 $forbidden_chars_to_replace[] = " ";
2206 }
2207 $forbidden_chars_to_remove = array();
2208 //$forbidden_chars_to_remove=array("(",")");
2209
2210 if (is_array($badcharstoreplace)) {
2211 $forbidden_chars_to_replace = $badcharstoreplace;
2212 }
2213 if (is_array($badcharstoremove)) {
2214 $forbidden_chars_to_remove = $badcharstoremove;
2215 }
2216
2217 // @phan-suppress-next-line PhanPluginSuspiciousParamOrderInternal
2218 return str_replace($forbidden_chars_to_replace, $newstr, str_replace($forbidden_chars_to_remove, "", $str));
2219}
2220
2221
2235function dol_string_nounprintableascii($str, $removetabcrlf = 1)
2236{
2237 if ($removetabcrlf) {
2238 return preg_replace('/[\x00-\x1F\x7F]/u', '', $str); // /u operator makes UTF8 valid characters being ignored so are not included into the replace
2239 } else {
2240 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
2241 }
2242}
2243
2250function dolSlugify($stringtoslugify)
2251{
2252 $slug = dol_string_unaccent($stringtoslugify);
2253
2254 // Convert special characters to their ASCII equivalents
2255 if (function_exists('iconv')) {
2256 $slug = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $slug);
2257 }
2258
2259 // Convert to lowercase
2260 $slug = strtolower($slug);
2261
2262 // Replace non-alphanumeric characters with hyphens
2263 $slug = preg_replace('/[^a-z0-9]+/', '-', $slug);
2264
2265 // Remove leading and trailing hyphens
2266 $slug = trim($slug, '-');
2267
2268 return $slug;
2269}
2270
2279function dol_escape_js($stringtoescape, $mode = 0, $noescapebackslashn = 0)
2280{
2281 if (is_null($stringtoescape)) {
2282 return '';
2283 }
2284
2285 // escape quotes and backslashes, newlines, etc.
2286 $substitjs = array("&#039;" => "\\'", "\r" => '\\r');
2287 //$substitjs['</']='<\/'; // We removed this. Should be useless.
2288 if (empty($noescapebackslashn)) {
2289 $substitjs["\n"] = '\\n';
2290 $substitjs['\\'] = '\\\\';
2291 }
2292 if (empty($mode)) {
2293 $substitjs["'"] = "\\'";
2294 $substitjs['"'] = "\\'";
2295 } elseif ($mode == 1) {
2296 $substitjs["'"] = "\\'";
2297 } elseif ($mode == 2) {
2298 $substitjs['"'] = '\\"';
2299 } elseif ($mode == 3) {
2300 $substitjs["'"] = "\\'";
2301 $substitjs['"'] = "\\\"";
2302 }
2303 return strtr((string) $stringtoescape, $substitjs);
2304}
2305
2315function dol_escape_uri($stringtoescape)
2316{
2317 return rawurlencode($stringtoescape);
2318}
2319
2326function dol_escape_json($stringtoescape)
2327{
2328 return str_replace('"', '\"', $stringtoescape);
2329}
2330
2338function dol_escape_php($stringtoescape, $stringforquotes = 2)
2339{
2340 if (is_null($stringtoescape)) {
2341 return '';
2342 }
2343
2344 if ($stringforquotes == 2) {
2345 return str_replace('"', "'", $stringtoescape);
2346 } elseif ($stringforquotes == 1) {
2347 // We remove the \ char.
2348 // If we allow the \ char, we can have $stringtoescape =
2349 // abc\';phpcodedanger; so the escapement will become
2350 // abc\\';phpcodedanger; and injecting this into
2351 // $a='...' will give $ac='abc\\';phpcodedanger;
2352 $stringtoescape = str_replace('\\', '', $stringtoescape);
2353 return str_replace("'", "\'", str_replace('"', "'", $stringtoescape));
2354 }
2355
2356 return 'Bad parameter for stringforquotes in dol_escape_php';
2357}
2358
2365function dol_escape_all($stringtoescape)
2366{
2367 return preg_replace('/[^a-z0-9_]/i', '', $stringtoescape);
2368}
2369
2376function dol_escape_xml($stringtoescape)
2377{
2378 return $stringtoescape;
2379}
2380
2381
2389function dol_strtolower($string, $encoding = "UTF-8")
2390{
2391 if (function_exists('mb_strtolower')) {
2392 return mb_strtolower($string, $encoding);
2393 } else {
2394 return strtolower($string);
2395 }
2396}
2397
2406function dol_strtoupper($string, $encoding = "UTF-8")
2407{
2408 if (function_exists('mb_strtoupper')) {
2409 return mb_strtoupper($string, $encoding);
2410 } else {
2411 return strtoupper($string);
2412 }
2413}
2414
2423function dol_ucfirst($string, $encoding = "UTF-8")
2424{
2425 if (function_exists('mb_substr')) {
2426 return mb_strtoupper(mb_substr($string, 0, 1, $encoding), $encoding) . mb_substr($string, 1, null, $encoding);
2427 } else {
2428 return ucfirst($string);
2429 }
2430}
2431
2440function dol_ucwords($string, $encoding = "UTF-8")
2441{
2442 if (function_exists('mb_convert_case')) {
2443 return mb_convert_case($string, MB_CASE_TITLE, $encoding);
2444 } else {
2445 return ucwords($string);
2446 }
2447}
2448
2449
2455function getCallerInfoString()
2456{
2457 $backtrace = debug_backtrace();
2458 $msg = "";
2459 if (count($backtrace) >= 1) {
2460 $pos = 1;
2461 if (count($backtrace) == 1) {
2462 $pos = 0;
2463 }
2464 $trace = $backtrace[$pos];
2465 if (isset($trace['file'], $trace['line'])) {
2466 $msg = " From {$trace['file']}:{$trace['line']}.";
2467 }
2468 }
2469 return $msg;
2470}
2471
2494function dol_syslog($message, $level = LOG_INFO, $ident = 0, $suffixinfilename = '', $restricttologhandler = '', $logcontext = null)
2495{
2496 global $conf, $user, $debugbar;
2497
2498 // If syslog module enabled
2499 if (!isModEnabled('syslog')) {
2500 return;
2501 }
2502
2503 // Check if we are into execution of code of a website
2504 if (defined('USEEXTERNALSERVER') && !defined('USEDOLIBARRSERVER') && !defined('USEDOLIBARREDITOR')) {
2505 global $website, $websitekey;
2506 if (is_object($website) && !empty($website->ref)) {
2507 $suffixinfilename .= '_website_' . $website->ref;
2508 } elseif (!empty($websitekey)) {
2509 $suffixinfilename .= '_website_' . $websitekey;
2510 }
2511 }
2512
2513 // Check if we have a forced suffix
2514 if (defined('USESUFFIXINLOG')) {
2515 $suffixinfilename .= constant('USESUFFIXINLOG');
2516 }
2517
2518 if ($ident < 0) {
2519 foreach ($conf->loghandlers as $loghandlerinstance) {
2520 $loghandlerinstance->setIdent($ident);
2521 }
2522 }
2523
2524 if (!empty($message)) {
2525 // Test log level
2526 // @phan-suppress-next-line PhanPluginDuplicateArrayKey
2527 $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');
2528
2529 if (!array_key_exists($level, $logLevels)) {
2530 dol_syslog('Error Bad Log Level ' . $level, LOG_ERR);
2531 $level = LOG_ERR;
2532 }
2533 if ($level > getDolGlobalInt('SYSLOG_LEVEL')) {
2534 return;
2535 }
2536
2537 if (!getDolGlobalString('MAIN_SHOW_PASSWORD_INTO_LOG')) {
2538 $message = preg_replace('/password=\'[^\']*\'/', 'password=\'hidden\'', $message); // protection to avoid to have value of password in log
2539 }
2540
2541 // If adding log inside HTML page is required
2542 if ((!empty($_REQUEST['logtohtml']) && getDolGlobalString('MAIN_ENABLE_LOG_TO_HTML'))
2543 || (is_object($user) && $user->hasRight('debugbar', 'read') && is_object($debugbar))
2544 ) {
2545 $ospid = sprintf("%7s", dol_trunc((string) getmypid(), 7, 'right', 'UTF-8', 1));
2546 $osuser = " " . sprintf("%6s", dol_trunc(function_exists('posix_getuid') ? posix_getuid() : '', 6, 'right', 'UTF-8', 1));
2547
2548 $conf->logbuffer[] = dol_print_date(time(), "%Y-%m-%d %H:%M:%S") . " " . sprintf("%-7s", $logLevels[$level]) . " " . $ospid . " " . $osuser . " " . $message;
2549 }
2550
2551 //TODO: Remove this. MAIN_ENABLE_LOG_INLINE_HTML should be deprecated and use a log handler dedicated to HTML output
2552 // If html log tag enabled and url parameter log defined, we show output log on HTML comments
2553 if (getDolGlobalString('MAIN_ENABLE_LOG_INLINE_HTML') && GETPOSTINT("log")) {
2554 print "\n\n<!-- Log start\n";
2555 print dol_escape_htmltag($message) . "\n";
2556 print "Log end -->\n";
2557 }
2558
2559 $data = array(
2560 'message' => $message,
2561 'script' => (isset($_SERVER['PHP_SELF']) ? basename($_SERVER['PHP_SELF'], '.php') : ''),
2562 'level' => $level,
2563 'user' => ((is_object($user) && $user->id) ? $user->login : ''),
2564 'ip' => '',
2565 'osuser' => function_exists('posix_getuid') ? (string) posix_getuid() : '',
2566 'ospid' => (string) getmypid() // on linux, max value is defined into cat /proc/sys/kernel/pid_max
2567 );
2568
2569 // For log, we want the reliable IP first.
2570 $remoteip = getUserRemoteIP(1); // Get ip when page run on a web server
2571 if (!empty($remoteip)) {
2572 $data['ip'] = $remoteip;
2573 // This is when server run behind a reverse proxy
2574 // A HTTP_X_FORWARDED_FOR as format "ip real of user, ip of proxy1, ip of proxy2, ..."
2575 // $data['ip'] is last
2576 if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
2577 $tmpips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
2578 $data['ip'] = '';
2579 $foundremoteip = 0;
2580 $j = 0;
2581 foreach ($tmpips as $tmpip) {
2582 $tmpip = trim($tmpip);
2583 if (strtolower($tmpip) == strtolower($remoteip)) {
2584 $foundremoteip = 1;
2585 }
2586 if (empty($data['ip'])) {
2587 $data['ip'] = $tmpip;
2588 } else {
2589 $j++;
2590 $data['ip'] .= (($j == 1) ? ' [via ' : ',') . $tmpip;
2591 }
2592 }
2593 if (!$foundremoteip) {
2594 $j++;
2595 $data['ip'] .= (($j == 1) ? ' [via ' : ',') . $remoteip;
2596 }
2597 $data['ip'] .= (($j > 0) ? ']' : '');
2598 } elseif (!empty($_SERVER['HTTP_CLIENT_IP'])) {
2599 $tmpips = explode(',', $_SERVER['HTTP_CLIENT_IP']);
2600 $data['ip'] = '';
2601 $foundremoteip = 0;
2602 $j = 0;
2603 foreach ($tmpips as $tmpip) {
2604 $tmpip = trim($tmpip);
2605 if (strtolower($tmpip) == strtolower($remoteip)) {
2606 $foundremoteip = 1;
2607 }
2608 if (empty($data['ip'])) {
2609 $data['ip'] = $tmpip;
2610 } else {
2611 $j++;
2612 $data['ip'] .= (($j == 1) ? ' [via ' : ',') . $tmpip;
2613 }
2614 }
2615 if (!$foundremoteip) {
2616 $j++;
2617 $data['ip'] .= (($j == 1) ? ' [via ' : ',') . $remoteip;
2618 }
2619 $data['ip'] .= (($j > 0) ? ']' : '');
2620 }
2621 } elseif (!empty($_SERVER['SERVER_ADDR'])) {
2622 // This is when PHP session is ran inside a web server but not inside a client request (example: init code of apache)
2623 $data['ip'] = (string) $_SERVER['SERVER_ADDR'];
2624 } elseif (!empty($_SERVER['COMPUTERNAME'])) {
2625 // 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).
2626 $data['ip'] = (string) $_SERVER['COMPUTERNAME'];
2627 } else {
2628 $data['ip'] = '???';
2629 }
2630
2631 if (!empty($_SERVER['USERNAME'])) {
2632 // 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).
2633 $data['osuser'] = (string) $_SERVER['USERNAME'];
2634 } elseif (!empty($_SERVER['LOGNAME'])) {
2635 // 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).
2636 $data['osuser'] = (string) $_SERVER['LOGNAME'];
2637 }
2638
2639 // Loop on each log handler and send output
2640 foreach ($conf->loghandlers as $loghandlerinstance) {
2641 if ($restricttologhandler && $loghandlerinstance->code != $restricttologhandler) {
2642 continue;
2643 }
2644 $loghandlerinstance->export($data, $suffixinfilename);
2645 }
2646 unset($data);
2647 }
2648
2649 if ($ident > 0) {
2650 foreach ($conf->loghandlers as $loghandlerinstance) {
2651 $loghandlerinstance->setIdent($ident);
2652 }
2653 }
2654}
2655
2656
2670function dol_format_address($object, $withcountry = 0, $sep = "\n", $outputlangs = null, $mode = 0, $extralangcode = '')
2671{
2672 global $langs, $hookmanager;
2673
2674 $ret = '';
2675 $countriesusingstate = array('AU', 'CA', 'US', 'IN', 'GB', 'ES', 'UK', 'TR', 'CN'); // See also MAIN_FORCE_STATE_INTO_ADDRESS
2676
2677 // See format of addresses on https://en.wikipedia.org/wiki/Address
2678 // Address
2679 if (empty($mode)) {
2680 $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)));
2681 }
2682 // Zip/Town/State
2683 if (isset($object->country_code) && in_array($object->country_code, array('AU', 'CA', 'US', 'CN')) || getDolGlobalString('MAIN_FORCE_STATE_INTO_ADDRESS')) {
2684 // US: title firstname name \n address lines \n town, state, zip \n country
2685 $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2686 $ret .= (($ret && $town) ? $sep : '') . $town;
2687
2688 if (!empty($object->state)) {
2689 $ret .= ($ret ? ($town ? ", " : $sep) : '') . $object->state;
2690 }
2691 if (!empty($object->zip)) {
2692 $ret .= ($ret ? (($town || $object->state) ? ", " : $sep) : '') . $object->zip;
2693 }
2694 } elseif (isset($object->country_code) && in_array($object->country_code, array('GB', 'UK'))) {
2695 // UK: title firstname name \n address lines \n town state \n zip \n country
2696 $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2697 $ret .= ($ret ? $sep : '') . $town;
2698 if (!empty($object->state)) {
2699 $ret .= ($ret ? ", " : '') . $object->state;
2700 }
2701 if (!empty($object->zip)) {
2702 $ret .= ($ret ? $sep : '') . $object->zip;
2703 }
2704 } elseif (isset($object->country_code) && in_array($object->country_code, array('ES', 'TR'))) {
2705 // ES: title firstname name \n address lines \n zip town \n state \n country
2706 $ret .= ($ret ? $sep : '') . $object->zip;
2707 $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2708 $ret .= ($town ? (($object->zip ? ' ' : '') . $town) : '');
2709 if (!empty($object->state)) {
2710 $ret .= $sep . $object->state;
2711 }
2712 } elseif (isset($object->country_code) && in_array($object->country_code, array('JP'))) {
2713 // JP: In romaji, title firstname name\n address lines \n [state,] town zip \n country
2714 // See https://www.sljfaq.org/afaq/addresses.html
2715 $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2716 $ret .= ($ret ? $sep : '') . ($object->state ? $object->state . ', ' : '') . $town . ($object->zip ? ' ' : '') . $object->zip;
2717 } elseif (isset($object->country_code) && in_array($object->country_code, array('IT'))) {
2718 // IT: title firstname name\n address lines \n zip town state_code \n country
2719 $ret .= ($ret ? $sep : '') . $object->zip;
2720 $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2721 $ret .= ($town ? (($object->zip ? ' ' : '') . $town) : '');
2722 $ret .= (empty($object->state_code) ? '' : (' ' . $object->state_code));
2723 } else {
2724 // Other: title firstname name \n address lines \n zip town[, state] \n country
2725 $town = (($extralangcode && !empty($object->array_languages['address'][$extralangcode])) ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2726 $ret .= !empty($object->zip) ? (($ret ? $sep : '') . $object->zip) : '';
2727 $ret .= ($town ? (($object->zip ? ' ' : ($ret ? $sep : '')) . $town) : '');
2728 if (!empty($object->state) && in_array($object->country_code, $countriesusingstate)) {
2729 $ret .= ($ret ? ", " : '') . $object->state;
2730 }
2731 }
2732
2733 if (!is_object($outputlangs)) {
2734 $outputlangs = $langs;
2735 }
2736 if ($withcountry) {
2737 $langs->load("dict");
2738 $ret .= (empty($object->country_code) ? '' : ($ret ? $sep : '') . $outputlangs->convToOutputCharset($outputlangs->transnoentitiesnoconv("Country" . $object->country_code)));
2739 }
2740 if ($hookmanager) {
2741 $parameters = array('withcountry' => $withcountry, 'sep' => $sep, 'outputlangs' => $outputlangs, 'mode' => $mode, 'extralangcode' => $extralangcode);
2742 $reshook = $hookmanager->executeHooks('formatAddress', $parameters, $object);
2743 if ($reshook > 0) {
2744 $ret = '';
2745 }
2746 $ret .= $hookmanager->resPrint;
2747 }
2748
2749 return $ret;
2750}
2751
2752
2753
2763function dol_strftime($fmt, $ts = false, $is_gmt = false)
2764{
2765 if ((abs($ts) <= 0x7FFFFFFF)) { // check if number in 32-bit signed range
2766 return dol_print_date($ts, $fmt, $is_gmt);
2767 } else {
2768 return 'Error date outside supported range';
2769 }
2770}
2771
2794function dol_print_date($time, $format = '', $tzoutput = 'auto', $outputlangs = null, $encodetooutput = false, $decorate = 0)
2795{
2796 global $conf, $langs;
2797
2798 // If date undefined or "", we return ""
2799 if (dol_strlen((string) $time) == 0) {
2800 return ''; // $time=0 allowed (it means 01/01/1970 00:00:00)
2801 }
2802
2803 if ($tzoutput === 'auto') {
2804 $tzoutput = (empty($conf) ? 'tzserver' : (isset($conf->tzuserinputkey) ? $conf->tzuserinputkey : 'tzserver'));
2805 }
2806
2807 // Clean parameters
2808 $to_gmt = false; // false if we want date in server timezone, true if we want to add offset
2809 $offsettz = $offsetdst = 0;
2810 if ($tzoutput) {
2811 $to_gmt = true; // For backward compatibility
2812 if (is_string($tzoutput)) {
2813 if ($tzoutput == 'tzserver') {
2814 $to_gmt = false;
2815 $offsettzstring = @date_default_timezone_get(); // Example 'Europe/Berlin' or 'Indian/Reunion'
2816 // @phan-suppress-next-line PhanPluginRedundantAssignment
2817 $offsettz = 0; // Timezone offset with server timezone (because to_gmt is false), so 0
2818 // @phan-suppress-next-line PhanPluginRedundantAssignment
2819 $offsetdst = 0; // Dst offset with server timezone (because to_gmt is false), so 0
2820 } elseif ($tzoutput == 'tzuser' || $tzoutput == 'tzuserrel') {
2821 $to_gmt = true;
2822 // if no session (by example in cron) may use MAIN_DOLIBARR_USER_TIMEZONE instead UTC
2823 $offsettzstring = (empty($_SESSION['dol_tz_string']) ? getDolGlobalString('MAIN_DOLIBARR_USER_TIMEZONE', 'UTC') : $_SESSION['dol_tz_string']); // Example 'Europe/Berlin' or 'Indian/Reunion'
2824
2825 if (class_exists('DateTimeZone')) {
2826 try {
2827 $user_date_tz = new DateTimeZone($offsettzstring);
2828 } catch (Exception $e) {
2829 // Bad value for $offsettzstring
2830 dol_syslog("DateInvalidTimeZoneException for timezone string '".$offsettzstring."'. Falling back to UTC.", LOG_ERR);
2831 $user_date_tz = new DateTimeZone('UTC'); // Force valid timezone as UTC
2832 }
2833 $user_dt = new DateTime();
2834 $user_dt->setTimezone($user_date_tz);
2835 $user_dt->setTimestamp($tzoutput == 'tzuser' ? dol_now() : (int) $time);
2836 $offsettz = $user_dt->getOffset(); // should include dst ?
2837 } else { // with old method (The 'tzuser' was processed like the 'tzuserrel')
2838 $offsettz = (empty($_SESSION['dol_tz']) ? 0 : $_SESSION['dol_tz']) * 60 * 60; // Will not be used anymore
2839 $offsetdst = (empty($_SESSION['dol_dst']) ? 0 : $_SESSION['dol_dst']) * 60 * 60; // Will not be used anymore
2840 }
2841 }
2842 }
2843 }
2844 if (!is_object($outputlangs)) {
2845 $outputlangs = $langs;
2846 }
2847 if (!$format) {
2848 $format = 'daytextshort';
2849 }
2850
2851 // Do we have to reduce the length of date (year on 2 chars) to save space.
2852 // Note: dayinputnoreduce is same than day but no reduction of year length will be done
2853 $reduceformat = (!empty($conf->dol_optimize_smallscreen) && in_array($format, array('day', 'dayhour', 'dayhoursec'))) ? 1 : 0; // Test on original $format param.
2854 $format = preg_replace('/inputnoreduce/', '', $format); // so format 'dayinputnoreduce' is processed like day
2855 $formatwithoutreduce = preg_replace('/reduceformat/', '', $format);
2856 if ($formatwithoutreduce != $format) {
2857 $format = $formatwithoutreduce;
2858 $reduceformat = 1;
2859 } // so format 'dayreduceformat' is processed like day
2860
2861 // Change predefined format into computer format. If found translation in lang file we use it, otherwise we use default.
2862 // TODO Add format daysmallyear and dayhoursmallyear
2863 if ($format == 'day') {
2864 $format = ($outputlangs->trans("FormatDateShort") != "FormatDateShort" ? $outputlangs->trans("FormatDateShort") : $conf->format_date_short);
2865 } elseif ($format == 'hour') {
2866 $format = ($outputlangs->trans("FormatHourShort") != "FormatHourShort" ? $outputlangs->trans("FormatHourShort") : $conf->format_hour_short);
2867 } elseif ($format == 'hoursec') {
2868 $s1 = $outputlangs->trans("FormatDateShort");
2869 $s2 = $outputlangs->trans("FormatDateHourSecShort");
2870 $s3 = trim(preg_replace('/'.preg_quote($s1, '/').'/', '', $s2)); // Try to guess the format for FormatHourSecShort using FormatDateShort and FormatDateHourSecShort
2871 $format = $s3;
2872 //$format = ($outputlangs->trans("FormatHourSecShort") != "FormatHourSecShort" ? $outputlangs->trans("FormatHourSecShort") : ($s3 ? $s3 : $conf->format_hour_sec_short));
2873 } elseif ($format == 'hourduration') {
2874 $format = ($outputlangs->trans("FormatHourShortDuration") != "FormatHourShortDuration" ? $outputlangs->trans("FormatHourShortDuration") : $conf->format_hour_short_duration);
2875 } elseif ($format == 'daytext') {
2876 $format = ($outputlangs->trans("FormatDateText") != "FormatDateText" ? $outputlangs->trans("FormatDateText") : $conf->format_date_text);
2877 } elseif ($format == 'daytextshort') {
2878 $format = ($outputlangs->trans("FormatDateTextShort") != "FormatDateTextShort" ? $outputlangs->trans("FormatDateTextShort") : $conf->format_date_text_short);
2879 } elseif ($format == 'dayhour') {
2880 $format = ($outputlangs->trans("FormatDateHourShort") != "FormatDateHourShort" ? $outputlangs->trans("FormatDateHourShort") : $conf->format_date_hour_short);
2881 } elseif ($format == 'dayhoursec') {
2882 $format = ($outputlangs->trans("FormatDateHourSecShort") != "FormatDateHourSecShort" ? $outputlangs->trans("FormatDateHourSecShort") : $conf->format_date_hour_sec_short);
2883 } elseif ($format == 'dayhourtext') {
2884 $format = ($outputlangs->trans("FormatDateHourText") != "FormatDateHourText" ? $outputlangs->trans("FormatDateHourText") : $conf->format_date_hour_text);
2885 } elseif ($format == 'dayhourtextshort') {
2886 $format = ($outputlangs->trans("FormatDateHourTextShort") != "FormatDateHourTextShort" ? $outputlangs->trans("FormatDateHourTextShort") : $conf->format_date_hour_text_short);
2887 } elseif ($format == 'dayhourlog') {
2888 // Format not sensitive to language
2889 $format = '%Y%m%d%H%M%S';
2890 } elseif ($format == 'dayhourlogsmall') {
2891 // Format not sensitive to language
2892 $format = '%y%m%d%H%M';
2893 } elseif ($format == 'dayhourldap') {
2894 $format = '%Y%m%d%H%M%SZ';
2895 } elseif ($format == 'dayhourxcard') {
2896 $format = '%Y%m%dT%H%M%SZ';
2897 } elseif ($format == 'dayxcard') {
2898 $format = '%Y%m%d';
2899 } elseif ($format == 'dayrfc') {
2900 $format = '%Y-%m-%d'; // DATE_RFC3339
2901 } elseif ($format == 'dayhourrfc') {
2902 $format = '%Y-%m-%dT%H:%M:%SZ'; // DATETIME RFC3339
2903 } elseif ($format == 'standard') {
2904 $format = '%Y-%m-%d %H:%M:%S';
2905 }
2906
2907 if ($reduceformat) {
2908 $format = str_replace('%Y', '%y', $format);
2909 $format = str_replace('yyyy', 'yy', $format);
2910 }
2911
2912 // Clean format
2913 if (preg_match('/%b/i', $format)) { // There is some text to translate
2914 // We inhibit translation to text made by strftime functions. We will use trans instead later.
2915 $format = str_replace('%b', '__b__', $format);
2916 $format = str_replace('%B', '__B__', $format);
2917 }
2918 if (preg_match('/%a/i', $format)) { // There is some text to translate
2919 // We inhibit translation to text made by strftime functions. We will use trans instead later.
2920 $format = str_replace('%a', '__a__', $format);
2921 $format = str_replace('%A', '__A__', $format);
2922 }
2923
2924 // Analyze date
2925 $reg = array();
2926 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
2927 dol_print_error(null, "Functions.lib::dol_print_date function called with a bad value" . getCallerInfoString());
2928 return '';
2929 } 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
2930 // This part of code should not be used anymore.
2931 dol_syslog("Functions.lib::dol_print_date function called with a bad value" . getCallerInfoString(), LOG_WARNING);
2932 // Date has format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'
2933 $syear = (!empty($reg[1]) ? $reg[1] : '');
2934 $smonth = (!empty($reg[2]) ? $reg[2] : '');
2935 $sday = (!empty($reg[3]) ? $reg[3] : '');
2936 $shour = (!empty($reg[4]) ? $reg[4] : '');
2937 $smin = (!empty($reg[5]) ? $reg[5] : '');
2938 $ssec = (!empty($reg[6]) ? $reg[6] : '');
2939
2940 $time = dol_mktime((int) $shour, (int) $smin, (int) $ssec, (int) $smonth, (int) $sday, (int) $syear, true);
2941
2942 if ($to_gmt) {
2943 $tzo = new DateTimeZone('UTC'); // when to_gmt is true, base for offsettz and offsetdst (so timetouse) is UTC
2944 } else {
2945 $tzo = new DateTimeZone(date_default_timezone_get()); // when to_gmt is false, base for offsettz and offsetdst (so timetouse) is PHP server
2946 }
2947 $dtts = new DateTime();
2948 $dtts->setTimestamp($time);
2949 $dtts->setTimezone($tzo);
2950 $newformat = str_replace(
2951 array('%Y', '%y', '%m', '%d', '%H', '%I', '%M', '%S', '%p', 'T', 'Z', '__a__', '__A__', '__b__', '__B__'),
2952 array('Y', 'y', 'm', 'd', 'H', 'h', 'i', 's', 'A', '__£__', '__$__', '__{__', '__}__', '__[__', '__]__'),
2953 $format
2954 );
2955 $ret = $dtts->format($newformat);
2956 $ret = str_replace(
2957 array('__£__', '__$__', '__{__', '__}__', '__[__', '__]__'),
2958 array('T', 'Z', '__a__', '__A__', '__b__', '__B__'),
2959 $ret
2960 );
2961 } else {
2962 // Date is a timestamps
2963 if ($time < 100000000000) { // Protection against bad date values
2964 $dtts = new DateTime();
2965 //var_dump($tzoutput.' '.$offsettzstring.' '.$offsettz.$offsetdst.' x '.$to_gmt);
2966 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
2967 $timetouse = (int) $time + $offsettz + $offsetdst; // TODO We could be able to disable use of offsettz and offsetdst to use only offsettzstring.
2968
2969 $tzo = new DateTimeZone('UTC'); // when to_gmt is true, base for offsettz and offsetdst (so timetouse) is UTC
2970 $dtts->setTimezone($tzo); // important: must be before the setTimestamp
2971 $dtts->setTimestamp($timetouse);
2972 } else {
2973 $timetouse = (int) $time + $offsettz + $offsetdst; // TODO We could be able to disable use of offsettz and offsetdst to use only offsettzstring.
2974
2975 $tzo = new DateTimeZone(date_default_timezone_get()); // when to_gmt is false, base for offsettz and offsetdst (so timetouse) is PHP server
2976 $dtts->setTimestamp($timetouse); // TODO May be we can invert setTimestamp and setTimezone
2977 $dtts->setTimezone($tzo);
2978 }
2979
2980 $newformat = str_replace(
2981 array('%Y', '%y', '%m', '%d', '%H', '%I', '%M', '%S', '%p', '%w', 'T', 'Z', '__a__', '__A__', '__b__', '__B__'),
2982 array('Y', 'y', 'm', 'd', 'H', 'h', 'i', 's', 'A', 'w', '__£__', '__$__', '__{__', '__}__', '__[__', '__]__'),
2983 $format
2984 );
2985
2986 $ret = $dtts->format($newformat);
2987 //var_dump($timetouse, $offsettz, $offsetdst, $tzo, $newformat, $ret);
2988 $ret = str_replace(
2989 array('__£__', '__$__', '__{__', '__}__', '__[__', '__]__'),
2990 array('T', 'Z', '__a__', '__A__', '__b__', '__B__'),
2991 $ret
2992 );
2993 } else {
2994 $ret = 'Bad value ' . $time . ' for date';
2995 }
2996 }
2997
2998 if (preg_match('/__b__/i', $format)) {
2999 $timetouse = $time + $offsettz + $offsetdst; // TODO We could be able to disable use of offsettz and offsetdst to use only offsettzstring.
3000
3001 if ($to_gmt) {
3002 $tzo = new DateTimeZone('UTC'); // when to_gmt is true, base for offsettz and offsetdst (so timetouse) is UTC
3003 } else {
3004 $tzo = new DateTimeZone(date_default_timezone_get()); // when to_gmt is false, base for offsettz and offsetdst (so timetouse) is PHP server
3005 }
3006 $dtts = new DateTime();
3007 $dtts->setTimestamp($timetouse);
3008 $dtts->setTimezone($tzo);
3009 $month = (int) $dtts->format("m");
3010 $month = sprintf("%02d", $month); // $month may be return with format '06' on some installation and '6' on other, so we force it to '06'.
3011 if ($encodetooutput) {
3012 $monthtext = $outputlangs->transnoentities('Month' . $month);
3013 $monthtextshort = $outputlangs->transnoentities('MonthShort' . $month);
3014 } else {
3015 $monthtext = $outputlangs->transnoentitiesnoconv('Month' . $month);
3016 $monthtextshort = $outputlangs->transnoentitiesnoconv('MonthShort' . $month);
3017 }
3018 //print 'monthtext='.$monthtext.' monthtextshort='.$monthtextshort;
3019 $ret = str_replace('__b__', $monthtextshort, $ret);
3020 $ret = str_replace('__B__', $monthtext, $ret);
3021 //print 'x'.$outputlangs->charset_output.'-'.$ret.'x';
3022 //return $ret;
3023 }
3024 if (preg_match('/__a__/i', $format)) {
3025 //print "time=$time offsettz=$offsettz offsetdst=$offsetdst offsettzstring=$offsettzstring";
3026 $timetouse = $time + $offsettz + $offsetdst; // TODO Replace this with function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
3027
3028 if ($to_gmt) {
3029 $tzo = new DateTimeZone('UTC');
3030 } else {
3031 $tzo = new DateTimeZone(date_default_timezone_get());
3032 }
3033 $dtts = new DateTime();
3034 $dtts->setTimestamp($timetouse);
3035 $dtts->setTimezone($tzo);
3036 $w = $dtts->format("w");
3037 $dayweek = $outputlangs->transnoentitiesnoconv('Day' . $w);
3038
3039 $ret = str_replace('__A__', $dayweek, $ret);
3040 $ret = str_replace('__a__', dol_substr($dayweek, 0, 3), $ret);
3041 }
3042
3043 if ($decorate) {
3044 $ret = preg_replace('/(\d\d:\d\d [AP]M)$/', '<span class="'.($decorate === 1 ? 'opacitymedium' : $decorate).'">\1</span>', $ret);
3045 $ret = preg_replace('/(\d\d:\d\d)$/', '<span class="'.($decorate === 1 ? 'opacitymedium' : $decorate).'">\1</span>', $ret);
3046 }
3047
3048 return $ret;
3049}
3050
3051
3072function dol_getdate($timestamp, $fast = false, $forcetimezone = '')
3073{
3074 if ($timestamp === '') {
3075 return array();
3076 }
3077
3078 $datetimeobj = new DateTime();
3079 $datetimeobj->setTimestamp($timestamp); // Use local PHP server timezone
3080 if ($forcetimezone) {
3081 $datetimeobj->setTimezone(new DateTimeZone($forcetimezone == 'gmt' ? 'UTC' : $forcetimezone)); // (add timezone relative to the date entered)
3082 }
3083 $arrayinfo = array(
3084 'year' => ((int) date_format($datetimeobj, 'Y')),
3085 'mon' => ((int) date_format($datetimeobj, 'm')),
3086 'mday' => ((int) date_format($datetimeobj, 'd')),
3087 'wday' => ((int) date_format($datetimeobj, 'w')),
3088 'yday' => ((int) date_format($datetimeobj, 'z')),
3089 'hours' => ((int) date_format($datetimeobj, 'H')),
3090 'minutes' => ((int) date_format($datetimeobj, 'i')),
3091 'seconds' => ((int) date_format($datetimeobj, 's')),
3092 '0' => $timestamp
3093 );
3094
3095 return $arrayinfo;
3096}
3097
3119function dol_mktime($hour, $minute, $second, $month, $day, $year, $gm = 'auto', $check = 1)
3120{
3121 global $conf;
3122 //print "- ".$hour.",".$minute.",".$second.",".$month.",".$day.",".$year.",".$_SERVER["WINDIR"]." -";
3123
3124 if ($gm === 'auto') {
3125 $gm = (empty($conf) ? 'tzserver' : $conf->tzuserinputkey);
3126 }
3127 //print 'gm:'.$gm.' gm === auto:'.($gm === 'auto').'<br>';exit;
3128
3129 // Clean parameters
3130 if ($hour == -1 || empty($hour)) {
3131 $hour = 0;
3132 }
3133 if ($minute == -1 || empty($minute)) {
3134 $minute = 0;
3135 }
3136 if ($second == -1 || empty($second)) {
3137 $second = 0;
3138 }
3139
3140 // Check parameters
3141 if ($check) {
3142 if (!$month || !$day) {
3143 return '';
3144 }
3145 if ($day > 31) {
3146 return '';
3147 }
3148 if ($month > 12) {
3149 return '';
3150 }
3151 if ($hour < 0 || $hour > 24) {
3152 return '';
3153 }
3154 if ($minute < 0 || $minute > 60) {
3155 return '';
3156 }
3157 if ($second < 0 || $second > 60) {
3158 return '';
3159 }
3160 }
3161
3162 if (empty($gm) || ($gm === 'server' || $gm === 'tzserver')) {
3163 $default_timezone = @date_default_timezone_get(); // Example 'Europe/Berlin'
3164 $localtz = new DateTimeZone($default_timezone);
3165 } elseif ($gm === 'user' || $gm === 'tzuser' || $gm === 'tzuserrel') {
3166 // We use dol_tz_string first because it is more reliable.
3167 $default_timezone = (empty($_SESSION["dol_tz_string"]) ? @date_default_timezone_get() : $_SESSION["dol_tz_string"]); // Example 'Europe/Berlin'
3168 try {
3169 $localtz = new DateTimeZone($default_timezone);
3170 } catch (Exception $e) {
3171 dol_syslog("Warning dol_tz_string contains an invalid value " . json_encode($_SESSION["dol_tz_string"] ?? null), LOG_WARNING);
3172 $default_timezone = @date_default_timezone_get();
3173 }
3174 } elseif (strrpos($gm, "tz,") !== false) {
3175 $timezone = (string) str_replace("tz,", "", $gm); // Example 'tz,Europe/Berlin'
3176 try {
3177 $localtz = new DateTimeZone($timezone);
3178 } catch (Exception $e) {
3179 dol_syslog("Warning passed timezone contains an invalid value " . $timezone, LOG_WARNING);
3180 }
3181 }
3182
3183 if (empty($localtz)) {
3184 $localtz = new DateTimeZone('UTC');
3185 }
3186 $dt = new DateTime('now', $localtz);
3187 $dt->setDate((int) $year, (int) $month, (int) $day);
3188 $dt->setTime((int) $hour, (int) $minute, (int) $second);
3189 $date = $dt->getTimestamp(); // should include daylight saving time
3190
3191 return $date;
3192}
3193
3194
3205function dol_now($mode = 'gmt')
3206{
3207 $ret = 0;
3208
3209 if ($mode === 'auto') {
3210 $mode = 'gmt';
3211 }
3212
3213 if ($mode == 'gmt') {
3214 $ret = time(); // Time for now at greenwich.
3215 } elseif ($mode == 'tzserver') { // Time for now with PHP server timezone added
3216 require_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php';
3217 $tzsecond = getServerTimeZoneInt('now'); // Contains tz+dayling saving time
3218 $ret = (int) (dol_now('gmt') + ($tzsecond * 3600));
3219 // } elseif ($mode == 'tzref') {// Time for now with parent company timezone is added
3220 // require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
3221 // $tzsecond=getParentCompanyTimeZoneInt(); // Contains tz+dayling saving time
3222 // $ret=dol_now('gmt')+($tzsecond*3600);
3223 } elseif ($mode == 'tzuser' || $mode == 'tzuserrel') {
3224 // Time for now with user timezone added
3225 // print 'time: '.time();
3226 $offsettz = (empty($_SESSION['dol_tz']) ? 0 : $_SESSION['dol_tz']) * 60 * 60;
3227 $offsetdst = (empty($_SESSION['dol_dst']) ? 0 : $_SESSION['dol_dst']) * 60 * 60;
3228 $ret = (int) (dol_now('gmt') + ($offsettz + $offsetdst));
3229 }
3230
3231 return $ret;
3232}
3233
3234
3243function dol_print_size($size, $shortvalue = 0, $shortunit = 0)
3244{
3245 global $conf, $langs;
3246 $level = 1024;
3247
3248 if (!empty($conf->dol_optimize_smallscreen)) {
3249 $shortunit = 1;
3250 }
3251
3252 // Set value text
3253 if (empty($shortvalue) || $size < ($level * 10)) {
3254 $ret = $size;
3255 $textunitshort = $langs->trans("b");
3256 $textunitlong = $langs->trans("Bytes");
3257 } else {
3258 $ret = round($size / $level, 0);
3259 $textunitshort = $langs->trans("Kb");
3260 $textunitlong = $langs->trans("KiloBytes");
3261 }
3262 // Use long or short text unit
3263 if (empty($shortunit)) {
3264 $ret .= ' ' . $textunitlong;
3265 } else {
3266 $ret .= ' ' . $textunitshort;
3267 }
3268
3269 return $ret;
3270}
3271
3282function dol_print_url($url, $target = '_blank', $max = 32, $withpicto = 0, $morecss = '')
3283{
3284 global $langs;
3285
3286 if (empty($url)) {
3287 return '';
3288 }
3289
3290 $linkstart = '<a href="';
3291 if (!preg_match('/^http/i', $url)) {
3292 $linkstart .= 'http://';
3293 }
3294 $linkstart .= $url;
3295 $linkstart .= '"';
3296 if ($target) {
3297 $linkstart .= ' target="' . $target . '"';
3298 }
3299 $linkstart .= ' title="' . $langs->trans("URL") . ': ' . $url . '"';
3300 $linkstart .= '>';
3301
3302 $link = '';
3303 if (!preg_match('/^http/i', $url)) {
3304 $link .= 'http://';
3305 }
3306 $link .= dol_trunc($url, $max);
3307
3308 $linkend = '</a>';
3309
3310 if ($morecss == 'float') { // deprecated
3311 return '<div class="nospan' . ($morecss ? ' ' . $morecss : '') . '" style="margin-right: 10px">' . ($withpicto ? img_picto($langs->trans("Url"), 'globe', 'class="paddingrightonly"') : '') . $link . '</div>';
3312 } else {
3313 return $linkstart . '<span class="nospan' . ($morecss ? ' ' . $morecss : '') . '" style="margin-right: 10px">' . ($withpicto ? img_picto('', 'globe', 'class="paddingrightonly"') : '') . $link . '</span>' . $linkend;
3314 }
3315}
3316
3330function dol_print_email($email, $contactid = 0, $socid = 0, $addlink = 0, $max = 0, $showinvalid = 2, $withpicto = 0, $morecss = 'paddingrightonly')
3331{
3332 global $user, $langs, $hookmanager;
3333
3334 //global $conf; $conf->global->AGENDA_ADDACTIONFOREMAIL = 1;
3335 //$showinvalid = 1; $email = 'rrrrr';
3336
3337 $newemail = dol_escape_htmltag($email);
3338
3339 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER') && $withpicto) {
3340 $withpicto = 0;
3341 }
3342
3343 if (empty($email)) {
3344 return '&nbsp;';
3345 }
3346
3347 if ($addlink == 1) {
3348 $newemail = '<a class="' . ($morecss ? $morecss : '') . '" style="text-overflow: ellipsis;" href="';
3349 if (!preg_match('/^mailto:/i', $email)) {
3350 $newemail .= 'mailto:';
3351 }
3352 $newemail .= $email;
3353 $newemail .= '" target="_blank">';
3354
3355 $newemail .= ($withpicto ? img_picto($langs->trans("EMail") . ' : ' . $email, (is_numeric($withpicto) ? 'email' : $withpicto), 'class="paddingrightonly"') : '');
3356
3357 if ($max > 0) {
3358 $newemail .= dol_trunc($email, $max);
3359 } else {
3360 $newemail .= $email;
3361 }
3362 $newemail .= '</a>';
3363
3364 if ($showinvalid) {
3365 include_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
3366 include_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
3367 $emailonly = CMailFile::getValidAddress($email, 2);
3368 if (!isValidEmail($emailonly)) {
3369 $langs->load("errors");
3370 $newemail .= img_warning($langs->transnoentitiesnoconv("ErrorBadEMail", $emailonly), '', 'paddingrightonly');
3371 } elseif ($showinvalid == 2 && !isValidMailDomain($emailonly)) {
3372 $langs->load("errors");
3373 $newemail .= img_warning($langs->transnoentitiesnoconv("ErrorBadMXDomain", $emailonly), '', 'paddingrightonly');
3374 }
3375 }
3376
3377 if (($contactid || $socid) && isModEnabled('agenda') && $user->hasRight("agenda", "myactions", "create")) {
3378 $type = 'AC_EMAIL';
3379 $linktoaddaction = '';
3380 if (getDolGlobalString('AGENDA_ADDACTIONFOREMAIL')) {
3381 $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>';
3382 }
3383 if ($linktoaddaction) {
3384 $newemail = '<div>' . $newemail . ' ' . $linktoaddaction . '</div>';
3385 }
3386 }
3387 } elseif ($addlink === 'thirdparty') {
3388 $tmpnewemail = '<a class="' . ($morecss ? $morecss : '') . '" style="text-overflow: ellipsis;" href="' . DOL_URL_ROOT . '/societe/card.php?socid=' . $socid . '&action=presend&mode=init#formmailbeforetitle">';
3389 $tmpnewemail .= ($withpicto ? img_picto($langs->trans("EMail") . ' : ' . $email, (is_numeric($withpicto) ? 'email' : $withpicto), 'class="paddingrightonly"') : '');
3390 if ($withpicto == 1) {
3391 $tmpnewemail .= $newemail;
3392 }
3393 $tmpnewemail .= '</a>';
3394
3395 $newemail = $tmpnewemail;
3396 } else {
3397 $newemail = ($withpicto ? img_picto($langs->trans("EMail") . ' : ' . $email, (is_numeric($withpicto) ? 'email' : $withpicto), 'class="paddingrightonly"') : '') . $newemail;
3398
3399 if ($showinvalid) {
3400 include_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
3401 include_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
3402 $emailonly = CMailFile::getValidAddress($email, 2);
3403 if (!isValidEmail($emailonly)) {
3404 $langs->load("errors");
3405 $newemail .= img_warning($langs->transnoentitiesnoconv("ErrorBadEMail", $email));
3406 } elseif ($showinvalid == 2 && !isValidMailDomain($emailonly)) {
3407 $langs->load("errors");
3408 $newemail .= img_warning($langs->transnoentitiesnoconv("ErrorBadMXDomain", $emailonly));
3409 }
3410 }
3411 }
3412
3413 //$rep = '<div class="nospan" style="margin-right: 10px">';
3414 //$rep = ($withpicto ? img_picto($langs->trans("EMail").' : '.$email, (is_numeric($withpicto) ? 'email' : $withpicto), 'class="paddingrightonly"') : '').$newemail;
3415 //$rep .= '</div>';
3416 $rep = $newemail;
3417 if (getDolGlobalString('MAIN_MAIL_COPY_ON_CLICK')) {
3418 $rep .= showValueWithClipboardCPButton($newemail, 0, 'none');
3419 }
3420
3421 if ($hookmanager) {
3422 $parameters = array('cid' => $contactid, 'socid' => $socid, 'addlink' => $addlink, 'picto' => $withpicto);
3423
3424 $reshook = $hookmanager->executeHooks('printEmail', $parameters, $email);
3425 if ($reshook > 0) {
3426 $rep = '';
3427 }
3428 $rep .= $hookmanager->resPrint;
3429 }
3430
3431 return $rep;
3432}
3433
3434
3440function getArrayOfSocialNetworks()
3441{
3442 global $db;
3443
3444 $socialnetworks = array();
3445 // Enable caching of array
3446 require_once DOL_DOCUMENT_ROOT . '/core/lib/memory.lib.php';
3447 $cachekey = dol_sanitizeKeyCode(str_replace(',', '_', 'socialnetworks_'.getEntity('c_socialnetworks')));
3448 $dataretrieved = dol_getcache($cachekey);
3449
3450 if (!is_null($dataretrieved)) {
3451 $socialnetworks = $dataretrieved;
3452 } else {
3453 $sql = "SELECT rowid, code, label, url, icon, active FROM " . MAIN_DB_PREFIX . "c_socialnetworks";
3454 $sql .= " WHERE entity IN (" . getEntity('c_socialnetworks').")";
3455
3456 $resql = $db->query($sql);
3457 if ($resql) {
3458 while ($obj = $db->fetch_object($resql)) {
3459 $socialnetworks[$obj->code] = array(
3460 'rowid' => $obj->rowid,
3461 'label' => $obj->label,
3462 'url' => $obj->url,
3463 'icon' => $obj->icon,
3464 'active' => $obj->active,
3465 );
3466 }
3467 }
3468 dol_setcache($cachekey, $socialnetworks); // If setting cache fails, this is not a problem, so we do not test result.
3469 }
3470
3471 return (is_array($socialnetworks) ? $socialnetworks : array());
3472}
3473
3484function dol_print_socialnetworks($value, $contactid, $socid, $type, $dictsocialnetworks = array())
3485{
3486 global $hookmanager, $langs, $user;
3487
3488 $htmllink = $value;
3489
3490 if (empty($value)) {
3491 return '&nbsp;';
3492 }
3493
3494 if (!empty($type)) {
3495 $htmllink = '<div class="divsocialnetwork inline-block valignmiddle">';
3496 // Use dictionary definition for picto $dictsocialnetworks[$type]['icon']
3497 $htmllink .= '<span class="fab pictofixedwidth ' . ($dictsocialnetworks[$type]['icon'] ? $dictsocialnetworks[$type]['icon'] : 'fa-link') . '"></span>';
3498 if ($type == 'skype') {
3499 $htmllink .= dol_escape_htmltag($value);
3500 $htmllink .= '&nbsp; <a href="skype:';
3501 $htmllink .= dol_string_nospecial($value, '_', '', array('@'));
3502 $htmllink .= '?call" alt="' . $langs->trans("Call") . '&nbsp;' . $value . '" title="' . dol_escape_htmltag($langs->trans("Call") . ' ' . $value) . '">';
3503 $htmllink .= '<img src="' . DOL_URL_ROOT . '/theme/common/skype_callbutton.png" border="0">';
3504 $htmllink .= '</a><a href="skype:';
3505 $htmllink .= dol_string_nospecial($value, '_', '', array('@'));
3506 $htmllink .= '?chat" alt="' . $langs->trans("Chat") . '&nbsp;' . $value . '" title="' . dol_escape_htmltag($langs->trans("Chat") . ' ' . $value) . '">';
3507 $htmllink .= '<img class="paddingleft" src="' . DOL_URL_ROOT . '/theme/common/skype_chatbutton.png" border="0">';
3508 $htmllink .= '</a>';
3509 if (($contactid || $socid) && isModEnabled('agenda') && $user->hasRight('agenda', 'myactions', 'create')) {
3510 $addlink = 'AC_SKYPE';
3511 $link = '';
3512 if (getDolGlobalString('AGENDA_ADDACTIONFORSKYPE')) {
3513 $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>';
3514 }
3515 $htmllink .= ($link ? ' ' . $link : '');
3516 }
3517 } else {
3518 if (!empty($dictsocialnetworks[$type]['url'])) {
3519 $tmpvirginurl = preg_replace('/\/?{socialid}/', '', $dictsocialnetworks[$type]['url']);
3520 if ($tmpvirginurl) {
3521 $value = preg_replace('/^www\.' . preg_quote($tmpvirginurl, '/') . '\/?/', '', $value);
3522 $value = preg_replace('/^' . preg_quote($tmpvirginurl, '/') . '\/?/', '', $value);
3523
3524 $tmpvirginurl3 = preg_replace('/^https:\/\//i', 'https://www.', $tmpvirginurl);
3525 if ($tmpvirginurl3) {
3526 $value = preg_replace('/^www\.' . preg_quote($tmpvirginurl3, '/') . '\/?/', '', $value);
3527 $value = preg_replace('/^' . preg_quote($tmpvirginurl3, '/') . '\/?/', '', $value);
3528 }
3529
3530 $tmpvirginurl2 = preg_replace('/^https?:\/\//i', '', $tmpvirginurl);
3531 if ($tmpvirginurl2) {
3532 $value = preg_replace('/^www\.' . preg_quote($tmpvirginurl2, '/') . '\/?/', '', $value);
3533 $value = preg_replace('/^' . preg_quote($tmpvirginurl2, '/') . '\/?/', '', $value);
3534 }
3535 }
3536 if (preg_match('/^https?:\/\//i', $value)) {
3537 $link = $value;
3538 } else {
3539 $link = str_replace('{socialid}', $value, $dictsocialnetworks[$type]['url']);
3540 }
3541 $valuetoshow = $value;
3542 $valuetoshow = preg_replace('/https:\/\/www\.(twitter|x|linkedin)\.com\/?/', '', $valuetoshow);
3543 if (preg_match('/^https?:\/\//i', $link)) {
3544 $htmllink .= '<a href="' . dol_sanitizeUrl($link, 0) . '" target="_blank" rel="noopener noreferrer">' . dol_escape_htmltag($valuetoshow) . '</a>';
3545 } else {
3546 $htmllink .= '<a href="' . dol_sanitizeUrl($link, 1) . '" target="_blank" rel="noopener noreferrer">' . dol_escape_htmltag($valuetoshow) . '</a>';
3547 }
3548 } else {
3549 $htmllink .= dol_escape_htmltag($value);
3550 }
3551 }
3552 $htmllink .= '</div>';
3553 } else {
3554 $langs->load("errors");
3555 $htmllink .= img_warning($langs->trans("ErrorBadSocialNetworkValue", $value));
3556 }
3557
3558 if ($hookmanager) {
3559 $parameters = array(
3560 'value' => $value,
3561 'cid' => $contactid,
3562 'socid' => $socid,
3563 'type' => $type,
3564 'dictsocialnetworks' => $dictsocialnetworks,
3565 );
3566
3567 $reshook = $hookmanager->executeHooks('printSocialNetworks', $parameters);
3568 if ($reshook > 0) {
3569 $htmllink = '';
3570 }
3571 $htmllink .= $hookmanager->resPrint;
3572 }
3573
3574 return $htmllink;
3575}
3576
3586function dol_print_profids($profID, $profIDtype, $countrycode = '', $addcpButton = 1)
3587{
3588 global $mysoc;
3589
3590 if (empty($profID) || empty($profIDtype)) {
3591 return '';
3592 }
3593 if (empty($countrycode)) {
3594 $countrycode = $mysoc->country_code;
3595 }
3596 $newProfID = $profID;
3597 $id = substr($profIDtype, -1);
3598 $ret = '';
3599 if (strtoupper($countrycode) == 'FR') {
3600 // France
3601 // (see https://www.economie.gouv.fr/entreprises/numeros-identification-entreprise)
3602
3603 if ($id == 1 && dol_strlen($newProfID) == 9) {
3604 // SIREN (ex: 123 123 123)
3605 $newProfID = substr($newProfID, 0, 3) . ' ' . substr($newProfID, 3, 3) . ' ' . substr($newProfID, 6, 3);
3606 }
3607 if ($id == 2 && dol_strlen($newProfID) == 14) {
3608 // SIRET (ex: 123 123 123 12345)
3609 $newProfID = substr($newProfID, 0, 3) . ' ' . substr($newProfID, 3, 3) . ' ' . substr($newProfID, 6, 3) . ' ' . substr($newProfID, 9, 5);
3610 }
3611 if ($id == 3 && dol_strlen($newProfID) == 5) {
3612 // NAF/APE (ex: 69.20Z)
3613 $newProfID = substr($newProfID, 0, 2) . '.' . substr($newProfID, 2, 3);
3614 }
3615 if ($profIDtype === 'VAT' && dol_strlen($newProfID) == 13) {
3616 // TVA intracommunautaire (ex: FR12 123 123 123)
3617 $newProfID = substr($newProfID, 0, 4) . ' ' . substr($newProfID, 4, 3) . ' ' . substr($newProfID, 7, 3) . ' ' . substr($newProfID, 10, 3);
3618 }
3619 }
3620 if (!empty($addcpButton)) {
3621 $ret = showValueWithClipboardCPButton(dol_escape_htmltag($profID), ($addcpButton == 1 ? 1 : 0), $newProfID);
3622 } else {
3623 $ret = $newProfID;
3624 }
3625 return $ret;
3626}
3627
3643function dol_print_phone($phone, $countrycode = '', $contactid = 0, $socid = 0, $addlink = '', $separ = "&nbsp;", $withpicto = '', $titlealt = '', $adddivfloat = 0, $morecss = 'paddingright')
3644{
3645 global $conf, $user, $langs, $mysoc, $hookmanager;
3646
3647 // Clean phone parameter
3648 $phone = is_null($phone) ? '' : preg_replace("/[\s.-]/", "", trim($phone));
3649 if (empty($phone)) {
3650 return '';
3651 }
3652 if (getDolGlobalString('MAIN_PHONE_SEPAR')) {
3653 $separ = getDolGlobalString('MAIN_PHONE_SEPAR');
3654 }
3655 if (empty($countrycode) && is_object($mysoc)) {
3656 $countrycode = $mysoc->country_code;
3657 }
3658
3659 // Short format for small screens
3660 if (!empty($conf->dol_optimize_smallscreen) && $separ != 'hidenum') {
3661 $separ = '';
3662 }
3663
3664 $newphone = $phone;
3665 $newphonewa = $phone;
3666 if (strtoupper($countrycode) == "FR") {
3667 // France
3668 if (dol_strlen($phone) == 10) {
3669 $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);
3670 } elseif (dol_strlen($phone) == 7) {
3671 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 2);
3672 } elseif (dol_strlen($phone) == 9) {
3673 $newphone = substr($newphone, 0, 2) . $separ . substr($newphone, 2, 3) . $separ . substr($newphone, 5, 2) . $separ . substr($newphone, 7, 2);
3674 } elseif (dol_strlen($phone) == 11) {
3675 $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);
3676 } elseif (dol_strlen($phone) == 12) {
3677 $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);
3678 } elseif (dol_strlen($phone) == 13) {
3679 $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);
3680 }
3681 } elseif (strtoupper($countrycode) == "CA") {
3682 if (dol_strlen($phone) == 10) {
3683 $newphone = ($separ != '' ? '(' : '') . substr($newphone, 0, 3) . ($separ != '' ? ')' : '') . $separ . substr($newphone, 3, 3) . ($separ != '' ? '-' : '') . substr($newphone, 6, 4);
3684 }
3685 } elseif (strtoupper($countrycode) == "PT") { //Portugal
3686 if (dol_strlen($phone) == 13) { //ex: +351_ABC_DEF_GHI
3687 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 3);
3688 }
3689 } elseif (strtoupper($countrycode) == "SR") { //Suriname
3690 if (dol_strlen($phone) == 10) { //ex: +597_ABC_DEF
3691 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3);
3692 } elseif (dol_strlen($phone) == 11) { //ex: +597_ABC_DEFG
3693 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 4);
3694 }
3695 } elseif (strtoupper($countrycode) == "DE") { //Deutschland
3696 if (dol_strlen($phone) == 14) { //ex: +49_ABCD_EFGH_IJK
3697 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 4) . $separ . substr($newphone, 7, 4) . $separ . substr($newphone, 11, 3);
3698 } elseif (dol_strlen($phone) == 13) { //ex: +49_ABC_DEFG_HIJ
3699 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 4) . $separ . substr($newphone, 10, 3);
3700 }
3701 } elseif (strtoupper($countrycode) == "ES") { //Spain
3702 if (dol_strlen($phone) == 12) { //ex: +34_ABC_DEF_GHI
3703 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
3704 }
3705 } elseif (strtoupper($countrycode) == "BF") { // Burkina Faso
3706 if (dol_strlen($phone) == 12) { //ex : +22 A BC_DE_FG_HI
3707 $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);
3708 }
3709 } elseif (strtoupper($countrycode) == "RO") { // Roumanie
3710 if (dol_strlen($phone) == 12) { //ex : +40 AB_CDE_FG_HI
3711 $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);
3712 }
3713 } elseif (strtoupper($countrycode) == "TR") { //Turquie
3714 if (dol_strlen($phone) == 13) { //ex : +90 ABC_DEF_GHIJ
3715 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 4);
3716 }
3717 } elseif (strtoupper($countrycode) == "US") { //Etat-Unis
3718 if (dol_strlen($phone) == 12) { //ex: +1 ABC_DEF_GHIJ
3719 $newphone = substr($newphone, 0, 2) . $separ . substr($newphone, 2, 3) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 4);
3720 }
3721 } elseif (strtoupper($countrycode) == "MX") { //Mexique
3722 if (dol_strlen($phone) == 12) { //ex: +52 ABCD_EFG_HI
3723 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 4) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 2);
3724 } elseif (dol_strlen($phone) == 11) { //ex: +52 AB_CD_EF_GH
3725 $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);
3726 } elseif (dol_strlen($phone) == 13) { //ex: +52 ABC_DEF_GHIJ
3727 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 4);
3728 }
3729 } elseif (strtoupper($countrycode) == "ML") { //Mali
3730 if (dol_strlen($phone) == 12) { //ex: +223 AB_CD_EF_GH
3731 $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);
3732 }
3733 } elseif (strtoupper($countrycode) == "TH") { //Thailand
3734 if (dol_strlen($phone) == 11) { //ex: +66_ABC_DE_FGH
3735 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 3);
3736 } elseif (dol_strlen($phone) == 12) { //ex: +66_A_BCD_EF_GHI
3737 $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);
3738 }
3739 } elseif (strtoupper($countrycode) == "MU") {
3740 //Maurice
3741 if (dol_strlen($phone) == 11) { //ex: +230_ABC_DE_FG
3742 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2);
3743 } elseif (dol_strlen($phone) == 12) { //ex: +230_ABCD_EF_GH
3744 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 4) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
3745 }
3746 } elseif (strtoupper($countrycode) == "ZA") { //Afrique du sud
3747 if (dol_strlen($phone) == 12) { //ex: +27_AB_CDE_FG_HI
3748 $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);
3749 }
3750 } elseif (strtoupper($countrycode) == "SY") { //Syrie
3751 if (dol_strlen($phone) == 12) { //ex: +963_AB_CD_EF_GH
3752 $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);
3753 } elseif (dol_strlen($phone) == 13) { //ex: +963_AB_CD_EF_GHI
3754 $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);
3755 }
3756 } elseif (strtoupper($countrycode) == "AE") { //Emirats Arabes Unis
3757 if (dol_strlen($phone) == 12) { //ex: +971_ABC_DEF_GH
3758 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 2);
3759 } elseif (dol_strlen($phone) == 13) { //ex: +971_ABC_DEF_GHI
3760 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 3);
3761 } elseif (dol_strlen($phone) == 14) { //ex: +971_ABC_DEF_GHIK
3762 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 4);
3763 }
3764 } elseif (strtoupper($countrycode) == "DZ") { //Algeria
3765 if (dol_strlen($phone) == 13) { //ex: +213_ABC_DEF_GHI
3766 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 3);
3767 }
3768 } elseif (strtoupper($countrycode) == "BE") { //Belgique
3769 if (dol_strlen($phone) == 11) { //ex: +32_ABC_DE_FGH
3770 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 3);
3771 } elseif (dol_strlen($phone) == 12) { //ex: +32_ABC_DEF_GHI
3772 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
3773 }
3774 } elseif (strtoupper($countrycode) == "PF") { //French Polynesia
3775 if (dol_strlen($phone) == 12) { //ex: +689_AB_CD_EF_GH
3776 $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);
3777 }
3778 } elseif (strtoupper($countrycode) == "CO") { //Colombie
3779 if (dol_strlen($phone) == 13) { //ex: +57_ABC_DEF_GH_IJ
3780 $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);
3781 }
3782 } elseif (strtoupper($countrycode) == "JO") { //Jordanie
3783 if (dol_strlen($phone) == 12) { //ex: +962_A_BCD_EF_GH
3784 $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);
3785 }
3786 } elseif (strtoupper($countrycode) == "JM") { //Jamaica
3787 if (dol_strlen($newphone) == 12) { //ex: +1867_ABC_DEFG
3788 $newphone = substr($newphone, 0, 5) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 4);
3789 }
3790 } elseif (strtoupper($countrycode) == "MG") { //Madagascar
3791 if (dol_strlen($phone) == 13) { //ex: +261_AB_CD_EFG_HI
3792 $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);
3793 }
3794 } elseif (strtoupper($countrycode) == "GB") { //Royaume uni
3795 if (dol_strlen($phone) == 13) { //ex: +44_ABCD_EFG_HIJ
3796 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 4) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 3);
3797 }
3798 } elseif (strtoupper($countrycode) == "CH") { //Suisse
3799 if (dol_strlen($phone) == 12) { //ex: +41_AB_CDE_FG_HI
3800 $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);
3801 } elseif (dol_strlen($phone) == 15) { // +41_AB_CDE_FGH_IJKL
3802 $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);
3803 }
3804 } elseif (strtoupper($countrycode) == "TN") { //Tunisie
3805 if (dol_strlen($phone) == 12) { //ex: +216_AB_CDE_FGH
3806 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
3807 }
3808 } elseif (strtoupper($countrycode) == "GF") { //Guyane francaise
3809 if (dol_strlen($phone) == 13) { //ex: +594_ABC_DE_FG_HI (ABC=594 de nouveau)
3810 $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);
3811 }
3812 } elseif (strtoupper($countrycode) == "GP") { //Guadeloupe
3813 if (dol_strlen($phone) == 13) { //ex: +590_ABC_DE_FG_HI (ABC=590 de nouveau)
3814 $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);
3815 }
3816 } elseif (strtoupper($countrycode) == "MQ") { //Martinique
3817 if (dol_strlen($phone) == 13) { //ex: +596_ABC_DE_FG_HI (ABC=596 de nouveau)
3818 $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);
3819 }
3820 } elseif (strtoupper($countrycode) == "IT") { //Italie
3821 if (dol_strlen($phone) == 12) { //ex: +39_ABC_DEF_GHI
3822 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
3823 } elseif (dol_strlen($phone) == 13) { //ex: +39_ABC_DEF_GH_IJ
3824 $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);
3825 }
3826 } elseif (strtoupper($countrycode) == "AU") {
3827 //Australie
3828 if (dol_strlen($phone) == 12) {
3829 //ex: +61_A_BCDE_FGHI
3830 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 1) . $separ . substr($newphone, 4, 4) . $separ . substr($newphone, 8, 4);
3831 }
3832 } elseif (strtoupper($countrycode) == "LU") {
3833 // Luxembourg
3834 if (dol_strlen($phone) == 10) { // fix 6 digits +352_AA_BB_CC
3835 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2);
3836 } elseif (dol_strlen($phone) == 11) { // fix 7 digits +352_AA_BB_CC_D
3837 $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);
3838 } elseif (dol_strlen($phone) == 12) { // fix 8 digits +352_AA_BB_CC_DD
3839 $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);
3840 } elseif (dol_strlen($phone) == 13) { // mobile +352_AAA_BB_CC_DD
3841 $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);
3842 }
3843 } elseif (strtoupper($countrycode) == "PE") {
3844 // Peru
3845 if (dol_strlen($phone) == 7) { // fix 7 numbers without code AAA_BBBB
3846 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 4);
3847 } elseif (dol_strlen($phone) == 9) { // mobile add code and fix 9 numbers +51_AAA_BBB_CCC
3848 $newphonewa = '+51' . $newphone;
3849 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3);
3850 } elseif (dol_strlen($phone) == 11) { // fix 11 numbers +511_AAA_BBBB
3851 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 4);
3852 } elseif (dol_strlen($phone) == 12) { // mobile +51_AAA_BBB_CCC
3853 $newphonewa = $newphone;
3854 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
3855 }
3856 } elseif (strtoupper($countrycode) == "IN") { //India
3857 if (dol_strlen($phone) == 13) {
3858 if ($withpicto == 'phone') { //ex: +91_AB_CDEF_GHIJ
3859 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 4) . $separ . substr($newphone, 9, 4);
3860 } else { //ex: +91_ABCDE_FGHIJ
3861 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 5) . $separ . substr($newphone, 8, 5);
3862 }
3863 }
3864 }
3865
3866 $newphoneastart = $newphoneaend = '';
3867 if (!empty($addlink)) { // Link on phone number (+ link to add action if conf->global->AGENDA_ADDACTIONFORPHONE set)
3868 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
3869 $newphoneastart = '<a href="tel:' . urlencode($phone) . '">';
3870 $newphoneaend .= '</a>';
3871 } elseif (isModEnabled('clicktodial') && $addlink == 'AC_TEL') { // If click to dial, we use click to dial url
3872 if (empty($user->clicktodial_loaded)) {
3873 $user->fetch_clicktodial();
3874 }
3875
3876 // Define urlmask
3877 $urlmask = getDolGlobalString('CLICKTODIAL_URL', 'ErrorClickToDialModuleNotConfigured');
3878 if (!empty($user->clicktodial_url)) {
3879 $urlmask = $user->clicktodial_url;
3880 }
3881
3882 $clicktodial_poste = (!empty($user->clicktodial_poste) ? urlencode($user->clicktodial_poste) : '');
3883 $clicktodial_login = (!empty($user->clicktodial_login) ? urlencode($user->clicktodial_login) : '');
3884 $clicktodial_password = (!empty($user->clicktodial_password) ? urlencode($user->clicktodial_password) : '');
3885 // This line is for backward compatibility @phan-suppress-next-line PhanPluginPrintfVariableFormatString
3886 $url = sprintf($urlmask, urlencode($phone), $clicktodial_poste, $clicktodial_login, $clicktodial_password);
3887 // Those lines are for substitution
3888 $substitarray = array(
3889 '__PHONEFROM__' => $clicktodial_poste,
3890 '__PHONETO__' => urlencode($phone),
3891 '__LOGIN__' => $clicktodial_login,
3892 '__PASS__' => $clicktodial_password
3893 );
3894 $url = make_substitutions($url, $substitarray);
3895 if (!getDolGlobalString('CLICKTODIAL_DO_NOT_USE_AJAX_CALL')) {
3896 // Default and recommended: New method using ajax without submitting a page making a javascript history.go(-1) back
3897 $newphoneastart = '<a href="' . $url . '" class="cssforclicktodial">'; // Call of ajax is handled by the lib_foot.js.php on class 'cssforclicktodial'
3898 $newphoneaend = '</a>';
3899 } else {
3900 // Old method
3901 $newphoneastart = '<a href="' . $url . '"';
3902 if (getDolGlobalString('CLICKTODIAL_FORCENEWTARGET')) {
3903 $newphoneastart .= ' target="_blank" rel="noopener noreferrer"';
3904 }
3905 $newphoneastart .= '>';
3906 $newphoneaend .= '</a>';
3907 }
3908 }
3909 //if (($contactid || $socid) && isModEnabled('agenda') && $user->hasRight('agenda', 'myactions', 'create'))
3910 if (isModEnabled('agenda') && $user->hasRight("agenda", "myactions", "create")) {
3911 $type = 'AC_TEL';
3912 $addlinktoagenda = '';
3913 if ($addlink == 'AC_FAX') {
3914 $type = 'AC_FAX';
3915 }
3916 if (getDolGlobalString('AGENDA_ADDACTIONFORPHONE')) {
3917 $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>';
3918 }
3919 if ($addlinktoagenda) {
3920 $newphone = '<span>' . $newphone . ' ' . $addlinktoagenda . '</span>';
3921 }
3922 }
3923 }
3924
3925 if (getDolGlobalString('CONTACT_PHONEMOBILE_SHOW_LINK_TO_WHATSAPP') && $withpicto == 'mobile') {
3926 // Link to Whatsapp
3927 $newphone .= ' <a href="https://wa.me/' . $newphonewa . '" target="_blank"'; // Use api to whatasapp contacts
3928 $newphone .= '><span class="paddingright fab fa-whatsapp" style="color:#25D366;" title="WhatsApp"></span></a>';
3929 }
3930
3931 if (empty($titlealt)) {
3932 $titlealt = ($withpicto == 'fax' ? $langs->trans("Fax") : $langs->trans("Phone"));
3933 }
3934 $rep = '';
3935
3936 if ($hookmanager) {
3937 $parameters = array('countrycode' => $countrycode, 'cid' => $contactid, 'socid' => $socid, 'titlealt' => $titlealt, 'picto' => $withpicto);
3938 $reshook = $hookmanager->executeHooks('printPhone', $parameters, $phone);
3939 $rep .= $hookmanager->resPrint;
3940 }
3941 if (empty($reshook)) {
3942 $picto = '';
3943 if ($withpicto) {
3944 if ($withpicto == 'fax') {
3945 $picto = 'phoning_fax';
3946 } elseif ($withpicto == 'phone') {
3947 $picto = 'phone';
3948 } elseif ($withpicto == 'mobile') {
3949 $picto = 'phoning_mobile';
3950 } else {
3951 $picto = '';
3952 }
3953 }
3954 if ($adddivfloat == 1) {
3955 $rep .= '<div class="nospan float' . ($morecss ? ' ' . $morecss : '') . '">';
3956 } elseif (empty($adddivfloat)) {
3957 $rep .= '<span' . ($morecss ? ' class="' . $morecss . '"' : '') . '>';
3958 }
3959
3960 $rep .= $newphoneastart;
3961 $rep .= ($withpicto ? img_picto($titlealt, $picto) : '');
3962 if ($separ != 'hidenum') {
3963 $rep .= ($withpicto ? ' ' : '') . $newphone;
3964 }
3965 $rep .= $newphoneaend;
3966
3967 if ($adddivfloat == 1) {
3968 $rep .= '</div>';
3969 } elseif (empty($adddivfloat)) {
3970 $rep .= '</span>';
3971 }
3972 }
3973
3974 return $rep;
3975}
3976
3985function dol_print_ip($ip, $mode = 0, $showname = 0)
3986{
3987 global $conf;
3988
3989 $ret = '';
3990 if (!isset($conf->cache['resolveips'])) {
3991 $conf->cache['resolveips'] = array();
3992 }
3993
3994 if ($mode != 2) {
3995 $countrycode = dolGetCountryCodeFromIp($ip);
3996 if ($countrycode) { // If success, countrycode is us, fr, ...
3997 if (file_exists(DOL_DOCUMENT_ROOT . '/theme/common/flags/' . $countrycode . '.png')) {
3998 $ret .= picto_from_langcode($countrycode);
3999 } else {
4000 $ret .= '(' . $countrycode . ')';
4001 }
4002 $ret .= '&nbsp;';
4003 } else {
4004 // Nothing
4005 }
4006 }
4007
4008 if (in_array($mode, [0, 2])) {
4009 $domain = '';
4010 if ($showname) {
4011 if (!array_key_exists($ip, $conf->cache['resolveips'])) {
4012 $domain = gethostbyaddr($ip);
4013 $conf->cache['resolveips'][$ip] = $domain; // false or domain
4014 } else {
4015 $domain = $conf->cache['resolveips'][$ip];
4016 }
4017 }
4018 if ($domain) {
4019 $ret .= $domain;
4020 } else {
4021 $ret .= $ip;
4022 }
4023 }
4024
4025 return $ret;
4026}
4027
4040function getUserRemoteIP($trusted = 0)
4041{
4042 if ($trusted) { // Return only IP we can rely on (not spoofable by the client)
4043 $ip = (empty($_SERVER['REMOTE_ADDR']) ? '' : $_SERVER['REMOTE_ADDR']); // value may be the IP of a proxy
4044 // 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)
4045 // This can happen if the proxy were added in the list of trusted proxy.
4046 return $ip;
4047 }
4048
4049 // Try to guess the real IP of client (but this may not be reliable)
4050 if (empty($_SERVER['HTTP_X_FORWARDED_FOR']) || preg_match('/[^0-9\.\:,\[\]\s]/', $_SERVER['HTTP_X_FORWARDED_FOR'])) {
4051 if (empty($_SERVER['HTTP_CLIENT_IP']) || preg_match('/[^0-9\.\:,\[\]\s]/', $_SERVER['HTTP_CLIENT_IP'])) {
4052 if (empty($_SERVER["HTTP_CF_CONNECTING_IP"])) {
4053 $ip = (empty($_SERVER['REMOTE_ADDR']) ? '' : $_SERVER['REMOTE_ADDR']); // value may be the IP of the proxy and not the client
4054 } else {
4055 $ip = $_SERVER["HTTP_CF_CONNECTING_IP"]; // value here may have been forged by client
4056 }
4057 } else {
4058 $ip = preg_replace('/,.*$/', '', $_SERVER['HTTP_CLIENT_IP']); // value is clean here but may have been forged by proxy
4059 }
4060 } else {
4061 $ip = preg_replace('/,.*$/', '', $_SERVER['HTTP_X_FORWARDED_FOR']); // value is clean here but may have been forged by proxy
4062 }
4063 return $ip;
4064}
4065
4072function dolGetCountryCodeFromIp($ip)
4073{
4074 $countrycode = '';
4075
4076 if (isModEnabled('geoipmaxmind')) {
4077 if (getDolGlobalString('GEOIP_VERSION') == 'php') {
4078 $datafile = getDolGlobalString('GEOIPMAXMIND_COUNTRY_DATAFILE');
4079 } else {
4080 $diroffile = getMultidirOutput(null, 'geoipmaxmind');
4081 $datafile = $diroffile . '/' . getDolGlobalString('GEOIPMAXMIND_COUNTRY_DATAFILE_EMBEDDED');
4082 }
4083 //$ip='24.24.24.24';
4084 //$datafile='/usr/share/GeoIP/GeoIP.dat'; Note that this must be downloaded datafile (not same than datafile provided with ubuntu packages)
4085 if ($datafile) {
4086 try {
4087 include_once DOL_DOCUMENT_ROOT . '/core/class/dolgeoip.class.php';
4088 $geoip = new DolGeoIP('country', $datafile);
4089 //print 'ip='.$ip.' databaseType='.$geoip->gi->databaseType." GEOIP_CITY_EDITION_REV1=".GEOIP_CITY_EDITION_REV1."\n";
4090 $countrycode = $geoip->getCountryCodeFromIP($ip);
4091 } catch (Exception $e) {
4092 //print 'Error with GeoIP database: '.$e->getMessage();
4093 }
4094 }
4095 }
4096
4097 return $countrycode;
4098}
4099
4100
4107function dol_user_country()
4108{
4109 //$ret=$user->xxx;
4110 $ret = '';
4111 if (isModEnabled('geoipmaxmind')) {
4112 $ip = getUserRemoteIP();
4113 $datafile = getDolGlobalString('GEOIPMAXMIND_COUNTRY_DATAFILE');
4114 //$ip='24.24.24.24';
4115 //$datafile='E:\Mes Sites\Web\Admin1\awstats\maxmind\GeoIP.dat';
4116 include_once DOL_DOCUMENT_ROOT . '/core/class/dolgeoip.class.php';
4117 $geoip = new DolGeoIP('country', $datafile);
4118 $countrycode = $geoip->getCountryCodeFromIP($ip);
4119 $ret = $countrycode;
4120 }
4121 return $ret;
4122}
4123
4136function dol_print_address($address, $htmlid, $element, $id, $noprint = 0, $charfornl = '')
4137{
4138 global $hookmanager;
4139
4140 $out = '';
4141
4142 if ($address) {
4143 if ($hookmanager) {
4144 $parameters = array('element' => $element, 'id' => $id);
4145 $reshook = $hookmanager->executeHooks('printAddress', $parameters, $address);
4146 $out .= $hookmanager->resPrint;
4147 }
4148 if (empty($reshook)) {
4149 if (empty($charfornl)) {
4150 $out .= nl2br((string) $address);
4151 } else {
4152 $out .= preg_replace('/[\r\n]+/', $charfornl, (string) $address);
4153 }
4154
4155 // TODO Remove this block, we can add this using the hook now
4156 $showgmap = $showomap = 0;
4157 if (($element == 'thirdparty' || $element == 'societe') && isModEnabled('google') && getDolGlobalString('GOOGLE_ENABLE_GMAPS')) {
4158 $showgmap = 1;
4159 }
4160 if ($element == 'contact' && isModEnabled('google') && getDolGlobalString('GOOGLE_ENABLE_GMAPS_CONTACTS')) {
4161 $showgmap = 1;
4162 }
4163 if ($element == 'member' && isModEnabled('google') && getDolGlobalString('GOOGLE_ENABLE_GMAPS_MEMBERS')) {
4164 $showgmap = 1;
4165 }
4166 if ($element == 'user' && isModEnabled('google') && getDolGlobalString('GOOGLE_ENABLE_GMAPS_USERS')) {
4167 $showgmap = 1;
4168 }
4169 if (($element == 'thirdparty' || $element == 'societe') && isModEnabled('openstreetmap') && getDolGlobalString('OPENSTREETMAP_ENABLE_MAPS')) {
4170 $showomap = 1;
4171 }
4172 if ($element == 'contact' && isModEnabled('openstreetmap') && getDolGlobalString('OPENSTREETMAP_ENABLE_MAPS_CONTACTS')) {
4173 $showomap = 1;
4174 }
4175 if ($element == 'member' && isModEnabled('openstreetmap') && getDolGlobalString('OPENSTREETMAP_ENABLE_MAPS_MEMBERS')) {
4176 $showomap = 1;
4177 }
4178 if ($element == 'user' && isModEnabled('openstreetmap') && getDolGlobalString('OPENSTREETMAP_ENABLE_MAPS_USERS')) {
4179 $showomap = 1;
4180 }
4181 if ($showgmap) {
4182 $url = dol_buildpath('/google/gmaps.php?mode=' . $element . '&id=' . $id, 1);
4183 $out .= ' <a href="' . $url . '" target="_gmaps"><img id="' . $htmlid . '" class="valigntextbottom" src="' . DOL_URL_ROOT . '/theme/common/gmap.png"></a>';
4184 }
4185 if ($showomap) {
4186 $url = dol_buildpath('/openstreetmap/maps.php?mode=' . $element . '&id=' . $id, 1);
4187 $out .= ' <a href="' . $url . '" target="_gmaps"><img id="' . $htmlid . '_openstreetmap" class="valigntextbottom" src="' . DOL_URL_ROOT . '/theme/common/gmap.png"></a>';
4188 }
4189 }
4190 }
4191 if ($noprint) {
4192 return $out;
4193 } else {
4194 print $out;
4195 return null;
4196 }
4197}
4198
4199
4209function isValidEmail($address, $acceptsupervisorkey = 0, $acceptuserkey = 0)
4210{
4211 if ($acceptsupervisorkey && $address == '__SUPERVISOREMAIL__') {
4212 return true;
4213 }
4214 if ($acceptuserkey && $address == '__USER_EMAIL__') {
4215 return true;
4216 }
4217 if (filter_var($address, FILTER_VALIDATE_EMAIL)) {
4218 return true;
4219 }
4220
4221 return false;
4222}
4223
4233function isValidMXRecord($domain)
4234{
4235 if (function_exists('idn_to_ascii') && function_exists('checkdnsrr')) {
4236 if (!checkdnsrr(idn_to_ascii($domain), 'MX')) {
4237 return 0;
4238 }
4239 if (function_exists('getmxrr')) {
4240 $mxhosts = array();
4241 $weight = array();
4242 getmxrr(idn_to_ascii($domain), $mxhosts, $weight);
4243 if (count($mxhosts) > 1) {
4244 return 1;
4245 }
4246 if (count($mxhosts) == 1 && !in_array((string) $mxhosts[0], array('', '.'))) {
4247 return 1;
4248 }
4249
4250 return 0;
4251 }
4252 }
4253
4254 // function idn_to_ascii or checkdnsrr or getmxrr does not exists
4255 return -1;
4256}
4257
4265function isValidPhone($phone)
4266{
4267 return true;
4268}
4269
4270
4280function dolGetFirstLetters($s, $nbofchar = 1)
4281{
4282 $ret = '';
4283 $tmparray = explode(' ', $s);
4284 foreach ($tmparray as $tmps) {
4285 $ret .= dol_substr($tmps, 0, $nbofchar);
4286 }
4287
4288 return $ret;
4289}
4290
4291
4299function dol_strlen($string, $stringencoding = 'UTF-8')
4300{
4301 if (is_null($string)) {
4302 return 0;
4303 }
4304
4305 if (function_exists('mb_strlen')) {
4306 return mb_strlen($string, $stringencoding);
4307 } else {
4308 return strlen($string);
4309 }
4310}
4311
4322function dol_substr($string, $start, $length = null, $stringencoding = '', $trunconbytes = 0)
4323{
4324 global $langs;
4325
4326 if (empty($stringencoding)) {
4327 $stringencoding = (empty($langs) ? 'UTF-8' : $langs->charset_output);
4328 }
4329
4330 $ret = '';
4331 if (empty($trunconbytes)) {
4332 if (function_exists('mb_substr')) {
4333 $ret = mb_substr($string, $start, $length, $stringencoding);
4334 } else {
4335 $ret = substr($string, $start, $length);
4336 }
4337 } else {
4338 if (function_exists('mb_strcut')) {
4339 $ret = mb_strcut($string, $start, $length, $stringencoding);
4340 } else {
4341 $ret = substr($string, $start, $length);
4342 }
4343 }
4344 return $ret;
4345}
4346
4347
4361function dol_trunc($string, $size = 40, $trunc = 'right', $stringencoding = 'UTF-8', $nodot = 0, $display = 0)
4362{
4363 global $conf;
4364
4365 if (empty($size) || getDolGlobalString('MAIN_DISABLE_TRUNC')) {
4366 return $string;
4367 }
4368
4369 if (empty($stringencoding)) {
4370 $stringencoding = 'UTF-8';
4371 }
4372 // reduce for small screen
4373 if (!empty($conf->dol_optimize_smallscreen) && $conf->dol_optimize_smallscreen == 1 && $display == 1) {
4374 $size = round($size / 3);
4375 }
4376
4377 // We go always here
4378 if ($trunc == 'right') {
4379 $newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
4380 if (dol_strlen($newstring, $stringencoding) > ($size + ($nodot ? 0 : 1))) {
4381 // If nodot is 0 and size is 1 chars more, we don't trunc and don't add '...'
4382 return dol_substr($newstring, 0, $size, $stringencoding) . ($nodot ? '' : '…');
4383 } else {
4384 //return 'u'.$size.'-'.$newstring.'-'.dol_strlen($newstring,$stringencoding).'-'.$string;
4385 return $string;
4386 }
4387 } elseif ($trunc == 'middle') {
4388 $newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
4389 if (dol_strlen($newstring, $stringencoding) > 2 && dol_strlen($newstring, $stringencoding) > ($size + 1)) {
4390 $size1 = (int) round($size / 2);
4391 $size2 = (int) round($size / 2);
4392 return dol_substr($newstring, 0, $size1, $stringencoding) . '…' . dol_substr($newstring, dol_strlen($newstring, $stringencoding) - $size2, $size2, $stringencoding);
4393 } else {
4394 return $string;
4395 }
4396 } elseif ($trunc == 'left') {
4397 $newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
4398 if (dol_strlen($newstring, $stringencoding) > ($size + ($nodot ? 0 : 1))) {
4399 // If nodot is 0 and size is 1 chars more, we don't trunc and don't add '...'
4400 return '…' . dol_substr($newstring, dol_strlen($newstring, $stringencoding) - $size, $size, $stringencoding);
4401 } else {
4402 return $string;
4403 }
4404 } elseif ($trunc == 'wrap') {
4405 $newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
4406 if (dol_strlen($newstring, $stringencoding) > ($size + 1)) {
4407 return dol_substr($newstring, 0, $size, $stringencoding) . "\n" . dol_trunc(dol_substr($newstring, $size, dol_strlen($newstring, $stringencoding) - $size, $stringencoding), $size, $trunc);
4408 } else {
4409 return $string;
4410 }
4411 } else {
4412 return 'BadParam3CallingDolTrunc';
4413 }
4414}
4415
4416
4428function vatrate($rate, $addpercent = false, $info_bits = 0, $usestarfornpr = 0, $html = 0)
4429{
4430 $morelabel = '';
4431
4432 if (preg_match('/%/', $rate)) {
4433 $rate = str_replace('%', '', $rate);
4434 $addpercent = true;
4435 }
4436 $reg = array();
4437 if (preg_match('/\‍((.*)\‍)/', $rate, $reg)) {
4438 $morelabel = ' (' . $reg[1] . ')';
4439 $rate = preg_replace('/\s*' . preg_quote($morelabel, '/') . '/', '', $rate);
4440 $morelabel = ' ' . ($html ? '<span class="opacitymedium small">' : '') . '(' . $reg[1] . ')' . ($html ? '</span>' : '');
4441 }
4442 if (preg_match('/\*/', $rate)) {
4443 $rate = str_replace('*', '', $rate);
4444 $info_bits |= 1;
4445 }
4446
4447 // If rate is '9/9/9' we don't change it. If rate is '9.000' we apply price()
4448 if (!preg_match('/\//', $rate)) {
4449 $ret = price($rate, 0, '', 0, 0) . ($addpercent ? '%' : '');
4450 } else {
4451 // TODO Split on / and output with a price2num to have clean numbers without ton of 000.
4452 $ret = $rate . ($addpercent ? '%' : '');
4453 }
4454 if (($info_bits & 1) && $usestarfornpr >= 0) {
4455 $ret .= ' *';
4456 }
4457 $ret .= $morelabel;
4458 return $ret;
4459}
4460
4461
4476function price($amount, $form = 0, $outlangs = '', $trunc = 1, $rounding = -1, $forcerounding = -1, $currency_code = '')
4477{
4478 global $langs, $conf;
4479
4480 // Clean parameters
4481 if (empty($amount)) {
4482 $amount = 0; // To have a numeric value if amount not defined or = ''
4483 }
4484 $amount = (is_numeric($amount) ? $amount : 0); // Check if amount is numeric, for example, an error occurred when amount value = o (letter) instead 0 (number)
4485 if ($rounding == -1) {
4486 $rounding = min(getDolGlobalString('MAIN_MAX_DECIMALS_UNIT'), getDolGlobalString('MAIN_MAX_DECIMALS_TOT'));
4487 }
4488 $nbdecimal = $rounding;
4489
4490 if ($outlangs === 'none') {
4491 // Use international separators
4492 $dec = '.';
4493 $thousand = '';
4494 } else {
4495 // Output separators by default (french)
4496 $dec = ',';
4497 $thousand = ' ';
4498
4499 // If $outlangs not forced, we use use language
4500 if (!($outlangs instanceof Translate)) {
4501 $outlangs = $langs;
4502 }
4503
4504 if ($outlangs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal") {
4505 $dec = $outlangs->transnoentitiesnoconv("SeparatorDecimal");
4506 }
4507 if ($outlangs->transnoentitiesnoconv("SeparatorThousand") != "SeparatorThousand") {
4508 $thousand = $outlangs->transnoentitiesnoconv("SeparatorThousand");
4509 }
4510 if ($thousand == 'None') {
4511 $thousand = '';
4512 } elseif ($thousand == 'Space') {
4513 $thousand = ' ';
4514 }
4515 }
4516 //print "outlangs=".$outlangs->defaultlang." amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
4517
4518 //print "amount=".$amount."-";
4519 $amount = str_replace(',', '.', $amount); // should be useless
4520 //print $amount."-";
4521 $data = explode('.', $amount);
4522 $decpart = isset($data[1]) ? $data[1] : '';
4523 $decpart = preg_replace('/0+$/i', '', $decpart); // Remove 0 at end of decimal part
4524 //print "decpart=".$decpart."<br>";
4525 $end = '';
4526
4527 // We increase nbdecimal if there is more decimal than asked (to not loose information)
4528 if (dol_strlen($decpart) > $nbdecimal) {
4529 $nbdecimal = dol_strlen($decpart);
4530 }
4531
4532 // If nbdecimal is higher than max to show
4533 $nbdecimalmaxshown = (int) str_replace('...', '', getDolGlobalString('MAIN_MAX_DECIMALS_SHOWN'));
4534 if ($trunc && $nbdecimal > $nbdecimalmaxshown) {
4535 $nbdecimal = $nbdecimalmaxshown;
4536 if (preg_match('/\.\.\./i', getDolGlobalString('MAIN_MAX_DECIMALS_SHOWN'))) {
4537 // If output is truncated, we show ...
4538 $end = '...';
4539 }
4540 }
4541
4542 // If force rounding
4543 if ((string) $forcerounding != '-1' && (string) $forcerounding != '') {
4544 if ($forcerounding === 'MU') {
4545 $nbdecimal = getDolGlobalInt('MAIN_MAX_DECIMALS_UNIT');
4546 } elseif ($forcerounding === 'MT') {
4547 $nbdecimal = getDolGlobalInt('MAIN_MAX_DECIMALS_TOT');
4548 } elseif ($forcerounding >= 0) {
4549 $nbdecimal = (int) $forcerounding;
4550 }
4551 }
4552
4553 // Format number
4554 $output = number_format((float) $amount, $nbdecimal, $dec, $thousand);
4555 // Add symbol of currency if requested
4556 $cursymbolbefore = $cursymbolafter = '';
4557 if ($currency_code && is_object($outlangs)) {
4558 if ($currency_code == 'auto') {
4559 $currency_code = $conf->currency;
4560 }
4561
4562 $listofcurrenciesbefore = array('AUD', 'CAD', 'CNY', 'COP', 'CLP', 'GBP', 'HKD', 'MXN', 'PEN', 'USD', 'CRC', 'ZAR');
4563 $listoflanguagesbefore = array('nl_NL');
4564 if (in_array($currency_code, $listofcurrenciesbefore) || in_array($outlangs->defaultlang, $listoflanguagesbefore)) {
4565 $cursymbolbefore .= $outlangs->getCurrencySymbol($currency_code);
4566 } else {
4567 $tmpcur = $outlangs->getCurrencySymbol($currency_code);
4568 $cursymbolafter .= ($tmpcur == $currency_code ? ' ' . $tmpcur : $tmpcur);
4569 }
4570 }
4571 if ($form) {
4572 $output = preg_replace('/\s/', '&nbsp;', $output);
4573 $output = $cursymbolbefore . $output . $end . ($cursymbolafter ? ' <span class="small">'.$cursymbolafter.'</span>' : '');
4574 $output = preg_replace('/\'/', '&#039;', $output);
4575 } else {
4576 $output = $cursymbolbefore . $output . $end . ($cursymbolafter ? ' '.$cursymbolafter : '');
4577 }
4578
4579 return $output;
4580}
4581
4607function price2num($amount, $rounding = '', $option = 0)
4608{
4609 global $langs;
4610
4611 // Clean parameters
4612 if (is_null($amount)) {
4613 $amount = '';
4614 }
4615
4616 // Round PHP function does not allow number like '1,234.56' nor '1.234,56' nor '1 234,56'
4617 // Numbers must be '1234.56'
4618 // Decimal delimiter for PHP and database SQL requests must be '.'
4619 $dec = ',';
4620 $thousand = ' ';
4621 if (is_null($langs)) { // $langs is not defined, we use english values.
4622 $dec = '.';
4623 $thousand = ',';
4624 } else {
4625 if ($langs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal") {
4626 $dec = $langs->transnoentitiesnoconv("SeparatorDecimal");
4627 }
4628 if ($langs->transnoentitiesnoconv("SeparatorThousand") != "SeparatorThousand") {
4629 $thousand = $langs->transnoentitiesnoconv("SeparatorThousand");
4630 }
4631 }
4632 if ($thousand == 'None') {
4633 $thousand = '';
4634 } elseif ($thousand == 'Space') {
4635 $thousand = ' ';
4636 }
4637 //print "amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
4638
4639 // Convert value to universal number format (no thousand separator, '.' as decimal separator)
4640 if ($option != 1) { // If not a PHP number or unknown, we change or clean format
4641 //print "\n".'PP'.$amount.' - '.$dec.' - '.$thousand.' - '.intval($amount).'<br>';
4642 if (!is_numeric($amount)) {
4643 $amount = preg_replace('/[a-zA-Z\/\\\*\‍(\‍)<>\_]/', '', $amount);
4644 }
4645
4646 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
4647 $amount = str_replace($thousand, '', $amount);
4648 }
4649
4650 // Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
4651 // to format defined by LC_NUMERIC after a calculation and we want source format to be like defined by Dolibarr setup.
4652 // So if number was already a good number, it is converted into local Dolibarr setup.
4653 if (is_numeric($amount)) {
4654 // We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
4655 $temps = sprintf("%10.10F", $amount - intval($amount)); // temps=0.0000000000 or 0.0000200000 or 9999.1000000000
4656 $temps = preg_replace('/([\.1-9])0+$/', '\\1', $temps); // temps=0. or 0.00002 or 9999.1
4657 $nbofdec = max(0, dol_strlen($temps) - 2); // -2 to remove "0."
4658 $amount = number_format($amount, $nbofdec, $dec, $thousand);
4659 }
4660 //print "QQ".$amount."<br>\n";
4661
4662 // Now make replaceents (the main goal of function)
4663
4664 if ($thousand != ',' && $thousand != '.') {
4665 // Accept the two types of decimal points french users (i.e., using ' ' for thousands)
4666
4667 // REGEX: Find the integral and decimal parts.
4668 //
4669 // We require that the decimal point only appears once in $amount.
4670 // The regex `/^(?<int>[^,]*,|[^.]*\.)(?<dec>[^.,]*)$/u` can be broken down as follows:
4671 // - `(?<int>[^,]*,|[^.]*\.)` is any accepted sequence up to the last potential decimal point '.' or ',' and named `int`.
4672 // It covers two cases:
4673 // - `[^,]*,`: Any sequence of characters that is not ',' with ',' accepted as the decimal point (from start of string because of earlier `^`);
4674 // - `[^.]*\.`: Any sequence of characters that is not a '.' with '.' accepted as the decimal point (from start of string.
4675 // - `(?<dec>[^.,]*)`: The sequence after the character accepted as the decimal point, not including it.
4676 $matches = array();
4677 if (preg_match('/^(?<int>[^,]*,|[^.]*\.)(?<dec>[^.,]*)$/u', $amount, $matches)) {
4678 $intPart = $matches['int'];
4679 $decPart = $matches['dec'];
4680
4681 // Remove all commas and dots from intPart
4682 $intPart = str_replace(['.', ','], '', $intPart);
4683
4684 // Combine intPart and decPart with a dot
4685 $amount = $intPart . $dec . $decPart;
4686 }
4687 }
4688
4689 $amount = str_replace(' ', '', $amount); // To avoid spaces
4690 $amount = str_replace($thousand, '', $amount); // Replace of thousand before replace of dec to avoid pb if thousand is .
4691 $amount = str_replace($dec, '.', $amount);
4692
4693 $amount = preg_replace('/[^0-9\-\.]/', '', $amount); // Clean non numeric chars (so it clean some UTF8 spaces for example.
4694 }
4695 //print ' XX'.$amount.' '.$rounding;
4696
4697 // Now, $amount is a real PHP float number. We make a rounding if required.
4698 if ($rounding) {
4699 $nbofdectoround = '';
4700 if ($rounding == 'MU') {
4701 $nbofdectoround = getDolGlobalInt('MAIN_MAX_DECIMALS_UNIT'); // usually 5
4702 } elseif ($rounding == 'MT') {
4703 $nbofdectoround = getDolGlobalInt('MAIN_MAX_DECIMALS_TOT'); // usually 2 or 3
4704 } elseif ($rounding == 'MS') {
4705 $nbofdectoround = getDolGlobalInt('MAIN_MAX_DECIMALS_STOCK', 5);
4706 } elseif ($rounding == 'CU') {
4707 $nbofdectoround = getDolGlobalInt('MAIN_MAX_DECIMALS_CURRENCY_UNIT', getDolGlobalInt('MAIN_MAX_DECIMALS_UNIT')); // TODO Use param of currency
4708 } elseif ($rounding == 'CT') {
4709 $nbofdectoround = getDolGlobalInt('MAIN_MAX_DECIMALS_CURRENCY_TOT', getDolGlobalInt('MAIN_MAX_DECIMALS_TOT')); // TODO Use param of currency
4710 } elseif (is_numeric($rounding)) {
4711 $nbofdectoround = (int) $rounding;
4712 }
4713
4714 //print " RR".$amount.' - '.$nbofdectoround.'<br>';
4715 if (dol_strlen($nbofdectoround)) {
4716 $amount = round(is_string($amount) ? (float) $amount : $amount, $nbofdectoround); // $nbofdectoround can be 0.
4717 } else {
4718 return 'ErrorBadParameterProvidedToFunction';
4719 }
4720 //print ' SS'.$amount.' - '.$nbofdec.' - '.$dec.' - '.$thousand.' - '.$nbofdectoround.'<br>';
4721
4722 // Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
4723 // to format defined by LC_NUMERIC after a calculation and we want source format to be defined by Dolibarr setup.
4724 if (is_numeric($amount)) {
4725 // We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
4726 $temps = sprintf("%10.10F", $amount - intval($amount)); // temps=0.0000000000 or 0.0000200000 or 9999.1000000000
4727 $temps = preg_replace('/([\.1-9])0+$/', '\\1', $temps); // temps=0. or 0.00002 or 9999.1
4728 $nbofdec = max(0, dol_strlen($temps) - 2); // -2 to remove "0."
4729 $amount = number_format($amount, min($nbofdec, $nbofdectoround), $dec, $thousand); // Convert amount to format with dolibarr dec and thousand
4730 }
4731 //print "TT".$amount.'<br>';
4732
4733 // Always make replace because each math function (like round) replace
4734 // with local values and we want a number that has a SQL string format x.y
4735 if ($thousand != ',' && $thousand != '.') {
4736 $amount = str_replace(',', '.', $amount); // To accept 2 notations for french users
4737 }
4738
4739 $amount = str_replace(' ', '', $amount); // To avoid spaces
4740 $amount = str_replace($thousand, '', $amount); // Replace of thousand before replace of dec to avoid pb if thousand is .
4741 $amount = str_replace($dec, '.', $amount);
4742
4743 $amount = preg_replace('/[^0-9\-\.]/', '', $amount); // Clean non numeric chars (so it clean some UTF8 spaces for example.
4744 }
4745
4746 return $amount;
4747}
4748
4749
4762function get_localtax($vatrate, $local, $thirdparty_buyer = null, $thirdparty_seller = null, $vatnpr = 0)
4763{
4764 global $db, $conf, $mysoc;
4765
4766 if (empty($thirdparty_seller) || !is_object($thirdparty_seller)) {
4767 $thirdparty_seller = $mysoc;
4768 }
4769
4770 dol_syslog(
4771 "get_localtax tva=" . $vatrate . " local=" . $local
4772 ." thirdparty_buyer id=" . (is_object($thirdparty_buyer) ? $thirdparty_buyer->id : '') . "/country_code=" . (is_object($thirdparty_buyer) ? $thirdparty_buyer->country_code : '')
4773 ." thirdparty_seller id=" . $thirdparty_seller->id . "/country_code=" . $thirdparty_seller->country_code
4774 ." thirdparty_seller localtax1_assuj=" . $thirdparty_seller->localtax1_assuj . " thirdparty_seller localtax2_assuj=" . $thirdparty_seller->localtax2_assuj
4775 );
4776
4777 $vatratecleaned = $vatrate;
4778 $reg = array();
4779 if (preg_match('/^(.*)\s*\‍((.*)\‍)$/', (string) $vatrate, $reg)) { // If vat is "xx (yy)"
4780 $vatratecleaned = trim($reg[1]);
4781 $vatratecode = $reg[2];
4782 }
4783
4784 /*if ($thirdparty_buyer->country_code != $thirdparty_seller->country_code)
4785 {
4786 return 0;
4787 }*/
4788
4789 // Some test to guess with no need to make database access
4790 if ($mysoc->country_code == 'ES') { // For spain localtaxes 1 and 2, tax is qualified if buyer use local tax
4791 if ($local == 1) {
4792 if (!$mysoc->localtax1_assuj || (string) $vatratecleaned == "0") {
4793 return 0;
4794 }
4795 if ($thirdparty_seller->id == $mysoc->id) {
4796 if (!$thirdparty_buyer->localtax1_assuj) {
4797 return 0;
4798 }
4799 } else {
4800 if (!$thirdparty_seller->localtax1_assuj) {
4801 return 0;
4802 }
4803 }
4804 }
4805
4806 if ($local == 2) {
4807 //if (! $mysoc->localtax2_assuj || (string) $vatratecleaned == "0") return 0;
4808 if (!$mysoc->localtax2_assuj) {
4809 return 0; // If main vat is 0, IRPF may be different than 0.
4810 }
4811 if ($thirdparty_seller->id == $mysoc->id) {
4812 if (!$thirdparty_buyer->localtax2_assuj) {
4813 return 0;
4814 }
4815 } else {
4816 if (!$thirdparty_seller->localtax2_assuj) {
4817 return 0;
4818 }
4819 }
4820 }
4821 } else {
4822 if ($local == 1 && !$thirdparty_seller->localtax1_assuj) {
4823 return 0;
4824 }
4825 if ($local == 2 && !$thirdparty_seller->localtax2_assuj) {
4826 return 0;
4827 }
4828 }
4829
4830 // For some country MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY is forced to on.
4831 if (in_array($mysoc->country_code, array('ES'))) {
4832 $conf->global->MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY = 1;
4833 }
4834
4835 // Search local taxes
4836 if (getDolGlobalString('MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY')) {
4837 if ($local == 1) {
4838 if ($thirdparty_seller != $mysoc) {
4839 if (!isOnlyOneLocalTax($local)) { // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
4840 return $thirdparty_seller->localtax1_value;
4841 }
4842 } else { // i am the seller
4843 if (!isOnlyOneLocalTax($local)) { // TODO If seller is me, why not always returning this, even if there is only one locatax vat.
4844 return getDolGlobalString('MAIN_INFO_VALUE_LOCALTAX1');
4845 }
4846 }
4847 }
4848 if ($local == 2) {
4849 if ($thirdparty_seller != $mysoc) {
4850 if (!isOnlyOneLocalTax($local)) { // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
4851 // TODO We should also return value defined on thirdparty only if defined
4852 return $thirdparty_seller->localtax2_value;
4853 }
4854 } else { // i am the seller
4855 if (in_array($mysoc->country_code, array('ES'))) {
4856 return $thirdparty_buyer->localtax2_value;
4857 } else {
4858 return getDolGlobalString('MAIN_INFO_VALUE_LOCALTAX2');
4859 }
4860 }
4861 }
4862 }
4863
4864 // By default, search value of local tax on line of common tax
4865 $sql = "SELECT t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
4866 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t, " . MAIN_DB_PREFIX . "c_country as c";
4867 $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '" . $db->escape($thirdparty_seller->country_code) . "'";
4868 $sql .= " AND t.taux = " . ((float) $vatratecleaned) . " AND t.active = 1";
4869 $sql .= " AND t.entity IN (" . getEntity('c_tva') . ")";
4870 if (!empty($vatratecode)) {
4871 $sql .= " AND t.code ='" . $db->escape($vatratecode) . "'"; // If we have the code, we use it in priority
4872 } else {
4873 $sql .= " AND t.recuperableonly = '" . $db->escape((string) $vatnpr) . "'";
4874 }
4875
4876 $resql = $db->query($sql);
4877
4878 if ($resql) {
4879 $obj = $db->fetch_object($resql);
4880 if ($obj) {
4881 if ($local == 1) {
4882 return $obj->localtax1;
4883 } elseif ($local == 2) {
4884 return $obj->localtax2;
4885 }
4886 }
4887 }
4888
4889 return 0;
4890}
4891
4892
4901function isOnlyOneLocalTax($local)
4902{
4903 $tax = get_localtax_by_third($local);
4904
4905 $valors = explode(":", $tax);
4906
4907 if (count($valors) > 1) {
4908 return false;
4909 } else {
4910 return true;
4911 }
4912}
4913
4920function get_localtax_by_third($local)
4921{
4922 global $db, $mysoc;
4923
4924 $sql = " SELECT t.localtax" . ((int) $local) . " as localtax";
4925 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t INNER JOIN " . MAIN_DB_PREFIX . "c_country as c ON c.rowid = t.fk_pays";
4926 $sql .= " WHERE c.code = '" . $db->escape($mysoc->country_code) . "' AND t.active = 1 AND t.entity IN (" . getEntity('c_tva') . ") AND t.taux = (";
4927 $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";
4928 $sql .= " WHERE c.code = '" . $db->escape($mysoc->country_code) . "' AND t.entity IN (" . getEntity('c_tva') . ") AND tt.active = 1)";
4929 $sql .= " AND t.localtax" . ((int) $local) . "_type <> '0'";
4930 $sql .= " ORDER BY t.rowid DESC";
4931
4932 $resql = $db->query($sql);
4933 if ($resql) {
4934 $obj = $db->fetch_object($resql);
4935 if ($obj) {
4936 return $obj->localtax;
4937 } else {
4938 return '0';
4939 }
4940 }
4941
4942 return 'Error';
4943}
4944
4945
4957function getTaxesFromId($vatrate, $buyer = null, $seller = null, $firstparamisid = 1)
4958{
4959 global $db;
4960
4961 dol_syslog("getTaxesFromId vat id or rate = " . $vatrate);
4962
4963 // Search local taxes
4964 $sql = "SELECT t.rowid, t.code, t.taux as rate, t.recuperableonly as npr, t.accountancy_code_sell, t.accountancy_code_buy,";
4965 $sql .= " t.localtax1, t.localtax1_type, t.localtax2, t.localtax2_type";
4966 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t";
4967 if ($firstparamisid) {
4968 $sql .= " WHERE t.rowid = " . (int) $vatrate;
4969 } else {
4970 $vatratecleaned = $vatrate;
4971 $vatratecode = '';
4972 $reg = array();
4973 if (preg_match('/^(.*)\s*\‍((.*)\‍)$/', $vatrate, $reg)) { // If vat is "xx (yy)"
4974 $vatratecleaned = $reg[1];
4975 $vatratecode = $reg[2];
4976 }
4977
4978 $sql .= ", " . MAIN_DB_PREFIX . "c_country as c";
4979 /*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 ??
4980 else $sql.= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($seller->country_code)."'";*/
4981 $sql .= " WHERE t.fk_pays = c.rowid";
4982 if (getDolGlobalString('SERVICE_ARE_ECOMMERCE_200238EC')) {
4983 $sql .= " AND c.code = '" . $db->escape($buyer->country_code) . "'";
4984 } else {
4985 $sql .= " AND c.code = '" . $db->escape($seller->country_code) . "'";
4986 }
4987 $sql .= " AND t.taux = " . ((float) $vatratecleaned) . " AND t.active = 1";
4988 $sql .= " AND t.entity IN (" . getEntity('c_tva') . ")";
4989 if ($vatratecode) {
4990 $sql .= " AND t.code = '" . $db->escape($vatratecode) . "'";
4991 }
4992 }
4993
4994 $resql = $db->query($sql);
4995 if ($resql) {
4996 $obj = $db->fetch_object($resql);
4997 if ($obj) {
4998 return array(
4999 'rowid' => $obj->rowid,
5000 'code' => $obj->code,
5001 'rate' => $obj->rate,
5002 'localtax1' => $obj->localtax1,
5003 'localtax1_type' => $obj->localtax1_type,
5004 'localtax2' => $obj->localtax2,
5005 'localtax2_type' => $obj->localtax2_type,
5006 'npr' => $obj->npr,
5007 'accountancy_code_sell' => $obj->accountancy_code_sell,
5008 'accountancy_code_buy' => $obj->accountancy_code_buy
5009 );
5010 } else {
5011 return array();
5012 }
5013 } else {
5015 }
5016
5017 return array();
5018}
5019
5036function getLocalTaxesFromRate($vatrate, $local, $buyer, $seller, $firstparamisid = 0)
5037{
5038 global $db, $mysoc;
5039
5040 dol_syslog("getLocalTaxesFromRate vatrate=" . $vatrate . " local=" . $local);
5041
5042 // Search local taxes
5043 $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";
5044 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t";
5045 if ($firstparamisid) {
5046 $sql .= " WHERE t.rowid = " . (int) $vatrate;
5047 } else {
5048 $vatratecleaned = $vatrate;
5049 $vatratecode = '';
5050 $reg = array();
5051 if (preg_match('/^(.*)\s*\‍((.*)\‍)$/', $vatrate, $reg)) { // If vat is "x.x (yy)"
5052 $vatratecleaned = $reg[1];
5053 $vatratecode = $reg[2];
5054 }
5055
5056 $sql .= ", " . MAIN_DB_PREFIX . "c_country as c";
5057 if (!empty($mysoc) && $mysoc->country_code == 'ES') {
5058 $countrycodetouse = ((empty($buyer) || empty($buyer->country_code)) ? $mysoc->country_code : $buyer->country_code);
5059 $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '" . $db->escape($countrycodetouse) . "'"; // local tax in spain use the buyer country ??
5060 } else {
5061 $countrycodetouse = ((empty($seller) || empty($seller->country_code)) ? $mysoc->country_code : $seller->country_code);
5062 $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '" . $db->escape($countrycodetouse) . "'";
5063 }
5064 $sql .= " AND t.taux = " . ((float) $vatratecleaned) . " AND t.active = 1";
5065 if ($vatratecode) {
5066 $sql .= " AND t.code = '" . $db->escape($vatratecode) . "'";
5067 }
5068 }
5069
5070 $resql = $db->query($sql);
5071 if ($resql) {
5072 $obj = $db->fetch_object($resql);
5073
5074 if ($obj) {
5075 $vateratestring = $obj->rate . ($obj->code ? ' (' . $obj->code . ')' : '');
5076
5077 if ($local == 1) {
5078 return array($obj->localtax1_type, get_localtax($vateratestring, $local, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
5079 } elseif ($local == 2) {
5080 return array($obj->localtax2_type, get_localtax($vateratestring, $local, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
5081 } else {
5082 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);
5083 }
5084 }
5085 }
5086
5087 return array();
5088}
5089
5100function get_product_vat_for_country($idprod, $thirdpartytouseforcountry, $idprodfournprice = 0)
5101{
5102 global $db, $mysoc;
5103
5104 require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
5105
5106 $ret = 0;
5107 $found = 0;
5108
5109 if ($idprod > 0) {
5110 // Load product
5111 $product = new Product($db);
5112 $product->fetch($idprod);
5113
5114 if (($mysoc->country_code == $thirdpartytouseforcountry->country_code)
5115 || (in_array($mysoc->country_code, array('FR', 'MC')) && in_array($thirdpartytouseforcountry->country_code, array('FR', 'MC')))
5116 || (in_array($mysoc->country_code, array('MQ', 'GP')) && in_array($thirdpartytouseforcountry->country_code, array('MQ', 'GP')))
5117 ) {
5118 // If country of thirdparty to consider is ours
5119 if ($idprodfournprice > 0) { // We want vat for product for a "supplier" object
5120 $result = $product->get_buyprice($idprodfournprice, 0, 0, '');
5121 if ($result > 0) {
5122 $ret = $product->vatrate_supplier;
5123 if ($product->default_vat_code_supplier) {
5124 $ret .= ' (' . $product->default_vat_code_supplier . ')';
5125 }
5126 $found = 1;
5127 }
5128 }
5129 if (!$found) {
5130 $ret = $product->tva_tx; // Default sales vat of product
5131 if ($product->default_vat_code) {
5132 $ret .= ' (' . $product->default_vat_code . ')';
5133 }
5134 $found = 1;
5135 }
5136 } else {
5137 // TODO Read default product vat according to product and an other countrycode.
5138 // Vat for couple anothercountrycode/product is data that is not managed and store yet, so we will fallback on next rule.
5139 }
5140 }
5141
5142 if (!$found) {
5143 if (!getDolGlobalString('MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS')) {
5144 // 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).
5145 $sql = "SELECT t.taux as vat_rate, t.code as default_vat_code";
5146 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t, " . MAIN_DB_PREFIX . "c_country as c";
5147 $sql .= " WHERE t.active = 1 AND t.fk_pays = c.rowid AND c.code = '" . $db->escape($thirdpartytouseforcountry->country_code) . "'";
5148 $sql .= " AND t.entity IN (" . getEntity('c_tva') . ")";
5149 $sql .= " ORDER BY t.use_default DESC, t.taux DESC, t.code ASC, t.recuperableonly ASC";
5150 $sql .= $db->plimit(1);
5151
5152 $resql = $db->query($sql);
5153 if ($resql) {
5154 $obj = $db->fetch_object($resql);
5155 if ($obj) {
5156 $ret = $obj->vat_rate;
5157 if ($obj->default_vat_code) {
5158 $ret .= ' (' . $obj->default_vat_code . ')';
5159 }
5160 }
5161 $db->free($resql);
5162 } else {
5164 }
5165 } else {
5166 // Forced value if autodetect fails. MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS can be
5167 // '1.23'
5168 // or '1.23 (CODE)'
5169 $defaulttx = '';
5170 if (getDolGlobalString('MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS') != 'none') {
5171 $defaulttx = getDolGlobalString('MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS');
5172 }
5173 /*if (preg_match('/\‍((.*)\‍)/', $defaulttx, $reg)) {
5174 $defaultcode = $reg[1];
5175 $defaulttx = preg_replace('/\s*\‍(.*\‍)/', '', $defaulttx);
5176 }*/
5177
5178 $ret = $defaulttx;
5179 }
5180 }
5181
5182 dol_syslog("get_product_vat_for_country: ret=" . $ret);
5183
5184 return $ret;
5185}
5186
5196function get_product_localtax_for_country($idprod, $local, $thirdpartytouseforcountry)
5197{
5198 global $db, $mysoc;
5199
5200 if (!class_exists('Product')) {
5201 require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
5202 }
5203
5204 $ret = 0;
5205 $found = 0;
5206
5207 if ($idprod > 0) {
5208 // Load product
5209 $product = new Product($db);
5210 $result = $product->fetch($idprod);
5211
5212 if ($mysoc->country_code == $thirdpartytouseforcountry->country_code) { // If selling country is ours
5213 /* Not defined yet, so we don't use this
5214 if ($local==1) $ret=$product->localtax1_tx;
5215 elseif ($local==2) $ret=$product->localtax2_tx;
5216 $found=1;
5217 */
5218 } else {
5219 // TODO Read default product vat according to product and another countrycode.
5220 // Vat for couple anothercountrycode/product is data that is not managed and store yet, so we will fallback on next rule.
5221 }
5222 }
5223
5224 if (!$found) {
5225 // If vat of product for the country not found or not defined, we return higher vat of country.
5226 $sql = "SELECT taux as vat_rate, localtax1, localtax2";
5227 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t, " . MAIN_DB_PREFIX . "c_country as c";
5228 $sql .= " WHERE t.active=1 AND t.fk_pays = c.rowid AND c.code='" . $db->escape($thirdpartytouseforcountry->country_code) . "'";
5229 $sql .= " AND t.entity IN (" . getEntity('c_tva') . ")";
5230 $sql .= " ORDER BY t.taux DESC, t.recuperableonly ASC";
5231 $sql .= $db->plimit(1);
5232
5233 $resql = $db->query($sql);
5234 if ($resql) {
5235 $obj = $db->fetch_object($resql);
5236 if ($obj) {
5237 if ($local == 1) {
5238 $ret = $obj->localtax1;
5239 } elseif ($local == 2) {
5240 $ret = $obj->localtax2;
5241 }
5242 }
5243 } else {
5245 }
5246 }
5247
5248 dol_syslog("get_product_localtax_for_country: ret=" . $ret);
5249 return $ret;
5250}
5251
5270function get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0)
5271{
5272 global $mysoc, $db, $hookmanager;
5273
5274 require_once DOL_DOCUMENT_ROOT . '/core/lib/company.lib.php';
5275
5276 // Note: possible values for tva_assuj are 0/1 or franchise/reel
5277 $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;
5278
5279 if (empty($thirdparty_seller->country_code)) {
5280 $thirdparty_seller->country_code = $mysoc->country_code;
5281 }
5282 $seller_country_code = $thirdparty_seller->country_code;
5283 $seller_in_cee = isInEEC($thirdparty_seller);
5284
5285 if (empty($thirdparty_buyer->country_code)) {
5286 $thirdparty_buyer->country_code = $mysoc->country_code;
5287 }
5288 $buyer_country_code = $thirdparty_buyer->country_code;
5289 $buyer_in_cee = isInEEC($thirdparty_buyer);
5290
5291 dol_syslog(
5292 "get_default_tva: seller use vat=" . $seller_use_vat . ", seller country=" . $seller_country_code . ", seller in cee=" . ((string) (int) $seller_in_cee)
5293 .", 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)
5294 .", idprod=" . $idprod . ", idprodfournprice=" . $idprodfournprice . ", SERVICE_ARE_ECOMMERCE_200238EC=" . getDolGlobalString('SERVICE_ARE_ECOMMERCE_200238EC')
5295 );
5296
5297 $vatvalue = 0;
5298 $vatrule = '';
5299
5300 // 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)
5301 // we use the buyer VAT.
5302 if (getDolGlobalString('SERVICE_ARE_ECOMMERCE_200238EC')) {
5303 if ($seller_in_cee && $buyer_in_cee) {
5304 $isacompany = $thirdparty_buyer->isACompany();
5305 if ($isacompany && !getDolGlobalString('MAIN_USE_VAT_ZERO_FOR_COMPANIES_IN_EEC_EVEN_IF_VAT_ID_UNKNOWN')) {
5306 require_once DOL_DOCUMENT_ROOT . '/core/lib/functions2.lib.php';
5307 if (!isValidVATID($thirdparty_buyer)) {
5308 $isacompany = 0;
5309 }
5310 }
5311
5312 if (!$isacompany) {
5313 $vatvalue = get_product_vat_for_country($idprod, $thirdparty_buyer, $idprodfournprice);
5314 $vatrule = 'VATRULE 0';
5315 }
5316 }
5317 }
5318
5319 // If seller does not use VAT, default VAT is 0. End of rule.
5320 if (empty($vatrule) && !$seller_use_vat) {
5321 //print 'VATRULE 1';
5322 // TODO get the VAT Code of exemption asked into setup if country isInEEC (from an array list of possible
5323 // values like VATEX-EU-132-*, VATEX-FR-FRANCHISE, VATEX-EU-AE...
5324 // When we had recorded it, we also added a corresponding entry into table of vat code if it does not exists yet.
5325 // Here we test if entry for the VAT exemption code exists in llx_vat, we can return '0 (VATEX-EU-132-xx)'
5326 // If not, we add it and we return '0 (VATEX-EU-132-xx)'
5327 $vatvalue = 0;
5328 $vatrule = 'VATRULE 1';
5329 }
5330
5331 // 'VATRULE 2' - Force VAT if a buyer department is defined on vat rates dictionary
5332 if (empty($vatrule) && !empty($thirdparty_buyer->state_id)) {
5333 $sql = "SELECT d.rowid, t.taux as vat_default_rate, t.code as vat_default_code ";
5334 $sql .= " FROM " . $db->prefix() . "c_tva as t";
5335 $sql .= " INNER JOIN " . $db->prefix() . "c_departements as d ON t.fk_department_buyer = d.rowid";
5336 $sql .= " WHERE d.rowid = " . ((int) $thirdparty_buyer->state_id);
5337 $sql .= " AND t.active > 0";
5338 $sql .= " AND t.entity IN (".getEntity('c_tva').")";
5339 $sql .= " ORDER BY t.use_default DESC, t.taux DESC, t.code ASC, t.recuperableonly ASC";
5340
5341 $res = $db->query($sql);
5342 if ($res) {
5343 if ($db->num_rows($res)) {
5344 $obj = $db->fetch_object($res);
5345
5346 $vatvalue = $obj->vat_default_rate . ' (' . $obj->vat_default_code . ')';
5347 $vatrule = 'VATRULE 2';
5348 }
5349 $db->free($res);
5350 }
5351 }
5352
5353 // If the (seller country = buyer country) then the default VAT = VAT of the product sold. End of rule.
5354 if (empty($vatrule) && (
5355 ($seller_country_code == $buyer_country_code)
5356 || (in_array($seller_country_code, array('FR', 'MC')) && in_array($buyer_country_code, array('FR', 'MC')))
5357 || (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.
5358 )) { // Warning ->country_code not always defined
5359 //print 'VATRULE 3';
5360 $tmpvat = get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
5361
5362 if ($seller_country_code == 'IN' && getDolGlobalString('MAIN_SALETAX_AUTOSWITCH_I_CS_FOR_INDIA')) {
5363 // Special case for india.
5364 //print 'VATRULE 3b';
5365 $reg = array();
5366 if (preg_match('/C+S-(\d+)/', $tmpvat, $reg) && $thirdparty_seller->state_id != $thirdparty_buyer->state_id) {
5367 // we must revert the C+S into I
5368 $tmpvat = str_replace("C+S", "I", $tmpvat);
5369 } elseif (preg_match('/I-(\d+)/', $tmpvat, $reg) && $thirdparty_seller->state_id == $thirdparty_buyer->state_id) {
5370 // we must revert the I into C+S
5371 $tmpvat = str_replace("I", "C+S", $tmpvat);
5372 }
5373 }
5374
5375 $vatvalue = $tmpvat;
5376 $vatrule = 'VATRULE 3b';
5377 }
5378
5379 // 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.
5380 // 'VATRULE 4' - Not supported
5381
5382 // If (seller and buyer in the European Community) and (buyer = individual) then VAT by default = VAT of the product sold. End of rule
5383 // If (seller and buyer in European Community) and (buyer = company) then VAT by default=0. End of rule
5384 if (empty($vatrule) && ($seller_in_cee && $buyer_in_cee)) {
5385 $isacompany = $thirdparty_buyer->isACompany();
5386 if ($isacompany && !getDolGlobalString('MAIN_USE_VAT_ZERO_FOR_COMPANIES_IN_EEC_EVEN_IF_VAT_ID_UNKNOWN')) {
5387 require_once DOL_DOCUMENT_ROOT . '/core/lib/functions2.lib.php';
5388 if (!isValidVATID($thirdparty_buyer)) {
5389 $isacompany = 0;
5390 }
5391 }
5392
5393 if (!$isacompany) {
5394 //print 'VATRULE 5';
5395 $vatvalue = get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
5396 $vatrule = 'VATRULE 5';
5397 } else {
5398 //print 'VATRULE 6';
5399 // TODO This is the case of VAT exemption 'VATEX-EU-IC'
5400 // If entry for the VAT exemption code exists in llx_vat, we can return '0 (VATEX-EU-IC)'
5401 // If not, we add it and we return '0 (VATEX-EU-IC)'
5402 $vatvalue = 0;
5403 $vatrule = 'VATRULE 6';
5404 }
5405 }
5406
5407 // 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
5408 // 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
5409 if (empty($vatrule) && getDolGlobalString('MAIN_USE_VAT_OF_PRODUCT_FOR_INDIVIDUAL_CUSTOMER_OUT_OF_EEC') && empty($buyer_in_cee)) {
5410 $isacompany = $thirdparty_buyer->isACompany();
5411 if (!$isacompany) {
5412 $vatvalue = get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
5413 $vatrule = 'VATRULE extra';
5414 //print 'VATRULE extra';
5415 }
5416 }
5417
5418 // Otherwise the VAT proposed by default=0. End of rule.
5419 // Rem: This means that at least one of the 2 is outside the European Community and the country differs
5420 //print 'VATRULE 7';
5421 // TODO This is the case of VAT exemption 'VATEX-EU-G'
5422 // If entry for the VAT exemption code exists in llx_vat, we can return '0 (VATEX-xxx)'
5423 // If not, we add it and we return '0 (VATEX-xxx)'
5424
5425 // Allow an external module to bypass the calculation of prices
5426 $parameters = array('vatvalue' => $vatvalue, 'vatrule' => $vatrule);
5427 $tmpobject = null;
5428 $tmpaction = '';
5429 // @phan-suppress-next-line PhanPluginConstantVariableNull
5430 $reshook = $hookmanager->executeHooks('get_default_tva', $parameters, $tmpobject, $tmpaction); // @phan-suppress-current-line PhanPluginConstantVariableNull
5431 if ($reshook > 0 && !empty($hookmanager->resArray['vatvalue'])) {
5432 $vatvalue = $hookmanager->resArray['vatvalue'];
5433 $vatrule = $hookmanager->resArray['vatrule']; // For information
5434 }
5435
5436 return $vatvalue;
5437}
5438
5439
5450function get_default_npr(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0)
5451{
5452 global $db;
5453
5454 if ($idprodfournprice > 0) {
5455 if (!class_exists('ProductFournisseur')) {
5456 require_once DOL_DOCUMENT_ROOT . '/fourn/class/fournisseur.product.class.php';
5457 }
5458 $prodprice = new ProductFournisseur($db);
5459 $prodprice->fetch_product_fournisseur_price($idprodfournprice);
5460 return $prodprice->fourn_tva_npr;
5461 } elseif ($idprod > 0) {
5462 if (!class_exists('Product')) {
5463 require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
5464 }
5465 $prod = new Product($db);
5466 $prod->fetch($idprod);
5467 return $prod->tva_npr;
5468 }
5469
5470 return 0;
5471}
5472
5486function get_default_localtax($thirdparty_seller, $thirdparty_buyer, $local, $idprod = 0)
5487{
5488 global $mysoc;
5489
5490 if (!is_object($thirdparty_seller)) {
5491 return -1;
5492 }
5493 if (!is_object($thirdparty_buyer)) {
5494 return -1;
5495 }
5496
5497 if (empty($thirdparty_seller->country_code)) {
5498 $thirdparty_seller->country_code = $mysoc->country_code;
5499 }
5500 $seller_country_code = $thirdparty_seller->country_code;
5501 //$seller_in_cee = isInEEC($thirdparty_seller);
5502
5503 if (empty($thirdparty_buyer->country_code)) {
5504 $thirdparty_buyer->country_code = $mysoc->country_code;
5505 }
5506 $buyer_country_code = $thirdparty_buyer->country_code;
5507 //$buyer_in_cee = isInEEC($thirdparty_buyer);
5508
5509 if ($local == 1) { // Localtax 1
5510 if ($mysoc->country_code == 'ES') {
5511 if (is_numeric($thirdparty_buyer->localtax1_assuj) && !$thirdparty_buyer->localtax1_assuj) {
5512 return 0;
5513 }
5514 } else {
5515 // Si vendeur non assujeti a Localtax1, localtax1 par default=0
5516 if (is_numeric($thirdparty_seller->localtax1_assuj) && !$thirdparty_seller->localtax1_assuj) {
5517 return 0;
5518 }
5519 if (!is_numeric($thirdparty_seller->localtax1_assuj) && $thirdparty_seller->localtax1_assuj == 'localtax1off') {
5520 return 0;
5521 }
5522 }
5523 } elseif ($local == 2) { //I Localtax 2
5524 // Si vendeur non assujeti a Localtax2, localtax2 par default=0
5525 if (is_numeric($thirdparty_seller->localtax2_assuj) && !$thirdparty_seller->localtax2_assuj) {
5526 return 0;
5527 }
5528 if (!is_numeric($thirdparty_seller->localtax2_assuj) && $thirdparty_seller->localtax2_assuj == 'localtax2off') {
5529 return 0;
5530 }
5531 }
5532
5533 if ($seller_country_code == $buyer_country_code) {
5534 return get_product_localtax_for_country($idprod, $local, $thirdparty_seller);
5535 }
5536
5537 return 0;
5538}
5539
5540
5559function get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart = '')
5560{
5561 if (empty($modulepart) && is_object($object)) {
5562 if (!empty($object->module)) {
5563 $modulepart = $object->module;
5564 } elseif (!empty($object->element)) {
5565 $modulepart = $object->element;
5566 }
5567 }
5568
5569 $path = '';
5570
5571 // Define $arrayforoldpath that is module path using a hierarchy on more than 1 level.
5572 $arrayforoldpath = array('cheque' => 2, 'category' => 2, 'supplier_invoice' => 2, 'invoice_supplier' => 2, 'mailing' => 2, 'supplier_payment' => 2);
5573 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
5574 $arrayforoldpath['product'] = 2;
5575 }
5576
5577 if (empty($level) && array_key_exists($modulepart, $arrayforoldpath)) {
5578 $level = $arrayforoldpath[$modulepart];
5579 }
5580 if (!empty($level) && array_key_exists($modulepart, $arrayforoldpath)) {
5581 // This part should be removed once all code is using "get_exdir" to forge path, with parameter $object and $modulepart provided.
5582 if (empty($num) && is_object($object)) {
5583 $num = ((int) $object->id);
5584 }
5585 if (empty($alpha)) {
5586 $num = preg_replace('/([^0-9])/i', '', $num);
5587 } else {
5588 $num = preg_replace('/^.*\-/i', '', $num);
5589 }
5590 $num = substr("000" . $num, -$level);
5591 if ($level == 1) {
5592 $path = substr($num, 0, 1);
5593 }
5594 if ($level == 2) {
5595 $path = substr($num, 1, 1) . '/' . substr($num, 0, 1);
5596 }
5597 if ($level == 3) {
5598 $path = substr($num, 2, 1) . '/' . substr($num, 1, 1) . '/' . substr($num, 0, 1);
5599 }
5600 } else {
5601 // We will enhance here a common way of forging path for document storage.
5602 // In a future, we may distribute directories on several levels depending on setup and object.
5603 // Here, $object->id, $object->ref and $modulepart are required.
5604 if (in_array($modulepart, array('societe', 'thirdparty')) && $object instanceof Societe) {
5605 // 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
5606 $path = dol_sanitizeFileName((string) $object->id);
5607 } else {
5608 $path = dol_sanitizeFileName(empty($object->ref) ? (string) ((is_object($object) && property_exists($object, 'id')) ? ((int) $object->id) : '') : $object->ref);
5609 }
5610 }
5611
5612 if (empty($withoutslash) && !empty($path)) {
5613 $path .= '/';
5614 }
5615
5616 return $path;
5617}
5618
5627function dol_mkdir($dir, $dataroot = '', $newmask = '')
5628{
5629 dol_syslog("functions.lib::dol_mkdir: dir=" . $dir, LOG_INFO);
5630
5631 $dir = dol_sanitizePathName($dir, '_', 0);
5632
5633 $dir_osencoded = dol_osencode($dir);
5634 if (@is_dir($dir_osencoded)) {
5635 return 0;
5636 }
5637
5638 $nberr = 0;
5639 $nbcreated = 0;
5640
5641 $ccdir = '';
5642 if (!empty($dataroot)) {
5643 // Remove data root from loop
5644 $dir = str_replace($dataroot . '/', '', $dir);
5645 $ccdir = $dataroot . '/';
5646 }
5647
5648 $cdir = explode("/", $dir);
5649 $num = count($cdir);
5650 for ($i = 0; $i < $num; $i++) {
5651 if ($i > 0) {
5652 $ccdir .= '/' . $cdir[$i];
5653 } else {
5654 $ccdir .= $cdir[$i];
5655 }
5656 $regs = array();
5657 if (preg_match("/^.:$/", $ccdir, $regs)) {
5658 continue; // If the Windows path is incomplete, continue with next directory
5659 }
5660
5661 // Attention, is_dir() can fail event if the directory exists
5662 // (i.e. according the open_basedir configuration)
5663 if ($ccdir) {
5664 $ccdir_osencoded = dol_osencode($ccdir);
5665 if (!@is_dir($ccdir_osencoded)) {
5666 dol_syslog("functions.lib::dol_mkdir: Directory '" . $ccdir . "' is not found (does not exists or is outside open_basedir PHP setting).", LOG_DEBUG);
5667
5668 umask(0);
5669 $dirmaskdec = octdec((string) $newmask);
5670 if (empty($newmask)) {
5671 $dirmaskdec = octdec(getDolGlobalString('MAIN_UMASK', '0755'));
5672 }
5673 $dirmaskdec |= octdec('0111'); // Set x bit required for directories
5674 if (!@mkdir($ccdir_osencoded, $dirmaskdec)) {
5675 // If the is_dir has returned a false information, we arrive here
5676 dol_syslog("functions.lib::dol_mkdir: Fails to create directory '" . $ccdir . "' (no permission to write into parent or directory already exists).", LOG_WARNING);
5677 $nberr++;
5678 } else {
5679 dol_syslog("functions.lib::dol_mkdir: Directory '" . $ccdir . "' created", LOG_DEBUG);
5680 $nberr = 0; // At this point in the code, the previous failures can be ignored -> set $nberr to 0
5681 $nbcreated++;
5682 }
5683 } else {
5684 $nberr = 0; // At this point in the code, the previous failures can be ignored -> set $nberr to 0
5685 }
5686 }
5687 }
5688 return ($nberr ? -$nberr : $nbcreated);
5689}
5690
5691
5699function dolChmod($filepath, $newmask = '')
5700{
5701 if (!empty($newmask)) {
5702 @chmod($filepath, octdec($newmask));
5703 } elseif (getDolGlobalString('MAIN_UMASK')) {
5704 @chmod($filepath, octdec(getDolGlobalString('MAIN_UMASK')));
5705 }
5706}
5707
5708
5714function picto_required()
5715{
5716 return '<span class="fieldrequired">*</span>';
5717}
5718
5719
5736function dol_string_nohtmltag($stringtoclean, $removelinefeed = 1, $pagecodeto = 'UTF-8', $strip_tags = 0, $removedoublespaces = 1)
5737{
5738 if (is_null($stringtoclean)) {
5739 return '';
5740 }
5741
5742 if ($removelinefeed == 2) {
5743 $stringtoclean = preg_replace('/<br[^>]*>(\n|\r)+/ims', '<br>', $stringtoclean);
5744 }
5745 $temp = preg_replace('/<br[^>]*>/i', "\n", $stringtoclean);
5746
5747 // 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)
5748 $temp = dol_html_entity_decode($temp, ENT_COMPAT | ENT_HTML5, $pagecodeto);
5749
5750 $temp = str_replace('< ', '__ltspace__', $temp);
5751 $temp = str_replace('<:', '__lttwopoints__', $temp);
5752
5753 if ($strip_tags) {
5754 $temp = strip_tags($temp);
5755 } else {
5756 // 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).
5757 $pattern = "/<[^<>]+>/";
5758 // Example of $temp: <a href="/myurl" title="<u>A title</u>">0000-021</a>
5759 // pass 1 - $temp after pass 1: <a href="/myurl" title="A title">0000-021
5760 // pass 2 - $temp after pass 2: 0000-021
5761 $tempbis = $temp;
5762 do {
5763 $temp = $tempbis;
5764 $tempbis = str_replace('<>', '', $temp); // No reason to have this into a text, except if value is to try bypass the next html cleaning
5765 $tempbis = preg_replace($pattern, '', $tempbis);
5766 //$idowhile++; print $temp.'-'.$tempbis."\n"; if ($idowhile > 100) break;
5767 } while ($tempbis != $temp);
5768
5769 $temp = $tempbis;
5770
5771 // 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).
5772 $temp = preg_replace('/<+([a-z]+)/i', '\1', $temp);
5773 }
5774
5775 $temp = dol_html_entity_decode($temp, ENT_COMPAT, $pagecodeto);
5776
5777 // Remove also carriage returns
5778 if ($removelinefeed == 1) {
5779 $temp = str_replace(array("\r\n", "\r", "\n"), " ", $temp);
5780 }
5781
5782 // And double spaces
5783 if ($removedoublespaces) {
5784 while (strpos($temp, " ") !== false) {
5785 $temp = str_replace(" ", " ", $temp);
5786 }
5787 }
5788
5789 $temp = str_replace('__ltspace__', '< ', $temp);
5790 $temp = str_replace('__lttwopoints__', '<:', $temp);
5791
5792 return trim($temp);
5793}
5794
5814function dol_string_onlythesehtmltags($stringtoclean, $cleanalsosomestyles = 1, $removeclassattribute = 1, $cleanalsojavascript = 0, $allowiframe = 0, $allowed_tags = array(), $allowlink = 0, $allowscript = 0, $allowstyle = 0, $allowphp = 0)
5815{
5816 $sav_allowed_tags = $allowed_tags;
5817
5818 if (empty($allowed_tags) || (is_string($allowed_tags) && preg_match('/^common/', $allowed_tags))) {
5819 $allowed_tags = array(
5820 // HTML 4
5821 "html",
5822 "head",
5823 "body",
5824 "article",
5825 "a",
5826 "abbr",
5827 "b",
5828 "blockquote",
5829 "br",
5830 "cite",
5831 "div",
5832 "dl",
5833 "dd",
5834 "dt",
5835 "em",
5836 "font",
5837 "img",
5838 "ins",
5839 "hr",
5840 "i",
5841 "li",
5842 "ol",
5843 "p",
5844 "q",
5845 "s",
5846 "span",
5847 "strike",
5848 "strong",
5849 "title",
5850 "table",
5851 "tr",
5852 "th",
5853 "td",
5854 "u",
5855 "ul",
5856 "sup",
5857 "sub",
5858 "blockquote",
5859 "pre",
5860 "h1",
5861 "h2",
5862 "h3",
5863 "h4",
5864 "h5",
5865 "h6",
5866
5867 // HTML 5
5868 "footer",
5869 "header",
5870 "menu",
5871 "menuitem",
5872 "nav",
5873 "section"
5874 );
5875 }
5876 $allowed_tags[] = "comment"; // this tags is added to manage comment <!--...--> that are replaced into <comment>...</comment>
5877 if ($allowiframe) {
5878 if (!in_array('iframe', $allowed_tags)) {
5879 $allowed_tags[] = "iframe";
5880 }
5881 }
5882 if ($allowlink) {
5883 if (!in_array('link', $allowed_tags)) {
5884 $allowed_tags[] = "link";
5885 }
5886 if (!in_array('meta', $allowed_tags)) {
5887 $allowed_tags[] = "meta";
5888 }
5889 }
5890 if ($allowscript) {
5891 if (!in_array('script', $allowed_tags)) {
5892 $allowed_tags[] = "script";
5893 }
5894 }
5895 if ($allowstyle) {
5896 if (!in_array('style', $allowed_tags)) {
5897 $allowed_tags[] = "style";
5898 }
5899 }
5900 if (is_string($sav_allowed_tags)) {
5901 $tmptags = explode(',', $sav_allowed_tags);
5902 foreach ($tmptags as $tag) {
5903 if ($tag != 'common') {
5904 $allowed_tags[] = $tag;
5905 }
5906 }
5907 }
5908
5909
5910 $allowed_tags_string = implode("><", $allowed_tags);
5911 $allowed_tags_string = '<' . $allowed_tags_string . '>';
5912
5913 $stringtoclean = str_replace('<!DOCTYPE html>', '__!DOCTYPE_HTML__', $stringtoclean); // Replace DOCTYPE to avoid to have it removed by the strip_tags
5914
5915 $stringtoclean = dol_string_nounprintableascii($stringtoclean, 0);
5916
5917 //$stringtoclean = preg_replace('/<!--[^>]*-->/', '', $stringtoclean);
5918 $stringtoclean = preg_replace('/<!--([^>]*)-->/', '<comment>\1</comment>', $stringtoclean);
5919
5920 if ($allowphp) {
5921 $allowed_tags[] = "commentphp";
5922 $stringtoclean = preg_replace('/^<\?php([^"]+)\?>$/i', '<commentphp>\1__</commentphp>', $stringtoclean); // Note: <?php ... > is allowed only if on the same line
5923 $stringtoclean = preg_replace('/"<\?php([^"]+)\?>"/i', '"<commentphp>\1</commentphp>"', $stringtoclean); // Note: "<?php ... >" is allowed only if on the same line
5924 }
5925
5926 $stringtoclean = preg_replace('/&colon;/i', ':', $stringtoclean);
5927 $stringtoclean = preg_replace('/&#58;|&#0+58|&#x3A/i', '', $stringtoclean); // refused string ':' encoded (no reason to have a : encoded like this) to disable 'javascript:...'
5928
5929 // Remove all HTML tags
5930 $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
5931
5932 if ($cleanalsosomestyles) { // Clean for remaining html tags
5933 //$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
5934 $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
5935 $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
5936 }
5937 if ($removeclassattribute) { // Clean for remaining html tags
5938 $temp = preg_replace('/(<[^>]+)\s+class=((["\']).*?\\3|\\w*)/i', '\\1', $temp);
5939 }
5940
5941 // Remove 'javascript:' that we should not find into a text
5942 // 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)).
5943 if ($cleanalsojavascript) {
5944 $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);
5945 }
5946
5947 $temp = str_replace('__!DOCTYPE_HTML__', '<!DOCTYPE html>', $temp); // Restore the DOCTYPE
5948
5949 if ($allowphp) {
5950 $temp = preg_replace('/<commentphp>(.*)<\/commentphp>/', '<?php\1?>', $temp); // Restore php code
5951 }
5952
5953 $temp = preg_replace('/<comment>([^>]*)<\/comment>/', '<!--\1-->', $temp); // Restore html comments
5954
5955
5956 return $temp;
5957}
5958
5959
5973function dol_string_onlythesehtmlattributes($stringtoclean, $allowed_attributes = null, $ishtml = null)
5974{
5975 if (is_null($allowed_attributes)) {
5976 $allowed_attributes = array(
5977 // HTML 4
5978 "allow",
5979 "allowfullscreen",
5980 "alt",
5981 "async",
5982 "class",
5983 "contenteditable",
5984 "crossorigin",
5985 "data-html",
5986 "frameborder",
5987 "height",
5988 "href",
5989 "id",
5990 "name",
5991 "property",
5992 "rel",
5993 "src",
5994 "style",
5995 "target",
5996 "title",
5997 "type",
5998 "width",
5999
6000 // HTML5
6001 "footer",
6002 "header",
6003 "menu",
6004 "menuitem",
6005 "nav",
6006 "section"
6007 );
6008 }
6009 // Always add content and http-equiv for meta tags, required to force encoding and keep html content in utf8 by load/saveHTML functions.
6010 if (!in_array("content", $allowed_attributes)) {
6011 $allowed_attributes[] = "content";
6012 }
6013 if (!in_array("http-equiv", $allowed_attributes)) {
6014 $allowed_attributes[] = "http-equiv";
6015 }
6016
6017 if (class_exists('DOMDocument') && !empty($stringtoclean)) {
6018 if ($ishtml === 0) {
6019 $stringtoclean = str_replace('&', '__AMPINTEXT__', $stringtoclean); // Restore & char not entity
6020 }
6021
6022 // Warning: loadHTML does not support HTML5 on old libxml versions.
6023 $dom = new DOMDocument('', 'UTF-8');
6024 // If $stringtoclean is wrong, it will generates warnings. So we disable warnings and restore them later.
6025 $savwarning = error_reporting();
6026 error_reporting(E_ALL & ~E_WARNING & ~E_NOTICE);
6027 $wrapperId = "dol_string_onlythesehtmlattributes___wrapper";
6028 $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);
6029 error_reporting($savwarning);
6030
6031 if ($dom instanceof DOMDocument) {
6032 for ($els = $dom->getElementsByTagname('*'), $i = $els->length - 1; $i >= 0; $i--) {
6033 $el = $els->item($i);
6034 if (!$el instanceof DOMElement) {
6035 continue;
6036 }
6037 $attrs = $el->attributes;
6038 for ($ii = $attrs->length - 1; $ii >= 0; $ii--) {
6039 //var_dump($attrs->item($ii));
6040 if (!empty($attrs->item($ii)->name)) {
6041 if (! in_array($attrs->item($ii)->name, $allowed_attributes)) {
6042 // Delete attribute if not into allowed_attributes @phan-suppress-next-line PhanUndeclaredMethod
6043 $els->item($i)->removeAttribute($attrs->item($ii)->name);
6044 } elseif (in_array($attrs->item($ii)->name, array('style'))) {
6045 // If attribute is 'style'
6046 $valuetoclean = $attrs->item($ii)->value;
6047
6048 if (isset($valuetoclean)) {
6049 do {
6050 $oldvaluetoclean = $valuetoclean;
6051 $valuetoclean = preg_replace('/\/\*.*\*\//m', '', $valuetoclean); // clean css comments
6052 $valuetoclean = preg_replace('/position\s*:\s*[a-z]+/mi', '', $valuetoclean);
6053 if ($els->item($i)->tagName == 'a') { // more paranoiac cleaning for clickable tags.
6054 $valuetoclean = preg_replace('/display\s*:/mi', '', $valuetoclean);
6055 $valuetoclean = preg_replace('/z-index\s*:/mi', '', $valuetoclean);
6056 $valuetoclean = preg_replace('/\s+(top|left|right|bottom)\s*:/mi', '', $valuetoclean);
6057 }
6058
6059 // We do not allow logout|passwordforgotten.php and action= into the content of a "style" tag
6060 $valuetoclean = preg_replace('/(logout|passwordforgotten)\.php/mi', '', $valuetoclean);
6061 $valuetoclean = preg_replace('/action=/mi', '', $valuetoclean);
6062 } while ($oldvaluetoclean != $valuetoclean);
6063 }
6064
6065 $attrs->item($ii)->value = $valuetoclean;
6066 }
6067 }
6068 }
6069 }
6070 }
6071
6072 $dom->encoding = 'UTF-8';
6073 $wrapper = $dom->getElementById($wrapperId);
6074 $return = '';
6075 foreach ($wrapper->childNodes as $child) {
6076 $return .= $dom->saveHTML($child);
6077 }
6078
6079 if ($ishtml === 0) {
6080 $return = str_replace('__AMPINTEXT__', '&', $return); // Restore & char not entity
6081 }
6082
6083 return trim($return);
6084 } else {
6085 return $stringtoclean;
6086 }
6087}
6088
6100function dol_string_neverthesehtmltags($stringtoclean, $disallowed_tags = array('textarea'), $cleanalsosomestyles = 0)
6101{
6102 $temp = $stringtoclean;
6103 foreach ($disallowed_tags as $tagtoremove) {
6104 $temp = preg_replace('/<\/?' . $tagtoremove . '>/', '', $temp);
6105 $temp = preg_replace('/<\/?' . $tagtoremove . '\s+[^>]*>/', '', $temp);
6106 }
6107
6108 if ($cleanalsosomestyles) {
6109 $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
6110 }
6111
6112 return $temp;
6113}
6114
6115
6125function dolCloseUnclosedHtmlTags($text)
6126{
6127 if (!is_string($text) || $text === '') {
6128 return $text;
6129 }
6130
6131 // Tags that never carry a closing tag
6132 $selfclosing = array('br', 'hr', 'img', 'input', 'meta', 'link', 'source', 'col', 'area', 'base', 'embed', 'param', 'track', 'wbr');
6133
6134 $opened = array();
6135 if (preg_match_all('/<\s*(\/?)([a-zA-Z][a-zA-Z0-9]*)[^>]*?(\/?)\s*>/', $text, $matches, PREG_SET_ORDER)) {
6136 foreach ($matches as $match) {
6137 $tag = strtolower($match[2]);
6138 if (in_array($tag, $selfclosing) || !empty($match[3])) {
6139 continue;
6140 }
6141 if (empty($match[1])) {
6142 $opened[] = $tag;
6143 } else {
6144 // Close the most recent matching opened tag, ignore a stray closing tag
6145 $idx = array_search($tag, array_reverse($opened, true), true);
6146 if ($idx !== false) {
6147 unset($opened[$idx]);
6148 }
6149 }
6150 }
6151 }
6152
6153 foreach (array_reverse($opened) as $tag) {
6154 $text .= '</'.$tag.'>';
6155 }
6156
6157 return $text;
6158}
6159
6169function dolGetFirstLineOfText($text, $nboflines = 1, $charset = 'UTF-8')
6170{
6171 if ($nboflines == 1) {
6172 if (dol_textishtml($text)) {
6173 $firstline = preg_replace('/<br[^>]*>.*$/s', '', $text); // The s pattern modifier means the . can match newline characters
6174 $firstline = preg_replace('/<div[^>]*>.*$/s', '', $firstline); // The s pattern modifier means the . can match newline characters
6175 } else {
6176 if (isset($text)) {
6177 $firstline = preg_replace('/[\n\r].*/', '', $text);
6178 } else {
6179 $firstline = '';
6180 }
6181 }
6182 return $firstline . (isset($firstline) && isset($text) && (strlen($firstline) != strlen($text)) ? '...' : '');
6183 } else {
6184 $ishtml = 0;
6185 if (dol_textishtml($text)) {
6186 $text = preg_replace('/\n/', '', $text);
6187 $ishtml = 1;
6188 $repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
6189 } else {
6190 $repTable = array("\t" => " ", "\n" => "<br>", "\r" => " ", "\0" => " ", "\x0B" => " ");
6191 }
6192
6193 $text = strtr($text, $repTable);
6194 if ($charset == 'UTF-8') {
6195 $pattern = '/(<br[^>]*>)/Uu';
6196 } else {
6197 // /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
6198 $pattern = '/(<br[^>]*>)/U'; // /U is to have UNGREEDY regex to limit to one html tag.
6199 }
6200 $a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
6201
6202 $firstline = '';
6203 $i = 0;
6204 $countline = 0;
6205 $lastaddediscontent = 1;
6206 while ($countline < $nboflines && isset($a[$i])) {
6207 if (preg_match('/<br[^>]*>/', $a[$i])) {
6208 if (array_key_exists($i + 1, $a) && !empty($a[$i + 1])) {
6209 $firstline .= ($ishtml ? "<br>\n" : "\n");
6210 // Is it a br for a new line of after a printed line ?
6211 if (!$lastaddediscontent) {
6212 $countline++;
6213 }
6214 $lastaddediscontent = 0;
6215 }
6216 } else {
6217 $firstline .= $a[$i];
6218 $lastaddediscontent = 1;
6219 $countline++;
6220 }
6221 $i++;
6222 }
6223
6224 $adddots = (isset($a[$i]) && (!preg_match('/<br[^>]*>/', $a[$i]) || (array_key_exists($i + 1, $a) && !empty($a[$i + 1]))));
6225 //unset($a);
6226 $ret = $firstline . ($adddots ? '...' : '');
6227 //exit;
6228 return $ret;
6229 }
6230}
6231
6232
6244function dol_nl2br($stringtoencode, $nl2brmode = 0, $forxml = false)
6245{
6246 if (is_null($stringtoencode)) {
6247 return '';
6248 }
6249
6250 if (!$nl2brmode) {
6251 return nl2br($stringtoencode, $forxml);
6252 } else {
6253 $ret = preg_replace('/(\r\n|\r|\n)/i', ($forxml ? '<br />' : '<br>'), $stringtoencode);
6254 return $ret;
6255 }
6256}
6257
6267function dol_htmlwithnojs($stringtoencode, $nouseofiframesandbox = 0, $check = 'restricthtml')
6268{
6269 if (empty($nouseofiframesandbox) && getDolGlobalString('MAIN_SECURITY_USE_SANDBOX_FOR_HTMLWITHNOJS')) {
6270 // TODO using sandbox on inline html content is not possible yet with current browsers
6271 //$s = '<iframe class="iframewithsandbox" sandbox><html><body>';
6272 //$s .= $stringtoencode;
6273 //$s .= '</body></html></iframe>';
6274 return $stringtoencode;
6275 } else {
6276 $out = $stringtoencode;
6277
6278 // First clean HTML content
6279 do {
6280 $oldstringtoclean = $out;
6281
6282 $outishtml = 0;
6283 if (dol_textishtml($out)) {
6284 $outishtml = 1;
6285 }
6286
6287 // HTML sanitizer by DOMDocument
6288 if (!empty($out) && getDolGlobalInt('MAIN_RESTRICTHTML_ONLY_VALID_HTML') && $check != 'restricthtmlallowunvalid') {
6289 try {
6290 libxml_use_internal_errors(false); // Avoid to fill memory with xml errors
6291 if (LIBXML_VERSION < 20900) {
6292 // Avoid load of external entities (security problem).
6293 // Required only if LIBXML_VERSION < 20900
6294 // @phan-suppress-next-line PhanDeprecatedFunctionInternal
6295 libxml_disable_entity_loader(true);
6296 }
6297
6298 $dom = new DOMDocument();
6299 // Add a trick '<div class="tricktoremove">' to solve pb with text without parent tag
6300 // like '<h1>Foo</h1><p>bar</p>' that wrongly ends up, without the trick, with '<h1>Foo<p>bar</p></h1>'
6301 // like 'abc' that wrongly ends up, without the trick, with '<p>abc</p>'
6302 // Add also a trick <html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"> to solve utf8 lost.
6303 // I don't know what the xml encoding is the trick for
6304
6305 if (!$outishtml) {
6306 $out = preg_replace('/&(?![a-zA-Z0-9#]+;)/', '__AMPINTEXT__', $out);
6307
6308 $out = dol_nl2br($out);
6309 }
6310
6311 // Note: <a href="https://__[aaa]__/aaa.html"> is transformed into <a href="https://__[aaa]__/aaa.html">
6312 // We don't want that, so we protect __[xxx]__ by replacing [ and ] before loadHTML and restore them after saveHTML
6313 $out = preg_replace_callback(
6314 '/__\[([0-9a-zA-Z_]+)\]__/',
6319 function ($m) {
6320 return '__BRACKETSTART' . $m[1] . 'BRACKETEND__';
6321 },
6322 $out
6323 );
6324 $wrapperId = "dol_htmlwithnojs___wrapper___toremove";
6325 $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);
6326
6327 $dom->encoding = 'UTF-8';
6328
6329 // Add a layer to remove some styles
6330 if (getDolGlobalInt('MAIN_RESTRICTHTML_ONLY_VALID_HTML') == 2) {
6331 foreach ($dom->getElementsByTagName('*') as $el) {
6332 if (!$el instanceof DOMElement) {
6333 continue;
6334 }
6335
6336 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
6337 if ($el->hasAttribute('style')) {
6338 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
6339 $style = $el->getAttribute('style');
6340
6341 // delete some styles
6342 $style = preg_replace('/z-index\s*:/i', '', $style);
6343 $style = preg_replace('/position\s*:/i', '', $style);
6344 $style = preg_replace('/top\s*:/i', '', $style);
6345 $style = preg_replace('/left\s*:/i', '', $style);
6346 $style = preg_replace('/background\s*:/i', '', $style);
6347 /*
6348 $style = preg_replace('/width\s*:/i', '', $style);
6349 $style = preg_replace('/height\s*:/i', '', $style);
6350 $style = preg_replace('/backdrop-filter\s*:/i', '', $style);
6351 */
6352 if (trim($style) === '') {
6353 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
6354 $el->removeAttribute('style');
6355 } else {
6356 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
6357 $el->setAttribute('style', $style);
6358 }
6359 }
6360 }
6361 }
6362
6363 $wrapper = $dom->getElementById($wrapperId);
6364 $result = '';
6365 foreach ($wrapper->childNodes as $child) {
6366 $result .= $dom->saveHTML($child);
6367 }
6368 $out = trim($result);
6369
6370 // Restore [ and ] that were protected before loadHTML
6371 $out = preg_replace_callback(
6372 '/__BRACKETSTART([0-9a-zA-Z_]+)BRACKETEND__/',
6377 function ($m) {
6378 return '__[' . $m[1] . ']__';
6379 },
6380 $out
6381 );
6382
6383 if (!$outishtml) { // If $out was not HTML content we made before a dol_nl2br so we must do the opposite operation now
6384 $out = str_replace('__AMPINTEXT__', '&', $out); // Restore & char not entity
6385 $out = preg_replace('/<br\s*\/?>/i', "\n", $out);
6386 }
6387 } catch (Exception $e) {
6388 // If error, invalid HTML string with no way to clean it
6389 //print $e->getMessage();
6390 $out = 'InvalidHTMLStringCantBeCleaned ' . $e->getMessage();
6391 }
6392 }
6393
6394 // HTML sanitizer by Tidy
6395 // Tidy can't be used for restricthtmlallowunvalid and restricthtmlallowlinkscript
6396 // Tidy can't be used for non html text content as it is corrupting the new lines fields.
6397 if (!empty($out) && getDolGlobalInt('MAIN_RESTRICTHTML_ONLY_VALID_HTML_TIDY') && !in_array($check, array('restricthtmlallowunvalid', 'restricthtmlallowlinkscript')) && $outishtml) {
6398 // TODO Try to implement a hack for restricthtmlallowlinkscript by renaming tag <link> and <script> ?
6399 try {
6400 //var_dump($out);
6401
6402 // Try cleaning using tidy
6403 if (extension_loaded('tidy') && class_exists("tidy")) {
6404 //print "aaa".$out."\n";
6405
6406 // See options at https://tidy.sourceforge.net/docs/quickref.html
6407 $config = array(
6408 'clean' => false,
6409 // 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;
6410 'quote-marks' => false,
6411 'doctype' => 'strict',
6412 'show-body-only' => true,
6413 "indent-attributes" => false,
6414 "vertical-space" => false,
6415 //'ident' => false, // Not always supported
6416 "wrap" => 0,
6417 'preserve-entities' => true
6418 // HTML5 tags
6419 //'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',
6420 //'new-blocklevel-tags' => 'footer header section menu menuitem'
6421 //'new-empty-tags' => 'command embed keygen source track wbr',
6422 //'new-inline-tags' => 'audio command datalist embed keygen mark menuitem meter output progress source time video wbr',
6423 );
6424
6425 // Tidy
6426 $locale = setlocale(LC_NUMERIC, '0'); // Tidy has a bug and is changing the PHP locale. So we save it to restore it after.
6427
6428 $tidy = new tidy();
6429 $out = $tidy->repairString($out, $config, 'utf8');
6430
6431 setlocale(LC_NUMERIC, $locale); // Restore original local
6432
6433 //print "xxx".$out;exit;
6434 }
6435
6436 //var_dump($out);
6437 } catch (Exception $e) {
6438 // If error, invalid HTML string with no way to clean it
6439 //print $e->getMessage();
6440 $out = 'InvalidHTMLStringCantBeCleaned ' . $e->getMessage();
6441 }
6442 }
6443
6444 // Clear ZERO WIDTH NO-BREAK SPACE, ZERO WIDTH SPACE, ZERO WIDTH JOINER
6445 // TODO $out = preg_replace('/[\x{2000}-\x{200D}\x{FEFF}]/u', ' ', $out);
6446 $out = preg_replace('/[\x{200B}-\x{200D}\x{FEFF}]/u', ' ', $out);
6447
6448 // Clean some html entities that are useless so text is cleaner
6449 $out = preg_replace('/&(tab|newline);/i', ' ', $out);
6450
6451 // Ckeditor uses the numeric entity for apostrophe, so we force it to
6452 // the text entity (all other special chars are encoded using text entities) so we can then exclude all numeric entities.
6453 $out = preg_replace('/&#39;/i', '&apos;', $out);
6454
6455 // 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).
6456 // 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
6457 // using a non conventionnal way to be encoded, to not have them sanitized just after)
6458 if (function_exists('realCharForNumericEntities')) { // May not exist when main.inc.php not loaded, for example in a CLI context
6459 $out = preg_replace_callback(
6460 '/&#(x?[0-9][0-9a-f]+;?)/i',
6465 static function ($m) {
6466 return realCharForNumericEntities($m);
6467 },
6468 $out
6469 );
6470 }
6471
6472 // Now we remove all remaining HTML entities starting with a number. We don't want such entities.
6473 $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'.
6474
6475 // Keep only some html tags and remove also some 'javascript:' strings
6476 if ($check == 'restricthtmlallowlinkscript') {
6477 $out = dol_string_onlythesehtmltags($out, 0, 1, 0, 0, array(), 1, 1, 1, getDolGlobalInt("UNSECURED_restricthtmlallowlinkscript_ALLOW_PHP"));
6478 } elseif ($check == 'restricthtmlallowclass' || $check == 'restricthtmlallowunvalid') {
6479 $out = dol_string_onlythesehtmltags($out, 0, 0, 1);
6480 } elseif ($check == 'restricthtmlallowiframe') {
6481 $out = dol_string_onlythesehtmltags($out, 0, 0, 1, 1);
6482 } else {
6483 $out = dol_string_onlythesehtmltags($out, 0, 1, 1); // styles are allowed to allow rich text editor features of ckeditor managed by the "style=" attribute
6484 }
6485
6486 // Keep only some html attributes and exclude non expected HTML attributes and clean content of some attributes (keep only alt=, title=...).
6487 if (getDolGlobalString('MAIN_RESTRICTHTML_REMOVE_ALSO_BAD_ATTRIBUTES')) {
6488 $out = dol_string_onlythesehtmlattributes($out, null, $outishtml);
6489 }
6490
6491 // Restore entity &apos; into &#39; (restricthtml is for html content so we can use html entity) because it is
6492 // compatible with HTML 4 used y CKEditor, and HTML 5 (when &apos; works only with HTML5).
6493 $out = preg_replace('/&apos;/i', "&#39;", $out);
6494
6495 // Now remove js
6496 // 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
6497 $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)>
6498 $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);
6499 $out = preg_replace('/on(dblclick|drop|durationchange|emptied|end|ended|error|focus(in|out)?|formdata|gotpointercapture|hashchange|input|invalid)[a-z]*\s*=/i', '', $out);
6500 $out = preg_replace('/on(lost|offline|online|message|pagehide|pageshow)[a-z]*\s*=/i', '', $out);
6501 $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);
6502 $out = preg_replace('/on(timeupdate|toggle|unhandled|unload|volumechange|waiting|wheel)[a-z]*\s*=/i', '', $out);
6503 // More not into the previous list
6504 $out = preg_replace('/on(repeat|begin|finish|beforeinput)[a-z]*\s*=/i', '', $out);
6505 // Add also a generic removal of any onxxx= attribute
6506 $out = preg_replace('/\son[a-z]+\s*=/i', ' ', $out);
6507 } while ($oldstringtoclean != $out);
6508
6509 // Check the limit of external links that are automatically executed in a Rich text content. We count:
6510 // '<img' to avoid <img src="http...">, we can only accept "<img src="data:..."
6511 // 'url(' to avoid inline style like background: url(http...
6512 // '<link' to avoid <link href="http...">
6513 $reg = array();
6514 $tmpout = preg_replace('/<img src="data:/mi', '<__IMG_SRC_DATA__ src="data:', $out);
6515 preg_match_all('/(<img|url\‍(|<link)/i', $tmpout, $reg);
6516 $nblinks = count($reg[0]);
6517 if ($nblinks > getDolGlobalInt("MAIN_SECURITY_MAX_IMG_IN_HTML_CONTENT", 1000)) {
6518 $out = 'ErrorTooManyLinksIntoHTMLString';
6519 }
6520
6521 if (getDolGlobalInt('MAIN_DISALLOW_URL_INTO_DESCRIPTIONS') == 2 || $check == 'restricthtmlnolink') {
6522 if ($nblinks > 0) {
6523 $out = 'ErrorHTMLLinksNotAllowed';
6524 }
6525 } elseif (getDolGlobalInt('MAIN_DISALLOW_URL_INTO_DESCRIPTIONS') == 1) {
6526 // Refuse any links except it they are to the wrapper document.php or viewimage.php
6527 $nblinks = 0;
6528
6529 // Loop on each url in src= and url(
6530 $pattern = '/src=["\']?(http[^"\']+)|url\‍(["\']?(http[^\‍)]+)/';
6531
6533
6534 $matches = array();
6535 if (preg_match_all($pattern, $out, $matches)) {
6536 // URLs are into $matches[1] or $matches[2]
6537 $urls = array();
6538 foreach ($matches[1] as $tmpval) {
6539 if (!empty($tmpval)) {
6540 $urls[] = $tmpval;
6541 }
6542 }
6543 foreach ($matches[2] as $tmpval) {
6544 if (!empty($tmpval)) {
6545 $urls[] = $tmpval;
6546 }
6547 }
6548
6549 // Show URLs
6550 $firstexturl = '';
6551 $secondexturl = '';
6552 foreach ($urls as $url) {
6553 $urlok = 0;
6554 $parsedurl = parse_url($url);
6555 if (!empty($parsedurl)) {
6556 if (preg_match('/'.preg_quote($dolibarr_main_url_root, '/').'/', $url)
6557 //&& preg_match('/(document|viewimage)\.php$/', $parsedurl['path']) && preg_match('/modulepart=(media|mycompany)/', $parsedurl['query'])
6558 ) {
6559 $urlok = 1;
6560 }
6561 }
6562 if (!$urlok) {
6563 $nblinks++;
6564 if (empty($firstexturl)) {
6565 $firstexturl = $url;
6566 } elseif (empty($secondexturl)) {
6567 $secondexturl = $url;
6568 }
6569 //echo "Found url = ".$url . "\n";
6570 }
6571 }
6572 if ($nblinks > 0) {
6573 $out = 'ErrorHTMLExternalLinksNotAllowed (Example: '.$firstexturl.($secondexturl ? ' '.$secondexturl : '').')';
6574 }
6575 }
6576 }
6577
6578 return $out;
6579 }
6580}
6581
6602function dol_htmlentitiesbr($stringtoencode, $nl2brmode = 0, $pagecodefrom = 'UTF-8', $removelasteolbr = 1)
6603{
6604 if (is_null($stringtoencode)) {
6605 return '';
6606 }
6607
6608 $newstring = $stringtoencode;
6609 if (dol_textishtml($stringtoencode)) { // Check if text is already HTML or not
6610 $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.
6611 if ($removelasteolbr) {
6612 $newstring = preg_replace('/<br>$/i', '', $newstring); // Remove last <br> (remove only last one)
6613 }
6614 $newstring = preg_replace('/[\x{200B}-\x{200D}\x{FEFF}]/u', ' ', $newstring);
6615 $newstring = strtr($newstring, array('&' => '__PROTECTand__', '<' => '__PROTECTlt__', '>' => '__PROTECTgt__', '"' => '__PROTECTdquot__'));
6616 $newstring = dol_htmlentities($newstring, ENT_COMPAT, $pagecodefrom); // Make entity encoding
6617 $newstring = strtr($newstring, array('__PROTECTand__' => '&', '__PROTECTlt__' => '<', '__PROTECTgt__' => '>', '__PROTECTdquot__' => '"'));
6618 } else {
6619 if ($removelasteolbr) {
6620 $newstring = preg_replace('/(\r\n|\r|\n)$/i', '', $newstring); // Remove last \n (may remove several)
6621 }
6622 $newstring = dol_nl2br(dol_htmlentities($newstring, ENT_COMPAT, $pagecodefrom), $nl2brmode);
6623 }
6624 // Other substitutions that htmlentities does not do
6625 //$newstring=str_replace(chr(128),'&euro;',$newstring); // 128 = 0x80. Not in html entity table. // Seems useles with TCPDF. Make bug with UTF8 languages
6626 return $newstring;
6627}
6628
6636function dol_htmlentitiesbr_decode($stringtodecode, $pagecodeto = 'UTF-8')
6637{
6638 $ret = dol_html_entity_decode($stringtodecode, ENT_COMPAT | ENT_HTML5, $pagecodeto);
6639 $ret = preg_replace('/' . "\r\n" . '<br(\s[\sa-zA-Z_="]*)?\/?>/i', "<br>", $ret);
6640 $ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>' . "\r\n" . '/i', "\r\n", $ret);
6641 $ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>' . "\n" . '/i', "\n", $ret);
6642 $ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>/i', "\n", $ret);
6643 return $ret;
6644}
6645
6652function dol_htmlcleanlastbr($stringtodecode)
6653{
6654 $ret = preg_replace('/&nbsp;$/i', "", $stringtodecode); // Because wysiwyg editor may add a &nbsp; at end of last line
6655 $ret = preg_replace('/(<br>|<br(\s[\sa-zA-Z_="]*)?\/?>|' . "\n" . '|' . "\r" . ')+$/i', "", $ret);
6656 return $ret;
6657}
6658
6668function dol_html_entity_decode($a, $b, $c = 'UTF-8', $keepsomeentities = 0)
6669{
6670 $newstring = $a;
6671 if ($keepsomeentities) {
6672 $newstring = strtr($newstring, array('&amp;' => '__andamp__', '&lt;' => '__andlt__', '&gt;' => '__andgt__', '"' => '__dquot__'));
6673 }
6674 $newstring = html_entity_decode((string) $newstring, (int) $b, (string) $c);
6675 if ($keepsomeentities) {
6676 $newstring = strtr($newstring, array('__andamp__' => '&amp;', '__andlt__' => '&lt;', '__andgt__' => '&gt;', '__dquot__' => '"'));
6677 }
6678 return $newstring;
6679}
6680
6692function dol_htmlentities($string, $flags = ENT_QUOTES | ENT_SUBSTITUTE, $encoding = 'UTF-8', $double_encode = false)
6693{
6694 return htmlentities($string, $flags, $encoding, $double_encode);
6695}
6696
6708function dol_string_is_good_iso($s, $clean = 0)
6709{
6710 $len = dol_strlen($s);
6711 $out = '';
6712 $ok = 1;
6713 for ($scursor = 0; $scursor < $len; $scursor++) {
6714 $ordchar = ord($s[$scursor]);
6715 //print $scursor.'-'.$ordchar.'<br>';
6716 if ($ordchar < 32 && $ordchar != 13 && $ordchar != 10) {
6717 $ok = 0;
6718 break;
6719 } elseif ($ordchar > 126 && $ordchar < 160) {
6720 $ok = 0;
6721 break;
6722 } elseif ($clean) {
6723 $out .= $s[$scursor];
6724 }
6725 }
6726 if ($clean) {
6727 return $out;
6728 }
6729 return $ok;
6730}
6731
6740function dol_nboflines($s, $maxchar = 0)
6741{
6742 if ($s == '') {
6743 return 0;
6744 }
6745 $arraystring = explode("\n", $s);
6746 $nb = count($arraystring);
6747
6748 return $nb;
6749}
6750
6751
6761function dol_nboflines_bis($text, $maxlinesize = 0, $charset = 'UTF-8')
6762{
6763 $repTable = array("\t" => " ", "\n" => "<br>", "\r" => " ", "\0" => " ", "\x0B" => " ");
6764 if (dol_textishtml($text)) {
6765 $repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
6766 }
6767
6768 $text = strtr($text, $repTable);
6769 if ($charset == 'UTF-8') {
6770 $pattern = '/(<br[^>]*>)/Uu';
6771 } else {
6772 // /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
6773 $pattern = '/(<br[^>]*>)/U'; // /U is to have UNGREEDY regex to limit to one html tag.
6774 }
6775 $a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
6776
6777 $nblines = (int) floor((count($a) + 1) / 2);
6778 // count possible auto line breaks
6779 if ($maxlinesize) {
6780 foreach ($a as $line) {
6781 if (dol_strlen($line) > $maxlinesize) {
6782 //$line_dec = html_entity_decode(strip_tags($line));
6783 $line_dec = html_entity_decode($line);
6784 if (dol_strlen($line_dec) > $maxlinesize) {
6785 $line_dec = wordwrap($line_dec, $maxlinesize, '\n', true);
6786 $nblines += substr_count($line_dec, '\n');
6787 }
6788 }
6789 }
6790 }
6791
6792 unset($a);
6793 return $nblines;
6794}
6795
6804function dol_textishtml($msg, $option = 0)
6805{
6806 if (is_null($msg)) {
6807 return false;
6808 }
6809
6810 if ($option == 1) {
6811 if (preg_match('/<(html|link|script)/i', $msg)) {
6812 return true;
6813 } elseif (preg_match('/<body/i', $msg)) {
6814 return true;
6815 } elseif (preg_match('/<\/textarea/i', $msg)) {
6816 return true;
6817 } elseif (preg_match('/<(b|em|i|u)(\s+[^>]+)?>/i', $msg)) {
6818 return true;
6819 } elseif (preg_match('/<br/i', $msg)) {
6820 return true;
6821 }
6822 return false;
6823 } else {
6824 // Remove all urls because 'http://aa?param1=abc&amp;param2=def' must not be used inside detection
6825 $msg = preg_replace('/https?:\/\/[^"\'\s]+/i', '', $msg);
6826 if (preg_match('/<(html|link|script|body)/i', $msg)) {
6827 return true;
6828 } elseif (preg_match('/<\/textarea/i', $msg)) {
6829 return true;
6830 } elseif (preg_match('/<(b|em|i|u)(\s+[^>]+)?>/i', $msg)) {
6831 return true;
6832 } elseif (preg_match('/<(br|hr)\/>/i', $msg)) {
6833 return true;
6834 } elseif (preg_match('/<(br|hr|div|font|li|p|span|strong|table)>/i', $msg)) {
6835 return true;
6836 } elseif (preg_match('/<(br|hr|div|font|li|p|span|strong|table)\s+[^<>\/]*\/?>/i', $msg)) {
6837 return true;
6838 } elseif (preg_match('/<img\s+[^<>]*src[^<>]*>/i', $msg)) {
6839 return true; // must accept <img src="http://example.com/aaa.png" />
6840 } elseif (preg_match('/<a\s+[^<>]*href[^<>]*>/i', $msg)) {
6841 return true; // must accept <a href="http://example.com/aaa.png" />
6842 } elseif (preg_match('/<h[0-9]>/i', $msg)) {
6843 return true;
6844 } elseif (preg_match('/&[A-Z0-9]{1,6};/i', $msg)) {
6845 // TODO If content is 'A link https://aaa?param=abc&amp;param2=def', it return true but must be false
6846 return true; // Html entities names (http://www.w3schools.com/tags/ref_entities.asp)
6847 } elseif (preg_match('/&#[0-9]{2,3};/i', $msg)) {
6848 return true; // Html entities numbers (http://www.w3schools.com/tags/ref_entities.asp)
6849 } elseif (preg_match('/&#x[a-f0-9][a-f0-9];/i', $msg)) {
6850 return true; // Html entities numbers in hexa
6851 }
6852
6853 return false;
6854 }
6855}
6856
6871function dol_concatdesc($text1, $text2, $forxml = false, $invert = false)
6872{
6873 if (!empty($invert)) {
6874 $tmp = $text1;
6875 $text1 = $text2;
6876 $text2 = $tmp;
6877 }
6878
6879 $ret = '';
6880 $ret .= (!dol_textishtml($text1) && dol_textishtml($text2)) ? dol_nl2br(dol_escape_htmltag($text1, 0, 1, '', 1), 0, $forxml) : $text1;
6881 $ret .= (!empty($text1) && !empty($text2)) ? ((dol_textishtml($text1) || dol_textishtml($text2)) ? ($forxml ? "<br >\n" : "<br>\n") : "\n") : "";
6882 $ret .= (dol_textishtml($text1) && !dol_textishtml($text2)) ? dol_nl2br(dol_escape_htmltag($text2, 0, 1, '', 1), 0, $forxml) : $text2;
6883 return $ret;
6884}
6885
6894function dol_concat($text1, $text2)
6895{
6896 return $text1.$text2;
6897}
6898
6906function safeArrayMap($callback, array $array)
6907{
6908 if (!is_string($callback)) {
6909 throw new InvalidArgumentException("Les callbacks sont désactivés.");
6910 }
6911 // Check that $callback is a sure function
6912 $allowed_callbacks = ['strtolower', 'strtoupper', 'intval'];
6913 if (!in_array($callback, $allowed_callbacks, true)) {
6914 throw new InvalidArgumentException("Callback function not allowed.");
6915 }
6916 return array_map($callback, $array);
6917}
6918
6919
6933function getCommonSubstitutionArray($outputlangs, $onlykey = 0, $exclude = null, $object = null, $include = null)
6934{
6935 global $db, $conf, $mysoc, $user, $extrafields;
6936
6937 $substitutionarray = array();
6938
6939 if ((empty($exclude) || !in_array('user', $exclude)) && (empty($include) || in_array('user', $include)) && $user instanceof User) {
6940 // Add SIGNATURE into substitutionarray first, so, when we will make the substitution,
6941 // this will include signature content first and then replace var found into content of signature
6942 //var_dump($onlykey);
6943 $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()
6944 $usersignature = $user->signature;
6945 if (getDolGlobalString('MAIN_MAIL_DEFAULT_SIGNATURE_FOR_ALL_USERS') && !empty($user->employee)) {
6946 $emailsendersignature = getDolGlobalString('MAIN_MAIL_DEFAULT_SIGNATURE_FOR_ALL_USERS');
6947 $usersignature = getDolGlobalString('MAIN_MAIL_DEFAULT_SIGNATURE_FOR_ALL_USERS');
6948 }
6949 $substitutionarray = array_merge($substitutionarray, array(
6950 '__SENDEREMAIL_SIGNATURE__' => (string) ((!getDolGlobalString('MAIN_MAIL_DO_NOT_USE_SIGN')) ? ($onlykey == 2 ? dol_trunc('SignatureFromTheSelectedSenderProfile', 30) : $emailsendersignature) : ''),
6951 '__USER_SIGNATURE__' => (string) (($usersignature && !getDolGlobalString('MAIN_MAIL_DO_NOT_USE_SIGN')) ? ($onlykey == 2 ? dol_trunc(dol_string_nohtmltag($usersignature), 30) : $usersignature) : '')
6952 ));
6953
6954 if (is_object($user) && ($user instanceof User)) {
6955 $substitutionarray = array_merge($substitutionarray, array(
6956 '__USER_ID__' => (string) $user->id,
6957 '__USER_LOGIN__' => (string) $user->login,
6958 '__USER_EMAIL__' => (string) $user->email,
6959 '__USER_PHONE__' => (string) dol_print_phone($user->office_phone, '', 0, 0, '', " ", '', '', -1),
6960 '__USER_PHONEPRO__' => (string) dol_print_phone($user->user_mobile, '', 0, 0, '', " ", '', '', -1),
6961 '__USER_PHONEMOBILE__' => (string) dol_print_phone($user->personal_mobile, '', 0, 0, '', " ", '', '', -1),
6962 '__USER_FAX__' => (string) $user->office_fax,
6963 '__USER_LASTNAME__' => (string) $user->lastname,
6964 '__USER_FIRSTNAME__' => (string) $user->firstname,
6965 '__USER_FULLNAME__' => (string) $user->getFullName($outputlangs),
6966 '__USER_SUPERVISOR_ID__' => (string) ($user->fk_user ? $user->fk_user : '0'),
6967 '__USER_JOB__' => (string) $user->job,
6968 '__USER_REMOTE_IP__' => (string) getUserRemoteIP(),
6969 '__USER_VCARD_URL__' => (string) $user->getOnlineVirtualCardUrl('', 'external')
6970 ));
6971 if (isModEnabled('stock') && getDolGlobalString('MAIN_DEFAULT_WAREHOUSE_USER') && is_object($user->warehouse)) {
6972 $substitutionarray = array_merge($substitutionarray, array(
6973 '__USER_WAREHOUSE_ID__' => isset($user->warehouse->id) ? $user->warehouse->id : '',
6974 '__USER_WAREHOUSE_REF__' => isset($user->warehouse->ref) ? $user->warehouse->ref : '',
6975 '__USER_WAREHOUSE_DESCRIPTION__' => isset($user->warehouse->description) ? $user->warehouse->description : '',
6976 '__USER_WAREHOUSE_ADDRESS__' => isset($user->warehouse->address) ? $user->warehouse->address : '',
6977 '__USER_WAREHOUSE_ZIP__' => isset($user->warehouse->zip) ? $user->warehouse->zip : '',
6978 '__USER_WAREHOUSE_TOWN__' => isset($user->warehouse->town) ? $user->warehouse->town : '',
6979 '__USER_WAREHOUSE_PHONE__' => isset($user->warehouse->phone) ? (string) dol_print_phone($user->warehouse->phone, '', 0, 0, '', " ", '', '', -1) : '',
6980 '__USER_WAREHOUSE_FAX__' => isset($user->warehouse->fax) ? (string) dol_print_phone($user->warehouse->fax, '', 0, 0, '', " ", '', '', -1) : ''
6981 ));
6982 }
6983 }
6984 }
6985 if ((empty($exclude) || !in_array('mycompany', $exclude)) && is_object($mysoc) && (empty($include) || in_array('mycompany', $include))) {
6986 $substitutionarray = array_merge($substitutionarray, array(
6987 '__MYCOMPANY_NAME__' => $mysoc->name,
6988 '__MYCOMPANY_EMAIL__' => $mysoc->email,
6989 '__MYCOMPANY_URL__' => $mysoc->url,
6990 '__MYCOMPANY_PHONE__' => dol_print_phone((string) $mysoc->phone, '', 0, 0, '', " ", '', '', -1),
6991 '__MYCOMPANY_PHONEMOBILE__' => dol_print_phone((string) $mysoc->phone_mobile, '', 0, 0, '', " ", '', '', -1),
6992 '__MYCOMPANY_FAX__' => dol_print_phone((string) $mysoc->fax, '', 0, 0, '', " ", '', '', -1),
6993 '__MYCOMPANY_PROFID1__' => $mysoc->idprof1,
6994 '__MYCOMPANY_PROFID2__' => $mysoc->idprof2,
6995 '__MYCOMPANY_PROFID3__' => $mysoc->idprof3,
6996 '__MYCOMPANY_PROFID4__' => $mysoc->idprof4,
6997 '__MYCOMPANY_PROFID5__' => $mysoc->idprof5,
6998 '__MYCOMPANY_PROFID6__' => $mysoc->idprof6,
6999 '__MYCOMPANY_PROFID7__' => $mysoc->idprof7,
7000 '__MYCOMPANY_PROFID8__' => $mysoc->idprof8,
7001 '__MYCOMPANY_PROFID9__' => $mysoc->idprof9,
7002 '__MYCOMPANY_PROFID10__' => $mysoc->idprof10,
7003 '__MYCOMPANY_CAPITAL__' => $mysoc->capital,
7004 '__MYCOMPANY_FULLADDRESS__' => (method_exists($mysoc, 'getFullAddress') ? $mysoc->getFullAddress(1, ', ') : ''), // $mysoc may be stdClass
7005 '__MYCOMPANY_ADDRESS__' => $mysoc->address,
7006 '__MYCOMPANY_VATNUMBER__' => $mysoc->tva_intra,
7007 '__MYCOMPANY_ZIP__' => $mysoc->zip,
7008 '__MYCOMPANY_TOWN__' => $mysoc->town,
7009 '__MYCOMPANY_STATE__' => $mysoc->state,
7010 '__MYCOMPANY_COUNTRY__' => $mysoc->country,
7011 '__MYCOMPANY_COUNTRY_ID__' => $mysoc->country_id,
7012 '__MYCOMPANY_COUNTRY_CODE__' => $mysoc->country_code,
7013 '__MYCOMPANY_CURRENCY_CODE__' => $conf->currency
7014 ));
7015 }
7016
7017 if (($onlykey || is_object($object)) && (empty($exclude) || !in_array('object', $exclude)) && (empty($include) || in_array('object', $include))) {
7018 if ($onlykey) {
7019 $substitutionarray['__ID__'] = '__ID__';
7020 $substitutionarray['__REF__'] = '__REF__';
7021 $substitutionarray['__NEWREF__'] = '__NEWREF__';
7022 $substitutionarray['__LABEL__'] = '__LABEL__';
7023 $substitutionarray['__REF_CLIENT__'] = '__REF_CLIENT__';
7024 $substitutionarray['__REF_SUPPLIER__'] = '__REF_SUPPLIER__';
7025 $substitutionarray['__NOTE_PUBLIC__'] = '__NOTE_PUBLIC__';
7026 $substitutionarray['__NOTE_PRIVATE__'] = '__NOTE_PRIVATE__';
7027 $substitutionarray['__EXTRAFIELD_XXX__'] = '__EXTRAFIELD_XXX__';
7028
7029 if (isModEnabled("societe")) { // Most objects are concerned
7030 $substitutionarray['__THIRDPARTY_ID__'] = '__THIRDPARTY_ID__';
7031 $substitutionarray['__THIRDPARTY_NAME__'] = '__THIRDPARTY_NAME__';
7032 $substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = '__THIRDPARTY_NAME_ALIAS__';
7033 $substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = '__THIRDPARTY_CODE_CLIENT__';
7034 $substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = '__THIRDPARTY_CODE_FOURNISSEUR__';
7035 $substitutionarray['__THIRDPARTY_EMAIL__'] = '__THIRDPARTY_EMAIL__';
7036 //$substitutionarray['__THIRDPARTY_EMAIL_URLENCODED__'] = '__THIRDPARTY_EMAIL_URLENCODED__'; // We hide this one
7037 $substitutionarray['__THIRDPARTY_URL__'] = '__THIRDPARTY_URL__';
7038 //$substitutionarray['__THIRDPARTY_URL_URLENCODED__'] = '__THIRDPARTY_URL_URLENCODED__'; // We hide this one
7039 $substitutionarray['__THIRDPARTY_PHONE__'] = '__THIRDPARTY_PHONE__';
7040 $substitutionarray['__THIRDPARTY_FAX__'] = '__THIRDPARTY_FAX__';
7041 $substitutionarray['__THIRDPARTY_ADDRESS__'] = '__THIRDPARTY_ADDRESS__';
7042 $substitutionarray['__THIRDPARTY_ZIP__'] = '__THIRDPARTY_ZIP__';
7043 $substitutionarray['__THIRDPARTY_TOWN__'] = '__THIRDPARTY_TOWN__';
7044 $substitutionarray['__THIRDPARTY_STATE__'] = '__THIRDPARTY_STATE__';
7045 $substitutionarray['__THIRDPARTY_IDPROF1__'] = '__THIRDPARTY_IDPROF1__';
7046 $substitutionarray['__THIRDPARTY_IDPROF2__'] = '__THIRDPARTY_IDPROF2__';
7047 $substitutionarray['__THIRDPARTY_IDPROF3__'] = '__THIRDPARTY_IDPROF3__';
7048 $substitutionarray['__THIRDPARTY_IDPROF4__'] = '__THIRDPARTY_IDPROF4__';
7049 $substitutionarray['__THIRDPARTY_IDPROF5__'] = '__THIRDPARTY_IDPROF5__';
7050 $substitutionarray['__THIRDPARTY_IDPROF6__'] = '__THIRDPARTY_IDPROF6__';
7051 $substitutionarray['__THIRDPARTY_IDPROF7__'] = '__THIRDPARTY_IDPROF7__';
7052 $substitutionarray['__THIRDPARTY_IDPROF8__'] = '__THIRDPARTY_IDPROF8__';
7053 $substitutionarray['__THIRDPARTY_IDPROF9__'] = '__THIRDPARTY_IDPROF9__';
7054 $substitutionarray['__THIRDPARTY_IDPROF10__'] = '__THIRDPARTY_IDPROF10__';
7055 $substitutionarray['__THIRDPARTY_TVAINTRA__'] = '__THIRDPARTY_TVAINTRA__';
7056 $substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = '__THIRDPARTY_NOTE_PUBLIC__';
7057 $substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = '__THIRDPARTY_NOTE_PRIVATE__';
7058 }
7059 if (isModEnabled('member') && (!is_object($object) || $object->element == 'adherent') && (empty($exclude) || !in_array('member', $exclude)) && (empty($include) || in_array('member', $include))) {
7060 $substitutionarray['__MEMBER_ID__'] = '__MEMBER_ID__';
7061 $substitutionarray['__MEMBER_TITLE__'] = '__MEMBER_TITLE__';
7062 $substitutionarray['__MEMBER_FIRSTNAME__'] = '__MEMBER_FIRSTNAME__';
7063 $substitutionarray['__MEMBER_LASTNAME__'] = '__MEMBER_LASTNAME__';
7064 $substitutionarray['__MEMBER_USER_LOGIN_INFORMATION__'] = 'Login and pass of the external user account';
7065 /*$substitutionarray['__MEMBER_NOTE_PUBLIC__'] = '__MEMBER_NOTE_PUBLIC__';
7066 $substitutionarray['__MEMBER_NOTE_PRIVATE__'] = '__MEMBER_NOTE_PRIVATE__';*/
7067 }
7068 // add substitution variables for ticket
7069 if (isModEnabled('ticket') && (!is_object($object) || $object->element == 'ticket') && (empty($exclude) || !in_array('ticket', $exclude)) && (empty($include) || in_array('ticket', $include))) {
7070 $substitutionarray['__TICKET_TRACKID__'] = '__TICKET_TRACKID__';
7071 $substitutionarray['__TICKET_SUBJECT__'] = '__TICKET_SUBJECT__';
7072 $substitutionarray['__TICKET_TYPE__'] = '__TICKET_TYPE__';
7073 $substitutionarray['__TICKET_SEVERITY__'] = '__TICKET_SEVERITY__';
7074 $substitutionarray['__TICKET_CATEGORY__'] = '__TICKET_CATEGORY__';
7075 $substitutionarray['__TICKET_ANALYTIC_CODE__'] = '__TICKET_ANALYTIC_CODE__';
7076 $substitutionarray['__TICKET_MESSAGE__'] = '__TICKET_MESSAGE__';
7077 $substitutionarray['__TICKET_PROGRESSION__'] = '__TICKET_PROGRESSION__';
7078 $substitutionarray['__TICKET_USER_ASSIGN__'] = '__TICKET_USER_ASSIGN__';
7079 }
7080 if (isModEnabled('recruitment') && (!is_object($object) || $object->element == 'recruitmentcandidature') && (empty($exclude) || !in_array('recruitment', $exclude)) && (empty($include) || in_array('recruitment', $include))) {
7081 $substitutionarray['__CANDIDATE_FULLNAME__'] = '__CANDIDATE_FULLNAME__';
7082 $substitutionarray['__CANDIDATE_FIRSTNAME__'] = '__CANDIDATE_FIRSTNAME__';
7083 $substitutionarray['__CANDIDATE_LASTNAME__'] = '__CANDIDATE_LASTNAME__';
7084 }
7085 if (isModEnabled('holiday') && (!is_object($object) || $object->element == 'holiday') && (empty($exclude) || !in_array('holiday', $exclude)) && (empty($include) || in_array('holiday', $include))) {
7086 $substitutionarray['__HOLIDAY_ARRAY_PER_EMPLOYEE_FOR_PERIOD__'] = '__HOLIDAY_ARRAY_PER_EMPLOYEE_FOR_PERIOD__';
7087 }
7088 if (isModEnabled('project') && (empty($exclude) || !in_array('project', $exclude)) && (empty($include) || in_array('project', $include))) { // Most objects
7089 $substitutionarray['__PROJECT_ID__'] = '__PROJECT_ID__';
7090 $substitutionarray['__PROJECT_REF__'] = '__PROJECT_REF__';
7091 $substitutionarray['__PROJECT_NAME__'] = '__PROJECT_NAME__';
7092 /*$substitutionarray['__PROJECT_NOTE_PUBLIC__'] = '__PROJECT_NOTE_PUBLIC__';
7093 $substitutionarray['__PROJECT_NOTE_PRIVATE__'] = '__PROJECT_NOTE_PRIVATE__';*/
7094 }
7095 if (isModEnabled('contract') && (!is_object($object) || $object->element == 'contract') && (empty($exclude) || !in_array('contract', $exclude)) && (empty($include) || in_array('contract', $include))) {
7096 $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = 'Highest date planned for a service start';
7097 $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = 'Highest date and hour planned for service start';
7098 $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = 'Lowest data for planned expiration of service';
7099 $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = 'Lowest date and hour for planned expiration of service';
7100 }
7101 if (isModEnabled("propal") && (!is_object($object) || $object->element == 'propal') && (empty($exclude) || !in_array('propal', $exclude)) && (empty($include) || in_array('propal', $include))) {
7102 $substitutionarray['__ONLINE_SIGN_URL__'] = 'ToOfferALinkForOnlineSignature';
7103 }
7104 if (isModEnabled("intervention") && (!is_object($object) || $object->element == 'fichinter') && (empty($exclude) || !in_array('intervention', $exclude)) && (empty($include) || in_array('intervention', $include))) {
7105 $substitutionarray['__ONLINE_SIGN_FICHINTER_URL__'] = 'ToOfferALinkForOnlineSignature';
7106 }
7107 $substitutionarray['__ONLINE_PAYMENT_URL__'] = 'UrlToPayOnlineIfApplicable';
7108 $substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__'] = 'TextAndUrlToPayOnlineIfApplicable';
7109 $substitutionarray['__SECUREKEYPAYMENT__'] = 'Security key (if key is not unique per record)';
7110 $substitutionarray['__SECUREKEYPAYMENT_MEMBER__'] = 'Security key for payment on a member subscription (one key per member)';
7111 $substitutionarray['__SECUREKEYPAYMENT_ORDER__'] = 'Security key for payment on an order';
7112 $substitutionarray['__SECUREKEYPAYMENT_INVOICE__'] = 'Security key for payment on an invoice';
7113 $substitutionarray['__SECUREKEYPAYMENT_CONTRACTLINE__'] = 'Security key for payment on a service of a contract';
7114
7115 $substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = 'Direct download url of a proposal';
7116 $substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = 'Direct download url of an order';
7117 $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = 'Direct download url of an invoice';
7118 $substitutionarray['__DIRECTDOWNLOAD_URL_CONTRACT__'] = 'Direct download url of a contract';
7119 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_PROPOSAL__'] = 'Direct download url of a supplier proposal';
7120 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_ORDER__'] = 'Direct download url of a supplier order';
7121 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_INVOICE__'] = 'Direct download url of a supplier invoice';
7122
7123 if (isModEnabled("shipping") && (!is_object($object) || $object->element == 'shipping')) {
7124 $substitutionarray['__SHIPPINGTRACKNUM__'] = 'Shipping tracking number';
7125 $substitutionarray['__SHIPPINGTRACKNUMURL__'] = 'Shipping tracking url';
7126 $substitutionarray['__SHIPPINGMETHOD__'] = 'Shipping method';
7127 }
7128 if (isModEnabled("reception") && (!is_object($object) || $object->element == 'reception')) {
7129 $substitutionarray['__RECEPTIONTRACKNUM__'] = 'Shipping tracking number of shipment';
7130 $substitutionarray['__RECEPTIONTRACKNUMURL__'] = 'Shipping tracking url';
7131 }
7132 } else {
7133 '@phan-var-force Adherent|Delivery $object';
7135 $substitutionarray['__ID__'] = $object->id;
7136 $substitutionarray['__REF__'] = $object->ref;
7137 $substitutionarray['__NEWREF__'] = $object->newref;
7138 $substitutionarray['__LABEL__'] = (isset($object->label) ? $object->label : (isset($object->title) ? $object->title : null));
7139 $substitutionarray['__REF_CLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : null));
7140 $substitutionarray['__REF_SUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
7141 $substitutionarray['__NOTE_PUBLIC__'] = (isset($object->note_public) ? $object->note_public : null);
7142 $substitutionarray['__NOTE_PRIVATE__'] = (isset($object->note_private) ? $object->note_private : null);
7143
7144 $substitutionarray['__DATE_CREATION__'] = (isset($object->date_creation) ? dol_print_date($object->date_creation, 'day', false, $outputlangs) : '');
7145 $substitutionarray['__DATE_MODIFICATION__'] = (isset($object->date_modification) ? dol_print_date($object->date_modification, 'day', false, $outputlangs) : '');
7146 $substitutionarray['__DATE_VALIDATION__'] = (isset($object->date_validation) ? dol_print_date($object->date_validation, 'day', false, $outputlangs) : '');
7147
7148 // handle date_delivery: in customer order/supplier order, the property name is delivery_date, in shipment/reception it is date_delivery
7149 $date_delivery = null;
7150 if (property_exists($object, 'date_delivery')) {
7151 $date_delivery = $object->date_delivery;
7152 } elseif (property_exists($object, 'delivery_date')) {
7153 $date_delivery = $object->delivery_date;
7154 }
7155 $substitutionarray['__DATE_DELIVERY__'] = (isset($date_delivery) ? dol_print_date($date_delivery, 'day', false, $outputlangs) : '');
7156 $substitutionarray['__DATE_DELIVERY_DAY__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%d") : '');
7157 $substitutionarray['__DATE_DELIVERY_DAY_TEXT__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%A") : '');
7158 $substitutionarray['__DATE_DELIVERY_MON__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%m") : '');
7159 $substitutionarray['__DATE_DELIVERY_MON_TEXT__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%b") : '');
7160 $substitutionarray['__DATE_DELIVERY_YEAR__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%Y") : '');
7161 $substitutionarray['__DATE_DELIVERY_HH__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%H") : '');
7162 $substitutionarray['__DATE_DELIVERY_MM__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%M") : '');
7163 $substitutionarray['__DATE_DELIVERY_SS__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%S") : '');
7164
7165 // For backward compatibility (deprecated)
7166 $substitutionarray['__REFCLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : null));
7167 $substitutionarray['__REFSUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
7168
7169 $substitutionarray['__SUPPLIER_ORDER_DATE_DELIVERY__'] = (isset($date_delivery) ? dol_print_date($date_delivery, 'day', false, $outputlangs) : '');
7170 $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 : '')) : '');
7171 $substitutionarray['__EXPIRATION_DATE__'] = (isset($object->fin_validite) ? dol_print_date($object->fin_validite, 'daytext') : '');
7172
7173 if (is_object($object) && ($object->element == 'adherent' || $object->element == 'member') && $object->id > 0) {
7174 '@phan-var-force Adherent $object';
7176 $birthday = (empty($object->birth) ? '' : dol_print_date($object->birth, 'day'));
7177
7178 $substitutionarray['__MEMBER_ID__'] = (isset($object->id) ? $object->id : '');
7179 if (method_exists($object, 'getCivilityLabel')) {
7180 $substitutionarray['__MEMBER_TITLE__'] = $object->getCivilityLabel();
7181 }
7182 $substitutionarray['__MEMBER_FIRSTNAME__'] = (isset($object->firstname) ? $object->firstname : '');
7183 $substitutionarray['__MEMBER_LASTNAME__'] = (isset($object->lastname) ? $object->lastname : '');
7184 $substitutionarray['__MEMBER_USER_LOGIN_INFORMATION__'] = '';
7185 if (method_exists($object, 'getFullName')) {
7186 $substitutionarray['__MEMBER_FULLNAME__'] = $object->getFullName($outputlangs);
7187 }
7188 $substitutionarray['__MEMBER_COMPANY__'] = (isset($object->societe) ? $object->societe : '');
7189 $substitutionarray['__MEMBER_ADDRESS__'] = (isset($object->address) ? $object->address : '');
7190 $substitutionarray['__MEMBER_ZIP__'] = (isset($object->zip) ? $object->zip : '');
7191 $substitutionarray['__MEMBER_TOWN__'] = (isset($object->town) ? $object->town : '');
7192 $substitutionarray['__MEMBER_STATE__'] = (isset($object->state) ? $object->state : '');
7193 $substitutionarray['__MEMBER_COUNTRY__'] = (isset($object->country) ? $object->country : '');
7194 $substitutionarray['__MEMBER_EMAIL__'] = (isset($object->email) ? $object->email : '');
7195 $substitutionarray['__MEMBER_BIRTH__'] = (isset($birthday) ? $birthday : '');
7196 $substitutionarray['__MEMBER_PHOTO__'] = (isset($object->photo) ? $object->photo : '');
7197 $substitutionarray['__MEMBER_LOGIN__'] = (isset($object->login) ? $object->login : '');
7198 $substitutionarray['__MEMBER_PASSWORD__'] = (isset($object->pass) ? $object->pass : '');
7199 $substitutionarray['__MEMBER_PHONE__'] = (isset($object->phone) ? dol_print_phone($object->phone) : '');
7200 $substitutionarray['__MEMBER_PHONEPRO__'] = (isset($object->phone_perso) ? dol_print_phone($object->phone_perso) : '');
7201 $substitutionarray['__MEMBER_PHONEMOBILE__'] = (isset($object->phone_mobile) ? dol_print_phone($object->phone_mobile) : '');
7202 $substitutionarray['__MEMBER_TYPE__'] = (isset($object->type) ? $object->type : '');
7203 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE__'] = dol_print_date($object->first_subscription_date, 'day');
7204
7205 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_RFC__'] = dol_print_date($object->first_subscription_date, 'dayrfc');
7206 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_START__'] = (isset($object->first_subscription_date_start) ? dol_print_date($object->first_subscription_date_start, 'day') : '');
7207 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_START_RFC__'] = (isset($object->first_subscription_date_start) ? dol_print_date($object->first_subscription_date_start, 'dayrfc') : '');
7208 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_END__'] = (isset($object->first_subscription_date_end) ? dol_print_date($object->first_subscription_date_end, 'day') : '');
7209 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_END_RFC__'] = (isset($object->first_subscription_date_end) ? dol_print_date($object->first_subscription_date_end, 'dayrfc') : '');
7210 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE__'] = dol_print_date($object->last_subscription_date, 'day');
7211 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_RFC__'] = dol_print_date($object->last_subscription_date, 'dayrfc');
7212 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_START__'] = dol_print_date($object->last_subscription_date_start, 'day');
7213 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_START_RFC__'] = dol_print_date($object->last_subscription_date_start, 'dayrfc');
7214 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_END__'] = dol_print_date($object->last_subscription_date_end, 'day');
7215 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_END_RFC__'] = dol_print_date($object->last_subscription_date_end, 'dayrfc');
7216 }
7217
7218 if (is_object($object) && $object->element == 'societe') {
7220 '@phan-var-force Societe $object';
7221 $substitutionarray['__THIRDPARTY_ID__'] = $object->id ?? '';
7222 $substitutionarray['__THIRDPARTY_NAME__'] = $object->name ?? '';
7223 $substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = $object->name_alias ?? '';
7224 $substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = $object->code_client ?? '';
7225 $substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = $object->code_fournisseur ?? '';
7226 $substitutionarray['__THIRDPARTY_EMAIL__'] = $object->email ?? '';
7227 $substitutionarray['__THIRDPARTY_EMAIL_URLENCODED__'] = urlencode($object->email ?? '');
7228 $substitutionarray['__THIRDPARTY_URL__'] = $object->url ?? '';
7229 $substitutionarray['__THIRDPARTY_URL_URLENCODED__'] = urlencode($object->url ?? '');
7230 $substitutionarray['__THIRDPARTY_PHONE__'] = dol_print_phone($object->phone ?? '');
7231 $substitutionarray['__THIRDPARTY_FAX__'] = dol_print_phone($object->fax ?? '');
7232 $substitutionarray['__THIRDPARTY_ADDRESS__'] = $object->address ?? '';
7233 $substitutionarray['__THIRDPARTY_ZIP__'] = $object->zip ?? '';
7234 $substitutionarray['__THIRDPARTY_TOWN__'] = $object->town ?? '';
7235 $substitutionarray['__THIRDPARTY_STATE__'] = $object->state ?? '';
7236 $substitutionarray['__THIRDPARTY_COUNTRY_ID__'] = ($object->country_id > 0 ?: '');
7237 $substitutionarray['__THIRDPARTY_COUNTRY_CODE__'] = $object->country_code ?? '';
7238 $substitutionarray['__THIRDPARTY_IDPROF1__'] = $object->idprof1 ?? '';
7239 $substitutionarray['__THIRDPARTY_IDPROF2__'] = $object->idprof2 ?? '';
7240 $substitutionarray['__THIRDPARTY_IDPROF3__'] = $object->idprof3 ?? '';
7241 $substitutionarray['__THIRDPARTY_IDPROF4__'] = $object->idprof4 ?? '';
7242 $substitutionarray['__THIRDPARTY_IDPROF5__'] = $object->idprof5 ?? '';
7243 $substitutionarray['__THIRDPARTY_IDPROF6__'] = $object->idprof6 ?? '';
7244 $substitutionarray['__THIRDPARTY_TVAINTRA__'] = $object->tva_intra ?? '';
7245 $substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = dol_htmlentitiesbr($object->note_public ?? '');
7246 $substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = dol_htmlentitiesbr($object->note_private ?? '');
7247 } elseif (is_object($object) && is_object($object->thirdparty)) {
7248 $substitutionarray['__THIRDPARTY_ID__'] = $object->thirdparty->id ?? '';
7249 $substitutionarray['__THIRDPARTY_NAME__'] = $object->thirdparty->name ?? '';
7250 $substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = $object->thirdparty->name_alias ?? '';
7251 $substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = $object->thirdparty->code_client ?? '';
7252 $substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = $object->thirdparty->code_fournisseur ?? '';
7253 $substitutionarray['__THIRDPARTY_EMAIL__'] = $object->thirdparty->email ?? '';
7254 $substitutionarray['__THIRDPARTY_EMAIL_URLENCODED__'] = urlencode($object->thirdparty->email ?? '');
7255 $substitutionarray['__THIRDPARTY_PHONE__'] = dol_print_phone($object->thirdparty->phone ?? '');
7256 $substitutionarray['__THIRDPARTY_FAX__'] = dol_print_phone($object->thirdparty->fax ?? '');
7257 $substitutionarray['__THIRDPARTY_ADDRESS__'] = $object->thirdparty->address ?? '';
7258 $substitutionarray['__THIRDPARTY_ZIP__'] = $object->thirdparty->zip ?? '';
7259 $substitutionarray['__THIRDPARTY_TOWN__'] = $object->thirdparty->town ?? '';
7260 $substitutionarray['__THIRDPARTY_STATE__'] = $object->thirdparty->state ?? '';
7261 $substitutionarray['__THIRDPARTY_COUNTRY_ID__'] = ($object->thirdparty->country_id > 0 ?: '');
7262 $substitutionarray['__THIRDPARTY_COUNTRY_CODE__'] = $object->thirdparty->country_code ?? '';
7263 $substitutionarray['__THIRDPARTY_IDPROF1__'] = $object->thirdparty->idprof1 ?? '';
7264 $substitutionarray['__THIRDPARTY_IDPROF2__'] = $object->thirdparty->idprof2 ?? '';
7265 $substitutionarray['__THIRDPARTY_IDPROF3__'] = $object->thirdparty->idprof3 ?? '';
7266 $substitutionarray['__THIRDPARTY_IDPROF4__'] = $object->thirdparty->idprof4 ?? '';
7267 $substitutionarray['__THIRDPARTY_IDPROF5__'] = $object->thirdparty->idprof5 ?? '';
7268 $substitutionarray['__THIRDPARTY_IDPROF6__'] = $object->thirdparty->idprof6 ?? '';
7269 $substitutionarray['__THIRDPARTY_TVAINTRA__'] = $object->thirdparty->tva_intra ?? '';
7270 $substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = dol_htmlentitiesbr($object->thirdparty->note_public ?? '');
7271 $substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = dol_htmlentitiesbr($object->thirdparty->note_private ?? '');
7272 }
7273
7274 if (is_object($object) && $object->element == 'recruitmentcandidature') {
7275 '@phan-var-force RecruitmentCandidature $object';
7277 $substitutionarray['__CANDIDATE_FULLNAME__'] = $object->getFullName($outputlangs);
7278 $substitutionarray['__CANDIDATE_FIRSTNAME__'] = isset($object->firstname) ? $object->firstname : '';
7279 $substitutionarray['__CANDIDATE_LASTNAME__'] = isset($object->lastname) ? $object->lastname : '';
7280 }
7281 if (is_object($object) && $object->element == 'conferenceorboothattendee') {
7282 '@phan-var-force ConferenceOrBoothAttendee $object';
7284 $substitutionarray['__ATTENDEE_FULLNAME__'] = $object->getFullName($outputlangs);
7285 $substitutionarray['__ATTENDEE_FIRSTNAME__'] = isset($object->firstname) ? $object->firstname : '';
7286 $substitutionarray['__ATTENDEE_LASTNAME__'] = isset($object->lastname) ? $object->lastname : '';
7287 }
7288
7289 if (is_object($object) && $object->element == 'project') {
7290 '@phan-var-force Project $object';
7292 $substitutionarray['__PROJECT_ID__'] = $object->id;
7293 $substitutionarray['__PROJECT_REF__'] = $object->ref;
7294 $substitutionarray['__PROJECT_NAME__'] = $object->title;
7295 } elseif (is_object($object)) {
7296 $project = null;
7297 if (!empty($object->project)) {
7298 $project = $object->project;
7299 }
7300 if (!is_null($project) && is_object($project)) {
7301 $substitutionarray['__PROJECT_ID__'] = $project->id;
7302 $substitutionarray['__PROJECT_REF__'] = $project->ref;
7303 $substitutionarray['__PROJECT_NAME__'] = $project->title;
7304 } else {
7305 // can substitute variables for project : uses lazy load in "make_substitutions" method
7306 $project_id = 0;
7307 if (!empty($object->fk_project) && $object->fk_project > 0) {
7308 $project_id = $object->fk_project;
7309 } elseif (!empty($object->fk_projet) && $object->fk_projet > 0) {
7310 $project_id = $object->fk_project;
7311 }
7312 if ($project_id > 0) {
7313 // path:class:method:id
7314 $substitutionarray['__PROJECT_ID__@lazyload'] = '/projet/class/project.class.php:Project:fetchAndSetSubstitution:' . $project_id;
7315 $substitutionarray['__PROJECT_REF__@lazyload'] = '/projet/class/project.class.php:Project:fetchAndSetSubstitution:' . $project_id;
7316 $substitutionarray['__PROJECT_NAME__@lazyload'] = '/projet/class/project.class.php:Project:fetchAndSetSubstitution:' . $project_id;
7317 }
7318 }
7319 }
7320
7321 if (is_object($object) && $object->element == 'facture') {
7322 '@phan-var-force Facture $object';
7324 $substitutionarray['__INVOICE_SITUATION_NUMBER__'] = isset($object->situation_counter) ? $object->situation_counter : '';
7325 }
7326 if (is_object($object) && $object->element == 'shipping') {
7327 '@phan-var-force Expedition $object';
7329 $substitutionarray['__SHIPPINGTRACKNUM__'] = $object->tracking_number;
7330 $substitutionarray['__SHIPPINGTRACKNUMURL__'] = $object->tracking_url;
7331 $substitutionarray['__SHIPPINGMETHOD__'] = $object->shipping_method;
7332 }
7333 if (is_object($object) && $object->element == 'reception') {
7334 '@phan-var-force Reception $object';
7336 $substitutionarray['__RECEPTIONTRACKNUM__'] = $object->tracking_number;
7337 $substitutionarray['__RECEPTIONTRACKNUMURL__'] = $object->tracking_url;
7338 }
7339
7340 if (is_object($object) && $object->element == 'contrat' && $object->id > 0 && is_array($object->lines)) {
7341 '@phan-var-force Contrat $object';
7343 $dateplannedstart = '';
7344 $datenextexpiration = '';
7345 foreach ($object->lines as $line) {
7346 if ($line->date_start > $dateplannedstart) {
7347 $dateplannedstart = $line->date_start;
7348 }
7349 if ($line->statut == 4 && $line->date_end && (!$datenextexpiration || $line->date_end < $datenextexpiration)) {
7350 $datenextexpiration = $line->date_end;
7351 }
7352 }
7353 $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = dol_print_date($dateplannedstart, 'day');
7354 $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE_RFC__'] = dol_print_date($dateplannedstart, 'dayrfc');
7355 $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = dol_print_date($dateplannedstart, 'standard');
7356
7357 $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = dol_print_date($datenextexpiration, 'day');
7358 $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE_RFC__'] = dol_print_date($datenextexpiration, 'dayrfc');
7359 $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = dol_print_date($datenextexpiration, 'standard');
7360 }
7361 // add substitution variables for ticket
7362 if (is_object($object) && $object->element == 'ticket') {
7363 '@phan-var-force Ticket $object';
7365 $substitutionarray['__TICKET_TRACKID__'] = $object->track_id;
7366 $substitutionarray['__TICKET_SUBJECT__'] = $object->subject;
7367 $substitutionarray['__TICKET_TYPE__'] = $object->type_code;
7368 $substitutionarray['__TICKET_SEVERITY__'] = $object->severity_code;
7369 $substitutionarray['__TICKET_CATEGORY__'] = $object->category_code; // For backward compatibility
7370 $substitutionarray['__TICKET_ANALYTIC_CODE__'] = $object->category_code;
7371 $substitutionarray['__TICKET_MESSAGE__'] = $object->message;
7372 $substitutionarray['__TICKET_PROGRESSION__'] = $object->progress;
7373 $userstat = new User($db);
7374 if ($object->fk_user_assign > 0) {
7375 $userstat->fetch($object->fk_user_assign);
7376 $substitutionarray['__TICKET_USER_ASSIGN__'] = dolGetFirstLastname($userstat->firstname, $userstat->lastname);
7377 }
7378
7379 if ($object->fk_user_create > 0) {
7380 $userstat->fetch($object->fk_user_create);
7381 $substitutionarray['__USER_CREATE__'] = dolGetFirstLastname($userstat->firstname, $userstat->lastname);
7382 }
7383 }
7384
7385 // Create dynamic tags for __EXTRAFIELD_FIELD__
7386 if ($object->table_element && $object->id > 0) {
7387 if (!is_object($extrafields)) {
7388 $extrafields = new ExtraFields($db);
7389 }
7390 $extrafields->fetch_name_optionals_label($object->table_element, true);
7391
7392 if ($object->fetch_optionals() > 0) { // @FIXME: Remove this, the fetch should have been done already, by the caller of getCommonSubstitutionArray()
7393 if (is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label']) > 0) {
7394 foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $label) {
7395 if ($extrafields->attributes[$object->table_element]['type'][$key] == 'date') {
7396 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = dol_print_date($object->array_options['options_' . $key], 'day');
7397 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_LOCALE__'] = dol_print_date($object->array_options['options_' . $key], 'day', 'tzserver', $outputlangs);
7398 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_RFC__'] = dol_print_date($object->array_options['options_' . $key], 'dayrfc');
7399 } elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'datetime') {
7400 $datetime = $object->array_options['options_' . $key];
7401 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhour') : '');
7402 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_LOCALE__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhour', 'tzserver', $outputlangs) : '');
7403 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_DAY_LOCALE__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'day', 'tzserver', $outputlangs) : '');
7404 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_RFC__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhourrfc') : '');
7405 } elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'phone') {
7406 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = dol_print_phone($object->array_options['options_' . $key]);
7407 } elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'price') {
7408 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = $object->array_options['options_' . $key];
7409 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_FORMATED__'] = price($object->array_options['options_' . $key]); // For compatibility
7410 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_FORMATTED__'] = price($object->array_options['options_' . $key]);
7411 } elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'select') {
7412 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = !empty($object->array_options['options_' . $key]) ? $object->array_options['options_' . $key] : '';
7413 $val = $extrafields->attributes[$object->table_element]['param'][$key]['options'][$object->array_options['options_'.$key]] ?? $object->array_options['options_'.$key];
7414 $substitutionarray['__EXTRAFIELD_'.strtoupper($key).'_LABEL__'] = $val;
7415 } elseif ($extrafields->attributes[$object->table_element]['type'][$key] != 'separator') {
7416 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = !empty($object->array_options['options_' . $key]) ? $object->array_options['options_' . $key] : '';
7417 }
7418 }
7419 }
7420 }
7421 }
7422
7423 // Complete substitution array with the url to make online payment
7424 if (empty($substitutionarray['__REF__'])) {
7425 $paymenturl = '';
7426 } else {
7427 // Set the online payment url link into __ONLINE_PAYMENT_URL__ key
7428 require_once DOL_DOCUMENT_ROOT . '/core/lib/payments.lib.php';
7429 $outputlangs->loadLangs(array('paypal', 'other'));
7430
7431 $amounttouse = 0;
7432 $typeforonlinepayment = 'free';
7433 if (is_object($object) && $object->element == 'commande') {
7434 $typeforonlinepayment = 'order';
7435 }
7436 if (is_object($object) && $object->element == 'facture') {
7437 $typeforonlinepayment = 'invoice';
7438 }
7439 if (is_object($object) && $object->element == 'member') {
7440 $typeforonlinepayment = 'member';
7441 if (!empty($object->last_subscription_amount)) {
7442 $amounttouse = $object->last_subscription_amount;
7443 }
7444 }
7445 if (is_object($object) && $object->element == 'contrat') {
7446 $typeforonlinepayment = 'contract';
7447 }
7448 if (is_object($object) && $object->element == 'fichinter') {
7449 $typeforonlinepayment = 'ficheinter';
7450 }
7451
7452 $url = getOnlinePaymentUrl(0, $typeforonlinepayment, $substitutionarray['__REF__'], (float) $amounttouse);
7453 $paymenturl = $url;
7454 }
7455
7456 if ($object->id > 0) {
7457 $substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__'] = ($paymenturl ? str_replace('\n', "\n", $outputlangs->trans("PredefinedMailContentLink", $paymenturl)) : '');
7458 $substitutionarray['__ONLINE_PAYMENT_URL__'] = $paymenturl;
7459
7460 // Show structured communication
7461 if (getDolGlobalString('INVOICE_PAYMENT_ENABLE_STRUCTURED_COMMUNICATION') && $object->element == 'facture') {
7462 include_once DOL_DOCUMENT_ROOT . '/core/lib/functions_be.lib.php';
7463 $substitutionarray['__PAYMENT_STRUCTURED_COMMUNICATION__'] = dolBECalculateStructuredCommunication((string) $object->ref, $object->type);
7464 }
7465
7466 if (getDolGlobalString('PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'propal') {
7467 $substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = $object->getLastMainDocLink($object->element);
7468 } else {
7469 $substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = '';
7470 }
7471 if (getDolGlobalString('ORDER_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'commande') {
7472 $substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = $object->getLastMainDocLink($object->element);
7473 } else {
7474 $substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = '';
7475 }
7476 if (getDolGlobalString('INVOICE_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'facture') {
7477 $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = $object->getLastMainDocLink($object->element);
7478 } else {
7479 $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = '';
7480 }
7481 if (getDolGlobalString('CONTRACT_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'contrat') {
7482 $substitutionarray['__DIRECTDOWNLOAD_URL_CONTRACT__'] = $object->getLastMainDocLink($object->element);
7483 } else {
7484 $substitutionarray['__DIRECTDOWNLOAD_URL_CONTRACT__'] = '';
7485 }
7486 if (getDolGlobalString('FICHINTER_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'fichinter') {
7487 $substitutionarray['__DIRECTDOWNLOAD_URL_FICHINTER__'] = $object->getLastMainDocLink($object->element);
7488 } else {
7489 $substitutionarray['__DIRECTDOWNLOAD_URL_FICHINTER__'] = '';
7490 }
7491 if (getDolGlobalString('SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'supplier_proposal') {
7492 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_PROPOSAL__'] = $object->getLastMainDocLink($object->element);
7493 } else {
7494 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_PROPOSAL__'] = '';
7495 }
7496 if (getDolGlobalString('SUPPLIER_ORDER_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'order_supplier') {
7497 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_ORDER__'] = $object->getLastMainDocLink($object->element);
7498 } else {
7499 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_ORDER__'] = '';
7500 }
7501 if (getDolGlobalString('SUPPLIER_INVOICE_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'invoice_supplier') {
7502 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_INVOICE__'] = $object->getLastMainDocLink($object->element);
7503 } else {
7504 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_INVOICE__'] = '';
7505 }
7506
7507 if (is_object($object) && $object->element == 'propal') {
7508 '@phan-var-force Propal $object';
7510 $substitutionarray['__URL_PROPOSAL__'] = DOL_MAIN_URL_ROOT . "/comm/propal/card.php?id=" . $object->id;
7511 require_once DOL_DOCUMENT_ROOT . '/core/lib/signature.lib.php';
7512 $substitutionarray['__ONLINE_SIGN_URL__'] = getOnlineSignatureUrl(0, 'proposal', (string) $object->ref, 1, $object);
7513 }
7514 if (is_object($object) && $object->element == 'commande') {
7515 '@phan-var-force Commande $object';
7517 $substitutionarray['__URL_ORDER__'] = DOL_MAIN_URL_ROOT . "/commande/card.php?id=" . $object->id;
7518 }
7519 if (is_object($object) && $object->element == 'facture') {
7520 '@phan-var-force Facture $object';
7522 $substitutionarray['__URL_INVOICE__'] = DOL_MAIN_URL_ROOT . "/compta/facture/card.php?id=" . $object->id;
7523 }
7524 if (is_object($object) && $object->element == 'contrat') {
7525 '@phan-var-force Contrat $object';
7527 $substitutionarray['__URL_CONTRACT__'] = DOL_MAIN_URL_ROOT . "/contrat/card.php?id=" . $object->id;
7528 require_once DOL_DOCUMENT_ROOT . '/core/lib/signature.lib.php';
7529 $substitutionarray['__ONLINE_SIGN_URL__'] = getOnlineSignatureUrl(0, 'contract', (string) $object->ref, 1, $object);
7530 }
7531 if (is_object($object) && $object->element == 'fichinter') {
7532 '@phan-var-force Fichinter $object';
7534 $substitutionarray['__URL_FICHINTER__'] = DOL_MAIN_URL_ROOT . "/fichinter/card.php?id=" . $object->id;
7535 require_once DOL_DOCUMENT_ROOT . '/core/lib/signature.lib.php';
7536 $substitutionarray['__ONLINE_SIGN_FICHINTER_URL__'] = getOnlineSignatureUrl(0, 'fichinter', (string) $object->ref, 1, $object);
7537 }
7538 if (is_object($object) && $object->element == 'supplier_proposal') {
7539 '@phan-var-force SupplierProposal $object';
7541 $substitutionarray['__URL_SUPPLIER_PROPOSAL__'] = DOL_MAIN_URL_ROOT . "/supplier_proposal/card.php?id=" . $object->id;
7542 }
7543 if (is_object($object) && $object->element == 'invoice_supplier') {
7544 '@phan-var-force FactureFournisseur $object';
7546 $substitutionarray['__URL_SUPPLIER_INVOICE__'] = DOL_MAIN_URL_ROOT . "/fourn/facture/card.php?id=" . $object->id;
7547 }
7548 if (is_object($object) && $object->element == 'payment_supplier') {
7549 '@phan-var-force PaiementFourn $object';
7551 //print_r($object);
7552 $liste_factures = [];
7553 $total = 0;
7554
7555 // @FIXME We must not have any repeated SQL access into this function.
7556 $sql = 'SELECT f.ref,f.multicurrency_code as f_mccode, pf.*
7557 FROM '.MAIN_DB_PREFIX.'paiementfourn_facturefourn as pf
7558 JOIN '.MAIN_DB_PREFIX.'facture_fourn as f ON pf.fk_facturefourn = f.rowid
7559 WHERE pf.fk_paiementfourn = '.((int) $object->id);
7560
7561 $resql = $db->query($sql);
7562 if ($resql) {
7563 while ($objp = $db->fetch_object($resql)) {
7564 $liste_factures[] = ' - '.$outputlangs->trans('Invoice').' '. $objp->ref.' '.$outputlangs->trans('AmountPayed').' '.price($objp->multicurrency_amount, 0, $outputlangs, 0, -1, -1, $objp->multicurrency_code);
7565 }
7566 }
7567 $substitutionarray['__SUPPLIER_PAYMENT_INVOICES_LIST__'] = implode("\n", $liste_factures);
7568 ;
7569 $substitutionarray['__SUPPLIER_PAYMENT_INVOICES_TOTAL__'] = price($object->multicurrency_amount, 0, $outputlangs, 0, -1, -1, $object->multicurrency_code ? $object->multicurrency_code : $conf->currency);
7570 }
7571 if (is_object($object) && $object->element == 'shipping') {
7572 '@phan-var-force Expedition $object';
7574 $substitutionarray['__URL_SHIPMENT__'] = DOL_MAIN_URL_ROOT . "/expedition/card.php?id=" . $object->id;
7575 if (getDolGlobalInt('EXPEDITION_ALLOW_ONLINESIGN')) {
7576 require_once DOL_DOCUMENT_ROOT . '/core/lib/signature.lib.php';
7577 $substitutionarray['__ONLINE_SIGN_URL__'] = getOnlineSignatureUrl(0, 'expedition', (string) $object->ref, 1, $object);
7578 }
7579 }
7580 }
7581
7582 if (is_object($object) && $object->element == 'action') {
7583 '@phan-var-force ActionComm $object';
7585 $substitutionarray['__EVENT_LABEL__'] = $object->label;
7586 $substitutionarray['__EVENT_DESCRIPTION__'] = $object->note;
7587 $substitutionarray['__EVENT_TYPE__'] = $outputlangs->trans("Action" . $object->type_code);
7588 $substitutionarray['__EVENT_DATE__'] = dol_print_date($object->datep, 'day', 'auto', $outputlangs);
7589 $substitutionarray['__EVENT_TIME__'] = dol_print_date($object->datep, 'hour', 'auto', $outputlangs);
7590 $substitutionarray['__EVENT_DATE_TZUSER__'] = dol_print_date($object->datep, 'day', 'tzuserrel', $outputlangs);
7591 $substitutionarray['__EVENT_TIME_TZUSER__'] = dol_print_date($object->datep, 'hour', 'tzuserrel', $outputlangs);
7592 }
7593 }
7594 }
7595
7596 if ((empty($exclude) || !in_array('objectamount', $exclude)) && (empty($include) || in_array('objectamount', $include))) {
7597 '@phan-var-force Facture|FactureRec $object';
7599 include_once DOL_DOCUMENT_ROOT . '/core/lib/functionsnumtoword.lib.php';
7600
7601 $substitutionarray['__DATE_YMD__'] = is_object($object) ? (isset($object->date) ? dol_print_date($object->date, 'day', false, $outputlangs) : null) : '';
7602 $substitutionarray['__DATE_DUE_YMD__'] = is_object($object) ? (isset($object->date_lim_reglement) ? dol_print_date($object->date_lim_reglement, 'day', false, $outputlangs) : null) : '';
7603 $substitutionarray['__DATE_YMD_TEXT__'] = is_object($object) ? (isset($object->date) ? dol_print_date($object->date, 'daytext', false, $outputlangs) : null) : '';
7604 $substitutionarray['__DATE_DUE_YMD_TEXT__'] = is_object($object) ? (isset($object->date_lim_reglement) ? dol_print_date($object->date_lim_reglement, 'daytext', false, $outputlangs) : null) : '';
7605
7606 $already_payed_all = 0;
7607 if (is_object($object) && ($object instanceof Facture)) {
7608 $already_payed_all = $object->totalpaid + $object->totaldeposits + $object->totalcreditnotes;
7609 }
7610
7611 $substitutionarray['__SIMPLE_HTML_TABLE__'] = is_object($object) && !empty($object->lines) ? showSimpleHTMLTable($outputlangs, $object) : "";
7612 $substitutionarray['__AMOUNT_EXCL_TAX__'] = is_object($object) ? $object->total_ht : '';
7613 $substitutionarray['__AMOUNT_EXCL_TAX_TEXT__'] = is_object($object) ? dol_convertToWord($object->total_ht, $outputlangs, '', true) : '';
7614 $substitutionarray['__AMOUNT_EXCL_TAX_TEXTCURRENCY__'] = is_object($object) ? dol_convertToWord($object->total_ht, $outputlangs, $conf->currency, true) : '';
7615
7616 $substitutionarray['__AMOUNT__'] = is_object($object) ? $object->total_ttc : '';
7617 $substitutionarray['__AMOUNT_TEXT__'] = is_object($object) ? dol_convertToWord($object->total_ttc, $outputlangs, '', true) : '';
7618 $substitutionarray['__AMOUNT_TEXTCURRENCY__'] = is_object($object) ? dol_convertToWord($object->total_ttc, $outputlangs, $conf->currency, true) : '';
7619
7620 $substitutionarray['__DEPOSIT_PERCENT__'] = is_object($object) ? $object->deposit_percent : '';
7621 $substitutionarray['__DEPOSIT_AMOUNT__'] = is_object($object) ? price2num($object->total_ttc * ($object->deposit_percent / 100), 'MT') : '';
7622
7623 $substitutionarray['__AMOUNT_REMAIN__'] = is_object($object) ? price2num($object->total_ttc - $already_payed_all, 'MT') : '';
7624
7625 $substitutionarray['__AMOUNT_VAT__'] = is_object($object) ? (isset($object->total_vat) ? $object->total_vat : $object->total_tva) : '';
7626 $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)) : '';
7627 $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)) : '';
7628
7629 $mysocuselocaltax1 = false;
7630 $mysocuselocaltax2 = false;
7631 if ($mysoc instanceof Societe && !empty($mysoc->country_code)) {
7632 $tmparray = $mysoc->useLocalTax(-1);
7633 $mysocuselocaltax1 = $tmparray[1];
7634 $mysocuselocaltax2 = $tmparray[2];
7635 }
7636
7637 // Local taxes
7638 if ($onlykey != 2 || $mysocuselocaltax1) {
7639 $substitutionarray['__AMOUNT_TAX2__'] = is_object($object) ? $object->total_localtax1 : '';
7640 }
7641 if ($onlykey != 2 || $mysocuselocaltax2) {
7642 $substitutionarray['__AMOUNT_TAX3__'] = is_object($object) ? $object->total_localtax2 : '';
7643 }
7644
7645 // Amount keys formatted in a currency
7646 $substitutionarray['__AMOUNT_EXCL_TAX_FORMATTED__'] = is_object($object) ? ($object->total_ht ? price($object->total_ht, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
7647 $substitutionarray['__AMOUNT_FORMATTED__'] = is_object($object) ? ($object->total_ttc ? price($object->total_ttc, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
7648 $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) : '';
7649 $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)) : '';
7650 if ($onlykey != 2 || $mysocuselocaltax1) {
7651 $substitutionarray['__AMOUNT_TAX2_FORMATTED__'] = is_object($object) ? ($object->total_localtax1 ? price($object->total_localtax1, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
7652 }
7653 if ($onlykey != 2 || $mysocuselocaltax2) {
7654 $substitutionarray['__AMOUNT_TAX3_FORMATTED__'] = is_object($object) ? ($object->total_localtax2 ? price($object->total_localtax2, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
7655 }
7656 // Amount keys formatted in a currency (with the typo error for backward compatibility)
7657 if ($onlykey != 2) {
7658 $substitutionarray['__AMOUNT_EXCL_TAX_FORMATED__'] = $substitutionarray['__AMOUNT_EXCL_TAX_FORMATTED__'];
7659 $substitutionarray['__AMOUNT_FORMATED__'] = $substitutionarray['__AMOUNT_FORMATTED__'];
7660 $substitutionarray['__AMOUNT_REMAIN_FORMATED__'] = $substitutionarray['__AMOUNT_REMAIN_FORMATTED__'];
7661 $substitutionarray['__AMOUNT_VAT_FORMATED__'] = $substitutionarray['__AMOUNT_VAT_FORMATTED__'];
7662 if ($mysocuselocaltax1) {
7663 $substitutionarray['__AMOUNT_TAX2_FORMATED__'] = $substitutionarray['__AMOUNT_TAX2_FORMATTED__'];
7664 }
7665 if ($mysoc->useLocalTax2) {
7666 $substitutionarray['__AMOUNT_TAX3_FORMATED__'] = $substitutionarray['__AMOUNT_TAX3_FORMATTED__'];
7667 }
7668 }
7669
7670 $substitutionarray['__AMOUNT_MULTICURRENCY__'] = (is_object($object) && isset($object->multicurrency_total_ttc)) ? $object->multicurrency_total_ttc : '';
7671 $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) : '';
7672 $substitutionarray['__AMOUNT_MULTICURRENCY_TEXT__'] = (is_object($object) && isset($object->multicurrency_total_ttc)) ? dol_convertToWord($object->multicurrency_total_ttc, $outputlangs, '', true) : '';
7673 $substitutionarray['__AMOUNT_MULTICURRENCY_TEXTCURRENCY__'] = (is_object($object) && isset($object->multicurrency_total_ttc)) ? dol_convertToWord($object->multicurrency_total_ttc, $outputlangs, $object->multicurrency_code, true) : '';
7674 $substitutionarray['__MULTICURRENCY_CODE__'] = (is_object($object) && isset($object->multicurrency_code)) ? $object->multicurrency_code : '';
7675 // TODO Add other keys for foreign multicurrency
7676
7677 // For backward compatibility
7678 if ($onlykey != 2) {
7679 $substitutionarray['__TOTAL_TTC__'] = is_object($object) ? $object->total_ttc : '';
7680 $substitutionarray['__TOTAL_HT__'] = is_object($object) ? $object->total_ht : '';
7681 $substitutionarray['__TOTAL_VAT__'] = is_object($object) ? (isset($object->total_vat) ? $object->total_vat : $object->total_tva) : '';
7682 }
7683 }
7684
7685 if ((empty($exclude) || !in_array('date', $exclude)) && (empty($include) || in_array('date', $include))) {
7686 include_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php';
7687
7688 $now = dol_now();
7689
7690 $tmp = dol_getdate($now, true);
7691 $tmp2 = dol_get_prev_day($tmp['mday'], $tmp['mon'], $tmp['year']);
7692 $tmp3 = dol_get_prev_month($tmp['mon'], $tmp['year']);
7693 $tmp4 = dol_get_next_day($tmp['mday'], $tmp['mon'], $tmp['year']);
7694 $tmp5 = dol_get_next_month($tmp['mon'], $tmp['year']);
7695
7696 $daytext = $outputlangs->trans('Day' . $tmp['wday']);
7697
7698 $substitutionarray = array_merge($substitutionarray, array(
7699 '__NOW_TMS__' => (string) $now, // Must be the string that represent the int
7700 '__NOW_TMS_YMD__' => dol_print_date($now, 'day', 'auto', $outputlangs),
7701 '__DAY__' => (string) $tmp['mday'],
7702 '__DAY_TEXT__' => $daytext, // Monday
7703 '__DAY_TEXT_SHORT__' => dol_trunc($daytext, 3, 'right', 'UTF-8', 1), // Mon
7704 '__DAY_TEXT_MIN__' => dol_trunc($daytext, 1, 'right', 'UTF-8', 1), // M
7705 '__MONTH__' => (string) $tmp['mon'],
7706 '__MONTH_TEXT__' => $outputlangs->transnoentitiesnoconv('Month' . sprintf("%02d", $tmp['mon'])),
7707 '__MONTH_TEXT_SHORT__' => $outputlangs->transnoentitiesnoconv('MonthShort' . sprintf("%02d", $tmp['mon'])),
7708 '__MONTH_TEXT_MIN__' => $outputlangs->transnoentitiesnoconv('MonthVeryShort' . sprintf("%02d", $tmp['mon'])),
7709 '__YEAR__' => (string) $tmp['year'],
7710 '__YEAR_PREVIOUS_MONTH__' => (string) $tmp3['year'],
7711 '__YEAR_NEXT_MONTH__' => (string) $tmp5['year'],
7712 '__PREVIOUS_DAY__' => (string) $tmp2['day'],
7713 '__PREVIOUS_MONTH__' => (string) $tmp3['month'],
7714 '__PREVIOUS_MONTH_TEXT__' => $outputlangs->transnoentitiesnoconv('Month' . sprintf("%02d", $tmp3['month'])),
7715 '__PREVIOUS_MONTH_TEXT_SHORT__' => $outputlangs->transnoentitiesnoconv('MonthShort' . sprintf("%02d", $tmp3['month'])),
7716 '__PREVIOUS_MONTH_TEXT_MIN__' => $outputlangs->transnoentitiesnoconv('MonthVeryShort' . sprintf("%02d", $tmp3['month'])),
7717 '__PREVIOUS_YEAR__' => (string) ($tmp['year'] - 1),
7718 '__NEXT_DAY__' => (string) $tmp4['day'],
7719 '__NEXT_MONTH__' => (string) $tmp5['month'],
7720 '__NEXT_MONTH_TEXT__' => $outputlangs->transnoentitiesnoconv('Month' . sprintf("%02d", $tmp5['month'])),
7721 '__NEXT_MONTH_TEXT_SHORT__' => $outputlangs->transnoentitiesnoconv('MonthShort' . sprintf("%02d", $tmp5['month'])),
7722 '__NEXT_MONTH_TEXT_MIN__' => $outputlangs->transnoentitiesnoconv('MonthVeryShort' . sprintf("%02d", $tmp5['month'])),
7723 '__NEXT_YEAR__' => (string) ($tmp['year'] + 1),
7724 ));
7725 }
7726
7727 if (isModEnabled('multicompany')) {
7728 $substitutionarray = array_merge($substitutionarray, array('__ENTITY_ID__' => $conf->entity));
7729 }
7730 if ((empty($exclude) || !in_array('system', $exclude)) && (empty($include) || in_array('user', $include))) {
7731 $substitutionarray['__DOL_MAIN_URL_ROOT__'] = DOL_MAIN_URL_ROOT;
7732 $substitutionarray['__(AnyTranslationKey)__'] = $outputlangs->transnoentitiesnoconv('TranslationOfKey');
7733 $substitutionarray['__(AnyTranslationKey|langfile)__'] = $outputlangs->transnoentitiesnoconv('TranslationOfKey') . ' (load also language file before)';
7734 $substitutionarray['__[AnyConstantKey]__'] = $outputlangs->transnoentitiesnoconv('ValueOfConstantKey');
7735 }
7736
7737 // Note: The lazyload variables are replaced only during the call by make_substitutions, and only if necessary
7738
7739 return $substitutionarray;
7740}
7741
7758function make_substitutions($text, $substitutionarray, $outputlangs = null, $converttextinhtmlifnecessary = 0)
7759{
7760 global $db, $langs;
7761
7762 if (!is_array($substitutionarray)) {
7763 return 'ErrorBadParameterSubstitutionArrayWhenCalling_make_substitutions';
7764 }
7765
7766 if (empty($outputlangs)) {
7767 $outputlangs = $langs;
7768 }
7769
7770 // Is initial text HTML or simple text ?
7771 $msgishtml = 0;
7772 if (dol_textishtml($text, 1)) {
7773 $msgishtml = 1;
7774 }
7775
7776 // Make substitution for language keys: __(AnyTranslationKey)__ or __(AnyTranslationKey|langfile)__
7777 if (is_object($outputlangs)) {
7778 $reg = array();
7779 while (preg_match('/__\‍(([^\‍)]+)\‍)__/', $text, $reg)) {
7780 // If key is __(TranslationKey|langfile)__, then force load of langfile.lang
7781 $tmp = explode('|', $reg[1]);
7782 if (!empty($tmp[1])) {
7783 $outputlangs->load($tmp[1]);
7784 }
7785
7786 $value = $outputlangs->transnoentitiesnoconv($reg[1]);
7787
7788 if (empty($converttextinhtmlifnecessary)) {
7789 // convert $newval into HTML is necessary
7790 $text = preg_replace('/__\‍(' . preg_quote($reg[1], '/') . '\‍)__/', $msgishtml ? dol_htmlentitiesbr($value) : $value, $text);
7791 } else {
7792 if (! $msgishtml) {
7793 $valueishtml = dol_textishtml($value, 1);
7794 //var_dump("valueishtml=".$valueishtml);
7795
7796 if ($valueishtml) {
7797 $text = dol_htmlentitiesbr($text);
7798 $msgishtml = 1;
7799 }
7800 } else {
7801 $value = dol_nl2br((string) $value);
7802 }
7803
7804 $text = preg_replace('/__\‍(' . preg_quote($reg[1], '/') . '\‍)__/', $value, $text);
7805 }
7806 }
7807 }
7808
7809 // Make substitution for constant keys.
7810 // Must be after the substitution of translation, so if the text of translation contains a string __[xxx]__, it is also converted.
7811 $reg = array();
7812 while (preg_match('/__\[([^\]]+)\]__/', $text, $reg)) {
7813 $originalkeyfound = $reg[1];
7814 $keyfound = preg_replace('/\|urlencode$/', '', $originalkeyfound);
7815
7816 if (isASecretKey($keyfound)) {
7817 $value = '*****forbidden*****';
7818 } else {
7819 $value = getDolGlobalString($keyfound);
7820 // Execute some functions on value of substitution key
7821 if (preg_match('/\|urlencode$/', $originalkeyfound)) {
7822 $value = urlencode($value);
7823 }
7824 }
7825
7826 if (empty($converttextinhtmlifnecessary)) {
7827 // convert $newval into HTML is necessary
7828 $text = preg_replace('/__\[' . preg_quote($originalkeyfound, '/') . '\]__/', $msgishtml ? dol_htmlentitiesbr($value) : $value, $text);
7829 } else {
7830 if (! $msgishtml) {
7831 $valueishtml = dol_textishtml($value, 1);
7832
7833 if ($valueishtml) {
7834 $text = dol_htmlentitiesbr($text);
7835 $msgishtml = 1;
7836 }
7837 } else {
7838 $value = dol_nl2br((string) $value);
7839 }
7840
7841 $text = preg_replace('/__\[' . preg_quote($originalkeyfound, '/') . '\]__/', $value, $text);
7842 }
7843 }
7844
7845 // Make substitution for array $substitutionarray
7846 foreach ($substitutionarray as $key => $value) {
7847 if (!isset($value)) {
7848 continue; // If value is null, it same than not having substitution key at all into array, we do not replace.
7849 }
7850
7851 if (getDolGlobalString('MAIN_MAIL_DO_NOT_USE_SIGN') && ($key == '__USER_SIGNATURE__' || $key == '__SENDEREMAIL_SIGNATURE__')) {
7852 $value = ''; // Protection
7853 }
7854
7855 if (empty($converttextinhtmlifnecessary)) {
7856 $text = str_replace((string) $key, (string) $value, $text); // Cast to string is needed when value is 123.5 for example
7857 } else {
7858 if (! $msgishtml) {
7859 $valueishtml = dol_textishtml($value, 1);
7860
7861 if ($valueishtml) {
7862 $text = dol_htmlentitiesbr($text);
7863 $msgishtml = 1;
7864 }
7865 } else {
7866 $value = dol_nl2br((string) $value);
7867 }
7868 $text = str_replace((string) $key, (string) $value, $text); // Cast to string is needed 123.5 for example
7869 }
7870 }
7871
7872 /*
7873 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
7874 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.
7875 If no, we don't need to make replacement, so we do nothing.
7876 If yes, we can make the substitution:
7877
7878 include_once $path;
7879 $tmpobj = new $class($db);
7880 $valuetouseforsubstitution = $tmpobj->$method($id, '__XXX__');
7881 And make the replacement of "__XXX__@lazyload" with $valuetouseforsubstitution
7882 */
7883 $memory_object_list = array();
7884 foreach ($substitutionarray as $key => $value) {
7885 $lazy_load_arr = array();
7886 if (preg_match('/(__[A-Z\_]+__)@lazyload$/', $key, $lazy_load_arr)) {
7887 if (isset($lazy_load_arr[1]) && !empty($lazy_load_arr[1])) {
7888 $key_to_substitute = $lazy_load_arr[1];
7889 if (preg_match('/' . preg_quote($key_to_substitute, '/') . '/', $text)) {
7890 $param_arr = explode(':', (string) $value);
7891 // path:class:method:id
7892 if (count($param_arr) >= 4) {
7893 $path = $param_arr[0];
7894 $class = $param_arr[1];
7895 $method = $param_arr[2];
7896 $id = (int) $param_arr[3];
7897 $keyinarrayresult = empty($param_arr[4]) ? '' : $param_arr[4];
7898
7899 // load class file and init object list in memory
7900 if (!isset($memory_object_list[$class])) {
7901 if (dol_is_file(DOL_DOCUMENT_ROOT . $path)) {
7902 require_once DOL_DOCUMENT_ROOT . $path;
7903 if (class_exists($class)) {
7904 $memory_object_list[$class] = array(
7905 'list' => array(),
7906 );
7907 }
7908 }
7909 }
7910
7911 // fetch object and set substitution
7912 if (isset($memory_object_list[$class]) && isset($memory_object_list[$class]['list'])) {
7913 if (method_exists($class, $method)) {
7914 if (!isset($memory_object_list[$class]['list'][$id])) {
7915 $tmpobj = new $class($db);
7916 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
7917 $tmpvaluetouseforsubstitution = $tmpobj->$method($id, $key_to_substitute);
7918 $memory_object_list[$class]['list'][$id] = $tmpobj;
7919 } else {
7920 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7921 $tmpobj = $memory_object_list[$class]['list'][$id];
7922 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
7923 $tmpvaluetouseforsubstitution = $tmpobj->$method($id, $key_to_substitute, true);
7924 }
7925
7926 if ($keyinarrayresult) {
7927 $valuetouseforsubstitution = (string) $tmpvaluetouseforsubstitution[$keyinarrayresult]; // Cast to string in case value is 123.5 for example
7928 } else {
7929 $valuetouseforsubstitution = (string) $tmpvaluetouseforsubstitution; // Cast to string in case value is 123.5 for example
7930 }
7931 $text = str_replace((string) $key_to_substitute, $valuetouseforsubstitution, $text);
7932 }
7933 }
7934 }
7935 }
7936 }
7937 }
7938 }
7939
7940 return $text;
7941}
7942
7955function complete_substitutions_array(&$substitutionarray, $outputlangs, $object = null, $parameters = null, $callfunc = "completesubstitutionarray")
7956{
7957 global $conf, $user;
7958
7959 require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
7960
7961 // Note: substitution key for each extrafields, using key __EXTRA_XXX__ is already available into the getCommonSubstitutionArray used to build the substitution array.
7962
7963 // Check if there is external substitution to do, requested by plugins
7964 $dirsubstitutions = array_merge(array(), (array) $conf->modules_parts['substitutions']);
7965
7966 foreach ($dirsubstitutions as $reldir) {
7967 $dir = dol_buildpath($reldir, 0);
7968
7969 // Check if directory exists
7970 if (!dol_is_dir($dir)) {
7971 continue;
7972 }
7973
7974 $substitfiles = dol_dir_list($dir, 'files', 0, 'functions_');
7975 foreach ($substitfiles as $substitfile) {
7976 $reg = array();
7977 if (preg_match('/functions_(.*)\.lib\.php/i', $substitfile['name'], $reg)) {
7978 $module = $reg[1];
7979
7980 dol_syslog("Library " . $substitfile['name'] . " found into " . $dir);
7981 // Include the user's functions file
7982 require_once $dir . $substitfile['name'];
7983 // Call the user's function, and only if it is defined
7984 $function_name = $module . "_" . $callfunc;
7985 if (function_exists($function_name)) {
7986 $function_name($substitutionarray, $outputlangs, $object, $parameters);
7987 }
7988 }
7989 }
7990 }
7991 if (getDolGlobalString('ODT_ENABLE_ALL_TAGS_IN_SUBSTITUTIONS')) {
7992 // to list all tags in odt template
7993 $tags = '';
7994 foreach ($substitutionarray as $key => $value) {
7995 $tags .= '{' . $key . '} => ' . $value . "\n";
7996 }
7997 $substitutionarray = array_merge($substitutionarray, array('__ALL_TAGS__' => $tags));
7998 }
7999}
8000
8010function print_date_range($date_start, $date_end, $format = '', $outputlangs = null)
8011{
8012 print get_date_range($date_start, $date_end, $format, $outputlangs);
8013}
8014
8025function get_date_range($date_start, $date_end, $format = '', $outputlangs = null, $withparenthesis = 1)
8026{
8027 global $langs;
8028
8029 $out = '';
8030
8031 if (!is_object($outputlangs)) {
8032 $outputlangs = $langs;
8033 }
8034
8035 if ($date_start && $date_end) {
8036 $out .= ($withparenthesis ? ($withparenthesis == 1 ? ' ' : '').'(' : '') . $outputlangs->transnoentitiesnoconv('DateFromTo', dol_print_date($date_start, $format, false, $outputlangs), dol_print_date($date_end, $format, false, $outputlangs)) . ($withparenthesis ? ')' : '');
8037 }
8038 if ($date_start && !$date_end) {
8039 $out .= ($withparenthesis ? ($withparenthesis == 1 ? ' ' : '').'(' : '') . $outputlangs->transnoentitiesnoconv('DateFrom', dol_print_date($date_start, $format, false, $outputlangs)) . ($withparenthesis ? ')' : '');
8040 }
8041 if (!$date_start && $date_end) {
8042 $out .= ($withparenthesis ? ($withparenthesis == 1 ? ' ' : '').'(' : '') . $outputlangs->transnoentitiesnoconv('DateUntil', dol_print_date($date_end, $format, false, $outputlangs)) . ($withparenthesis ? ')' : '');
8043 }
8044
8045 return $out;
8046}
8047
8056function dolGetFirstLastname($firstname, $lastname, $nameorder = -1)
8057{
8058 $ret = '';
8059 // If order not defined, we use the setup
8060 if ($nameorder < 0) {
8061 $nameorder = (!getDolGlobalString('MAIN_FIRSTNAME_NAME_POSITION') ? 1 : 0);
8062 }
8063 if ($nameorder == 1) {
8064 $ret .= $firstname;
8065 if ($firstname && $lastname) {
8066 $ret .= ' ';
8067 }
8068 $ret .= $lastname;
8069 } elseif ($nameorder == 2 || $nameorder == 3) {
8070 $ret .= $firstname;
8071 if (empty($ret) && $nameorder == 3) {
8072 $ret .= $lastname;
8073 }
8074 } else { // 0, 4 or 5
8075 $ret .= $lastname;
8076 if (empty($ret) && $nameorder == 5) {
8077 $ret .= $firstname;
8078 }
8079 if ($nameorder == 0) {
8080 if ($firstname && $lastname) {
8081 $ret .= ' ';
8082 }
8083 $ret .= $firstname;
8084 }
8085 }
8086 return $ret;
8087}
8088
8089
8097function dolSort($arraytosort)
8098{
8099 sort($arraytosort);
8100 return $arraytosort;
8101}
8102
8124function dol_sort_array(&$array, $index, $order = 'asc', $natsort = 0, $case_sensitive = 0, $keepindex = 0)
8125{
8126 // Clean parameters
8127 $order = strtolower($order);
8128
8129 if (is_array($array)) {
8130 $sizearray = count($array);
8131 if ($sizearray > 0) {
8132 // Build a temp array with sorting key as value
8133 $temp = array();
8134 foreach (array_keys($array) as $key) {
8135 $tmpmultikey = explode(',', $index);
8136 $newindex = $tmpmultikey[0];
8137 if (is_object($array[$key])) {
8138 $temp[$key] = empty($array[$key]->$newindex) ? 0 : $array[$key]->$newindex;
8139 // Add other keys
8140 if (!empty($tmpmultikey[1])) {
8141 $newindex = $tmpmultikey[1];
8142 $temp[$key] .= '__' . (empty($array[$key]->$newindex) ? 0 : $array[$key]->$newindex);
8143 }
8144 } else {
8145 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable,PhanTypeArraySuspicious,PhanTypeMismatchDimFetch
8146 $temp[$key] = empty($array[$key][$newindex]) ? 0 : $array[$key][$newindex];
8147 // Add other keys
8148 if (!empty($tmpmultikey[1])) {
8149 $newindex = $tmpmultikey[1];
8150 // @phan-suppress-next-line PhanTypeArraySuspicious,PhanTypeMismatchDimFetch
8151 $temp[$key] .= '__' . (empty($array[$key][$newindex]) ? 0 : $array[$key][$newindex]);
8152 }
8153 }
8154 if ($natsort == -1) {
8155 $temp[$key] = '___' . $temp[$key]; // We add a string at begin of value to force an alpha order when using asort.
8156 }
8157 }
8158 if (empty($natsort) || $natsort == -1) {
8159 if ($order == 'asc') {
8160 asort($temp);
8161 } else {
8162 arsort($temp);
8163 }
8164 } else {
8165 if ($case_sensitive) {
8166 natsort($temp);
8167 } else {
8168 natcasesort($temp); // natecasesort is not sensible to case
8169 }
8170 if ($order != 'asc') {
8171 $temp = array_reverse($temp, true);
8172 }
8173 }
8174
8175 $sorted = array();
8176
8177 foreach (array_keys($temp) as $key) {
8178 (is_numeric($key) && empty($keepindex)) ? $sorted[] = $array[$key] : $sorted[$key] = $array[$key];
8179 }
8180
8181 return $sorted;
8182 }
8183 }
8184 return $array;
8185}
8186
8187
8195function utf8_check($str)
8196{
8197 $str = (string) $str; // Sometimes string is an int.
8198
8199 // We must use here a binary strlen function (so not dol_strlen)
8200 $strLength = strlen($str);
8201 for ($i = 0; $i < $strLength; $i++) {
8202 if (ord($str[$i]) < 0x80) {
8203 continue; // 0bbbbbbb
8204 } elseif ((ord($str[$i]) & 0xE0) == 0xC0) {
8205 $n = 1; // 110bbbbb
8206 } elseif ((ord($str[$i]) & 0xF0) == 0xE0) {
8207 $n = 2; // 1110bbbb
8208 } elseif ((ord($str[$i]) & 0xF8) == 0xF0) {
8209 $n = 3; // 11110bbb
8210 } elseif ((ord($str[$i]) & 0xFC) == 0xF8) {
8211 $n = 4; // 111110bb
8212 } elseif ((ord($str[$i]) & 0xFE) == 0xFC) {
8213 $n = 5; // 1111110b
8214 } else {
8215 return false; // Does not match any model
8216 }
8217 for ($j = 0; $j < $n; $j++) { // n bytes matching 10bbbbbb follow ?
8218 if ((++$i == strlen($str)) || ((ord($str[$i]) & 0xC0) != 0x80)) {
8219 return false;
8220 }
8221 }
8222 }
8223 return true;
8224}
8225
8233function utf8_valid($str)
8234{
8235 /* 2 other methods to test if string is utf8
8236 $validUTF8 = mb_check_encoding($messagetext, 'UTF-8');
8237 $validUTF8b = ! (false === mb_detect_encoding($messagetext, 'UTF-8', true));
8238 */
8239 return preg_match('//u', $str) ? true : false;
8240}
8241
8242
8249function ascii_check($str)
8250{
8251 if (function_exists('mb_check_encoding')) {
8252 //if (mb_detect_encoding($str, 'ASCII', true) return false;
8253 if (!mb_check_encoding($str, 'ASCII')) {
8254 return false;
8255 }
8256 } else {
8257 if (preg_match('/[^\x00-\x7f]/', $str)) {
8258 return false; // Contains a byte > 7f
8259 }
8260 }
8261
8262 return true;
8263}
8264
8265
8273function dol_osencode($str)
8274{
8275 $tmp = ini_get("unicode.filesystem_encoding");
8276 if (empty($tmp) && !empty($_SERVER["WINDIR"])) {
8277 $tmp = 'iso-8859-1'; // By default for windows
8278 }
8279 if (empty($tmp)) {
8280 $tmp = 'utf-8'; // By default for other
8281 }
8282 if (getDolGlobalString('MAIN_FILESYSTEM_ENCODING')) {
8283 $tmp = getDolGlobalString('MAIN_FILESYSTEM_ENCODING');
8284 }
8285
8286 if ($tmp == 'iso-8859-1') {
8287 return mb_convert_encoding($str, 'ISO-8859-1', 'UTF-8');
8288 }
8289 return $str;
8290}
8291
8292
8308function dol_getIdFromCode($db, $key, $tablename, $fieldkey = 'code', $fieldid = 'id', $entityfilter = 0, $filters = '', $useCache = true)
8309{
8310 global $conf;
8311
8312 // If key empty
8313 if ($key == '') {
8314 return 0;
8315 }
8316
8317 // Check in cache
8318 if ($useCache && isset($conf->cache['codeid'][$tablename][$key][$fieldid])) { // Can be defined to 0 or ''
8319 return $conf->cache['codeid'][$tablename][$key][$fieldid]; // Found in cache
8320 }
8321
8322 dol_syslog('dol_getIdFromCode (value for field ' . $fieldid . ' from key ' . $key . ' not found into cache)', LOG_DEBUG);
8323
8324 $sql = "SELECT " . $db->sanitize($fieldid) . " as valuetoget";
8325 $sql .= " FROM " . MAIN_DB_PREFIX . $db->sanitize($tablename);
8326 if ($fieldkey == 'id' || $fieldkey == 'rowid') {
8327 $sql .= " WHERE " . $db->sanitize($fieldkey) . " = " . ((int) $key);
8328 } else {
8329 $sql .= " WHERE " . $db->sanitize($fieldkey) . " = '" . $db->escape($key) . "'";
8330 }
8331 if (!empty($entityfilter)) {
8332 $sql .= " AND entity IN (" . getEntity($tablename) . ")";
8333 }
8334 if ($filters) {
8335 $sql .= $filters; // @phan-suppress-current-line SqlInjection
8336 }
8337
8338 $resql = $db->query($sql);
8339 if ($resql) {
8340 $obj = $db->fetch_object($resql);
8341 $valuetoget = '';
8342 if ($obj) {
8343 $valuetoget = $obj->valuetoget;
8344 $conf->cache['codeid'][$tablename][$key][$fieldid] = $valuetoget;
8345 } else {
8346 $conf->cache['codeid'][$tablename][$key][$fieldid] = '';
8347 }
8348 $db->free($resql);
8349
8350 return $valuetoget;
8351 } else {
8352 return -1;
8353 }
8354}
8355
8365function isStringVarMatching($var, $regextext, $matchrule = 1)
8366{
8367 // Tolerate callers (custom modules, older code) that already pass a full regex with delimiters
8368 // like '/^(aaa|bbb)/' instead of the bare body. Without this, the function would build
8369 // '/^/^(aaa|bbb)//' which trips preg_match() with 'Unknown modifier ^'.
8370 $regextext = preg_replace('#^/\^?#', '', (string) $regextext);
8371 $regextext = preg_replace('#\$?/[imsxuADSUXJ]*$#', '', $regextext);
8372
8373 if ($matchrule == 1) {
8374 if ($var == 'mainmenu') {
8375 global $mainmenu;
8376 return (preg_match('/^' . $regextext . '/', $mainmenu));
8377 } elseif ($var == 'leftmenu') {
8378 global $leftmenu;
8379 return (preg_match('/^' . $regextext . '/', $leftmenu));
8380 } else {
8381 return 'This variable is not accessible with dol_eval';
8382 }
8383 } else {
8384 return 'This value '.$matchrule.' for param $matchrule is not yet implemented';
8385 }
8386}
8387
8388
8398function verifCond($strToEvaluate, $onlysimplestring = '1')
8399{
8400 //print $strToEvaluate."<br>\n";
8401 $rights = true;
8402 if (isset($strToEvaluate) && $strToEvaluate !== '') {
8403 //var_dump($strToEvaluate);
8404 //$rep = dol_eval($strToEvaluate, 1, 0, '1'); // to show the error
8405 $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
8406
8407 // On string syntax error, dol_eval may return a string that start with 'Bad call of ...' or 'Bad string syntax to evaluate...' !!!
8408 //var_dump($strToEvaluate, $rep);
8409 $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));
8410 //var_dump($rights);
8411 }
8412 return $rights;
8413}
8414
8430function dol_eval($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestring = '1')
8431{
8432 if ($returnvalue != 1) {
8433 dol_syslog("Use of dol_eval with parameter returnvalue = 0 is now forbidden. Please fix this", LOG_ERR);
8434 }
8435
8436 global $dolibarr_main_use_dol_eval_new; // experimental option, not yet ready
8437 if (!empty($dolibarr_main_use_dol_eval_new)) {
8438 return dol_eval_new($s);
8439 } else {
8440 return dol_eval_standard($s, $hideerrors, $onlysimplestring);
8441 }
8442}
8443
8454function dol_eval_new($s)
8455{
8456 // Only this global variables can be read by eval function and returned to caller
8457 global $conf, // Read of const is done with getDolGlobalString() but we need $conf->currency for example
8458 $db, $langs, $user, $website, $websitepage,
8459 $action, $mainmenu, $leftmenu,
8460 $mysoc,
8461 $objectoffield, // To allow the use of $objectoffield in computed fields
8462
8463 // Old variables used
8464 $object;
8465
8466 if (getDolGlobalString('MAIN_ALLOW_OLD_VAR_OBJ_IN_DOL_EVAL')) {
8467 global $obj; // To get $obj used into list when dol_eval() is used for computed fields and $obj is not yet $object
8468 }
8469
8470 // PHP < 7.4.0
8471 defined('T_COALESCE_EQUAL') || define('T_COALESCE_EQUAL', PHP_INT_MAX);
8472 defined('T_FN') || define('T_FN', PHP_INT_MAX);
8473
8474 // PHP < 8.0.0
8475 defined('T_ATTRIBUTE') || define('T_ATTRIBUTE', PHP_INT_MAX);
8476 defined('T_MATCH') || define('T_MATCH', PHP_INT_MAX);
8477 defined('T_NAME_FULLY_QUALIFIED') || define('T_NAME_FULLY_QUALIFIED', PHP_INT_MAX);
8478 defined('T_NAME_QUALIFIED') || define('T_NAME_QUALIFIED', PHP_INT_MAX);
8479 defined('T_NAME_RELATIVE') || define('T_NAME_RELATIVE', PHP_INT_MAX);
8480
8481 // PHP < 8.1.0
8482 defined('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG') || define('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG', PHP_INT_MAX);
8483 defined('T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG') || define('T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG', PHP_INT_MAX);
8484 defined('T_ENUM') || define('T_ENUM', PHP_INT_MAX);
8485 defined('T_READONLY') || define('T_READONLY', PHP_INT_MAX);
8486
8487 // PHP < 8.4.0
8488 defined('T_PRIVATE_SET') || define('T_PRIVATE_SET', PHP_INT_MAX);
8489 defined('T_PROTECTED_SET') || define('T_PROTECTED_SET', PHP_INT_MAX);
8490 defined('T_PUBLIC_SET') || define('T_PUBLIC_SET', PHP_INT_MAX);
8491
8492 $prohibited_token_ids = [
8493 /*
8494 * Prohibited int tokens
8495 */
8496
8497 // T_AND_EQUAL', 'T_ARRAY', 'T_ARRAY_CAST', 'T_AS',
8498 'T_ABSTRACT',
8499 'T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG',
8500 'T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG',
8501 'T_ATTRIBUTE',
8502 // 'T_BOOLEAN_AND', 'T_BOOLEAN_OR', 'T_BOOL_CAST', 'T_BREAK',
8503 'T_BAD_CHARACTER',
8504 // 'T_CASE', 'T_CLASS_C', 'T_CLONE', 'T_COALESCE', 'T_COALESCE_EQUAL', 'T_COMMENT', 'T_CONCAT_EQUAL',
8505 // 'T_CONSTANT_ENCAPSED_STRING', 'T_CONTINUE', 'T_CURLY_OPEN',
8506 'T_CALLABLE',
8507 'T_CATCH',
8508 'T_CLASS',
8509 'T_CLOSE_TAG',
8510 'T_CONST',
8511 // 'T_DEC', 'T_DEFAULT', 'T_DIV_EQUAL', 'T_DNUMBER', 'T_DO', 'T_DOC_COMMENT',
8512 // 'T_DOLLAR_OPEN_CURLY_BRACES', 'T_DOUBLE_ARROW', 'T_DOUBLE_CAST', 'T_DOUBLE_COLON',
8513 'T_DECLARE',
8514 'T_DIR',
8515 // 'T_ELLIPSIS', 'T_ELSE', 'T_ELSEIF', 'T_EMPTY', 'T_ENCAPSED_AND_WHITESPACE', 'T_ENDFOR',
8516 // 'T_ENDFOREACH', 'T_ENDIF', 'T_ENDSWITCH', 'T_ENDWHILE', 'T_END_HEREDOC',
8517 'T_ECHO',
8518 'T_ENDDECLARE',
8519 'T_ENUM',
8520 'T_EVAL',
8521 'T_EXIT',
8522 'T_EXTENDS',
8523 // 'T_FOR', 'T_FOREACH',
8524 'T_FILE',
8525 'T_FINAL',
8526 'T_FINALLY',
8527 'T_FN',
8528 'T_FUNCTION',
8529 'T_FUNC_C',
8530 'T_GLOBAL',
8531 'T_GOTO',
8532 'T_HALT_COMPILER',
8533 // 'T_IF', 'T_INC', 'T_INLINE_HTML', 'T_INSTANCEOF', 'T_INT_CAST', 'T_ISSET', 'T_IS_EQUAL', 'T_IS_GREATER_OR_EQUAL',
8534 // 'T_IS_IDENTICAL', 'T_IS_NOT_EQUAL', 'T_IS_NOT_IDENTICAL', 'T_IS_SMALLER_OR_EQUAL',
8535 'T_IMPLEMENTS',
8536 'T_INCLUDE',
8537 'T_INCLUDE_ONCE',
8538 'T_INSTEADOF',
8539 'T_INTERFACE',
8540 // 'T_LIST', 'T_LNUMBER', 'T_LOGICAL_AND', 'T_LOGICAL_OR', 'T_LOGICAL_XOR',
8541 'T_LINE',
8542 // 'T_MINUS_EQUAL', 'T_MOD_EQUAL', 'T_MUL_EQUAL',
8543 'T_METHOD_C',
8544 // 'T_NEW',
8545 // 'T_NS_SEPARATOR', 'T_NUM_STRING',
8546 'T_NAMESPACE',
8547 // 'T_NAME_FULLY_QUALIFIED', 'T_NAME_QUALIFIED', 'T_NAME_RELATIVE', 'T_NS_C',
8548 // 'T_OBJECT_CAST', 'T_OBJECT_OPERATOR', 'T_OR_EQUAL',
8549 'T_OPEN_TAG',
8550 'T_OPEN_TAG_WITH_ECHO',
8551 // 'T_PAAMAYIM_NEKUDOTAYIM', 'T_PLUS_EQUAL', 'T_POW', 'T_POW_EQUAL',
8552 'T_PRINT',
8553 'T_PRIVATE',
8554 'T_PROTECTED',
8555 'T_PUBLIC',
8556 // 'T_PROPERTY_C',
8557 'T_READONLY',
8558 'T_REQUIRE',
8559 'T_REQUIRE_ONCE',
8560 'T_RETURN',
8561 // 'T_SL', 'T_SL_EQUAL', 'T_SPACESHIP', 'T_SR', 'T_SR_EQUAL', 'T_START_HEREDOC', 'T_STATIC',
8562 // 'T_STRING', 'T_STRING_CAST', 'T_STRING_VARNAME', 'T_SWITCH',
8563 'T_STATIC',
8564 'T_THROW',
8565 'T_TRAIT',
8566 'T_TRAIT_C',
8567 'T_TRY',
8568 'T_UNSET',
8569 'T_UNSET_CAST',
8570 'T_USE',
8571 // 'T_VARIABLE',
8572 'T_VAR',
8573 // 'T_WHILE', 'T_WHITESPACE',
8574 // 'T_XOR_EQUAL',
8575 // 'T_YIELD', 'T_YIELD_FROM',
8576
8577 /*
8578 * Prohibited string tokens
8579 */
8580 ';',
8581 '`',
8582 ];
8583
8584 $prohibited_variables = [
8585 '$_COOKIE',
8586 '$_ENV',
8587 '$_FILES',
8588 '$GLOBALS',
8589 '$_GET',
8590 '$_POST',
8591 '$_REQUEST',
8592 '$_SERVER',
8593 '$_SESSION',
8594 ];
8595
8596 $forbiddenphpfunctions = array();
8597 $forbiddenphpmethods = array();
8598
8599 // Same list than in dol_eval
8600 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("override_function", "session_id", "session_create_id", "session_regenerate_id"));
8601 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("get_defined_functions", "get_defined_vars", "get_defined_constants", "get_declared_classes"));
8602 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("function"));
8603
8604 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("GETPOST")); // native dolibarr functions
8605
8606 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("ob_start"));
8607
8608 // Functions with callable parameters
8609 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("call_user_func", "call_user_func_array"));
8610 $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"));
8611 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("usort", "uasort", "uksort"));
8612 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("preg_replace_callback", "preg_replace_callback_array", "header_register_callback"));
8613 $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"));
8614 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("spl_autoload_register", "spl_autoload_unregister", "iterator_apply", "session_set_save_handler"));
8615 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("forward_static_call", "forward_static_call_array", "register_postsend_function"));
8616 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("readline_completion_function", "readline_callback_handler_install"));
8617
8618 // Exec functions
8619 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("exec", "passthru", "shell_exec", "system", "proc_open", "popen"));
8620 $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"));
8621 $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", ));
8622 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("putenv", "dl", "apache_child_terminate", "apache_setenv"));
8623 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_eval", "dol_eval_new", "dol_eval_standard", "executeCLI", "verifCond", "dolEncrypt", "dolDecrypt")); // native dolibarr functions
8624 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("eval", "create_function", "assert", "mb_ereg_replace")); // function with eval capabilities
8625
8626 // Include functions
8627 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("require", "include", "require_once", "include_once"));
8628
8629 $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
8630 $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"));
8631
8632 if (!getDolGlobalString('MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL')) { // We disallow all function that allow to obfuscate the real name of a function
8633 // @phpcs:ignore
8634 $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
8635 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_concat", "dol_concatdesc")); // native dolibarr functions
8636 }
8637
8638 $forbiddenphpmethods = array_merge($forbiddenphpmethods, array('invoke', 'invokeArgs')); // Methods of ReflectionFunction to execute a function
8639
8640 $prohibited_functions = array_merge($forbiddenphpfunctions, $forbiddenphpmethods);
8641
8642 $prohibited_token_arrangements = [
8643 // Variable functions "$a(", '"$a"(', "'FN_NAME'(", ('FN_NAME')()
8644 ' T_VARIABLE ( ',
8645 ' " ( ',
8646 ' \' ( ',
8647 ' T_CONSTANT_ENCAPSED_STRING ( ',
8648 ' ) ( ',
8649 ];
8650
8651 $tokens = token_get_all("<?php return {$s};", TOKEN_PARSE);
8652
8653 $tokens_arrangement = ' ';
8654
8655 for ($i = 2, $c = count($tokens) - 1; $i < $c; ++$i) { // ignore <?php return and ;
8656 if (is_array($tokens[$i])) {
8657 $token_id = $tokens[$i][0];
8658 $token_value = $tokens[$i][1];
8659 $token_name = token_name($tokens[$i][0]);
8660 } else {
8661 $token_id = $tokens[$i];
8662 $token_value = $tokens[$i];
8663 $token_name = $tokens[$i];
8664 }
8665
8666 // Ignore whitespaces
8667 if (T_WHITESPACE === $token_id) {
8668 continue;
8669 }
8670
8671 // Keep history to check arrangements
8672 $tokens_arrangement .= "{$token_name} ";
8673
8674 // Prohibited Variables
8675 if (
8676 T_VARIABLE === $token_id
8677 && in_array($token_value, $prohibited_variables, true)
8678 ) {
8679 return "Bad string syntax to evaluate. « {$token_value} » is prohibited in « {$s} »";
8680 }
8681
8682 // Prohibited Functions
8683 if (
8684 T_STRING === $token_id
8685 && in_array($token_value, $prohibited_functions, true)
8686 ) {
8687 return "Bad string syntax to evaluate. « {$token_value} » is prohibited in « {$s} »";
8688 }
8689 }
8690
8691 // Prohibited Token IDs
8692 $maxi = count($prohibited_token_ids);
8693 for ($i = 0; $i < $maxi; ++$i) {
8694 if (false !== strpos($tokens_arrangement, " {$prohibited_token_ids[$i]} ")) {
8695 return "Bad string syntax to evaluate. « {$prohibited_token_ids[$i]} » is prohibited in « {$s} »";
8696 }
8697 }
8698
8699 // Prohibited token arrangements
8700 $maxi = count($prohibited_token_arrangements);
8701 for ($i = 0; $i < $maxi; ++$i) {
8702 if (false !== strpos($tokens_arrangement, $prohibited_token_arrangements[$i])) {
8703 return "Bad string syntax to evaluate. « {$prohibited_token_arrangements[$i]} » is prohibited in « {$s} »";
8704 }
8705 }
8706
8707 // Return result
8708 try {
8709 return @eval("return {$s};") ?? '';
8710 } catch (Throwable $ex) {
8711 return "Bad string syntax to evaluate. Exception during evaluation: " . $s . " - " . $ex->getMessage();
8712 }
8713}
8714
8729function dol_eval_standard($s, $hideerrors = 1, $onlysimplestring = '1')
8730{
8731 // Only this global variables can be read by eval function and returned to caller
8732 // The less we have, the better it is.
8733
8734 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()
8735 global $db, $langs, $user, $website, $websitepage;
8736 global $action, $mainmenu, $leftmenu;
8737 global $mysoc;
8738 global $objectoffield; // To allow the use of $objectoffield in computed fields
8739 global $object;
8740
8741 // Old variables (deprecated since v23)
8742 if (getDolGlobalString('MAIN_ALLOW_OLD_VAR_OBJ_IN_DOL_EVAL')) {
8743 global $obj; // To get $obj used into list when dol_eval() is used for computed fields and $obj is not yet $objectoffield
8744 }
8745
8746 $isObBufferActive = false; // When true, the ObBuffer must be cleaned in the exception handler
8747 if ($onlysimplestring == '0') { // '0' is deprecated, we process it as the more secured '1'
8748 $onlysimplestring = '1';
8749 }
8750 if (!in_array($onlysimplestring, array('1', '2'))) {
8751 return "Bad call of dol_eval. Parameter onlysimplestring must be '1' or '2'.";
8752 }
8753 if (!is_scalar($s)) {
8754 return "Bad call of dol_eval. First parameter must be a string, found ".var_export($s, true);
8755 }
8756
8757 try {
8758 global $dolibarr_main_restrict_eval_methods;
8759
8760 // Set $dolibarr_main_restrict_eval_methods_array
8761 if (!isset($dolibarr_main_restrict_eval_methods)) {
8762 $dolibarr_main_restrict_eval_methods = 'getDolGlobalString, getDolGlobalInt, getDolCurrency, getDolEntity, getDolDBType, fetchNoCompute, hasRight, isAdmin, isExternalUser, isModEnabled, isStringVarMatching, abs, min, max, round, dol_now, preg_match';
8763 }
8764 //print '$dolibarr_main_restrict_eval_methods = '.$dolibarr_main_restrict_eval_methods."\n";
8765 $dolibarr_main_restrict_eval_methods_array = explode(',', str_replace(" ", "", $dolibarr_main_restrict_eval_methods));
8766
8767 // Test on dangerous char (used for RCE), we allow only characters to make PHP variable testing
8768 // We must accept with 1: '1 && getDolGlobalInt("doesnotexist1") && getDolGlobalString("MAIN_FEATURES_LEVEL")'
8769 // We must accept with 1: '$user->hasRight("cabinetmed", "read") && !$objectoffield->canvas == "patient@cabinetmed"'
8770 // 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"
8771
8772 // Check if there is dynamic call (first we check chars are all into a whitelist chars)
8773 $specialcharsallowed = '^$_+-.*>&|=!?():"\',/@';
8774 if ($onlysimplestring == '2') {
8775 $specialcharsallowed .= '<[]'; // Later we check that < has space before and after
8776 }
8777 global $dolibarr_main_allow_unsecured_special_chars_in_dol_eval;
8778 if (!empty($dolibarr_main_allow_unsecured_special_chars_in_dol_eval)) {
8779 $specialcharsallowed .= (string) $dolibarr_main_allow_unsecured_special_chars_in_dol_eval;
8780 }
8781 if (preg_match('/[^a-z0-9\s' . preg_quote($specialcharsallowed, '/') . ']/i', $s)) {
8782 return 'Bad string syntax to evaluate (found chars that are not chars for a simple one line clean eval string): ' . $s;
8783 }
8784
8785 // Check if we found a | without a space before and after
8786 /* Disabled to allow preg_match('/(AAA|BBB)/')
8787 $tmps = str_replace(' || ', '__XXX__', $s);
8788 if (strpos($tmps, '|') !== false) {
8789 return 'Bad string syntax to evaluate (The char | can be used only when duplicated || with a space before and after): ' . $s;
8790 }
8791 */
8792
8793 // Check if there is PHP comments (can be used to obfuscate code)
8794 if (strpos($s, '/*') !== false || strpos($s, '//') !== false) {
8795 return 'Bad string syntax to evaluate (The comment string /* and // are not allowed): ' . $s;
8796 }
8797
8798 // Check if we found a ? without a space before and after
8799 $tmps = str_replace(' ? ', '__XXX__', $s);
8800 if (strpos($tmps, '?') !== false) {
8801 return 'Bad string syntax to evaluate (The char ? can be used only with a space before and after): ' . $s;
8802 }
8803
8804 // Check if there is a < or <= without spaces after
8805 if (preg_match('/<=?[^\s]/', $s)) {
8806 return 'Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found a < or <= without space after): ' . $s;
8807 }
8808
8809 // Check if there is an include or a require
8810 if (preg_match('/(include|include_once|require|require_once)/', $s)) {
8811 return 'Bad string syntax to evaluate (found not allowed key include|include_once|require|require_once): ' . $s;
8812 }
8813
8814 // Check if there is dynamic call (first we use black list patterns)
8815 if (preg_match('/\$[\w]*\s*\‍(/', $s)) {
8816 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;
8817 }
8818
8819 if (empty($dolibarr_main_restrict_eval_methods)) {
8820 // If $dolibarr_main_restrict_eval_methods was set to '', we must check if we try dynamic call
8821
8822 // First we remove white list pattern of using parenthesis then testing if one open parenthesis exists
8823 $savescheck = '';
8824 $scheck = $s;
8825 while ($scheck && $savescheck != $scheck) {
8826 $savescheck = $scheck;
8827 $scheck = preg_replace('/->[a-zA-Z0-9_]+\‍(/', '->__METHOD__', $scheck); // accept parenthesis in '...->method(...'
8828 $scheck = preg_replace('/::[a-zA-Z0-9_]+\‍(/', '->__METHOD__', $scheck); // accept parenthesis in '...::method(...'
8829 $scheck = preg_replace('/^\‍(+/', '__PARENTHESIS__ ', $scheck); // accept parenthesis in '(...'. Must replace with "__PARENTHESIS__ with a space after "to allow following substitutions
8830 $scheck = preg_replace('/\&\&\s+\‍(/', '__ANDPARENTHESIS__ ', $scheck); // accept parenthesis in '&& ('. Must replace with "__PARENTHESIS__ with a space after" to allow following substitutions
8831 $scheck = preg_replace('/\|\|\s+\‍(/', '__ORPARENTHESIS__ ', $scheck); // accept parenthesis in '|| ('. Must replace with "__PARENTHESIS__ with a space after" to allow following substitutions
8832 $scheck = preg_replace('/^!?[a-zA-Z0-9_]+\‍(/', '__FUNCTION__', $scheck); // accept parenthesis in 'function(' and '!function('
8833 $scheck = preg_replace('/\s!?[a-zA-Z0-9_]+\‍(/', '__FUNCTION__', $scheck); // accept parenthesis in '... function(' and '... !function('
8834 $scheck = preg_replace('/^!\‍(/', '__NOTANDPARENTHESIS__', $scheck); // accept parenthesis in '!('
8835 $scheck = preg_replace('/\s!\‍(/', ' __NOTANDPARENTHESIS__', $scheck); // accept parenthesis in '... !('
8836 $scheck = preg_replace('/(\^|\')\‍(/', '__REGEXSTART__', $scheck); // To allow preg_match('/^(aaa|bbb)/'... or isStringVarMatching('leftmenu', '(aaa|bbb)')
8837 }
8838 //print 'scheck='.$scheck." : ".strpos($scheck, '(')."<br>\n";
8839
8840 // Now test if it remains 1 open parenthesis.
8841 if (strpos($scheck, '(') !== false) {
8842 return 'Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found call of a function or method without using the direct name of the function): ' . $s;
8843 }
8844 }
8845
8846 if (strpos($s, '`') !== false) {
8847 return 'Bad string syntax to evaluate (backtick char is forbidden): ' . $s;
8848 }
8849
8850 // Disallow also concat operator
8851 if (!getDolGlobalString('MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL')) {
8852 if (preg_match('/[^0-9]+\.[^0-9]+/', $s)) { // We refuse . if not between 2 numbers
8853 return 'Bad string syntax to evaluate (dot char is forbidden if not strictly between 2 numbers): ' . $s;
8854 }
8855 }
8856
8857 // We exclude string using a $ character that are not an expected global or temporary vars, so that are not:
8858 // $db, $langs, $leftmenu, $topmenu, $user, $langs, $objectoffield, $var....
8859 $savescheck = '';
8860 $scheck = $s;
8861 while ($scheck && $savescheck != $scheck) {
8862 $savescheck = $scheck;
8863 $scheck = preg_replace('/\$conf->[a-z\_]+->enabled/', '__VARCONFENABLED__', $scheck); // Remove this once $user->module->enabled has been replaced everywhere with isModEnabled.
8864 $scheck = preg_replace('/\$user->id/', '__VARUSERID__', $scheck);
8865 $scheck = preg_replace('/\$user->hasRight/', '__VARUSERHASRIGHT__', $scheck);
8866 $scheck = preg_replace('/\$user->rights/', '__VARUSERHASRIGHT__', $scheck); // Remove this once $user->rights->xxx is replaced everywhere with $user->hasRight()
8867 $scheck = preg_replace('/\$user->isAdmin/', '__VARUSERHASRIGHT__', $scheck);
8868 $scheck = preg_replace('/\$user->admin/', '__VARUSERISADMIN__', $scheck); // Remove this once $user->admin is replaced everywhere with $user->isAdmin()
8869 $scheck = preg_replace('/\$user->isExternalUser/', '__VARUSERSOCID__', $scheck);
8870 $scheck = preg_replace('/\$user->socid/', '__VARUSERSOCID__', $scheck); // Remove this once $user->admin is replaced everywhere with $user->isExternalUser()
8871 $scheck = preg_replace('/\‍(\$db\‍)/', '__VARDB__', $scheck);
8872 $scheck = preg_replace('/\$langs/', '__VARLANGSTRANS__', $scheck);
8873 $scheck = preg_replace('/\$mysoc/', '__VARMYSOC__', $scheck);
8874 $scheck = preg_replace('/\$action/', '__VARACTION__', $scheck);
8875 $scheck = preg_replace('/\$mainmenu/', '__VARMAINMENU__', $scheck); // Remove this once all tests on $mainmenu has been replaced with isStringVarMatching
8876 $scheck = preg_replace('/\$leftmenu/', '__VARLEFTMENU__', $scheck); // Remove this once all tests on $mainmenu has been replaced with isStringVarMatching
8877 $scheck = preg_replace('/\$websitepage/', '__VARWEBSITEPAGE__', $scheck);
8878 $scheck = preg_replace('/\$website/', '__VARWEBSITE__', $scheck);
8879 $scheck = preg_replace('/\$objectoffield/', '__VAROBJECTOFFIELD__', $scheck);
8880 $scheck = preg_replace('/\$object/', '__VAROBJECT__', $scheck);
8881 $scheck = preg_replace('/\$var/', '__VARVAR__', $scheck);
8882
8883 // deprecated (now we use $objecf->canvas or $objectoffield->canvas)
8884 $scheck = preg_replace('/\$soc->canvas/', '__VARSOCCANVAS__', $scheck);
8885 $scheck = preg_replace('/\$obj->canvas/', '__VAROBJCANVAS__', $scheck);
8886
8887 // Now test if it remains one '$'
8888 if (strpos($scheck, '$') !== false) {
8889 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);
8890 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;
8891 }
8892 }
8893
8894 // We block use of php exec or php file functions
8895 $forbiddenphpstrings = array('_ENV', '_SESSION', '_COOKIE', '_GET', '_GLOBAL', '_POST', '_REQUEST', 'ReflectionFunction', 'SplFileObject', 'SplTempFileObject');
8896
8897 if (empty($dolibarr_main_restrict_eval_methods)) { // If forced to ''
8898 // 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)
8899 // 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
8900 // like we can do with array_map and its callable parameter: dol_eval('json_encode(array_map(implode("",["ex","ec"]), ["id"]))', 1, 1, '0')
8901 $forbiddenphpfunctions = array();
8902 $forbiddenphpmethods = array();
8903
8904 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("override_function", "session_id", "session_create_id", "session_regenerate_id"));
8905 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("get_defined_functions", "get_defined_vars", "get_defined_constants", "get_declared_classes"));
8906 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("function"));
8907
8908 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("GETPOST")); // native dolibarr functions
8909
8910 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("ob_start"));
8911
8912 // Functions with callable parameters
8913 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("call_user_func", "call_user_func_array"));
8914 $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"));
8915 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("usort", "uasort", "uksort"));
8916 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("preg_replace_callback", "preg_replace_callback_array", "header_register_callback"));
8917 $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"));
8918 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("spl_autoload_register", "spl_autoload_unregister", "iterator_apply", "session_set_save_handler"));
8919 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("forward_static_call", "forward_static_call_array", "register_postsend_function"));
8920 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("readline_completion_function", "readline_callback_handler_install"));
8921
8922 // Exec functions
8923 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("exec", "passthru", "shell_exec", "system", "proc_open", "popen"));
8924 $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"));
8925 $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", ));
8926 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("putenv", "dl", "apache_child_terminate", "apache_setenv"));
8927 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_eval", "dol_eval_new", "dol_eval_standard", "executeCLI", "verifCond", "dolEncrypt", "dolDecrypt")); // native dolibarr functions
8928 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("eval", "create_function", "assert", "mb_ereg_replace")); // function with eval capabilities
8929
8930 // Include functions
8931 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("require", "include", "require_once", "include_once"));
8932
8933 $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
8934 $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"));
8935
8936 if (!getDolGlobalString('MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL')) { // We disallow all function that allow to obfuscate the real name of a function
8937 // @phpcs:ignore
8938 $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
8939 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_concat", "dol_concatdesc")); // native dolibarr functions
8940 }
8941 // Remove from blacklist the function that are into the whitelist
8942 /*foreach ($forbiddenphpfunctions as $key => $forbiddenphpfunction) {
8943 if (in_array($forbiddenphpfunction, $dolibarr_main_restrict_eval_methods_array)) {
8944 unset($forbiddenphpfunctions[$key]);
8945 }
8946 }*/
8947
8948 $forbiddenphpmethods = array_merge($forbiddenphpmethods, array('invoke', 'invokeArgs')); // Methods of ReflectionFunction to execute a function
8949 // Remove from blacklist the function that are into the whitelist
8950 /*foreach ($forbiddenphpmethods as $key => $forbiddenphpmethod) {
8951 if (in_array($forbiddenphpmethod, $dolibarr_main_restrict_eval_methods_array)) {
8952 unset($forbiddenphpmethods[$key]);
8953 }
8954 }*/
8955
8956 $forbiddenphpregex = 'global\s*\$';
8957 $forbiddenphpregex .= '|posix_[a-zA-Z0-9_]*|'; // All posix functions
8958 $forbiddenphpregex .= '\b(' . implode('|', $forbiddenphpfunctions) . ')\b';
8959
8960 $forbiddenphpmethodsregex = '->(' . implode('|', $forbiddenphpmethods) . ')';
8961
8962 // Now scan all forbidden patterns
8963 do {
8964 $oldstringtoclean = $s;
8965 $s = str_ireplace($forbiddenphpstrings, '__forbiddenstring__', $s);
8966 $s = preg_replace('/' . $forbiddenphpregex . '/i', '__forbiddenstring__', $s);
8967 $s = preg_replace('/' . $forbiddenphpmethodsregex . '/i', '__forbiddenstring__', $s);
8968 //$s = preg_replace('/\$[a-zA-Z0-9_\->\$]+\‍(/i', '', $s); // Remove $function( call and $mycall->mymethod(
8969 } while ($oldstringtoclean != $s);
8970
8971 if (strpos($s, '__forbiddenstring__') !== false) {
8972 dol_syslog('Bad string syntax to evaluate: ' . $s, LOG_WARNING);
8973 return 'Bad string syntax to evaluate: ' . $s;
8974 }
8975 }
8976
8977 if (!empty($dolibarr_main_restrict_eval_methods)) {
8978 // Accept only white-listed allowed function and classes
8979 // TODO Get all pattern '/([\s\w]+)\‍(/', then check that $matches[1] is a defined class or a function into a given list
8980 $pattern = '/([\s\w\'\]\"]+)\‍(/';
8981
8982 $matches = array();
8983 preg_match_all($pattern, $s, $matches);
8984
8985 if (count($matches)) {
8986 foreach ($matches[1] as $m) {
8987 $m = trim($m);
8988 if (empty($m)) {
8989 continue;
8990 }
8991 $reg = array();
8992 if (!preg_match('/new ([A-Z][\w]+)/i', $m, $reg)) {
8993 if (!in_array($m, $dolibarr_main_restrict_eval_methods_array)) {
8994 if ($m != "'" && $m != '"') {
8995 dol_syslog('Bad string syntax to evaluate: ' . $s, LOG_WARNING);
8996 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;
8997 }
8998 }
8999 } else {
9000 if (!class_exists($reg[1])) {
9001 dol_syslog('Bad string syntax to evaluate: Class "'.$reg[1].'" does not exist. ' . $s, LOG_WARNING);
9002 return 'Bad string syntax to evaluate. Class "'.$reg[1].'" does not exist. ' . $s;
9003 }
9004 $parents = class_parents($reg[1]); // Get list of parent classes of class we want to check
9005 if (!in_array('CommonObject', $parents)) { // Only classes that inherit CommonObject are ok. This forbid dangerous classes like ReflectionFunction, SplFileObject, ...
9006 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);
9007 return 'Bad string syntax to evaluate. Class "'.$reg[1].'" is not allowed because only classes extended CommonObject can be used in dynamic evaluation. ' . $s;
9008 }
9009 }
9010 }
9011 }
9012
9013 $forbiddenphpregex = 'global\s*\$';
9014 $forbiddenphpregex .= '|'; // or
9015 $forbiddenphpregex .= '}\s*\[';
9016 $forbiddenphpregex .= '|'; // or
9017 $forbiddenphpregex .= '\‍)\s*\‍(';
9018
9019 // Now scan all forbidden patterns
9020 do {
9021 $oldstringtoclean = $s;
9022 $s = str_ireplace($forbiddenphpstrings, '__forbiddenstring__', $s);
9023 $s = preg_replace('/' . $forbiddenphpregex . '/i', '__forbiddenstring__', $s);
9024 //$s = preg_replace('/' . $forbiddenphpmethodsregex . '/i', '__forbiddenstring__', $s);
9025 //$s = preg_replace('/\$[a-zA-Z0-9_\->\$]+\‍(/i', '', $s); // Remove $function( call and $mycall->mymethod(
9026 } while ($oldstringtoclean != $s);
9027
9028 if (strpos($s, '__forbiddenstring__') !== false) {
9029 dol_syslog('Bad string syntax to evaluate: ' . $s, LOG_WARNING);
9030 return 'Bad string syntax to evaluate: ' . $s;
9031 }
9032 }
9033
9034 //print $s."<br>\n";
9035 ob_start(); // An evaluation has no reason to output data
9036 $isObBufferActive = true;
9037 $tmps = $hideerrors ? @eval('return ' . $s . ';') : eval('return ' . $s . ';');
9038 $tmpo = ob_get_clean(); // This close the buffer
9039 $isObBufferActive = false;
9040 if ($tmpo) {
9041 print 'Bad string syntax to evaluate. Some data were output when it should not when evaluating: ' . $s;
9042 }
9043 return $tmps;
9044 } catch (Exception $e) {
9045 if ($isObBufferActive) {
9046 // Clean up buffer which was left behind due to exception.
9047 $tmpo = ob_get_clean(); // This close the buffer
9048 $isObBufferActive = false;
9049 }
9050 $error = 'dol_eval try/catch error for string: ' . $s . ' - Error: ';
9051 $error .= $e->getMessage();
9052 dol_syslog($error, LOG_WARNING);
9053 return 'Exception during evaluation: ' . $s;
9054 } catch (Error $e) {
9055 if ($isObBufferActive) {
9056 // Clean up buffer which was left behind due to exception.
9057 $tmpo = ob_get_clean(); // This close the buffer
9058 $isObBufferActive = false;
9059 }
9060 $error = 'dol_eval try/catch error for string: ' . $s . ' - Error: ';
9061 $error .= $e->getMessage();
9062 dol_syslog($error, LOG_WARNING);
9063 return 'Exception during evaluation: ' . $s;
9064 }
9065}
9066
9074function dol_validElement($element)
9075{
9076 return (trim($element) != '');
9077}
9078
9079
9087function getLanguageCodeFromCountryCode($countrycode)
9088{
9089 global $mysoc;
9090
9091 if (empty($countrycode)) {
9092 return null;
9093 }
9094
9095 if (strtoupper($countrycode) == 'MQ') {
9096 return 'fr_CA';
9097 }
9098 if (strtoupper($countrycode) == 'SE') {
9099 return 'sv_SE'; // se_SE is Sami/Sweden, and we want in priority sv_SE for SE country
9100 }
9101 if (strtoupper($countrycode) == 'CH') {
9102 if ($mysoc->country_code == 'FR') {
9103 return 'fr_CH';
9104 }
9105 if ($mysoc->country_code == 'DE') {
9106 return 'de_CH';
9107 }
9108 if ($mysoc->country_code == 'IT') {
9109 return 'it_CH';
9110 }
9111 }
9112
9113 // Locale list taken from:
9114 // http://stackoverflow.com/questions/3191664/
9115 // list-of-all-locales-and-their-short-codes
9116 $locales = array(
9117 'af-ZA',
9118 'am-ET',
9119 'ar-AE',
9120 'ar-BH',
9121 'ar-DZ',
9122 'ar-EG',
9123 'ar-IQ',
9124 'ar-JO',
9125 'ar-KW',
9126 'ar-LB',
9127 'ar-LY',
9128 'ar-MA',
9129 'ar-OM',
9130 'ar-QA',
9131 'ar-SA',
9132 'ar-SY',
9133 'ar-TN',
9134 'ar-YE',
9135 //'as-IN', // Moved after en-IN
9136 'ba-RU',
9137 'be-BY',
9138 'bg-BG',
9139 'bn-BD',
9140 //'bn-IN', // Moved after en-IN
9141 'bo-CN',
9142 'br-FR',
9143 'ca-ES',
9144 'co-FR',
9145 'cs-CZ',
9146 'cy-GB',
9147 'da-DK',
9148 'de-AT',
9149 'de-CH',
9150 'de-DE',
9151 'de-LI',
9152 'de-LU',
9153 'dv-MV',
9154 'el-GR',
9155 'en-AU',
9156 'en-BZ',
9157 'en-CA',
9158 'en-GB',
9159 'en-IE',
9160 'en-IN',
9161 'as-IN', // as-IN must be after en-IN (en in priority if country is IN)
9162 'bn-IN', // bn-IN must be after en-IN (en in priority if country is IN)
9163 'en-JM',
9164 'en-MY',
9165 'en-NZ',
9166 'en-PH',
9167 'en-SG',
9168 'en-TT',
9169 'en-US',
9170 'en-ZA',
9171 'en-ZW',
9172 'es-AR',
9173 'es-BO',
9174 'es-CL',
9175 'es-CO',
9176 'es-CR',
9177 'es-DO',
9178 'es-EC',
9179 'es-ES',
9180 'es-GT',
9181 'es-HN',
9182 'es-MX',
9183 'es-NI',
9184 'es-PA',
9185 'es-PE',
9186 'es-PR',
9187 'es-PY',
9188 'es-SV',
9189 'es-US',
9190 'es-UY',
9191 'es-VE',
9192 'et-EE',
9193 'eu-ES',
9194 'fa-IR',
9195 'fi-FI',
9196 'fo-FO',
9197 'fr-BE',
9198 'fr-CA',
9199 'fr-CH',
9200 'fr-FR',
9201 'fr-LU',
9202 'fr-MC',
9203 'fy-NL',
9204 'ga-IE',
9205 'gd-GB',
9206 'gl-ES',
9207 'gu-IN',
9208 'he-IL',
9209 'hi-IN',
9210 'hr-BA',
9211 'hr-HR',
9212 'hu-HU',
9213 'hy-AM',
9214 'id-ID',
9215 'ig-NG',
9216 'ii-CN',
9217 'is-IS',
9218 'it-CH',
9219 'it-IT',
9220 'ja-JP',
9221 'ka-GE',
9222 'kk-KZ',
9223 'kl-GL',
9224 'km-KH',
9225 'kn-IN',
9226 'ko-KR',
9227 'ky-KG',
9228 'lb-LU',
9229 'lo-LA',
9230 'lt-LT',
9231 'lv-LV',
9232 'mi-NZ',
9233 'mk-MK',
9234 'ml-IN',
9235 'mn-MN',
9236 'mr-IN',
9237 'ms-BN',
9238 'ms-MY',
9239 'mt-MT',
9240 'nb-NO',
9241 'ne-NP',
9242 'nl-BE',
9243 'nl-NL',
9244 'nn-NO',
9245 'oc-FR',
9246 'or-IN',
9247 'pa-IN',
9248 'pl-PL',
9249 'ps-AF',
9250 'pt-BR',
9251 'pt-PT',
9252 'rm-CH',
9253 'ro-MD',
9254 'ro-RO',
9255 'ru-RU',
9256 'rw-RW',
9257 'sa-IN',
9258 'se-FI',
9259 'se-NO',
9260 'se-SE',
9261 'si-LK',
9262 'sk-SK',
9263 'sl-SI',
9264 'sq-AL',
9265 'sv-FI',
9266 'sv-SE',
9267 'sw-KE',
9268 'ta-IN',
9269 'te-IN',
9270 'th-TH',
9271 'tk-TM',
9272 'tn-ZA',
9273 'tr-TR',
9274 'tt-RU',
9275 'ug-CN',
9276 'uk-UA',
9277 'ur-PK',
9278 'vi-VN',
9279 'wo-SN',
9280 'xh-ZA',
9281 'yo-NG',
9282 'zh-CN',
9283 'zh-HK',
9284 'zh-MO',
9285 'zh-SG',
9286 'zh-TW',
9287 'zu-ZA',
9288 );
9289
9290 $buildprimarykeytotest = strtolower($countrycode) . '-' . strtoupper($countrycode);
9291 if (in_array($buildprimarykeytotest, $locales)) {
9292 return strtolower($countrycode) . '_' . strtoupper($countrycode);
9293 }
9294
9295 if (function_exists('locale_get_primary_language') && function_exists('locale_get_region')) { // Need extension php-intl
9296 foreach ($locales as $locale) {
9297 $locale_language = locale_get_primary_language($locale);
9298 $locale_region = locale_get_region($locale);
9299 if (strtoupper($countrycode) == $locale_region) {
9300 //var_dump($locale.' - '.$locale_language.' - '.$locale_region);
9301 return strtolower($locale_language) . '_' . strtoupper($locale_region);
9302 }
9303 }
9304 } else {
9305 dol_syslog("Warning Extension php-intl is not available", LOG_WARNING);
9306 }
9307
9308 return null;
9309}
9310
9341function complete_head_from_modules($conf, $langs, $object, &$head, &$h, $type, $mode = 'add', $filterorigmodule = '')
9342{
9343 global $hookmanager, $db;
9344
9345 if (isset($conf->modules_parts['tabs'][$type]) && is_array($conf->modules_parts['tabs'][$type])) {
9346 foreach ($conf->modules_parts['tabs'][$type] as $value) {
9347 $values = explode(':', $value);
9348
9349 $reg = array();
9350 if ($mode == 'add' && !preg_match('/^\-/', $values[1])) {
9351 if (count($values) !== 6) {
9352 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);
9353 continue;
9354 }
9355
9356 // new declaration with permissions:
9357 // $value='objecttype:+tabname1:Title1:langfile@mymodule:$user->rights->mymodule->read:/mymodule/mynewtab1.php?id=__ID__'
9358 // $value='objecttype:+tabname1:Title1,class,pathfile,method:langfile@mymodule:$user->rights->mymodule->read:/mymodule/mynewtab1.php?id=__ID__'
9359 if ($values[0] != $type) {
9360 continue;
9361 }
9362
9363 $newtab = array();
9364 $postab = $h;
9365 // detect if position set in $values[1] ie : +(2)mytab@mymodule (first tab is 0, second is one, ...)
9366 $str = $values[1];
9367 $posstart = strpos($str, '(');
9368 if ($posstart > 0) {
9369 $posend = strpos($str, ')');
9370 if ($posstart > 0) {
9371 $res1 = substr($str, $posstart + 1, $posend - $posstart - 1);
9372 if (is_numeric($res1)) {
9373 $postab = (int) $res1;
9374 $values[1] = '+' . substr($str, $posend + 1);
9375 }
9376 }
9377 }
9378
9379 global $objectoffield; // So we can use $objectoffield int verifCond
9380 $objectoffield = $object;
9381
9382 if (!verifCond($values[4], '2')) {
9383 continue;
9384 }
9385
9386 if ($values[3]) {
9387 if ($filterorigmodule) { // If a filter of module origin has been requested
9388 if (strpos($values[3], '@')) { // This is an external module
9389 if ($filterorigmodule != 'external') {
9390 continue;
9391 }
9392 } else { // This looks a core module
9393 if ($filterorigmodule != 'core') {
9394 continue;
9395 }
9396 }
9397 }
9398 $langs->load($values[3]);
9399 }
9400
9401 if (preg_match('/SUBSTITUTION_([^_]+)/i', $values[2], $reg)) {
9402 // If label is "SUBSTITUION_..."
9403 $substitutionarray = array();
9404 complete_substitutions_array($substitutionarray, $langs, $object, array('needforkey' => $values[2]));
9405 $label = make_substitutions($reg[1], $substitutionarray);
9406 } else {
9407 // If label is "Label,Class,File,Method", we call the method to show content inside the badge
9408 $labeltemp = explode(',', $values[2]);
9409 $label = $langs->trans($labeltemp[0]);
9410
9411 if (!empty($labeltemp[1]) && is_object($object) && !empty($object->id)) {
9412 dol_include_once($labeltemp[2]);
9413 $classtoload = $labeltemp[1];
9414 if (class_exists($classtoload)) {
9415 $obj = new $classtoload($db);
9416 $function = $labeltemp[3];
9417 if ($obj && $function && method_exists($obj, $function)) {
9418 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
9419 $nbrec = $obj->$function($object->id, $obj);
9420 if (!empty($nbrec)) {
9421 $label .= '<span class="badge marginleftonlyshort">' . $nbrec . '</span>';
9422 }
9423 }
9424 }
9425 }
9426 }
9427 $url = preg_replace('/__ID__/i', ((is_object($object) && !empty($object->id)) ? $object->id : ''), $values[5]);
9428 $link = parse_url($url);
9429 $query = [];
9430 if (isset($link['query'])) {
9431 parse_str($link['query'], $query);
9432 }
9433 $newtab[0] = dolBuildUrl(dol_buildpath($link['path'], 1), $query);
9434 $newtab[1] = $label;
9435 $newtab[2] = str_replace('+', '', $values[1]);
9436 $h++;
9437
9438 // set tab at its position
9439 $head = array_merge(array_slice($head, 0, $postab), array($newtab), array_slice($head, $postab));
9440 } elseif ($mode == 'remove' && preg_match('/^\-/', $values[1])) {
9441 if ($values[0] != $type) {
9442 continue;
9443 }
9444 $tabname = str_replace('-', '', $values[1]);
9445 foreach ($head as $key => $val) {
9446 $condition = (!empty($values[3]) ? verifCond($values[3], '2') : 1);
9447 //var_dump($key.' - '.$tabname.' - '.$head[$key][2].' - '.$values[3].' - '.$condition);
9448 if ($head[$key][2] == $tabname && $condition) {
9449 unset($head[$key]);
9450 break;
9451 }
9452 }
9453 }
9454 }
9455 }
9456
9457 // No need to make a return $head. Var is modified as a reference
9458 if (!empty($hookmanager)) {
9459 $parameters = array('object' => $object, 'mode' => $mode, 'head' => &$head, 'filterorigmodule' => $filterorigmodule, 'type' => $type);
9460 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
9461 $reshook = $hookmanager->executeHooks('completeTabsHead', $parameters, $object);
9462 if ($reshook > 0) { // Hook ask to replace completely the array
9463 $head = $hookmanager->resArray;
9464 } else { // Hook
9465 $head = array_merge($head, $hookmanager->resArray);
9466 }
9467 $h = count($head);
9468 }
9469}
9470
9471
9481function dolExplodeIntoArray($string, $delimiter = ';', $kv = '=')
9482{
9483 if (is_null($string)) {
9484 return array();
9485 }
9486
9487 if (preg_match('/^\[.*\]$/sm', $delimiter) || preg_match('/^\‍(.*\‍)$/sm', $delimiter)) {
9488 // This is a regex string
9489 $newdelimiter = $delimiter;
9490 } else {
9491 // This is a simple string
9492 // @phan-suppress-next-line PhanPluginSuspiciousParamPositionInternal
9493 $newdelimiter = preg_quote($delimiter, '/');
9494 }
9495
9496 if ($a = preg_split('/' . $newdelimiter . '/', $string)) {
9497 $ka = array();
9498 foreach ($a as $s) { // each part
9499 if ($s) {
9500 if ($pos = strpos($s, $kv)) { // key/value delimiter
9501 $ka[trim(substr($s, 0, $pos))] = trim(substr($s, $pos + strlen($kv)));
9502 } else { // key delimiter not found
9503 $ka[] = trim($s);
9504 }
9505 }
9506 }
9507 return $ka;
9508 }
9509
9510 return array();
9511}
9512
9520function dolExplodeKeepIfQuotes($input)
9521{
9522 // Use regexp to capture words and section in quotes
9523 $matches = array();
9524 preg_match_all('/"([^"]*)"|\'([^\']*)\'|(\S+)/', $input, $matches);
9525
9526 // Merge result and delete empty values
9527
9528 $result = array_map(
9535 static function ($a, $b, $c) {
9536 if ($a !== '') {
9537 return $a;
9538 }
9539 if ($b !== '') {
9540 return $b;
9541 }
9542 if ($c !== '') {
9543 return $c;
9544 }
9545 return '';
9546 },
9547 $matches[1],
9548 $matches[2],
9549 $matches[3]
9550 );
9551 return array_values(array_filter(
9552 $result,
9559 static function ($val) {
9560 return $val !== '';
9561 }
9562 ));
9563}
9564
9565
9573function dol_getmypid()
9574{
9575 if (!function_exists('getmypid')) {
9576 return mt_rand(99900000, 99965535);
9577 } else {
9578 return getmypid(); // May be a number on 64 bits (depending on OS)
9579 }
9580}
9581
9604function natural_search($fields, $value, $mode = 0, $nofirstand = 0, $sqltoadd = '')
9605{
9606 global $db, $langs;
9607
9608 $value = trim($value);
9609
9610 if ($mode == 0) {
9611 $value = preg_replace('/\*/', '%', $value); // Replace * with %
9612 }
9613 if ($mode == 1) {
9614 $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
9615 }
9616
9617 $value = preg_replace('/\s*\|\s*/', '|', $value);
9618
9619 // Split criteria on ' ' but not if we are inside quotes.
9620 // For mode 3, the split is done later on the , only and not on the ' '.
9621 if ($mode != -3 && $mode != 3) {
9622 $crits = dolExplodeKeepIfQuotes($value);
9623 } else {
9624 $crits = array($value);
9625 }
9626
9627 $res = '';
9628 if (!is_array($fields)) {
9629 $fields = array($fields);
9630 }
9631 $i1 = 0; // count the nb of "and" criteria added (all fields / criteria)
9632 foreach ($crits as $crit) { // Loop on each AND criteria
9633 $crit = trim($crit);
9634 $i2 = 0; // count the nb of valid criteria added for this this first criteria
9635 $newres = '';
9636
9637 foreach ($fields as $field) {
9638 if ($mode == 1) {
9639 $tmpcrits = explode('|', $crit);
9640 $i3 = 0; // count the nb of valid criteria added for this current field
9641 foreach ($tmpcrits as $tmpcrit) {
9642 if ($tmpcrit !== '0' && empty($tmpcrit)) {
9643 continue;
9644 }
9645 $tmpcrit = trim($tmpcrit);
9646
9647 $newres .= (($i2 > 0 || $i3 > 0) ? ' OR ' : '');
9648
9649 $operator = '=';
9650 $newcrit = preg_replace('/([!<>=]+)/', '', $tmpcrit);
9651
9652 $reg = array();
9653 preg_match('/([!<>=]+)/', $tmpcrit, $reg);
9654 if (!empty($reg[1])) {
9655 $operator = $reg[1];
9656 }
9657 if ($newcrit != '') {
9658 $numnewcrit = price2num($newcrit);
9659 if (is_numeric($numnewcrit)) {
9660 $newres .= $db->sanitize($field) . ' ' . $operator . ' ' . ((float) $numnewcrit); // should be a numeric
9661 } else {
9662 $newres .= '1 = 2'; // force false, we received a corrupted data
9663 }
9664 $i3++; // a criteria was added to string
9665 }
9666 }
9667 $i2++; // a criteria for 1 more field was added to string
9668 } elseif ($mode == 2 || $mode == -2) {
9669 $crit = preg_replace('/[^\-0-9,]/', '', $crit); // ID are always integer
9670 $newres .= ($i2 > 0 ? ' OR ' : '') . $db->sanitize($field) . " " . ($mode == -2 ? 'NOT ' : '');
9671 $newres .= $crit ? "IN (" . $db->sanitize($db->escape($crit)) . ")" : "IN (0)";
9672 if ($mode == -2) {
9673 $newres .= ' OR ' . $db->sanitize($field) . ' IS NULL';
9674 }
9675 $i2++; // a criteria for 1 more field was added to string
9676 } elseif ($mode == 3 || $mode == -3) {
9677 $tmparray = explode(',', $crit);
9678 if (count($tmparray)) {
9679 $listofcodes = '';
9680 $listofcodesnot = '';
9681 foreach ($tmparray as $val) {
9682 $val = trim($val);
9683 if ($val !== '') {
9684 if (preg_match('/^!/', $val)) {
9685 $listofcodesnot .= ($listofcodesnot ? ',' : '');
9686 $listofcodesnot .= "'" . $db->escape(preg_replace('/^!=?/', '', $val)) . "'";
9687 } else {
9688 $listofcodes .= ($listofcodes ? ',' : '');
9689 $listofcodes .= "'" . $db->escape($val) . "'";
9690 }
9691 }
9692 }
9693 $newres .= ($i2 > 0 ? ' OR ' : '');
9694 if ($listofcodes && $listofcodesnot) {
9695 $newres .= '(';
9696 }
9697 if ($listofcodes) {
9698 $newres .= $db->sanitize($field) . " " . ($mode == -3 ? 'NOT IN' : 'IN') . " (" . $db->sanitize($listofcodes, 1, 0, 1) . ")";
9699 }
9700 if ($listofcodes && $listofcodesnot) {
9701 $newres .= ' AND ';
9702 }
9703 if ($listofcodesnot) {
9704 $newres .= $db->sanitize($field) . " " . ($mode == -3 ? 'IN ' : 'NOT IN') . " (" . $db->sanitize($listofcodesnot, 1, 0, 1) . ")";
9705 }
9706 if ($listofcodes && $listofcodesnot) {
9707 $newres .= ')';
9708 }
9709 $i2++; // a criteria for 1 more field was added to string
9710 }
9711 if ($mode == -3) {
9712 $newres .= ' OR ' . $db->sanitize($field) . ' IS NULL';
9713 }
9714 } elseif ($mode == 4) {
9715 $tmparray = explode(',', $crit);
9716 if (count($tmparray)) {
9717 $listofcodes = '';
9718 foreach ($tmparray as $val) {
9719 $val = trim($val);
9720 if ($val) {
9721 $newres .= ($i2 > 0 ? " OR (" : "(") . $db->sanitize($field) . " LIKE '" . $db->escape($val) . ",%'";
9722 $newres .= ' OR ' . $db->sanitize($field) . " = '" . $db->escape($val) . "'";
9723 $newres .= ' OR ' . $db->sanitize($field) . " LIKE '%," . $db->escape($val) . "'";
9724 $newres .= ' OR ' . $db->sanitize($field) . " LIKE '%," . $db->escape($val) . ",%'";
9725 $newres .= ')';
9726 $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)
9727 }
9728 }
9729 }
9730 } else { // $mode=0
9731 $tmpcrits = explode('|', $crit);
9732 $i3 = 0; // count the nb of valid criteria added for the current couple criteria/field
9733 foreach ($tmpcrits as $tmpcrit) { // loop on each OR criteria
9734 if ($tmpcrit !== '0' && empty($tmpcrit)) {
9735 continue;
9736 }
9737 $tmpcrit = trim($tmpcrit);
9738
9739 if ($tmpcrit == '^$' || strpos($crit, '!') === 0) { // If we search empty, we must combined different OR fields with AND
9740 $newres .= (($i2 > 0 || $i3 > 0) ? ' AND ' : '');
9741 } else {
9742 $newres .= (($i2 > 0 || $i3 > 0) ? ' OR ' : '');
9743 }
9744
9745 $isSellist = false;
9746 $table = $label = $key = null;
9747
9748 if (strpos($field, 'ef.') === 0) {
9749 $extrafieldName = substr($field, 3);
9750 $extrafields = new ExtraFields($db);
9751 $extrafields->fetch_name_optionals_label('product');
9752
9753 if (isset($extrafields->attributes['product']['type'][$extrafieldName]) && $extrafields->attributes['product']['type'][$extrafieldName] === 'sellist') {
9754 $isSellist = true;
9755 $paramArray = $extrafields->attributes['product']['param'][$extrafieldName]['options'] ?? [];
9756 $param = array_key_first($paramArray);
9757 list($table, $label, $key) = explode(':', $param);
9758 }
9759 }
9760
9761 if (preg_match('/\.(id|rowid)$/', $field)) { // Special case for rowid that is sometimes a ref so used as a search field
9762 $newres .= $db->sanitize($field) . " = " . (is_numeric($tmpcrit) ? ((float) $tmpcrit) : '0');
9763 } else {
9764 $tmpcrit2 = $tmpcrit;
9765 $tmpbefore = '%';
9766 $tmpafter = '%';
9767 $tmps = '';
9768
9769 if ($isSellist) {
9770 $newres .= $field . " IN (SELECT t." . $key . " FROM " . $db->prefix() . $table . " AS t WHERE t." . $label . " LIKE '%" . $db->escape($tmpcrit2) . "%')";
9771 } else {
9772 if (preg_match('/^!/', $tmpcrit)) {
9773 $tmps .= $db->sanitize($field) . " NOT LIKE "; // ! as exclude character
9774 $tmpcrit2 = preg_replace('/^!/', '', $tmpcrit2);
9775 } else {
9776 $tmps .= $db->sanitize($field) . " LIKE ";
9777 }
9778 $tmps .= "'";
9779
9780 if (preg_match('/^[\^\$]/', $tmpcrit)) {
9781 $tmpbefore = '';
9782 $tmpcrit2 = preg_replace('/^[\^\$]/', '', $tmpcrit2);
9783 }
9784 if (preg_match('/[\^\$]$/', $tmpcrit)) {
9785 $tmpafter = '';
9786 $tmpcrit2 = preg_replace('/[\^\$]$/', '', $tmpcrit2);
9787 }
9788
9789 if ($tmpcrit2 == '' || preg_match('/^!/', $tmpcrit)) {
9790 $tmps = "(" . $tmps;
9791 }
9792 $newres .= $tmps;
9793 $newres .= $tmpbefore;
9794 $newres .= $db->escape($tmpcrit2);
9795 $newres .= $tmpafter;
9796 $newres .= "'";
9797 if ($tmpcrit2 == '' || preg_match('/^!/', $tmpcrit)) {
9798 $newres .= " OR " . $field . " IS NULL)";
9799 }
9800 }
9801 }
9802
9803 $i3++;
9804 }
9805
9806 $i2++; // a criteria for 1 more field was added to string
9807 }
9808 }
9809
9810 if ($sqltoadd) {
9811 $newres .= ($newres ? '' : ' OR ').str_replace('__KEYTOSEARCH__', $crit, $sqltoadd);
9812 }
9813
9814 if ($newres) {
9815 $res = $res . ($res ? ' AND ' : '') . ($i2 > 1 ? '(' : '') . $newres . ($i2 > 1 ? ')' : '');
9816 }
9817 $i1++;
9818 }
9819 $res = ($nofirstand ? "" : " AND ") . "(" . $res . ")";
9820
9821 return $res;
9822}
9823
9824
9833function getImageFileNameForSize($file, $extName, $extImgTarget = '')
9834{
9835 $dirName = dirname($file);
9836 if ($dirName == '.') {
9837 $dirName = '';
9838 }
9839
9840 if (!in_array($extName, array('', '_small', '_mini'))) {
9841 return 'Bad parameter extName';
9842 }
9843
9844 $fileName = preg_replace('/(\.gif|\.jpeg|\.jpg|\.png|\.bmp|\.webp|\.avif)$/i', '', $file); // We remove image extension, whatever is its case
9845 $fileName = basename($fileName);
9846
9847 if (empty($extImgTarget)) {
9848 $extImgTarget = (preg_match('/\.jpg$/i', $file) ? '.jpg' : '');
9849 }
9850 if (empty($extImgTarget)) {
9851 $extImgTarget = (preg_match('/\.jpeg$/i', $file) ? '.jpeg' : '');
9852 }
9853 if (empty($extImgTarget)) {
9854 $extImgTarget = (preg_match('/\.gif$/i', $file) ? '.gif' : '');
9855 }
9856 if (empty($extImgTarget)) {
9857 $extImgTarget = (preg_match('/\.png$/i', $file) ? '.png' : '');
9858 }
9859 if (empty($extImgTarget)) {
9860 $extImgTarget = (preg_match('/\.bmp$/i', $file) ? '.bmp' : '');
9861 }
9862 if (empty($extImgTarget)) {
9863 $extImgTarget = (preg_match('/\.webp$/i', $file) ? '.webp' : '');
9864 }
9865 if (empty($extImgTarget)) {
9866 $extImgTarget = (preg_match('/\.avif$/i', $file) ? '.avif' : '');
9867 }
9868
9869 if (!$extImgTarget) {
9870 return $file;
9871 }
9872
9873 $subdir = '';
9874 if ($extName) {
9875 $subdir = 'thumbs/';
9876 }
9877
9878 return ($dirName ? $dirName . '/' : '') . $subdir . $fileName . $extName . $extImgTarget; // New filename for thumb
9879}
9880
9881
9888function getLabelSpecialCode($idcode)
9889{
9890 global $langs;
9891
9892 $arrayspecialines = array(1 => 'Transport', 2 => 'EcoTax', 3 => 'Option');
9893 if ($idcode > 10) {
9894 return 'Module ID ' . $idcode;
9895 }
9896 if (!empty($arrayspecialines[$idcode])) {
9897 return $langs->trans($arrayspecialines[$idcode]);
9898 }
9899 return '';
9900}
9901
9902
9910function dolIsAllowedForPreview($file)
9911{
9912 // Check .noexe extension in filename
9913 if (preg_match('/\.noexe$/i', $file)) {
9914 return 0;
9915 }
9916
9917 // Check mime types
9918 $mime_preview = array('avif', 'bmp', 'jpeg', 'png', 'gif', 'tiff', 'pdf', 'plain', 'css', 'webp', 'webm', 'mp4');
9919 if (getDolGlobalString('MAIN_ALLOW_SVG_FILES_AS_IMAGES')) {
9920 $mime_preview[] = 'svg+xml';
9921 }
9922 //$mime_preview[]='vnd.oasis.opendocument.presentation';
9923 //$mime_preview[]='archive';
9924 $num_mime = array_search(dol_mimetype($file, '', 1), $mime_preview);
9925 if ($num_mime !== false) {
9926 return 1;
9927 }
9928
9929 // By default, not allowed for preview
9930 return 0;
9931}
9932
9933
9943function dol_mimetype($file, $default = 'application/octet-stream', $mode = 0)
9944{
9945 $mime = $default;
9946 $imgmime = 'other.png';
9947 $famime = 'file-o';
9948 $srclang = '';
9949
9950 $tmpfile = preg_replace('/\.noexe$/', '', $file);
9951
9952 // Plain text files
9953 if (preg_match('/\.txt$/i', $tmpfile)) {
9954 $mime = 'text/plain';
9955 $imgmime = 'text.png';
9956 $famime = 'file-alt';
9957 } elseif (preg_match('/\.rtx$/i', $tmpfile)) {
9958 $mime = 'text/richtext';
9959 $imgmime = 'text.png';
9960 $famime = 'file-alt';
9961 } elseif (preg_match('/\.csv$/i', $tmpfile)) {
9962 $mime = 'text/csv';
9963 $imgmime = 'text.png';
9964 $famime = 'file-csv';
9965 } elseif (preg_match('/\.tsv$/i', $tmpfile)) {
9966 $mime = 'text/tab-separated-values';
9967 $imgmime = 'text.png';
9968 $famime = 'file-alt';
9969 } elseif (preg_match('/\.(cf|conf|log)$/i', $tmpfile)) {
9970 $mime = 'text/plain';
9971 $imgmime = 'text.png';
9972 $famime = 'file-alt';
9973 } elseif (preg_match('/\.ini$/i', $tmpfile)) {
9974 $mime = 'text/plain';
9975 $imgmime = 'text.png';
9976 $srclang = 'ini';
9977 $famime = 'file-alt';
9978 } elseif (preg_match('/\.md$/i', $tmpfile)) {
9979 $mime = 'text/plain';
9980 $imgmime = 'text.png';
9981 $srclang = 'md';
9982 $famime = 'file-alt';
9983 } elseif (preg_match('/\.css$/i', $tmpfile)) {
9984 $mime = 'text/css';
9985 $imgmime = 'css.png';
9986 $srclang = 'css';
9987 $famime = 'file-alt';
9988 } elseif (preg_match('/\.lang$/i', $tmpfile)) {
9989 $mime = 'text/plain';
9990 $imgmime = 'text.png';
9991 $srclang = 'lang';
9992 $famime = 'file-alt';
9993 } elseif (preg_match('/\.(crt|cer|key|pub)$/i', $tmpfile)) { // Certificate files
9994 $mime = 'text/plain';
9995 $imgmime = 'text.png';
9996 $famime = 'file-alt';
9997 } elseif (preg_match('/\.(html|htm|shtml)$/i', $tmpfile)) { // XML based (HTML/XML/XAML)
9998 $mime = 'text/html';
9999 $imgmime = 'html.png';
10000 $srclang = 'html';
10001 $famime = 'file-alt';
10002 } elseif (preg_match('/\.(xml|xhtml)$/i', $tmpfile)) {
10003 $mime = 'text/xml';
10004 $imgmime = 'other.png';
10005 $srclang = 'xml';
10006 $famime = 'file-alt';
10007 } elseif (preg_match('/\.xaml$/i', $tmpfile)) {
10008 $mime = 'text/xml';
10009 $imgmime = 'other.png';
10010 $srclang = 'xaml';
10011 $famime = 'file-alt';
10012 } elseif (preg_match('/\.bas$/i', $tmpfile)) { // Languages
10013 $mime = 'text/plain';
10014 $imgmime = 'text.png';
10015 $srclang = 'bas';
10016 $famime = 'file-code';
10017 } elseif (preg_match('/\.(c)$/i', $tmpfile)) {
10018 $mime = 'text/plain';
10019 $imgmime = 'text.png';
10020 $srclang = 'c';
10021 $famime = 'file-code';
10022 } elseif (preg_match('/\.(cpp)$/i', $tmpfile)) {
10023 $mime = 'text/plain';
10024 $imgmime = 'text.png';
10025 $srclang = 'cpp';
10026 $famime = 'file-code';
10027 } elseif (preg_match('/\.cs$/i', $tmpfile)) {
10028 $mime = 'text/plain';
10029 $imgmime = 'text.png';
10030 $srclang = 'cs';
10031 $famime = 'file-code';
10032 } elseif (preg_match('/\.(h)$/i', $tmpfile)) {
10033 $mime = 'text/plain';
10034 $imgmime = 'text.png';
10035 $srclang = 'h';
10036 $famime = 'file-code';
10037 } elseif (preg_match('/\.(java|jsp)$/i', $tmpfile)) {
10038 $mime = 'text/plain';
10039 $imgmime = 'text.png';
10040 $srclang = 'java';
10041 $famime = 'file-code';
10042 } elseif (preg_match('/\.php([0-9]{1})?$/i', $tmpfile)) {
10043 $mime = 'text/plain';
10044 $imgmime = 'php.png';
10045 $srclang = 'php';
10046 $famime = 'file-code';
10047 } elseif (preg_match('/\.phtml$/i', $tmpfile)) {
10048 $mime = 'text/plain';
10049 $imgmime = 'php.png';
10050 $srclang = 'php';
10051 $famime = 'file-code';
10052 } elseif (preg_match('/\.(pl|pm)$/i', $tmpfile)) {
10053 $mime = 'text/plain';
10054 $imgmime = 'pl.png';
10055 $srclang = 'perl';
10056 $famime = 'file-code';
10057 } elseif (preg_match('/\.sql$/i', $tmpfile)) {
10058 $mime = 'text/plain';
10059 $imgmime = 'text.png';
10060 $srclang = 'sql';
10061 $famime = 'file-code';
10062 } elseif (preg_match('/\.js$/i', $tmpfile)) {
10063 $mime = 'text/x-javascript';
10064 $imgmime = 'jscript.png';
10065 $srclang = 'js';
10066 $famime = 'file-code';
10067 } elseif (preg_match('/\.odp$/i', $tmpfile)) { // Open office
10068 $mime = 'application/vnd.oasis.opendocument.presentation';
10069 $imgmime = 'ooffice.png';
10070 $famime = 'file-powerpoint';
10071 } elseif (preg_match('/\.ods$/i', $tmpfile)) {
10072 $mime = 'application/vnd.oasis.opendocument.spreadsheet';
10073 $imgmime = 'ooffice.png';
10074 $famime = 'file-excel';
10075 } elseif (preg_match('/\.odt$/i', $tmpfile)) {
10076 $mime = 'application/vnd.oasis.opendocument.text';
10077 $imgmime = 'ooffice.png';
10078 $famime = 'file-word';
10079 } elseif (preg_match('/\.mdb$/i', $tmpfile)) { // MS Office
10080 $mime = 'application/msaccess';
10081 $imgmime = 'mdb.png';
10082 $famime = 'file';
10083 } elseif (preg_match('/\.doc[xm]?$/i', $tmpfile)) {
10084 $mime = 'application/msword';
10085 $imgmime = 'doc.png';
10086 $famime = 'file-word';
10087 } elseif (preg_match('/\.dot[xm]?$/i', $tmpfile)) {
10088 $mime = 'application/msword';
10089 $imgmime = 'doc.png';
10090 $famime = 'file-word';
10091 } elseif (preg_match('/\.xlt(x)?$/i', $tmpfile)) {
10092 $mime = 'application/vnd.ms-excel';
10093 $imgmime = 'xls.png';
10094 $famime = 'file-excel';
10095 } elseif (preg_match('/\.xla(m)?$/i', $tmpfile)) {
10096 $mime = 'application/vnd.ms-excel';
10097 $imgmime = 'xls.png';
10098 $famime = 'file-excel';
10099 } elseif (preg_match('/\.xls$/i', $tmpfile)) {
10100 $mime = 'application/vnd.ms-excel';
10101 $imgmime = 'xls.png';
10102 $famime = 'file-excel';
10103 } elseif (preg_match('/\.xls[bmx]$/i', $tmpfile)) {
10104 $mime = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
10105 $imgmime = 'xls.png';
10106 $famime = 'file-excel';
10107 } elseif (preg_match('/\.pps[mx]?$/i', $tmpfile)) {
10108 $mime = 'application/vnd.ms-powerpoint';
10109 $imgmime = 'ppt.png';
10110 $famime = 'file-powerpoint';
10111 } elseif (preg_match('/\.ppt[mx]?$/i', $tmpfile)) {
10112 $mime = 'application/x-mspowerpoint';
10113 $imgmime = 'ppt.png';
10114 $famime = 'file-powerpoint';
10115 } elseif (preg_match('/\.pdf$/i', $tmpfile)) { // Other
10116 $mime = 'application/pdf';
10117 $imgmime = 'pdf.png';
10118 $famime = 'file-pdf';
10119 } elseif (preg_match('/\.bat$/i', $tmpfile)) { // Scripts
10120 $mime = 'text/x-bat';
10121 $imgmime = 'script.png';
10122 $srclang = 'dos';
10123 $famime = 'file-code';
10124 } elseif (preg_match('/\.sh$/i', $tmpfile)) {
10125 $mime = 'text/x-sh';
10126 $imgmime = 'script.png';
10127 $srclang = 'bash';
10128 $famime = 'file-code';
10129 } elseif (preg_match('/\.ksh$/i', $tmpfile)) {
10130 $mime = 'text/x-ksh';
10131 $imgmime = 'script.png';
10132 $srclang = 'bash';
10133 $famime = 'file-code';
10134 } elseif (preg_match('/\.bash$/i', $tmpfile)) {
10135 $mime = 'text/x-bash';
10136 $imgmime = 'script.png';
10137 $srclang = 'bash';
10138 $famime = 'file-code';
10139 } elseif (preg_match('/\.ico$/i', $tmpfile)) { // Images
10140 $mime = 'image/x-icon';
10141 $imgmime = 'image.png';
10142 $famime = 'file-image';
10143 } elseif (preg_match('/\.(jpg|jpeg)$/i', $tmpfile)) {
10144 $mime = 'image/jpeg';
10145 $imgmime = 'image.png';
10146 $famime = 'file-image';
10147 } elseif (preg_match('/\.png$/i', $tmpfile)) {
10148 $mime = 'image/png';
10149 $imgmime = 'image.png';
10150 $famime = 'file-image';
10151 } elseif (preg_match('/\.gif$/i', $tmpfile)) {
10152 $mime = 'image/gif';
10153 $imgmime = 'image.png';
10154 $famime = 'file-image';
10155 } elseif (preg_match('/\.bmp$/i', $tmpfile)) {
10156 $mime = 'image/bmp';
10157 $imgmime = 'image.png';
10158 $famime = 'file-image';
10159 } elseif (preg_match('/\.(tif|tiff)$/i', $tmpfile)) {
10160 $mime = 'image/tiff';
10161 $imgmime = 'image.png';
10162 $famime = 'file-image';
10163 } elseif (preg_match('/\.svg$/i', $tmpfile)) {
10164 $mime = 'image/svg+xml';
10165 $imgmime = 'image.png';
10166 $famime = 'file-image';
10167 } elseif (preg_match('/\.webp$/i', $tmpfile)) {
10168 $mime = 'image/webp';
10169 $imgmime = 'image.png';
10170 $famime = 'file-image';
10171 } elseif (preg_match('/\.vcs$/i', $tmpfile)) { // Calendar
10172 $mime = 'text/calendar';
10173 $imgmime = 'other.png';
10174 $famime = 'file-alt';
10175 } elseif (preg_match('/\.ics$/i', $tmpfile)) {
10176 $mime = 'text/calendar';
10177 $imgmime = 'other.png';
10178 $famime = 'file-alt';
10179 } elseif (preg_match('/\.torrent$/i', $tmpfile)) { // Other
10180 $mime = 'application/x-bittorrent';
10181 $imgmime = 'other.png';
10182 $famime = 'file-o';
10183 } elseif (preg_match('/\.(mp3|ogg|au|wav|wma|mid)$/i', $tmpfile)) { // Audio
10184 $mime = 'audio';
10185 $imgmime = 'audio.png';
10186 $famime = 'file-audio';
10187 } elseif (preg_match('/\.mp4$/i', $tmpfile)) { // Video
10188 $mime = 'video/mp4';
10189 $imgmime = 'video.png';
10190 $famime = 'file-video';
10191 } elseif (preg_match('/\.ogv$/i', $tmpfile)) {
10192 $mime = 'video/ogg';
10193 $imgmime = 'video.png';
10194 $famime = 'file-video';
10195 } elseif (preg_match('/\.webm$/i', $tmpfile)) {
10196 $mime = 'video/webm';
10197 $imgmime = 'video.png';
10198 $famime = 'file-video';
10199 } elseif (preg_match('/\.avif$/i', $tmpfile)) {
10200 $mime = 'image/avif';
10201 $imgmime = 'image.png';
10202 $famime = 'file-image';
10203 } elseif (preg_match('/\.avi$/i', $tmpfile)) {
10204 $mime = 'video/x-msvideo';
10205 $imgmime = 'video.png';
10206 $famime = 'file-video';
10207 } elseif (preg_match('/\.divx$/i', $tmpfile)) {
10208 $mime = 'video/divx';
10209 $imgmime = 'video.png';
10210 $famime = 'file-video';
10211 } elseif (preg_match('/\.xvid$/i', $tmpfile)) {
10212 $mime = 'video/xvid';
10213 $imgmime = 'video.png';
10214 $famime = 'file-video';
10215 } elseif (preg_match('/\.(wmv|mpg|mpeg)$/i', $tmpfile)) {
10216 $mime = 'video';
10217 $imgmime = 'video.png';
10218 $famime = 'file-video';
10219 } elseif (preg_match('/\.(zip|rar|gz|tgz|xz|z|cab|bz2|7z|tar|lzh|zst)$/i', $tmpfile)) { // Archive
10220 // application/xxx where zzz is zip, ...
10221 $mime = 'archive';
10222 $imgmime = 'archive.png';
10223 $famime = 'file-archive';
10224 } elseif (preg_match('/\.(exe|com)$/i', $tmpfile)) { // Exe
10225 $mime = 'application/octet-stream';
10226 $imgmime = 'other.png';
10227 $famime = 'file-o';
10228 } elseif (preg_match('/\.(dll|lib|o|so|a)$/i', $tmpfile)) { // Lib
10229 $mime = 'library';
10230 $imgmime = 'library.png';
10231 $famime = 'file-o';
10232 } elseif (preg_match('/\.err$/i', $tmpfile)) { // phpcs:ignore
10233 $mime = 'error';
10234 $imgmime = 'error.png';
10235 $famime = 'file-alt';
10236 }
10237
10238 if ($famime == 'file-o') {
10239 // file-o seems to not work in fontawesome 5
10240 $famime = 'file';
10241 }
10242
10243 // Return mimetype string
10244 switch ((int) $mode) {
10245 case 1:
10246 $tmp = explode('/', $mime);
10247 return (!empty($tmp[1]) ? $tmp[1] : $tmp[0]);
10248 case 2:
10249 return $imgmime;
10250 case 3:
10251 return $srclang;
10252 case 4:
10253 return $famime;
10254 }
10255 return $mime;
10256}
10257
10269function getDictionaryValue($tablename, $field, $id, $checkentity = false, $rowidfield = 'rowid')
10270{
10271 global $conf, $db;
10272
10273 $tablename = preg_replace('/^' . preg_quote(MAIN_DB_PREFIX, '/') . '/', '', $tablename); // Clean name of table for backward compatibility.
10274
10275 $dictvalues = (isset($conf->cache['dictvalues_' . $tablename]) ? $conf->cache['dictvalues_' . $tablename] : null);
10276
10277 if (is_null($dictvalues)) {
10278 $dictvalues = array();
10279
10280 $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
10281 if ($checkentity) {
10282 $sql .= ' AND entity IN (0,' . getEntity($tablename) . ')';
10283 }
10284
10285 $resql = $db->query($sql);
10286 if ($resql) {
10287 while ($obj = $db->fetch_object($resql)) {
10288 $dictvalues[$obj->$rowidfield] = $obj; // $obj is stdClass
10289 }
10290 } else {
10292 }
10293
10294 $conf->cache['dictvalues_' . $tablename] = $dictvalues;
10295 }
10296
10297 if (!empty($dictvalues[$id])) {
10298 // Found
10299 $tmp = $dictvalues[$id];
10300 return (property_exists($tmp, $field) ? $tmp->$field : '');
10301 } else {
10302 // Not found
10303 return '';
10304 }
10305}
10306
10313function colorIsLight($stringcolor)
10314{
10315 $stringcolor = str_replace('#', '', $stringcolor);
10316 $res = -1;
10317 if (!empty($stringcolor)) {
10318 $res = 0;
10319 $tmp = explode(',', $stringcolor);
10320 if (count($tmp) > 1) { // This is a comma RGB ('255','255','255')
10321 $r = $tmp[0];
10322 $g = $tmp[1];
10323 $b = $tmp[2];
10324 } else {
10325 $hexr = $stringcolor[0] . $stringcolor[1];
10326 $hexg = $stringcolor[2] . $stringcolor[3];
10327 $hexb = $stringcolor[4] . $stringcolor[5];
10328 $r = hexdec($hexr);
10329 $g = hexdec($hexg);
10330 $b = hexdec($hexb);
10331 }
10332 $bright = (max($r, $g, $b) + min($r, $g, $b)) / 510.0; // HSL algorithm
10333 if ($bright > 0.6) {
10334 $res = 1;
10335 }
10336 }
10337 return $res;
10338}
10339
10348function isVisibleToUserType($type_user, &$menuentry, &$listofmodulesforexternal)
10349{
10350 //print 'type_user='.$type_user.' module='.$menuentry['module'].' enabled='.$menuentry['enabled'].' perms='.$menuentry['perms'];
10351 //print 'ok='.in_array($menuentry['module'], $listofmodulesforexternal);
10352 if (empty($menuentry['enabled'])) {
10353 return 0; // Entry disabled by condition
10354 }
10355 if ($type_user && array_key_exists('module', $menuentry) && $menuentry['module']) {
10356 $tmploops = explode('|', $menuentry['module']);
10357 $found = 0;
10358 foreach ($tmploops as $tmploop) {
10359 if (in_array($tmploop, $listofmodulesforexternal)) {
10360 $found++;
10361 break;
10362 }
10363 }
10364 if (!$found) {
10365 return 0; // Entry is for menus all excluded to external users
10366 }
10367 }
10368 if (!$menuentry['perms'] && $type_user) {
10369 return 0; // No permissions and user is external
10370 }
10371 if (!$menuentry['perms'] && getDolGlobalString('MAIN_MENU_HIDE_UNAUTHORIZED')) {
10372 return 0; // No permissions and option to hide when not allowed, even for internal user, is on
10373 }
10374 if (!$menuentry['perms']) {
10375 return 2; // No permissions and user is external
10376 }
10377 return 1;
10378}
10379
10387function roundUpToNextMultiple($n, $x = 5)
10388{
10389 $result = (ceil($n) % $x === 0) ? ceil($n) : (round(($n + $x / 2) / $x) * $x);
10390 return (int) $result;
10391}
10392
10393
10404function getElementProperties($elementType)
10405{
10406 global $conf, $db, $hookmanager;
10407
10408 $regs = array();
10409
10410 //$element_type='facture';
10411
10412 $classfile = $classname = $classpath = $subdir = $dir_output = $dir_temp = $parent_element = '';
10413
10414 // Parse element/subelement
10415 $module = $elementType;
10416 $element = $elementType;
10417 $subelement = $elementType;
10418 $table_element = $elementType;
10419
10420 // If we ask a resource form external module (instead of default path)
10421 if (preg_match('/^([^@]+)@([^@]+)$/i', $elementType, $regs)) { // 'myobject@mymodule'
10422 $element = $subelement = $regs[1];
10423 $module = $regs[2];
10424 } elseif (preg_match('/^([^_]+)_([^_]+)/i', $element, $regs)) { // 'myobject_mysubobject' with myobject=mymodule, example 'project_task'
10425 // This is an alternative syntax to 'myobject@mymodule', so it must not be applied when the previous case already matched,
10426 // otherwise the module resolved from the '@' syntax would be overwritten by a wrong guess when $element contains a '_'.
10427 $module = $element = $regs[1];
10428 $subelement = $regs[2];
10429 }
10430
10431 // Object lines will use parent classpath and module ref
10432 if (substr($elementType, -3) == 'det') {
10433 $module = preg_replace('/det$/', '', $element);
10434 $subelement = preg_replace('/det$/', '', $subelement);
10435 $classpath = $module . '/class';
10436 $classfile = $module;
10437 $classname = preg_replace('/det$/', 'Line', $element);
10438 if (in_array($module, array('expedition', 'propale', 'facture', 'contrat', 'fichinter', 'supplier_order', 'commandefournisseur'))) {
10439 $classname = preg_replace('/det$/', 'Ligne', $element);
10440 }
10441 }
10442 // For compatibility and to work with non standard path
10443 if ($elementType == "action" || $elementType == "actioncomm") {
10444 $classpath = 'comm/action/class';
10445 $subelement = 'Actioncomm';
10446 $module = 'agenda';
10447 $table_element = 'actioncomm';
10448 } elseif ($elementType == 'cronjob') {
10449 $classpath = 'cron/class';
10450 $module = 'cron';
10451 $table_element = 'cron';
10452 } elseif ($elementType == 'adherent_type') {
10453 $classpath = 'adherents/class';
10454 $classfile = 'adherent_type';
10455 $module = 'adherent';
10456 $subelement = 'adherent_type';
10457 $classname = 'AdherentType';
10458 $table_element = 'adherent_type';
10459 } elseif ($elementType == 'bank_account') {
10460 $classpath = 'compta/bank/class';
10461 $module = 'bank'; // We need $conf->bank->dir_output and not $conf->banque->dir_output
10462 $classfile = 'account';
10463 $classname = 'Account';
10464 } elseif ($elementType == 'bank_line') {
10465 $classpath = 'compta/bank/class';
10466 $module = 'bank'; // We need $conf->bank->dir_output and not $conf->banque->dir_output
10467 $classfile = 'account';
10468 $classname = 'AccountLine';
10469 } elseif ($elementType == 'category') {
10470 $classpath = 'categories/class';
10471 $module = 'categorie';
10472 $subelement = 'categorie';
10473 $table_element = 'categorie';
10474 } elseif ($elementType == 'contact') {
10475 $classpath = 'contact/class';
10476 $classfile = 'contact';
10477 $module = 'societe';
10478 $subelement = 'contact';
10479 $table_element = 'socpeople';
10480 $subdir = '/contact';
10481 } elseif ($elementType == 'inventory') {
10482 $module = 'product';
10483 $classpath = 'product/inventory/class';
10484 } elseif ($elementType == 'inventoryline') {
10485 $module = 'product';
10486 $classpath = 'product/inventory/class';
10487 $table_element = 'inventorydet';
10488 $parent_element = 'inventory';
10489 } elseif ($elementType == 'stock' || $elementType == 'entrepot' || $elementType == 'warehouse') {
10490 $module = 'stock';
10491 $classpath = 'product/stock/class';
10492 $classfile = 'entrepot';
10493 $classname = 'Entrepot';
10494 $table_element = 'entrepot';
10495 } elseif ($elementType == 'project') {
10496 $classpath = 'projet/class';
10497 $module = 'projet';
10498 $table_element = 'projet';
10499 } elseif ($elementType == 'project_task') {
10500 $classpath = 'projet/class';
10501 $module = 'projet';
10502 $subelement = 'task';
10503 $table_element = 'projet_task';
10504 } elseif ($elementType == 'mo') {
10505 $classpath = 'mrp/class';
10506 $module = 'mrp';
10507 $classfile = 'mo';
10508 $classname = 'Mo';
10509 $table_element = 'mrp_mo';
10510 } elseif ($elementType == 'facture' || $elementType == 'invoice') {
10511 $classpath = 'compta/facture/class';
10512 $module = 'facture';
10513 $subelement = 'facture';
10514 $table_element = 'facture';
10515 } elseif ($elementType == 'facturedet') {
10516 $classpath = 'compta/facture/class';
10517 $classfile = 'facture';
10518 $classname = 'FactureLigne';
10519 $module = 'facture';
10520 $table_element = 'facturedet';
10521 $parent_element = 'facture';
10522 } elseif ($elementType == 'facturerec' || $elementType == 'facture_rec') {
10523 $classpath = 'compta/facture/class';
10524 $classfile = 'facture-rec';
10525 $module = 'facture';
10526 $classname = 'FactureRec';
10527 } elseif ($elementType == 'commande' || $elementType == 'order') {
10528 $classpath = 'commande/class';
10529 $module = 'commande';
10530 $subelement = 'commande';
10531 $table_element = 'commande';
10532 } elseif ($elementType == 'commandedet') {
10533 $classpath = 'commande/class';
10534 $classfile = 'commande';
10535 $classname = 'OrderLine';
10536 $module = 'commande';
10537 $table_element = 'commandedet';
10538 $parent_element = 'commande';
10539 } elseif ($elementType == 'propal') {
10540 $classpath = 'comm/propal/class';
10541 $table_element = 'propal';
10542 } elseif ($elementType == 'propaldet') {
10543 $classpath = 'comm/propal/class';
10544 $classfile = 'propal';
10545 $subelement = 'propaleligne';
10546 $module = 'propal';
10547 $table_element = 'propaldet';
10548 $parent_element = 'propal';
10549 } elseif ($elementType == 'shipping' || $elementType == 'shipment') {
10550 $classpath = 'expedition/class';
10551 $classfile = 'expedition';
10552 $classname = 'Expedition';
10553 $module = 'expedition';
10554 $table_element = 'expedition';
10555 } elseif ($elementType == 'expeditiondet' || $elementType == 'shippingdet') {
10556 $classpath = 'expedition/class';
10557 $classfile = 'expedition';
10558 $classname = 'ExpeditionLigne';
10559 $module = 'expedition';
10560 $table_element = 'expeditiondet';
10561 $parent_element = 'expedition';
10562 } elseif ($elementType == 'delivery_note') {
10563 $classpath = 'delivery/class';
10564 $subelement = 'delivery';
10565 $module = 'expedition';
10566 } elseif ($elementType == 'delivery') {
10567 $classpath = 'delivery/class';
10568 $subelement = 'delivery';
10569 $module = 'expedition';
10570 } elseif ($elementType == 'deliverydet') {
10571 // @todo
10572 } elseif ($elementType == 'supplier_proposal') {
10573 $classpath = 'supplier_proposal/class';
10574 $module = 'supplier_proposal';
10575 $element = 'supplierproposal';
10576 $classfile = 'supplier_proposal';
10577 $subelement = 'supplierproposal';
10578 } elseif ($elementType == 'supplier_proposaldet') {
10579 $classpath = 'supplier_proposal/class';
10580 $module = 'supplier_proposal';
10581 $classfile = 'supplier_proposal';
10582 $classname = 'SupplierProposalLine';
10583 $table_element = 'supplier_proposaldet';
10584 $parent_element = 'supplier_proposal';
10585 } elseif ($elementType == 'contract') {
10586 $classpath = 'contrat/class';
10587 $module = 'contrat';
10588 $subelement = 'contrat';
10589 $table_element = 'contract';
10590 } elseif ($elementType == 'contratdet') {
10591 $classpath = 'contrat/class';
10592 $module = 'contrat';
10593 $table_element = 'contratdet';
10594 $parent_element = 'contrat';
10595 } elseif ($elementType == 'mailing') {
10596 $classpath = 'comm/mailing/class';
10597 $module = 'mailing';
10598 $classfile = 'mailing';
10599 $classname = 'Mailing';
10600 $subelement = '';
10601 } elseif ($elementType == 'member' || $elementType == 'adherent') {
10602 $classpath = 'adherents/class';
10603 $module = 'adherent';
10604 $subelement = 'adherent';
10605 $table_element = 'adherent';
10606 } elseif ($elementType == 'subscription') {
10607 $classpath = 'adherents/class';
10608 $classfile = 'subscription';
10609 $module = 'adherent';
10610 $subelement = 'subscription';
10611 $classname = 'Subscription';
10612 $table_element = 'subscription';
10613 } elseif ($elementType == 'usergroup') {
10614 $classpath = 'user/class';
10615 $module = 'user';
10616 } elseif ($elementType == 'mrp') {
10617 $classpath = 'mrp/class';
10618 $classfile = 'mo';
10619 $classname = 'Mo';
10620 $module = 'mrp';
10621 $subelement = '';
10622 $table_element = 'mrp_mo';
10623 } elseif ($elementType == 'mrp_production') {
10624 $classpath = 'mrp/class';
10625 $classfile = 'mo';
10626 $classname = 'MoLine';
10627 $module = 'mrp';
10628 $subelement = '';
10629 $table_element = 'mrp_production';
10630 $parent_element = 'mo';
10631 } elseif ($elementType == 'cabinetmed_cons') {
10632 $classpath = 'cabinetmed/class';
10633 $module = 'cabinetmed';
10634 $subelement = 'cabinetmedcons';
10635 $table_element = 'cabinetmedcons';
10636 } elseif ($elementType == 'fichinter') {
10637 $classpath = 'fichinter/class';
10638 $module = 'ficheinter';
10639 $subelement = 'fichinter';
10640 $table_element = 'fichinter';
10641 } elseif ($elementType == 'dolresource' || $elementType == 'resource') {
10642 $classpath = 'resource/class';
10643 $module = 'resource';
10644 $subelement = 'dolresource';
10645 $table_element = 'resource';
10646 } elseif ($elementType == 'opensurvey_sondage') {
10647 $classpath = 'opensurvey/class';
10648 $module = 'opensurvey';
10649 $subelement = 'opensurveysondage';
10650 } elseif ($elementType == 'order_supplier' || $elementType == 'supplier_order' || $elementType == 'commande_fournisseur' || $elementType == 'commandefournisseur') {
10651 $classpath = 'fourn/class';
10652 $module = 'fournisseur';
10653 $classfile = 'fournisseur.commande';
10654 $element = 'order_supplier';
10655 $subelement = '';
10656 $classname = 'CommandeFournisseur';
10657 $table_element = 'commande_fournisseur';
10658 } elseif ($elementType == 'commande_fournisseurdet') {
10659 $classpath = 'fourn/class';
10660 $module = 'fournisseur';
10661 $classfile = 'fournisseur.commande';
10662 $element = 'commande_fournisseurdet';
10663 $subelement = '';
10664 $classname = 'CommandeFournisseurLigne';
10665 $table_element = 'commande_fournisseurdet';
10666 $parent_element = 'commande_fournisseur';
10667 } elseif ($elementType == 'invoice_supplier' || $elementType == 'supplier_invoice' || $elementType == 'facture_fourn') {
10668 $classpath = 'fourn/class';
10669 $module = 'fournisseur';
10670 $classfile = 'fournisseur.facture';
10671 $element = 'invoice_supplier';
10672 $subelement = '';
10673 $classname = 'FactureFournisseur';
10674 $table_element = 'facture_fourn';
10675 } elseif ($elementType == 'facture_fourn_det') {
10676 $classpath = 'fourn/class';
10677 $module = 'fournisseur';
10678 $classfile = 'fournisseur.facture';
10679 $element = 'facture_fourn_det';
10680 $subelement = '';
10681 $classname = 'SupplierInvoiceLine';
10682 $table_element = 'facture_fourn_det';
10683 $parent_element = 'invoice_supplier';
10684 } elseif ($elementType == "service") {
10685 $classpath = 'product/class';
10686 $module = 'product';
10687 $subelement = 'product';
10688 $table_element = 'product';
10689 } elseif ($elementType == 'product_attribute') {
10690 $module = 'variants';
10691 $element = 'product_attribute';
10692 $subelement = 'product_attribute';
10693 $classpath = 'variants/class';
10694 $classfile = 'ProductAttribute';
10695 $classname = 'ProductAttribute';
10696 $table_element = 'product_attribute';
10697 } elseif ($elementType == 'product_attribute_value') {
10698 $module = 'variants';
10699 $element = 'product_attribute_value';
10700 $subelement = 'product_attribute_value';
10701 $classpath = 'variants/class';
10702 $classfile = 'ProductAttributeValue';
10703 $classname = 'ProductAttributeValue';
10704 $table_element = 'product_attribute_value';
10705 $parent_element = 'product_attribute';
10706 } elseif ($elementType == 'salary') {
10707 $classpath = 'salaries/class';
10708 $module = 'salaries';
10709 } elseif ($elementType == 'payment_salary') {
10710 $classpath = 'salaries/class';
10711 $classfile = 'paymentsalary';
10712 $classname = 'PaymentSalary';
10713 $module = 'salaries';
10714 } elseif ($elementType == 'payment') {
10715 $classpath = 'compta/paiement/class';
10716 $classfile = 'paiement';
10717 $classname = 'Paiement';
10718 $module = 'facture'; // A customer payment belongs to the invoice module, there is no 'compta' module
10719 $element = 'payment';
10720 $subelement = 'payment';
10721 $table_element = 'paiement';
10722 } elseif ($elementType == 'payment_supplier') {
10723 $classpath = 'fourn/class';
10724 $classfile = 'paiementfourn';
10725 $classname = 'PaiementFourn';
10726 $module = 'fournisseur';
10727 $element = 'payment_supplier';
10728 $subelement = 'payment_supplier';
10729 $table_element = 'paiementfourn';
10730 } elseif ($elementType == 'payment_various') {
10731 $classpath = 'compta/bank/class';
10732 $classfile = 'paymentvarious';
10733 $classname = 'PaymentVarious';
10734 $module = 'bank'; // We need $conf->bank->dir_output and not $conf->banque->dir_output
10735 $element = 'payment_various';
10736 $subelement = 'payment_various';
10737 $table_element = 'payment_various';
10738 } elseif ($elementType == 'stocktransfer') {
10739 $classpath = 'product/stock/stocktransfer/class';
10740 $classfile = 'stocktransfer';
10741 $classname = 'StockTransfer'; // Not the ucfirst() of the element, so it must be set explicitly
10742 $module = 'stocktransfer';
10743 $subelement = 'stocktransfer';
10744 $table_element = 'stocktransfer_stocktransfer';
10745 } elseif ($elementType == 'job' || $elementType == 'position' || $elementType == 'skill' || $elementType == 'evaluation') {
10746 $classpath = 'hrm/class';
10747 $classfile = $elementType;
10748 $classname = ucfirst($elementType);
10749 $module = 'hrm';
10750 $subelement = $elementType;
10751 $table_element = ($elementType == 'position' ? 'hrm_job_user' : 'hrm_'.$elementType);
10752 $subdir = '/'.$elementType;
10753 } elseif ($elementType == 'productlot') {
10754 $module = 'productbatch';
10755 $classpath = 'product/stock/class';
10756 $classfile = 'productlot';
10757 $classname = 'Productlot';
10758 $element = 'productlot';
10759 $subelement = '';
10760 $table_element = 'product_lot';
10761 } elseif ($elementType == 'societeaccount') {
10762 $classpath = 'societe/class';
10763 $classfile = 'societeaccount';
10764 $classname = 'SocieteAccount';
10765 $module = 'societe';
10766 } elseif ($elementType == 'websitepage' || $elementType == 'website_page') {
10767 $classpath = 'website/class';
10768 $classfile = 'websitepage';
10769 $classname = 'Websitepage';
10770 $module = 'website';
10771 $subelement = 'websitepage';
10772 $table_element = 'website_page';
10773 } elseif ($elementType == 'fiscalyear') {
10774 $classpath = 'core/class';
10775 $module = 'accounting';
10776 $subelement = 'fiscalyear';
10777 } elseif ($elementType == 'chargesociales') {
10778 $classpath = 'compta/sociales/class';
10779 $module = 'tax';
10780 $table_element = 'chargesociales';
10781 } elseif ($elementType == 'tva') {
10782 $classpath = 'compta/tva/class';
10783 $module = 'tax';
10784 $subdir = '/vat';
10785 $table_element = 'tva';
10786 } elseif ($elementType == 'emailsenderprofile') {
10787 $module = '';
10788 $classpath = 'core/class';
10789 $classfile = 'emailsenderprofile';
10790 $classname = 'EmailSenderProfile';
10791 $table_element = 'c_email_senderprofile';
10792 $subelement = '';
10793 } elseif ($elementType == 'conferenceorboothattendee') {
10794 $classpath = 'eventorganization/class';
10795 $classfile = 'conferenceorboothattendee';
10796 $classname = 'ConferenceOrBoothAttendee';
10797 $module = 'eventorganization';
10798 } elseif ($elementType == 'conferenceorbooth') {
10799 $classpath = 'eventorganization/class';
10800 $classfile = 'conferenceorbooth';
10801 $classname = 'ConferenceOrBooth';
10802 $module = 'eventorganization';
10803 $subdir = '/conferenceorbooth';
10804 } elseif ($elementType == 'ccountry') {
10805 $module = '';
10806 $classpath = 'core/class';
10807 $classfile = 'ccountry';
10808 $classname = 'Ccountry';
10809 $table_element = 'c_country';
10810 $subelement = '';
10811 } elseif ($elementType == 'ecmfiles') {
10812 $module = 'ecm';
10813 $classpath = 'ecm/class';
10814 $classfile = 'ecmfiles';
10815 $classname = 'Ecmfiles';
10816 $table_element = 'ecmfiles';
10817 $subelement = '';
10818 } elseif ($elementType == 'knowledgerecord' || $elementType == 'knowledgemanagement') {
10819 $module = 'knowledgemanagement';
10820 $classpath = 'knowledgemanagement/class';
10821 $classfile = 'knowledgerecord';
10822 $classname = 'KnowledgeRecord';
10823 $table_element = 'knowledgemanagement_knowledgerecord';
10824 $subelement = '';
10825 } elseif ($elementType == 'customer') {
10826 $module = 'societe';
10827 $classpath = 'societe/class';
10828 $classfile = 'client';
10829 $classname = 'Client';
10830 $table_element = 'societe';
10831 $subelement = '';
10832 } elseif ($elementType == 'fournisseur' || $elementType == 'supplier') {
10833 $module = 'societe';
10834 $classpath = 'fourn/class';
10835 $classfile = 'fournisseur';
10836 $classname = 'Fournisseur';
10837 $table_element = 'societe';
10838 $subelement = '';
10839 } elseif ($elementType == 'recruitmentcandidature') {
10840 $module = 'recruitment';
10841 $classfile = 'recruitmentcandidature';
10842 $classpath = 'recruitment/class';
10843 $classname = 'RecruitmentCandidature';
10844 $subelement = 'recruitmentcandidature';
10845 $subdir = '/recruitmentcandidature';
10846 } elseif ($elementType == 'recruitmentjobposition') {
10847 $module = 'recruitment';
10848 $classfile = 'recruitmentjobposition';
10849 $classpath = 'recruitment/class';
10850 $classname = 'RecruitmentJobPosition';
10851 $subelement = 'recruitmentjobposition';
10852 $subdir = '/recruitmentjobposition';
10853 }
10854
10855
10856 if (empty($classfile)) {
10857 $classfile = strtolower($subelement);
10858 }
10859 if (empty($classname)) {
10860 $classname = ucfirst($subelement);
10861 }
10862 if (empty($classpath)) {
10863 $classpath = $module . '/class';
10864 }
10865
10866 //print 'getElementProperties subdir='.$subdir;
10867
10868 // Set dir_output
10869 if ($module && isset($conf->$module)) { // The generic case
10870 if (!empty($conf->$module->multidir_output[$conf->entity])) {
10871 $dir_output = $conf->$module->multidir_output[$conf->entity];
10872 } elseif (!empty($conf->$module->output[$conf->entity])) {
10873 $dir_output = $conf->$module->output[$conf->entity];
10874 } elseif (!empty($conf->$module->dir_output)) {
10875 $dir_output = $conf->$module->dir_output;
10876 }
10877 if (!empty($conf->$module->multidir_temp[$conf->entity])) {
10878 $dir_temp = $conf->$module->multidir_temp[$conf->entity];
10879 } elseif (!empty($conf->$module->temp[$conf->entity])) {
10880 $dir_temp = $conf->$module->temp[$conf->entity];
10881 } elseif (!empty($conf->$module->dir_temp)) {
10882 $dir_temp = $conf->$module->dir_temp;
10883 }
10884 }
10885
10886 // Overwrite value for special cases
10887 if ($element == 'order_supplier' && isModEnabled('fournisseur')) {
10888 $dir_output = $conf->fournisseur->commande->dir_output;
10889 $dir_temp = $conf->fournisseur->commande->dir_temp;
10890 } elseif ($element == 'invoice_supplier' && isModEnabled('fournisseur')) {
10891 $dir_output = $conf->fournisseur->facture->dir_output;
10892 $dir_temp = $conf->fournisseur->facture->dir_temp;
10893 } elseif ($elementType == 'payment' && isModEnabled('invoice') && isset($conf->compta->payment)) {
10894 // A customer payment is stored into a sub object of $conf, not handled by the generic case.
10895 // Note: we must test $elementType and not $element, because the 'myobject_mysubobject' rule above
10896 // rewrites $element to 'payment' for the element 'payment_salary' too, which is stored elsewhere.
10897 $dir_output = $conf->compta->payment->dir_output;
10898 $dir_temp = $conf->compta->payment->dir_temp;
10899 } elseif ($elementType == 'payment_supplier' && isModEnabled('fournisseur') && isset($conf->fournisseur->payment)) {
10900 $dir_output = $conf->fournisseur->payment->dir_output;
10901 $dir_temp = $conf->fournisseur->payment->dir_temp;
10902 }
10903 // The sub directory must not be appended when the module is disabled, because $dir_output is then empty
10904 // and we would return a path at the root of the file system instead of an empty string.
10905 if (!empty($dir_output)) {
10906 $dir_output .= $subdir;
10907 }
10908 if (!empty($dir_temp)) {
10909 $dir_temp .= $subdir;
10910 }
10911
10912 $elementProperties = array(
10913 'module' => $module,
10914 'element' => $element,
10915 'table_element' => $table_element,
10916 'subelement' => $subelement,
10917 'classpath' => $classpath,
10918 'classfile' => $classfile,
10919 'classname' => $classname,
10920 'dir_output' => $dir_output,
10921 'dir_temp' => $dir_temp,
10922 'parent_element' => $parent_element,
10923 );
10924
10925
10926 // Add hook
10927 if (!is_object($hookmanager)) {
10928 include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
10929 $hookmanager = new HookManager($db);
10930 }
10931 $hookmanager->initHooks(array('elementproperties'));
10932
10933
10934 // Hook params
10935 $parameters = array(
10936 'elementType' => $elementType,
10937 'elementProperties' => $elementProperties
10938 );
10939
10940 $reshook = $hookmanager->executeHooks('getElementProperties', $parameters);
10941
10942 if ($reshook) {
10943 $elementProperties = $hookmanager->resArray;
10944 } elseif (!empty($hookmanager->resArray) && is_array($hookmanager->resArray)) { // resArray is always an array but for security against misconfigured external modules
10945 $elementProperties = array_replace($elementProperties, $hookmanager->resArray);
10946 }
10947
10948 // context of elementproperties doesn't need to exist out of this function so delete it to avoid elementproperties context is equal to all
10949 if (($key = array_search('elementproperties', $hookmanager->contextarray)) !== false) {
10950 unset($hookmanager->contextarray[$key]);
10951 }
10952
10953 return $elementProperties;
10954}
10955
10969function fetchObjectByElement($element_id, $element_type, $element_ref = '', $useCache = 0, $maxCacheByType = 10)
10970{
10971 global $db, $conf;
10972
10973 $ret = 0;
10974
10975 $element_prop = getElementProperties($element_type);
10976 //var_dump($element_prop); exit;
10977
10978 if ($element_prop['module'] == 'product' || $element_prop['module'] == 'service') {
10979 // For example, for an extrafield 'product' (shared for both product and service) that is a link to an object,
10980 // this is called with $element_type = 'product' when we need element properties of a service, we must return a product. If we create the
10981 // extrafield for a service, it is not supported and not found when editing the product/service card. So we must keep 'product' for extrafields
10982 // of service and we will return properties of a product.
10983 $ismodenabled = (isModEnabled('product') || isModEnabled('service'));
10984 } elseif ($element_prop['module'] == 'societeaccount') {
10985 $ismodenabled = isModEnabled('website') || isModEnabled('webportal');
10986 } else {
10987 $ismodenabled = isModEnabled($element_prop['module']);
10988 }
10989 //var_dump('element_type='.$element_type);
10990 //var_dump($element_prop);
10991 //var_dump($element_prop['module'].' '.$ismodenabled);
10992 if (is_array($element_prop) && (empty($element_prop['module']) || $ismodenabled)) {
10993 if ($useCache === 1 && $element_id > 0
10994 && !empty($conf->cache['fetchObjectByElement'][$element_type])
10995 && !empty($conf->cache['fetchObjectByElement'][$element_type][$element_id])
10996 && is_object($conf->cache['fetchObjectByElement'][$element_type][$element_id])
10997 ) {
10998 return $conf->cache['fetchObjectByElement'][$element_type][$element_id];
10999 }
11000
11001 dol_include_once('/' . $element_prop['classpath'] . '/' . $element_prop['classfile'] . '.class.php');
11002
11003 if (class_exists($element_prop['classname'])) {
11004 $className = $element_prop['classname'];
11005 $objecttmp = new $className($db);
11006 '@phan-var-force CommonObject $objecttmp';
11009 if ($element_id > 0 || !empty($element_ref)) {
11010 $ret = $objecttmp->fetch($element_id, $element_ref);
11011 if ($ret >= 0) {
11012 if (empty($objecttmp->module)) {
11013 $objecttmp->module = $element_prop['module'];
11014 }
11015
11016 if ($useCache > 0) {
11017 if (!isset($conf->cache['fetchObjectByElement'][$element_type])) {
11018 $conf->cache['fetchObjectByElement'][$element_type] = [];
11019 }
11020
11021 // Manage cache limit
11022 if (! empty($conf->cache['fetchObjectByElement'][$element_type]) && is_array($conf->cache['fetchObjectByElement'][$element_type]) && count($conf->cache['fetchObjectByElement'][$element_type]) >= $maxCacheByType) {
11023 array_shift($conf->cache['fetchObjectByElement'][$element_type]);
11024 }
11025
11026 $conf->cache['fetchObjectByElement'][$element_type][$element_id] = $objecttmp;
11027 }
11028
11029 return $objecttmp;
11030 }
11031 } else {
11032 return $objecttmp; // returned an object without fetch
11033 }
11034 } else {
11035 dol_syslog($element_prop['classname'] . ' doesn\'t exists in /' . $element_prop['classpath'] . '/' . $element_prop['classfile'] . '.class.php');
11036 return -1;
11037 }
11038 }
11039
11040 return $ret;
11041}
11042
11048function getExecutableContent()
11049{
11050 $arrayofregexextension = array(
11051 'htm',
11052 'html',
11053 'shtml',
11054 'js',
11055 'phar',
11056 'php',
11057 'php3',
11058 'php4',
11059 'php5',
11060 'phtml',
11061 'pht',
11062 'pl',
11063 'py',
11064 'cgi',
11065 'ksh',
11066 'sh',
11067 'shtml',
11068 'bash',
11069 'bat',
11070 'cmd',
11071 'wpk',
11072 'exe',
11073 'dmg',
11074 'appimage'
11075 );
11076
11077 return $arrayofregexextension;
11078}
11079
11086function isAFileWithExecutableContent($filename)
11087{
11088 $arrayofregexextension = getExecutableContent();
11089
11090 foreach ($arrayofregexextension as $fileextension) {
11091 if (preg_match('/\.' . preg_quote($fileextension, '/') . '$/i', $filename)) {
11092 return true;
11093 }
11094 }
11095
11096 return false;
11097}
11098
11106function newToken()
11107{
11108 return empty($_SESSION['newtoken']) ? '' : $_SESSION['newtoken'];
11109}
11110
11118function currentToken()
11119{
11120 return isset($_SESSION['token']) ? $_SESSION['token'] : '';
11121}
11122
11128function getNonce()
11129{
11130 global $conf;
11131
11132 if (empty($conf->cache['nonce'])) {
11133 include_once DOL_DOCUMENT_ROOT . '/core/lib/security.lib.php';
11134 $conf->cache['nonce'] = dolGetRandomBytes(8);
11135 }
11136
11137 return $conf->cache['nonce'];
11138}
11139
11140
11149function readfileLowMemory($fullpath_original_file_osencoded, $method = -1)
11150{
11151 if ($method == -1) {
11152 $method = 0;
11153 if (getDolGlobalString('MAIN_FORCE_READFILE_WITH_FREAD')) {
11154 $method = 1;
11155 }
11156 if (getDolGlobalString('MAIN_FORCE_READFILE_WITH_STREAM_COPY')) {
11157 $method = 2;
11158 }
11159 }
11160
11161 // Be sure we don't have output buffering enabled to have readfile working correctly
11162 $level = ob_get_level();
11163 ob_start();
11164 while (ob_get_level() > $level) {
11165 ob_end_clean();
11166 }
11167
11168 // Solution 0
11169 if ($method == 0) {
11170 readfile($fullpath_original_file_osencoded);
11171 } elseif ($method == 1) {
11172 // Solution 1
11173 $handle = fopen($fullpath_original_file_osencoded, "rb");
11174 while (!feof($handle)) {
11175 print fread($handle, 8192);
11176 }
11177 fclose($handle);
11178 } elseif ($method == 2) {
11179 // Solution 2
11180 $handle1 = fopen($fullpath_original_file_osencoded, "rb");
11181 $handle2 = fopen("php://output", "wb");
11182 stream_copy_to_stream($handle1, $handle2);
11183 fclose($handle1);
11184 fclose($handle2);
11185 }
11186}
11187
11188
11196function jsonOrUnserialize($stringtodecode, $assoc = true)
11197{
11198 $result = json_decode($stringtodecode, $assoc);
11199 if ($result === null) {
11200 $result = unserialize($stringtodecode, ['allowed_classes' => false]); // For backward compatibility. Is no more used in recent versions.
11201 }
11202
11203 return $result;
11204}
11205
11206
11224function forgeSQLFromUniversalSearchCriteria($filter, &$errorstr = '', $noand = 0, $nopar = 0, $noerror = 0, $forbiddenfields = array())
11225{
11226 global $db, $user;
11227
11228 if (is_null($filter) || !is_string($filter) || $filter === '') {
11229 return '';
11230 }
11231 if (!preg_match('/^\‍(.*\‍)$/', $filter)) { // If $filter does not start and end with ()
11232 $filter = '(' . $filter . ')';
11233 }
11234
11235 $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'
11236 $firstandlastparenthesis = 0;
11237
11238 if (!dolCheckFilters($filter, $errorstr, $firstandlastparenthesis)) {
11239 if ($noerror) {
11240 return '1 = 2';
11241 } else {
11242 return 'Filter syntax error - ' . $errorstr; // Bad balance of parenthesis, we return an error message or force a SQL not found
11243 }
11244 }
11245
11246 // Test the filter syntax
11247 $t = preg_replace_callback('/' . $regexstring . '/i', 'dolForgeDummyCriteriaCallback', $filter);
11248 $t = str_ireplace(array('and', 'or', ' '), '', $t); // Remove the only strings allowed between each () criteria
11249
11250 // If the string result contains something else than '()', the syntax was wrong
11251 if (preg_match('/[^\‍(\‍)]/', $t)) {
11252 $tmperrorstr = 'Bad syntax of the search string';
11253 $errorstr = 'Bad syntax of the search string: ' . $filter;
11254 if ($noerror) {
11255 return '1 = 2';
11256 } else {
11257 dol_syslog("forgeSQLFromUniversalSearchCriteria Filter error - " . $errorstr, LOG_WARNING);
11258 return 'Filter error - ' . $tmperrorstr; // Bad syntax of the search string, we return an error message or force a SQL not found
11259 }
11260 }
11261
11262 global $globalforbiddenfields; // For use by dolForgeSQLCriteriaCallback()
11263 $globalforbiddenfields = $forbiddenfields;
11264
11265 $ret = ($noand ? "" : " AND ") . ($nopar ? "" : '(');
11266 $ret .= preg_replace_callback('/' . $regexstring . '/i', 'dolForgeSQLCriteriaCallback', $filter);
11267 $ret .= ($nopar ? "" : ')');
11268
11269 if (is_object($db)) {
11270 $ret = str_replace('__NOW__', "'" . $db->idate(dol_now()) . "'", $ret);
11271 }
11272 if (is_object($user)) {
11273 $ret = str_replace('__USER_ID__', (string) $user->id, $ret);
11274 }
11275
11276 return $ret;
11277}
11278
11286function dolForgeExplodeAnd($sqlfilters)
11287{
11288 $arrayofandtags = array();
11289 $nbofchars = dol_strlen($sqlfilters);
11290
11291 $error = '';
11292 $parenthesislevel = 0;
11293 $result = dolCheckFilters($sqlfilters, $error, $parenthesislevel);
11294 if (!$result) {
11295 return array();
11296 }
11297 if ($parenthesislevel >= 1) {
11298 $sqlfilters = preg_replace('/^\‍(/', '', preg_replace('/\‍)$/', '', $sqlfilters));
11299 }
11300
11301 $i = 0;
11302 $s = '';
11303 $countparenthesis = 0;
11304 while ($i < $nbofchars) {
11305 $char = dol_substr($sqlfilters, $i, 1);
11306
11307 if ($char == '(') {
11308 $countparenthesis++;
11309 } elseif ($char == ')') {
11310 $countparenthesis--;
11311 }
11312
11313 if ($countparenthesis == 0) {
11314 $char2 = dol_substr($sqlfilters, $i + 1, 1);
11315 $char3 = dol_substr($sqlfilters, $i + 2, 1);
11316 if ($char == 'A' && $char2 == 'N' && $char3 == 'D') {
11317 // We found a AND
11318 $s = trim($s);
11319 if (!preg_match('/^\‍(.*\‍)$/', $s)) {
11320 $s = '(' . $s . ')';
11321 }
11322 $arrayofandtags[] = $s;
11323 $s = '';
11324 $i += 2;
11325 } else {
11326 $s .= $char;
11327 }
11328 } else {
11329 $s .= $char;
11330 }
11331 $i++;
11332 }
11333 if ($s) {
11334 $s = trim($s);
11335 if (!preg_match('/^\‍(.*\‍)$/', $s)) {
11336 $s = '(' . $s . ')';
11337 }
11338 $arrayofandtags[] = $s;
11339 }
11340
11341 return $arrayofandtags;
11342}
11343
11353function dolCheckFilters($sqlfilters, &$error = '', &$parenthesislevel = 0)
11354{
11355 //$regexstring='\‍(([^:\'\‍(\‍)]+:[^:\'\‍(\‍)]+:[^:\‍(\‍)]+)\‍)';
11356 //$tmp=preg_replace_all('/'.$regexstring.'/', '', $sqlfilters);
11357 $tmp = $sqlfilters;
11358
11359 $nb = dol_strlen($tmp);
11360 $counter = 0;
11361 $parenthesislevel = 0;
11362
11363 $error = '';
11364
11365 $i = 0;
11366 while ($i < $nb) {
11367 $char = dol_substr($tmp, $i, 1);
11368
11369 if ($char == '(') {
11370 if ($i == $parenthesislevel && $parenthesislevel == $counter) {
11371 // We open a parenthesis and it is the first char
11372 $parenthesislevel++;
11373 }
11374 $counter++;
11375 } elseif ($char == ')') {
11376 $nbcharremaining = ($nb - $i - 1);
11377 if ($nbcharremaining >= $counter) {
11378 $parenthesislevel = min($parenthesislevel, $counter - 1);
11379 }
11380 if ($parenthesislevel > $counter && $nbcharremaining >= $counter) {
11381 $parenthesislevel = $counter;
11382 }
11383 $counter--;
11384 }
11385
11386 if ($counter < 0) {
11387 $error = "Wrong balance of parenthesis in sqlfilters=" . $sqlfilters;
11388 $parenthesislevel = 0;
11389 dol_syslog($error, LOG_WARNING);
11390 return false;
11391 }
11392
11393 $i++;
11394 }
11395
11396 if ($counter > 0) {
11397 $error = "Wrong balance of parenthesis in sqlfilters=" . $sqlfilters;
11398 $parenthesislevel = 0;
11399 dol_syslog($error, LOG_WARNING);
11400 return false;
11401 }
11402
11403 return true;
11404}
11405
11413function dolForgeDummyCriteriaCallback($matches)
11414{
11415 //dol_syslog("Convert matches ".$matches[1]);
11416 if (empty($matches[1])) {
11417 return '';
11418 }
11419 $tmp = explode(':', $matches[1]);
11420 if (count($tmp) < 3) {
11421 return '';
11422 }
11423
11424 return '()'; // An empty criteria
11425}
11426
11436function dolForgeSQLCriteriaCallback($matches)
11437{
11438 global $db;
11439 global $globalforbiddenfields;
11440
11441 //dol_syslog("Convert matches ".$matches[1]);
11442 if (empty($matches[1])) {
11443 return '';
11444 }
11445 $tmp = explode(':', $matches[1], 3);
11446 if (count($tmp) < 3) {
11447 return '';
11448 }
11449
11450 // Add fields that are forbidden by the caller when using USF search criteria
11451 // so we can't guess them using sequantial comparisons
11452 $newforbiddenfields = $globalforbiddenfields;
11453 if (!is_array($newforbiddenfields)) {
11454 $newforbiddenfields = array();
11455 }
11456 // Add fields that are ALWAYS forbidden when using USF search criteria
11457 $newforbiddenfields[] = 'pass';
11458 $newforbiddenfields[] = 'pass_crypted';
11459 $newforbiddenfields[] = 'api_key';
11460
11461 $operand = preg_replace('/[^a-z0-9\._]/i', '', trim($tmp[0]));
11462
11463 // Test that operand is not a forbidden search field
11464 if (!empty($newforbiddenfields)) {
11465 $operandwithoutprefix = preg_replace('/^[a-z0-9_]+\./i', '', $operand); // Remove prefix like t. or o. or s. or u. or d. or ...
11466 if (in_array(strtolower($operandwithoutprefix), $newforbiddenfields)) {
11467 return '1=1';
11468 }
11469 }
11470
11471 $operator = strtoupper(preg_replace('/[^a-z<>!=]/i', '', trim($tmp[1])));
11472
11473 $realOperator = [
11474 'NOTLIKE' => 'NOT LIKE',
11475 'ISNOT' => 'IS NOT',
11476 'NOTIN' => 'NOT IN',
11477 '!=' => '<>',
11478 ];
11479
11480 if (array_key_exists($operator, $realOperator)) {
11481 $operator = $realOperator[$operator];
11482 }
11483
11484 $tmpescaped = $tmp[2];
11485
11486 //print "Case: ".$operator." ".$operand." ".$tmpescaped."\n";
11487
11488 $regbis = array();
11489
11490 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)
11491 global $dolibarr_allow_unsecured_select_in_extrafields_filter;
11492
11493 //if (!preg_match('/^\‍(.*\‍)$/', $tmpescaped)) {
11494 $tmpescaped2 = '(';
11495 // Explode and sanitize each element in list
11496 $tmpelemarray = explode(',', $tmpescaped);
11497 foreach ($tmpelemarray as $tmpkey => $tmpelem) {
11498 $reg = array();
11499 $tmpelem = trim($tmpelem);
11500 if (preg_match('/^\'(.*)\'$/', $tmpelem, $reg)) {
11501 $tmpelemarray[$tmpkey] = "'" . $db->escape($db->sanitize($reg[1], 2, 1, 1, 1)) . "'";
11502 } elseif (ctype_digit((string) $tmpelem)) { // if only 0-9 chars, no .
11503 $tmpelemarray[$tmpkey] = (int) $tmpelem;
11504 } elseif (is_numeric((string) $tmpelem)) { // it can be a float with a .
11505 $tmpelemarray[$tmpkey] = (float) $tmpelem;
11506 } elseif (!empty($dolibarr_allow_unsecured_select_in_extrafields_filter)) {
11507 $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)
11508 } else {
11509 $tmpelemarray[$tmpkey] = preg_replace('/[^a-z0-9_]/i', '', $tmpelem); // it can be a name of field or a substitution variable like '__NOW__'
11510 }
11511 }
11512 $tmpescaped2 .= implode(',', $tmpelemarray);
11513 $tmpescaped2 .= ')';
11514
11515 $tmpescaped = $tmpescaped2;
11516 } elseif ($operator == 'LIKE' || $operator == 'NOT LIKE') {
11517 if (preg_match('/^\'([^\']*)\'$/', $tmpescaped, $regbis)) {
11518 $tmpescaped = $regbis[1];
11519 }
11520 //$tmpescaped = "'".$db->escape($db->escapeforlike($regbis[1]))."'";
11521 $tmpescaped = "'" . $db->escape($tmpescaped) . "'"; // We do not escape the _ and % so the LIKE will work as expected
11522 } elseif (preg_match('/^\'(.*)\'$/', $tmpescaped, $regbis)) {
11523 // TODO Retrieve type of field for $operand field name.
11524 // So we can complete format. For example we could complete a year with month and day.
11525 $tmpescaped = "'" . $db->escape($regbis[1]) . "'";
11526 } else {
11527 if (strtoupper($tmpescaped) == 'NULL') {
11528 $tmpescaped = 'NULL';
11529 } elseif (ctype_digit((string) $tmpescaped)) { // if only 0-9 chars, no .
11530 $tmpescaped = (int) $tmpescaped;
11531 } elseif (is_numeric((string) $tmpescaped)) { // it can be a float with a .
11532 $tmpescaped = (float) $tmpescaped;
11533 } else {
11534 $tmpescaped = preg_replace('/[^a-z0-9_]/i', '', $tmpescaped); // it can be a name of field or a substitution variable like '__NOW__'
11535 }
11536 }
11537
11538 return '(' . $db->escape($operand) . ' ' . strtoupper($operator) . ' ' . $tmpescaped . ')';
11539}
11540
11541
11551function getTimelineIcon($actionstatic, &$histo, $key)
11552{
11553 dol_syslog('getTimelineIcon::begin', LOG_DEBUG);
11554 global $langs;
11555
11556 $out = '<!-- timeline icon -->' . "\n";
11557 $iconClass = 'fa fa-comments';
11558 $img_picto = '';
11559 $colorClass = '';
11560 $pictoTitle = '';
11561
11562 if ($histo[$key]['percent'] == -1) {
11563 $colorClass = 'timeline-icon-not-applicble';
11564 $pictoTitle = $langs->trans('StatusNotApplicable');
11565 } elseif ($histo[$key]['percent'] == 0) {
11566 $colorClass = 'timeline-icon-todo';
11567 $pictoTitle = $langs->trans('StatusActionToDo') . ' (0%)';
11568 } elseif ($histo[$key]['percent'] > 0 && $histo[$key]['percent'] < 100) {
11569 $colorClass = 'timeline-icon-in-progress';
11570 $pictoTitle = $langs->trans('StatusActionInProcess') . ' (' . $histo[$key]['percent'] . '%)';
11571 } elseif ($histo[$key]['percent'] >= 100) {
11572 $colorClass = 'timeline-icon-done';
11573 $pictoTitle = $langs->trans('StatusActionDone') . ' (100%)';
11574 }
11575
11576 if ($actionstatic->code == 'AC_TICKET_CREATE') {
11577 $iconClass = 'fa fa-ticket';
11578 } elseif ($actionstatic->code == 'AC_TICKET_MODIFY') {
11579 $iconClass = 'fa fa-pencilxxx';
11580 } elseif (preg_match('/^TICKET_MSG/', $actionstatic->code)) {
11581 $iconClass = 'fa fa-comments';
11582 } elseif (preg_match('/^TICKET_MSG_PRIVATE/', $actionstatic->code)) {
11583 $iconClass = 'fa fa-mask';
11584 } elseif (getDolGlobalString('AGENDA_USE_EVENT_TYPE')) {
11585 if ($actionstatic->type_picto) {
11586 $img_picto = img_picto('', $actionstatic->type_picto);
11587 } else {
11588 if ($actionstatic->type_code == 'AC_RDV') {
11589 $iconClass = 'fa fa-handshake';
11590 } elseif ($actionstatic->type_code == 'AC_TEL') {
11591 $iconClass = 'fa fa-phone';
11592 } elseif ($actionstatic->type_code == 'AC_FAX') {
11593 $iconClass = 'fa fa-fax';
11594 } elseif ($actionstatic->type_code == 'AC_EMAIL') {
11595 $iconClass = 'fa fa-envelope';
11596 } elseif ($actionstatic->type_code == 'AC_INT') {
11597 $iconClass = 'fa fa-shipping-fast';
11598 } elseif ($actionstatic->type_code == 'AC_OTH_AUTO') {
11599 $iconClass = 'fa fa-robot';
11600 } elseif (!preg_match('/_AUTO/', $actionstatic->type_code)) {
11601 $iconClass = 'fa fa-robot';
11602 }
11603 }
11604 }
11605
11606 $out .= '<i class="' . $iconClass . ' ' . $colorClass . '" title="' . $pictoTitle . '">' . $img_picto . '</i>' . "\n";
11607 return $out;
11608}
11609
11617{
11618 global $db;
11619
11620 $documents = array();
11621
11622 $sql = 'SELECT ecm.rowid as id, ecm.src_object_type, ecm.src_object_id, ecm.filepath, ecm.filename, ecm.agenda_id';
11623 $sql .= ' FROM ' . MAIN_DB_PREFIX . 'ecm_files ecm';
11624 $sql .= " WHERE ecm.filepath = 'agenda/" . ((int) $object->id) . "'";
11625 //$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
11626 $sql .= ' OR ecm.agenda_id = ' . (int) $object->id;
11627 $sql .= ' ORDER BY ecm.position ASC';
11628
11629 $resql = $db->query($sql);
11630 if ($resql) {
11631 if ($db->num_rows($resql)) {
11632 while ($obj = $db->fetch_object($resql)) {
11633 $documents[$obj->id] = $obj;
11634 }
11635 }
11636 }
11637
11638 return $documents;
11639}
11640
11641
11653function buildParamDate($prefix, $timestamp = null, $hourTime = '', $gm = 'auto')
11654{
11655 if ($timestamp === null) {
11656 $timestamp = GETPOSTDATE($prefix, $hourTime, $gm);
11657 }
11658 $TParam = array(
11659 $prefix . 'day' => intval(dol_print_date($timestamp, '%d')),
11660 $prefix . 'month' => intval(dol_print_date($timestamp, '%m')),
11661 $prefix . 'year' => intval(dol_print_date($timestamp, '%Y')),
11662 );
11663 if ($hourTime === 'getpost' || ($timestamp !== null && dol_print_date($timestamp, '%H:%M:%S') !== '00:00:00')) {
11664 $TParam = array_merge($TParam, array(
11665 $prefix . 'hour' => intval(dol_print_date($timestamp, '%H')),
11666 $prefix . 'min' => intval(dol_print_date($timestamp, '%M')),
11667 $prefix . 'sec' => intval(dol_print_date($timestamp, '%S'))
11668 ));
11669 }
11670
11671 return '&' . http_build_query($TParam);
11672}
11673
11692function recordNotFound($message = '', $printheader = 1, $printfooter = 1, $showonlymessage = 0, $params = null)
11693{
11694 global $conf, $db, $langs, $hookmanager;
11695 global $action, $object;
11696
11697 if (!is_object($langs)) {
11698 include_once DOL_DOCUMENT_ROOT . '/core/class/translate.class.php';
11699 $langs = new Translate('', $conf);
11700 $langs->setDefaultLang();
11701 }
11702
11703 $langs->load("errors");
11704
11705 if ($printheader) {
11706 if (function_exists("llxHeader")) {
11707 llxHeader('');
11708 } elseif (function_exists("llxHeaderVierge")) {
11709 llxHeaderVierge('');
11710 }
11711 }
11712
11713 print '<div class="error">';
11714 if (empty($message)) {
11715 print $langs->trans("ErrorRecordNotFound");
11716 } else {
11717 print $langs->trans($message);
11718 }
11719 print '</div>';
11720 print '<br>';
11721
11722 if (empty($showonlymessage)) {
11723 if (empty($hookmanager)) {
11724 include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
11725 $hookmanager = new HookManager($db);
11726 // Initialize a technical object to manage hooks of page. Note that conf->hooks_modules contains an array of hook context
11727 $hookmanager->initHooks(array('main'));
11728 }
11729
11730 $parameters = array('message' => $message, 'params' => $params);
11731 $reshook = $hookmanager->executeHooks('getErrorRecordNotFound', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
11732 print $hookmanager->resPrint;
11733 }
11734
11735 if ($printfooter && function_exists("llxFooter")) {
11736 llxFooter();
11737 if (is_object($db)) {
11738 $db->close();
11739 }
11740 }
11741 exit(0);
11742}
11743
11774function array_merge_recursive_distinct(array $array1, array $array2): array
11775{
11776 $merged = $array1;
11777
11778 foreach ($array2 as $key => $value) {
11779 if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
11780 $merged[$key] = array_merge_recursive_distinct($merged[$key], $value);
11781 } else {
11782 $merged[$key] = $value;
11783 }
11784 }
11785
11786 return $merged;
11787}
11788
11795function getObjectSocId($obj)
11796{
11797 if (!empty($obj->socid)) {
11798 return (int) $obj->socid;
11799 } elseif (!empty($obj->soc_id)) {
11800 return (int) $obj->soc_id;
11801 } elseif (!empty($obj->societe_id)) {
11802 return (int) $obj->societe_id;
11803 }
11804 return null;
11805}
11806
11813{
11814 $default = 10;
11815 if (!empty($_SESSION['dol_screenheight']) && $_SESSION['dol_screenheight'] < 700) {
11816 $default = 8;
11817 } elseif (!empty($_SESSION['dol_screenheight']) && $_SESSION['dol_screenheight'] < 950) {
11818 $default = 10;
11819 } elseif (!empty($_SESSION['dol_screenheight']) && $_SESSION['dol_screenheight'] > 1130) {
11820 $default = 15;
11821 }
11822
11823 return $default;
11824}
$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:334
$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.
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:524
dol_get_next_day($day, $month, $year)
Return next day.
Definition date.lib.php:509
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:493
dol_get_next_month($month, $year)
Return next month.
Definition date.lib.php:543
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:
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
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)
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.
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)
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.
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).
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.
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)
getDoliDBInstance($type, $host, $user, $pass, $name, $port)
Return a DoliDB instance (database handler).
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).
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
img_warning($titlealt='default', $moreatt='', $morecss='pictowarning')
Show warning logo.
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
showSimpleHTMLTable($outputlangs, $object)
Returns simple order table template as string.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
Definition html.lib.php:172
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string
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