dolibarr 24.0.1
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 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 'commande_fournisseur':
191 $module = 'fournisseur';
192 $subdirectory = '/commande';
193 break;
194 case 'expedition':
195 case 'shipment':
196 case 'shipping':
197 $module = 'expedition';
198 $subdirectory = '/sending';
199 break;
200 case 'company':
201 $module = 'societe';
202 break;
203 case 'service':
204 case 'produit':
205 $module = 'product';
206 break;
207 case 'project_task':
208 $module = 'projet';
209
210 // Fetch the project to build the correct path
211 $object->fetchProject();
212
213 $subdirectory = '/'.$object->project->ref;
214 break;
215 case 'action':
216 case 'actioncomm':
217 case 'event':
218 $module = 'agenda';
219 break;
220 default:
221 break;
222 }
223
224 // Get the relative path of directory
225 if ($mode == 'output' || $mode == 'outputrel' || $mode == 'version') {
226 if (isset($conf->$module) && property_exists($conf->$module, 'multidir_output')) {
227 $s = '';
228 if ($mode != 'outputrel') {
229 // An entity with no declared directory returned an undefined index, so an empty path that
230 // made the caller read or write a relative path under the web root. Answer the error instead.
231 $entity = (int) (empty($object->entity) ? $conf->entity : $object->entity);
232 if (!isset($conf->$module->multidir_output[$entity])) {
233 return 'error-diroutput-not-defined-for-this-entity-and-object='.$module;
234 }
235 $s = $conf->$module->multidir_output[$entity] . $subdirectory;
236 }
237 if ($forobject && $object->id > 0) {
238 $s .= ($mode != 'outputrel' ? '/' : '') . get_exdir(0, 0, 0, 0, $object);
239 }
240 return dol_sanitizePathName($s);
241 } elseif (isset($conf->$module) && property_exists($conf->$module, 'dir_output')) {
242 $s = '';
243 if ($mode != 'outputrel') {
244 $s = $conf->$module->dir_output . $subdirectory;
245 }
246 if ($forobject && $object->id > 0) {
247 $s .= ($mode != 'outputrel' ? '/' : '') . get_exdir(0, 0, 0, 0, $object);
248 }
249 return dol_sanitizePathName($s);
250 } else {
251 return 'error-diroutput-not-defined-for-this-entity-and-object='.$module;
252 }
253 } elseif ($mode == 'temp') {
254 if (isset($conf->$module) && property_exists($conf->$module, 'multidir_temp')) {
255 // Same guard as the 'output' mode above, see the comment there
256 $entity = (int) (empty($object->entity) ? $conf->entity : $object->entity);
257 if (!isset($conf->$module->multidir_temp[$entity])) {
258 return 'error-dirtemp-not-defined-for-this-entity-and-object='.$module;
259 }
260 return dol_sanitizePathName($conf->$module->multidir_temp[$entity]);
261 } elseif (isset($conf->$module) && property_exists($conf->$module, 'dir_temp')) {
262 return dol_sanitizePathName($conf->$module->dir_temp);
263 } else {
264 return 'error-dirtemp-not-defined-for-this-entity-and-object='.$module;
265 }
266 } else {
267 return 'error-bad-value-for-mode';
268 }
269}
270
280function getMultidirTemp($object, $module = '', $forobject = 0)
281{
282 return getMultidirOutput($object, $module, $forobject, 'temp');
283}
284
294function getMultidirVersion($object, $module = '', $forobject = 0)
295{
296 return getMultidirOutput($object, $module, $forobject, 'version');
297}
298
299
308function getDolGlobalString($key, $default = '')
309{
310 global $conf;
311 return (string) (isset($conf->global->$key) ? $conf->global->$key : $default);
312}
313
320{
321 global $dolibarr_login_badcharunauthorized;
322
323 if (isset($dolibarr_login_badcharunauthorized)) {
324 if ($dolibarr_login_badcharunauthorized === 'MAIN_LOGIN_BADCHARUNAUTHORIZED') {
325 return getDolGlobalString('MAIN_LOGIN_BADCHARUNAUTHORIZED', ',@<>"\'');
326 }
327
328 return (string) $dolibarr_login_badcharunauthorized;
329 }
330
331 return ',@<>"\'';
332}
333
343function getDolGlobalInt($key, $default = 0)
344{
345 global $conf;
346 return (int) (isset($conf->global->$key) ? $conf->global->$key : $default);
347}
348
358function getDolGlobalFloat($key, $default = 0)
359{
360 global $conf;
361 return (float) (isset($conf->global->$key) ? $conf->global->$key : $default);
362}
363
372function getDolGlobalBool($key, $default = false)
373{
374 global $conf;
375 return (bool) ($conf->global->$key ?? $default);
376}
377
384{
385 global $conf;
386 return (string) $conf->currency;
387}
388
395{
396 global $conf;
397 return (string) $conf->dol_optimize_smallscreen;
398}
399
405function getDolEntity()
406{
407 global $conf;
408 return (int) $conf->entity;
409}
410
416function getDolDBType()
417{
418 global $conf;
419 return $conf->db->type;
420}
421
429{
430 return str_replace('_', '', basename(dirname($s)).basename($s, '.php'));
431}
432
442function getDolUserString($key, $default = '', $tmpuser = null)
443{
444 if (empty($tmpuser)) {
445 global $user;
446 $tmpuser = $user;
447 }
448
449 return (string) (isset($tmpuser->conf->$key) ? $tmpuser->conf->$key : $default);
450}
451
460function getDolUserInt($key, $default = 0, $tmpuser = null)
461{
462 if (empty($tmpuser)) {
463 global $user;
464 $tmpuser = $user;
465 }
466
467 return (int) (isset($tmpuser->conf->$key) ? $tmpuser->conf->$key : $default);
468}
469
470
479define(
480 'MODULE_MAPPING',
481 array(
482 // Map deprecated names to new names
483 'adherent' => 'member', // Has new directory
484 'member_type' => 'adherent_type', // No directory, but file called adherent_type
485 'banque' => 'bank', // Has new directory
486 'contrat' => 'contract', // Has new directory
487 'entrepot' => 'stock', // Has new directory
488 'projet' => 'project', // Has new directory
489 'categorie' => 'category', // Has old directory
490 'commande' => 'order', // Has old directory
491 'expedition' => 'shipping', // Has old directory
492 'facture' => 'invoice', // Has old directory
493 'fichinter' => 'intervention', // Has old directory
494 'ficheinter' => 'intervention', // Backup for 'fichinter'
495 'propale' => 'propal', // Has old directory
496 'societe' => 'thirdparty', // Has old directory
497 'socpeople' => 'contact', // Has old directory
498 'fournisseur' => 'supplier', // Has old directory
499
500 'actioncomm' => 'agenda', // NO module directory (public dir agenda)
501 'product_price' => 'productprice', // NO directory
502 'product_fournisseur_price' => 'productsupplierprice', // NO directory
503 )
504);
505
512function isModEnabled($module)
513{
514 global $conf;
515
516 // Fix old names (map to new names)
517 $arrayconv = MODULE_MAPPING;
518 $arrayconvbis = array_flip(MODULE_MAPPING);
519
520 if (!getDolGlobalString('MAIN_USE_NEW_SUPPLIERMOD')) {
521 // Special cases: both use the same module.
522 $arrayconv['supplier_order'] = 'fournisseur';
523 $arrayconv['supplier_invoice'] = 'fournisseur';
524 }
525
526 $module_alt = $module;
527 if (!empty($arrayconv[$module])) {
528 $module_alt = $arrayconv[$module];
529 }
530 $module_bis = $module;
531 if (!empty($arrayconvbis[$module])) {
532 $module_bis = $arrayconvbis[$module];
533 }
534
535 return !empty($conf->modules[$module]) || !empty($conf->modules[$module_alt]) || !empty($conf->modules[$module_bis]);
536}
537
548function getWarningDelay($module, $parmlevel1, $parmlevel2 = '')
549{
550 global $conf;
551
552 // For compatibility with bad naming on module
553 $moduletomoduletouse = array(
554 'invoice' => 'facture',
555 );
556 $moduleParmsMapping = array(
557 'product' => 'produit',
558 );
559
560 if (!empty($moduletomoduletouse[$module])) {
561 $module = $moduletomoduletouse[$module];
562 }
563
564 $warningDelayPath = $parmlevel1;
565 if (!empty($moduleParmsMapping[$warningDelayPath])) {
566 $warningDelayPath = $moduleParmsMapping[$warningDelayPath];
567 }
568
569 if ($parmlevel2) {
570 if (!empty($conf->$module) && !empty($conf->$module->$warningDelayPath) && !empty($conf->$module->$warningDelayPath->$parmlevel2) && !empty($conf->$module->$warningDelayPath->$parmlevel2->warning_delay)) {
571 return (int) $conf->$module->$warningDelayPath->$parmlevel2->warning_delay;
572 }
573 } else {
574 if (!empty($conf->$module) && !empty($conf->$module->$warningDelayPath) && !empty($conf->$module->$warningDelayPath->warning_delay)) {
575 return (int) $conf->$module->$warningDelayPath->warning_delay;
576 }
577 }
578
579 return 0;
580}
581
588function isDolTms($timestamp)
589{
590 if ($timestamp === '') {
591 dol_syslog('Using empty string for a timestamp is deprecated, prefer use of null when calling page ' . $_SERVER["PHP_SELF"] . getCallerInfoString(), LOG_DEBUG);
592 return false;
593 }
594 if (is_null($timestamp) || !is_numeric($timestamp)) {
595 return false;
596 }
597
598 return true;
599}
600
612function getDoliDBInstance($type, $host, $user, $pass, $name, $port)
613{
614 require_once DOL_DOCUMENT_ROOT . "/core/db/" . $type . '.class.php';
615
616 $class = 'DoliDB' . ucfirst($type);
617 $db = new $class($type, $host, $user, $pass, $name, $port);
618 return $db;
619}
620
638function getEntity($element, $shared = 1, $currentobject = null)
639{
640 global $conf, $mc, $hookmanager, $object, $action, $db;
641
642 if (!is_object($hookmanager)) {
643 include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
644 $hookmanager = new HookManager($db);
645 }
646
647 // fix different element names (France to English)
648 switch ($element) {
649 case 'projet':
650 $element = 'project';
651 break;
652 case 'contrat':
653 $element = 'contract';
654 break; // "/contrat/class/contrat.class.php"
655 case 'order_supplier':
656 $element = 'supplier_order';
657 break; // "/fourn/class/fournisseur.commande.class.php"
658 case 'invoice_supplier':
659 $element = 'supplier_invoice';
660 break; // "/fourn/class/fournisseur.facture.class.php"
661 }
662
663 if (is_object($mc)) {
664 $out = $mc->getEntity($element, $shared, $currentobject);
665 } else {
666 $out = '';
667 $addzero = array('user', 'usergroup', 'cronjob', 'c_email_templates', 'email_template', 'default_values', 'overwrite_trans');
668 if (getDolGlobalString('HOLIDAY_ALLOW_ZERO_IN_DIC')) { // this constant break the dictionary admin without Multicompany
669 $addzero[] = 'c_holiday_types';
670 }
671 if (in_array($element, $addzero)) {
672 $out .= '0,';
673 }
674 $out .= ((int) $conf->entity);
675 }
676
677 // Manipulate entities to query on the fly
678 $parameters = array(
679 'element' => $element,
680 'shared' => $shared,
681 'object' => $object,
682 'currentobject' => $currentobject,
683 'out' => $out
684 );
685 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
686 $reshook = $hookmanager->executeHooks('hookGetEntity', $parameters, $currentobject, $action); // Note that $action and $object may have been modified by some hooks
687
688 if (is_numeric($reshook)) {
689 if ($reshook == 0 && !empty($hookmanager->resPrint)) {
690 $out .= ',' . $hookmanager->resPrint; // add
691 } elseif ($reshook == 1) {
692 $out = $hookmanager->resPrint; // replace
693 }
694 }
695
696 return $out;
697}
698
705function setEntity($currentobject)
706{
707 global $conf, $mc;
708
709 if (is_object($mc) && method_exists($mc, 'setEntity')) {
710 return $mc->setEntity($currentobject);
711 } else {
712 return ((is_object($currentobject) && $currentobject->id > 0 && ((int) $currentobject->entity) > 0) ? (int) $currentobject->entity : $conf->entity);
713 }
714}
715
722function isASecretKey($keyname)
723{
724 return preg_match('/(_pass|password|_pw|_key|securekey|serverkey|secret\d?|p12key|exportkey|_PW_[a-z]+|token)$/i', $keyname);
725}
726
727
734function num2Alpha($n)
735{
736 $r = '';
737 for ($r = ""; $n >= 0; $n = intval($n / 26) - 1) {
738 $r = chr($n % 26 + 0x41) . $r;
739 }
740 return $r;
741}
742
743
760function getBrowserInfo($user_agent)
761{
762 include_once DOL_DOCUMENT_ROOT . '/includes/mobiledetect/mobiledetectlib/Mobile_Detect.php';
763
764 $name = 'unknown';
765 $version = '';
766 $os = 'unknown';
767 $phone = '';
768
769 $user_agent = substr($user_agent, 0, 512); // Avoid to process too large user agent
770
771 // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal Bad definition of Mobile_Detect function
772 $detectmobile = new Mobile_Detect(null, $user_agent);
773 $tablet = $detectmobile->isTablet();
774
775 if ($detectmobile->isMobile()) {
776 $phone = 'unknown';
777
778 // If phone/smartphone, we set phone os name.
779 if ($detectmobile->is('AndroidOS')) {
780 $os = $phone = 'android';
781 } elseif ($detectmobile->is('BlackBerryOS')) {
782 $os = $phone = 'blackberry';
783 } elseif ($detectmobile->is('iOS')) {
784 $os = 'ios';
785 $phone = 'iphone';
786 } elseif ($detectmobile->is('PalmOS')) {
787 $os = $phone = 'palm';
788 } elseif ($detectmobile->is('SymbianOS')) {
789 $os = 'symbian';
790 } elseif ($detectmobile->is('webOS')) {
791 $os = 'webos';
792 } elseif ($detectmobile->is('MaemoOS')) {
793 $os = 'maemo';
794 } elseif ($detectmobile->is('WindowsMobileOS') || $detectmobile->is('WindowsPhoneOS')) {
795 $os = 'windows';
796 }
797 }
798
799 // OS
800 if (preg_match('/linux/i', $user_agent)) {
801 $os = 'linux';
802 } elseif (preg_match('/macintosh/i', $user_agent)) {
803 $os = 'macintosh';
804 } elseif (preg_match('/windows/i', $user_agent)) {
805 $os = 'windows';
806 }
807
808 // Name
809 $reg = array();
810 if (preg_match('/firefox(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
811 $name = 'firefox';
812 $version = empty($reg[2]) ? '' : $reg[2];
813 } elseif (preg_match('/edge(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
814 $name = 'edge';
815 $version = empty($reg[2]) ? '' : $reg[2];
816 } elseif (preg_match('/chrome(\/|\s)([\d\.]+)/i', $user_agent, $reg)) {
817 $name = 'chrome';
818 $version = empty($reg[2]) ? '' : $reg[2];
819 } elseif (preg_match('/chrome/i', $user_agent, $reg)) {
820 // we can have 'chrome (Mozilla...) chrome x.y' in one string
821 $name = 'chrome';
822 } elseif (preg_match('/iceweasel/i', $user_agent)) {
823 $name = 'iceweasel';
824 } elseif (preg_match('/epiphany/i', $user_agent)) {
825 $name = 'epiphany';
826 } elseif (preg_match('/safari(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
827 $name = 'safari';
828 $version = empty($reg[2]) ? '' : $reg[2];
829 } elseif (preg_match('/opera(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
830 // Safari is often present in string for mobile but its not.
831 $name = 'opera';
832 $version = empty($reg[2]) ? '' : $reg[2];
833 } elseif (preg_match('/(MSIE\s([0-9]+\.[0-9]))|.*(Trident\/[0-9]+.[0-9];.*rv:([0-9]+\.[0-9]+))/i', $user_agent, $reg)) {
834 $name = 'ie';
835 $version = end($reg);
836 } elseif (preg_match('/(Windows NT\s([0-9]+\.[0-9])).*(Trident\/[0-9]+.[0-9];.*rv:([0-9]+\.[0-9]+))/i', $user_agent, $reg)) {
837 // MS products at end
838 $name = 'ie';
839 $version = end($reg);
840 } elseif (preg_match('/l[iy]n(x|ks)(\‍(|\/|\s)*([\d\.]+)/i', $user_agent, $reg)) {
841 // MS products at end
842 $name = 'textbrowser';
843 $version = empty($reg[3]) ? '' : $reg[3];
844 } elseif (preg_match('/w3m\/([\d\.]+)/i', $user_agent, $reg)) {
845 // MS products at end
846 $name = 'textbrowser';
847 $version = empty($reg[1]) ? '' : $reg[1];
848 }
849
850 if ($tablet) {
851 $layout = 'tablet';
852 } elseif ($phone) {
853 $layout = 'phone';
854 } else {
855 $layout = 'classic';
856 }
857
858 return array(
859 'browsername' => $name,
860 'browserversion' => $version,
861 'browseros' => $os,
862 'browserua' => $user_agent,
863 'layout' => $layout, // tablet, phone, classic
864 'phone' => $phone, // deprecated
865 'tablet' => $tablet // deprecated
866 );
867}
868
874function dol_shutdown()
875{
876 global $db;
877 $disconnectdone = false;
878 $depth = 0;
879 if (is_object($db) && !empty($db->connected)) {
880 $depth = $db->transaction_opened;
881 $disconnectdone = $db->close();
882 }
883 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));
884}
885
895function GETPOSTISSET($paramname)
896{
897 $isset = false;
898
899 $relativepathstring = $_SERVER["PHP_SELF"];
900 // Clean $relativepathstring
901 if (constant('DOL_URL_ROOT')) {
902 $relativepathstring = preg_replace('/^' . preg_quote(constant('DOL_URL_ROOT'), '/') . '/', '', $relativepathstring);
903 }
904 $relativepathstring = ltrim($relativepathstring, '/');
905 $relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
906
907 // Code for search criteria persistence.
908 // Retrieve values if restore_lastsearch_values
909 if (!empty($_GET['restore_lastsearch_values'])) { // Use $_GET here and not GETPOST
910 if (!empty($_SESSION['lastsearch_values_' . $relativepathstring])) { // If there is saved values
911 $tmp = json_decode($_SESSION['lastsearch_values_' . $relativepathstring], true);
912 if (is_array($tmp)) {
913 foreach ($tmp as $key => $val) {
914 if ($key == $paramname) { // We are on the requested parameter
915 $isset = true;
916 break;
917 }
918 }
919 }
920 }
921 // If there is saved contextpage, limit, page or mode
922 if ($paramname == 'contextpage' && !empty($_SESSION['lastsearch_contextpage_' . $relativepathstring])) {
923 $isset = true;
924 } elseif ($paramname == 'limit' && !empty($_SESSION['lastsearch_limit_' . $relativepathstring])) {
925 $isset = true;
926 } elseif ($paramname == 'page' && !empty($_SESSION['lastsearch_page_' . $relativepathstring])) {
927 $isset = true;
928 } elseif ($paramname == 'mode' && !empty($_SESSION['lastsearch_mode_' . $relativepathstring])) {
929 $isset = true;
930 }
931 } else {
932 $isset = (isset($_POST[$paramname]) || isset($_GET[$paramname])); // We must keep $_POST and $_GET here
933 }
934
935 return $isset;
936}
937
946function GETPOSTISARRAY($paramname, $method = 0)
947{
948 // for $method test need return the same $val as GETPOST
949 if (empty($method)) {
950 $val = isset($_GET[$paramname]) ? $_GET[$paramname] : (isset($_POST[$paramname]) ? $_POST[$paramname] : '');
951 } elseif ($method == 1) {
952 $val = isset($_GET[$paramname]) ? $_GET[$paramname] : '';
953 } elseif ($method == 2) {
954 $val = isset($_POST[$paramname]) ? $_POST[$paramname] : '';
955 } elseif ($method == 3) {
956 $val = isset($_POST[$paramname]) ? $_POST[$paramname] : (isset($_GET[$paramname]) ? $_GET[$paramname] : '');
957 } else {
958 $val = 'BadFirstParameterForGETPOST';
959 }
960
961 return is_array($val);
962}
963
964
975function GETPOSTINT($paramname, $method = 0, $nodefault = 0)
976{
977 return (int) GETPOST($paramname, 'int', $method, null, null, 0, $nodefault);
978}
979
993function GETPOSTFLOAT($paramname, $rounding = '', $option = 2)
994{
995 // 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.)
996 return (float) price2num(GETPOST($paramname), $rounding, $option);
997}
998
1014function GETPOSTDATE($prefix, $hourTime = '', $gm = 'auto', $saverestore = '')
1015{
1016 $m = array();
1017 if ($hourTime === 'getpost' || $hourTime === 'getpostend') {
1018 $hour = (GETPOSTISSET($prefix . 'hour') && GETPOSTINT($prefix . 'hour') >= 0) ? GETPOSTINT($prefix . 'hour') : ($hourTime === 'getpostend' ? 23 : 0);
1019 $minute = (GETPOSTISSET($prefix . 'min') && GETPOSTINT($prefix . 'min') >= 0) ? GETPOSTINT($prefix . 'min') : ($hourTime === 'getpostend' ? 59 : 0);
1020 $second = (GETPOSTISSET($prefix . 'sec') && GETPOSTINT($prefix . 'sec') >= 0) ? GETPOSTINT($prefix . 'sec') : ($hourTime === 'getpostend' ? 59 : 0);
1021 } elseif (preg_match('/^(\d\d):(\d\d):(\d\d)$/', $hourTime, $m)) {
1022 $hour = intval($m[1]);
1023 $minute = intval($m[2]);
1024 $second = intval($m[3]);
1025 } elseif ($hourTime === 'end') {
1026 $hour = 23;
1027 $minute = 59;
1028 $second = 59;
1029 } else {
1030 $hour = $minute = $second = 0;
1031 }
1032
1033 if (
1034 $saverestore
1035 && !GETPOSTISSET($prefix . 'day')
1036 && !GETPOSTISSET($prefix . 'month')
1037 && !GETPOSTISSET($prefix . 'year')
1038 && isset($_SESSION['DOLDATE_' . $saverestore . '_day'])
1039 && isset($_SESSION['DOLDATE_' . $saverestore . '_month'])
1040 && isset($_SESSION['DOLDATE_' . $saverestore . '_year'])
1041 ) {
1042 $day = $_SESSION['DOLDATE_' . $saverestore . '_day'];
1043 $month = $_SESSION['DOLDATE_' . $saverestore . '_month'];
1044 $year = $_SESSION['DOLDATE_' . $saverestore . '_year'];
1045 } else {
1046 $month = GETPOSTINT($prefix . 'month');
1047 $day = GETPOSTINT($prefix . 'day');
1048 $year = GETPOSTINT($prefix . 'year');
1049 }
1050
1051 // normalize out of range values
1052 $hour = (int) min($hour, 23);
1053 $minute = (int) min($minute, 59);
1054 $second = (int) min($second, 59);
1055
1056 if ($saverestore) {
1057 $_SESSION['DOLDATE_' . $saverestore . '_day'] = $day;
1058 $_SESSION['DOLDATE_' . $saverestore . '_month'] = $month;
1059 $_SESSION['DOLDATE_' . $saverestore . '_year'] = $year;
1060 }
1061
1062 //print "$hour, $minute, $second, $month, $day, $year, $gm<br>";
1063 return dol_mktime($hour, $minute, $second, $month, $day, $year, $gm);
1064}
1065
1107function GETPOST($paramname, $check = 'alphanohtml', $method = 0, $filter = null, $options = null, $noreplace = 0, $nodefault = 0)
1108{
1109 global $langs, $mysoc, $user, $conf;
1110
1111 if (empty($paramname)) { // Explicit test for null for phan.
1112 return 'BadFirstParameterForGETPOST';
1113 }
1114 if (empty($check)) {
1115 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);
1116 // Enable this line to know who call the GETPOST with '' $check parameter.
1117 //var_dump(getCallerInfoString());
1118 }
1119 if (in_array($paramname, array('sortfield', 'sortorder'))) { // Force the $check to a more appropriated value
1120 $check = 'aZ09comma';
1121 }
1122
1123 if (empty($method)) {
1124 $out = isset($_GET[$paramname]) ? $_GET[$paramname] : (isset($_POST[$paramname]) ? $_POST[$paramname] : '');
1125 } elseif ($method == 1) {
1126 $out = isset($_GET[$paramname]) ? $_GET[$paramname] : '';
1127 } elseif ($method == 2) {
1128 $out = isset($_POST[$paramname]) ? $_POST[$paramname] : '';
1129 } elseif ($method == 3) {
1130 $out = isset($_POST[$paramname]) ? $_POST[$paramname] : (isset($_GET[$paramname]) ? $_GET[$paramname] : '');
1131 } else {
1132 return 'BadThirdParameterForGETPOST';
1133 }
1134
1135 $relativepathstring = ''; // For static analysis - looks possibly undefined if not set.
1136
1137 if (empty($method) || $method == 3 || $method == 4) {
1138 $relativepathstring = (empty($_SERVER["PHP_SELF"]) ? '' : $_SERVER["PHP_SELF"]);
1139 // Clean $relativepathstring
1140 if (constant('DOL_URL_ROOT')) {
1141 $relativepathstring = preg_replace('/^' . preg_quote(constant('DOL_URL_ROOT'), '/') . '/', '', $relativepathstring);
1142 }
1143 $relativepathstring = ltrim($relativepathstring, '/');
1144 $relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
1145
1146 // Code for search criteria persistence.
1147 // Retrieve saved values if restore_lastsearch_values is set
1148 if (!empty($_GET['restore_lastsearch_values'])) { // Use $_GET here and not GETPOST
1149 if (!empty($_SESSION['lastsearch_values_' . $relativepathstring])) { // If there is saved values
1150 $tmp = json_decode($_SESSION['lastsearch_values_' . $relativepathstring], true);
1151 if (is_array($tmp)) {
1152 foreach ($tmp as $key => $val) {
1153 if ($key == $paramname) { // We are on the requested parameter
1154 $out = $val;
1155 break;
1156 }
1157 }
1158 }
1159 }
1160 // If there is saved contextpage, page or limit
1161 if ($paramname == 'contextpage' && !empty($_SESSION['lastsearch_contextpage_' . $relativepathstring])) {
1162 $out = $_SESSION['lastsearch_contextpage_' . $relativepathstring];
1163 } elseif ($paramname == 'limit' && !empty($_SESSION['lastsearch_limit_' . $relativepathstring])) {
1164 $out = $_SESSION['lastsearch_limit_' . $relativepathstring];
1165 } elseif ($paramname == 'page' && !empty($_SESSION['lastsearch_page_' . $relativepathstring])) {
1166 $out = $_SESSION['lastsearch_page_' . $relativepathstring];
1167 } elseif ($paramname == 'mode' && !empty($_SESSION['lastsearch_mode_' . $relativepathstring])) {
1168 $out = $_SESSION['lastsearch_mode_' . $relativepathstring];
1169 }
1170 } elseif (!isset($_GET['sortfield'])) {
1171 // Else, retrieve default values if we are not doing a sort
1172 // 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
1173 if (!empty($_GET['action']) && $_GET['action'] == 'create' && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) {
1174 // Search default value from $object->field
1175 global $object;
1176 '@phan-var-force CommonObject $object'; // Suppose it's a CommonObject for analysis, but other objects have the $fields field as well
1177 if (is_object($object) && isset($object->fields[$paramname]['default'])) {
1178 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
1179 $out = $object->fields[$paramname]['default'];
1180 }
1181 }
1182 if (getDolGlobalString('MAIN_ENABLE_DEFAULT_VALUES')) {
1183 if (!empty($_GET['action']) && (preg_match('/^create/', $_GET['action']) || preg_match('/^presend/', $_GET['action'])) && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) {
1184 // Now search in setup to overwrite default values
1185 if (!empty($user->default_values)) { // $user->default_values defined from menu 'Setup - Default values'
1186 if (isset($user->default_values[$relativepathstring]['createform'])) {
1187 foreach ($user->default_values[$relativepathstring]['createform'] as $defkey => $defval) {
1188 $qualified = 0;
1189 if ($defkey != '_noquery_') {
1190 $tmpqueryarraytohave = explode('&', $defkey);
1191 $tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
1192 $foundintru = 0;
1193 foreach ($tmpqueryarraytohave as $tmpquerytohave) {
1194 if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) {
1195 $foundintru = 1;
1196 }
1197 }
1198 if (!$foundintru) {
1199 $qualified = 1;
1200 }
1201 } else {
1202 $qualified = 1;
1203 }
1204
1205 if ($qualified) {
1206 if (isset($user->default_values[$relativepathstring]['createform'][$defkey][$paramname])) {
1207 $out = $user->default_values[$relativepathstring]['createform'][$defkey][$paramname];
1208 break;
1209 }
1210 }
1211 }
1212 }
1213 }
1214 } elseif (!empty($paramname) && !isset($_GET[$paramname]) && !isset($_POST[$paramname]) && empty($nodefault)) {
1215 // Management of default search_filters and sort order
1216 if (!empty($user->default_values)) {
1217 // $user->default_values defined from menu 'Setup - Default values'
1218 //var_dump($user->default_values[$relativepathstring]);
1219 if ($paramname == 'sortfield' || $paramname == 'sortorder') {
1220 // Sorted on which fields ? ASC or DESC ?
1221 if (isset($user->default_values[$relativepathstring]['sortorder'])) {
1222 // Even if paramname is sortfield, data are stored into ['sortorder...']
1223 foreach ($user->default_values[$relativepathstring]['sortorder'] as $defkey => $defval) {
1224 $qualified = 0;
1225 if ($defkey != '_noquery_') {
1226 $tmpqueryarraytohave = explode('&', $defkey);
1227 $tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
1228 $foundintru = 0;
1229 foreach ($tmpqueryarraytohave as $tmpquerytohave) {
1230 if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) {
1231 $foundintru = 1;
1232 }
1233 }
1234 if (!$foundintru) {
1235 $qualified = 1;
1236 }
1237 } else {
1238 $qualified = 1;
1239 }
1240
1241 if ($qualified) {
1242 $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
1243 foreach ($user->default_values[$relativepathstring]['sortorder'][$defkey] as $key => $val) {
1244 if ($out) {
1245 $out .= ', ';
1246 }
1247 if ($paramname == 'sortfield') {
1248 $out .= dol_string_nospecial($key, '', $forbidden_chars_to_replace);
1249 }
1250 if ($paramname == 'sortorder') {
1251 $out .= dol_string_nospecial($val, '', $forbidden_chars_to_replace);
1252 }
1253 }
1254 //break; // No break for sortfield and sortorder so we can cumulate fields (is it really useful ?)
1255 }
1256 }
1257 }
1258 } elseif (isset($user->default_values[$relativepathstring]['filters'])) {
1259 foreach ($user->default_values[$relativepathstring]['filters'] as $defkey => $defval) { // $defkey is a querystring like 'a=b&c=d', $defval is key of user
1260 if (!empty($_GET['disabledefaultvalues'])) { // If set of default values has been disabled by a request parameter
1261 continue;
1262 }
1263 $qualified = 0;
1264 if ($defkey != '_noquery_') {
1265 $tmpqueryarraytohave = explode('&', $defkey);
1266 $tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
1267 $foundintru = 0;
1268 foreach ($tmpqueryarraytohave as $tmpquerytohave) {
1269 if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) {
1270 $foundintru = 1;
1271 }
1272 }
1273 if (!$foundintru) {
1274 $qualified = 1;
1275 }
1276 } else {
1277 $qualified = 1;
1278 }
1279
1280 if ($qualified && isset($user->default_values[$relativepathstring]['filters'][$defkey][$paramname])) {
1281 // We must keep $_POST and $_GET here
1282 if (isset($_POST['search_all']) || isset($_GET['search_all'])) {
1283 // We made a search from quick search menu, do we still use default filter ?
1284 if (!getDolGlobalString('MAIN_DISABLE_DEFAULT_FILTER_FOR_QUICK_SEARCH')) {
1285 $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
1286 $out = dol_string_nospecial($user->default_values[$relativepathstring]['filters'][$defkey][$paramname], '', $forbidden_chars_to_replace);
1287 }
1288 } else {
1289 $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
1290 $out = dol_string_nospecial($user->default_values[$relativepathstring]['filters'][$defkey][$paramname], '', $forbidden_chars_to_replace);
1291 }
1292 break;
1293 }
1294 }
1295 }
1296 }
1297 }
1298 }
1299 }
1300 }
1301
1302 // 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)
1303 // Example of variables: __DAY__, __MONTH__, __YEAR__, __MYCOMPANY_COUNTRY_ID__, __USER_ID__, ...
1304 // 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.
1305 '@phan-var-force string $paramname';
1306 if (!is_array($out) && empty($_POST[$paramname]) && empty($noreplace)) {
1307 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
1308 $substitutionarray = getCommonSubstitutionArray($langs, 0, null, $user, array('mycompany', 'date', 'system', 'user'));
1309 complete_substitutions_array($substitutionarray, $langs, $user);
1310
1311 $out = make_substitutions($out, $substitutionarray, $langs);
1312 }
1313 }
1314
1315 // Check type of variable and make sanitization according to this
1316 if (preg_match('/^array/', $check)) { // If 'array' or 'array:restricthtml' or 'array:aZ09' or 'array:int'
1317 $tmpcheck = 'alphanohtml';
1318 if ($out === null || $out === '') {
1319 $out = array();
1320 } elseif (!is_array($out)) {
1321 $out = explode(',', $out);
1322 } else {
1323 $tmparray = explode(':', $check);
1324 if (!empty($tmparray[1])) {
1325 $tmpcheck = $tmparray[1];
1326 }
1327 }
1328 foreach ($out as $outkey => $outval) {
1329 $out[$outkey] = sanitizeVal($outval, $tmpcheck, $filter, $options);
1330 }
1331 } else {
1332 // If field name is 'search_xxx' then we force the add of space after each < and > (when following char is numeric) because it means
1333 // 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
1334 if (strpos($paramname, 'search_') === 0) {
1335 $out = preg_replace('/([<>])([-+]?\d)/', '\1 \2', $out);
1336 }
1337
1338 // @phan-suppress-next-line UnknownSanitizeType
1339 $out = sanitizeVal($out, $check, $filter, $options);
1340 }
1341
1342 // Sanitizing for special parameters.
1343 // 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.
1344 // @TODO Merge backtopage with backtourl
1345 // @TODO Rename backtolist into backtopagelist
1346 // @TODO Merge urlfrom into backtourl
1347 if (preg_match('/^backto/i', $paramname) || preg_match('/^urlfrom/i', $paramname)) {
1348 $out = str_replace('\\', '/', $out); // Can be before the loop because only 1 char is replaced. No risk to get it after other replacements.
1349 $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.
1350 do {
1351 $oldstringtoclean = $out;
1352 $out = str_ireplace(array('javascript', 'vbscript', '&colon', '&#'), '', $out);
1353 $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'
1354 $out = preg_replace(array('/^[a-z]*\/\s*\/+/i'), '', $out); // We remove schema*// to remove external URL
1355 } while ($oldstringtoclean != $out);
1356 }
1357
1358 // Code for search criteria persistence.
1359 // Save data into session if key start with 'search_'
1360 if (empty($method) || $method == 3 || $method == 4) {
1361 if (preg_match('/^search_/', $paramname) || in_array($paramname, array('sortorder', 'sortfield'))) {
1362 //var_dump($paramname.' - '.$out.' '.$user->default_values[$relativepathstring]['filters'][$paramname]);
1363
1364 // We save search key only if $out not empty that means:
1365 // - posted value not empty, or
1366 // - 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).
1367
1368 if ($out != '' && isset($user)) { // $out = '0' or 'abc', it is a search criteria to keep
1369 $user->lastsearch_values_tmp[$relativepathstring][$paramname] = $out;
1370 }
1371 }
1372 }
1373
1374 if ($paramname == 'hashp' && $out == 'shared') {
1375 $out = ''; // We refuse to have hashp=shared as a parameter
1376 }
1377
1378 return $out;
1379}
1380
1390function sanitizeVal($out = '', $check = 'alphanohtml', $filter = null, $options = null)
1391{
1392 // TODO : use class "Validate" to perform tests (and add missing tests) if needed for factorize
1393 // Check is done after replacement
1394 if ($out === null) {
1395 $out = '';
1396 }
1397 switch ($check) {
1398 case 'none':
1399 case 'password':
1400 break;
1401 case 'int': // Check param is a numeric value (integer but also float or hexadecimal)
1402 if (!is_numeric($out)) {
1403 $out = '';
1404 }
1405 break;
1406 case 'intcomma':
1407 if (is_array($out)) {
1408 $out = implode(',', $out);
1409 }
1410 if (preg_match('/[^0-9,-]+/i', $out)) {
1411 $out = '';
1412 }
1413 break;
1414 case 'san_alpha':
1415 dol_syslog("Use of parameter value 'san_alpha' in GETPOST is deprecated. Use 'alphanohtml', 'aZ09comma', ...", LOG_WARNING);
1416 $out = filter_var($out, FILTER_SANITIZE_STRING);
1417 break;
1418 case 'email':
1419 $out = filter_var($out, FILTER_SANITIZE_EMAIL);
1420 break;
1421 case 'url':
1422 //$out = filter_var($out, FILTER_SANITIZE_URL); // Not reliable, replaced with FILTER_VALIDATE_URL
1423 $out = preg_replace('/[^:\/\[\]a-z0-9@\$\'\*\~\.\-_,;\?\!=%&+#]+/i', '', $out);
1424 // TODO Allow ( ) but only into password of https://login:password@domain...
1425 break;
1426 case 'aZ':
1427 if (!is_array($out)) {
1428 $out = trim($out);
1429 if (preg_match('/[^a-z]+/i', $out)) {
1430 $out = '';
1431 }
1432 }
1433 break;
1434 case 'aZ09':
1435 if (!is_array($out)) {
1436 $out = trim($out);
1437 if (preg_match('/[^a-z0-9_\-\.]+/i', $out)) {
1438 $out = '';
1439 }
1440 }
1441 break;
1442 case 'aZ09arobase': // great to sanitize $objecttype parameter
1443 if (!is_array($out)) {
1444 $out = trim($out);
1445 if (preg_match('/[^a-z0-9_\-\.@]+/i', $out)) {
1446 $out = '';
1447 }
1448 }
1449 break;
1450 case 'aZ09comma': // great to sanitize $sortfield or $sortorder params that can be 't.abc,t.def_gh'
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 'alpha': // No html and no ../ and "
1459 case 'alphanohtml': // Recommended for most scalar parameters and search parameters. Not valid for json string.
1460 if (!is_array($out)) {
1461 $out = trim($out);
1462 do {
1463 $oldstringtoclean = $out;
1464 // Remove html tags
1465 $out = dol_string_nohtmltag($out, 0);
1466 // 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).
1467 $out = preg_replace('/\\\‍([0-9xu])/', '/\1', $out);
1468 // Remove also other dangerous string sequences
1469 // '../' or '..\' is dangerous because it allows dir transversals
1470 // '&#38', '&#0000038', '&#x26'... is a the char '&' alone but there is no reason to accept such way to encode input char
1471 // '"' = '&#34' = '&#0000034' = '&#x22' is dangerous because param in url can close the href= or src= and add javascript functions.
1472 // '&#47', '&#0000047', '&#x2F' is the char '/' but there is no reason to accept such way to encode this input char
1473 // '&#92' = '&#0000092' = '&#x5C' is the char '\' but there is no reason to accept such way to encode this input char
1474 $out = str_ireplace(array('../', '..\\', '&#38', '&#0000038', '&#x26', '&quot', '"', '&#34', '&#0000034', '&#x22', '&#47', '&#0000047', '&#x2F', '&#92', '&#0000092', '&#x5C'), '', $out);
1475 } while ($oldstringtoclean != $out);
1476 // keep lines feed
1477 }
1478 break;
1479 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'
1480 if (!is_array($out)) {
1481 $out = trim($out);
1482 do {
1483 $oldstringtoclean = $out;
1484 // Decode html entities
1485 $out = dol_html_entity_decode($out, ENT_COMPAT | ENT_HTML5, 'UTF-8');
1486 // 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).
1487 $out = preg_replace('/\\\‍([0-9xu])/', '/\1', $out);
1488 // Remove also other dangerous string sequences
1489 // '../' or '..\' is dangerous because it allows dir transversals
1490 // '&#38', '&#0000038', '&#x26'... is a the char '&' alone but there is no reason to accept such way to encode input char
1491 // '"' = '&#34' = '&#0000034' = '&#x22' is dangerous because param in url can close the href= or src= and add javascript functions.
1492 // '&#47', '&#0000047', '&#x2F' is the char '/' but there is no reason to accept such way to encode this input char
1493 // '&#92' = '&#0000092' = '&#x5C' is the char '\' but there is no reason to accept such way to encode this input char
1494 $out = str_ireplace(array('../', '..\\', '&#38', '&#0000038', '&#x26', '&quot', '"', '&#34', '&#0000034', '&#x22', '&#47', '&#0000047', '&#x2F', '&#92', '&#0000092', '&#x5C'), '', $out);
1495 } while ($oldstringtoclean != $out);
1496 }
1497 break;
1498 case 'nohtml': // No html. Valid for JSON strings.
1499 $out = dol_string_nohtmltag($out, 0);
1500 break;
1501 case 'restricthtmlnolink':
1502 case 'restricthtml': // Recommended for most html textarea
1503 case 'restricthtmlallowclass':
1504 case 'restricthtmlallowiframe':
1505 case 'restricthtmlallowlinkscript': // Allow link and script tag for head section.
1506 case 'restricthtmlallowunvalid':
1507 $out = dol_htmlwithnojs($out, 1, $check);
1508 break;
1509 case 'custom':
1510 if (!empty($out)) {
1511 if (empty($filter)) {
1512 return 'BadParameterForGETPOST - Param 3 of sanitizeVal()';
1513 }
1514 if (is_null($options)) {
1515 $options = 0;
1516 }
1517 $out = filter_var($out, $filter, $options);
1518 }
1519 break;
1520 default:
1521 dol_syslog("Error, you call sanitizeVal() with a bad value for the check type. Data will be sanitized with alphanohtml.", LOG_ERR);
1522 $out = GETPOST($out, 'alphanohtml');
1523 break;
1524 }
1525
1526 return $out;
1527}
1528
1537function dolSetCookie(string $cookiename, string $cookievalue, int $expire = -1)
1538{
1539 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/securitycore.lib.php';
1540
1541 global $dolibarr_main_force_https;
1542
1543 if ($expire == -1) {
1544 $expire = (time() + (86400 * 354)); // keep cookie 1 year.
1545 }
1546
1547 if (PHP_VERSION_ID < 70300) {
1548 setcookie($cookiename, empty($cookievalue) ? '' : $cookievalue, empty($cookievalue) ? 0 : $expire, '/', '', !(empty($dolibarr_main_force_https) && isHTTPS() === false), true); // add tag httponly
1549 } else {
1550 // Only available for php >= 7.3
1551 $cookieparams = array(
1552 'expires' => empty($cookievalue) ? 0 : $expire,
1553 'path' => '/',
1554 //'domain' => '.mywebsite.com', // the dot at the beginning allows compatibility with subdomains
1555 'secure' => !(empty($dolibarr_main_force_https) && isHTTPS() === false),
1556 'httponly' => true,
1557 'samesite' => 'Lax' // None || Lax || Strict
1558 );
1559 setcookie($cookiename, empty($cookievalue) ? '' : $cookievalue, $cookieparams);
1560 }
1561 if (empty($cookievalue)) {
1562 unset($_COOKIE[$cookiename]);
1563 }
1564}
1565
1566if (!function_exists('dol_getprefix')) {
1577 function dol_getprefix($mode = '')
1578 {
1579 // If prefix is for email (we need to have $conf already loaded for this case)
1580 if ($mode == 'email') {
1581 global $conf;
1582
1583 if (getDolGlobalString('MAIL_PREFIX_FOR_EMAIL_ID')) { // If MAIL_PREFIX_FOR_EMAIL_ID is set
1584 if (getDolGlobalString('MAIL_PREFIX_FOR_EMAIL_ID') != 'SERVER_NAME') {
1585 return getDolGlobalString('MAIL_PREFIX_FOR_EMAIL_ID');
1586 } elseif (isset($_SERVER["SERVER_NAME"])) { // If MAIL_PREFIX_FOR_EMAIL_ID is set to 'SERVER_NAME'
1587 return $_SERVER["SERVER_NAME"];
1588 }
1589 }
1590
1591 // The recommended value if MAIL_PREFIX_FOR_EMAIL_ID is not defined (may be not defined for old versions)
1592 if (!empty($conf->file->instance_unique_id)) {
1593 return sha1('dolibarr' . $conf->file->instance_unique_id);
1594 }
1595
1596 // For backward compatibility when instance_unique_id is not set
1597 return sha1(DOL_DOCUMENT_ROOT . DOL_URL_ROOT);
1598 }
1599
1600 // If prefix is for session (no need to have $conf loaded)
1601 global $dolibarr_main_instance_unique_id, $dolibarr_main_cookie_cryptkey; // This is loaded by filefunc.inc.php
1602 $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
1603
1604 // The recommended value (may be not defined for old versions)
1605 if (!empty($tmp_instance_unique_id)) {
1606 return sha1('dolibarr' . $tmp_instance_unique_id);
1607 }
1608
1609 // For backward compatibility when instance_unique_id is not set
1610 if (isset($_SERVER["SERVER_NAME"]) && isset($_SERVER["DOCUMENT_ROOT"])) {
1611 return sha1($_SERVER["SERVER_NAME"] . $_SERVER["DOCUMENT_ROOT"] . DOL_DOCUMENT_ROOT . DOL_URL_ROOT);
1612 } else {
1613 return sha1(DOL_DOCUMENT_ROOT . DOL_URL_ROOT);
1614 }
1615 }
1616}
1617
1628function dol_include_once($relpath, $classname = '')
1629{
1630 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']
1631
1632 if (strpos($relpath, '..') !== false) {
1633 // Found a not valid path
1634 dol_syslog('functions::dol_include_once Tried to load a file with a path including a forbidden sequence ".." : ' . $relpath, LOG_WARNING);
1635 return false;
1636 }
1637 if (!preg_match('/\.php$/', $relpath)) {
1638 // Found a not valid path
1639 dol_syslog('functions::dol_include_once Tried to load a file that is not a PHP file : ' . $relpath, LOG_WARNING);
1640 return false;
1641 }
1642
1643 $fullpath = dol_buildpath($relpath);
1644
1645 if (!file_exists($fullpath)) {
1646 dol_syslog('functions::dol_include_once Tried to load unexisting file: ' . $relpath, LOG_WARNING);
1647 return false;
1648 }
1649 if (!empty($classname) && !class_exists($classname)) {
1650 return include $fullpath;
1651 } else {
1652 return include_once $fullpath;
1653 }
1654}
1655
1656
1670function dol_buildpath($path, $type = 0, $returnemptyifnotfound = 0)
1671{
1672 global $conf;
1673
1674 $path = preg_replace('/^\//', '', $path);
1675
1676 if (empty($type)) { // For a filesystem path
1677 $res = DOL_DOCUMENT_ROOT . '/' . $path; // Standard default path
1678 if (is_array($conf->file->dol_document_root)) {
1679 foreach ($conf->file->dol_document_root as $key => $dirroot) { // ex: array("main"=>"/home/main/htdocs", "alt0"=>"/home/dirmod/htdocs", ...)
1680 if ($key == 'main') {
1681 continue;
1682 }
1683 // if (@file_exists($dirroot.'/'.$path)) {
1684 if (@file_exists($dirroot . '/' . $path)) { // avoid [php:warn]
1685 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/...'
1686 continue;
1687 }
1688 $res = $dirroot . '/' . $path;
1689 return $res;
1690 }
1691 }
1692 }
1693 if ($returnemptyifnotfound) {
1694 // Not found into alternate dir
1695 if ($returnemptyifnotfound == 1 || !file_exists($res)) {
1696 return '';
1697 }
1698 }
1699 } else {
1700 // For an url path
1701 // We try to get local path of file on filesystem from url
1702 // Note that trying to know if a file on disk exist by forging path on disk from url
1703 // works only for some web server and some setup. This is bugged when
1704 // using proxy, rewriting, virtual path, etc...
1705 $res = '';
1706 if ($type == 1) {
1707 $res = DOL_URL_ROOT . '/' . $path; // Standard value
1708 }
1709 if ($type == 2) {
1710 $res = DOL_MAIN_URL_ROOT . '/' . $path; // Standard value
1711 }
1712 if ($type == 3) {
1713 $res = DOL_URL_ROOT . '/' . $path;
1714 }
1715
1716 foreach ($conf->file->dol_document_root as $key => $dirroot) { // ex: array(["main"]=>"/home/main/htdocs", ["alt0"]=>"/home/dirmod/htdocs", ...)
1717 if ($key == 'main') {
1718 if ($type == 3) {
1719 /*global $dolibarr_main_url_root;*/
1720
1721 // Define $urlwithroot
1722 $urlwithouturlroot = preg_replace('/' . preg_quote(DOL_URL_ROOT, '/') . '$/i', '', trim($conf->file->dol_main_url_root));
1723 $urlwithroot = $urlwithouturlroot . DOL_URL_ROOT; // This is to use external domain name found into config file
1724 //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current
1725
1726 $res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : $urlwithroot) . '/' . $path; // Test on start with http is for old conf syntax
1727 }
1728 continue;
1729 }
1730 $regs = array();
1731 preg_match('/^([^\?]+(\.css\.php|\.css|\.js\.php|\.js|\.png|\.jpg|\.php)?)/i', $path, $regs); // Take part before '?'
1732 if (!empty($regs[1])) {
1733 //print $key.'-'.$dirroot.'/'.$path.'-'.$conf->file->dol_url_root[$type].'<br>'."\n";
1734 //if (file_exists($dirroot.'/'.$regs[1])) {
1735 if (@file_exists($dirroot . '/' . $regs[1])) { // avoid [php:warn]
1736 if ($type == 1) {
1737 $res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : DOL_URL_ROOT) . $conf->file->dol_url_root[$key] . '/' . $path;
1738 } elseif ($type == 2) {
1739 $res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : DOL_MAIN_URL_ROOT) . $conf->file->dol_url_root[$key] . '/' . $path;
1740 } elseif ($type == 3) {
1741 /*global $dolibarr_main_url_root;*/
1742
1743 // Define $urlwithroot
1744 $urlwithouturlroot = preg_replace('/' . preg_quote(DOL_URL_ROOT, '/') . '$/i', '', trim($conf->file->dol_main_url_root));
1745 $urlwithroot = $urlwithouturlroot . DOL_URL_ROOT; // This is to use external domain name found into config file
1746 //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current
1747
1748 $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
1749 }
1750 break;
1751 }
1752 }
1753 }
1754 }
1755
1756 return $res;
1757}
1758
1768function dolBuildUrl($url, $params = [], $addtoken = false, $anchor = '')
1769{
1770 global $db, $hookmanager;
1771
1772 if (!is_object($hookmanager)) {
1773 include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
1774 $hookmanager = new HookManager($db);
1775 }
1776 if ((!isset($params['mainmenu']) || empty($params['mainmenu'])) && GETPOSTISSET('mainmenu')) {
1777 $params = array_merge($params, ['mainmenu' => (GETPOST('mainmenu', 'restricthtml'))]);
1778 }
1779 if ((!isset($params['leftmenu'])/* || empty($params['leftmenu']) */) && GETPOSTISSET('leftmenu')) { // do not fill leftmenu if we have leftmenu=
1780 $params = array_merge($params, ['leftmenu' => (GETPOST('leftmenu', 'restricthtml'))]);
1781 }
1782 $parameters = [
1783 'path' => &$url,
1784 'params' => &$params,
1785 'addtoken' => &$addtoken,
1786 ];
1787 $hookmanager->executeHooks('buildurl', $parameters);
1788 if ($addtoken) {
1789 $params = array_merge($params, ['token' => newToken()]);
1790 }
1791 if ($params) {
1792 $url .= '?' . http_build_query($params);
1793 }
1794 if ($anchor) {
1795 $url .= '#' . preg_replace('/[^a-z]/i', '', $anchor);
1796 }
1797
1798 return $url;
1799}
1800
1811function dol_get_object_properties($obj, $properties = [])
1812{
1813 // Get real properties using get_object_vars() if $properties is empty
1814 if (empty($properties)) {
1815 return get_object_vars($obj);
1816 }
1817
1818 $existingProperties = [];
1819 $realProperties = get_object_vars($obj);
1820
1821 // Get the real or magic property values
1822 foreach ($properties as $property) {
1823 if (array_key_exists($property, $realProperties)) {
1824 // Real property, add the value
1825 $existingProperties[$property] = $obj->{$property};
1826 } elseif (property_exists($obj, $property)) {
1827 // Magic property
1828 $existingProperties[$property] = $obj->{$property};
1829 }
1830 }
1831
1832 return $existingProperties;
1833}
1834
1835
1853function dol_clone($srcobject, $native = 2)
1854{
1855 if ($native == 0) {
1856 // deprecated method, use the method with native = 2 instead
1857 dol_syslog("Warning, call to dol_clone() with the deprecated parameter native=0, use 2 instead", LOG_WARNING);
1858
1859 $tmpsavdb = null;
1860 if (isset($srcobject->db) && isset($srcobject->db->db) && is_object($srcobject->db->db) && get_class($srcobject->db->db) == 'PgSql\Connection') {
1861 $tmpsavdb = $srcobject->db;
1862 unset($srcobject->db); // Such property can not be serialized with pgsl (when object->db->db = 'PgSql\Connection')
1863 }
1864
1865 $myclone = unserialize(serialize($srcobject)); // serialize then unserialize is a hack to be sure to have a new object for all fields
1866
1867 if (!empty($tmpsavdb)) {
1868 $srcobject->db = $tmpsavdb;
1869 }
1870 } elseif ($native == 2) {
1871 // recommended method to have a full secured isolated cloned object
1872 $myclone = new stdClass();
1873 $tmparray = get_object_vars($srcobject); // return only public properties
1874
1875 if (is_array($tmparray)) {
1876 foreach ($tmparray as $propertykey => $propertyval) {
1877 if (is_scalar($propertyval) || is_array($propertyval)) {
1878 $myclone->$propertykey = $propertyval;
1879 }
1880 }
1881 }
1882 } else {
1883 $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)
1884 }
1885
1886 return $myclone;
1887}
1888
1889
1898function dol_clone_in_array($srcobject, $startlevel = 0)
1899{
1900 if (is_object($srcobject)) {
1901 $srcobject = get_object_vars($srcobject); // exclude private/protected properties
1902 }
1903
1904 if (is_array($srcobject)) {
1905 $result = [];
1906 foreach ($srcobject as $key => $value) {
1907 if (in_array($key, array('db', 'fields', 'error', 'errorhidden', 'errors', 'oldcopy', 'linkedObjects', 'linked_objects'))) {
1908 continue;
1909 }
1910 $result[$key] = dol_clone_in_array($value, $startlevel + 1);
1911 }
1912 return $result;
1913 }
1914
1915 return $srcobject;
1916}
1917
1918
1928function dol_size($size, $type = '')
1929{
1930 global $conf;
1931 if (empty($conf->dol_optimize_smallscreen)) {
1932 return $size;
1933 }
1934 if ($type == 'width' && $size > 250) {
1935 return 250;
1936 } else {
1937 return 10;
1938 }
1939}
1940
1941
1955function dol_sanitizeFileName($str, $newstr = '_', $unaccent = 1, $includequotes = 0, $allowdash = 0)
1956{
1957 $str = (string) $str;
1958
1959 // List of special chars for filenames in windows are defined on page https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
1960 // Char '>' '<' '|' '$' and ';' are special chars for shells.
1961 // Char '/' and '\' are file delimiters.
1962 // 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
1963 $filesystem_forbidden_chars = array('<', '>', '/', '\\', '?', '*', '|', '"', ':', '°', '$', ';', '`');
1964 if ($includequotes) {
1965 $filesystem_forbidden_chars[] = "'";
1966 }
1967 $tmp = dol_string_nospecial($unaccent ? dol_string_unaccent($str) : $str, $newstr, $filesystem_forbidden_chars);
1968 $tmp = preg_replace('/\-\-+/', '_', $tmp);
1969 if (empty($allowdash)) {
1970 $tmp = preg_replace('/\s+\-([^\s])/', ' _$1', $tmp);
1971 $tmp = preg_replace('/\s+\-$/', '', $tmp);
1972 }
1973 $tmp = str_replace('..', '', $tmp);
1974 $tmp = str_replace('~', $newstr, $tmp);
1975 $tmp = preg_replace('/\s{2,}/', ' ', $tmp);
1976
1977 return $tmp;
1978}
1979
1980
1993function dol_sanitizePathName($str, $newstr = '_', $unaccent = 0, $allowdash = 0)
1994{
1995 // List of special chars for filenames in windows are defined on page https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
1996 // Char '>' '<' '|' '$' ';' and '`' are special chars for shells.
1997 // Char '?' and '*' are for wild card chars.
1998 // Char '"' is dangerous.
1999 // Char '°' is just not expected.
2000 // 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
2001 // Chars '--' and '~' can be used for path transversal
2002 $filesystem_forbidden_chars = array('<', '>', '?', '*', '|', '"', '°', '$', ';', '`');
2003
2004 $tmp = $str;
2005 if ($unaccent) {
2006 $tmp = dol_string_unaccent($tmp);
2007 }
2008 $tmp = dol_string_nospecial($tmp, $newstr, $filesystem_forbidden_chars);
2009 $tmp = preg_replace('/\-\-+/', $newstr, $tmp);
2010 if (empty($allowdash)) {
2011 $tmp = preg_replace('/\s+\-([^\s])/', ' '.$newstr.'$1', $tmp);
2012 $tmp = preg_replace('/\s+\-$/', '', $tmp);
2013 }
2014 $tmp = str_replace('..', $newstr, $tmp);
2015 $tmp = str_replace('~', $newstr, $tmp);
2016 $tmp = preg_replace('/\s{2,}/', ' ', $tmp);
2017
2018 return $tmp;
2019}
2020
2028function dol_sanitizeUrl($stringtoclean, $type = 1)
2029{
2030 // 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)
2031 // We should use dol_string_nounprintableascii but function may not be yet loaded/available
2032 $stringtoclean = preg_replace('/[\x00-\x1F\x7F]/u', '', $stringtoclean); // /u operator makes UTF8 valid characters being ignored so are not included into the replace
2033 // We clean html comments because some hacks try to obfuscate evil strings by inserting HTML comments. Example: on<!-- -->error=alert(1)
2034 $stringtoclean = preg_replace('/<!--[^>]*-->/', '', $stringtoclean);
2035
2036 $stringtoclean = str_replace('\\', '/', $stringtoclean);
2037 if ($type == 1) {
2038 // removing : should disable links to external url like http:aaa)
2039 // removing ';' should disable "named" html entities encode into an url (we should not have this into an url)
2040 $stringtoclean = str_replace(array(':', ';', '@'), '', $stringtoclean);
2041 }
2042
2043 do {
2044 $oldstringtoclean = $stringtoclean;
2045 // removing '&colon' should disable links to external url like http:aaa)
2046 // removing '&#' should disable "numeric" html entities encode into an url (we should not have this into an url)
2047 $stringtoclean = str_ireplace(array('javascript', 'vbscript', '&colon', '&#'), '', $stringtoclean);
2048 } while ($oldstringtoclean != $stringtoclean);
2049
2050 if ($type == 1) {
2051 // removing '//' should disable links to external url like //aaa or http//)
2052 $stringtoclean = preg_replace(array('/^[a-z]*\/\/+/i'), '', $stringtoclean);
2053 }
2054
2055 return $stringtoclean;
2056}
2057
2064function dol_sanitizeEmail($stringtoclean)
2065{
2066 do {
2067 $oldstringtoclean = $stringtoclean;
2068 $stringtoclean = str_ireplace(array('"', ':', '[', ']', "\n", "\r", '\\', '\/'), '', $stringtoclean);
2069 } while ($oldstringtoclean != $stringtoclean);
2070
2071 return $stringtoclean;
2072}
2073
2082function dol_sanitizeKeyCode($str)
2083{
2084 return preg_replace('/[^\w]+/', '', $str);
2085}
2086
2087
2096function dol_string_unaccent($str)
2097{
2098 if (is_null($str)) {
2099 return '';
2100 }
2101
2102 if (utf8_check($str)) {
2103 if (extension_loaded('intl') && getDolGlobalString('MAIN_UNACCENT_USE_TRANSLITERATOR')) {
2104 $transliterator = Transliterator::createFromRules(':: Any-Latin; :: Latin-ASCII; :: NFD; :: [:Nonspacing Mark:] Remove; :: NFC;', Transliterator::FORWARD);
2105 return $transliterator->transliterate($str);
2106 }
2107 // See http://www.utf8-chartable.de/
2108 $string = rawurlencode($str);
2109 $replacements = array(
2110 '%C3%80' => 'A', '%C3%81' => 'A', '%C3%82' => 'A', '%C3%83' => 'A', '%C3%84' => 'A', '%C3%85' => 'A',
2111 '%C3%87' => 'C',
2112 '%C3%88' => 'E', '%C3%89' => 'E', '%C3%8A' => 'E', '%C3%8B' => 'E',
2113 '%C3%8C' => 'I', '%C3%8D' => 'I', '%C3%8E' => 'I', '%C3%8F' => 'I',
2114 '%C3%91' => 'N',
2115 '%C3%92' => 'O', '%C3%93' => 'O', '%C3%94' => 'O', '%C3%95' => 'O', '%C3%96' => 'O', '%C5%90' => 'O',
2116 '%C5%A0' => 'S',
2117 '%C3%99' => 'U', '%C3%9A' => 'U', '%C3%9B' => 'U', '%C3%9C' => 'U', '%C5%B0' => 'U',
2118 '%C3%9D' => 'Y', '%C5%B8' => 'y',
2119 '%C3%A0' => 'a', '%C3%A1' => 'a', '%C3%A2' => 'a', '%C3%A3' => 'a', '%C3%A4' => 'a', '%C3%A5' => 'a',
2120 '%C3%A7' => 'c',
2121 '%C3%A8' => 'e', '%C3%A9' => 'e', '%C3%AA' => 'e', '%C3%AB' => 'e',
2122 '%C3%AC' => 'i', '%C3%AD' => 'i', '%C3%AE' => 'i', '%C3%AF' => 'i',
2123 '%C3%B1' => 'n',
2124 '%C3%B2' => 'o', '%C3%B3' => 'o', '%C3%B4' => 'o', '%C3%B5' => 'o', '%C3%B6' => 'o', '%C5%91' => 'o',
2125 '%C5%A1' => 's',
2126 '%C3%B9' => 'u', '%C3%BA' => 'u', '%C3%BB' => 'u', '%C3%BC' => 'u', '%C5%B1' => 'u',
2127 '%C3%BD' => 'y', '%C3%BF' => 'y',
2128 '%CC%80' => '',
2129 '%CC%81' => '',
2130 '%CC%82' => '',
2131 '%CC%83' => '',
2132 '%CC%84' => '',
2133 '%CC%85' => '',
2134 '%CC%86' => '',
2135 '%CC%87' => '',
2136 '%CC%88' => '',
2137 '%CC%89' => '',
2138 '%CC%8A' => '',
2139 '%CC%8B' => '',
2140 '%CC%8C' => '',
2141 '%CC%8D' => '',
2142 '%CC%8E' => '',
2143 '%CC%8F' => '',
2144 '%CC%90' => '',
2145 '%CC%91' => '',
2146 '%CC%A7' => ''
2147 );
2148 $string = strtr($string, $replacements);
2149 return rawurldecode($string);
2150 } else {
2151 // See http://www.ascii-code.com/
2152 $string = strtr(
2153 $str,
2154 "\xC0\xC1\xC2\xC3\xC4\xC5\xC7
2155 \xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1
2156 \xD2\xD3\xD4\xD5\xD8\xD9\xDA\xDB\xDD
2157 \xE0\xE1\xE2\xE3\xE4\xE5\xE7\xE8\xE9\xEA\xEB
2158 \xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF8
2159 \xF9\xFA\xFB\xFC\xFD\xFF",
2160 "AAAAAAC
2161 EEEEIIIIDN
2162 OOOOOUUUY
2163 aaaaaaceeee
2164 iiiidnooooo
2165 uuuuyy"
2166 );
2167 $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"));
2168 return $string;
2169 }
2170}
2171
2185function dol_string_nospecial($str, $newstr = '_', $badcharstoreplace = '', $badcharstoremove = '', $keepspaces = 0)
2186{
2187 $forbidden_chars_to_replace = array("'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ",", ";", "=", '°', '$', ';'); // more complete than dol_sanitizeFileName
2188 if (empty($keepspaces)) {
2189 $forbidden_chars_to_replace[] = " ";
2190 }
2191 $forbidden_chars_to_remove = array();
2192 //$forbidden_chars_to_remove=array("(",")");
2193
2194 if (is_array($badcharstoreplace)) {
2195 $forbidden_chars_to_replace = $badcharstoreplace;
2196 }
2197 if (is_array($badcharstoremove)) {
2198 $forbidden_chars_to_remove = $badcharstoremove;
2199 }
2200
2201 // @phan-suppress-next-line PhanPluginSuspiciousParamOrderInternal
2202 return str_replace($forbidden_chars_to_replace, $newstr, str_replace($forbidden_chars_to_remove, "", $str));
2203}
2204
2205
2219function dol_string_nounprintableascii($str, $removetabcrlf = 1)
2220{
2221 if ($removetabcrlf) {
2222 return preg_replace('/[\x00-\x1F\x7F]/u', '', $str); // /u operator makes UTF8 valid characters being ignored so are not included into the replace
2223 } else {
2224 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
2225 }
2226}
2227
2234function dolSlugify($stringtoslugify)
2235{
2236 $slug = dol_string_unaccent($stringtoslugify);
2237
2238 // Convert special characters to their ASCII equivalents
2239 if (function_exists('iconv')) {
2240 $slug = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $slug);
2241 }
2242
2243 // Convert to lowercase
2244 $slug = strtolower($slug);
2245
2246 // Replace non-alphanumeric characters with hyphens
2247 $slug = preg_replace('/[^a-z0-9]+/', '-', $slug);
2248
2249 // Remove leading and trailing hyphens
2250 $slug = trim($slug, '-');
2251
2252 return $slug;
2253}
2254
2263function dol_escape_js($stringtoescape, $mode = 0, $noescapebackslashn = 0)
2264{
2265 if (is_null($stringtoescape)) {
2266 return '';
2267 }
2268
2269 // escape quotes and backslashes, newlines, etc.
2270 $substitjs = array("&#039;" => "\\'", "\r" => '\\r');
2271 //$substitjs['</']='<\/'; // We removed this. Should be useless.
2272 if (empty($noescapebackslashn)) {
2273 $substitjs["\n"] = '\\n';
2274 $substitjs['\\'] = '\\\\';
2275 }
2276 if (empty($mode)) {
2277 $substitjs["'"] = "\\'";
2278 $substitjs['"'] = "\\'";
2279 } elseif ($mode == 1) {
2280 $substitjs["'"] = "\\'";
2281 } elseif ($mode == 2) {
2282 $substitjs['"'] = '\\"';
2283 } elseif ($mode == 3) {
2284 $substitjs["'"] = "\\'";
2285 $substitjs['"'] = "\\\"";
2286 }
2287 return strtr((string) $stringtoescape, $substitjs);
2288}
2289
2299function dol_escape_uri($stringtoescape)
2300{
2301 return rawurlencode($stringtoescape);
2302}
2303
2310function dol_escape_json($stringtoescape)
2311{
2312 return str_replace('"', '\"', $stringtoescape);
2313}
2314
2322function dol_escape_php($stringtoescape, $stringforquotes = 2)
2323{
2324 if (is_null($stringtoescape)) {
2325 return '';
2326 }
2327
2328 if ($stringforquotes == 2) {
2329 return str_replace('"', "'", $stringtoescape);
2330 } elseif ($stringforquotes == 1) {
2331 // We remove the \ char.
2332 // If we allow the \ char, we can have $stringtoescape =
2333 // abc\';phpcodedanger; so the escapement will become
2334 // abc\\';phpcodedanger; and injecting this into
2335 // $a='...' will give $ac='abc\\';phpcodedanger;
2336 $stringtoescape = str_replace('\\', '', $stringtoescape);
2337 return str_replace("'", "\'", str_replace('"', "'", $stringtoescape));
2338 }
2339
2340 return 'Bad parameter for stringforquotes in dol_escape_php';
2341}
2342
2349function dol_escape_all($stringtoescape)
2350{
2351 return preg_replace('/[^a-z0-9_]/i', '', $stringtoescape);
2352}
2353
2360function dol_escape_xml($stringtoescape)
2361{
2362 return $stringtoescape;
2363}
2364
2374function dolPrintLabel($s, $escapeonlyhtmltags = 0)
2375{
2376 return dol_escape_htmltag(dol_string_nohtmltag($s, 1, 'UTF-8', 0, 0), 0, 0, '', $escapeonlyhtmltags, 1);
2377}
2378
2387function dolPrintText($s)
2388{
2389 return dol_escape_htmltag(dol_string_nohtmltag($s, 2, 'UTF-8', 0, 0), 0, 1, '', 0, 1);
2390}
2391
2403function dolPrintHTML($s, $allowiframe = 0, $moreallowedtags = array())
2404{
2405 // If text is already HTML, we want to escape only dangerous chars else we want to escape all content.
2406 //$isAlreadyHTML = dol_textishtml($s);
2407
2408 // dol_htmlentitiesbr encode all chars except "'" if string is not already HTML, but
2409 // encode only special char like accented chars but not &, <, >, ", ' if already HTML.
2410 $stringWithEntitesForSpecialChar = dol_htmlentitiesbr((string) $s);
2411
2412 $allowedtags = 'common';
2413 if (!empty($moreallowedtags)) {
2414 $allowedtags .= ','.implode(',', $moreallowedtags);
2415 }
2416 return dol_escape_htmltag(dol_htmlwithnojs(dol_string_onlythesehtmltags($stringWithEntitesForSpecialChar, 1, 1, 1, $allowiframe, $allowedtags)), 1, 1, $allowedtags, 0, 1);
2417}
2418
2429function dolPrintHTMLForAttribute($s, $escapeonlyhtmltags = 0, $allowothertags = array())
2430{
2431 $allowedtags = array('br', 'b', 'font', 'hr', 'span');
2432 if (!empty($allowothertags) && is_array($allowothertags)) {
2433 $allowedtags = array_merge($allowedtags, $allowothertags);
2434 }
2435 // The dol_htmlentitiesbr will convert simple text into html, including switching accent into HTML entities
2436 // The dol_escape_htmltag will escape html tags.
2437 if ($escapeonlyhtmltags) {
2438 return dol_escape_htmltag(dol_string_onlythesehtmltags($s, 1, 0, 0, 0, $allowedtags), 1, -1, '', 1, 1);
2439 } else {
2440 return dol_escape_htmltag(dol_string_onlythesehtmltags(dol_htmlentitiesbr($s), 1, 0, 0, 0, $allowedtags), 1, -1, '', 0, 1);
2441 }
2442}
2443
2452function dolPrintHTMLForAttributeUrl($s)
2453{
2454 // The dol_htmlentitiesbr has been removed compared to dolPrintHTMLForAttribute because we know content is a HTML URL string (even if we have no way to detect it automatically)
2455 // The dol_escape_htmltag will escape html chars.
2456 $escapeonlyhtmltags = 1;
2457 return dol_escape_htmltag(dol_string_onlythesehtmltags($s, 1, 1, 1, 0, array()), 0, 0, '', $escapeonlyhtmltags, 1);
2458}
2459
2469function dolPrintHTMLForTextArea($s, $allowiframe = 0)
2470{
2471 return dol_escape_htmltag(dol_htmlwithnojs(dol_string_onlythesehtmltags(dol_htmlentitiesbr($s), 1, 1, 1, $allowiframe)), 1, 1, '', 0, 1);
2472}
2473
2480function dolPrintPassword($s)
2481{
2482 return htmlspecialchars($s, ENT_HTML5, 'UTF-8');
2483}
2484
2485
2502function dol_escape_htmltag($stringtoescape, $keepb = 0, $keepn = 0, $noescapetags = '', $escapeonlyhtmltags = 0, $cleanalsojavascript = 0)
2503{
2504 $reg = array();
2505 if (preg_match('/^common([a-z,]*)/', $noescapetags, $reg)) {
2506 $noescapetags = 'html,body,a,b,em,hr,i,u,ul,ol,li,br,div,img,font,p,span,strong,table,tr,td,th,tbody,h1,h2,h3,h4,h5,h6,h7,h8,h9';
2507 // Add also html5 tags
2508 $noescapetags .= ',header,footer,nav,section,menu,menuitem';
2509 if (!empty($reg[1])) {
2510 $noescapetags .= $reg[1];
2511 }
2512 }
2513 if ($cleanalsojavascript) {
2514 $stringtoescape = dol_string_onlythesehtmltags($stringtoescape, 0, 0, $cleanalsojavascript, 0, array(), 0);
2515 }
2516
2517 // escape quotes and backslashes, newlines, etc.
2518 if ($escapeonlyhtmltags) {
2519 $tmp = htmlspecialchars_decode((string) $stringtoescape, ENT_COMPAT);
2520 } else {
2521 // We make a manipulation by calling the html_entity_decode() to convert content into NON HTML UTF8 string.
2522 // Because content can be or not already HTML.
2523 // For example, this decode &egrave; into its UTF-8 char so string is UTF8 (but numbers entities like &#39; is not decoded).
2524 // In a future, we should not need this
2525
2526 $tmp = (string) $stringtoescape;
2527
2528 // We protect the 6 special entities that we don't want to decode.
2529 $tmp = str_ireplace('&lt', '__DONOTDECODELT', $tmp);
2530 $tmp = str_ireplace('&gt', '__DONOTDECODEGT', $tmp);
2531 $tmp = str_ireplace('&amp', '__DONOTDECODEAMP', $tmp);
2532 $tmp = str_ireplace('&quot', '__DONOTDECODEQUOT', $tmp);
2533 $tmp = str_ireplace('&apos', '__DONOTDECODEAPOS', $tmp);
2534 $tmp = str_ireplace('&#39', '__DONOTDECODE39', $tmp);
2535
2536 $tmp = html_entity_decode((string) $tmp, ENT_COMPAT, 'UTF-8'); // Convert entities into UTF8
2537
2538 // We restore the 6 special entities that we don't want to have been decoded by previous command
2539 $tmp = str_ireplace('__DONOTDECODELT', '&lt', $tmp);
2540 $tmp = str_ireplace('__DONOTDECODEGT', '&gt', $tmp);
2541 $tmp = str_ireplace('__DONOTDECODEAMP', '&amp', $tmp);
2542 $tmp = str_ireplace('__DONOTDECODEQUOT', '&quot', $tmp);
2543 $tmp = str_ireplace('__DONOTDECODEAPOS', '&apos', $tmp);
2544 $tmp = str_ireplace('__DONOTDECODE39', '&#39', $tmp);
2545
2546 $tmp = str_ireplace('&#39;', '__SIMPLEQUOTE__', $tmp); // HTML 4
2547 }
2548 if (!$keepb) {
2549 $tmp = strtr($tmp, array("<b>" => '', '</b>' => '', '<strong>' => '', '</strong>' => ''));
2550 }
2551 if (!$keepn) {
2552 $tmp = strtr($tmp, array("\r" => '\\r', "\n" => '\\n'));
2553 } elseif ($keepn == -1) {
2554 $tmp = strtr($tmp, array("\r" => '', "\n" => ''));
2555 }
2556
2557 if ($escapeonlyhtmltags) {
2558 $tmp = htmlspecialchars($tmp, ENT_COMPAT, 'UTF-8');
2559 return $tmp;
2560 } else {
2561 // Now we protect all the tags we want to keep
2562 $tmparrayoftags = array();
2563 if ($noescapetags) {
2564 $tmparrayoftags = explode(',', $noescapetags);
2565 }
2566
2567 if (count($tmparrayoftags)) {
2568 // Now we will protect tags (defined into $tmparrayoftags) that we want to keep untouched
2569
2570 $reg = array();
2571 // Remove reserved keywords. They are forbidden in a source string
2572 $tmp = str_ireplace(array('__DOUBLEQUOTE', '__BEGINTAGTOREPLACE', '__ENDTAGTOREPLACE', '__BEGINENDTAGTOREPLACE'), '', $tmp);
2573
2574 foreach ($tmparrayoftags as $tagtoreplace) {
2575 // For case of tag without attributes '<abc>', '</abc>', '<abc />', we protect them to avoid transformation by htmlentities() later
2576 $tmp = preg_replace('/<' . preg_quote($tagtoreplace, '/') . '>/', '__BEGINTAGTOREPLACE' . $tagtoreplace . '__', $tmp);
2577 $tmp = str_ireplace('</' . $tagtoreplace . '>', '__ENDTAGTOREPLACE' . $tagtoreplace . '__', $tmp);
2578 $tmp = preg_replace('/<' . preg_quote($tagtoreplace, '/') . ' \/>/', '__BEGINENDTAGTOREPLACE' . $tagtoreplace . '__', $tmp);
2579
2580 // For case of tag with attributes.
2581 // All the occurrences are protected in a single pass: the replacement string contains no '<', so it
2582 // can never build a new tag to protect (a loop replacing one distinct attribute string per round was
2583 // rescanning the whole content for each of them, so the cost was quadratic on large contents).
2584 $tmp = preg_replace_callback(
2585 '/<'.preg_quote($tagtoreplace, '/').'(\s+)([^>]+)>/',
2590 static function ($reg) use ($tagtoreplace) {
2591 // We want to protect the attribute part ... in '<xxx ...>' to avoid transformation by htmlentities() later
2592 $tmpattributes = str_ireplace(array('[', ']'), '_', $reg[2]); // We must never have [ ] inside the attribute string
2593 $tmpattributes = str_ireplace('"', '__DOUBLEQUOTE__', $tmpattributes);
2594 $tmpattributes = preg_replace('/[^a-z0-9_%,\/\?\;\s=&\.\-@:\.#\+]/i', '', $tmpattributes);
2595 //$tmpattributes = preg_replace("/float:\s*(left|right)/", "", $tmpattributes); // Disabled: we must not remove content
2596 return '__BEGINTAGTOREPLACE'.$tagtoreplace.'['.$tmpattributes.']__';
2597 },
2598 $tmp
2599 ) ?? $tmp;
2600 }
2601
2602 $tmp = str_ireplace('&amp', '__ANDNOSEMICOLON__', $tmp);
2603 $tmp = str_ireplace('&quot', '__DOUBLEQUOTENOSEMICOLON__', $tmp);
2604 $tmp = str_ireplace('&lt', '__LESSTHAN__', $tmp);
2605 $tmp = str_ireplace('&gt', '__GREATERTHAN__', $tmp);
2606 }
2607
2608 // Warning: htmlentities encode all special chars that remains (except "'" with ENT_COMPAT).
2609 $result = htmlentities($tmp, ENT_COMPAT, 'UTF-8');
2610
2611 //print $result;
2612
2613 if (count($tmparrayoftags)) {
2614 // Restore protected tags
2615 foreach ($tmparrayoftags as $tagtoreplace) {
2616 $result = str_ireplace('__BEGINTAGTOREPLACE' . $tagtoreplace . '__', '<' . $tagtoreplace . '>', $result);
2617 $result = preg_replace('/__BEGINTAGTOREPLACE' . $tagtoreplace . '\[([^\]]*)\]__/', '<' . $tagtoreplace . ' \1>', $result);
2618 $result = str_ireplace('__ENDTAGTOREPLACE' . $tagtoreplace . '__', '</' . $tagtoreplace . '>', $result);
2619 $result = str_ireplace('__BEGINENDTAGTOREPLACE' . $tagtoreplace . '__', '<' . $tagtoreplace . ' />', $result);
2620 $result = preg_replace('/__BEGINENDTAGTOREPLACE' . $tagtoreplace . '\[([^\]]*)\]__/', '<' . $tagtoreplace . ' \1 />', $result);
2621 }
2622
2623 $result = str_ireplace('__DOUBLEQUOTE__', '"', $result);
2624
2625 $result = str_ireplace('__ANDNOSEMICOLON__', '&amp', $result);
2626 $result = str_ireplace('__DOUBLEQUOTENOSEMICOLON__', '&quot', $result);
2627 $result = str_ireplace('__LESSTHAN__', '&lt', $result);
2628 $result = str_ireplace('__GREATERTHAN__', '&gt', $result);
2629 }
2630
2631 $result = str_ireplace('__SIMPLEQUOTE__', '&#39;', $result);
2632
2633 //$result="\n\n\n".var_export($tmp, true)."\n\n\n".var_export($result, true);
2634
2635 return $result;
2636 }
2637}
2638
2646function dol_strtolower($string, $encoding = "UTF-8")
2647{
2648 if (function_exists('mb_strtolower')) {
2649 return mb_strtolower($string, $encoding);
2650 } else {
2651 return strtolower($string);
2652 }
2653}
2654
2663function dol_strtoupper($string, $encoding = "UTF-8")
2664{
2665 if (function_exists('mb_strtoupper')) {
2666 return mb_strtoupper($string, $encoding);
2667 } else {
2668 return strtoupper($string);
2669 }
2670}
2671
2680function dol_ucfirst($string, $encoding = "UTF-8")
2681{
2682 if (function_exists('mb_substr')) {
2683 return mb_strtoupper(mb_substr($string, 0, 1, $encoding), $encoding) . mb_substr($string, 1, null, $encoding);
2684 } else {
2685 return ucfirst($string);
2686 }
2687}
2688
2697function dol_ucwords($string, $encoding = "UTF-8")
2698{
2699 if (function_exists('mb_convert_case')) {
2700 return mb_convert_case($string, MB_CASE_TITLE, $encoding);
2701 } else {
2702 return ucwords($string);
2703 }
2704}
2705
2706
2712function getCallerInfoString()
2713{
2714 $backtrace = debug_backtrace();
2715 $msg = "";
2716 if (count($backtrace) >= 1) {
2717 $pos = 1;
2718 if (count($backtrace) == 1) {
2719 $pos = 0;
2720 }
2721 $trace = $backtrace[$pos];
2722 if (isset($trace['file'], $trace['line'])) {
2723 $msg = " From {$trace['file']}:{$trace['line']}.";
2724 }
2725 }
2726 return $msg;
2727}
2728
2751function dol_syslog($message, $level = LOG_INFO, $ident = 0, $suffixinfilename = '', $restricttologhandler = '', $logcontext = null)
2752{
2753 global $conf, $user, $debugbar;
2754
2755 // If syslog module enabled
2756 if (!isModEnabled('syslog')) {
2757 return;
2758 }
2759
2760 // Check if we are into execution of code of a website
2761 if (defined('USEEXTERNALSERVER') && !defined('USEDOLIBARRSERVER') && !defined('USEDOLIBARREDITOR')) {
2762 global $website, $websitekey;
2763 if (is_object($website) && !empty($website->ref)) {
2764 $suffixinfilename .= '_website_' . $website->ref;
2765 } elseif (!empty($websitekey)) {
2766 $suffixinfilename .= '_website_' . $websitekey;
2767 }
2768 }
2769
2770 // Check if we have a forced suffix
2771 if (defined('USESUFFIXINLOG')) {
2772 $suffixinfilename .= constant('USESUFFIXINLOG');
2773 }
2774
2775 if ($ident < 0) {
2776 foreach ($conf->loghandlers as $loghandlerinstance) {
2777 $loghandlerinstance->setIdent($ident);
2778 }
2779 }
2780
2781 if (!empty($message)) {
2782 // Test log level
2783 // @phan-suppress-next-line PhanPluginDuplicateArrayKey
2784 $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');
2785
2786 if (!array_key_exists($level, $logLevels)) {
2787 dol_syslog('Error Bad Log Level ' . $level, LOG_ERR);
2788 $level = LOG_ERR;
2789 }
2790 if ($level > getDolGlobalInt('SYSLOG_LEVEL')) {
2791 return;
2792 }
2793
2794 if (!getDolGlobalString('MAIN_SHOW_PASSWORD_INTO_LOG')) {
2795 $message = preg_replace('/password=\'[^\']*\'/', 'password=\'hidden\'', $message); // protection to avoid to have value of password in log
2796 }
2797
2798 // If adding log inside HTML page is required
2799 if ((!empty($_REQUEST['logtohtml']) && getDolGlobalString('MAIN_ENABLE_LOG_TO_HTML'))
2800 || (is_object($user) && $user->hasRight('debugbar', 'read') && is_object($debugbar))
2801 ) {
2802 $ospid = sprintf("%7s", dol_trunc((string) getmypid(), 7, 'right', 'UTF-8', 1));
2803 $osuser = " " . sprintf("%6s", dol_trunc(function_exists('posix_getuid') ? posix_getuid() : '', 6, 'right', 'UTF-8', 1));
2804
2805 $conf->logbuffer[] = dol_print_date(time(), "%Y-%m-%d %H:%M:%S") . " " . sprintf("%-7s", $logLevels[$level]) . " " . $ospid . " " . $osuser . " " . $message;
2806 }
2807
2808 //TODO: Remove this. MAIN_ENABLE_LOG_INLINE_HTML should be deprecated and use a log handler dedicated to HTML output
2809 // If html log tag enabled and url parameter log defined, we show output log on HTML comments
2810 if (getDolGlobalString('MAIN_ENABLE_LOG_INLINE_HTML') && GETPOSTINT("log")) {
2811 print "\n\n<!-- Log start\n";
2812 print dol_escape_htmltag($message) . "\n";
2813 print "Log end -->\n";
2814 }
2815
2816 $data = array(
2817 'message' => $message,
2818 'script' => (isset($_SERVER['PHP_SELF']) ? basename($_SERVER['PHP_SELF'], '.php') : ''),
2819 'level' => $level,
2820 'user' => ((is_object($user) && $user->id) ? $user->login : ''),
2821 'ip' => '',
2822 'osuser' => function_exists('posix_getuid') ? (string) posix_getuid() : '',
2823 'ospid' => (string) getmypid() // on linux, max value is defined into cat /proc/sys/kernel/pid_max
2824 );
2825
2826 // For log, we want the reliable IP first.
2827 $remoteip = getUserRemoteIP(1); // Get ip when page run on a web server
2828 if (!empty($remoteip)) {
2829 $data['ip'] = $remoteip;
2830 // This is when server run behind a reverse proxy
2831 // A HTTP_X_FORWARDED_FOR as format "ip real of user, ip of proxy1, ip of proxy2, ..."
2832 // $data['ip'] is last
2833 if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
2834 $tmpips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
2835 $data['ip'] = '';
2836 $foundremoteip = 0;
2837 $j = 0;
2838 foreach ($tmpips as $tmpip) {
2839 $tmpip = trim($tmpip);
2840 if (strtolower($tmpip) == strtolower($remoteip)) {
2841 $foundremoteip = 1;
2842 }
2843 if (empty($data['ip'])) {
2844 $data['ip'] = $tmpip;
2845 } else {
2846 $j++;
2847 $data['ip'] .= (($j == 1) ? ' [via ' : ',') . $tmpip;
2848 }
2849 }
2850 if (!$foundremoteip) {
2851 $j++;
2852 $data['ip'] .= (($j == 1) ? ' [via ' : ',') . $remoteip;
2853 }
2854 $data['ip'] .= (($j > 0) ? ']' : '');
2855 } elseif (!empty($_SERVER['HTTP_CLIENT_IP'])) {
2856 $tmpips = explode(',', $_SERVER['HTTP_CLIENT_IP']);
2857 $data['ip'] = '';
2858 $foundremoteip = 0;
2859 $j = 0;
2860 foreach ($tmpips as $tmpip) {
2861 $tmpip = trim($tmpip);
2862 if (strtolower($tmpip) == strtolower($remoteip)) {
2863 $foundremoteip = 1;
2864 }
2865 if (empty($data['ip'])) {
2866 $data['ip'] = $tmpip;
2867 } else {
2868 $j++;
2869 $data['ip'] .= (($j == 1) ? ' [via ' : ',') . $tmpip;
2870 }
2871 }
2872 if (!$foundremoteip) {
2873 $j++;
2874 $data['ip'] .= (($j == 1) ? ' [via ' : ',') . $remoteip;
2875 }
2876 $data['ip'] .= (($j > 0) ? ']' : '');
2877 }
2878 } elseif (!empty($_SERVER['SERVER_ADDR'])) {
2879 // This is when PHP session is ran inside a web server but not inside a client request (example: init code of apache)
2880 $data['ip'] = (string) $_SERVER['SERVER_ADDR'];
2881 } elseif (!empty($_SERVER['COMPUTERNAME'])) {
2882 // 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).
2883 $data['ip'] = (string) $_SERVER['COMPUTERNAME'];
2884 } else {
2885 $data['ip'] = '???';
2886 }
2887
2888 if (!empty($_SERVER['USERNAME'])) {
2889 // 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).
2890 $data['osuser'] = (string) $_SERVER['USERNAME'];
2891 } elseif (!empty($_SERVER['LOGNAME'])) {
2892 // 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).
2893 $data['osuser'] = (string) $_SERVER['LOGNAME'];
2894 }
2895
2896 // Loop on each log handler and send output
2897 foreach ($conf->loghandlers as $loghandlerinstance) {
2898 if ($restricttologhandler && $loghandlerinstance->code != $restricttologhandler) {
2899 continue;
2900 }
2901 $loghandlerinstance->export($data, $suffixinfilename);
2902 }
2903 unset($data);
2904 }
2905
2906 if ($ident > 0) {
2907 foreach ($conf->loghandlers as $loghandlerinstance) {
2908 $loghandlerinstance->setIdent($ident);
2909 }
2910 }
2911}
2912
2924function dolButtonToOpenExportDialog($name, $label, $buttonstring, $exportSiteName, $overwriteGitUrl, $website)
2925{
2926 global $langs, $db;
2927
2928 $form = new Form($db);
2929
2930 $templatenameforexport = $website->name_template; // Example 'website_template-corporate'
2931 if (empty($templatenameforexport)) {
2932 $templatenameforexport = 'website_' . $website->ref;
2933 }
2934
2935 $out = '';
2936 $out .= '<input type="button" class="cursorpointer button bordertransp" id="open-dialog-' . $name . '" value="' . dol_escape_htmltag($buttonstring) . '"/>';
2937
2938 // for generate popup
2939 $out .= '<script nonce="' . getNonce() . '" type="text/javascript">';
2940 $out .= 'jQuery(document).ready(function () {';
2941 $out .= ' jQuery("#open-dialog-' . $name . '").click(function () {';
2942 $out .= ' var dialogHtml = \'';
2943
2944 $dialogcontent = ' <div id="custom-dialog-' . $name . '">';
2945 $dialogcontent .= ' <div style="margin-top: 20px;">';
2946 $dialogcontent .= ' <label for="export-site-' . $name . '"><strong>' . $langs->trans("ExportSiteLabel") . '...</label><br>';
2947 $dialogcontent .= ' <button class="button smallpaddingimp" id="export-site-' . $name . '">' . dol_escape_htmltag($langs->trans("DownloadZip")) . '</button>';
2948 $dialogcontent .= ' </div>';
2949 $dialogcontent .= ' <br>';
2950 $dialogcontent .= ' <div style="margin-top: 20px;">';
2951 $dialogcontent .= ' <strong>' . $langs->trans("ExportSiteGitLabel") . ' ' . $form->textwithpicto('', $langs->trans("SourceFiles"), 1, 'help', '', 0, 3, '') . '</strong><br>';
2952 $dialogcontent .= ' <form action="' . dol_escape_htmltag($overwriteGitUrl) . '" method="POST">';
2953 $dialogcontent .= ' <input type="hidden" name="action" value="overwritesite">';
2954 $dialogcontent .= ' <input type="hidden" name="token" value="' . newToken() . '">';
2955 $dialogcontent .= ' <input type="text" autofocus name="export_path" id="export-path-' . $name . '" placeholder="' . $langs->trans('ExportPath') . '" style="width:400px " value="' . dol_escape_htmltag($templatenameforexport) . '"/><br>';
2956 $dialogcontent .= ' <button type="submit" class="button smallpaddingimp" id="overwrite-git-' . $name . '">' . dol_escape_htmltag($langs->trans("ExportIntoGIT")) . '</button>';
2957 $dialogcontent .= ' </form>';
2958 $dialogcontent .= ' </div>';
2959 $dialogcontent .= ' </div>';
2960
2961 $out .= dol_escape_js($dialogcontent);
2962
2963 $out .= '\';';
2964
2965
2966 // Add the content of the dialog to the body of the page
2967 $out .= ' var $dialog = jQuery("#custom-dialog-' . $name . '");';
2968 $out .= ' if ($dialog.length > 0) {
2969 $dialog.remove();
2970 }
2971 jQuery("body").append(dialogHtml);';
2972
2973 // Configuration of popup
2974 $out .= ' jQuery("#custom-dialog-' . $name . '").dialog({';
2975 $out .= ' autoOpen: false,';
2976 $out .= ' modal: true,';
2977 $out .= ' height: 290,';
2978 $out .= ' width: "40%",';
2979 $out .= ' title: "' . dol_escape_js($label) . '",';
2980 $out .= ' });';
2981
2982 // Simulate a click on the original "submit" input to export the site.
2983 $out .= ' jQuery("#export-site-' . $name . '").click(function () {';
2984 $out .= ' console.log("Clic on exportsite.");';
2985 $out .= ' var target = jQuery("input[name=\'' . dol_escape_js($exportSiteName) . '\']");';
2986 $out .= ' console.log("element founded:", target.length > 0);';
2987 $out .= ' if (target.length > 0) { target.click(); }';
2988 $out .= ' jQuery("#custom-dialog-' . $name . '").dialog("close");';
2989 $out .= ' });';
2990
2991 // open popup
2992 $out .= ' jQuery("#custom-dialog-' . $name . '").dialog("open");';
2993 $out .= ' return false;';
2994 $out .= ' });';
2995 $out .= '});';
2996 $out .= '</script>';
2997
2998 return $out;
2999}
3000
3001
3018function dolButtonToOpenUrlInDialogPopup($name, $label, $buttonstring, $url, $disabled = '', $morecss = 'classlink button bordertransp', $jsonopen = '', $jsonclose = '', $accesskey = '')
3019{
3020 global $conf;
3021
3022 if (strpos($url, '?') > 0) {
3023 $url .= '&dol_hide_topmenu=1&dol_hide_leftmenu=1&dol_openinpopup=' . urlencode($name);
3024 } else {
3025 $url .= '?dol_hide_topmenu=1&dol_hide_leftmenu=1&dol_openinpopup=' . urlencode($name);
3026 }
3027
3028 if (preg_match('/^https/i', $url)) {
3029 $urltoopen = $url;
3030 } else {
3031 $urltoopen = DOL_URL_ROOT . $url;
3032 }
3033
3034 $out = '';
3035
3036 //print '<input type="submit" class="button bordertransp"'.$disabled.' value="'.dol_escape_htmltag($langs->trans("MediaFiles")).'" name="file_manager">';
3037 $out .= '<!-- a link for button to open url into a dialog popup -->';
3038 $out .= '<a ' . ($accesskey ? ' accesskey="' . $accesskey . '"' : '') . ' class="cursorpointer reposition button_' . $name . ($morecss ? ' ' . $morecss : '') . '"' . $disabled . ' title="' . dol_escape_htmltag($label) . '"';
3039 if (empty($conf->use_javascript_ajax)) {
3040 $out .= ' href="' . $urltoopen . '" target="_blank"';
3041 } elseif ($jsonopen) {
3042 $out .= ' href="#" onclick="' . $jsonopen . '"';
3043 } else {
3044 $out .= ' href="#"';
3045 }
3046 $out .= '>' . $buttonstring . '</a>';
3047
3048 if (!empty($conf->use_javascript_ajax)) {
3049 // Add code to open url using the popup.
3050 $out .= '<!-- code to open popup and variables to retrieve returned variables -->';
3051 $out .= '<div id="idfordialog' . $name . '" class="hidden">' . (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2 ? 'div for dialog' : '') . '</div>';
3052
3053 $out .= '<!-- Add js code to open dialog popup on dialog -->';
3054 $out .= '<script nonce="' . getNonce() . '" type="text/javascript">
3055 jQuery(document).ready(function () {
3056 jQuery(".button_' . $name . '").click(function () {
3057 console.log(\'Open popup with jQuery(...).dialog() on URL ' . dol_escape_js($urltoopen) . '\');
3058 var $tmpdialog = $(\'#idfordialog' . $name . '\');
3059 $tmpdialog.html(\'<iframe class="iframedialog" id="iframedialog' . $name . '" style="border: 0px;" src="' . $urltoopen . '" width="100%" height="98%"></iframe>\');
3060 $tmpdialog.dialog({
3061 autoOpen: false,
3062 modal: true,
3063 height: (window.innerHeight - 150),
3064 width: \'80%\',
3065 title: \'' . dol_escape_js($label) . '\',
3066 open: function (event, ui) {
3067 console.log("open popup name=' . $name . '");
3068 },
3069 close: function (event, ui) {
3070 console.log("Popup is closed, run jsonclose = ' . $jsonclose . '");
3071 ' . (empty($jsonclose) || preg_match('/^TODO/', $jsonclose) ? '' : $jsonclose . ';') . '
3072 }
3073 });
3074
3075 $tmpdialog.dialog(\'open\');
3076 return false;
3077 });
3078 });
3079 </script>';
3080 }
3081 return $out;
3082}
3083
3100function dol_fiche_head($links = array(), $active = '0', $title = '', $notab = 0, $picto = '', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '', $limittoshow = 0, $moretabssuffix = '')
3101{
3102 print dol_get_fiche_head($links, $active, $title, $notab, $picto, $pictoisfullpath, $morehtmlright, $morecss, $limittoshow, $moretabssuffix);
3103}
3104
3122function dol_get_fiche_head($links = array(), $active = '', $title = '', $notab = 0, $picto = '', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '', $limittoshow = 0, $moretabssuffix = '', $dragdropfile = 0, $morecssdiv = '')
3123{
3124 global $conf, $langs, $hookmanager;
3125
3126 // Show title
3127 $showtitle = 1;
3128 if (!empty($conf->dol_optimize_smallscreen)) {
3129 $showtitle = 0;
3130 }
3131
3132 $out = "\n" . '<!-- dol_fiche_head - dol_get_fiche_head -->';
3133
3134 if ((!empty($title) && $showtitle) || $morehtmlright || !empty($links)) {
3135 $out .= '<div class="tabs' . ($picto ? '' : ' nopaddingleft') . '" data-role="controlgroup" data-type="horizontal">' . "\n";
3136 }
3137
3138 // Show right part
3139 if ($morehtmlright) {
3140 $out .= '<div class="inline-block floatright tabsElem">' . $morehtmlright . '</div>'; // Output right area first so when space is missing, text is in front of tabs and not under.
3141 }
3142
3143 // Show tabs
3144
3145 // Define max of key (max may be higher than sizeof because of hole due to module disabling some tabs).
3146 $maxkey = -1;
3147 if (is_array($links) && !empty($links)) {
3148 $keys = array_keys($links);
3149 if (count($keys)) {
3150 $maxkey = max($keys);
3151 }
3152 }
3153
3154 // Show tabs
3155 // if =0 we don't use the feature
3156 if (empty($limittoshow)) {
3157 $limittoshow = getDolGlobalInt('MAIN_MAXTABS_IN_CARD', 99);
3158 }
3159 if (!empty($conf->dol_optimize_smallscreen)) { // If on smartphone, we limit to 1 tab to show
3160 $limittoshow = 1;
3161 }
3162
3163 $displaytab = 0;
3164 $nbintab = 0;
3165 $popuptab = 0;
3166 $outmore = '';
3167 for ($i = 0; $i <= $maxkey; $i++) {
3168 if ((is_numeric($active) && $i == $active) || (!empty($links[$i][2]) && !is_numeric($active) && $active == $links[$i][2])) {
3169 // If active tab is already present
3170 if ($i >= $limittoshow) {
3171 $limittoshow--;
3172 }
3173 }
3174 }
3175
3176 for ($i = 0; $i <= $maxkey; $i++) {
3177 if ((is_numeric($active) && $i == $active) || (!empty($links[$i][2]) && !is_numeric($active) && $active == $links[$i][2])) {
3178 $isactive = true;
3179 } else {
3180 $isactive = false;
3181 }
3182
3183 if ($i < $limittoshow || $isactive) {
3184 // Output entry with a visible tab
3185 $out .= '<div class="inline-block tabsElem' . ($isactive ? ' tabsElemActive' : '') . ((!$isactive && getDolGlobalString('MAIN_HIDE_INACTIVETAB_ON_PRINT')) ? ' hideonprint' : '') . '"><!-- id tab = ' . (empty($links[$i][2]) ? '' : dol_escape_htmltag($links[$i][2])) . ' -->';
3186
3187 if (isset($links[$i][2]) && $links[$i][2] == 'image') {
3188 if (!empty($links[$i][0])) {
3189 $out .= '<a class="tabimage' . ($morecss ? ' ' . $morecss : '') . '" href="' . $links[$i][0] . '">' . $links[$i][1] . '</a>' . "\n";
3190 } else {
3191 $out .= '<span class="tabspan">' . $links[$i][1] . '</span>' . "\n";
3192 }
3193 } elseif (!empty($links[$i][1])) {
3194 //print "x $i $active ".$links[$i][2]." z";
3195 $out .= '<div class="tab tab' . ($isactive ? 'active' : 'unactive') . '" style="margin: 0 !important">';
3196
3197 if (!empty($links[$i][0])) {
3198 $titletoshow = preg_replace('/<.*$/', '', $links[$i][1]);
3199 $out .= '<a' . (!empty($links[$i][2]) ? ' id="' . $links[$i][2] . '"' : '') . ' class="tab inline-block valignmiddle' . ($morecss ? ' ' . $morecss : '') . (!empty($links[$i][5]) ? ' ' . $links[$i][5] : '') . '" href="' . $links[$i][0] . '" title="' . dol_escape_htmltag($titletoshow) . '">';
3200 }
3201
3202 if ($displaytab == 0 && $picto) {
3203 $out .= img_picto($title, $picto, '', $pictoisfullpath, 0, 0, '', 'imgTabTitle paddingright marginrightonlyshort');
3204 }
3205
3206 $out .= $links[$i][1];
3207 if (!empty($links[$i][0])) {
3208 $out .= '</a>' . "\n";
3209 }
3210 $out .= empty($links[$i][4]) ? '' : $links[$i][4];
3211 $out .= '</div>';
3212 }
3213
3214 $out .= '</div>';
3215 } else {
3216 // Add entry into the combo popup with the other tabs
3217 if (!$popuptab) {
3218 $popuptab = 1;
3219 $outmore .= '<div class="popuptabset wordwrap">'; // The css used to hide/show popup
3220 }
3221 $outmore_content = '';
3222
3223 if (isset($links[$i][2]) && $links[$i][2] == 'image') {
3224 if (!empty($links[$i][0])) {
3225 $outmore_content .= '<a class="tabimage' . ($morecss ? ' ' . $morecss : '') . '" href="' . $links[$i][0] . '">' . $links[$i][1] . '</a>' . "\n";
3226 } else {
3227 $outmore_content .= '<span class="tabspan">' . $links[$i][1] . '</span>' . "\n";
3228 }
3229 } elseif (!empty($links[$i][1])) {
3230 $outmore_content .= '<a' . (!empty($links[$i][2]) ? ' id="' . $links[$i][2] . '"' : '') . ' class="wordwrap inline-block' . ($morecss ? ' ' . $morecss : '') . '" href="' . $links[$i][0] . '">';
3231 $outmore_content .= preg_replace('/([a-z])\|([a-z])/i', '\\1 | \\2', $links[$i][1]); // Replace x|y with x | y to allow wrap on long composed texts.
3232 $outmore_content .= '</a>' . "\n";
3233 }
3234 if ($outmore_content !== '') {
3235 $outmore .= '<div class="popuptab wordwrap" style="display:inherit;">' . $outmore_content . '</div>';
3236 }
3237
3238 $nbintab++;
3239 }
3240
3241 $displaytab = $i + 1;
3242 }
3243 if ($popuptab) {
3244 $outmore .= '</div>';
3245 }
3246
3247 if ($popuptab) { // If there is some tabs not shown
3248 $left = ($langs->trans("DIRECTION") == 'rtl' ? 'right' : 'left');
3249 $right = ($langs->trans("DIRECTION") == 'rtl' ? 'left' : 'right');
3250 $widthofpopup = 240;
3251
3252 $tabsname = $moretabssuffix;
3253 if (empty($tabsname)) {
3254 $tabsname = str_replace("@", "", $picto);
3255 }
3256 $out .= '<div id="moretabs' . $tabsname . '" class="inline-block tabsElem valignmiddle">';
3257 if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2) {
3258 $out .= '<div class="tab valignmiddle"><a href="#" class="tab moretab inline-block tabunactive valignmiddle"><span class="fa fa-angle-down"></span> <span class="opacitymedium">+' . $nbintab . '</span></a></div>'; // Do not use "reposition" class in the "More".
3259 }
3260 $out .= '<div id="moretabsList' . $tabsname . '" style="width: ' . $widthofpopup . 'px; position: absolute; ' . $left . ': -999em; text-align: ' . $left . '; margin:0px; padding:2px; z-index:10;">';
3261 $out .= $outmore;
3262 $out .= '</div>';
3263 $out .= '<div></div>';
3264 $out .= "</div>\n";
3265
3266 $out .= '<script nonce="' . getNonce() . '">';
3267 $out .= "$('#moretabs" . $tabsname . "').mouseenter( function() {
3268 var x = this.offsetLeft, y = this.offsetTop;
3269 console.log('mouseenter " . $left . " x='+x+' y='+y+' window.innerWidth='+window.innerWidth);
3270 if ((window.innerWidth - x) < " . ($widthofpopup + 10) . ") {
3271 $('#moretabsList" . $tabsname . "').css('" . $right . "','8px');
3272 }
3273 $('#moretabsList" . $tabsname . "').css('" . $left . "','auto');
3274 });
3275 ";
3276 $out .= "$('#moretabs" . $tabsname . "').mouseleave( function() { console.log('mouseleave " . $left . "'); $('#moretabsList" . $tabsname . "').css('" . $left . "','-999em');});";
3277 $out .= "</script>";
3278 }
3279
3280 if ((!empty($title) && $showtitle) || $morehtmlright || !empty($links)) {
3281 $out .= "</div>\n";
3282 }
3283
3284 if (!$notab || $notab == -1 || $notab == -2 || $notab == -3 || $notab == -4) {
3285 $out .= "\n" . '<div id="dragDropAreaTabBar" class="tabBar' . ($notab == -1 ? '' : ($notab == -2 ? ' tabBarNoTop' : ((($notab == -3 || $notab == -4) ? ' noborderbottom' : '') . ($notab == -4 ? '' : ' tabBarWithBottom'))));
3286 $out .= ($morecssdiv ? ' ' . $morecssdiv : '');
3287 $out .= '">' . "\n";
3288 }
3289 if (!empty($dragdropfile)) {
3290 include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
3291 $out .= dragAndDropFileUpload("dragDropAreaTabBar");
3292 }
3293 $parameters = array('tabname' => $active, 'out' => $out);
3294 $reshook = $hookmanager->executeHooks('printTabsHead', $parameters); // This hook usage is called just before output the head of tabs. Take also a look at "completeTabsHead"
3295 if ($reshook > 0) {
3296 $out = $hookmanager->resPrint;
3297 }
3298
3299 return $out;
3300}
3301
3309function dol_fiche_end($notab = 0)
3310{
3311 print dol_get_fiche_end($notab);
3312}
3313
3320function dol_get_fiche_end($notab = 0)
3321{
3322 if (!$notab || $notab == -1) {
3323 return "\n</div>\n";
3324 } else {
3325 return '';
3326 }
3327}
3328
3348function dol_banner_tab($object, $paramid, $morehtml = '', $shownav = 1, $fieldid = 'rowid', $fieldref = 'ref', $morehtmlref = '', $moreparam = '', $nodbprefix = 0, $morehtmlleft = '', $morehtmlstatus = '', $onlybanner = 0, $morehtmlright = '')
3349{
3350 global $conf, $form, $user, $langs, $hookmanager, $action;
3351
3352 $error = 0;
3353
3354 $maxvisiblephotos = 1;
3355 $showimage = 1;
3356 $entity = (empty($object->entity) ? $conf->entity : $object->entity);
3357 // @phan-suppress-next-line PhanUndeclaredMethod
3358 $showbarcode = !isModEnabled('barcode') ? 0 : (empty($object->barcode) ? 0 : 1);
3359 if (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && !$user->hasRight('barcode', 'lire_advance')) {
3360 $showbarcode = 0;
3361 }
3362 $modulepart = 'unknown';
3363
3364 if (in_array($object->element, ['societe', 'contact', 'product', 'ticket', 'bom'])) {
3365 $modulepart = $object->element;
3366 } elseif ($object->element == 'member') {
3367 $modulepart = 'memberphoto';
3368 } elseif ($object->element == 'user') {
3369 $modulepart = 'userphoto';
3370 }
3371
3372 if (class_exists("Imagick")) {
3373 if ($object->element == 'expensereport' || $object->element == 'propal' || $object->element == 'commande' || $object->element == 'facture' || $object->element == 'supplier_proposal') {
3374 $modulepart = $object->element;
3375 } elseif ($object->element == 'fichinter' || $object->element == 'intervention') {
3376 $modulepart = 'ficheinter';
3377 } elseif ($object->element == 'contrat' || $object->element == 'contract') {
3378 $modulepart = 'contract';
3379 } elseif ($object->element == 'order_supplier') {
3380 $modulepart = 'supplier_order';
3381 } elseif ($object->element == 'invoice_supplier') {
3382 $modulepart = 'supplier_invoice';
3383 }
3384 }
3385
3386 if ($object->element == 'product') {
3388 '@phan-var-force Product $object';
3389 $width = 80;
3390 $cssclass = 'photowithmargin photoref';
3391 $showimage = $object->is_photo_available($conf->product->multidir_output[$entity]);
3392 $maxvisiblephotos = getDolGlobalInt('PRODUCT_MAX_VISIBLE_PHOTO', 5);
3393 if ($conf->browser->layout == 'phone') {
3394 $maxvisiblephotos = 1;
3395 }
3396 $useLinkPathPhoto = getDolGlobalInt('PRODUCT_USE_LINK_PATH_FOR_PHOTO');
3397 if ($showimage || $useLinkPathPhoto) {
3398 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">' . $object->show_photos('product', $conf->product->multidir_output[$entity], 1, $maxvisiblephotos, 0, 0, 0, 0, $width, 0, '') . '</div>';
3399 } else {
3400 if (getDolGlobalString('PRODUCT_NODISPLAYIFNOPHOTO')) {
3401 $nophoto = '';
3402 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
3403 } else { // Show no photo link
3404 $nophoto = '/public/theme/common/nophoto.png';
3405 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><img class="photo' . $modulepart . ($cssclass ? ' ' . $cssclass : '') . '" title="' . dol_escape_htmltag($langs->trans("UploadAnImageToSeeAPhotoHere", $langs->transnoentitiesnoconv("Documents"))) . '" alt="No photo"' . ($width ? ' style="width: ' . $width . 'px"' : '') . ' src="' . DOL_URL_ROOT . $nophoto . '"></div>';
3406 }
3407 }
3408 } elseif ($object->element == 'category') {
3410 '@phan-var-force Categorie $object';
3411 $width = 80;
3412 $cssclass = 'photowithmargin photoref';
3413 $showimage = $object->isAnyPhotoAvailable($conf->categorie->multidir_output[$entity]);
3414 $maxvisiblephotos = getDolGlobalInt('CATEGORY_MAX_VISIBLE_PHOTO', 5);
3415 if ($conf->browser->layout == 'phone') {
3416 $maxvisiblephotos = 1;
3417 }
3418 if ($showimage) {
3419 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">' . $object->show_photos('category', $conf->categorie->multidir_output[$entity], 'small', $maxvisiblephotos, 0, 0, 0, 0, $width, 0, '') . '</div>';
3420 } else {
3421 if (getDolGlobalString('CATEGORY_NODISPLAYIFNOPHOTO')) {
3422 $nophoto = '';
3423 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
3424 } else { // Show no photo link
3425 $nophoto = '/public/theme/common/nophoto.png';
3426 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><img class="photo' . $modulepart . ($cssclass ? ' ' . $cssclass : '') . '" title="' . dol_escape_htmltag($langs->trans("UploadAnImageToSeeAPhotoHere", $langs->transnoentitiesnoconv("Documents"))) . '" alt="No photo"' . ($width ? ' style="width: ' . $width . 'px"' : '') . ' src="' . DOL_URL_ROOT . $nophoto . '"></div>';
3427 }
3428 }
3429 } elseif ($object->element == 'bom') {
3431 '@phan-var-force Bom $object';
3432 $width = 80;
3433 $cssclass = 'photowithmargin photoref';
3434 $showimage = $object->is_photo_available($conf->bom->multidir_output[$entity]);
3435 $maxvisiblephotos = getDolGlobalInt('BOM_MAX_VISIBLE_PHOTO', 5);
3436 if ($conf->browser->layout == 'phone') {
3437 $maxvisiblephotos = 1;
3438 }
3439 if ($showimage) {
3440 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">' . $object->show_photos('bom', $conf->bom->multidir_output[$entity], 'small', $maxvisiblephotos, 0, 0, 0, 0, $width, 0, '') . '</div>';
3441 } else {
3442 if (getDolGlobalString('BOM_NODISPLAYIFNOPHOTO')) {
3443 $nophoto = '';
3444 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
3445 } else { // Show no photo link
3446 $nophoto = '/public/theme/common/nophoto.png';
3447 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><img class="photo' . $modulepart . ($cssclass ? ' ' . $cssclass : '') . '" title="' . dol_escape_htmltag($langs->trans("UploadAnImageToSeeAPhotoHere", $langs->transnoentitiesnoconv("Documents"))) . '" alt="No photo"' . ($width ? ' style="width: ' . $width . 'px"' : '') . ' src="' . DOL_URL_ROOT . $nophoto . '"></div>';
3448 }
3449 }
3450 } elseif ($object->element == 'ticket') {
3451 $width = 80;
3452 $cssclass = 'photoref';
3454 '@phan-var-force Ticket $object';
3455 $showimage = $object->is_photo_available($conf->ticket->multidir_output[$entity] . '/' . $object->ref);
3456 $maxvisiblephotos = getDolGlobalInt('TICKET_MAX_VISIBLE_PHOTO', 2);
3457 if ($conf->browser->layout == 'phone') {
3458 $maxvisiblephotos = 1;
3459 }
3460
3461 if ($showimage) {
3462 $showphoto = $object->show_photos('ticket', $conf->ticket->multidir_output[$entity], 'small', $maxvisiblephotos, 0, 0, 0, $width, 0);
3463 if ($object->nbphoto > 0) {
3464 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">' . $showphoto . '</div>';
3465 } else {
3466 $showimage = 0;
3467 }
3468 }
3469 if (!$showimage) {
3470 if (getDolGlobalString('TICKET_NODISPLAYIFNOPHOTO')) {
3471 $nophoto = '';
3472 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
3473 } else { // Show no photo link
3474 $nophoto = img_picto('No photo', 'object_ticket');
3475 $morehtmlleft .= '<!-- No photo to show -->';
3476 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref">';
3477 $morehtmlleft .= $nophoto;
3478 $morehtmlleft .= '</div></div>';
3479 }
3480 }
3481 } else {
3482 // $modulepart may have been set previously if Imagick class exists (see before).
3483 if ($modulepart != 'unknown' || method_exists($object, 'getDataToShowPhoto')) {
3484 $phototoshow = '';
3485 // Check if a preview file is available
3486 if (in_array($modulepart, array('propal', 'commande', 'facture', 'ficheinter', 'contract', 'supplier_order', 'supplier_proposal', 'supplier_invoice', 'expensereport')) && class_exists("Imagick")) {
3487 $objectref = dol_sanitizeFileName($object->ref);
3488 $dir_output = (empty($conf->$modulepart->multidir_output[$entity]) ? $conf->$modulepart->dir_output : $conf->$modulepart->multidir_output[$entity]) . "/";
3489 if (in_array($modulepart, array('invoice_supplier', 'supplier_invoice'))) {
3490 $subdir = get_exdir($object->id, 2, 0, 1, $object, $modulepart);
3491 $subdir .= ((!empty($subdir) && !preg_match('/\/$/', $subdir)) ? '/' : '') . $objectref; // the objectref dir is not included into get_exdir when used with level=2, so we add it at end
3492 } else {
3493 $subdir = get_exdir($object->id, 0, 0, 1, $object, $modulepart);
3494 }
3495 if (empty($subdir)) {
3496 $subdir = 'errorgettingsubdirofobject'; // Protection to avoid to return empty path
3497 }
3498
3499 $filepath = $dir_output . $subdir . "/";
3500
3501 $filepdf = $filepath . $objectref . ".pdf";
3502 $relativepath = $subdir . '/' . $objectref . '.pdf';
3503
3504 // Define path to preview pdf file (preview precompiled "file.ext" are "file.ext_preview.png")
3505 $fileimage = $filepdf . '_preview.png';
3506 $relativepathimage = $relativepath . '_preview.png';
3507
3508 $pdfexists = file_exists($filepdf);
3509
3510 // If PDF file exists
3511 if ($pdfexists) {
3512 // Conversion du PDF en image png si fichier png non existent
3513 if (!file_exists($fileimage) || (filemtime($fileimage) < filemtime($filepdf))) {
3514 if (!getDolGlobalString('MAIN_DISABLE_PDF_THUMBS')) { // If you experience trouble with pdf thumb generation and imagick, you can disable here.
3515 include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
3516 $ret = dol_convert_file($filepdf, 'png', $fileimage, '0'); // Convert first page of PDF into a file _preview.png
3517 if ($ret < 0) {
3518 $error++;
3519 }
3520 }
3521 }
3522 }
3523
3524 if ($pdfexists && !$error) {
3525 $heightforphotref = 80;
3526 if (!empty($conf->dol_optimize_smallscreen)) {
3527 $heightforphotref = 60;
3528 }
3529 // If the preview file is found
3530 if (file_exists($fileimage)) {
3531 $phototoshow = '<div class="photoref">';
3532 $phototoshow .= '<img height="' . $heightforphotref . '" class="photo photowithborder" src="' . DOL_URL_ROOT . '/viewimage.php?modulepart=apercu' . $modulepart . '&amp;file=' . urlencode($relativepathimage) . '">';
3533 $phototoshow .= '</div>';
3534 }
3535 }
3536 } elseif (!$phototoshow) { // example if modulepart = 'societe' or 'photo' or 'memberphoto'
3537 $phototoshow .= $form->showphoto($modulepart, $object, 0, 0, 0, 'photowithmargin photoref', 'small', 1, 0);
3538 }
3539
3540 if ($phototoshow) {
3541 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">';
3542 $morehtmlleft .= $phototoshow;
3543 $morehtmlleft .= '</div>';
3544 }
3545 }
3546
3547 if (empty($phototoshow)) { // Show No photo link (picto of object)
3548 if ($object->element == 'action') {
3549 $width = 80;
3550 $cssclass = 'photorefcenter';
3551 $nophoto = img_picto('No photo', 'title_agenda');
3552 } else {
3553 $width = 14;
3554 $cssclass = 'photorefcenter';
3555 $picto = $object->picto; // @phan-suppress-current-line PhanUndeclaredProperty
3556 $prefix = 'object_';
3557 if ($object->element == 'project' && !$object->public) { // @phan-suppress-current-line PhanUndeclaredProperty
3558 $picto = 'project'; // instead of projectpub
3559 }
3560 if (strpos($picto, 'fontawesome_') !== false) {
3561 $prefix = '';
3562 }
3563 $nophoto = img_picto('No photo', $prefix . $picto);
3564 }
3565 $morehtmlleft .= '<!-- No photo to show -->';
3566 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref">';
3567 $morehtmlleft .= $nophoto;
3568 $morehtmlleft .= '</div></div>';
3569 }
3570 }
3571
3572 if (getDolGlobalString('MAIN_SHOW_TECHNICAL_ID') && (getDolGlobalString('MAIN_SHOW_TECHNICAL_ID') == '1' || preg_match('/' . preg_quote($object->element, '/') . '/i', getDolGlobalString('MAIN_SHOW_TECHNICAL_ID'))) && !empty($object->id)) {
3573 $morehtmlref .= '<div style="clear: both;"></div>';
3574 $morehtmlref .= '<div class="smallimp refidno opacitymedium banner-object-technical-id">';
3575 $morehtmlref .= $langs->trans("TechnicalID") . ': ' . ((int) $object->id);
3576 $morehtmlref .= '</div>';
3577 }
3578
3579
3580 // Show barcode
3581 if ($showbarcode) {
3582 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">' . $form->showbarcode($object, 100, 'photoref valignmiddle') . '</div>';
3583 }
3584
3585 if ($object->element == 'societe') {
3587 if (!empty($conf->use_javascript_ajax) && $user->hasRight('societe', 'creer') && getDolGlobalString('MAIN_DIRECT_STATUS_UPDATE')) {
3588 $morehtmlstatus .= ajax_object_onoff($object, 'status', 'status', 'InActivity', 'ActivityCeased');
3589 } else {
3590 $morehtmlstatus .= $object->getLibStatut(6);
3591 }
3592 } elseif ($object->element == 'product') {
3594 //$morehtmlstatus.=$langs->trans("Status").' ('.$langs->trans("Sell").') ';
3595 if (!empty($conf->use_javascript_ajax) && $user->hasRight('produit', 'creer') && getDolGlobalString('MAIN_DIRECT_STATUS_UPDATE')) {
3596 $morehtmlstatus .= ajax_object_onoff($object, 'status', 'status', 'ProductStatusOnSell', 'ProductStatusNotOnSell');
3597 } else {
3598 $morehtmlstatus .= '<span class="statusrefsell">' . $object->getLibStatut(6, 0) . '</span>';
3599 }
3600 $morehtmlstatus .= ' &nbsp; ';
3601 //$morehtmlstatus.=$langs->trans("Status").' ('.$langs->trans("Buy").') ';
3602 if (!empty($conf->use_javascript_ajax) && $user->hasRight('produit', 'creer') && getDolGlobalString('MAIN_DIRECT_STATUS_UPDATE')) {
3603 $morehtmlstatus .= ajax_object_onoff($object, 'status_buy', 'status_buy', 'ProductStatusOnBuy', 'ProductStatusNotOnBuy');
3604 } else {
3605 $morehtmlstatus .= '<span class="statusrefbuy">' . $object->getLibStatut(6, 1) . '</span>';
3606 }
3607 } elseif (in_array($object->element, array('salary'))) {
3609 '@phan-var-force Salary $object';
3610 $tmptxt = $object->getLibStatut(6, $object->alreadypaid);
3611 if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) {
3612 $tmptxt = $object->getLibStatut(5, $object->alreadypaid);
3613 }
3614 $morehtmlstatus .= $tmptxt;
3615 } elseif (in_array($object->element, array('facture', 'invoice', 'invoice_supplier'))) {
3617 '@phan-var-force Facture|FactureFournisseur|CommonInvoice $object';
3618 if (!isset($object->alreadypaid)) {
3619 $object->totalpaid = $object->getSommePaiement(0);
3620 $object->totalcreditnotes = $object->getSumCreditNotesUsed(0);
3621 $object->totaldeposits = $object->getSumDepositsUsed(0);
3622 $object->alreadypaid = $object->totalpaid + $object->totalcreditnotes + $object->totaldeposits;
3623 }
3624 $tmptxt = $object->getLibStatut(6, (float) $object->alreadypaid);
3625 if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) {
3626 $tmptxt = $object->getLibStatut(5, (float) $object->alreadypaid);
3627 }
3628 $morehtmlstatus .= $tmptxt;
3629 } elseif (in_array($object->element, array('chargesociales', 'loan', 'tva'))) { // TODO Move this to use ->alreadypaid like for invoices
3631 '@phan-var-force ChargeSociales|Loan|Tva $object';
3632 $tmptxt = $object->getLibStatut(6, $object->totalpaid);
3633 if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) {
3634 $tmptxt = $object->getLibStatut(5, $object->totalpaid);
3635 }
3636 $morehtmlstatus .= $tmptxt;
3637 } elseif ($object->element == 'contrat' || $object->element == 'contract') {
3639 if ($object->status == 0) {
3640 $morehtmlstatus .= $object->getLibStatut(5);
3641 } else {
3642 $morehtmlstatus .= $object->getLibStatut(4);
3643 }
3644 } elseif ($object->element == 'facturerec') {
3646 '@phan-var-force FactureRec $object';
3647 if ($object->frequency == 0) {
3648 $morehtmlstatus .= $object->getLibStatut(2);
3649 } else {
3650 $morehtmlstatus .= $object->getLibStatut(5);
3651 }
3652 } elseif ($object->element == 'project_task') {
3654 $tmptxt = $object->getLibStatut(4);
3655 $morehtmlstatus .= $tmptxt;
3656 } elseif (method_exists($object, 'getLibStatut')) { // Generic case for status
3657 $tmptxt = $object->getLibStatut(6);
3658 if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) {
3659 $tmptxt = $object->getLibStatut(5);
3660 }
3661 $morehtmlstatus .= $tmptxt;
3662 }
3663
3664 // Say if object was dispatched/transferred "into accountancy"
3665 if (isModEnabled('accounting') && in_array($object->element, array('bank', 'paiementcharge', 'facture', 'invoice', 'invoice_supplier', 'expensereport', 'payment_various'))) {
3666 // Note: For 'chargesociales', 'salaries'... this is the payments that are dispatched (so element = 'bank')
3667 if (method_exists($object, 'getVentilExportCompta')) {
3668 $accounted = $object->getVentilExportCompta(1);
3669 $langs->load("accountancy");
3670 $morehtmlstatus .= '</div><div class="statusref statusrefbis"><span class="opacitymedium">' . ($accounted > 0 ? '<a href="' . DOL_URL_ROOT . '/accountancy/bookkeeping/list.php?search_mvt_num=' . ((int) $accounted) . '">' . $langs->trans("Accounted") . '</a>' : $langs->trans("NotYetAccounted")) . '</span>';
3671 }
3672 }
3673
3674 // Add alias for thirdparty
3675 if (!empty($object->name_alias)) {
3677 '@phan-var-force Societe $object';
3678 $morehtmlref .= '<div class="refidno opacitymedium banner-object-name-alias">' . dol_escape_htmltag($object->name_alias) . '</div>';
3679 }
3680
3681 // Add label
3682 if (in_array($object->element, array('product', 'bank_account', 'project_task'))) {
3684 if (!empty($object->label)) {
3685 $morehtmlref .= '<div class="refidno banner-object-label">' . $object->label . '</div>';
3686 }
3687 }
3688 // Show address and email
3689 if (method_exists($object, 'getBannerAddress') && !in_array($object->element, array('product', 'bookmark', 'ecm_directories', 'ecm_files'))) {
3690 $moreaddress = $object->getBannerAddress('refaddress', $object); // address, email, url, social networks
3691 if ($moreaddress) {
3692 $morehtmlref .= '<div class="refidno refaddress">';
3693 $morehtmlref .= $moreaddress;
3694 $morehtmlref .= '</div>';
3695 }
3696 }
3697
3698 $parameters = array('morehtmlref' => &$morehtmlref, 'moreparam' => &$moreparam, 'morehtmlleft' => &$morehtmlleft, 'morehtmlstatus' => &$morehtmlstatus, 'morehtmlright' => &$morehtmlright);
3699 $reshook = $hookmanager->executeHooks('formDolBanner', $parameters, $object, $action);
3700 if ($reshook < 0) {
3701 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
3702 } elseif (empty($reshook)) {
3703 $morehtmlref .= $hookmanager->resPrint;
3704 } elseif ($reshook > 0) {
3705 $morehtmlref = $hookmanager->resPrint;
3706 }
3707
3708 // $morehtml is the right part (link "Back to list")
3709 // $morehtmlref is the part after the ref
3710 // $morehtmlleft is the picto or photo of banner
3711 // $morehtmlstatus is part under the status
3712 // $morehtmlright is part of htmlright
3713
3714 print '<div class="' . ($onlybanner ? 'arearefnobottom ' : 'arearef ') . 'heightref valignmiddle centpercent object-banner-tab-container" data-module-part="'.dolPrintHTMLForAttribute($modulepart).'">';
3715 print $form->showrefnav($object, $paramid, $morehtml, $shownav, $fieldid, $fieldref, $morehtmlref, $moreparam, $nodbprefix, $morehtmlleft, $morehtmlstatus, $morehtmlright);
3716 print '</div>';
3717 print '<div class="underrefbanner clearboth"></div>';
3718}
3719
3729function fieldLabel($langkey, $fieldkey, $fieldrequired = 0)
3730{
3731 global $langs;
3732 $ret = '';
3733 if ($fieldrequired) {
3734 $ret .= '<span class="fieldrequired">';
3735 }
3736 $ret .= '<label for="' . $fieldkey . '">';
3737 $ret .= $langs->trans($langkey);
3738 $ret .= '</label>';
3739 if ($fieldrequired) {
3740 $ret .= '</span>';
3741 }
3742 return $ret;
3743}
3744
3758function dol_format_address($object, $withcountry = 0, $sep = "\n", $outputlangs = null, $mode = 0, $extralangcode = '')
3759{
3760 global $langs, $hookmanager;
3761
3762 $ret = '';
3763 $countriesusingstate = array('AU', 'CA', 'US', 'IN', 'GB', 'ES', 'UK', 'TR', 'CN'); // See also MAIN_FORCE_STATE_INTO_ADDRESS
3764
3765 // See format of addresses on https://en.wikipedia.org/wiki/Address
3766 // Address
3767 if (empty($mode)) {
3768 $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)));
3769 }
3770 // Zip/Town/State
3771 if (isset($object->country_code) && in_array($object->country_code, array('AU', 'CA', 'US', 'CN')) || getDolGlobalString('MAIN_FORCE_STATE_INTO_ADDRESS')) {
3772 // US: title firstname name \n address lines \n town, state, zip \n country
3773 $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
3774 $ret .= (($ret && $town) ? $sep : '') . $town;
3775
3776 if (!empty($object->state)) {
3777 $ret .= ($ret ? ($town ? ", " : $sep) : '') . $object->state;
3778 }
3779 if (!empty($object->zip)) {
3780 $ret .= ($ret ? (($town || $object->state) ? ", " : $sep) : '') . $object->zip;
3781 }
3782 } elseif (isset($object->country_code) && in_array($object->country_code, array('GB', 'UK'))) {
3783 // UK: title firstname name \n address lines \n town state \n zip \n country
3784 $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
3785 $ret .= ($ret ? $sep : '') . $town;
3786 if (!empty($object->state)) {
3787 $ret .= ($ret ? ", " : '') . $object->state;
3788 }
3789 if (!empty($object->zip)) {
3790 $ret .= ($ret ? $sep : '') . $object->zip;
3791 }
3792 } elseif (isset($object->country_code) && in_array($object->country_code, array('ES', 'TR'))) {
3793 // ES: title firstname name \n address lines \n zip town \n state \n country
3794 $ret .= ($ret ? $sep : '') . $object->zip;
3795 $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
3796 $ret .= ($town ? (($object->zip ? ' ' : '') . $town) : '');
3797 if (!empty($object->state)) {
3798 $ret .= $sep . $object->state;
3799 }
3800 } elseif (isset($object->country_code) && in_array($object->country_code, array('JP'))) {
3801 // JP: In romaji, title firstname name\n address lines \n [state,] town zip \n country
3802 // See https://www.sljfaq.org/afaq/addresses.html
3803 $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
3804 $ret .= ($ret ? $sep : '') . ($object->state ? $object->state . ', ' : '') . $town . ($object->zip ? ' ' : '') . $object->zip;
3805 } elseif (isset($object->country_code) && in_array($object->country_code, array('IT'))) {
3806 // IT: title firstname name\n address lines \n zip town state_code \n country
3807 $ret .= ($ret ? $sep : '') . $object->zip;
3808 $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
3809 $ret .= ($town ? (($object->zip ? ' ' : '') . $town) : '');
3810 $ret .= (empty($object->state_code) ? '' : (' ' . $object->state_code));
3811 } else {
3812 // Other: title firstname name \n address lines \n zip town[, state] \n country
3813 $town = (($extralangcode && !empty($object->array_languages['address'][$extralangcode])) ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
3814 $ret .= !empty($object->zip) ? (($ret ? $sep : '') . $object->zip) : '';
3815 $ret .= ($town ? (($object->zip ? ' ' : ($ret ? $sep : '')) . $town) : '');
3816 if (!empty($object->state) && in_array($object->country_code, $countriesusingstate)) {
3817 $ret .= ($ret ? ", " : '') . $object->state;
3818 }
3819 }
3820
3821 if (!is_object($outputlangs)) {
3822 $outputlangs = $langs;
3823 }
3824 if ($withcountry) {
3825 $langs->load("dict");
3826 $ret .= (empty($object->country_code) ? '' : ($ret ? $sep : '') . $outputlangs->convToOutputCharset($outputlangs->transnoentitiesnoconv("Country" . $object->country_code)));
3827 }
3828 if ($hookmanager) {
3829 $parameters = array('withcountry' => $withcountry, 'sep' => $sep, 'outputlangs' => $outputlangs, 'mode' => $mode, 'extralangcode' => $extralangcode);
3830 $reshook = $hookmanager->executeHooks('formatAddress', $parameters, $object);
3831 if ($reshook > 0) {
3832 $ret = '';
3833 }
3834 $ret .= $hookmanager->resPrint;
3835 }
3836
3837 return $ret;
3838}
3839
3840
3841
3851function dol_strftime($fmt, $ts = false, $is_gmt = false)
3852{
3853 if ((abs($ts) <= 0x7FFFFFFF)) { // check if number in 32-bit signed range
3854 return dol_print_date($ts, $fmt, $is_gmt);
3855 } else {
3856 return 'Error date outside supported range';
3857 }
3858}
3859
3882function dol_print_date($time, $format = '', $tzoutput = 'auto', $outputlangs = null, $encodetooutput = false, $decorate = 0)
3883{
3884 global $conf, $langs;
3885
3886 // If date undefined or "", we return ""
3887 if (dol_strlen((string) $time) == 0) {
3888 return ''; // $time=0 allowed (it means 01/01/1970 00:00:00)
3889 }
3890
3891 if ($tzoutput === 'auto') {
3892 $tzoutput = (empty($conf) ? 'tzserver' : (isset($conf->tzuserinputkey) ? $conf->tzuserinputkey : 'tzserver'));
3893 }
3894
3895 // Clean parameters
3896 $to_gmt = false; // false if we want date in server timezone, true if we want to add offset
3897 $offsettz = $offsetdst = 0;
3898 if ($tzoutput) {
3899 $to_gmt = true; // For backward compatibility
3900 if (is_string($tzoutput)) {
3901 if ($tzoutput == 'tzserver') {
3902 $to_gmt = false;
3903 $offsettzstring = @date_default_timezone_get(); // Example 'Europe/Berlin' or 'Indian/Reunion'
3904 // @phan-suppress-next-line PhanPluginRedundantAssignment
3905 $offsettz = 0; // Timezone offset with server timezone (because to_gmt is false), so 0
3906 // @phan-suppress-next-line PhanPluginRedundantAssignment
3907 $offsetdst = 0; // Dst offset with server timezone (because to_gmt is false), so 0
3908 } elseif ($tzoutput == 'tzuser' || $tzoutput == 'tzuserrel') {
3909 $to_gmt = true;
3910 // if no session (by example in cron) may use MAIN_DOLIBARR_USER_TIMEZONE instead UTC
3911 $offsettzstring = (empty($_SESSION['dol_tz_string']) ? getDolGlobalString('MAIN_DOLIBARR_USER_TIMEZONE', 'UTC') : $_SESSION['dol_tz_string']); // Example 'Europe/Berlin' or 'Indian/Reunion'
3912
3913 if (class_exists('DateTimeZone')) {
3914 try {
3915 $user_date_tz = new DateTimeZone($offsettzstring);
3916 } catch (Exception $e) {
3917 // Bad value for $offsettzstring
3918 dol_syslog("DateInvalidTimeZoneException for timezone string '".$offsettzstring."'. Falling back to UTC.", LOG_ERR);
3919 $user_date_tz = new DateTimeZone('UTC'); // Force valid timezone as UTC
3920 }
3921 $user_dt = new DateTime();
3922 $user_dt->setTimezone($user_date_tz);
3923 $user_dt->setTimestamp($tzoutput == 'tzuser' ? dol_now() : (int) $time);
3924 $offsettz = $user_dt->getOffset(); // should include dst ?
3925 } else { // with old method (The 'tzuser' was processed like the 'tzuserrel')
3926 $offsettz = (empty($_SESSION['dol_tz']) ? 0 : $_SESSION['dol_tz']) * 60 * 60; // Will not be used anymore
3927 $offsetdst = (empty($_SESSION['dol_dst']) ? 0 : $_SESSION['dol_dst']) * 60 * 60; // Will not be used anymore
3928 }
3929 }
3930 }
3931 }
3932 if (!is_object($outputlangs)) {
3933 $outputlangs = $langs;
3934 }
3935 if (!$format) {
3936 $format = 'daytextshort';
3937 }
3938
3939 // Do we have to reduce the length of date (year on 2 chars) to save space.
3940 // Note: dayinputnoreduce is same than day but no reduction of year length will be done
3941 $reduceformat = (!empty($conf->dol_optimize_smallscreen) && in_array($format, array('day', 'dayhour', 'dayhoursec'))) ? 1 : 0; // Test on original $format param.
3942 $format = preg_replace('/inputnoreduce/', '', $format); // so format 'dayinputnoreduce' is processed like day
3943 $formatwithoutreduce = preg_replace('/reduceformat/', '', $format);
3944 if ($formatwithoutreduce != $format) {
3945 $format = $formatwithoutreduce;
3946 $reduceformat = 1;
3947 } // so format 'dayreduceformat' is processed like day
3948
3949 // Change predefined format into computer format. If found translation in lang file we use it, otherwise we use default.
3950 // TODO Add format daysmallyear and dayhoursmallyear
3951 if ($format == 'day') {
3952 $format = ($outputlangs->trans("FormatDateShort") != "FormatDateShort" ? $outputlangs->trans("FormatDateShort") : $conf->format_date_short);
3953 } elseif ($format == 'hour') {
3954 $format = ($outputlangs->trans("FormatHourShort") != "FormatHourShort" ? $outputlangs->trans("FormatHourShort") : $conf->format_hour_short);
3955 } elseif ($format == 'hoursec') {
3956 $s1 = $outputlangs->trans("FormatDateShort");
3957 $s2 = $outputlangs->trans("FormatDateHourSecShort");
3958 $s3 = trim(preg_replace('/'.preg_quote($s1, '/').'/', '', $s2)); // Try to guess the format for FormatHourSecShort using FormatDateShort and FormatDateHourSecShort
3959 $format = $s3;
3960 //$format = ($outputlangs->trans("FormatHourSecShort") != "FormatHourSecShort" ? $outputlangs->trans("FormatHourSecShort") : ($s3 ? $s3 : $conf->format_hour_sec_short));
3961 } elseif ($format == 'hourduration') {
3962 $format = ($outputlangs->trans("FormatHourShortDuration") != "FormatHourShortDuration" ? $outputlangs->trans("FormatHourShortDuration") : $conf->format_hour_short_duration);
3963 } elseif ($format == 'daytext') {
3964 $format = ($outputlangs->trans("FormatDateText") != "FormatDateText" ? $outputlangs->trans("FormatDateText") : $conf->format_date_text);
3965 } elseif ($format == 'daytextshort') {
3966 $format = ($outputlangs->trans("FormatDateTextShort") != "FormatDateTextShort" ? $outputlangs->trans("FormatDateTextShort") : $conf->format_date_text_short);
3967 } elseif ($format == 'dayhour') {
3968 $format = ($outputlangs->trans("FormatDateHourShort") != "FormatDateHourShort" ? $outputlangs->trans("FormatDateHourShort") : $conf->format_date_hour_short);
3969 } elseif ($format == 'dayhoursec') {
3970 $format = ($outputlangs->trans("FormatDateHourSecShort") != "FormatDateHourSecShort" ? $outputlangs->trans("FormatDateHourSecShort") : $conf->format_date_hour_sec_short);
3971 } elseif ($format == 'dayhourtext') {
3972 $format = ($outputlangs->trans("FormatDateHourText") != "FormatDateHourText" ? $outputlangs->trans("FormatDateHourText") : $conf->format_date_hour_text);
3973 } elseif ($format == 'dayhourtextshort') {
3974 $format = ($outputlangs->trans("FormatDateHourTextShort") != "FormatDateHourTextShort" ? $outputlangs->trans("FormatDateHourTextShort") : $conf->format_date_hour_text_short);
3975 } elseif ($format == 'dayhourlog') {
3976 // Format not sensitive to language
3977 $format = '%Y%m%d%H%M%S';
3978 } elseif ($format == 'dayhourlogsmall') {
3979 // Format not sensitive to language
3980 $format = '%y%m%d%H%M';
3981 } elseif ($format == 'dayhourldap') {
3982 $format = '%Y%m%d%H%M%SZ';
3983 } elseif ($format == 'dayhourxcard') {
3984 $format = '%Y%m%dT%H%M%SZ';
3985 } elseif ($format == 'dayxcard') {
3986 $format = '%Y%m%d';
3987 } elseif ($format == 'dayrfc') {
3988 $format = '%Y-%m-%d'; // DATE_RFC3339
3989 } elseif ($format == 'dayhourrfc') {
3990 $format = '%Y-%m-%dT%H:%M:%SZ'; // DATETIME RFC3339
3991 } elseif ($format == 'standard') {
3992 $format = '%Y-%m-%d %H:%M:%S';
3993 }
3994
3995 if ($reduceformat) {
3996 $format = str_replace('%Y', '%y', $format);
3997 $format = str_replace('yyyy', 'yy', $format);
3998 }
3999
4000 // Clean format
4001 if (preg_match('/%b/i', $format)) { // There is some text to translate
4002 // We inhibit translation to text made by strftime functions. We will use trans instead later.
4003 $format = str_replace('%b', '__b__', $format);
4004 $format = str_replace('%B', '__B__', $format);
4005 }
4006 if (preg_match('/%a/i', $format)) { // There is some text to translate
4007 // We inhibit translation to text made by strftime functions. We will use trans instead later.
4008 $format = str_replace('%a', '__a__', $format);
4009 $format = str_replace('%A', '__A__', $format);
4010 }
4011
4012 // Analyze date
4013 $reg = array();
4014 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
4015 dol_print_error(null, "Functions.lib::dol_print_date function called with a bad value" . getCallerInfoString());
4016 return '';
4017 } 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
4018 // This part of code should not be used anymore.
4019 dol_syslog("Functions.lib::dol_print_date function called with a bad value" . getCallerInfoString(), LOG_WARNING);
4020 // Date has format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'
4021 $syear = (!empty($reg[1]) ? $reg[1] : '');
4022 $smonth = (!empty($reg[2]) ? $reg[2] : '');
4023 $sday = (!empty($reg[3]) ? $reg[3] : '');
4024 $shour = (!empty($reg[4]) ? $reg[4] : '');
4025 $smin = (!empty($reg[5]) ? $reg[5] : '');
4026 $ssec = (!empty($reg[6]) ? $reg[6] : '');
4027
4028 $time = dol_mktime((int) $shour, (int) $smin, (int) $ssec, (int) $smonth, (int) $sday, (int) $syear, true);
4029
4030 if ($to_gmt) {
4031 $tzo = new DateTimeZone('UTC'); // when to_gmt is true, base for offsettz and offsetdst (so timetouse) is UTC
4032 } else {
4033 $tzo = new DateTimeZone(date_default_timezone_get()); // when to_gmt is false, base for offsettz and offsetdst (so timetouse) is PHP server
4034 }
4035 $dtts = new DateTime();
4036 $dtts->setTimestamp($time);
4037 $dtts->setTimezone($tzo);
4038 $newformat = str_replace(
4039 array('%Y', '%y', '%m', '%d', '%H', '%I', '%M', '%S', '%p', 'T', 'Z', '__a__', '__A__', '__b__', '__B__'),
4040 array('Y', 'y', 'm', 'd', 'H', 'h', 'i', 's', 'A', '__£__', '__$__', '__{__', '__}__', '__[__', '__]__'),
4041 $format
4042 );
4043 $ret = $dtts->format($newformat);
4044 $ret = str_replace(
4045 array('__£__', '__$__', '__{__', '__}__', '__[__', '__]__'),
4046 array('T', 'Z', '__a__', '__A__', '__b__', '__B__'),
4047 $ret
4048 );
4049 } else {
4050 // Date is a timestamps
4051 if ($time < 100000000000) { // Protection against bad date values
4052 $dtts = new DateTime();
4053 //var_dump($tzoutput.' '.$offsettzstring.' '.$offsettz.$offsetdst.' x '.$to_gmt);
4054 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
4055 $timetouse = (int) $time + $offsettz + $offsetdst; // TODO We could be able to disable use of offsettz and offsetdst to use only offsettzstring.
4056
4057 $tzo = new DateTimeZone('UTC'); // when to_gmt is true, base for offsettz and offsetdst (so timetouse) is UTC
4058 $dtts->setTimezone($tzo); // important: must be before the setTimestamp
4059 $dtts->setTimestamp($timetouse);
4060 } else {
4061 $timetouse = (int) $time + $offsettz + $offsetdst; // TODO We could be able to disable use of offsettz and offsetdst to use only offsettzstring.
4062
4063 $tzo = new DateTimeZone(date_default_timezone_get()); // when to_gmt is false, base for offsettz and offsetdst (so timetouse) is PHP server
4064 $dtts->setTimestamp($timetouse); // TODO May be we can invert setTimestamp and setTimezone
4065 $dtts->setTimezone($tzo);
4066 }
4067
4068 $newformat = str_replace(
4069 array('%Y', '%y', '%m', '%d', '%H', '%I', '%M', '%S', '%p', '%w', 'T', 'Z', '__a__', '__A__', '__b__', '__B__'),
4070 array('Y', 'y', 'm', 'd', 'H', 'h', 'i', 's', 'A', 'w', '__£__', '__$__', '__{__', '__}__', '__[__', '__]__'),
4071 $format
4072 );
4073
4074 $ret = $dtts->format($newformat);
4075 //var_dump($timetouse, $offsettz, $offsetdst, $tzo, $newformat, $ret);
4076 $ret = str_replace(
4077 array('__£__', '__$__', '__{__', '__}__', '__[__', '__]__'),
4078 array('T', 'Z', '__a__', '__A__', '__b__', '__B__'),
4079 $ret
4080 );
4081 } else {
4082 $ret = 'Bad value ' . $time . ' for date';
4083 }
4084 }
4085
4086 if (preg_match('/__b__/i', $format)) {
4087 $timetouse = $time + $offsettz + $offsetdst; // TODO We could be able to disable use of offsettz and offsetdst to use only offsettzstring.
4088
4089 if ($to_gmt) {
4090 $tzo = new DateTimeZone('UTC'); // when to_gmt is true, base for offsettz and offsetdst (so timetouse) is UTC
4091 } else {
4092 $tzo = new DateTimeZone(date_default_timezone_get()); // when to_gmt is false, base for offsettz and offsetdst (so timetouse) is PHP server
4093 }
4094 $dtts = new DateTime();
4095 $dtts->setTimestamp($timetouse);
4096 $dtts->setTimezone($tzo);
4097 $month = (int) $dtts->format("m");
4098 $month = sprintf("%02d", $month); // $month may be return with format '06' on some installation and '6' on other, so we force it to '06'.
4099 if ($encodetooutput) {
4100 $monthtext = $outputlangs->transnoentities('Month' . $month);
4101 $monthtextshort = $outputlangs->transnoentities('MonthShort' . $month);
4102 } else {
4103 $monthtext = $outputlangs->transnoentitiesnoconv('Month' . $month);
4104 $monthtextshort = $outputlangs->transnoentitiesnoconv('MonthShort' . $month);
4105 }
4106 //print 'monthtext='.$monthtext.' monthtextshort='.$monthtextshort;
4107 $ret = str_replace('__b__', $monthtextshort, $ret);
4108 $ret = str_replace('__B__', $monthtext, $ret);
4109 //print 'x'.$outputlangs->charset_output.'-'.$ret.'x';
4110 //return $ret;
4111 }
4112 if (preg_match('/__a__/i', $format)) {
4113 //print "time=$time offsettz=$offsettz offsetdst=$offsetdst offsettzstring=$offsettzstring";
4114 $timetouse = $time + $offsettz + $offsetdst; // TODO Replace this with function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
4115
4116 if ($to_gmt) {
4117 $tzo = new DateTimeZone('UTC');
4118 } else {
4119 $tzo = new DateTimeZone(date_default_timezone_get());
4120 }
4121 $dtts = new DateTime();
4122 $dtts->setTimestamp($timetouse);
4123 $dtts->setTimezone($tzo);
4124 $w = $dtts->format("w");
4125 $dayweek = $outputlangs->transnoentitiesnoconv('Day' . $w);
4126
4127 $ret = str_replace('__A__', $dayweek, $ret);
4128 $ret = str_replace('__a__', dol_substr($dayweek, 0, 3), $ret);
4129 }
4130
4131 if ($decorate) {
4132 $ret = preg_replace('/(\d\d:\d\d [AP]M)$/', '<span class="'.($decorate === 1 ? 'opacitymedium' : $decorate).'">\1</span>', $ret);
4133 $ret = preg_replace('/(\d\d:\d\d)$/', '<span class="'.($decorate === 1 ? 'opacitymedium' : $decorate).'">\1</span>', $ret);
4134 }
4135
4136 return $ret;
4137}
4138
4139
4160function dol_getdate($timestamp, $fast = false, $forcetimezone = '')
4161{
4162 if ($timestamp === '') {
4163 return array();
4164 }
4165
4166 $datetimeobj = new DateTime();
4167 $datetimeobj->setTimestamp($timestamp); // Use local PHP server timezone
4168 if ($forcetimezone) {
4169 $datetimeobj->setTimezone(new DateTimeZone($forcetimezone == 'gmt' ? 'UTC' : $forcetimezone)); // (add timezone relative to the date entered)
4170 }
4171 $arrayinfo = array(
4172 'year' => ((int) date_format($datetimeobj, 'Y')),
4173 'mon' => ((int) date_format($datetimeobj, 'm')),
4174 'mday' => ((int) date_format($datetimeobj, 'd')),
4175 'wday' => ((int) date_format($datetimeobj, 'w')),
4176 'yday' => ((int) date_format($datetimeobj, 'z')),
4177 'hours' => ((int) date_format($datetimeobj, 'H')),
4178 'minutes' => ((int) date_format($datetimeobj, 'i')),
4179 'seconds' => ((int) date_format($datetimeobj, 's')),
4180 '0' => $timestamp
4181 );
4182
4183 return $arrayinfo;
4184}
4185
4207function dol_mktime($hour, $minute, $second, $month, $day, $year, $gm = 'auto', $check = 1)
4208{
4209 global $conf;
4210 //print "- ".$hour.",".$minute.",".$second.",".$month.",".$day.",".$year.",".$_SERVER["WINDIR"]." -";
4211
4212 if ($gm === 'auto') {
4213 $gm = (empty($conf) ? 'tzserver' : $conf->tzuserinputkey);
4214 }
4215 //print 'gm:'.$gm.' gm === auto:'.($gm === 'auto').'<br>';exit;
4216
4217 // Clean parameters
4218 if ($hour == -1 || empty($hour)) {
4219 $hour = 0;
4220 }
4221 if ($minute == -1 || empty($minute)) {
4222 $minute = 0;
4223 }
4224 if ($second == -1 || empty($second)) {
4225 $second = 0;
4226 }
4227
4228 // Check parameters
4229 if ($check) {
4230 if (!$month || !$day) {
4231 return '';
4232 }
4233 if ($day > 31) {
4234 return '';
4235 }
4236 if ($month > 12) {
4237 return '';
4238 }
4239 if ($hour < 0 || $hour > 24) {
4240 return '';
4241 }
4242 if ($minute < 0 || $minute > 60) {
4243 return '';
4244 }
4245 if ($second < 0 || $second > 60) {
4246 return '';
4247 }
4248 }
4249
4250 if (empty($gm) || ($gm === 'server' || $gm === 'tzserver')) {
4251 $default_timezone = @date_default_timezone_get(); // Example 'Europe/Berlin'
4252 $localtz = new DateTimeZone($default_timezone);
4253 } elseif ($gm === 'user' || $gm === 'tzuser' || $gm === 'tzuserrel') {
4254 // We use dol_tz_string first because it is more reliable.
4255 $default_timezone = (empty($_SESSION["dol_tz_string"]) ? @date_default_timezone_get() : $_SESSION["dol_tz_string"]); // Example 'Europe/Berlin'
4256 try {
4257 $localtz = new DateTimeZone($default_timezone);
4258 } catch (Exception $e) {
4259 dol_syslog("Warning dol_tz_string contains an invalid value " . json_encode($_SESSION["dol_tz_string"] ?? null), LOG_WARNING);
4260 $default_timezone = @date_default_timezone_get();
4261 }
4262 } elseif (strrpos($gm, "tz,") !== false) {
4263 $timezone = (string) str_replace("tz,", "", $gm); // Example 'tz,Europe/Berlin'
4264 try {
4265 $localtz = new DateTimeZone($timezone);
4266 } catch (Exception $e) {
4267 dol_syslog("Warning passed timezone contains an invalid value " . $timezone, LOG_WARNING);
4268 }
4269 }
4270
4271 if (empty($localtz)) {
4272 $localtz = new DateTimeZone('UTC');
4273 }
4274 $dt = new DateTime('now', $localtz);
4275 $dt->setDate((int) $year, (int) $month, (int) $day);
4276 $dt->setTime((int) $hour, (int) $minute, (int) $second);
4277 $date = $dt->getTimestamp(); // should include daylight saving time
4278
4279 return $date;
4280}
4281
4282
4293function dol_now($mode = 'gmt')
4294{
4295 $ret = 0;
4296
4297 if ($mode === 'auto') {
4298 $mode = 'gmt';
4299 }
4300
4301 if ($mode == 'gmt') {
4302 $ret = time(); // Time for now at greenwich.
4303 } elseif ($mode == 'tzserver') { // Time for now with PHP server timezone added
4304 require_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php';
4305 $tzsecond = getServerTimeZoneInt('now'); // Contains tz+dayling saving time
4306 $ret = (int) (dol_now('gmt') + ($tzsecond * 3600));
4307 // } elseif ($mode == 'tzref') {// Time for now with parent company timezone is added
4308 // require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
4309 // $tzsecond=getParentCompanyTimeZoneInt(); // Contains tz+dayling saving time
4310 // $ret=dol_now('gmt')+($tzsecond*3600);
4311 } elseif ($mode == 'tzuser' || $mode == 'tzuserrel') {
4312 // Time for now with user timezone added
4313 // print 'time: '.time();
4314 $offsettz = (empty($_SESSION['dol_tz']) ? 0 : $_SESSION['dol_tz']) * 60 * 60;
4315 $offsetdst = (empty($_SESSION['dol_dst']) ? 0 : $_SESSION['dol_dst']) * 60 * 60;
4316 $ret = (int) (dol_now('gmt') + ($offsettz + $offsetdst));
4317 }
4318
4319 return $ret;
4320}
4321
4322
4331function dol_print_size($size, $shortvalue = 0, $shortunit = 0)
4332{
4333 global $conf, $langs;
4334 $level = 1024;
4335
4336 if (!empty($conf->dol_optimize_smallscreen)) {
4337 $shortunit = 1;
4338 }
4339
4340 // Set value text
4341 if (empty($shortvalue) || $size < ($level * 10)) {
4342 $ret = $size;
4343 $textunitshort = $langs->trans("b");
4344 $textunitlong = $langs->trans("Bytes");
4345 } else {
4346 $ret = round($size / $level, 0);
4347 $textunitshort = $langs->trans("Kb");
4348 $textunitlong = $langs->trans("KiloBytes");
4349 }
4350 // Use long or short text unit
4351 if (empty($shortunit)) {
4352 $ret .= ' ' . $textunitlong;
4353 } else {
4354 $ret .= ' ' . $textunitshort;
4355 }
4356
4357 return $ret;
4358}
4359
4370function dol_print_url($url, $target = '_blank', $max = 32, $withpicto = 0, $morecss = '')
4371{
4372 global $langs;
4373
4374 if (empty($url)) {
4375 return '';
4376 }
4377
4378 $linkstart = '<a href="';
4379 if (!preg_match('/^http/i', $url)) {
4380 $linkstart .= 'http://';
4381 }
4382 $linkstart .= $url;
4383 $linkstart .= '"';
4384 if ($target) {
4385 $linkstart .= ' target="' . $target . '"';
4386 }
4387 $linkstart .= ' title="' . $langs->trans("URL") . ': ' . $url . '"';
4388 $linkstart .= '>';
4389
4390 $link = '';
4391 if (!preg_match('/^http/i', $url)) {
4392 $link .= 'http://';
4393 }
4394 $link .= dol_trunc($url, $max);
4395
4396 $linkend = '</a>';
4397
4398 if ($morecss == 'float') { // deprecated
4399 return '<div class="nospan' . ($morecss ? ' ' . $morecss : '') . '" style="margin-right: 10px">' . ($withpicto ? img_picto($langs->trans("Url"), 'globe', 'class="paddingrightonly"') : '') . $link . '</div>';
4400 } else {
4401 return $linkstart . '<span class="nospan' . ($morecss ? ' ' . $morecss : '') . '" style="margin-right: 10px">' . ($withpicto ? img_picto('', 'globe', 'class="paddingrightonly"') : '') . $link . '</span>' . $linkend;
4402 }
4403}
4404
4418function dol_print_email($email, $contactid = 0, $socid = 0, $addlink = 0, $max = 0, $showinvalid = 2, $withpicto = 0, $morecss = 'paddingrightonly')
4419{
4420 global $user, $langs, $hookmanager;
4421
4422 //global $conf; $conf->global->AGENDA_ADDACTIONFOREMAIL = 1;
4423 //$showinvalid = 1; $email = 'rrrrr';
4424
4425 $newemail = dol_escape_htmltag($email);
4426
4427 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER') && $withpicto) {
4428 $withpicto = 0;
4429 }
4430
4431 if (empty($email)) {
4432 return '&nbsp;';
4433 }
4434
4435 if ($addlink == 1) {
4436 $newemail = '<a class="' . ($morecss ? $morecss : '') . '" style="text-overflow: ellipsis;" href="';
4437 if (!preg_match('/^mailto:/i', $email)) {
4438 $newemail .= 'mailto:';
4439 }
4440 $newemail .= $email;
4441 $newemail .= '" target="_blank">';
4442
4443 $newemail .= ($withpicto ? img_picto($langs->trans("EMail") . ' : ' . $email, (is_numeric($withpicto) ? 'email' : $withpicto), 'class="paddingrightonly"') : '');
4444
4445 if ($max > 0) {
4446 $newemail .= dol_trunc($email, $max);
4447 } else {
4448 $newemail .= $email;
4449 }
4450 $newemail .= '</a>';
4451
4452 if ($showinvalid) {
4453 include_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
4454 include_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
4455 $emailonly = CMailFile::getValidAddress($email, 2);
4456 if (!isValidEmail($emailonly)) {
4457 $langs->load("errors");
4458 $newemail .= img_warning($langs->transnoentitiesnoconv("ErrorBadEMail", $emailonly), '', 'paddingrightonly');
4459 } elseif ($showinvalid == 2 && !isValidMailDomain($emailonly)) {
4460 $langs->load("errors");
4461 $newemail .= img_warning($langs->transnoentitiesnoconv("ErrorBadMXDomain", $emailonly), '', 'paddingrightonly');
4462 }
4463 }
4464
4465 if (($contactid || $socid) && isModEnabled('agenda') && $user->hasRight("agenda", "myactions", "create")) {
4466 $type = 'AC_EMAIL';
4467 $linktoaddaction = '';
4468 if (getDolGlobalString('AGENDA_ADDACTIONFOREMAIL')) {
4469 $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>';
4470 }
4471 if ($linktoaddaction) {
4472 $newemail = '<div>' . $newemail . ' ' . $linktoaddaction . '</div>';
4473 }
4474 }
4475 } elseif ($addlink === 'thirdparty') {
4476 $tmpnewemail = '<a class="' . ($morecss ? $morecss : '') . '" style="text-overflow: ellipsis;" href="' . DOL_URL_ROOT . '/societe/card.php?socid=' . $socid . '&action=presend&mode=init#formmailbeforetitle">';
4477 $tmpnewemail .= ($withpicto ? img_picto($langs->trans("EMail") . ' : ' . $email, (is_numeric($withpicto) ? 'email' : $withpicto), 'class="paddingrightonly"') : '');
4478 if ($withpicto == 1) {
4479 $tmpnewemail .= $newemail;
4480 }
4481 $tmpnewemail .= '</a>';
4482
4483 $newemail = $tmpnewemail;
4484 } else {
4485 $newemail = ($withpicto ? img_picto($langs->trans("EMail") . ' : ' . $email, (is_numeric($withpicto) ? 'email' : $withpicto), 'class="paddingrightonly"') : '') . $newemail;
4486
4487 if ($showinvalid) {
4488 include_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
4489 include_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
4490 $emailonly = CMailFile::getValidAddress($email, 2);
4491 if (!isValidEmail($emailonly)) {
4492 $langs->load("errors");
4493 $newemail .= img_warning($langs->transnoentitiesnoconv("ErrorBadEMail", $email));
4494 } elseif ($showinvalid == 2 && !isValidMailDomain($emailonly)) {
4495 $langs->load("errors");
4496 $newemail .= img_warning($langs->transnoentitiesnoconv("ErrorBadMXDomain", $emailonly));
4497 }
4498 }
4499 }
4500
4501 //$rep = '<div class="nospan" style="margin-right: 10px">';
4502 //$rep = ($withpicto ? img_picto($langs->trans("EMail").' : '.$email, (is_numeric($withpicto) ? 'email' : $withpicto), 'class="paddingrightonly"') : '').$newemail;
4503 //$rep .= '</div>';
4504 $rep = $newemail;
4505 if (getDolGlobalString('MAIN_MAIL_COPY_ON_CLICK')) {
4506 $rep .= showValueWithClipboardCPButton($newemail, 0, 'none');
4507 }
4508
4509 if ($hookmanager) {
4510 $parameters = array('cid' => $contactid, 'socid' => $socid, 'addlink' => $addlink, 'picto' => $withpicto);
4511
4512 $reshook = $hookmanager->executeHooks('printEmail', $parameters, $email);
4513 if ($reshook > 0) {
4514 $rep = '';
4515 }
4516 $rep .= $hookmanager->resPrint;
4517 }
4518
4519 return $rep;
4520}
4521
4522
4538function dolOutputDates($datep, $datef = null, $fullday = 0, $addseconds = 0, $pictotoadd = '', $tzoutput = 'tzuserrel', $reduceformat = 0)
4539{
4540 $tmpa = dol_getdate($datep);
4541 if (empty($datef)) {
4542 $tmpb = $tmpa;
4543 } else {
4544 $tmpb = dol_getdate($datef);
4545 }
4546
4547 $s = '';
4548
4549 if ($tmpa['mday'] == $tmpb['mday'] && $tmpa['mon'] == $tmpb['mon'] && $tmpa['year'] == $tmpb['year']) {
4550 // The same day
4551 $s .= '<div class="center inline-block">';
4552 if ($tmpa['hours'] != $tmpb['hours'] || $tmpa['minutes'] != $tmpb['minutes']) {
4553 // Not the same hour
4554 $s .= dol_print_date($datep, 'day'.($reduceformat ? 'reduceformat' : ''), $tzoutput);
4555 $s .= $pictotoadd;
4556 if (empty($fullday)) {
4557 $s .= '<br><span class="small opacitymedium">';
4558 $s .= dol_print_date($datep, 'hour'.($addseconds ? 'sec' : '').'reduceformat', $tzoutput);
4559 $s .= '-'.dol_print_date($datef, 'hour'.($addseconds ? 'sec' : '').'reduceformat', $tzoutput);
4560 $s .= '</span>';
4561 }
4562 } else {
4563 // The same hour
4564 $s .= dol_print_date($datep, 'day'.($reduceformat ? 'reduceformat' : ''), 'tzuserrel');
4565 $s .= $pictotoadd;
4566 if (empty($fullday)) {
4567 $s .= '<br><span class="small opacitymedium">';
4568 $s .= dol_print_date($datep, 'hour'.($addseconds ? 'sec' : '').'reduceformat', $tzoutput);
4569 $s .= '</span>';
4570 }
4571 }
4572 $s .= '</div>';
4573 } else {
4574 // Not the same day
4575 $s .= '<div class="center inline-block dateborderright">';
4576 $s .= dol_print_date($datep, 'day'.($reduceformat ? 'reduceformat' : ''), $tzoutput);
4577 if (empty($fullday)) {
4578 $s .= '<br><span class="small opacitymedium">';
4579 $s .= dol_print_date($datep, 'hour'.($addseconds ? 'sec' : '').'reduceformat', $tzoutput);
4580 $s .= '</span>';
4581 }
4582 $s .= '</div>';
4583 $s .= '<div class="center inline-block dateborderleft">';
4584 $s .= dol_print_date($datef, 'day'.($reduceformat ? 'reduceformat' : ''), 'tzuserrel');
4585 $s .= $pictotoadd;
4586 if (empty($fullday)) {
4587 $s .= '<br><span class="small opacitymedium">';
4588 $s .= dol_print_date($datef, 'hour'.($addseconds ? 'sec' : '').'reduceformat', $tzoutput);
4589 $s .= '</span>';
4590 }
4591 $s .= '</div>';
4592 }
4593
4594 return $s;
4595}
4596
4597
4603function getArrayOfSocialNetworks()
4604{
4605 global $db;
4606
4607 $socialnetworks = array();
4608 // Enable caching of array
4609 require_once DOL_DOCUMENT_ROOT . '/core/lib/memory.lib.php';
4610 $cachekey = dol_sanitizeKeyCode(str_replace(',', '_', 'socialnetworks_'.getEntity('c_socialnetworks')));
4611 $dataretrieved = dol_getcache($cachekey);
4612
4613 if (!is_null($dataretrieved)) {
4614 $socialnetworks = $dataretrieved;
4615 } else {
4616 $sql = "SELECT rowid, code, label, url, icon, active FROM " . MAIN_DB_PREFIX . "c_socialnetworks";
4617 $sql .= " WHERE entity IN (" . getEntity('c_socialnetworks').")";
4618
4619 $resql = $db->query($sql);
4620 if ($resql) {
4621 while ($obj = $db->fetch_object($resql)) {
4622 $socialnetworks[$obj->code] = array(
4623 'rowid' => $obj->rowid,
4624 'label' => $obj->label,
4625 'url' => $obj->url,
4626 'icon' => $obj->icon,
4627 'active' => $obj->active,
4628 );
4629 }
4630 }
4631 dol_setcache($cachekey, $socialnetworks); // If setting cache fails, this is not a problem, so we do not test result.
4632 }
4633
4634 return (is_array($socialnetworks) ? $socialnetworks : array());
4635}
4636
4647function dol_print_socialnetworks($value, $contactid, $socid, $type, $dictsocialnetworks = array())
4648{
4649 global $hookmanager, $langs, $user;
4650
4651 $htmllink = $value;
4652
4653 if (empty($value)) {
4654 return '&nbsp;';
4655 }
4656
4657 if (!empty($type)) {
4658 $htmllink = '<div class="divsocialnetwork inline-block valignmiddle">';
4659 // Use dictionary definition for picto $dictsocialnetworks[$type]['icon']
4660 $htmllink .= '<span class="fab pictofixedwidth ' . ($dictsocialnetworks[$type]['icon'] ? $dictsocialnetworks[$type]['icon'] : 'fa-link') . '"></span>';
4661 if ($type == 'skype') {
4662 $htmllink .= dol_escape_htmltag($value);
4663 $htmllink .= '&nbsp; <a href="skype:';
4664 $htmllink .= dol_string_nospecial($value, '_', '', array('@'));
4665 $htmllink .= '?call" alt="' . $langs->trans("Call") . '&nbsp;' . $value . '" title="' . dol_escape_htmltag($langs->trans("Call") . ' ' . $value) . '">';
4666 $htmllink .= '<img src="' . DOL_URL_ROOT . '/theme/common/skype_callbutton.png" border="0">';
4667 $htmllink .= '</a><a href="skype:';
4668 $htmllink .= dol_string_nospecial($value, '_', '', array('@'));
4669 $htmllink .= '?chat" alt="' . $langs->trans("Chat") . '&nbsp;' . $value . '" title="' . dol_escape_htmltag($langs->trans("Chat") . ' ' . $value) . '">';
4670 $htmllink .= '<img class="paddingleft" src="' . DOL_URL_ROOT . '/theme/common/skype_chatbutton.png" border="0">';
4671 $htmllink .= '</a>';
4672 if (($contactid || $socid) && isModEnabled('agenda') && $user->hasRight('agenda', 'myactions', 'create')) {
4673 $addlink = 'AC_SKYPE';
4674 $link = '';
4675 if (getDolGlobalString('AGENDA_ADDACTIONFORSKYPE')) {
4676 $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>';
4677 }
4678 $htmllink .= ($link ? ' ' . $link : '');
4679 }
4680 } else {
4681 if (!empty($dictsocialnetworks[$type]['url'])) {
4682 $tmpvirginurl = preg_replace('/\/?{socialid}/', '', $dictsocialnetworks[$type]['url']);
4683 if ($tmpvirginurl) {
4684 $value = preg_replace('/^www\.' . preg_quote($tmpvirginurl, '/') . '\/?/', '', $value);
4685 $value = preg_replace('/^' . preg_quote($tmpvirginurl, '/') . '\/?/', '', $value);
4686
4687 $tmpvirginurl3 = preg_replace('/^https:\/\//i', 'https://www.', $tmpvirginurl);
4688 if ($tmpvirginurl3) {
4689 $value = preg_replace('/^www\.' . preg_quote($tmpvirginurl3, '/') . '\/?/', '', $value);
4690 $value = preg_replace('/^' . preg_quote($tmpvirginurl3, '/') . '\/?/', '', $value);
4691 }
4692
4693 $tmpvirginurl2 = preg_replace('/^https?:\/\//i', '', $tmpvirginurl);
4694 if ($tmpvirginurl2) {
4695 $value = preg_replace('/^www\.' . preg_quote($tmpvirginurl2, '/') . '\/?/', '', $value);
4696 $value = preg_replace('/^' . preg_quote($tmpvirginurl2, '/') . '\/?/', '', $value);
4697 }
4698 }
4699 if (preg_match('/^https?:\/\//i', $value)) {
4700 $link = $value;
4701 } else {
4702 $link = str_replace('{socialid}', $value, $dictsocialnetworks[$type]['url']);
4703 }
4704 $valuetoshow = $value;
4705 $valuetoshow = preg_replace('/https:\/\/www\.(twitter|x|linkedin)\.com\/?/', '', $valuetoshow);
4706 if (preg_match('/^https?:\/\//i', $link)) {
4707 $htmllink .= '<a href="' . dol_sanitizeUrl($link, 0) . '" target="_blank" rel="noopener noreferrer">' . dol_escape_htmltag($valuetoshow) . '</a>';
4708 } else {
4709 $htmllink .= '<a href="' . dol_sanitizeUrl($link, 1) . '" target="_blank" rel="noopener noreferrer">' . dol_escape_htmltag($valuetoshow) . '</a>';
4710 }
4711 } else {
4712 $htmllink .= dol_escape_htmltag($value);
4713 }
4714 }
4715 $htmllink .= '</div>';
4716 } else {
4717 $langs->load("errors");
4718 $htmllink .= img_warning($langs->trans("ErrorBadSocialNetworkValue", $value));
4719 }
4720
4721 if ($hookmanager) {
4722 $parameters = array(
4723 'value' => $value,
4724 'cid' => $contactid,
4725 'socid' => $socid,
4726 'type' => $type,
4727 'dictsocialnetworks' => $dictsocialnetworks,
4728 );
4729
4730 $reshook = $hookmanager->executeHooks('printSocialNetworks', $parameters);
4731 if ($reshook > 0) {
4732 $htmllink = '';
4733 }
4734 $htmllink .= $hookmanager->resPrint;
4735 }
4736
4737 return $htmllink;
4738}
4739
4749function dol_print_profids($profID, $profIDtype, $countrycode = '', $addcpButton = 1)
4750{
4751 global $mysoc;
4752
4753 if (empty($profID) || empty($profIDtype)) {
4754 return '';
4755 }
4756 if (empty($countrycode)) {
4757 $countrycode = $mysoc->country_code;
4758 }
4759 $newProfID = $profID;
4760 $id = substr($profIDtype, -1);
4761 $ret = '';
4762 if (strtoupper($countrycode) == 'FR') {
4763 // France
4764 // (see https://www.economie.gouv.fr/entreprises/numeros-identification-entreprise)
4765
4766 if ($id == 1 && dol_strlen($newProfID) == 9) {
4767 // SIREN (ex: 123 123 123)
4768 $newProfID = substr($newProfID, 0, 3) . ' ' . substr($newProfID, 3, 3) . ' ' . substr($newProfID, 6, 3);
4769 }
4770 if ($id == 2 && dol_strlen($newProfID) == 14) {
4771 // SIRET (ex: 123 123 123 12345)
4772 $newProfID = substr($newProfID, 0, 3) . ' ' . substr($newProfID, 3, 3) . ' ' . substr($newProfID, 6, 3) . ' ' . substr($newProfID, 9, 5);
4773 }
4774 if ($id == 3 && dol_strlen($newProfID) == 5) {
4775 // NAF/APE (ex: 69.20Z)
4776 $newProfID = substr($newProfID, 0, 2) . '.' . substr($newProfID, 2, 3);
4777 }
4778 if ($profIDtype === 'VAT' && dol_strlen($newProfID) == 13) {
4779 // TVA intracommunautaire (ex: FR12 123 123 123)
4780 $newProfID = substr($newProfID, 0, 4) . ' ' . substr($newProfID, 4, 3) . ' ' . substr($newProfID, 7, 3) . ' ' . substr($newProfID, 10, 3);
4781 }
4782 }
4783 if (!empty($addcpButton)) {
4784 $ret = showValueWithClipboardCPButton(dol_escape_htmltag($profID), ($addcpButton == 1 ? 1 : 0), $newProfID);
4785 } else {
4786 $ret = $newProfID;
4787 }
4788 return $ret;
4789}
4790
4806function dol_print_phone($phone, $countrycode = '', $contactid = 0, $socid = 0, $addlink = '', $separ = "&nbsp;", $withpicto = '', $titlealt = '', $adddivfloat = 0, $morecss = 'paddingright')
4807{
4808 global $conf, $user, $langs, $mysoc, $hookmanager;
4809
4810 // Clean phone parameter
4811 $phone = is_null($phone) ? '' : preg_replace("/[\s.-]/", "", trim($phone));
4812 if (empty($phone)) {
4813 return '';
4814 }
4815 if (getDolGlobalString('MAIN_PHONE_SEPAR')) {
4816 $separ = getDolGlobalString('MAIN_PHONE_SEPAR');
4817 }
4818 if (empty($countrycode) && is_object($mysoc)) {
4819 $countrycode = $mysoc->country_code;
4820 }
4821
4822 // Short format for small screens
4823 if (!empty($conf->dol_optimize_smallscreen) && $separ != 'hidenum') {
4824 $separ = '';
4825 }
4826
4827 $newphone = $phone;
4828 $newphonewa = $phone;
4829 if (strtoupper($countrycode) == "FR") {
4830 // France
4831 if (dol_strlen($phone) == 10) {
4832 $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);
4833 } elseif (dol_strlen($phone) == 7) {
4834 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 2);
4835 } elseif (dol_strlen($phone) == 9) {
4836 $newphone = substr($newphone, 0, 2) . $separ . substr($newphone, 2, 3) . $separ . substr($newphone, 5, 2) . $separ . substr($newphone, 7, 2);
4837 } elseif (dol_strlen($phone) == 11) {
4838 $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);
4839 } elseif (dol_strlen($phone) == 12) {
4840 $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);
4841 } elseif (dol_strlen($phone) == 13) {
4842 $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);
4843 }
4844 } elseif (strtoupper($countrycode) == "CA") {
4845 if (dol_strlen($phone) == 10) {
4846 $newphone = ($separ != '' ? '(' : '') . substr($newphone, 0, 3) . ($separ != '' ? ')' : '') . $separ . substr($newphone, 3, 3) . ($separ != '' ? '-' : '') . substr($newphone, 6, 4);
4847 }
4848 } elseif (strtoupper($countrycode) == "PT") { //Portugal
4849 if (dol_strlen($phone) == 13) { //ex: +351_ABC_DEF_GHI
4850 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 3);
4851 }
4852 } elseif (strtoupper($countrycode) == "SR") { //Suriname
4853 if (dol_strlen($phone) == 10) { //ex: +597_ABC_DEF
4854 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3);
4855 } elseif (dol_strlen($phone) == 11) { //ex: +597_ABC_DEFG
4856 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 4);
4857 }
4858 } elseif (strtoupper($countrycode) == "DE") { //Deutschland
4859 if (dol_strlen($phone) == 14) { //ex: +49_ABCD_EFGH_IJK
4860 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 4) . $separ . substr($newphone, 7, 4) . $separ . substr($newphone, 11, 3);
4861 } elseif (dol_strlen($phone) == 13) { //ex: +49_ABC_DEFG_HIJ
4862 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 4) . $separ . substr($newphone, 10, 3);
4863 }
4864 } elseif (strtoupper($countrycode) == "ES") { //Spain
4865 if (dol_strlen($phone) == 12) { //ex: +34_ABC_DEF_GHI
4866 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
4867 }
4868 } elseif (strtoupper($countrycode) == "BF") { // Burkina Faso
4869 if (dol_strlen($phone) == 12) { //ex : +22 A BC_DE_FG_HI
4870 $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);
4871 }
4872 } elseif (strtoupper($countrycode) == "RO") { // Roumanie
4873 if (dol_strlen($phone) == 12) { //ex : +40 AB_CDE_FG_HI
4874 $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);
4875 }
4876 } elseif (strtoupper($countrycode) == "TR") { //Turquie
4877 if (dol_strlen($phone) == 13) { //ex : +90 ABC_DEF_GHIJ
4878 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 4);
4879 }
4880 } elseif (strtoupper($countrycode) == "US") { //Etat-Unis
4881 if (dol_strlen($phone) == 12) { //ex: +1 ABC_DEF_GHIJ
4882 $newphone = substr($newphone, 0, 2) . $separ . substr($newphone, 2, 3) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 4);
4883 }
4884 } elseif (strtoupper($countrycode) == "MX") { //Mexique
4885 if (dol_strlen($phone) == 12) { //ex: +52 ABCD_EFG_HI
4886 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 4) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 2);
4887 } elseif (dol_strlen($phone) == 11) { //ex: +52 AB_CD_EF_GH
4888 $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);
4889 } elseif (dol_strlen($phone) == 13) { //ex: +52 ABC_DEF_GHIJ
4890 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 4);
4891 }
4892 } elseif (strtoupper($countrycode) == "ML") { //Mali
4893 if (dol_strlen($phone) == 12) { //ex: +223 AB_CD_EF_GH
4894 $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);
4895 }
4896 } elseif (strtoupper($countrycode) == "TH") { //Thailand
4897 if (dol_strlen($phone) == 11) { //ex: +66_ABC_DE_FGH
4898 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 3);
4899 } elseif (dol_strlen($phone) == 12) { //ex: +66_A_BCD_EF_GHI
4900 $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);
4901 }
4902 } elseif (strtoupper($countrycode) == "MU") {
4903 //Maurice
4904 if (dol_strlen($phone) == 11) { //ex: +230_ABC_DE_FG
4905 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2);
4906 } elseif (dol_strlen($phone) == 12) { //ex: +230_ABCD_EF_GH
4907 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 4) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
4908 }
4909 } elseif (strtoupper($countrycode) == "ZA") { //Afrique du sud
4910 if (dol_strlen($phone) == 12) { //ex: +27_AB_CDE_FG_HI
4911 $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);
4912 }
4913 } elseif (strtoupper($countrycode) == "SY") { //Syrie
4914 if (dol_strlen($phone) == 12) { //ex: +963_AB_CD_EF_GH
4915 $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);
4916 } elseif (dol_strlen($phone) == 13) { //ex: +963_AB_CD_EF_GHI
4917 $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);
4918 }
4919 } elseif (strtoupper($countrycode) == "AE") { //Emirats Arabes Unis
4920 if (dol_strlen($phone) == 12) { //ex: +971_ABC_DEF_GH
4921 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 2);
4922 } elseif (dol_strlen($phone) == 13) { //ex: +971_ABC_DEF_GHI
4923 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 3);
4924 } elseif (dol_strlen($phone) == 14) { //ex: +971_ABC_DEF_GHIK
4925 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 4);
4926 }
4927 } elseif (strtoupper($countrycode) == "DZ") { //Algeria
4928 if (dol_strlen($phone) == 13) { //ex: +213_ABC_DEF_GHI
4929 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 3);
4930 }
4931 } elseif (strtoupper($countrycode) == "BE") { //Belgique
4932 if (dol_strlen($phone) == 11) { //ex: +32_ABC_DE_FGH
4933 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 3);
4934 } elseif (dol_strlen($phone) == 12) { //ex: +32_ABC_DEF_GHI
4935 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
4936 }
4937 } elseif (strtoupper($countrycode) == "PF") { //French Polynesia
4938 if (dol_strlen($phone) == 12) { //ex: +689_AB_CD_EF_GH
4939 $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);
4940 }
4941 } elseif (strtoupper($countrycode) == "CO") { //Colombie
4942 if (dol_strlen($phone) == 13) { //ex: +57_ABC_DEF_GH_IJ
4943 $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);
4944 }
4945 } elseif (strtoupper($countrycode) == "JO") { //Jordanie
4946 if (dol_strlen($phone) == 12) { //ex: +962_A_BCD_EF_GH
4947 $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);
4948 }
4949 } elseif (strtoupper($countrycode) == "JM") { //Jamaica
4950 if (dol_strlen($newphone) == 12) { //ex: +1867_ABC_DEFG
4951 $newphone = substr($newphone, 0, 5) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 4);
4952 }
4953 } elseif (strtoupper($countrycode) == "MG") { //Madagascar
4954 if (dol_strlen($phone) == 13) { //ex: +261_AB_CD_EFG_HI
4955 $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);
4956 }
4957 } elseif (strtoupper($countrycode) == "GB") { //Royaume uni
4958 if (dol_strlen($phone) == 13) { //ex: +44_ABCD_EFG_HIJ
4959 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 4) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 3);
4960 }
4961 } elseif (strtoupper($countrycode) == "CH") { //Suisse
4962 if (dol_strlen($phone) == 12) { //ex: +41_AB_CDE_FG_HI
4963 $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);
4964 } elseif (dol_strlen($phone) == 15) { // +41_AB_CDE_FGH_IJKL
4965 $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);
4966 }
4967 } elseif (strtoupper($countrycode) == "TN") { //Tunisie
4968 if (dol_strlen($phone) == 12) { //ex: +216_AB_CDE_FGH
4969 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
4970 }
4971 } elseif (strtoupper($countrycode) == "GF") { //Guyane francaise
4972 if (dol_strlen($phone) == 13) { //ex: +594_ABC_DE_FG_HI (ABC=594 de nouveau)
4973 $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);
4974 }
4975 } elseif (strtoupper($countrycode) == "GP") { //Guadeloupe
4976 if (dol_strlen($phone) == 13) { //ex: +590_ABC_DE_FG_HI (ABC=590 de nouveau)
4977 $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);
4978 }
4979 } elseif (strtoupper($countrycode) == "MQ") { //Martinique
4980 if (dol_strlen($phone) == 13) { //ex: +596_ABC_DE_FG_HI (ABC=596 de nouveau)
4981 $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);
4982 }
4983 } elseif (strtoupper($countrycode) == "IT") { //Italie
4984 if (dol_strlen($phone) == 12) { //ex: +39_ABC_DEF_GHI
4985 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
4986 } elseif (dol_strlen($phone) == 13) { //ex: +39_ABC_DEF_GH_IJ
4987 $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);
4988 }
4989 } elseif (strtoupper($countrycode) == "AU") {
4990 //Australie
4991 if (dol_strlen($phone) == 12) {
4992 //ex: +61_A_BCDE_FGHI
4993 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 1) . $separ . substr($newphone, 4, 4) . $separ . substr($newphone, 8, 4);
4994 }
4995 } elseif (strtoupper($countrycode) == "LU") {
4996 // Luxembourg
4997 if (dol_strlen($phone) == 10) { // fix 6 digits +352_AA_BB_CC
4998 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2);
4999 } elseif (dol_strlen($phone) == 11) { // fix 7 digits +352_AA_BB_CC_D
5000 $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);
5001 } elseif (dol_strlen($phone) == 12) { // fix 8 digits +352_AA_BB_CC_DD
5002 $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);
5003 } elseif (dol_strlen($phone) == 13) { // mobile +352_AAA_BB_CC_DD
5004 $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);
5005 }
5006 } elseif (strtoupper($countrycode) == "PE") {
5007 // Peru
5008 if (dol_strlen($phone) == 7) { // fix 7 numbers without code AAA_BBBB
5009 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 4);
5010 } elseif (dol_strlen($phone) == 9) { // mobile add code and fix 9 numbers +51_AAA_BBB_CCC
5011 $newphonewa = '+51' . $newphone;
5012 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3);
5013 } elseif (dol_strlen($phone) == 11) { // fix 11 numbers +511_AAA_BBBB
5014 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 4);
5015 } elseif (dol_strlen($phone) == 12) { // mobile +51_AAA_BBB_CCC
5016 $newphonewa = $newphone;
5017 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
5018 }
5019 } elseif (strtoupper($countrycode) == "IN") { //India
5020 if (dol_strlen($phone) == 13) {
5021 if ($withpicto == 'phone') { //ex: +91_AB_CDEF_GHIJ
5022 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 4) . $separ . substr($newphone, 9, 4);
5023 } else { //ex: +91_ABCDE_FGHIJ
5024 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 5) . $separ . substr($newphone, 8, 5);
5025 }
5026 }
5027 }
5028
5029 $newphoneastart = $newphoneaend = '';
5030 if (!empty($addlink)) { // Link on phone number (+ link to add action if conf->global->AGENDA_ADDACTIONFORPHONE set)
5031 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
5032 $newphoneastart = '<a href="tel:' . urlencode($phone) . '">';
5033 $newphoneaend .= '</a>';
5034 } elseif (isModEnabled('clicktodial') && $addlink == 'AC_TEL') { // If click to dial, we use click to dial url
5035 if (empty($user->clicktodial_loaded)) {
5036 $user->fetch_clicktodial();
5037 }
5038
5039 // Define urlmask
5040 $urlmask = getDolGlobalString('CLICKTODIAL_URL', 'ErrorClickToDialModuleNotConfigured');
5041 if (!empty($user->clicktodial_url)) {
5042 $urlmask = $user->clicktodial_url;
5043 }
5044
5045 $clicktodial_poste = (!empty($user->clicktodial_poste) ? urlencode($user->clicktodial_poste) : '');
5046 $clicktodial_login = (!empty($user->clicktodial_login) ? urlencode($user->clicktodial_login) : '');
5047 $clicktodial_password = (!empty($user->clicktodial_password) ? urlencode($user->clicktodial_password) : '');
5048 // This line is for backward compatibility @phan-suppress-next-line PhanPluginPrintfVariableFormatString
5049 $url = sprintf($urlmask, urlencode($phone), $clicktodial_poste, $clicktodial_login, $clicktodial_password);
5050 // Those lines are for substitution
5051 $substitarray = array(
5052 '__PHONEFROM__' => $clicktodial_poste,
5053 '__PHONETO__' => urlencode($phone),
5054 '__LOGIN__' => $clicktodial_login,
5055 '__PASS__' => $clicktodial_password
5056 );
5057 $url = make_substitutions($url, $substitarray);
5058 if (!getDolGlobalString('CLICKTODIAL_DO_NOT_USE_AJAX_CALL')) {
5059 // Default and recommended: New method using ajax without submitting a page making a javascript history.go(-1) back
5060 $newphoneastart = '<a href="' . $url . '" class="cssforclicktodial">'; // Call of ajax is handled by the lib_foot.js.php on class 'cssforclicktodial'
5061 $newphoneaend = '</a>';
5062 } else {
5063 // Old method
5064 $newphoneastart = '<a href="' . $url . '"';
5065 if (getDolGlobalString('CLICKTODIAL_FORCENEWTARGET')) {
5066 $newphoneastart .= ' target="_blank" rel="noopener noreferrer"';
5067 }
5068 $newphoneastart .= '>';
5069 $newphoneaend .= '</a>';
5070 }
5071 }
5072
5073 //if (($contactid || $socid) && isModEnabled('agenda') && $user->hasRight('agenda', 'myactions', 'create'))
5074 if (isModEnabled('agenda') && $user->hasRight("agenda", "myactions", "create")) {
5075 $type = 'AC_TEL';
5076 $addlinktoagenda = '';
5077 if ($addlink == 'AC_FAX') {
5078 $type = 'AC_FAX';
5079 }
5080 if (getDolGlobalString('AGENDA_ADDACTIONFORPHONE')) {
5081 $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>';
5082 }
5083 if ($addlinktoagenda) {
5084 $newphone = '<span>' . $newphone . ' ' . $addlinktoagenda . '</span>';
5085 }
5086 }
5087 }
5088
5089 if (getDolGlobalString('CONTACT_PHONEMOBILE_SHOW_LINK_TO_WHATSAPP') && $withpicto == 'mobile') {
5090 // Link to Whatsapp
5091 $newphone .= ' <a href="https://wa.me/' . $newphonewa . '" target="_blank"'; // Use api to whatasapp contacts
5092 $newphone .= '><span class="paddingright fab fa-whatsapp" style="color:#25D366;" title="WhatsApp"></span></a>';
5093 }
5094
5095 if (empty($titlealt)) {
5096 $titlealt = ($withpicto == 'fax' ? $langs->trans("Fax") : $langs->trans("Phone"));
5097 }
5098 $rep = '';
5099
5100 if ($hookmanager) {
5101 $parameters = array('countrycode' => $countrycode, 'cid' => $contactid, 'socid' => $socid, 'titlealt' => $titlealt, 'picto' => $withpicto);
5102 $reshook = $hookmanager->executeHooks('printPhone', $parameters, $phone);
5103 $rep .= $hookmanager->resPrint;
5104 }
5105 if (empty($reshook)) {
5106 $picto = '';
5107 if ($withpicto) {
5108 if ($withpicto == 'fax') {
5109 $picto = 'phoning_fax';
5110 } elseif ($withpicto == 'phone') {
5111 $picto = 'phone';
5112 } elseif ($withpicto == 'mobile') {
5113 $picto = 'phoning_mobile';
5114 } else {
5115 $picto = '';
5116 }
5117 }
5118 if ($adddivfloat == 1) {
5119 $rep .= '<div class="nospan float' . ($morecss ? ' ' . $morecss : '') . '">';
5120 } elseif (empty($adddivfloat)) {
5121 $rep .= '<span' . ($morecss ? ' class="' . $morecss . '"' : '') . '>';
5122 }
5123
5124 $rep .= $newphoneastart;
5125 $rep .= ($withpicto ? img_picto($titlealt, $picto) : '');
5126 if ($separ != 'hidenum') {
5127 $rep .= ($withpicto ? ' ' : '') . $newphone;
5128 }
5129 $rep .= $newphoneaend;
5130
5131 if ($adddivfloat == 1) {
5132 $rep .= '</div>';
5133 } elseif (empty($adddivfloat)) {
5134 $rep .= '</span>';
5135 }
5136 }
5137
5138 return $rep;
5139}
5140
5149function dol_print_ip($ip, $mode = 0, $showname = 0)
5150{
5151 global $conf;
5152
5153 $ret = '';
5154 if (!isset($conf->cache['resolveips'])) {
5155 $conf->cache['resolveips'] = array();
5156 }
5157
5158 if ($mode != 2) {
5159 $countrycode = dolGetCountryCodeFromIp($ip);
5160 if ($countrycode) { // If success, countrycode is us, fr, ...
5161 if (file_exists(DOL_DOCUMENT_ROOT . '/theme/common/flags/' . $countrycode . '.png')) {
5162 $ret .= picto_from_langcode($countrycode);
5163 } else {
5164 $ret .= '(' . $countrycode . ')';
5165 }
5166 $ret .= '&nbsp;';
5167 } else {
5168 // Nothing
5169 }
5170 }
5171
5172 if (in_array($mode, [0, 2])) {
5173 $domain = '';
5174 if ($showname) {
5175 if (!array_key_exists($ip, $conf->cache['resolveips'])) {
5176 $domain = gethostbyaddr($ip);
5177 $conf->cache['resolveips'][$ip] = $domain; // false or domain
5178 } else {
5179 $domain = $conf->cache['resolveips'][$ip];
5180 }
5181 }
5182 if ($domain) {
5183 $ret .= $domain;
5184 } else {
5185 $ret .= $ip;
5186 }
5187 }
5188
5189 return $ret;
5190}
5191
5204function getUserRemoteIP($trusted = 0)
5205{
5206 if ($trusted) { // Return only IP we can rely on (not spoofable by the client)
5207 $ip = (empty($_SERVER['REMOTE_ADDR']) ? '' : $_SERVER['REMOTE_ADDR']); // value may be the IP of a proxy
5208 // 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)
5209 // This can happen if the proxy were added in the list of trusted proxy.
5210 return $ip;
5211 }
5212
5213 // Try to guess the real IP of client (but this may not be reliable)
5214 if (empty($_SERVER['HTTP_X_FORWARDED_FOR']) || preg_match('/[^0-9\.\:,\[\]\s]/', $_SERVER['HTTP_X_FORWARDED_FOR'])) {
5215 if (empty($_SERVER['HTTP_CLIENT_IP']) || preg_match('/[^0-9\.\:,\[\]\s]/', $_SERVER['HTTP_CLIENT_IP'])) {
5216 if (empty($_SERVER["HTTP_CF_CONNECTING_IP"])) {
5217 $ip = (empty($_SERVER['REMOTE_ADDR']) ? '' : $_SERVER['REMOTE_ADDR']); // value may be the IP of the proxy and not the client
5218 } else {
5219 $ip = $_SERVER["HTTP_CF_CONNECTING_IP"]; // value here may have been forged by client
5220 }
5221 } else {
5222 $ip = preg_replace('/,.*$/', '', $_SERVER['HTTP_CLIENT_IP']); // value is clean here but may have been forged by proxy
5223 }
5224 } else {
5225 $ip = preg_replace('/,.*$/', '', $_SERVER['HTTP_X_FORWARDED_FOR']); // value is clean here but may have been forged by proxy
5226 }
5227 return $ip;
5228}
5229
5236function dolGetCountryCodeFromIp($ip)
5237{
5238 $countrycode = '';
5239
5240 if (isModEnabled('geoipmaxmind')) {
5241 if (getDolGlobalString('GEOIP_VERSION') == 'php') {
5242 $datafile = getDolGlobalString('GEOIPMAXMIND_COUNTRY_DATAFILE');
5243 } else {
5244 $diroffile = getMultidirOutput(null, 'geoipmaxmind');
5245 $datafile = $diroffile . '/' . getDolGlobalString('GEOIPMAXMIND_COUNTRY_DATAFILE_EMBEDDED');
5246 }
5247 //$ip='24.24.24.24';
5248 //$datafile='/usr/share/GeoIP/GeoIP.dat'; Note that this must be downloaded datafile (not same than datafile provided with ubuntu packages)
5249 if ($datafile) {
5250 try {
5251 include_once DOL_DOCUMENT_ROOT . '/core/class/dolgeoip.class.php';
5252 $geoip = new DolGeoIP('country', $datafile);
5253 //print 'ip='.$ip.' databaseType='.$geoip->gi->databaseType." GEOIP_CITY_EDITION_REV1=".GEOIP_CITY_EDITION_REV1."\n";
5254 $countrycode = $geoip->getCountryCodeFromIP($ip);
5255 } catch (Exception $e) {
5256 //print 'Error with GeoIP database: '.$e->getMessage();
5257 }
5258 }
5259 }
5260
5261 return $countrycode;
5262}
5263
5264
5271function dol_user_country()
5272{
5273 global $conf, $langs, $user;
5274
5275 //$ret=$user->xxx;
5276 $ret = '';
5277 if (isModEnabled('geoipmaxmind')) {
5278 $ip = getUserRemoteIP();
5279 $datafile = getDolGlobalString('GEOIPMAXMIND_COUNTRY_DATAFILE');
5280 //$ip='24.24.24.24';
5281 //$datafile='E:\Mes Sites\Web\Admin1\awstats\maxmind\GeoIP.dat';
5282 include_once DOL_DOCUMENT_ROOT . '/core/class/dolgeoip.class.php';
5283 $geoip = new DolGeoIP('country', $datafile);
5284 $countrycode = $geoip->getCountryCodeFromIP($ip);
5285 $ret = $countrycode;
5286 }
5287 return $ret;
5288}
5289
5302function dol_print_address($address, $htmlid, $element, $id, $noprint = 0, $charfornl = '')
5303{
5304 global $hookmanager;
5305
5306 $out = '';
5307
5308 if ($address) {
5309 if ($hookmanager) {
5310 $parameters = array('element' => $element, 'id' => $id);
5311 $reshook = $hookmanager->executeHooks('printAddress', $parameters, $address);
5312 $out .= $hookmanager->resPrint;
5313 }
5314 if (empty($reshook)) {
5315 if (empty($charfornl)) {
5316 $out .= nl2br((string) $address);
5317 } else {
5318 $out .= preg_replace('/[\r\n]+/', $charfornl, (string) $address);
5319 }
5320
5321 // TODO Remove this block, we can add this using the hook now
5322 $showgmap = $showomap = 0;
5323 if (($element == 'thirdparty' || $element == 'societe') && isModEnabled('google') && getDolGlobalString('GOOGLE_ENABLE_GMAPS')) {
5324 $showgmap = 1;
5325 }
5326 if ($element == 'contact' && isModEnabled('google') && getDolGlobalString('GOOGLE_ENABLE_GMAPS_CONTACTS')) {
5327 $showgmap = 1;
5328 }
5329 if ($element == 'member' && isModEnabled('google') && getDolGlobalString('GOOGLE_ENABLE_GMAPS_MEMBERS')) {
5330 $showgmap = 1;
5331 }
5332 if ($element == 'user' && isModEnabled('google') && getDolGlobalString('GOOGLE_ENABLE_GMAPS_USERS')) {
5333 $showgmap = 1;
5334 }
5335 if (($element == 'thirdparty' || $element == 'societe') && isModEnabled('openstreetmap') && getDolGlobalString('OPENSTREETMAP_ENABLE_MAPS')) {
5336 $showomap = 1;
5337 }
5338 if ($element == 'contact' && isModEnabled('openstreetmap') && getDolGlobalString('OPENSTREETMAP_ENABLE_MAPS_CONTACTS')) {
5339 $showomap = 1;
5340 }
5341 if ($element == 'member' && isModEnabled('openstreetmap') && getDolGlobalString('OPENSTREETMAP_ENABLE_MAPS_MEMBERS')) {
5342 $showomap = 1;
5343 }
5344 if ($element == 'user' && isModEnabled('openstreetmap') && getDolGlobalString('OPENSTREETMAP_ENABLE_MAPS_USERS')) {
5345 $showomap = 1;
5346 }
5347 if ($showgmap) {
5348 $url = dol_buildpath('/google/gmaps.php?mode=' . $element . '&id=' . $id, 1);
5349 $out .= ' <a href="' . $url . '" target="_gmaps"><img id="' . $htmlid . '" class="valigntextbottom" src="' . DOL_URL_ROOT . '/theme/common/gmap.png"></a>';
5350 }
5351 if ($showomap) {
5352 $url = dol_buildpath('/openstreetmap/maps.php?mode=' . $element . '&id=' . $id, 1);
5353 $out .= ' <a href="' . $url . '" target="_gmaps"><img id="' . $htmlid . '_openstreetmap" class="valigntextbottom" src="' . DOL_URL_ROOT . '/theme/common/gmap.png"></a>';
5354 }
5355 }
5356 }
5357 if ($noprint) {
5358 return $out;
5359 } else {
5360 print $out;
5361 return null;
5362 }
5363}
5364
5365
5375function isValidEmail($address, $acceptsupervisorkey = 0, $acceptuserkey = 0)
5376{
5377 if ($acceptsupervisorkey && $address == '__SUPERVISOREMAIL__') {
5378 return true;
5379 }
5380 if ($acceptuserkey && $address == '__USER_EMAIL__') {
5381 return true;
5382 }
5383 if (filter_var($address, FILTER_VALIDATE_EMAIL)) {
5384 return true;
5385 }
5386
5387 return false;
5388}
5389
5399function isValidMXRecord($domain)
5400{
5401 if (function_exists('idn_to_ascii') && function_exists('checkdnsrr')) {
5402 if (!checkdnsrr(idn_to_ascii($domain), 'MX')) {
5403 return 0;
5404 }
5405 if (function_exists('getmxrr')) {
5406 $mxhosts = array();
5407 $weight = array();
5408 getmxrr(idn_to_ascii($domain), $mxhosts, $weight);
5409 if (count($mxhosts) > 1) {
5410 return 1;
5411 }
5412 if (count($mxhosts) == 1 && !in_array((string) $mxhosts[0], array('', '.'))) {
5413 return 1;
5414 }
5415
5416 return 0;
5417 }
5418 }
5419
5420 // function idn_to_ascii or checkdnsrr or getmxrr does not exists
5421 return -1;
5422}
5423
5431function isValidPhone($phone)
5432{
5433 return true;
5434}
5435
5436
5446function dolGetFirstLetters($s, $nbofchar = 1)
5447{
5448 $ret = '';
5449 $tmparray = explode(' ', $s);
5450 foreach ($tmparray as $tmps) {
5451 $ret .= dol_substr($tmps, 0, $nbofchar);
5452 }
5453
5454 return $ret;
5455}
5456
5457
5465function dol_strlen($string, $stringencoding = 'UTF-8')
5466{
5467 if (is_null($string)) {
5468 return 0;
5469 }
5470
5471 if (function_exists('mb_strlen')) {
5472 return mb_strlen($string, $stringencoding);
5473 } else {
5474 return strlen($string);
5475 }
5476}
5477
5488function dol_substr($string, $start, $length = null, $stringencoding = '', $trunconbytes = 0)
5489{
5490 global $langs;
5491
5492 if (empty($stringencoding)) {
5493 $stringencoding = (empty($langs) ? 'UTF-8' : $langs->charset_output);
5494 }
5495
5496 $ret = '';
5497 if (empty($trunconbytes)) {
5498 if (function_exists('mb_substr')) {
5499 $ret = mb_substr($string, $start, $length, $stringencoding);
5500 } else {
5501 $ret = substr($string, $start, $length);
5502 }
5503 } else {
5504 if (function_exists('mb_strcut')) {
5505 $ret = mb_strcut($string, $start, $length, $stringencoding);
5506 } else {
5507 $ret = substr($string, $start, $length);
5508 }
5509 }
5510 return $ret;
5511}
5512
5513
5527function dol_trunc($string, $size = 40, $trunc = 'right', $stringencoding = 'UTF-8', $nodot = 0, $display = 0)
5528{
5529 global $conf;
5530
5531 if (empty($size) || getDolGlobalString('MAIN_DISABLE_TRUNC')) {
5532 return $string;
5533 }
5534
5535 if (empty($stringencoding)) {
5536 $stringencoding = 'UTF-8';
5537 }
5538 // reduce for small screen
5539 if (!empty($conf->dol_optimize_smallscreen) && $conf->dol_optimize_smallscreen == 1 && $display == 1) {
5540 $size = round($size / 3);
5541 }
5542
5543 // We go always here
5544 if ($trunc == 'right') {
5545 $newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
5546 if (dol_strlen($newstring, $stringencoding) > ($size + ($nodot ? 0 : 1))) {
5547 // If nodot is 0 and size is 1 chars more, we don't trunc and don't add '...'
5548 return dol_substr($newstring, 0, $size, $stringencoding) . ($nodot ? '' : '…');
5549 } else {
5550 //return 'u'.$size.'-'.$newstring.'-'.dol_strlen($newstring,$stringencoding).'-'.$string;
5551 return $string;
5552 }
5553 } elseif ($trunc == 'middle') {
5554 $newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
5555 if (dol_strlen($newstring, $stringencoding) > 2 && dol_strlen($newstring, $stringencoding) > ($size + 1)) {
5556 $size1 = (int) round($size / 2);
5557 $size2 = (int) round($size / 2);
5558 return dol_substr($newstring, 0, $size1, $stringencoding) . '…' . dol_substr($newstring, dol_strlen($newstring, $stringencoding) - $size2, $size2, $stringencoding);
5559 } else {
5560 return $string;
5561 }
5562 } elseif ($trunc == 'left') {
5563 $newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
5564 if (dol_strlen($newstring, $stringencoding) > ($size + ($nodot ? 0 : 1))) {
5565 // If nodot is 0 and size is 1 chars more, we don't trunc and don't add '...'
5566 return '…' . dol_substr($newstring, dol_strlen($newstring, $stringencoding) - $size, $size, $stringencoding);
5567 } else {
5568 return $string;
5569 }
5570 } elseif ($trunc == 'wrap') {
5571 $newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
5572 if (dol_strlen($newstring, $stringencoding) > ($size + 1)) {
5573 return dol_substr($newstring, 0, $size, $stringencoding) . "\n" . dol_trunc(dol_substr($newstring, $size, dol_strlen($newstring, $stringencoding) - $size, $stringencoding), $size, $trunc);
5574 } else {
5575 return $string;
5576 }
5577 } else {
5578 return 'BadParam3CallingDolTrunc';
5579 }
5580}
5581
5589function getPictoForType($key, $morecss = '')
5590{
5591 // Set array with type -> picto
5592 $type2picto = array(
5593 'varchar' => 'font',
5594 'text' => 'font',
5595 'html' => 'code',
5596 'int' => 'sort-numeric-down',
5597 'double' => 'sort-numeric-down',
5598 'price' => 'currency',
5599 'pricecy' => 'multicurrency',
5600 'password' => 'key',
5601 'boolean' => 'check-square',
5602 'date' => 'calendar',
5603 'datetime' => 'calendar',
5604 'duration' => 'hourglass',
5605 'phone' => 'phone',
5606 'mail' => 'email',
5607 'url' => 'url',
5608 'ip' => 'country',
5609 'select' => 'list',
5610 'sellist' => 'list',
5611 'stars' => 'fontawesome_star_fas',
5612 'radio' => 'check-circle',
5613 'checkbox' => 'list',
5614 'chkbxlst' => 'list',
5615 'link' => 'link',
5616 'icon' => "question",
5617 'point' => "country",
5618 'multipts' => 'country',
5619 'linestrg' => "country",
5620 'polygon' => "country",
5621 'separate' => 'minus'
5622 );
5623
5624 if (!empty($type2picto[$key])) {
5625 return img_picto('', $type2picto[$key], 'class="pictofixedwidth' . ($morecss ? ' ' . $morecss : '') . '"');
5626 }
5627
5628 return img_picto('', 'generic', 'class="pictofixedwidth' . ($morecss ? ' ' . $morecss : '') . '"');
5629}
5630
5631
5655function img_picto($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0, $srconly = 0, $notitle = 0, $alt = '', $morecss = '', $marginleftonlyshort = 2, $allowothertags = array())
5656{
5657 global $conf;
5658
5659 // We forge fullpathpicto for image to $path/img/$picto. By default, we take DOL_URL_ROOT/theme/$conf->theme/img/$picto
5660 $url = DOL_URL_ROOT;
5661 $theme = isset($conf->theme) ? $conf->theme : null;
5662 $path = 'theme/' . $theme;
5663 if (empty($picto)) {
5664 $picto = 'generic';
5665 }
5666
5667 // Define fullpathpicto to use into src
5668 if ($pictoisfullpath) {
5669 // Clean parameters
5670 if (!preg_match('/(\.png|\.gif|\.svg)$/i', $picto)) {
5671 $picto .= '.png';
5672 }
5673 $fullpathpicto = $picto;
5674 $reg = array();
5675 if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
5676 $morecss .= ($morecss ? ' ' : '') . $reg[1];
5677 $moreatt = str_replace('class="' . $reg[1] . '"', '', $moreatt);
5678 }
5679 } else {
5680 // $picto can not be null since replaced with 'generic' in that case
5681 // $pictowithouttext = preg_replace('/(\.png|\.gif|\.svg)$/', '', (is_null($picto) ? '' : $picto));
5682 $pictowithouttext = preg_replace('/(\.png|\.gif|\.svg)$/', '', $picto);
5683 $pictowithouttext = str_replace('object_', '', $pictowithouttext);
5684 $pictowithouttext = str_replace('_nocolor', '', $pictowithouttext);
5685
5686 // Fix some values of $pictowithouttext
5687 $pictoconvertkey = array(
5688 'facture' => 'bill',
5689 'shipping' => 'shipment',
5690 'fichinter' => 'intervention',
5691 'agenda' => 'calendar',
5692 'invoice_supplier' => 'supplier_invoice',
5693 'order_supplier' => 'supplier_order');
5694 if (in_array($pictowithouttext, array_keys($pictoconvertkey))) {
5695 $pictowithouttext = $pictoconvertkey[$pictowithouttext];
5696 }
5697
5698 if (strpos($pictowithouttext, 'fontawesome_') === 0 || strpos($pictowithouttext, 'fa-') === 0) {
5699 // This is a font awesome image 'fontawesome_xxx' or 'fa-xxx'
5700 $pictowithouttext = str_replace('fontawesome_', '', $pictowithouttext);
5701 $pictowithouttext = str_replace('fa-', '', $pictowithouttext);
5702
5703 // Compatibility with old fontawesome versions
5704 if ($pictowithouttext == 'file-o') {
5705 $pictowithouttext = 'file';
5706 }
5707
5708 $pictowithouttextarray = explode('_', $pictowithouttext);
5709 $marginleftonlyshort = 0;
5710
5711 if (!empty($pictowithouttextarray[1])) {
5712 // Syntax is 'fontawesome_fakey_faprefix_facolor_fasize' or 'fa-fakey_faprefix_facolor_fasize'
5713 $fakey = 'fa-' . $pictowithouttextarray[0];
5714 $faprefix = empty($pictowithouttextarray[1]) ? 'fas' : $pictowithouttextarray[1];
5715 $facolor = empty($pictowithouttextarray[2]) ? '' : $pictowithouttextarray[2];
5716 $fasize = empty($pictowithouttextarray[3]) ? '' : $pictowithouttextarray[3];
5717 } else {
5718 $fakey = 'fa-' . $pictowithouttext;
5719 $faprefix = 'fas';
5720 $facolor = '';
5721 $fasize = '';
5722 }
5723
5724 // This snippet only needed since function img_edit accepts only one additional parameter: no separate one for css only.
5725 // class/style need to be extracted to avoid duplicate class/style validation errors when $moreatt is added to the end of the attributes.
5726 $morestyle = '';
5727 $reg = array();
5728 if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
5729 $morecss .= ($morecss ? ' ' : '') . $reg[1];
5730 $moreatt = str_replace('class="' . $reg[1] . '"', '', $moreatt);
5731 }
5732 if (preg_match('/style="([^"]+)"/', $moreatt, $reg)) {
5733 $morestyle = $reg[1];
5734 $moreatt = str_replace('style="' . $reg[1] . '"', '', $moreatt);
5735 }
5736 $moreatt = trim($moreatt);
5737
5738 $enabledisablehtml = '<span class="' . $faprefix . ' ' . $fakey . ($marginleftonlyshort ? ($marginleftonlyshort == 1 ? ' marginleftonlyshort' : ' marginleftonly') : '');
5739 $enabledisablehtml .= ($morecss ? ' ' . $morecss : '') . '" style="' . ($fasize ? ('font-size: ' . $fasize . ';') : '') . ($facolor ? (' color: ' . $facolor . ';') : '') . ($morestyle ? ' ' . $morestyle : '') . '"' . (($notitle || empty($titlealt)) ? '' : ' title="' . dol_escape_htmltag($titlealt) . '"') . ($moreatt ? ' ' . $moreatt : '') . '>';
5740 $enabledisablehtml .= '</span>';
5741
5742 return $enabledisablehtml;
5743 }
5744
5745 if (empty($srconly) && !preg_match('/[\.\/@]/', $picto)) { // If original picto code does not contains a / and no . inside, it is not a path to an image file on disk
5746 $fakey = $pictowithouttext;
5747 $facolor = '';
5748 $fasize = '';
5749 $fa = getDolGlobalString('MAIN_FONTAWESOME_ICON_STYLE', 'fas');
5750 if (in_array($pictowithouttext, array('card', 'bell', 'clock', 'establishment', 'file', 'file-o', 'generic', 'minus-square', 'object_generic', 'pdf', 'plus-square', 'timespent', 'note', 'off', 'on', 'object_bookmark', 'bookmark', 'vcard'))) {
5751 $fa = 'far';
5752 }
5753 if (in_array($pictowithouttext, array('black-tie', 'discord', 'facebook', 'flickr', 'github', 'google', 'google-plus-g', 'instagram', 'linkedin', 'meetup', 'microsoft', 'pinterest', 'skype', 'slack', 'twitter', 'reddit', 'snapchat', 'stripe', 'stripe-s', 'tumblr', 'viadeo', 'whatsapp', 'youtube'))) {
5754 $fa = 'fab';
5755 }
5756
5757 $arrayconvpictotofa = getImgPictoConv('fa');
5758
5759 if ($pictowithouttext == 'off') {
5760 $fakey = 'fa-square';
5761 $fasize = '1.3em';
5762 } elseif ($pictowithouttext == 'on') {
5763 $fakey = 'fa-check-square';
5764 $fasize = '1.3em';
5765 } elseif ($pictowithouttext == 'listlight') {
5766 $fakey = 'fa-download';
5767 $marginleftonlyshort = 1;
5768 } elseif ($pictowithouttext == 'printer') {
5769 $fakey = 'fa-print';
5770 $fasize = '1.2em';
5771 } elseif ($pictowithouttext == 'note') {
5772 $fakey = 'fa-sticky-note';
5773 $marginleftonlyshort = 1;
5774 } elseif (in_array($pictowithouttext, array('1uparrow', '1downarrow', '1leftarrow', '1rightarrow', '1uparrow_selected', '1downarrow_selected', '1leftarrow_selected', '1rightarrow_selected'))) {
5775 $convertarray = array('1uparrow' => 'caret-up', '1downarrow' => 'caret-down', '1leftarrow' => 'caret-left', '1rightarrow' => 'caret-right', '1uparrow_selected' => 'caret-up', '1downarrow_selected' => 'caret-down', '1leftarrow_selected' => 'caret-left', '1rightarrow_selected' => 'caret-right');
5776 $fakey = 'fa-' . $convertarray[$pictowithouttext];
5777 if (preg_match('/selected/', $pictowithouttext)) {
5778 $facolor = '#888';
5779 }
5780 $marginleftonlyshort = 1;
5781 } elseif (!empty($arrayconvpictotofa[$pictowithouttext])) {
5782 $fakey = 'fa-' . $arrayconvpictotofa[$pictowithouttext];
5783 } else {
5784 $fakey = 'fa-' . $pictowithouttext;
5785 }
5786
5787 if (in_array($pictowithouttext, array('dollyrevert', 'member', 'members', 'contract', 'group', 'resource', 'shipment', 'reception'))) {
5788 $morecss .= ' em092';
5789 }
5790 if (in_array($pictowithouttext, array('conferenceorbooth', 'eventorganization', 'holiday', 'info', 'info_black', 'project', 'workstation'))) {
5791 $morecss .= ' em088';
5792 }
5793 if (in_array($pictowithouttext, array('asset', 'intervention', 'payment', 'loan', 'partnership', 'stock', 'technic'))) {
5794 $morecss .= ' em080';
5795 }
5796
5797 // Define $marginleftonlyshort
5798 $arrayconvpictotomarginleftonly = array(
5799 'bank',
5800 'check',
5801 'delete',
5802 'generic',
5803 'grip',
5804 'grip_title',
5805 'jabber',
5806 'grip_title',
5807 'grip',
5808 'listlight',
5809 'note',
5810 'on',
5811 'off',
5812 'playdisabled',
5813 'printer',
5814 'resize',
5815 'sign-out',
5816 'stats',
5817 'switch_on',
5818 'switch_on_grey',
5819 'switch_on_red',
5820 'switch_off',
5821 'switch_off_grey',
5822 'switch_off_red',
5823 'uparrow',
5824 '1uparrow',
5825 '1downarrow',
5826 '1leftarrow',
5827 '1rightarrow',
5828 '1uparrow_selected',
5829 '1downarrow_selected',
5830 '1leftarrow_selected',
5831 '1rightarrow_selected'
5832 );
5833 if (!array_key_exists($pictowithouttext, $arrayconvpictotomarginleftonly)) {
5834 $marginleftonlyshort = 0;
5835 }
5836
5837 // Add CSS
5838 $arrayconvpictotomorcess = array(
5839 'action' => 'infobox-action',
5840 'account' => 'infobox-bank_account',
5841 'accounting_account' => 'infobox-bank_account',
5842 'accountline' => 'infobox-bank_account',
5843 'accountancy' => 'infobox-bank_account',
5844 'admin' => 'opacitymedium',
5845 'asset' => 'infobox-bank_account',
5846 'bank_account' => 'infobox-bank_account',
5847 'bill' => 'infobox-commande',
5848 'billa' => 'infobox-commande',
5849 'billr' => 'infobox-commande',
5850 'billd' => 'infobox-commande',
5851 'bookcal' => 'infobox-portal',
5852 'margin' => 'infobox-bank_account',
5853 'conferenceorbooth' => 'infobox-project',
5854 'cash-register' => 'infobox-portal',
5855 'contract' => 'infobox-contrat',
5856 'check' => 'font-status4',
5857 'conversation' => 'infobox-contrat',
5858 'donation' => 'infobox-commande',
5859 'dolly' => 'infobox-commande',
5860 'dollyrevert' => 'flip infobox-order_supplier',
5861 'ecm' => 'infobox-action',
5862 'eventorganization' => 'infobox-project',
5863 'hrm' => 'infobox-adherent',
5864 'group' => 'infobox-adherent',
5865 'intervention' => 'infobox-contrat',
5866 'incoterm' => 'infobox-supplier_proposal',
5867 'intracommreport' => 'infobox-bank_account',
5868 'currency' => 'infobox-bank_account',
5869 'multicurrency' => 'infobox-bank_account',
5870 'members' => 'infobox-adherent',
5871 'member' => 'infobox-adherent',
5872 'money-bill-alt' => 'infobox-bank_account',
5873 'order' => 'infobox-commande',
5874 'user' => 'infobox-adherent',
5875 'users' => 'infobox-adherent',
5876 'error' => 'pictoerror',
5877 'warning' => 'pictowarning',
5878 'switch_on' => 'font-status4',
5879 'switch_on_warning' => 'font-status4 warning',
5880 'switch_on_red' => 'font-status8',
5881 'switch_off_warning' => 'font-status4 warning',
5882 'switch_off_red' => 'font-status8',
5883 'holiday' => 'infobox-holiday',
5884 'info' => 'opacityhigh',
5885 'info_black' => 'purple',
5886 'invoice' => 'infobox-commande',
5887 'knowledgemanagement' => 'infobox-contrat rotate90',
5888 'loan' => 'infobox-commande',
5889 'payment' => 'infobox-bank_account',
5890 'payment_vat' => 'infobox-bank_account',
5891 'poll' => 'infobox-portal',
5892 'pos' => 'infobox-bank_account',
5893 'project' => 'infobox-project',
5894 'projecttask' => 'infobox-project',
5895 'propal' => 'infobox-propal',
5896 'proposal' => 'infobox-propal',
5897 'private' => 'infobox-project',
5898 'reception' => 'flip infobox-order_supplier',
5899 'recruitmentjobposition' => 'infobox-adherent',
5900 'recruitmentcandidature' => 'infobox-adherent',
5901 'resource' => 'infobox-action',
5902 'salary' => 'infobox-commande',
5903 'shapes' => 'infobox-adherent',
5904 'shipment' => 'infobox-commande',
5905 'store' => 'infobox-portal',
5906 'stripe' => 'infobox-bank_account',
5907 'supplier_invoice' => 'infobox-order_supplier',
5908 'supplier_invoicea' => 'infobox-order_supplier',
5909 'supplier_invoiced' => 'infobox-order_supplier',
5910 'supplier_invoicer' => 'infobox-order_supplier',
5911 'supplier' => 'infobox-order_supplier',
5912 'supplier_order' => 'infobox-order_supplier',
5913 'supplier_proposal' => 'infobox-supplier_proposal',
5914 'ticket' => 'infobox-contrat',
5915 'title_accountancy' => 'infobox-bank_account',
5916 'title_hrm' => 'infobox-holiday',
5917 'expensereport' => 'infobox-expensereport',
5918 'trip' => 'infobox-expensereport',
5919 'title_agenda' => 'infobox-action',
5920 'vat' => 'infobox-bank_account',
5921 'webportal' => 'infobox-portal',
5922 'website' => 'infobox-portal',
5923 //'title_setup'=>'infobox-action', 'tools'=>'infobox-action',
5924 'list-alt' => 'imgforviewmode',
5925 'calendar' => 'imgforviewmode',
5926 'calendarweek' => 'imgforviewmode',
5927 'calendarmonth' => 'imgforviewmode',
5928 'calendarday' => 'imgforviewmode',
5929 'calendarperuser' => 'imgforviewmode',
5930 'calendarpertype' => 'imgforviewmode'
5931 );
5932 if (!empty($arrayconvpictotomorcess[$pictowithouttext]) && strpos($picto, '_nocolor') === false) {
5933 $morecss .= ($morecss ? ' ' : '') . $arrayconvpictotomorcess[$pictowithouttext];
5934 }
5935
5936 // Define $color
5937 $arrayconvpictotocolor = array(
5938 'address' => '#6c6aa8',
5939 'building' => '#6c6aa8',
5940 'bom' => '#a69944',
5941 'clone' => '#999',
5942 'cog' => '#999',
5943 'companies' => '#6c6aa8',
5944 'company' => '#6c6aa8',
5945 'contact' => '#6c6aa8',
5946 'cron' => '#555',
5947 'dynamicprice' => '#a69944',
5948 'edit' => '#444',
5949 'note' => '#999',
5950 'error' => '',
5951 'help' => '#bbb',
5952 'listlight' => '#999',
5953 'language' => '#555',
5954 //'dolly'=>'#a69944', 'dollyrevert'=>'#a69944',
5955 'lock' => '#ddd',
5956 'lot' => '#a69944',
5957 'map-marker-alt' => '#aaa',
5958 'mrp' => '#a69944',
5959 'product' => '#a69944',
5960 'service' => '#a69944',
5961 'inventory' => '#a69944',
5962 'stock' => '#a69944',
5963 'movement' => '#a69944',
5964 'other' => '#ddd',
5965 'world' => '#986c6a',
5966 'partnership' => '#6c6aa8',
5967 'playdisabled' => '#ccc',
5968 'printer' => '#444',
5969 'projectpub' => '#986c6a',
5970 'resize' => '#444',
5971 'rss' => '#cba',
5972 //'shipment'=>'#a69944',
5973 'search-plus' => '#808080',
5974 'security' => '#999',
5975 'square' => '#888',
5976 'stop-circle' => '#888',
5977 'stats' => '#444',
5978 'superadmin' => '#600',
5979 'switch_off' => '#999',
5980 'technic' => '#999',
5981 'tick' => '#282',
5982 'timespent' => '#555',
5983 'uncheck' => '#800',
5984 'uparrow' => '#555',
5985 'user-cog' => '#999',
5986 'country' => '#aaa',
5987 'globe-americas' => '#aaa',
5988 'region' => '#aaa',
5989 'state' => '#aaa',
5990 //'website' => '#304',
5991 'workstation' => '#a69944'
5992 );
5993 if (isset($arrayconvpictotocolor[$pictowithouttext]) && strpos($picto, '_nocolor') === false) {
5994 $facolor = $arrayconvpictotocolor[$pictowithouttext];
5995 }
5996
5997 // This snippet only needed since function img_edit accepts only one additional parameter: no separate one for css only.
5998 // class/style need to be extracted to avoid duplicate class/style validation errors when $moreatt is added to the end of the attributes.
5999 $morestyle = '';
6000 $reg = array();
6001 if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
6002 $morecss .= ($morecss ? ' ' : '') . $reg[1];
6003 $moreatt = str_replace('class="' . $reg[1] . '"', '', $moreatt);
6004 }
6005 if (preg_match('/style="([^"]+)"/', $moreatt, $reg)) {
6006 $morestyle = $reg[1];
6007 $moreatt = str_replace('style="' . $reg[1] . '"', '', $moreatt);
6008 }
6009 $moreatt = trim($moreatt);
6010
6011 $enabledisablehtml = '<span class="' . $fa . ' ' . $fakey . ($marginleftonlyshort ? ($marginleftonlyshort == 1 ? ' marginleftonlyshort' : ' marginleftonly') : '');
6012 $enabledisablehtml .= ($morecss ? ' ' . $morecss : '') . '" style="' . ($fasize ? ('font-size: ' . $fasize . ';') : '') . ($facolor ? (' color: ' . $facolor . ';') : '') . ($morestyle ? ' ' . $morestyle : '') . '"' . (($notitle || empty($titlealt)) ? '' : ' title="' . dol_escape_htmltag($titlealt) . '"') . ($moreatt ? ' ' . $moreatt : '') . '>';
6013 $enabledisablehtml .= '</span>';
6014
6015 return $enabledisablehtml;
6016 }
6017
6018 if (getDolGlobalString('MAIN_OVERWRITE_THEME_PATH')) {
6019 $path = getDolGlobalString('MAIN_OVERWRITE_THEME_PATH') . '/theme/' . $theme; // If the theme does not have the same name as the module
6020 } elseif (getDolGlobalString('MAIN_OVERWRITE_THEME_RES')) {
6021 $path = getDolGlobalString('MAIN_OVERWRITE_THEME_RES') . '/theme/' . getDolGlobalString('MAIN_OVERWRITE_THEME_RES'); // To allow an external module to overwrite image resources whatever is activated theme
6022 } elseif (!empty($conf->modules_parts['theme']) && array_key_exists($theme, $conf->modules_parts['theme'])) {
6023 $path = $theme . '/theme/' . $theme; // If the theme have the same name as the module
6024 }
6025
6026 // If we ask an image into $url/$mymodule/img (instead of default path)
6027 $regs = array();
6028 if (preg_match('/^([^@]+)@([^@]+)$/i', $picto, $regs)) {
6029 $picto = $regs[1];
6030 $path = $regs[2]; // $path is $mymodule
6031 }
6032
6033 // Clean parameters
6034 if (!preg_match('/(\.png|\.gif|\.svg)$/i', $picto)) {
6035 $picto .= '.png';
6036 }
6037 // If alt path are defined, define url where img file is, according to physical path
6038 // ex: array(["main"]=>"/home/maindir/htdocs", ["alt0"]=>"/home/moddir0/htdocs", ...)
6039 foreach ($conf->file->dol_document_root as $type => $dirroot) {
6040 if ($type == 'main') {
6041 continue;
6042 }
6043 // This consumes a lot of time, that's why enabling alternative dir like "custom" dir should be avoid
6044 if (file_exists($dirroot . '/' . $path . '/img/' . $picto) && !empty($conf->file->dol_url_root)) {
6045 $url = DOL_URL_ROOT . $conf->file->dol_url_root[$type];
6046 break;
6047 }
6048 }
6049
6050 // $url is '' or '/custom', $path is current theme or
6051 $fullpathpicto = $url . '/' . $path . '/img/' . $picto;
6052 }
6053
6054 if ($srconly) {
6055 return $fullpathpicto;
6056 }
6057
6058 // tag title is used for tooltip on <a>, tag alt can be used with very simple text on image for blind people
6059 return '<img src="' . $fullpathpicto . '"' . ($notitle ? '' : ' alt="' . dolPrintHTMLForAttribute($alt, 0, $allowothertags) . '"') . (($notitle || empty($titlealt)) ? '' : ' title="' . dolPrintHTMLForAttribute($titlealt, 0, $allowothertags) . '"') . ($moreatt ? ' ' . $moreatt . ($morecss ? ' class="' . $morecss . '"' : '') : ' class="inline-block' . ($morecss ? ' ' . $morecss : '') . '"') . '>'; // Alt is used for accessibility, title for popup
6060}
6061
6069function getImgPictoConv($mode = 'fa')
6070{
6071 global $conf;
6072
6073 if (empty($mode) || $mode == 'fa') {
6074 // Array when the fa picto key is different than the Dolibarr picto key.
6075 $arrayconvpictotofa = array(
6076 'account' => 'university',
6077 'accounting_account' => 'clipboard-list',
6078 'accountline' => 'receipt',
6079 'accountancy' => 'search-dollar',
6080 'action' => 'calendar-alt',
6081 'add' => 'plus-circle',
6082 'address' => 'address-book',
6083 'ai' => 'magic',
6084 'admin' => 'star',
6085 'asset' => 'money-check-alt',
6086 'autofill' => 'fill',
6087 'back' => 'arrow-left',
6088 'bank_account' => 'university',
6089 'bill' => 'file-invoice-dollar',
6090 'billa' => 'file-excel',
6091 'billr' => 'file-invoice-dollar',
6092 'billd' => 'file-medical',
6093 'blockedlog' => 'file-archive',
6094 'bookcal' => 'calendar-check',
6095 'supplier_invoice' => 'file-invoice-dollar',
6096 'supplier_invoicea' => 'file-excel',
6097 'supplier_invoicer' => 'file-invoice-dollar',
6098 'supplier_invoiced' => 'file-medical',
6099 'bom' => 'shapes',
6100 'card' => 'address-card',
6101 'chart' => 'chart-line',
6102 'company' => 'building',
6103 'contact' => 'address-book',
6104 'contract' => 'suitcase',
6105 'collab' => 'people-arrows',
6106 'conversation' => 'comments',
6107 'country' => 'globe-americas',
6108 'cron' => 'business-time',
6109 'cross' => 'times',
6110 'chevron-double-left' => 'angle-double-left',
6111 'chevron-double-right' => 'angle-double-right',
6112 'chevron-double-down' => 'angle-double-down',
6113 'chevron-double-top' => 'angle-double-up',
6114 'donation' => 'gift',
6115 'dynamicprice' => 'hand-holding-usd',
6116 'setup' => 'cog',
6117 'companies' => 'building',
6118 'products' => 'cube',
6119 'commercial' => 'suitcase',
6120 'invoicing' => 'coins',
6121 'accounting' => 'search-dollar',
6122 'category' => 'tag',
6123 'dollyrevert' => 'dolly',
6124 'file-o' => 'file',
6125 'generate' => 'plus-square',
6126 'hrm' => 'user-tie',
6127 'incoterm' => 'truck-loading',
6128 'margin' => 'calculator',
6129 'members' => 'user-friends',
6130 'ticket' => 'ticket-alt',
6131 'globe' => 'external-link-alt',
6132 'lot' => 'barcode',
6133 'email' => 'at',
6134 'establishment' => 'building',
6135 'edit' => 'pencil-alt',
6136 'entity' => 'globe',
6137 'graph' => 'chart-line',
6138 'grip_title' => 'arrows-alt',
6139 'grip' => 'arrows-alt',
6140 'help' => 'question-circle',
6141 'generic' => 'file',
6142 'holiday' => 'umbrella-beach',
6143 'info' => 'info-circle',
6144 'info_black' => 'info-circle',
6145 'inventory' => 'boxes',
6146 'intracommreport' => 'globe-europe',
6147 'jobprofile' => 'cogs',
6148 'knowledgemanagement' => 'ticket-alt',
6149 'label' => 'layer-group',
6150 'layout' => 'columns',
6151 'line' => 'bars',
6152 'loan' => 'money-bill-alt',
6153 'member' => 'user-alt',
6154 'meeting' => 'chalkboard-teacher',
6155 'mrp' => 'cubes',
6156 'next' => 'arrow-alt-circle-right',
6157 'trip' => 'wallet',
6158 'expensereport' => 'wallet',
6159 'group' => 'users',
6160 'movement' => 'people-carry',
6161 'sign-out' => 'sign-out-alt',
6162 'superadmin' => 'star',
6163 'switch_off' => 'toggle-off',
6164 'switch_off_grey' => 'toggle-off',
6165 'switch_off_warning' => 'toggle-off',
6166 'switch_off_red' => 'toggle-off',
6167 'switch_on' => 'toggle-on',
6168 'switch_on_grey' => 'toggle-on',
6169 'switch_on_warning' => 'toggle-on',
6170 'switch_on_red' => 'toggle-on',
6171 'check' => 'check',
6172 'bookmark' => 'star',
6173 'bank' => 'university',
6174 'close_title' => 'times',
6175 'delete' => 'trash',
6176 'filter' => 'filter',
6177 'list-alt' => 'list-alt',
6178 'calendarlist' => 'bars',
6179 'calendar' => 'calendar-alt',
6180 'calendarmonth' => 'calendar-alt',
6181 'calendarweek' => 'calendar-week',
6182 'calendarday' => 'calendar-day',
6183 'calendarperuser' => 'table',
6184 'calendarpertype' => 'table',
6185 'intervention' => 'ambulance',
6186 'invoice' => 'file-invoice-dollar',
6187 'order' => 'file-invoice',
6188 'error' => 'exclamation-triangle',
6189 'warning' => 'exclamation-triangle',
6190 'other' => 'square',
6191 'playdisabled' => 'play',
6192 'pdf' => 'file-pdf',
6193 'poll' => 'check-double',
6194 'pos' => 'cash-register',
6195 'preview' => 'binoculars',
6196 'project' => 'project-diagram',
6197 'projectpub' => 'project-diagram',
6198 'projecttask' => 'tasks',
6199 'propal' => 'file-signature',
6200 'proposal' => 'file-signature',
6201 'partnership' => 'handshake',
6202 'payment' => 'money-check-alt',
6203 'payment_vat' => 'money-check-alt',
6204 'pictoconfirm' => 'check-square',
6205 'phoning' => 'phone',
6206 'phoning_mobile' => 'mobile-alt',
6207 'phoning_fax' => 'fax',
6208 'previous' => 'arrow-alt-circle-left',
6209 'printer' => 'print',
6210 'product' => 'cube',
6211 'puce' => 'angle-right',
6212 'recent' => 'check-square',
6213 'reception' => 'dolly',
6214 'recruitmentjobposition' => 'id-card-alt',
6215 'recruitmentcandidature' => 'id-badge',
6216 'resize' => 'crop',
6217 'supplier_order' => 'dol-order_supplier',
6218 'supplier_proposal' => 'file-signature',
6219 'refresh' => 'redo',
6220 'region' => 'map-marked',
6221 'replacement' => 'exchange-alt',
6222 'resource' => 'laptop-house',
6223 'recurring' => 'history',
6224 'service' => 'concierge-bell',
6225 'skill' => 'shapes',
6226 'state' => 'map-marked-alt',
6227 'security' => 'key',
6228 'salary' => 'wallet',
6229 'shipment' => 'dolly',
6230 'stock' => 'box-open',
6231 'stats' => 'chart-bar',
6232 'split' => 'code-branch',
6233 'status' => 'stop-circle',
6234 'stripe' => 'stripe-s',
6235 'supplier' => 'building',
6236 'technic' => 'cogs',
6237 'tick' => 'check',
6238 'timespent' => 'clock',
6239 'title_setup' => 'tools',
6240 'title_accountancy' => 'money-check-alt',
6241 'title_bank' => 'university',
6242 'title_hrm' => 'umbrella-beach',
6243 'title_agenda' => 'calendar-alt',
6244 'uncheck' => 'times',
6245 'uparrow' => 'share',
6246 'url' => 'external-link-alt',
6247 'vat' => 'money-check-alt',
6248 'vcard' => 'arrow-alt-circle-down',
6249 'jabber' => 'comment',
6250 'website' => 'globe-americas',
6251 'workstation' => 'pallet',
6252 'webhook' => 'bullseye',
6253 'world' => 'globe',
6254 'private' => 'user-lock',
6255 'conferenceorbooth' => 'chalkboard-teacher',
6256 'eventorganization' => 'project-diagram',
6257 'webportal' => 'door-open'
6258 );
6259
6260 if ($conf->currency == 'EUR') {
6261 $arrayconvpictotofa['currency'] = 'euro-sign';
6262 $arrayconvpictotofa['multicurrency'] = 'dollar-sign';
6263 } else {
6264 $arrayconvpictotofa['currency'] = 'dollar-sign';
6265 $arrayconvpictotofa['multicurrency'] = 'euro-sign';
6266 }
6267 } else {
6268 $arrayconvpictotofa = array();
6269 }
6270
6271 return $arrayconvpictotofa;
6272}
6273
6274
6289function img_object($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0, $srconly = 0, $notitle = 0, $allowothertags = array())
6290{
6291 if (strpos($picto, '^') === 0) {
6292 return img_picto($titlealt, str_replace('^', '', $picto), $moreatt, $pictoisfullpath, $srconly, $notitle, '', '', 2, $allowothertags);
6293 } else {
6294 return img_picto($titlealt, 'object_' . $picto, $moreatt, $pictoisfullpath, $srconly, $notitle, '', '', 2, $allowothertags);
6295 }
6296}
6297
6309function img_weather($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0, $morecss = '')
6310{
6311 global $conf;
6312
6313 if (is_numeric($picto)) {
6314 //$leveltopicto = array(0=>'weather-clear.png', 1=>'weather-few-clouds.png', 2=>'weather-clouds.png', 3=>'weather-many-clouds.png', 4=>'weather-storm.png');
6315 //$picto = $leveltopicto[$picto];
6316 return '<i class="fa fa-weather-level' . $picto . '"></i>';
6317 } elseif (!preg_match('/(\.png|\.gif)$/i', $picto)) {
6318 $picto .= '.png';
6319 }
6320
6321 $path = DOL_URL_ROOT . '/theme/' . $conf->theme . '/img/weather/' . $picto;
6322
6323 return img_picto($titlealt, $path, $moreatt, 1, 0, 0, '', $morecss);
6324}
6325
6337function img_picto_common($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0, $notitle = 0)
6338{
6339 global $conf;
6340
6341 if (!preg_match('/(\.png|\.gif)$/i', $picto)) {
6342 $picto .= '.png';
6343 }
6344
6345 if ($pictoisfullpath) {
6346 $path = $picto;
6347 } else {
6348 $path = DOL_URL_ROOT . '/theme/common/' . $picto;
6349
6350 if (getDolGlobalInt('MAIN_MODULE_CAN_OVERWRITE_COMMONICONS')) {
6351 $themepath = DOL_DOCUMENT_ROOT . '/theme/' . $conf->theme . '/img/' . $picto;
6352
6353 if (file_exists($themepath)) {
6354 $path = $themepath;
6355 }
6356 }
6357 }
6358
6359 return img_picto($titlealt, $path, $moreatt, 1, 0, $notitle);
6360}
6361
6375function img_action($titlealt, $numaction, $picto = '', $moreatt = '')
6376{
6377 global $langs;
6378
6379 if (empty($titlealt) || $titlealt == 'default') {
6380 if ($numaction == '-1' || $numaction == 'ST_NO') {
6381 $numaction = -1;
6382 $titlealt = $langs->transnoentitiesnoconv('ChangeDoNotContact');
6383 } elseif ($numaction == '0' || $numaction == 'ST_NEVER') {
6384 $numaction = 0;
6385 $titlealt = $langs->transnoentitiesnoconv('ChangeNeverContacted');
6386 } elseif ($numaction == '1' || $numaction == 'ST_TODO') {
6387 $numaction = 1;
6388 $titlealt = $langs->transnoentitiesnoconv('ChangeToContact');
6389 } elseif ($numaction == '2' || $numaction == 'ST_PEND') {
6390 $numaction = 2;
6391 $titlealt = $langs->transnoentitiesnoconv('ChangeContactInProcess');
6392 } elseif ($numaction == '3' || $numaction == 'ST_DONE') {
6393 $numaction = 3;
6394 $titlealt = $langs->transnoentitiesnoconv('ChangeContactDone');
6395 } else {
6396 $titlealt = $langs->transnoentitiesnoconv('ChangeStatus ' . $numaction);
6397 $numaction = 0;
6398 }
6399 }
6400 if (!is_numeric($numaction)) {
6401 $numaction = 0;
6402 }
6403
6404 return img_picto($titlealt, (empty($picto) ? 'stcomm' . $numaction . '.png' : $picto), $moreatt);
6405}
6406
6414function img_edit_add($titlealt = 'default', $other = '')
6415{
6416 global $langs;
6417
6418 if ($titlealt == 'default') {
6419 $titlealt = $langs->trans('Add');
6420 }
6421
6422 return img_picto($titlealt, 'edit_add.png', $other);
6423}
6431function img_edit_remove($titlealt = 'default', $other = '')
6432{
6433 global $langs;
6434
6435 if ($titlealt == 'default') {
6436 $titlealt = $langs->trans('Remove');
6437 }
6438
6439 return img_picto($titlealt, 'edit_remove.png', $other);
6440}
6441
6450function img_edit($titlealt = 'default', $float = 0, $other = '')
6451{
6452 global $langs;
6453
6454 if ($titlealt == 'default') {
6455 $titlealt = $langs->trans('Modify');
6456 }
6457
6458 return img_picto($titlealt, 'edit', ($float ? 'style="float: ' . ($langs->tab_translate["DIRECTION"] == 'rtl' ? 'left' : 'right') . '"' : "") . ($other ? ' ' . $other : ''));
6459}
6460
6469function img_view($titlealt = 'default', $float = 0, $other = 'class="valignmiddle"')
6470{
6471 global $langs;
6472
6473 if ($titlealt == 'default') {
6474 $titlealt = $langs->trans('View');
6475 }
6476
6477 $moreatt = ($float ? 'style="float: right" ' : '') . $other;
6478
6479 return img_picto($titlealt, 'eye', $moreatt);
6480}
6481
6490function img_delete($titlealt = 'default', $other = 'class="pictodelete"', $morecss = '')
6491{
6492 global $langs;
6493
6494 if ($titlealt == 'default') {
6495 $titlealt = $langs->trans('Delete');
6496 }
6497
6498 return img_picto($titlealt, 'delete', $other, 0, 0, 0, '', $morecss);
6499}
6500
6508function img_printer($titlealt = "default", $other = '')
6509{
6510 global $langs;
6511 if ($titlealt == "default") {
6512 $titlealt = $langs->trans("Print");
6513 }
6514 return img_picto($titlealt, 'printer', $other);
6515}
6516
6524function img_split($titlealt = 'default', $other = 'class="pictosplit"')
6525{
6526 global $langs;
6527
6528 if ($titlealt == 'default') {
6529 $titlealt = $langs->trans('Split');
6530 }
6531
6532 return img_picto($titlealt, 'split', $other);
6533}
6534
6542function img_help($usehelpcursor = 1, $usealttitle = 1)
6543{
6544 global $langs;
6545
6546 if ($usealttitle) {
6547 if (is_string($usealttitle)) {
6548 $usealttitle = dol_escape_htmltag($usealttitle);
6549 } else {
6550 $usealttitle = $langs->trans('Info');
6551 }
6552 }
6553
6554 return img_picto($usealttitle, 'info', 'style="vertical-align: middle;' . ($usehelpcursor == 1 ? ' cursor: help' : ($usehelpcursor == 2 ? ' cursor: pointer' : '')) . '"');
6555}
6556
6563function img_info($titlealt = 'default')
6564{
6565 global $langs;
6566
6567 if ($titlealt == 'default') {
6568 $titlealt = $langs->trans('Informations');
6569 }
6570
6571 return img_picto($titlealt, 'info', 'style="vertical-align: middle;"');
6572}
6573
6582function img_warning($titlealt = 'default', $moreatt = '', $morecss = 'pictowarning')
6583{
6584 global $langs;
6585
6586 if ($titlealt == 'default') {
6587 $titlealt = $langs->trans('Warning');
6588 }
6589
6590 //return '<div class="imglatecoin">'.img_picto($titlealt, 'warning_white.png', 'class="pictowarning valignmiddle"'.($moreatt ? ($moreatt == '1' ? ' style="float: right"' : ' '.$moreatt): '')).'</div>';
6591 return img_picto($titlealt, 'warning', 'class="' . $morecss . '"' . ($moreatt ? ($moreatt == '1' ? ' style="float: right"' : ' ' . $moreatt) : ''));
6592}
6593
6600function img_error($titlealt = 'default')
6601{
6602 global $langs;
6603
6604 if ($titlealt == 'default') {
6605 $titlealt = $langs->trans('Error');
6606 }
6607
6608 return img_picto($titlealt, 'error');
6609}
6610
6618function img_next($titlealt = 'default', $moreatt = '')
6619{
6620 global $langs;
6621
6622 if ($titlealt == 'default') {
6623 $titlealt = $langs->trans('Next');
6624 }
6625
6626 //return img_picto($titlealt, 'next.png', $moreatt);
6627 return '<span class="fa fa-chevron-right paddingright paddingleft" title="' . dol_escape_htmltag($titlealt) . '"></span>';
6628}
6629
6637function img_previous($titlealt = 'default', $moreatt = '')
6638{
6639 global $langs;
6640
6641 if ($titlealt == 'default') {
6642 $titlealt = $langs->trans('Previous');
6643 }
6644
6645 //return img_picto($titlealt, 'previous.png', $moreatt);
6646 return '<span class="fa fa-chevron-left paddingright paddingleft" title="' . dol_escape_htmltag($titlealt) . '"></span>';
6647}
6648
6657function img_down($titlealt = 'default', $selected = 0, $moreclass = '')
6658{
6659 global $langs;
6660
6661 if ($titlealt == 'default') {
6662 $titlealt = $langs->trans('Down');
6663 }
6664
6665 return img_picto($titlealt, ($selected ? '1downarrow_selected' : '1downarrow'), 'class="imgdown' . ($moreclass ? " " . $moreclass : "") . '"');
6666}
6667
6676function img_up($titlealt = 'default', $selected = 0, $moreclass = '')
6677{
6678 global $langs;
6679
6680 if ($titlealt == 'default') {
6681 $titlealt = $langs->trans('Up');
6682 }
6683
6684 return img_picto($titlealt, ($selected ? '1uparrow_selected' : '1uparrow'), 'class="imgup' . ($moreclass ? " " . $moreclass : "") . '"');
6685}
6686
6695function img_left($titlealt = 'default', $selected = 0, $moreatt = '')
6696{
6697 global $langs;
6698
6699 if ($titlealt == 'default') {
6700 $titlealt = $langs->trans('Left');
6701 }
6702
6703 return img_picto($titlealt, ($selected ? '1leftarrow_selected' : '1leftarrow'), $moreatt);
6704}
6705
6714function img_right($titlealt = 'default', $selected = 0, $moreatt = '')
6715{
6716 global $langs;
6717
6718 if ($titlealt == 'default') {
6719 $titlealt = $langs->trans('Right');
6720 }
6721
6722 return img_picto($titlealt, ($selected ? '1rightarrow_selected' : '1rightarrow'), $moreatt);
6723}
6724
6732function img_allow($allow, $titlealt = 'default')
6733{
6734 global $langs;
6735
6736 if ($titlealt == 'default') {
6737 $titlealt = $langs->trans('Active');
6738 }
6739
6740 if ($allow == 1) {
6741 return img_picto($titlealt, 'tick');
6742 }
6743
6744 return '-';
6745}
6746
6754function img_credit_card($brand, $morecss = 'fa-2x inline-block valignmiddle')
6755{
6756 if (is_null($morecss)) {
6757 $morecss = 'fa-2x';
6758 }
6759
6760 if ($brand == 'visa' || $brand == 'Visa') {
6761 $brand = 'cc-visa';
6762 } elseif ($brand == 'mastercard' || $brand == 'MasterCard') {
6763 $brand = 'cc-mastercard';
6764 } elseif ($brand == 'amex' || $brand == 'American Express') {
6765 $brand = 'cc-amex';
6766 } elseif ($brand == 'discover' || $brand == 'Discover') {
6767 $brand = 'cc-discover';
6768 } elseif ($brand == 'jcb' || $brand == 'JCB') {
6769 $brand = 'cc-jcb';
6770 } elseif ($brand == 'diners' || $brand == 'Diners club') {
6771 $brand = 'cc-diners-club';
6772 } elseif (!in_array($brand, array('cc-visa', 'cc-mastercard', 'cc-amex', 'cc-discover', 'cc-jcb', 'cc-diners-club'))) {
6773 $brand = 'credit-card';
6774 }
6775
6776 return '<span class="fa fa-' . $brand . ' fa-fw' . ($morecss ? ' ' . $morecss : '') . '"></span>';
6777}
6778
6787function img_mime($file, $titlealt = '', $morecss = '')
6788{
6789 require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
6790
6791 $mimetype = dol_mimetype($file, '', 1);
6792 //$mimeimg = dol_mimetype($file, '', 2);
6793 $mimefa = dol_mimetype($file, '', 4);
6794
6795 if (empty($titlealt)) {
6796 $titlealt = 'Mime type: ' . $mimetype;
6797 }
6798
6799 //return img_picto_common($titlealt, 'mime/'.$mimeimg, 'class="'.$morecss.'"');
6800 return '<i class="fa fa-' . $mimefa . ' ' . (preg_match('/pictofixedwidth/', $morecss) ? '' : 'paddingright ') . ($morecss ? ' ' . $morecss : '') . '"' . ($titlealt ? ' title="' . dolPrintHTMLForAttribute($titlealt) . '"' : '') . '></i>';
6801}
6802
6803
6811function img_search($titlealt = 'default', $other = '')
6812{
6813 global $langs;
6814
6815 if ($titlealt == 'default') {
6816 $titlealt = $langs->trans('Search');
6817 }
6818
6819 $img = img_picto($titlealt, 'search', $other, 0, 1);
6820
6821 $input = '<input type="image" class="liste_titre" name="button_search" src="' . $img . '" ';
6822 $input .= 'value="' . dol_escape_htmltag($titlealt) . '" title="' . dol_escape_htmltag($titlealt) . '" >';
6823
6824 return $input;
6825}
6826
6834function img_searchclear($titlealt = 'default', $other = '')
6835{
6836 global $langs;
6837
6838 if ($titlealt == 'default') {
6839 $titlealt = $langs->trans('Search');
6840 }
6841
6842 $img = img_picto($titlealt, 'searchclear.png', $other, 0, 1);
6843
6844 $input = '<input type="image" class="liste_titre" name="button_removefilter" src="' . $img . '" ';
6845 $input .= 'value="' . dol_escape_htmltag($titlealt) . '" title="' . dol_escape_htmltag($titlealt) . '" >';
6846
6847 return $input;
6848}
6849
6864function info_admin($text, $infoonimgalt = 0, $nodiv = 0, $admin = '1', $morecss = 'hideonsmartphone', $textfordropdown = '', $picto = '', $textonpictotooltip = '', $cssfordropdown = 'info_admin')
6865{
6866 global $conf, $langs;
6867
6868 if ($infoonimgalt) {
6869 $result = img_picto($text, 'info', 'class="' . ($morecss ? ' ' . $morecss : '') . '"');
6870 } else {
6871 if (empty($conf->use_javascript_ajax)) {
6872 $textfordropdown = '';
6873 }
6874
6875 $class = (empty($admin) ? 'undefined' : ((string) $admin == '1' ? 'info' : $admin));
6876 $fa = 'info-circle';
6877 if ($picto == 'warning') {
6878 $fa = 'exclamation-triangle';
6879 }
6880 $result = ($nodiv ? '' : '<div class="wordbreak ' . $class . ($cssfordropdown ? ' ' . $cssfordropdown : '') . ($morecss ? ' ' . $morecss : '') . ($textfordropdown ? ' hidden' : '') . '">');
6881 $result .= img_picto(((string) $admin ? $langs->trans('InfoAdmin') : $langs->trans('Note')).($textonpictotooltip ? ' : '.$textonpictotooltip : ''), $fa);
6882 $result .= ' ';
6883 $result .= dol_escape_htmltag($text, 1, 0, 'div,span,b,br,a');
6884 $result .= ($nodiv ? '' : '</div>');
6885
6886 if ($textfordropdown) {
6887 $tmpresult = '<span class="' . $class . ' '. $cssfordropdown.'text opacitymedium cursorpointer">' . $langs->trans($textfordropdown) . ' ' . img_picto($langs->trans($textfordropdown), '1downarrow') . '</span>';
6888 $tmpresult .= '<script nonce="' . getNonce() . '" type="text/javascript">
6889 jQuery(document).ready(function() {
6890 jQuery(".' . $cssfordropdown . 'text").click(function() {
6891 console.log("toggle text of .'.$cssfordropdown.'");
6892 jQuery(".' . $cssfordropdown . '").toggle().removeClass("hidden");
6893 });
6894 });
6895 </script>';
6896
6897 $result = $tmpresult . $result;
6898 }
6899 }
6900
6901 return $result;
6902}
6903
6904
6916function dol_print_error($db = null, $error = '', $errors = null)
6917{
6918 global $conf, $langs, $user, $argv;
6919 global $dolibarr_main_prod;
6920
6921 $out = '';
6922 $syslog = '';
6923
6924 // If error occurs before the $lang object was loaded
6925 if (!$langs) {
6926 require_once DOL_DOCUMENT_ROOT . '/core/class/translate.class.php';
6927 $langs = new Translate('', $conf);
6928 $langs->load("main");
6929 }
6930
6931 // Load translation files required by the error messages
6932 $langs->loadLangs(array('main', 'errors'));
6933
6934 if ($_SERVER['DOCUMENT_ROOT']) { // Mode web
6935 $out .= $langs->trans("DolibarrHasDetectedError") . ".<br>\n";
6936 if (getDolGlobalInt('MAIN_FEATURES_LEVEL') > 0) {
6937 $out .= "You use an experimental or develop level of features, so please do NOT report any bugs or vulnerability, except if problem is confirmed after moving option MAIN_FEATURES_LEVEL back to 0.<br>\n";
6938 }
6939 $out .= $langs->trans("InformationToHelpDiagnose") . ":<br>\n";
6940
6941 $out .= "<b>" . $langs->trans("Date") . ":</b> " . dol_print_date(time(), 'dayhourlog') . "<br>\n";
6942 $out .= "<b>" . $langs->trans("Dolibarr") . ":</b> " . DOL_VERSION . " - https://www.dolibarr.org<br>\n";
6943 if (isset($conf->global->MAIN_FEATURES_LEVEL)) {
6944 $out .= "<b>" . $langs->trans("LevelOfFeature") . ":</b> " . getDolGlobalInt('MAIN_FEATURES_LEVEL') . "<br>\n";
6945 }
6946 if ($user instanceof User) {
6947 $out .= "<b>" . $langs->trans("Login") . ":</b> " . $user->login . "<br>\n";
6948 }
6949 if (function_exists("phpversion")) {
6950 $out .= "<b>" . $langs->trans("PHP") . ":</b> " . phpversion() . "<br>\n";
6951 }
6952 $out .= "<b>" . $langs->trans("Server") . ":</b> " . (isset($_SERVER["SERVER_SOFTWARE"]) ? dol_htmlentities($_SERVER["SERVER_SOFTWARE"], ENT_COMPAT) : '') . "<br>\n";
6953 if (function_exists("php_uname")) {
6954 $out .= "<b>" . $langs->trans("OS") . ":</b> " . php_uname() . "<br>\n";
6955 }
6956 $out .= "<b>" . $langs->trans("UserAgent") . ":</b> " . (isset($_SERVER["HTTP_USER_AGENT"]) ? dol_htmlentities($_SERVER["HTTP_USER_AGENT"], ENT_COMPAT) : '') . "<br>\n";
6957 $out .= "<br>\n";
6958 $out .= "<b>" . $langs->trans("RequestedUrl") . ":</b> " . (isset($_SERVER["REQUEST_URI"]) ? dol_htmlentities($_SERVER["REQUEST_URI"], ENT_COMPAT) : '') . "<br>\n";
6959 $out .= "<b>" . $langs->trans("Referer") . ":</b> " . (isset($_SERVER["HTTP_REFERER"]) ? dol_htmlentities($_SERVER["HTTP_REFERER"], ENT_COMPAT) : '') . "<br>\n";
6960 $out .= "<b>" . $langs->trans("MenuManager") . ":</b> " . (isset($conf->standard_menu) ? dol_htmlentities($conf->standard_menu, ENT_COMPAT) : '') . "<br>\n";
6961 $out .= "<br>\n";
6962 $syslog .= "url=" . (isset($_SERVER["REQUEST_URI"]) ? dol_escape_htmltag($_SERVER["REQUEST_URI"]) : '');
6963 $syslog .= ", query_string=" . (isset($_SERVER["QUERY_STRING"]) ? dol_escape_htmltag($_SERVER["QUERY_STRING"]) : '');
6964 } else { // Mode CLI
6965 $out .= '> ' . $langs->transnoentities("ErrorInternalErrorDetected") . ":\n" . $argv[0] . "\n";
6966 $syslog .= "pid=" . dol_getmypid();
6967 }
6968
6969 if (!empty($conf->modules)) {
6970 $out .= "<b>" . $langs->trans("Modules") . ":</b> " . implode(', ', $conf->modules) . "<br>\n";
6971 }
6972
6973 if (is_object($db)) {
6974 if ($_SERVER['DOCUMENT_ROOT']) { // Mode web
6975 $out .= "<b>" . $langs->trans("DatabaseTypeManager") . ":</b> " . $db->type . "<br>\n";
6976 $lastqueryerror = $db->lastqueryerror();
6977 if (!utf8_check($lastqueryerror)) {
6978 $lastqueryerror = "SQL error string is not a valid UTF8 string. We can't show it.";
6979 }
6980 $out .= "<b>" . $langs->trans("RequestLastAccessInError") . ":</b> " . ($lastqueryerror ? dol_escape_htmltag($lastqueryerror) : $langs->trans("ErrorNoRequestInError")) . "<br>\n";
6981 $out .= "<b>" . $langs->trans("ReturnCodeLastAccessInError") . ":</b> " . ($db->lasterrno() ? dol_escape_htmltag($db->lasterrno()) : $langs->trans("ErrorNoRequestInError")) . "<br>\n";
6982 $out .= "<b>" . $langs->trans("InformationLastAccessInError") . ":</b> " . ($db->lasterror() ? dol_escape_htmltag($db->lasterror()) : $langs->trans("ErrorNoRequestInError")) . "<br>\n";
6983 $out .= "<br>\n";
6984 } else { // Mode CLI
6985 // No dol_escape_htmltag for output, we are in CLI mode
6986 $out .= '> ' . $langs->transnoentities("DatabaseTypeManager") . ":\n" . $db->type . "\n";
6987 $out .= '> ' . $langs->transnoentities("RequestLastAccessInError") . ":\n" . ($db->lastqueryerror() ? $db->lastqueryerror() : $langs->transnoentities("ErrorNoRequestInError")) . "\n";
6988 $out .= '> ' . $langs->transnoentities("ReturnCodeLastAccessInError") . ":\n" . ($db->lasterrno() ? $db->lasterrno() : $langs->transnoentities("ErrorNoRequestInError")) . "\n";
6989 $out .= '> ' . $langs->transnoentities("InformationLastAccessInError") . ":\n" . ($db->lasterror() ? $db->lasterror() : $langs->transnoentities("ErrorNoRequestInError")) . "\n";
6990 }
6991 $syslog .= ", sql=" . $db->lastquery();
6992 $syslog .= ", db_error=" . $db->lasterror();
6993 }
6994
6995 if ($error || $errors) {
6996 // Merge all into $errors array
6997 if (is_array($error) && is_array($errors)) {
6998 $errors = array_merge($error, $errors);
6999 } elseif (is_array($error)) { // deprecated, use second parameters
7000 $errors = $error;
7001 } elseif (is_array($errors) && !empty($error)) {
7002 $errors = array_merge(array($error), $errors);
7003 } elseif (!empty($error)) {
7004 $errors = array_merge(array($error), array($errors));
7005 }
7006
7007 $langs->load("errors");
7008
7009 foreach ($errors as $msg) {
7010 if (empty($msg)) {
7011 continue;
7012 }
7013 if ($_SERVER['DOCUMENT_ROOT']) { // Mode web
7014 $out .= "<b>" . $langs->trans("Message") . ":</b> " . dol_escape_htmltag($msg) . "<br>\n";
7015 } else { // Mode CLI
7016 $out .= '> ' . $langs->transnoentities("Message") . ":\n" . $msg . "\n";
7017 }
7018 $syslog .= ", msg=" . $msg;
7019 }
7020 }
7021 if (empty($dolibarr_main_prod) && $_SERVER['DOCUMENT_ROOT'] && function_exists('xdebug_print_function_stack') && function_exists('xdebug_call_file')) {
7022 xdebug_print_function_stack();
7023 $out .= '<b>XDebug information:</b>' . "<br>\n";
7024 $out .= 'File: ' . xdebug_call_file() . "<br>\n";
7025 $out .= 'Line: ' . xdebug_call_line() . "<br>\n";
7026 $out .= 'Function: ' . xdebug_call_function() . "<br>\n";
7027 $out .= "<br>\n";
7028 }
7029
7030 // Return a http header with error code if possible
7031 if (!headers_sent()) {
7032 if (function_exists('top_httphead')) { // In CLI context, the method does not exists
7033 top_httphead();
7034 }
7035 //http_response_code(500); // If we use 500, message is not output with some command line tools
7036 http_response_code(202); // If we use 202, this is not really an error message, but this allow to output message on command line tools
7037 }
7038
7039 if (empty($dolibarr_main_prod)) {
7040 print $out;
7041 } else {
7042 if (empty($langs->defaultlang)) {
7043 $langs->setDefaultLang();
7044 }
7045 $langs->loadLangs(array("main", "errors")); // Reload main because language may have been set only on previous line so we have to reload files we need.
7046 // This should not happen, except if there is a bug somewhere. Enabled and check log in such case.
7047 print 'This website or feature is currently temporarily not available or failed after a technical error.<br><br>This may be due to a maintenance operation. Current status of operation (' . dol_print_date(dol_now(), 'dayhourrfc') . ') are on next line...<br><br>' . "\n";
7048 print $langs->trans("DolibarrHasDetectedError") . '. ';
7049 print $langs->trans("YouCanSetOptionDolibarrMainProdToZero");
7050 if (!defined("MAIN_CORE_ERROR")) {
7051 define("MAIN_CORE_ERROR", 1);
7052 }
7053 }
7054
7055 dol_syslog("Error " . $syslog, LOG_ERR);
7056}
7057
7068function dol_print_error_email($prefixcode, $errormessage = '', $errormessages = array(), $morecss = 'error', $email = '')
7069{
7070 global $langs;
7071
7072 if (empty($email)) {
7073 $email = getDolGlobalString('MAIN_INFO_SOCIETE_MAIL');
7074 }
7075
7076 $langs->load("errors");
7077 $now = dol_now();
7078
7079 print '<br><div class="center login_main_message"><div class="' . $morecss . '">';
7080 print $langs->trans("ErrorContactEMail", $email, $prefixcode . '-' . dol_print_date($now, '%Y%m%d%H%M%S'));
7081 if ($errormessage) {
7082 print '<br><br>' . $errormessage;
7083 }
7084 if (is_array($errormessages) && count($errormessages)) {
7085 foreach ($errormessages as $mesgtoshow) {
7086 print '<br><br>' . $mesgtoshow;
7087 }
7088 }
7089 print '</div></div>';
7090}
7091
7108function print_liste_field_titre($name, $file = "", $field = "", $begin = "", $param = "", $moreattrib = "", $sortfield = "", $sortorder = "", $prefix = "", $tooltip = "", $forcenowrapcolumntitle = 0)
7109{
7110 print getTitleFieldOfList($name, 0, $file, $field, $begin, $param, $moreattrib, $sortfield, $sortorder, $prefix, 0, $tooltip, $forcenowrapcolumntitle);
7111}
7112
7131function getTitleFieldOfList($name, $thead = 0, $file = "", $field = "", $begin = "", $moreparam = "", $moreattrib = "", $sortfield = "", $sortorder = "", $prefix = "", $disablesortlink = 0, $tooltip = '', $forcenowrapcolumntitle = 0)
7132{
7133 global $langs, $form;
7134 //print "$name, $file, $field, $begin, $options, $moreattrib, $sortfield, $sortorder<br>\n";
7135
7136 if ($moreattrib == 'class="right"') {
7137 $prefix .= 'right '; // For backward compatibility
7138 }
7139
7140 $tooltip = (string) $tooltip; // In case $tooltip is null
7141
7142 $sortorder = strtoupper((string) $sortorder);
7143 $out = '';
7144 $sortimg = '';
7145
7146 $tag = 'th';
7147 if ($thead == 2) {
7148 $tag = 'div';
7149 }
7150
7151 $tmpsortfield = explode(',', (string) $sortfield);
7152 $sortfield1 = trim($tmpsortfield[0]); // If $sortfield is 'd.datep,d.id', it becomes 'd.datep'
7153 $tmpfield = explode(',', $field);
7154 $field1 = trim($tmpfield[0]); // If $field is 'd.datep,d.id', it becomes 'd.datep'
7155
7156 if (strpos((string) $tooltip, ':') !== false) {
7157 $tmptooltip = explode(':', (string) $tooltip);
7158 } else {
7159 $tmptooltip = array($tooltip);
7160 }
7161
7162 $wrapcolumntitle = (empty($forcenowrapcolumntitle) || (!empty($tmptooltip[2]) && $tmptooltip[2] == '-1'));
7163
7164 if (!getDolGlobalString('MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE') && $wrapcolumntitle) {
7165 $prefix = 'wrapcolumntitle ' . $prefix;
7166 }
7167
7168 //var_dump('field='.$field.' field1='.$field1.' sortfield='.$sortfield.' sortfield1='.$sortfield1);
7169 // If field is used as sort criteria we use a specific css class liste_titre_sel
7170 // Example if (sortfield,field)=("nom","xxx.nom") or (sortfield,field)=("nom","nom")
7171 $liste_titre = 'liste_titre';
7172 if ($field1 && ($sortfield1 == $field1 || $sortfield1 == preg_replace("/^[^\.]+\./", "", $field1))) {
7173 $liste_titre = 'liste_titre_sel';
7174 }
7175
7176 $tagstart = '<' . $tag . ' class="' . $prefix . $liste_titre . '" ' . $moreattrib;
7177 //$out .= (($field && empty($conf->global->MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE) && preg_match('/^[a-zA-Z_0-9\s\.\-:&;]*$/', $name)) ? ' title="'.dol_escape_htmltag($langs->trans($name)).'"' : '');
7178 $tagstart .= ($name && !getDolGlobalString('MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE') && $wrapcolumntitle && !dol_textishtml($name)) ? ' title="' . dolPrintHTMLForAttribute($langs->trans($name)) . '"' : '';
7179 $tagstart .= '>';
7180
7181 if (empty($thead) && $field && empty($disablesortlink)) { // If this is a sort field
7182 $options = preg_replace('/sortfield=([a-zA-Z0-9,\s\.]+)/i', '', (is_scalar($moreparam) ? $moreparam : ''));
7183 $options = preg_replace('/sortorder=([a-zA-Z0-9,\s\.]+)/i', '', $options);
7184 $options = preg_replace('/&+/i', '&', $options);
7185 if (!preg_match('/^&/', $options)) {
7186 $options = '&' . $options;
7187 }
7188
7189 $sortordertouseinlink = '';
7190 if ($field1 != $sortfield1) { // We are on another field than current sorted field
7191 if (preg_match('/^DESC/i', $sortorder)) {
7192 $sortordertouseinlink .= str_repeat('desc,', count(explode(',', $field)));
7193 } else { // We reverse the var $sortordertouseinlink
7194 $sortordertouseinlink .= str_repeat('asc,', count(explode(',', $field)));
7195 }
7196 } else { // We are on field that is the first current sorting criteria
7197 if (preg_match('/^ASC/i', $sortorder)) { // We reverse the var $sortordertouseinlink
7198 $sortordertouseinlink .= str_repeat('desc,', count(explode(',', $field)));
7199 } else {
7200 $sortordertouseinlink .= str_repeat('asc,', count(explode(',', $field)));
7201 }
7202 }
7203 $sortordertouseinlink = preg_replace('/,$/', '', $sortordertouseinlink);
7204 $out .= '<a class="reposition" href="' . dolBuildUrl($file, ['sortfield' => $field, 'sortorder' => $sortordertouseinlink, 'begin' => $begin]) . $options . '"';
7205 //$out .= (getDolGlobalString('MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE') ? '' : ' title="'.dol_escape_htmltag($langs->trans($name)).'"');
7206 $out .= '>';
7207 }
7208 if ($tooltip && $tmptooltip[0]) {
7209 // You can also use 'TranslationString:[keyfortooltiponclick]:[tooltipdirection]' for a tooltip on click or to change tooltip position.
7210 $out .= $form->textwithpicto($langs->trans((string) $name), $langs->trans((string) $tmptooltip[0]), (empty($tmptooltip[2]) ? '1' : $tmptooltip[2]), 'help', ((!empty($tmptooltip[2]) && $tmptooltip[2] == '-1') ? 'paddingrightonly' : ''), 0, 3, (empty($tmptooltip[1]) ? '' : 'extra_' . str_replace('.', '_', $field) . '_' . $tmptooltip[1]));
7211 } else {
7212 $out .= $langs->trans((string) $name);
7213 }
7214
7215 if (empty($thead) && $field && empty($disablesortlink)) { // If this is a sort field
7216 $out .= '</a>';
7217 }
7218
7219 if (empty($thead) && $field) { // If this is a sort field
7220 $options = preg_replace('/sortfield=([a-zA-Z0-9,\s\.]+)/i', '', (is_scalar($moreparam) ? $moreparam : ''));
7221 $options = preg_replace('/sortorder=([a-zA-Z0-9,\s\.]+)/i', '', $options);
7222 $options = preg_replace('/&+/i', '&', $options);
7223 if (!preg_match('/^&/', $options)) {
7224 $options = '&' . $options;
7225 }
7226
7227 if (!$sortorder || ($field1 != $sortfield1)) {
7228 // Nothing
7229 } else {
7230 if (preg_match('/^DESC/', $sortorder)) {
7231 $sortimg .= '<span class="nowrap">' . img_up("Z-A", 0, 'paddingright') . '</span>';
7232 }
7233 if (preg_match('/^ASC/', $sortorder)) {
7234 $sortimg .= '<span class="nowrap">' . img_down("A-Z", 0, 'paddingright') . '</span>';
7235 }
7236 }
7237 }
7238
7239 $tagend = '</' . $tag . '>';
7240
7241 $out = $tagstart . $sortimg . $out . $tagend;
7242
7243 return $out;
7244}
7245
7254function print_titre($title)
7255{
7256 dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING);
7257
7258 print '<div class="titre">' . $title . '</div>';
7259}
7260
7272function print_fiche_titre($title, $mesg = '', $picto = 'generic', $pictoisfullpath = 0, $id = '')
7273{
7274 print load_fiche_titre($title, $mesg, $picto, $pictoisfullpath, $id);
7275}
7276
7291function load_fiche_titre($title, $morehtmlright = '', $picto = 'generic', $pictoisfullpath = 0, $id = '', $morecssontable = '', $morehtmlcenter = '', $morecssonpicto = 'widthpictotitle')
7292{
7293 $return = '';
7294
7295 if ($picto == 'setup') {
7296 $picto = 'generic';
7297 }
7298
7299 $return .= "\n";
7300 $return .= '<table ' . ($id ? 'id="' . $id . '" ' : '') . 'class="centpercent notopnoleftnoright table-fiche-title' . ($morecssontable ? ' ' . $morecssontable : '') . '">'; // margin bottom must be same than into print_barre_list
7301 $return .= '<tr class="toptitle">';
7302 if ($picto) {
7303 $return .= '<td class="nobordernopadding widthpictotitle valignmiddle col-picto">' . img_picto('', $picto, 'class="valignmiddle pictotitle'.($morecssonpicto ? ' '.$morecssonpicto : '').'"', $pictoisfullpath) . '</td>';
7304 }
7305 $return .= '<td class="nobordernopadding valignmiddle col-title">';
7306 $return .= '<div class="titre inline-block">';
7307 $return .= '<span class="inline-block valignmiddle print-barre-liste">' . $title . '</span>'; // $title is already HTML sanitized content
7308 $return .= '</div>';
7309 $return .= '</td>';
7310 if (dol_strlen($morehtmlcenter)) {
7311 $return .= '<td class="nobordernopadding center valignmiddle col-center">' . $morehtmlcenter . '</td>';
7312 }
7313 if (dol_strlen($morehtmlright)) {
7314 $return .= '<td class="nobordernopadding titre_right wordbreakimp right valignmiddle col-right">' . $morehtmlright . '</td>';
7315 }
7316 $return .= '</tr></table>' . "\n";
7317
7318 return $return;
7319}
7320
7344function print_barre_liste($title, $page, $file, $options = '', $sortfield = '', $sortorder = '', $morehtmlcenter = '', $num = -1, $totalnboflines = '', $picto = 'generic', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '', $limit = -1, $selectlimitsuffix = 0, $hidenavigation = 0, $pagenavastextinput = 0, $morehtmlrightbeforearrow = '')
7345{
7346 global $conf, $langs;
7347
7348 $savlimit = $limit;
7349 $savtotalnboflines = $totalnboflines;
7350 if (is_numeric($totalnboflines)) {
7351 $totalnboflines = abs($totalnboflines);
7352 }
7353
7354 // Detect if there is a subtitle
7355 $subtitle = '';
7356 $tmparray = preg_split('/<br>/i', $title, 2);
7357 if (!empty($tmparray[1])) {
7358 $title = $tmparray[0];
7359 $subtitle = $tmparray[1];
7360 }
7361
7362 $page = (int) $page;
7363
7364 if ($picto == 'setup') {
7365 $picto = 'title_setup';
7366 }
7367 if (($conf->browser->name == 'ie') && $picto == 'generic') {
7368 $picto = 'title.gif';
7369 }
7370 if ($limit < 0) {
7371 $limit = $conf->liste_limit;
7372 }
7373
7374 if ($savlimit != 0 && (($num > $limit) || ($num == -1) || ($limit == 0))) {
7375 $nextpage = 1;
7376 } else {
7377 $nextpage = 0;
7378 }
7379 //print 'totalnboflines='.$totalnboflines.'-savlimit='.$savlimit.'-limit='.$limit.'-num='.$num.'-nextpage='.$nextpage.'-selectlimitsuffix='.$selectlimitsuffix.'-hidenavigation='.$hidenavigation;
7380
7381 print "\n";
7382 print "<!-- Begin print_barre_liste -->\n";
7383 print '<table class="centpercent notopnoleftnoright table-fiche-title' . ($morecss ? ' ' . $morecss : '') . '">';
7384 print '<tr class="toptitle">'; // margin bottom must be same than into load_fiche_tire
7385
7386 // Left
7387
7388 if ($picto && $title) {
7389 print '<td class="nobordernopadding widthpictotitle valignmiddle col-picto">';
7390 print img_picto('', $picto, 'class="valignmiddle pictotitle widthpictotitle"', $pictoisfullpath);
7391 print '</td>';
7392 }
7393
7394 print '<td class="nobordernopadding valignmiddle col-title">';
7395 print '<div class="titre inline-block nowrap">';
7396 print '<span class="inline-block valignmiddle print-barre-liste">' . $title . '</span>'; // $title may contains HTML like a combo list from page consumption.php, so we do not use dolPrintLabel here()
7397 if (!empty($title) && $savtotalnboflines >= 0 && (string) $savtotalnboflines != '') {
7398 if (is_numeric($totalnboflines) && (int) $totalnboflines > 0) {
7399 print '<span class="opacitymedium colorblack marginleftonly totalnboflines valignmiddle" title="' . $langs->trans("NbRecordQualified") . '">(' . $totalnboflines . ')</span>';
7400 } else {
7401 print '<span class="opacitymedium colorblack marginleftonly totalnboflines valignmiddle">(' . $totalnboflines . ')</span>';
7402 }
7403 }
7404 print '</div>';
7405 if (!empty($subtitle)) {
7406 print '<br><div class="subtitle inline-block hideonsmartphone">' . $subtitle . '</div>';
7407 }
7408 print '</td>';
7409
7410 // Center
7411 if ($morehtmlcenter && empty($conf->dol_optimize_smallscreen)) {
7412 print '<td class="nobordernopadding center valignmiddle col-center">' . $morehtmlcenter . '</td>';
7413 }
7414
7415 // Right
7416 print '<td class="nobordernopadding valignmiddle right col-right">';
7417 print '<input type="hidden" name="pageplusoneold" value="' . ((int) $page + 1) . '">';
7418 $query = [];
7419 parse_str($options, $query);
7420 if ($sortfield) {
7421 $query += ['sortfield' => $sortfield];
7422 }
7423 if ($sortorder) {
7424 $query += ['sortorder' => $sortorder];
7425 }
7426
7427 $options = '&' . http_build_query($query);
7428 if ($page) {
7429 $query = array_merge($query, ['page' => $page]);
7430 }
7431 // Show navigation bar
7432 $pagelist = '';
7433 if ($savlimit != 0 && ($page > 0 || $num > $limit)) {
7434 if ($totalnboflines) { // If we know total nb of lines
7435 // Define nb of extra page links before and after selected page + ... + first or last
7436 $maxnbofpage = (empty($conf->dol_optimize_smallscreen) ? 4 : 0);
7437
7438 if ($limit > 0) {
7439 $nbpages = ceil($totalnboflines / $limit);
7440 } else {
7441 $nbpages = 1;
7442 }
7443 $cpt = ($page - $maxnbofpage);
7444 if ($cpt < 0) {
7445 $cpt = 0;
7446 }
7447
7448 if ($cpt >= 1) {
7449 if (empty($pagenavastextinput)) {
7450 $query['page'] = 0;
7451 $pagelist .= '<li class="pagination"><a class="reposition" href="' . dolBuildUrl($file, $query) . '">1</a></li>';
7452 if ($cpt > 2) {
7453 $pagelist .= '<li class="pagination"><span class="inactive">...</span></li>';
7454 } elseif ($cpt == 2) {
7455 $query['page'] = 0;
7456 $pagelist .= '<li class="pagination"><a class="reposition" href="' . dolBuildUrl($file, $query) . '">2</a></li>';
7457 }
7458 }
7459 }
7460
7461 do {
7462 if ($pagenavastextinput) {
7463 if ($cpt == $page) {
7464 $pagelist .= '<li class="pagination pageplusone valignmiddle"><input type="text" class="' . ($totalnboflines > 100 ? 'width40' : 'width25') . ' center pageplusone heightofcombo" name="pageplusone" value="' . ($page + 1) . '"></li>';
7465 $pagelist .= '/';
7466 }
7467 } else {
7468 if ($cpt == $page) {
7469 $pagelist .= '<li class="pagination"><span class="active">' . ($page + 1) . '</span></li>';
7470 } else {
7471 $query['page'] = $cpt;
7472 $pagelist .= '<li class="pagination"><a class="reposition" href="' . dolBuildUrl($file, $query) . '">' . ($cpt + 1) . '</a></li>';
7473 }
7474 }
7475 $cpt++;
7476 } while ($cpt < $nbpages && $cpt <= ($page + $maxnbofpage));
7477
7478 if (empty($pagenavastextinput)) {
7479 if ($cpt < $nbpages) {
7480 if ($cpt < $nbpages - 2) {
7481 $pagelist .= '<li class="pagination"><span class="inactive">...</span></li>';
7482 } elseif ($cpt == $nbpages - 2) {
7483 $query['page'] = ($nbpages - 2);
7484 $pagelist .= '<li class="pagination"><a class="reposition" href="' . dolBuildUrl($file, $query) . '">' . ($nbpages - 1) . '</a></li>';
7485 }
7486 $query['page'] = ($nbpages - 1);
7487 $pagelist .= '<li class="pagination"><a class="reposition" href="' . dolBuildUrl($file, $query) . '">' . $nbpages . '</a></li>';
7488 }
7489 } else {
7490 $query['page'] = ($nbpages - 1);
7491 $pagelist .= '<li class="pagination paginationlastpage"><a class="reposition" href="' . dolBuildUrl($file, $query) . '">' . $nbpages . '</a></li>';
7492 }
7493 } else {
7494 $pagelist .= '<li class="pagination"><span class="active">' . ($page + 1) . "</li>";
7495 }
7496 }
7497
7498 if ($savlimit || $morehtmlright || $morehtmlrightbeforearrow) {
7499 // Show the combolist to select number of record per page and the navigation arrows.
7500 print_fleche_navigation($page, $file, $options, $nextpage, $pagelist, $morehtmlright, $savlimit, $totalnboflines, $selectlimitsuffix, $morehtmlrightbeforearrow, $hidenavigation); // output the div and ul for previous/last completed with page numbers into $pagelist
7501 }
7502
7503 // js to autoselect page field on focus
7504 if ($pagenavastextinput) {
7505 print ajax_autoselect('.pageplusone');
7506 }
7507
7508 print '</td>';
7509 print '</tr>';
7510
7511 print "</table>\n";
7512
7513 // Center
7514 if ($morehtmlcenter && !empty($conf->dol_optimize_smallscreen)) {
7515 print '<div class="nobordernopadding marginbottomonly center valignmiddle col-center centpercent">' . $morehtmlcenter . '</div>';
7516 }
7517
7518 print "<!-- End title -->\n\n";
7519}
7520
7537function print_fleche_navigation($page, $file, $options = '', $nextpage = 0, $betweenarrows = '', $afterarrows = '', $limit = -1, $totalnboflines = 0, $selectlimitsuffix = '', $beforearrows = '', $hidenavigation = 0)
7538{
7539 global $conf, $langs;
7540
7541 print '<div class="pagination"><ul>';
7542 if ($beforearrows) {
7543 print '<li class="paginationbeforearrows">';
7544 print $beforearrows;
7545 print '</li>';
7546 }
7547
7548 if (empty($hidenavigation)) {
7549 if ((int) $limit > 0 && (empty($selectlimitsuffix) || !is_numeric($selectlimitsuffix))) {
7550 $pagesizechoices = '10:10,15:15,20:20,25:25,50:50,100:100,250:250,500:500,1000:1000';
7551 $pagesizechoices .= ',5000:5000';
7552 //$pagesizechoices .= ',10000:10000'; // Memory trouble on most browsers
7553 //$pagesizechoices .= ',20000:20000'; // Memory trouble on most browsers
7554 //$pagesizechoices .= ',0:'.$langs->trans("All"); // Not yet supported
7555 //$pagesizechoices .= ',2:2';
7556 if (getDolGlobalString('MAIN_PAGESIZE_CHOICES')) {
7557 $pagesizechoices = getDolGlobalString('MAIN_PAGESIZE_CHOICES');
7558 }
7559
7560 if (getDolGlobalString('MAIN_USE_HTML5_LIMIT_SELECTOR')) {
7561 print '<li class="pagination">';
7562 print '<input onfocus="this.value=null;" onchange="this.blur();" class="flat selectlimit nopadding maxwidth75 right pageplusone" id="limit" name="limit" list="limitlist" title="' . dol_escape_htmltag($langs->trans("MaxNbOfRecordPerPage")) . '" value="' . $limit . '">';
7563 print '<datalist id="limitlist">';
7564 } else {
7565 print '<li class="paginationcombolimit valignmiddle">';
7566 print '<select id="limit' . (is_numeric($selectlimitsuffix) ? '' : $selectlimitsuffix) . '" name="'.(is_numeric($selectlimitsuffix) ? 'limit' : $selectlimitsuffix).'" class="flat selectlimit nopadding maxwidth75 center' . (is_numeric($selectlimitsuffix) ? '' : ' ' . $selectlimitsuffix) . '" title="' . dol_escape_htmltag($langs->trans("MaxNbOfRecordPerPage")) . '">';
7567 }
7568 $tmpchoice = explode(',', $pagesizechoices);
7569 $tmpkey = $limit . ':' . $limit;
7570 if (!in_array($tmpkey, $tmpchoice)) {
7571 $tmpchoice[$tmpkey] = $tmpkey;
7572 }
7573 $tmpkey = $conf->liste_limit . ':' . $conf->liste_limit;
7574 if (!in_array($tmpkey, $tmpchoice)) {
7575 $tmpchoice[$tmpkey] = $tmpkey;
7576 }
7577 asort($tmpchoice, SORT_NUMERIC);
7578 foreach ($tmpchoice as $val) {
7579 $selected = '';
7580 $tmp = explode(':', $val);
7581 $key = $tmp[0];
7582 $val = $tmp[1];
7583 if ($key != '' && $val != '') {
7584 if ((int) $key == (int) $limit) {
7585 $selected = ' selected="selected"';
7586 }
7587 print '<option name="' . $key . '"' . $selected . '>' . dol_escape_htmltag($val) . '</option>' . "\n";
7588 }
7589 }
7590 if (getDolGlobalString('MAIN_USE_HTML5_LIMIT_SELECTOR')) {
7591 print '</datalist>';
7592 } else {
7593 print '</select>';
7594 print ajax_combobox("limit" . (is_numeric($selectlimitsuffix) ? '' : $selectlimitsuffix), array(), 0, 0, 'resolve', '-1', 'limit');
7595 //print ajax_combobox("limit");
7596 }
7597
7598 if ($conf->use_javascript_ajax) {
7599 print '<!-- JS CODE TO ENABLE select limit to launch submit of page -->
7600 <script>
7601 jQuery(document).ready(function () {
7602 jQuery(".selectlimit").change(function() {
7603 console.log("We change limit so we submit the form");
7604 $(this).parents(\'form:first\').submit();
7605 });
7606 });
7607 </script>
7608 ';
7609 }
7610 print '</li>';
7611 }
7612 if ($page > 0) {
7613 print '<li class="pagination paginationpage paginationpageleft"><a class="paginationprevious reposition" href="' . $file . '?page=' . ($page - 1) . $options . '"><i class="fa fa-chevron-left" title="' . dol_escape_htmltag($langs->trans("Previous")) . '"></i></a></li>';
7614 }
7615 if ($betweenarrows) {
7616 print '<!--<div class="betweenarrows nowraponall inline-block">-->';
7617 print $betweenarrows;
7618 print '<!--</div>-->';
7619 }
7620 if ($nextpage > 0) {
7621 print '<li class="pagination paginationpage paginationpageright"><a class="paginationnext reposition" href="' . $file . '?page=' . ($page + 1) . $options . '"><i class="fa fa-chevron-right" title="' . dol_escape_htmltag($langs->trans("Next")) . '"></i></a></li>';
7622 }
7623 if ($afterarrows) {
7624 print '<li class="paginationafterarrows">';
7625 print $afterarrows;
7626 print '</li>';
7627 }
7628 }
7629 print '</ul></div>' . "\n";
7630}
7631
7632
7644function vatrate($rate, $addpercent = false, $info_bits = 0, $usestarfornpr = 0, $html = 0)
7645{
7646 $morelabel = '';
7647
7648 if (preg_match('/%/', $rate)) {
7649 $rate = str_replace('%', '', $rate);
7650 $addpercent = true;
7651 }
7652 $reg = array();
7653 if (preg_match('/\‍((.*)\‍)/', $rate, $reg)) {
7654 $morelabel = ' (' . $reg[1] . ')';
7655 $rate = preg_replace('/\s*' . preg_quote($morelabel, '/') . '/', '', $rate);
7656 $morelabel = ' ' . ($html ? '<span class="opacitymedium small">' : '') . '(' . $reg[1] . ')' . ($html ? '</span>' : '');
7657 }
7658 if (preg_match('/\*/', $rate)) {
7659 $rate = str_replace('*', '', $rate);
7660 $info_bits |= 1;
7661 }
7662
7663 // If rate is '9/9/9' we don't change it. If rate is '9.000' we apply price()
7664 if (!preg_match('/\//', $rate)) {
7665 $ret = price($rate, 0, '', 0, 0) . ($addpercent ? '%' : '');
7666 } else {
7667 // TODO Split on / and output with a price2num to have clean numbers without ton of 000.
7668 $ret = $rate . ($addpercent ? '%' : '');
7669 }
7670 if (($info_bits & 1) && $usestarfornpr >= 0) {
7671 $ret .= ' *';
7672 }
7673 $ret .= $morelabel;
7674 return $ret;
7675}
7676
7677
7692function price($amount, $form = 0, $outlangs = '', $trunc = 1, $rounding = -1, $forcerounding = -1, $currency_code = '')
7693{
7694 global $langs, $conf;
7695
7696 // Clean parameters
7697 if (empty($amount)) {
7698 $amount = 0; // To have a numeric value if amount not defined or = ''
7699 }
7700 $amount = (is_numeric($amount) ? $amount : 0); // Check if amount is numeric, for example, an error occurred when amount value = o (letter) instead 0 (number)
7701 if ($rounding == -1) {
7702 $rounding = min(getDolGlobalString('MAIN_MAX_DECIMALS_UNIT'), getDolGlobalString('MAIN_MAX_DECIMALS_TOT'));
7703 }
7704 $nbdecimal = $rounding;
7705
7706 if ($outlangs === 'none') {
7707 // Use international separators
7708 $dec = '.';
7709 $thousand = '';
7710 } else {
7711 // Output separators by default (french)
7712 $dec = ',';
7713 $thousand = ' ';
7714
7715 // If $outlangs not forced, we use use language
7716 if (!($outlangs instanceof Translate)) {
7717 $outlangs = $langs;
7718 }
7719
7720 if ($outlangs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal") {
7721 $dec = $outlangs->transnoentitiesnoconv("SeparatorDecimal");
7722 }
7723 if ($outlangs->transnoentitiesnoconv("SeparatorThousand") != "SeparatorThousand") {
7724 $thousand = $outlangs->transnoentitiesnoconv("SeparatorThousand");
7725 }
7726 if ($thousand == 'None') {
7727 $thousand = '';
7728 } elseif ($thousand == 'Space') {
7729 $thousand = ' ';
7730 }
7731 }
7732 //print "outlangs=".$outlangs->defaultlang." amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
7733
7734 //print "amount=".$amount."-";
7735 $amount = str_replace(',', '.', $amount); // should be useless
7736 //print $amount."-";
7737 $data = explode('.', $amount);
7738 $decpart = isset($data[1]) ? $data[1] : '';
7739 $decpart = preg_replace('/0+$/i', '', $decpart); // Remove 0 at end of decimal part
7740 //print "decpart=".$decpart."<br>";
7741 $end = '';
7742
7743 // We increase nbdecimal if there is more decimal than asked (to not loose information)
7744 if (dol_strlen($decpart) > $nbdecimal) {
7745 $nbdecimal = dol_strlen($decpart);
7746 }
7747
7748 // If nbdecimal is higher than max to show
7749 $nbdecimalmaxshown = (int) str_replace('...', '', getDolGlobalString('MAIN_MAX_DECIMALS_SHOWN'));
7750 if ($trunc && $nbdecimal > $nbdecimalmaxshown) {
7751 $nbdecimal = $nbdecimalmaxshown;
7752 if (preg_match('/\.\.\./i', getDolGlobalString('MAIN_MAX_DECIMALS_SHOWN'))) {
7753 // If output is truncated, we show ...
7754 $end = '...';
7755 }
7756 }
7757
7758 // If force rounding
7759 if ((string) $forcerounding != '-1' && (string) $forcerounding != '') {
7760 if ($forcerounding === 'MU') {
7761 $nbdecimal = getDolGlobalInt('MAIN_MAX_DECIMALS_UNIT');
7762 } elseif ($forcerounding === 'MT') {
7763 $nbdecimal = getDolGlobalInt('MAIN_MAX_DECIMALS_TOT');
7764 } elseif ($forcerounding >= 0) {
7765 $nbdecimal = (int) $forcerounding;
7766 }
7767 }
7768
7769 // Format number
7770 $output = number_format((float) $amount, $nbdecimal, $dec, $thousand);
7771 // Add symbol of currency if requested
7772 $cursymbolbefore = $cursymbolafter = '';
7773 if ($currency_code && is_object($outlangs)) {
7774 if ($currency_code == 'auto') {
7775 $currency_code = $conf->currency;
7776 }
7777
7778 $listofcurrenciesbefore = array('AUD', 'CAD', 'CNY', 'COP', 'CLP', 'GBP', 'HKD', 'MXN', 'PEN', 'USD', 'CRC', 'ZAR');
7779 $listoflanguagesbefore = array('nl_NL');
7780 if (in_array($currency_code, $listofcurrenciesbefore) || in_array($outlangs->defaultlang, $listoflanguagesbefore)) {
7781 $cursymbolbefore .= $outlangs->getCurrencySymbol($currency_code);
7782 } else {
7783 $tmpcur = $outlangs->getCurrencySymbol($currency_code);
7784 $cursymbolafter .= ($tmpcur == $currency_code ? ' ' . $tmpcur : $tmpcur);
7785 }
7786 }
7787 if ($form) {
7788 $output = preg_replace('/\s/', '&nbsp;', $output);
7789 $output = $cursymbolbefore . $output . $end . ($cursymbolafter ? ' <span class="small">'.$cursymbolafter.'</span>' : '');
7790 $output = preg_replace('/\'/', '&#039;', $output);
7791 } else {
7792 $output = $cursymbolbefore . $output . $end . ($cursymbolafter ? ' '.$cursymbolafter : '');
7793 }
7794
7795 return $output;
7796}
7797
7823function price2num($amount, $rounding = '', $option = 0)
7824{
7825 global $langs;
7826
7827 // Clean parameters
7828 if (is_null($amount)) {
7829 $amount = '';
7830 }
7831
7832 // Round PHP function does not allow number like '1,234.56' nor '1.234,56' nor '1 234,56'
7833 // Numbers must be '1234.56'
7834 // Decimal delimiter for PHP and database SQL requests must be '.'
7835 $dec = ',';
7836 $thousand = ' ';
7837 if (is_null($langs)) { // $langs is not defined, we use english values.
7838 $dec = '.';
7839 $thousand = ',';
7840 } else {
7841 if ($langs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal") {
7842 $dec = $langs->transnoentitiesnoconv("SeparatorDecimal");
7843 }
7844 if ($langs->transnoentitiesnoconv("SeparatorThousand") != "SeparatorThousand") {
7845 $thousand = $langs->transnoentitiesnoconv("SeparatorThousand");
7846 }
7847 }
7848 if ($thousand == 'None') {
7849 $thousand = '';
7850 } elseif ($thousand == 'Space') {
7851 $thousand = ' ';
7852 }
7853 //print "amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
7854
7855 // Convert value to universal number format (no thousand separator, '.' as decimal separator)
7856 if ($option != 1) { // If not a PHP number or unknown, we change or clean format
7857 //print "\n".'PP'.$amount.' - '.$dec.' - '.$thousand.' - '.intval($amount).'<br>';
7858 if (!is_numeric($amount)) {
7859 $amount = preg_replace('/[a-zA-Z\/\\\*\‍(\‍)<>\_]/', '', $amount);
7860 }
7861
7862 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
7863 $amount = str_replace($thousand, '', $amount);
7864 }
7865
7866 // Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
7867 // to format defined by LC_NUMERIC after a calculation and we want source format to be like defined by Dolibarr setup.
7868 // So if number was already a good number, it is converted into local Dolibarr setup.
7869 if (is_numeric($amount)) {
7870 // We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
7871 $temps = sprintf("%10.10F", $amount - intval($amount)); // temps=0.0000000000 or 0.0000200000 or 9999.1000000000
7872 $temps = preg_replace('/([\.1-9])0+$/', '\\1', $temps); // temps=0. or 0.00002 or 9999.1
7873 $nbofdec = max(0, dol_strlen($temps) - 2); // -2 to remove "0."
7874 $amount = number_format($amount, $nbofdec, $dec, $thousand);
7875 }
7876 //print "QQ".$amount."<br>\n";
7877
7878 // Now make replaceents (the main goal of function)
7879
7880 if ($thousand != ',' && $thousand != '.') {
7881 // Accept the two types of decimal points french users (i.e., using ' ' for thousands)
7882
7883 // REGEX: Find the integral and decimal parts.
7884 //
7885 // We require that the decimal point only appears once in $amount.
7886 // The regex `/^(?<int>[^,]*,|[^.]*\.)(?<dec>[^.,]*)$/u` can be broken down as follows:
7887 // - `(?<int>[^,]*,|[^.]*\.)` is any accepted sequence up to the last potential decimal point '.' or ',' and named `int`.
7888 // It covers two cases:
7889 // - `[^,]*,`: Any sequence of characters that is not ',' with ',' accepted as the decimal point (from start of string because of earlier `^`);
7890 // - `[^.]*\.`: Any sequence of characters that is not a '.' with '.' accepted as the decimal point (from start of string.
7891 // - `(?<dec>[^.,]*)`: The sequence after the character accepted as the decimal point, not including it.
7892 $matches = array();
7893 if (preg_match('/^(?<int>[^,]*,|[^.]*\.)(?<dec>[^.,]*)$/u', $amount, $matches)) {
7894 $intPart = $matches['int'];
7895 $decPart = $matches['dec'];
7896
7897 // Remove all commas and dots from intPart
7898 $intPart = str_replace(['.', ','], '', $intPart);
7899
7900 // Combine intPart and decPart with a dot
7901 $amount = $intPart . $dec . $decPart;
7902 }
7903 }
7904
7905 $amount = str_replace(' ', '', $amount); // To avoid spaces
7906 $amount = str_replace($thousand, '', $amount); // Replace of thousand before replace of dec to avoid pb if thousand is .
7907 $amount = str_replace($dec, '.', $amount);
7908
7909 $amount = preg_replace('/[^0-9\-\.]/', '', $amount); // Clean non numeric chars (so it clean some UTF8 spaces for example.
7910 }
7911 //print ' XX'.$amount.' '.$rounding;
7912
7913 // Now, $amount is a real PHP float number. We make a rounding if required.
7914 if ($rounding) {
7915 $nbofdectoround = '';
7916 if ($rounding == 'MU') {
7917 $nbofdectoround = getDolGlobalInt('MAIN_MAX_DECIMALS_UNIT'); // usually 5
7918 } elseif ($rounding == 'MT') {
7919 $nbofdectoround = getDolGlobalInt('MAIN_MAX_DECIMALS_TOT'); // usually 2 or 3
7920 } elseif ($rounding == 'MS') {
7921 $nbofdectoround = getDolGlobalInt('MAIN_MAX_DECIMALS_STOCK', 5);
7922 } elseif ($rounding == 'CU') {
7923 $nbofdectoround = getDolGlobalInt('MAIN_MAX_DECIMALS_CURRENCY_UNIT', getDolGlobalInt('MAIN_MAX_DECIMALS_UNIT')); // TODO Use param of currency
7924 } elseif ($rounding == 'CT') {
7925 $nbofdectoround = getDolGlobalInt('MAIN_MAX_DECIMALS_CURRENCY_TOT', getDolGlobalInt('MAIN_MAX_DECIMALS_TOT')); // TODO Use param of currency
7926 } elseif (is_numeric($rounding)) {
7927 $nbofdectoround = (int) $rounding;
7928 }
7929
7930 //print " RR".$amount.' - '.$nbofdectoround.'<br>';
7931 if (dol_strlen($nbofdectoround)) {
7932 $amount = round(is_string($amount) ? (float) $amount : $amount, $nbofdectoround); // $nbofdectoround can be 0.
7933 } else {
7934 return 'ErrorBadParameterProvidedToFunction';
7935 }
7936 //print ' SS'.$amount.' - '.$nbofdec.' - '.$dec.' - '.$thousand.' - '.$nbofdectoround.'<br>';
7937
7938 // Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
7939 // to format defined by LC_NUMERIC after a calculation and we want source format to be defined by Dolibarr setup.
7940 if (is_numeric($amount)) {
7941 // We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
7942 $temps = sprintf("%10.10F", $amount - intval($amount)); // temps=0.0000000000 or 0.0000200000 or 9999.1000000000
7943 $temps = preg_replace('/([\.1-9])0+$/', '\\1', $temps); // temps=0. or 0.00002 or 9999.1
7944 $nbofdec = max(0, dol_strlen($temps) - 2); // -2 to remove "0."
7945 $amount = number_format($amount, min($nbofdec, $nbofdectoround), $dec, $thousand); // Convert amount to format with dolibarr dec and thousand
7946 }
7947 //print "TT".$amount.'<br>';
7948
7949 // Always make replace because each math function (like round) replace
7950 // with local values and we want a number that has a SQL string format x.y
7951 if ($thousand != ',' && $thousand != '.') {
7952 $amount = str_replace(',', '.', $amount); // To accept 2 notations for french users
7953 }
7954
7955 $amount = str_replace(' ', '', $amount); // To avoid spaces
7956 $amount = str_replace($thousand, '', $amount); // Replace of thousand before replace of dec to avoid pb if thousand is .
7957 $amount = str_replace($dec, '.', $amount);
7958
7959 $amount = preg_replace('/[^0-9\-\.]/', '', $amount); // Clean non numeric chars (so it clean some UTF8 spaces for example.
7960 }
7961
7962 return $amount;
7963}
7964
7977function showDimensionInBestUnit($dimension, $unit, $type, $outputlangs, $round = -1, $forceunitoutput = 'no', $use_short_label = 0)
7978{
7979 require_once DOL_DOCUMENT_ROOT . '/core/lib/product.lib.php';
7980
7981 if (($forceunitoutput == 'no' && $dimension < 1 / 10000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == -6)) {
7982 $dimension *= 1000000;
7983 $unit -= 6;
7984 } elseif (($forceunitoutput == 'no' && $dimension < 1 / 10 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == -3)) {
7985 $dimension *= 1000;
7986 $unit -= 3;
7987 } elseif (($forceunitoutput == 'no' && $dimension > 100000000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == 6)) {
7988 $dimension /= 1000000;
7989 $unit += 6;
7990 } elseif (($forceunitoutput == 'no' && $dimension > 100000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == 3)) {
7991 $dimension /= 1000;
7992 $unit += 3;
7993 }
7994 // Special case when we want output unit into pound or ounce
7995 /* TODO
7996 if ($unit < 90 && $type == 'weight' && is_numeric($forceunitoutput) && (($forceunitoutput == 98) || ($forceunitoutput == 99))
7997 {
7998 $dimension = // convert dimension from standard unit into ounce or pound
7999 $unit = $forceunitoutput;
8000 }
8001 if ($unit > 90 && $type == 'weight' && is_numeric($forceunitoutput) && $forceunitoutput < 90)
8002 {
8003 $dimension = // convert dimension from standard unit into ounce or pound
8004 $unit = $forceunitoutput;
8005 }*/
8006
8007 $ret = price($dimension, 0, $outputlangs, 0, 0, $round);
8008 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
8009 $ret .= ' ' . measuringUnitString(0, $type, $unit, $use_short_label, $outputlangs);
8010
8011 return $ret;
8012}
8013
8014
8027function get_localtax($vatrate, $local, $thirdparty_buyer = null, $thirdparty_seller = null, $vatnpr = 0)
8028{
8029 global $db, $conf, $mysoc;
8030
8031 if (empty($thirdparty_seller) || !is_object($thirdparty_seller)) {
8032 $thirdparty_seller = $mysoc;
8033 }
8034
8035 dol_syslog("get_localtax tva=" . $vatrate . " local=" . $local . " thirdparty_buyer id=" . (is_object($thirdparty_buyer) ? $thirdparty_buyer->id : '') . "/country_code=" . (is_object($thirdparty_buyer) ? $thirdparty_buyer->country_code : '') . " thirdparty_seller id=" . $thirdparty_seller->id . "/country_code=" . $thirdparty_seller->country_code . " thirdparty_seller localtax1_assuj=" . $thirdparty_seller->localtax1_assuj . " thirdparty_seller localtax2_assuj=" . $thirdparty_seller->localtax2_assuj);
8036
8037 $vatratecleaned = $vatrate;
8038 $reg = array();
8039 if (preg_match('/^(.*)\s*\‍((.*)\‍)$/', (string) $vatrate, $reg)) { // If vat is "xx (yy)"
8040 $vatratecleaned = trim($reg[1]);
8041 $vatratecode = $reg[2];
8042 }
8043
8044 /*if ($thirdparty_buyer->country_code != $thirdparty_seller->country_code)
8045 {
8046 return 0;
8047 }*/
8048
8049 // Some test to guess with no need to make database access
8050 if ($mysoc->country_code == 'ES') { // For spain localtaxes 1 and 2, tax is qualified if buyer use local tax
8051 if ($local == 1) {
8052 if (!$mysoc->localtax1_assuj || (string) $vatratecleaned == "0") {
8053 return 0;
8054 }
8055 if ($thirdparty_seller->id == $mysoc->id) {
8056 if (!$thirdparty_buyer->localtax1_assuj) {
8057 return 0;
8058 }
8059 } else {
8060 if (!$thirdparty_seller->localtax1_assuj) {
8061 return 0;
8062 }
8063 }
8064 }
8065
8066 if ($local == 2) {
8067 //if (! $mysoc->localtax2_assuj || (string) $vatratecleaned == "0") return 0;
8068 if (!$mysoc->localtax2_assuj) {
8069 return 0; // If main vat is 0, IRPF may be different than 0.
8070 }
8071 if ($thirdparty_seller->id == $mysoc->id) {
8072 if (!$thirdparty_buyer->localtax2_assuj) {
8073 return 0;
8074 }
8075 } else {
8076 if (!$thirdparty_seller->localtax2_assuj) {
8077 return 0;
8078 }
8079 }
8080 }
8081 } else {
8082 if ($local == 1 && !$thirdparty_seller->localtax1_assuj) {
8083 return 0;
8084 }
8085 if ($local == 2 && !$thirdparty_seller->localtax2_assuj) {
8086 return 0;
8087 }
8088 }
8089
8090 // For some country MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY is forced to on.
8091 if (in_array($mysoc->country_code, array('ES'))) {
8092 $conf->global->MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY = 1;
8093 }
8094
8095 // Search local taxes
8096 if (getDolGlobalString('MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY')) {
8097 if ($local == 1) {
8098 if ($thirdparty_seller != $mysoc) {
8099 if (!isOnlyOneLocalTax($local)) { // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
8100 return $thirdparty_seller->localtax1_value;
8101 }
8102 } else { // i am the seller
8103 if (!isOnlyOneLocalTax($local)) { // TODO If seller is me, why not always returning this, even if there is only one locatax vat.
8104 return getDolGlobalString('MAIN_INFO_VALUE_LOCALTAX1');
8105 }
8106 }
8107 }
8108 if ($local == 2) {
8109 if ($thirdparty_seller != $mysoc) {
8110 if (!isOnlyOneLocalTax($local)) { // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
8111 // TODO We should also return value defined on thirdparty only if defined
8112 return $thirdparty_seller->localtax2_value;
8113 }
8114 } else { // i am the seller
8115 if (in_array($mysoc->country_code, array('ES'))) {
8116 return $thirdparty_buyer->localtax2_value;
8117 } else {
8118 return getDolGlobalString('MAIN_INFO_VALUE_LOCALTAX2');
8119 }
8120 }
8121 }
8122 }
8123
8124 // By default, search value of local tax on line of common tax
8125 $sql = "SELECT t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
8126 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t, " . MAIN_DB_PREFIX . "c_country as c";
8127 $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '" . $db->escape($thirdparty_seller->country_code) . "'";
8128 $sql .= " AND t.taux = " . ((float) $vatratecleaned) . " AND t.active = 1";
8129 $sql .= " AND t.entity IN (" . getEntity('c_tva') . ")";
8130 if (!empty($vatratecode)) {
8131 $sql .= " AND t.code ='" . $db->escape($vatratecode) . "'"; // If we have the code, we use it in priority
8132 } else {
8133 $sql .= " AND t.recuperableonly = '" . $db->escape((string) $vatnpr) . "'";
8134 }
8135
8136 $resql = $db->query($sql);
8137
8138 if ($resql) {
8139 $obj = $db->fetch_object($resql);
8140 if ($obj) {
8141 if ($local == 1) {
8142 return $obj->localtax1;
8143 } elseif ($local == 2) {
8144 return $obj->localtax2;
8145 }
8146 }
8147 }
8148
8149 return 0;
8150}
8151
8152
8161function isOnlyOneLocalTax($local)
8162{
8163 $tax = get_localtax_by_third($local);
8164
8165 $valors = explode(":", $tax);
8166
8167 if (count($valors) > 1) {
8168 return false;
8169 } else {
8170 return true;
8171 }
8172}
8173
8180function get_localtax_by_third($local)
8181{
8182 global $db, $mysoc;
8183
8184 $sql = " SELECT t.localtax" . ((int) $local) . " as localtax";
8185 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t INNER JOIN " . MAIN_DB_PREFIX . "c_country as c ON c.rowid = t.fk_pays";
8186 $sql .= " WHERE c.code = '" . $db->escape($mysoc->country_code) . "' AND t.active = 1 AND t.entity IN (" . getEntity('c_tva') . ") AND t.taux = (";
8187 $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";
8188 $sql .= " WHERE c.code = '" . $db->escape($mysoc->country_code) . "' AND t.entity IN (" . getEntity('c_tva') . ") AND tt.active = 1)";
8189 $sql .= " AND t.localtax" . ((int) $local) . "_type <> '0'";
8190 $sql .= " ORDER BY t.rowid DESC";
8191
8192 $resql = $db->query($sql);
8193 if ($resql) {
8194 $obj = $db->fetch_object($resql);
8195 if ($obj) {
8196 return $obj->localtax;
8197 } else {
8198 return '0';
8199 }
8200 }
8201
8202 return 'Error';
8203}
8204
8205
8217function getTaxesFromId($vatrate, $buyer = null, $seller = null, $firstparamisid = 1)
8218{
8219 global $db;
8220
8221 dol_syslog("getTaxesFromId vat id or rate = " . $vatrate);
8222
8223 // Search local taxes
8224 $sql = "SELECT t.rowid, t.code, t.taux as rate, t.recuperableonly as npr, t.accountancy_code_sell, t.accountancy_code_buy,";
8225 $sql .= " t.localtax1, t.localtax1_type, t.localtax2, t.localtax2_type";
8226 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t";
8227 if ($firstparamisid) {
8228 $sql .= " WHERE t.rowid = " . (int) $vatrate;
8229 } else {
8230 $vatratecleaned = $vatrate;
8231 $vatratecode = '';
8232 $reg = array();
8233 if (preg_match('/^(.*)\s*\‍((.*)\‍)$/', $vatrate, $reg)) { // If vat is "xx (yy)"
8234 $vatratecleaned = $reg[1];
8235 $vatratecode = $reg[2];
8236 }
8237
8238 $sql .= ", " . MAIN_DB_PREFIX . "c_country as c";
8239 /*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 ??
8240 else $sql.= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($seller->country_code)."'";*/
8241 $sql .= " WHERE t.fk_pays = c.rowid";
8242 if (getDolGlobalString('SERVICE_ARE_ECOMMERCE_200238EC')) {
8243 $sql .= " AND c.code = '" . $db->escape($buyer->country_code) . "'";
8244 } else {
8245 $sql .= " AND c.code = '" . $db->escape($seller->country_code) . "'";
8246 }
8247 $sql .= " AND t.taux = " . ((float) $vatratecleaned) . " AND t.active = 1";
8248 $sql .= " AND t.entity IN (" . getEntity('c_tva') . ")";
8249 if ($vatratecode) {
8250 $sql .= " AND t.code = '" . $db->escape($vatratecode) . "'";
8251 }
8252 }
8253
8254 $resql = $db->query($sql);
8255 if ($resql) {
8256 $obj = $db->fetch_object($resql);
8257 if ($obj) {
8258 return array(
8259 'rowid' => $obj->rowid,
8260 'code' => $obj->code,
8261 'rate' => $obj->rate,
8262 'localtax1' => $obj->localtax1,
8263 'localtax1_type' => $obj->localtax1_type,
8264 'localtax2' => $obj->localtax2,
8265 'localtax2_type' => $obj->localtax2_type,
8266 'npr' => $obj->npr,
8267 'accountancy_code_sell' => $obj->accountancy_code_sell,
8268 'accountancy_code_buy' => $obj->accountancy_code_buy
8269 );
8270 } else {
8271 return array();
8272 }
8273 } else {
8275 }
8276
8277 return array();
8278}
8279
8296function getLocalTaxesFromRate($vatrate, $local, $buyer, $seller, $firstparamisid = 0)
8297{
8298 global $db, $mysoc;
8299
8300 dol_syslog("getLocalTaxesFromRate vatrate=" . $vatrate . " local=" . $local);
8301
8302 // Search local taxes
8303 $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";
8304 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t";
8305 if ($firstparamisid) {
8306 $sql .= " WHERE t.rowid = " . (int) $vatrate;
8307 } else {
8308 $vatratecleaned = $vatrate;
8309 $vatratecode = '';
8310 $reg = array();
8311 if (preg_match('/^(.*)\s*\‍((.*)\‍)$/', $vatrate, $reg)) { // If vat is "x.x (yy)"
8312 $vatratecleaned = $reg[1];
8313 $vatratecode = $reg[2];
8314 }
8315
8316 $sql .= ", " . MAIN_DB_PREFIX . "c_country as c";
8317 if (!empty($mysoc) && $mysoc->country_code == 'ES') {
8318 $countrycodetouse = ((empty($buyer) || empty($buyer->country_code)) ? $mysoc->country_code : $buyer->country_code);
8319 $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '" . $db->escape($countrycodetouse) . "'"; // local tax in spain use the buyer country ??
8320 } else {
8321 $countrycodetouse = ((empty($seller) || empty($seller->country_code)) ? $mysoc->country_code : $seller->country_code);
8322 $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '" . $db->escape($countrycodetouse) . "'";
8323 }
8324 $sql .= " AND t.taux = " . ((float) $vatratecleaned) . " AND t.active = 1";
8325 if ($vatratecode) {
8326 $sql .= " AND t.code = '" . $db->escape($vatratecode) . "'";
8327 }
8328 }
8329
8330 $resql = $db->query($sql);
8331 if ($resql) {
8332 $obj = $db->fetch_object($resql);
8333
8334 if ($obj) {
8335 $vateratestring = $obj->rate . ($obj->code ? ' (' . $obj->code . ')' : '');
8336
8337 if ($local == 1) {
8338 return array($obj->localtax1_type, get_localtax($vateratestring, $local, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
8339 } elseif ($local == 2) {
8340 return array($obj->localtax2_type, get_localtax($vateratestring, $local, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
8341 } else {
8342 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);
8343 }
8344 }
8345 }
8346
8347 return array();
8348}
8349
8360function get_product_vat_for_country($idprod, $thirdpartytouseforcountry, $idprodfournprice = 0)
8361{
8362 global $db, $mysoc;
8363
8364 require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
8365
8366 $ret = 0;
8367 $found = 0;
8368
8369 if ($idprod > 0) {
8370 // Load product
8371 $product = new Product($db);
8372 $product->fetch($idprod);
8373
8374 if (($mysoc->country_code == $thirdpartytouseforcountry->country_code)
8375 || (in_array($mysoc->country_code, array('FR', 'MC')) && in_array($thirdpartytouseforcountry->country_code, array('FR', 'MC')))
8376 || (in_array($mysoc->country_code, array('MQ', 'GP')) && in_array($thirdpartytouseforcountry->country_code, array('MQ', 'GP')))
8377 ) {
8378 // If country of thirdparty to consider is ours
8379 if ($idprodfournprice > 0) { // We want vat for product for a "supplier" object
8380 $result = $product->get_buyprice($idprodfournprice, 0, 0, '');
8381 if ($result > 0) {
8382 $ret = $product->vatrate_supplier;
8383 if ($product->default_vat_code_supplier) {
8384 $ret .= ' (' . $product->default_vat_code_supplier . ')';
8385 }
8386 $found = 1;
8387 }
8388 }
8389 if (!$found) {
8390 $ret = $product->tva_tx; // Default sales vat of product
8391 if ($product->default_vat_code) {
8392 $ret .= ' (' . $product->default_vat_code . ')';
8393 }
8394 $found = 1;
8395 }
8396 } else {
8397 // TODO Read default product vat according to product and an other countrycode.
8398 // Vat for couple anothercountrycode/product is data that is not managed and store yet, so we will fallback on next rule.
8399 }
8400 }
8401
8402 if (!$found) {
8403 if (!getDolGlobalString('MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS')) {
8404 // 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).
8405 $sql = "SELECT t.taux as vat_rate, t.code as default_vat_code";
8406 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t, " . MAIN_DB_PREFIX . "c_country as c";
8407 $sql .= " WHERE t.active = 1 AND t.fk_pays = c.rowid AND c.code = '" . $db->escape($thirdpartytouseforcountry->country_code) . "'";
8408 $sql .= " AND t.entity IN (" . getEntity('c_tva') . ")";
8409 $sql .= " ORDER BY t.use_default DESC, t.taux DESC, t.code ASC, t.recuperableonly ASC";
8410 $sql .= $db->plimit(1);
8411
8412 $resql = $db->query($sql);
8413 if ($resql) {
8414 $obj = $db->fetch_object($resql);
8415 if ($obj) {
8416 $ret = $obj->vat_rate;
8417 if ($obj->default_vat_code) {
8418 $ret .= ' (' . $obj->default_vat_code . ')';
8419 }
8420 }
8421 $db->free($resql);
8422 } else {
8424 }
8425 } else {
8426 // Forced value if autodetect fails. MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS can be
8427 // '1.23'
8428 // or '1.23 (CODE)'
8429 $defaulttx = '';
8430 if (getDolGlobalString('MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS') != 'none') {
8431 $defaulttx = getDolGlobalString('MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS');
8432 }
8433 /*if (preg_match('/\‍((.*)\‍)/', $defaulttx, $reg)) {
8434 $defaultcode = $reg[1];
8435 $defaulttx = preg_replace('/\s*\‍(.*\‍)/', '', $defaulttx);
8436 }*/
8437
8438 $ret = $defaulttx;
8439 }
8440 }
8441
8442 dol_syslog("get_product_vat_for_country: ret=" . $ret);
8443
8444 return $ret;
8445}
8446
8456function get_product_localtax_for_country($idprod, $local, $thirdpartytouseforcountry)
8457{
8458 global $db, $mysoc;
8459
8460 if (!class_exists('Product')) {
8461 require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
8462 }
8463
8464 $ret = 0;
8465 $found = 0;
8466
8467 if ($idprod > 0) {
8468 // Load product
8469 $product = new Product($db);
8470 $result = $product->fetch($idprod);
8471
8472 if ($mysoc->country_code == $thirdpartytouseforcountry->country_code) { // If selling country is ours
8473 /* Not defined yet, so we don't use this
8474 if ($local==1) $ret=$product->localtax1_tx;
8475 elseif ($local==2) $ret=$product->localtax2_tx;
8476 $found=1;
8477 */
8478 } else {
8479 // TODO Read default product vat according to product and another countrycode.
8480 // Vat for couple anothercountrycode/product is data that is not managed and store yet, so we will fallback on next rule.
8481 }
8482 }
8483
8484 if (!$found) {
8485 // If vat of product for the country not found or not defined, we return higher vat of country.
8486 $sql = "SELECT taux as vat_rate, localtax1, localtax2";
8487 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t, " . MAIN_DB_PREFIX . "c_country as c";
8488 $sql .= " WHERE t.active=1 AND t.fk_pays = c.rowid AND c.code='" . $db->escape($thirdpartytouseforcountry->country_code) . "'";
8489 $sql .= " AND t.entity IN (" . getEntity('c_tva') . ")";
8490 $sql .= " ORDER BY t.taux DESC, t.recuperableonly ASC";
8491 $sql .= $db->plimit(1);
8492
8493 $resql = $db->query($sql);
8494 if ($resql) {
8495 $obj = $db->fetch_object($resql);
8496 if ($obj) {
8497 if ($local == 1) {
8498 $ret = $obj->localtax1;
8499 } elseif ($local == 2) {
8500 $ret = $obj->localtax2;
8501 }
8502 }
8503 } else {
8505 }
8506 }
8507
8508 dol_syslog("get_product_localtax_for_country: ret=" . $ret);
8509 return $ret;
8510}
8511
8530function get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0)
8531{
8532 global $mysoc, $db, $hookmanager;
8533
8534 require_once DOL_DOCUMENT_ROOT . '/core/lib/company.lib.php';
8535
8536 // Note: possible values for tva_assuj are 0/1 or franchise/reel
8537 $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;
8538
8539 if (empty($thirdparty_seller->country_code)) {
8540 $thirdparty_seller->country_code = $mysoc->country_code;
8541 }
8542 $seller_country_code = $thirdparty_seller->country_code;
8543 $seller_in_cee = isInEEC($thirdparty_seller);
8544
8545 if (empty($thirdparty_buyer->country_code)) {
8546 $thirdparty_buyer->country_code = $mysoc->country_code;
8547 }
8548 $buyer_country_code = $thirdparty_buyer->country_code;
8549 $buyer_in_cee = isInEEC($thirdparty_buyer);
8550
8551 dol_syslog("get_default_tva: seller use vat=" . $seller_use_vat . ", seller country=" . $seller_country_code . ", seller in cee=" . ((string) (int) $seller_in_cee) . ", buyer vat number=" . $thirdparty_buyer->tva_intra . " buyer country=" . $buyer_country_code . ", buyer state=" . $thirdparty_buyer->state_id . " buyer in cee=" . ((string) (int) $buyer_in_cee) . ", idprod=" . $idprod . ", idprodfournprice=" . $idprodfournprice . ", SERVICE_ARE_ECOMMERCE_200238EC=" . getDolGlobalString('SERVICE_ARE_ECOMMERCE_200238EC'));
8552
8553 $vatvalue = 0;
8554 $vatrule = '';
8555
8556 // 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)
8557 // we use the buyer VAT.
8558 if (getDolGlobalString('SERVICE_ARE_ECOMMERCE_200238EC')) {
8559 if ($seller_in_cee && $buyer_in_cee) {
8560 $isacompany = $thirdparty_buyer->isACompany();
8561 if ($isacompany && !getDolGlobalString('MAIN_USE_VAT_ZERO_FOR_COMPANIES_IN_EEC_EVEN_IF_VAT_ID_UNKNOWN')) {
8562 require_once DOL_DOCUMENT_ROOT . '/core/lib/functions2.lib.php';
8563 if (!isValidVATID($thirdparty_buyer)) {
8564 $isacompany = 0;
8565 }
8566 }
8567
8568 if (!$isacompany) {
8569 $vatvalue = get_product_vat_for_country($idprod, $thirdparty_buyer, $idprodfournprice);
8570 $vatrule = 'VATRULE 0';
8571 }
8572 }
8573 }
8574
8575 // If seller does not use VAT, default VAT is 0. End of rule.
8576 if (empty($vatrule) && !$seller_use_vat) {
8577 //print 'VATRULE 1';
8578 // TODO get the VAT Code of exemption asked into setup if country isInEEC (from an array list of possible
8579 // values like VATEX-EU-132-*, VATEX-FR-FRANCHISE, VATEX-EU-AE...
8580 // When we had recorded it, we also added a corresponding entry into table of vat code if it does not exists yet.
8581 // Here we test if entry for the VAT exemption code exists in llx_vat, we can return '0 (VATEX-EU-132-xx)'
8582 // If not, we add it and we return '0 (VATEX-EU-132-xx)'
8583 $vatvalue = 0;
8584 $vatrule = 'VATRULE 1';
8585 }
8586
8587 // 'VATRULE 2' - Force VAT if a buyer department is defined on vat rates dictionary
8588 if (empty($vatrule) && !empty($thirdparty_buyer->state_id)) {
8589 $sql = "SELECT d.rowid, t.taux as vat_default_rate, t.code as vat_default_code ";
8590 $sql .= " FROM " . $db->prefix() . "c_tva as t";
8591 $sql .= " INNER JOIN " . $db->prefix() . "c_departements as d ON t.fk_department_buyer = d.rowid";
8592 $sql .= " WHERE d.rowid = " . ((int) $thirdparty_buyer->state_id);
8593 $sql .= " AND t.active > 0";
8594 $sql .= " AND t.entity IN (".getEntity('c_tva').")";
8595 $sql .= " ORDER BY t.use_default DESC, t.taux DESC, t.code ASC, t.recuperableonly ASC";
8596
8597 $res = $db->query($sql);
8598 if ($res) {
8599 if ($db->num_rows($res)) {
8600 $obj = $db->fetch_object($res);
8601
8602 $vatvalue = $obj->vat_default_rate . ' (' . $obj->vat_default_code . ')';
8603 $vatrule = 'VATRULE 2';
8604 }
8605 $db->free($res);
8606 }
8607 }
8608
8609 // If the (seller country = buyer country) then the default VAT = VAT of the product sold. End of rule.
8610 if (empty($vatrule) && (
8611 ($seller_country_code == $buyer_country_code)
8612 || (in_array($seller_country_code, array('FR', 'MC')) && in_array($buyer_country_code, array('FR', 'MC')))
8613 || (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.
8614 )) { // Warning ->country_code not always defined
8615 //print 'VATRULE 3';
8616 $tmpvat = get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
8617
8618 if ($seller_country_code == 'IN' && getDolGlobalString('MAIN_SALETAX_AUTOSWITCH_I_CS_FOR_INDIA')) {
8619 // Special case for india.
8620 //print 'VATRULE 3b';
8621 $reg = array();
8622 if (preg_match('/C+S-(\d+)/', $tmpvat, $reg) && $thirdparty_seller->state_id != $thirdparty_buyer->state_id) {
8623 // we must revert the C+S into I
8624 $tmpvat = str_replace("C+S", "I", $tmpvat);
8625 } elseif (preg_match('/I-(\d+)/', $tmpvat, $reg) && $thirdparty_seller->state_id == $thirdparty_buyer->state_id) {
8626 // we must revert the I into C+S
8627 $tmpvat = str_replace("I", "C+S", $tmpvat);
8628 }
8629 }
8630
8631 $vatvalue = $tmpvat;
8632 $vatrule = 'VATRULE 3b';
8633 }
8634
8635 // 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.
8636 // 'VATRULE 4' - Not supported
8637
8638 // If (seller and buyer in the European Community) and (buyer = individual) then VAT by default = VAT of the product sold. End of rule
8639 // If (seller and buyer in European Community) and (buyer = company) then VAT by default=0. End of rule
8640 if (empty($vatrule) && ($seller_in_cee && $buyer_in_cee)) {
8641 $isacompany = $thirdparty_buyer->isACompany();
8642 if ($isacompany && !getDolGlobalString('MAIN_USE_VAT_ZERO_FOR_COMPANIES_IN_EEC_EVEN_IF_VAT_ID_UNKNOWN')) {
8643 require_once DOL_DOCUMENT_ROOT . '/core/lib/functions2.lib.php';
8644 if (!isValidVATID($thirdparty_buyer)) {
8645 $isacompany = 0;
8646 }
8647 }
8648
8649 if (!$isacompany) {
8650 //print 'VATRULE 5';
8651 $vatvalue = get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
8652 $vatrule = 'VATRULE 5';
8653 } else {
8654 //print 'VATRULE 6';
8655 // TODO This is the case of VAT exemption 'VATEX-EU-IC'
8656 // If entry for the VAT exemption code exists in llx_vat, we can return '0 (VATEX-EU-IC)'
8657 // If not, we add it and we return '0 (VATEX-EU-IC)'
8658 $vatvalue = 0;
8659 $vatrule = 'VATRULE 6';
8660 }
8661 }
8662
8663 // 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
8664 // 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
8665 if (empty($vatrule) && getDolGlobalString('MAIN_USE_VAT_OF_PRODUCT_FOR_INDIVIDUAL_CUSTOMER_OUT_OF_EEC') && empty($buyer_in_cee)) {
8666 $isacompany = $thirdparty_buyer->isACompany();
8667 if (!$isacompany) {
8668 $vatvalue = get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
8669 $vatrule = 'VATRULE extra';
8670 //print 'VATRULE extra';
8671 }
8672 }
8673
8674 // Otherwise the VAT proposed by default=0. End of rule.
8675 // Rem: This means that at least one of the 2 is outside the European Community and the country differs
8676 //print 'VATRULE 7';
8677 // TODO This is the case of VAT exemption 'VATEX-EU-G'
8678 // If entry for the VAT exemption code exists in llx_vat, we can return '0 (VATEX-xxx)'
8679 // If not, we add it and we return '0 (VATEX-xxx)'
8680
8681 // Allow an external module to bypass the calculation of prices
8682 $parameters = array('vatvalue' => $vatvalue, 'vatrule' => $vatrule);
8683 $tmpobject = null;
8684 $tmpaction = '';
8685 // @phan-suppress-next-line PhanPluginConstantVariableNull
8686 $reshook = $hookmanager->executeHooks('get_default_tva', $parameters, $tmpobject, $tmpaction); // @phan-suppress-current-line PhanPluginConstantVariableNull
8687 if ($reshook > 0 && !empty($hookmanager->resArray['vatvalue'])) {
8688 $vatvalue = $hookmanager->resArray['vatvalue'];
8689 $vatrule = $hookmanager->resArray['vatrule']; // For information
8690 }
8691
8692 return $vatvalue;
8693}
8694
8695
8706function get_default_npr(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0)
8707{
8708 global $db;
8709
8710 if ($idprodfournprice > 0) {
8711 if (!class_exists('ProductFournisseur')) {
8712 require_once DOL_DOCUMENT_ROOT . '/fourn/class/fournisseur.product.class.php';
8713 }
8714 $prodprice = new ProductFournisseur($db);
8715 $prodprice->fetch_product_fournisseur_price($idprodfournprice);
8716 return $prodprice->fourn_tva_npr;
8717 } elseif ($idprod > 0) {
8718 if (!class_exists('Product')) {
8719 require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
8720 }
8721 $prod = new Product($db);
8722 $prod->fetch($idprod);
8723 return $prod->tva_npr;
8724 }
8725
8726 return 0;
8727}
8728
8742function get_default_localtax($thirdparty_seller, $thirdparty_buyer, $local, $idprod = 0)
8743{
8744 global $mysoc;
8745
8746 if (!is_object($thirdparty_seller)) {
8747 return -1;
8748 }
8749 if (!is_object($thirdparty_buyer)) {
8750 return -1;
8751 }
8752
8753 if (empty($thirdparty_seller->country_code)) {
8754 $thirdparty_seller->country_code = $mysoc->country_code;
8755 }
8756 $seller_country_code = $thirdparty_seller->country_code;
8757 //$seller_in_cee = isInEEC($thirdparty_seller);
8758
8759 if (empty($thirdparty_buyer->country_code)) {
8760 $thirdparty_buyer->country_code = $mysoc->country_code;
8761 }
8762 $buyer_country_code = $thirdparty_buyer->country_code;
8763 //$buyer_in_cee = isInEEC($thirdparty_buyer);
8764
8765 if ($local == 1) { // Localtax 1
8766 if ($mysoc->country_code == 'ES') {
8767 if (is_numeric($thirdparty_buyer->localtax1_assuj) && !$thirdparty_buyer->localtax1_assuj) {
8768 return 0;
8769 }
8770 } else {
8771 // Si vendeur non assujeti a Localtax1, localtax1 par default=0
8772 if (is_numeric($thirdparty_seller->localtax1_assuj) && !$thirdparty_seller->localtax1_assuj) {
8773 return 0;
8774 }
8775 if (!is_numeric($thirdparty_seller->localtax1_assuj) && $thirdparty_seller->localtax1_assuj == 'localtax1off') {
8776 return 0;
8777 }
8778 }
8779 } elseif ($local == 2) { //I Localtax 2
8780 // Si vendeur non assujeti a Localtax2, localtax2 par default=0
8781 if (is_numeric($thirdparty_seller->localtax2_assuj) && !$thirdparty_seller->localtax2_assuj) {
8782 return 0;
8783 }
8784 if (!is_numeric($thirdparty_seller->localtax2_assuj) && $thirdparty_seller->localtax2_assuj == 'localtax2off') {
8785 return 0;
8786 }
8787 }
8788
8789 if ($seller_country_code == $buyer_country_code) {
8790 return get_product_localtax_for_country($idprod, $local, $thirdparty_seller);
8791 }
8792
8793 return 0;
8794}
8795
8804function yn($yesno, $format = 1, $color = 0)
8805{
8806 global $langs;
8807
8808 $result = 'unknown';
8809 $classname = '';
8810 if ($yesno === true || (int) $yesno == 1 || (isset($yesno) && (strtolower($yesno) == 'yes' || strtolower($yesno) == 'true'))) { // To set to 'no' before the test because of the '== 0'
8811 $result = $langs->trans('yes');
8812 if ($format == 1 || $format == 3) {
8813 $result = $langs->trans("Yes");
8814 }
8815 if ($format == 2) {
8816 $result = '<input type="checkbox" value="1" checked disabled>';
8817 }
8818 if ($format == 3) {
8819 $result = '<input type="checkbox" value="1" checked disabled> ' . $result;
8820 }
8821 if ($format == 4 || !is_numeric($format)) {
8822 $result = img_picto(is_numeric($format) ? '' : $format, 'check');
8823 }
8824
8825 $classname = 'ok';
8826 } else {
8827 $result = $langs->trans("no");
8828 if ($format == 1 || $format == 3) {
8829 $result = $langs->trans("No");
8830 }
8831 if ($format == 2) {
8832 $result = '<input type="checkbox" value="0" disabled>';
8833 }
8834 if ($format == 3) {
8835 $result = '<input type="checkbox" value="0" disabled> ' . $result;
8836 }
8837 if ($format == 4 || !is_numeric($format)) {
8838 $result = img_picto(is_numeric($format) ? '' : $format, 'uncheck');
8839 }
8840
8841 if ($color == 2) {
8842 $classname = 'ok';
8843 } else {
8844 $classname = 'error';
8845 }
8846 }
8847 if ($color) {
8848 return '<span class="' . $classname . '">' . $result . '</span>';
8849 }
8850 return $result;
8851}
8852
8871function get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart = '')
8872{
8873 if (empty($modulepart) && is_object($object)) {
8874 if (!empty($object->module)) {
8875 $modulepart = $object->module;
8876 } elseif (!empty($object->element)) {
8877 $modulepart = $object->element;
8878 }
8879 }
8880
8881 $path = '';
8882
8883 // Define $arrayforoldpath that is module path using a hierarchy on more than 1 level.
8884 $arrayforoldpath = array('cheque' => 2, 'category' => 2, 'supplier_invoice' => 2, 'invoice_supplier' => 2, 'mailing' => 2, 'supplier_payment' => 2);
8885 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
8886 $arrayforoldpath['product'] = 2;
8887 }
8888
8889 if (empty($level) && array_key_exists($modulepart, $arrayforoldpath)) {
8890 $level = $arrayforoldpath[$modulepart];
8891 }
8892 if (!empty($level) && array_key_exists($modulepart, $arrayforoldpath)) {
8893 // This part should be removed once all code is using "get_exdir" to forge path, with parameter $object and $modulepart provided.
8894 if (empty($num) && is_object($object)) {
8895 $num = ((int) $object->id);
8896 }
8897 if (empty($alpha)) {
8898 $num = preg_replace('/([^0-9])/i', '', $num);
8899 } else {
8900 $num = preg_replace('/^.*\-/i', '', $num);
8901 }
8902 $num = substr("000" . $num, -$level);
8903 if ($level == 1) {
8904 $path = substr($num, 0, 1);
8905 }
8906 if ($level == 2) {
8907 $path = substr($num, 1, 1) . '/' . substr($num, 0, 1);
8908 }
8909 if ($level == 3) {
8910 $path = substr($num, 2, 1) . '/' . substr($num, 1, 1) . '/' . substr($num, 0, 1);
8911 }
8912 } else {
8913 // We will enhance here a common way of forging path for document storage.
8914 // In a future, we may distribute directories on several levels depending on setup and object.
8915 // Here, $object->id, $object->ref and $modulepart are required.
8916 if (in_array($modulepart, array('societe', 'thirdparty')) && $object instanceof Societe) {
8917 // 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
8918 $path = dol_sanitizeFileName((string) $object->id);
8919 } else {
8920 $path = dol_sanitizeFileName(empty($object->ref) ? (string) ((is_object($object) && property_exists($object, 'id')) ? ((int) $object->id) : '') : $object->ref);
8921 }
8922 }
8923
8924 if (empty($withoutslash) && !empty($path)) {
8925 $path .= '/';
8926 }
8927
8928 return $path;
8929}
8930
8939function dol_mkdir($dir, $dataroot = '', $newmask = '')
8940{
8941 dol_syslog("functions.lib::dol_mkdir: dir=" . $dir, LOG_INFO);
8942
8943 $dir = dol_sanitizePathName($dir, '_', 0);
8944
8945 $dir_osencoded = dol_osencode($dir);
8946 if (@is_dir($dir_osencoded)) {
8947 return 0;
8948 }
8949
8950 $nberr = 0;
8951 $nbcreated = 0;
8952
8953 $ccdir = '';
8954 if (!empty($dataroot)) {
8955 // Remove data root from loop
8956 $dir = str_replace($dataroot . '/', '', $dir);
8957 $ccdir = $dataroot . '/';
8958 }
8959
8960 $cdir = explode("/", $dir);
8961 $num = count($cdir);
8962 for ($i = 0; $i < $num; $i++) {
8963 if ($i > 0) {
8964 $ccdir .= '/' . $cdir[$i];
8965 } else {
8966 $ccdir .= $cdir[$i];
8967 }
8968 $regs = array();
8969 if (preg_match("/^.:$/", $ccdir, $regs)) {
8970 continue; // If the Windows path is incomplete, continue with next directory
8971 }
8972
8973 // Attention, is_dir() can fail event if the directory exists
8974 // (i.e. according the open_basedir configuration)
8975 if ($ccdir) {
8976 $ccdir_osencoded = dol_osencode($ccdir);
8977 if (!@is_dir($ccdir_osencoded)) {
8978 dol_syslog("functions.lib::dol_mkdir: Directory '" . $ccdir . "' is not found (does not exists or is outside open_basedir PHP setting).", LOG_DEBUG);
8979
8980 umask(0);
8981 $dirmaskdec = octdec((string) $newmask);
8982 if (empty($newmask)) {
8983 $dirmaskdec = octdec(getDolGlobalString('MAIN_UMASK', '0755'));
8984 }
8985 $dirmaskdec |= octdec('0111'); // Set x bit required for directories
8986 if (!@mkdir($ccdir_osencoded, $dirmaskdec)) {
8987 // If the is_dir has returned a false information, we arrive here
8988 dol_syslog("functions.lib::dol_mkdir: Fails to create directory '" . $ccdir . "' (no permission to write into parent or directory already exists).", LOG_WARNING);
8989 $nberr++;
8990 } else {
8991 dol_syslog("functions.lib::dol_mkdir: Directory '" . $ccdir . "' created", LOG_DEBUG);
8992 $nberr = 0; // At this point in the code, the previous failures can be ignored -> set $nberr to 0
8993 $nbcreated++;
8994 }
8995 } else {
8996 $nberr = 0; // At this point in the code, the previous failures can be ignored -> set $nberr to 0
8997 }
8998 }
8999 }
9000 return ($nberr ? -$nberr : $nbcreated);
9001}
9002
9003
9011function dolChmod($filepath, $newmask = '')
9012{
9013 if (!empty($newmask)) {
9014 @chmod($filepath, octdec($newmask));
9015 } elseif (getDolGlobalString('MAIN_UMASK')) {
9016 @chmod($filepath, octdec(getDolGlobalString('MAIN_UMASK')));
9017 }
9018}
9019
9020
9026function picto_required()
9027{
9028 return '<span class="fieldrequired">*</span>';
9029}
9030
9031
9048function dol_string_nohtmltag($stringtoclean, $removelinefeed = 1, $pagecodeto = 'UTF-8', $strip_tags = 0, $removedoublespaces = 1)
9049{
9050 if (is_null($stringtoclean)) {
9051 return '';
9052 }
9053
9054 if ($removelinefeed == 2) {
9055 $stringtoclean = preg_replace('/<br[^>]*>(\n|\r)+/ims', '<br>', $stringtoclean);
9056 }
9057 $temp = preg_replace('/<br[^>]*>/i', "\n", $stringtoclean);
9058
9059 // 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)
9060 $temp = dol_html_entity_decode($temp, ENT_COMPAT | ENT_HTML5, $pagecodeto);
9061
9062 $temp = str_replace('< ', '__ltspace__', $temp);
9063 $temp = str_replace('<:', '__lttwopoints__', $temp);
9064
9065 if ($strip_tags) {
9066 $temp = strip_tags($temp);
9067 } else {
9068 // 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).
9069 $pattern = "/<[^<>]+>/";
9070 // Example of $temp: <a href="/myurl" title="<u>A title</u>">0000-021</a>
9071 // pass 1 - $temp after pass 1: <a href="/myurl" title="A title">0000-021
9072 // pass 2 - $temp after pass 2: 0000-021
9073 $tempbis = $temp;
9074 do {
9075 $temp = $tempbis;
9076 $tempbis = str_replace('<>', '', $temp); // No reason to have this into a text, except if value is to try bypass the next html cleaning
9077 $tempbis = preg_replace($pattern, '', $tempbis);
9078 //$idowhile++; print $temp.'-'.$tempbis."\n"; if ($idowhile > 100) break;
9079 } while ($tempbis != $temp);
9080
9081 $temp = $tempbis;
9082
9083 // 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).
9084 $temp = preg_replace('/<+([a-z]+)/i', '\1', $temp);
9085 }
9086
9087 $temp = dol_html_entity_decode($temp, ENT_COMPAT, $pagecodeto);
9088
9089 // Remove also carriage returns
9090 if ($removelinefeed == 1) {
9091 $temp = str_replace(array("\r\n", "\r", "\n"), " ", $temp);
9092 }
9093
9094 // And double spaces
9095 if ($removedoublespaces) {
9096 while (strpos($temp, " ") !== false) {
9097 $temp = str_replace(" ", " ", $temp);
9098 }
9099 }
9100
9101 $temp = str_replace('__ltspace__', '< ', $temp);
9102 $temp = str_replace('__lttwopoints__', '<:', $temp);
9103
9104 return trim($temp);
9105}
9106
9126function dol_string_onlythesehtmltags($stringtoclean, $cleanalsosomestyles = 1, $removeclassattribute = 1, $cleanalsojavascript = 0, $allowiframe = 0, $allowed_tags = array(), $allowlink = 0, $allowscript = 0, $allowstyle = 0, $allowphp = 0)
9127{
9128 $sav_allowed_tags = $allowed_tags;
9129
9130 if (empty($allowed_tags) || (is_string($allowed_tags) && preg_match('/^common/', $allowed_tags))) {
9131 $allowed_tags = array(
9132 // HTML 4
9133 "html",
9134 "head",
9135 "body",
9136 "article",
9137 "a",
9138 "abbr",
9139 "b",
9140 "blockquote",
9141 "br",
9142 "cite",
9143 "div",
9144 "dl",
9145 "dd",
9146 "dt",
9147 "em",
9148 "font",
9149 "img",
9150 "ins",
9151 "hr",
9152 "i",
9153 "li",
9154 "ol",
9155 "p",
9156 "q",
9157 "s",
9158 "span",
9159 "strike",
9160 "strong",
9161 "title",
9162 "table",
9163 "tr",
9164 "th",
9165 "td",
9166 "u",
9167 "ul",
9168 "sup",
9169 "sub",
9170 "blockquote",
9171 "pre",
9172 "h1",
9173 "h2",
9174 "h3",
9175 "h4",
9176 "h5",
9177 "h6",
9178
9179 // HTML 5
9180 "footer",
9181 "header",
9182 "menu",
9183 "menuitem",
9184 "nav",
9185 "section"
9186 );
9187 }
9188 $allowed_tags[] = "comment"; // this tags is added to manage comment <!--...--> that are replaced into <comment>...</comment>
9189 if ($allowiframe) {
9190 if (!in_array('iframe', $allowed_tags)) {
9191 $allowed_tags[] = "iframe";
9192 }
9193 }
9194 if ($allowlink) {
9195 if (!in_array('link', $allowed_tags)) {
9196 $allowed_tags[] = "link";
9197 }
9198 if (!in_array('meta', $allowed_tags)) {
9199 $allowed_tags[] = "meta";
9200 }
9201 }
9202 if ($allowscript) {
9203 if (!in_array('script', $allowed_tags)) {
9204 $allowed_tags[] = "script";
9205 }
9206 }
9207 if ($allowstyle) {
9208 if (!in_array('style', $allowed_tags)) {
9209 $allowed_tags[] = "style";
9210 }
9211 }
9212 if (is_string($sav_allowed_tags)) {
9213 $tmptags = explode(',', $sav_allowed_tags);
9214 foreach ($tmptags as $tag) {
9215 if ($tag != 'common') {
9216 $allowed_tags[] = $tag;
9217 }
9218 }
9219 }
9220
9221
9222 $allowed_tags_string = implode("><", $allowed_tags);
9223 $allowed_tags_string = '<' . $allowed_tags_string . '>';
9224
9225 $stringtoclean = str_replace('<!DOCTYPE html>', '__!DOCTYPE_HTML__', $stringtoclean); // Replace DOCTYPE to avoid to have it removed by the strip_tags
9226
9227 $stringtoclean = dol_string_nounprintableascii($stringtoclean, 0);
9228
9229 //$stringtoclean = preg_replace('/<!--[^>]*-->/', '', $stringtoclean);
9230 $stringtoclean = preg_replace('/<!--([^>]*)-->/', '<comment>\1</comment>', $stringtoclean);
9231
9232 if ($allowphp) {
9233 $allowed_tags[] = "commentphp";
9234 $stringtoclean = preg_replace('/^<\?php([^"]+)\?>$/i', '<commentphp>\1__</commentphp>', $stringtoclean); // Note: <?php ... > is allowed only if on the same line
9235 $stringtoclean = preg_replace('/"<\?php([^"]+)\?>"/i', '"<commentphp>\1</commentphp>"', $stringtoclean); // Note: "<?php ... >" is allowed only if on the same line
9236 }
9237
9238 $stringtoclean = preg_replace('/&colon;/i', ':', $stringtoclean);
9239 $stringtoclean = preg_replace('/&#58;|&#0+58|&#x3A/i', '', $stringtoclean); // refused string ':' encoded (no reason to have a : encoded like this) to disable 'javascript:...'
9240
9241 // Remove all HTML tags
9242 $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
9243
9244 if ($cleanalsosomestyles) { // Clean for remaining html tags
9245 //$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
9246 $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
9247 $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
9248 }
9249 if ($removeclassattribute) { // Clean for remaining html tags
9250 $temp = preg_replace('/(<[^>]+)\s+class=((["\']).*?\\3|\\w*)/i', '\\1', $temp);
9251 }
9252
9253 // Remove 'javascript:' that we should not find into a text
9254 // 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)).
9255 if ($cleanalsojavascript) {
9256 $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);
9257 }
9258
9259 $temp = str_replace('__!DOCTYPE_HTML__', '<!DOCTYPE html>', $temp); // Restore the DOCTYPE
9260
9261 if ($allowphp) {
9262 $temp = preg_replace('/<commentphp>(.*)<\/commentphp>/', '<?php\1?>', $temp); // Restore php code
9263 }
9264
9265 $temp = preg_replace('/<comment>([^>]*)<\/comment>/', '<!--\1-->', $temp); // Restore html comments
9266
9267
9268 return $temp;
9269}
9270
9271
9284function dol_string_onlythesehtmlattributes($stringtoclean, $allowed_attributes = null)
9285{
9286 if (is_null($allowed_attributes)) {
9287 $allowed_attributes = array(
9288 // HTML 4
9289 "allow",
9290 "allowfullscreen",
9291 "alt",
9292 "async",
9293 "class",
9294 "contenteditable",
9295 "crossorigin",
9296 "data-html",
9297 "frameborder",
9298 "height",
9299 "href",
9300 "id",
9301 "name",
9302 "property",
9303 "rel",
9304 "src",
9305 "style",
9306 "target",
9307 "title",
9308 "type",
9309 "width",
9310
9311 // HTML5
9312 "footer",
9313 "header",
9314 "menu",
9315 "menuitem",
9316 "nav",
9317 "section"
9318 );
9319 }
9320 // Always add content and http-equiv for meta tags, required to force encoding and keep html content in utf8 by load/saveHTML functions.
9321 if (!in_array("content", $allowed_attributes)) {
9322 $allowed_attributes[] = "content";
9323 }
9324 if (!in_array("http-equiv", $allowed_attributes)) {
9325 $allowed_attributes[] = "http-equiv";
9326 }
9327
9328 if (class_exists('DOMDocument') && !empty($stringtoclean)) {
9329 //$stringtoclean = '<?xml encoding="UTF-8"><html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>'.$stringtoclean.'</body></html>';
9330 $stringtoclean = '<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>' . $stringtoclean . '</body></html>';
9331
9332 // Warning: loadHTML does not support HTML5 on old libxml versions.
9333 $dom = new DOMDocument('', 'UTF-8');
9334 // If $stringtoclean is wrong, it will generates warnings. So we disable warnings and restore them later.
9335 $savwarning = error_reporting();
9336 error_reporting(E_ALL & ~E_WARNING & ~E_NOTICE);
9337 $dom->loadHTML($stringtoclean, LIBXML_ERR_NONE | LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD | LIBXML_NONET | LIBXML_NOWARNING | LIBXML_NOXMLDECL);
9338 error_reporting($savwarning);
9339
9340 if ($dom instanceof DOMDocument) {
9341 for ($els = $dom->getElementsByTagname('*'), $i = $els->length - 1; $i >= 0; $i--) {
9342 $el = $els->item($i);
9343 if (!$el instanceof DOMElement) {
9344 continue;
9345 }
9346 $attrs = $el->attributes;
9347 for ($ii = $attrs->length - 1; $ii >= 0; $ii--) {
9348 //var_dump($attrs->item($ii));
9349 if (!empty($attrs->item($ii)->name)) {
9350 if (! in_array($attrs->item($ii)->name, $allowed_attributes)) {
9351 // Delete attribute if not into allowed_attributes @phan-suppress-next-line PhanUndeclaredMethod
9352 $els->item($i)->removeAttribute($attrs->item($ii)->name);
9353 } elseif (in_array($attrs->item($ii)->name, array('style'))) {
9354 // If attribute is 'style'
9355 $valuetoclean = $attrs->item($ii)->value;
9356
9357 if (isset($valuetoclean)) {
9358 do {
9359 $oldvaluetoclean = $valuetoclean;
9360 $valuetoclean = preg_replace('/\/\*.*\*\//m', '', $valuetoclean); // clean css comments
9361 $valuetoclean = preg_replace('/position\s*:\s*[a-z]+/mi', '', $valuetoclean);
9362 if ($els->item($i)->tagName == 'a') { // more paranoiac cleaning for clickable tags.
9363 $valuetoclean = preg_replace('/display\s*:/mi', '', $valuetoclean);
9364 $valuetoclean = preg_replace('/z-index\s*:/mi', '', $valuetoclean);
9365 $valuetoclean = preg_replace('/\s+(top|left|right|bottom)\s*:/mi', '', $valuetoclean);
9366 }
9367
9368 // We do not allow logout|passwordforgotten.php and action= into the content of a "style" tag
9369 $valuetoclean = preg_replace('/(logout|passwordforgotten)\.php/mi', '', $valuetoclean);
9370 $valuetoclean = preg_replace('/action=/mi', '', $valuetoclean);
9371 } while ($oldvaluetoclean != $valuetoclean);
9372 }
9373
9374 $attrs->item($ii)->value = $valuetoclean;
9375 }
9376 }
9377 }
9378 }
9379 }
9380
9381 $dom->encoding = 'UTF-8';
9382
9383 $return = $dom->saveHTML(); // This may add a LF at end of lines, so we will trim later
9384 //$return = '<html><body>aaaa</p>bb<p>ssdd</p>'."\n<p>aaa</p>aa<p>bb</p>";
9385
9386 //$return = preg_replace('/^'.preg_quote('<?xml encoding="UTF-8">', '/').'/', '', $return);
9387 $return = preg_replace('/^' . preg_quote('<html><head><', '/') . '[^<>]*' . preg_quote('></head><body>', '/') . '/', '', $return);
9388 $return = preg_replace('/' . preg_quote('</body></html>', '/') . '$/', '', trim($return));
9389
9390 return trim($return);
9391 } else {
9392 return $stringtoclean;
9393 }
9394}
9395
9407function dol_string_neverthesehtmltags($stringtoclean, $disallowed_tags = array('textarea'), $cleanalsosomestyles = 0)
9408{
9409 $temp = $stringtoclean;
9410 foreach ($disallowed_tags as $tagtoremove) {
9411 $temp = preg_replace('/<\/?' . $tagtoremove . '>/', '', $temp);
9412 $temp = preg_replace('/<\/?' . $tagtoremove . '\s+[^>]*>/', '', $temp);
9413 }
9414
9415 if ($cleanalsosomestyles) {
9416 $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
9417 }
9418
9419 return $temp;
9420}
9421
9422
9432function dolCloseUnclosedHtmlTags($text)
9433{
9434 if (!is_string($text) || $text === '') {
9435 return $text;
9436 }
9437
9438 // Tags that never carry a closing tag
9439 $selfclosing = array('br', 'hr', 'img', 'input', 'meta', 'link', 'source', 'col', 'area', 'base', 'embed', 'param', 'track', 'wbr');
9440
9441 $opened = array();
9442 if (preg_match_all('/<\s*(\/?)([a-zA-Z][a-zA-Z0-9]*)[^>]*?(\/?)\s*>/', $text, $matches, PREG_SET_ORDER)) {
9443 foreach ($matches as $match) {
9444 $tag = strtolower($match[2]);
9445 if (in_array($tag, $selfclosing) || !empty($match[3])) {
9446 continue;
9447 }
9448 if (empty($match[1])) {
9449 $opened[] = $tag;
9450 } else {
9451 // Close the most recent matching opened tag, ignore a stray closing tag
9452 $idx = array_search($tag, array_reverse($opened, true), true);
9453 if ($idx !== false) {
9454 unset($opened[$idx]);
9455 }
9456 }
9457 }
9458 }
9459
9460 foreach (array_reverse($opened) as $tag) {
9461 $text .= '</'.$tag.'>';
9462 }
9463
9464 return $text;
9465}
9466
9476function dolGetFirstLineOfText($text, $nboflines = 1, $charset = 'UTF-8')
9477{
9478 if ($nboflines == 1) {
9479 if (dol_textishtml($text)) {
9480 $firstline = preg_replace('/<br[^>]*>.*$/s', '', $text); // The s pattern modifier means the . can match newline characters
9481 $firstline = preg_replace('/<div[^>]*>.*$/s', '', $firstline); // The s pattern modifier means the . can match newline characters
9482 } else {
9483 if (isset($text)) {
9484 $firstline = preg_replace('/[\n\r].*/', '', $text);
9485 } else {
9486 $firstline = '';
9487 }
9488 }
9489 return $firstline . (isset($firstline) && isset($text) && (strlen($firstline) != strlen($text)) ? '...' : '');
9490 } else {
9491 $ishtml = 0;
9492 if (dol_textishtml($text)) {
9493 $text = preg_replace('/\n/', '', $text);
9494 $ishtml = 1;
9495 $repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
9496 } else {
9497 $repTable = array("\t" => " ", "\n" => "<br>", "\r" => " ", "\0" => " ", "\x0B" => " ");
9498 }
9499
9500 $text = strtr($text, $repTable);
9501 if ($charset == 'UTF-8') {
9502 $pattern = '/(<br[^>]*>)/Uu';
9503 } else {
9504 // /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
9505 $pattern = '/(<br[^>]*>)/U'; // /U is to have UNGREEDY regex to limit to one html tag.
9506 }
9507 $a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
9508
9509 $firstline = '';
9510 $i = 0;
9511 $countline = 0;
9512 $lastaddediscontent = 1;
9513 while ($countline < $nboflines && isset($a[$i])) {
9514 if (preg_match('/<br[^>]*>/', $a[$i])) {
9515 if (array_key_exists($i + 1, $a) && !empty($a[$i + 1])) {
9516 $firstline .= ($ishtml ? "<br>\n" : "\n");
9517 // Is it a br for a new line of after a printed line ?
9518 if (!$lastaddediscontent) {
9519 $countline++;
9520 }
9521 $lastaddediscontent = 0;
9522 }
9523 } else {
9524 $firstline .= $a[$i];
9525 $lastaddediscontent = 1;
9526 $countline++;
9527 }
9528 $i++;
9529 }
9530
9531 $adddots = (isset($a[$i]) && (!preg_match('/<br[^>]*>/', $a[$i]) || (array_key_exists($i + 1, $a) && !empty($a[$i + 1]))));
9532 //unset($a);
9533 $ret = $firstline . ($adddots ? '...' : '');
9534 //exit;
9535 return $ret;
9536 }
9537}
9538
9539
9551function dol_nl2br($stringtoencode, $nl2brmode = 0, $forxml = false)
9552{
9553 if (is_null($stringtoencode)) {
9554 return '';
9555 }
9556
9557 if (!$nl2brmode) {
9558 return nl2br($stringtoencode, $forxml);
9559 } else {
9560 $ret = preg_replace('/(\r\n|\r|\n)/i', ($forxml ? '<br />' : '<br>'), $stringtoencode);
9561 return $ret;
9562 }
9563}
9564
9574function dol_htmlwithnojs($stringtoencode, $nouseofiframesandbox = 0, $check = 'restricthtml')
9575{
9576 if (empty($nouseofiframesandbox) && getDolGlobalString('MAIN_SECURITY_USE_SANDBOX_FOR_HTMLWITHNOJS')) {
9577 // TODO using sandbox on inline html content is not possible yet with current browsers
9578 //$s = '<iframe class="iframewithsandbox" sandbox><html><body>';
9579 //$s .= $stringtoencode;
9580 //$s .= '</body></html></iframe>';
9581 return $stringtoencode;
9582 } else {
9583 $out = $stringtoencode;
9584
9585 // First clean HTML content
9586 do {
9587 $oldstringtoclean = $out;
9588
9589 $outishtml = 0;
9590 if (dol_textishtml($out)) {
9591 $outishtml = 1;
9592 }
9593
9594 // HTML sanitizer by DOMDocument
9595 if (!empty($out) && getDolGlobalInt('MAIN_RESTRICTHTML_ONLY_VALID_HTML') && $check != 'restricthtmlallowunvalid') {
9596 try {
9597 libxml_use_internal_errors(false); // Avoid to fill memory with xml errors
9598 if (LIBXML_VERSION < 20900) {
9599 // Avoid load of external entities (security problem).
9600 // Required only if LIBXML_VERSION < 20900
9601 // @phan-suppress-next-line PhanDeprecatedFunctionInternal
9602 libxml_disable_entity_loader(true);
9603 }
9604
9605 $dom = new DOMDocument();
9606 // Add a trick '<div class="tricktoremove">' to solve pb with text without parent tag
9607 // like '<h1>Foo</h1><p>bar</p>' that wrongly ends up, without the trick, with '<h1>Foo<p>bar</p></h1>'
9608 // like 'abc' that wrongly ends up, without the trick, with '<p>abc</p>'
9609 // Add also a trick <html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"> to solve utf8 lost.
9610 // I don't know what the xml encoding is the trick for
9611 if ($outishtml) {
9612 //$out = '<?xml encoding="UTF-8"><html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body><div class="tricktoremove">'.$out.'</div></body></html>';
9613 $out = '<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body><div class="tricktoremove">' . $out . '</div></body></html>';
9614 //$out = '<html><head><meta charset="utf-8"></head><body><div class="tricktoremove">'.$out.'</div></body></html>';
9615 } else {
9616 // String is not a HTML content but pure text so we accept the & char alone. We protect it from being modified by HTML Tidy or XML sanitizers
9617 $out = preg_replace('/&(?![a-zA-Z0-9#]+;)/', '__AMPINTEXT__', $out);
9618
9619 //$out = '<?xml encoding="UTF-8"><html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body><div class="tricktoremove">'.dol_nl2br($out).'</div></body></html>';
9620 $out = '<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body><div class="tricktoremove">' . dol_nl2br($out) . '</div></body></html>';
9621 //$out = '<html><head><meta charset="utf-8"></head><body><div class="tricktoremove">'.dol_nl2br($out).'</div></body></html>';
9622 }
9623
9624 // Note: <a href="https://__[aaa]__/aaa.html"> is transformed into <a href="https://__[aaa]__/aaa.html">
9625 // We don't want that, so we protect __[xxx]__ by replacing [ and ] before loadHTML and restore them after saveHTML
9626 $out = preg_replace_callback(
9627 '/__\[([0-9a-zA-Z_]+)\]__/',
9632 function ($m) {
9633 return '__BRACKETSTART' . $m[1] . 'BRACKETEND__';
9634 },
9635 $out
9636 );
9637
9638 $dom->loadHTML($out, LIBXML_HTML_NODEFDTD | LIBXML_ERR_NONE | LIBXML_HTML_NOIMPLIED | LIBXML_NONET | LIBXML_NOWARNING | LIBXML_NOERROR | LIBXML_NOXMLDECL);
9639
9640 $dom->encoding = 'UTF-8';
9641
9642 // Add a layer to remove some styles
9643 if (getDolGlobalInt('MAIN_RESTRICTHTML_ONLY_VALID_HTML') == 2) {
9644 foreach ($dom->getElementsByTagName('*') as $el) {
9645 if (!$el instanceof DOMElement) {
9646 continue;
9647 }
9648
9649 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
9650 if ($el->hasAttribute('style')) {
9651 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
9652 $style = $el->getAttribute('style');
9653
9654 // delete some styles
9655 $style = preg_replace('/z-index\s*:/i', '', $style);
9656 $style = preg_replace('/position\s*:/i', '', $style);
9657 $style = preg_replace('/top\s*:/i', '', $style);
9658 $style = preg_replace('/left\s*:/i', '', $style);
9659 $style = preg_replace('/background\s*:/i', '', $style);
9660 /*
9661 $style = preg_replace('/width\s*:/i', '', $style);
9662 $style = preg_replace('/height\s*:/i', '', $style);
9663 $style = preg_replace('/backdrop-filter\s*:/i', '', $style);
9664 */
9665 if (trim($style) === '') {
9666 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
9667 $el->removeAttribute('style');
9668 } else {
9669 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
9670 $el->setAttribute('style', $style);
9671 }
9672 }
9673 }
9674 }
9675
9676 $out = trim($dom->saveHTML());
9677
9678 // Restore [ and ] that were protected before loadHTML
9679 $out = preg_replace_callback(
9680 '/__BRACKETSTART([0-9a-zA-Z_]+)BRACKETEND__/',
9685 function ($m) {
9686 return '__[' . $m[1] . ']__';
9687 },
9688 $out
9689 );
9690
9691 // Remove the trick added to solve pb with text in utf8 and text without parent tag
9692 //$out = preg_replace('/^'.preg_quote('<?xml encoding="UTF-8">', '/').'/', '', $out);
9693 $out = preg_replace('/^' . preg_quote('<html><head><', '/') . '[^<>]+' . preg_quote('></head><body><div class="tricktoremove">', '/') . '/', '', $out);
9694 $out = preg_replace('/' . preg_quote('</div></body></html>', '/') . '$/', '', trim($out));
9695 //$out = preg_replace('/^<\?xml encoding="UTF-8"><div class="tricktoremove">/', '', $out);
9696 //$out = preg_replace('/<\/div>$/', '', $out);
9697
9698 if (!$outishtml) { // If $out was not HTML content we made before a dol_nl2br so we must do the opposite operation now
9699 $out = str_replace('__AMPINTEXT__', '&', $out); // Restore & char not entity
9700 $out = str_replace('<br>', '', $out);
9701 }
9702 } catch (Exception $e) {
9703 // If error, invalid HTML string with no way to clean it
9704 //print $e->getMessage();
9705 $out = 'InvalidHTMLStringCantBeCleaned ' . $e->getMessage();
9706 }
9707 }
9708
9709 // HTML sanitizer by Tidy
9710 // Tidy can't be used for restricthtmlallowunvalid and restricthtmlallowlinkscript
9711 // Tidy can't be used for non html text content as it is corrupting the new lines fields.
9712 if (!empty($out) && getDolGlobalInt('MAIN_RESTRICTHTML_ONLY_VALID_HTML_TIDY') && !in_array($check, array('restricthtmlallowunvalid', 'restricthtmlallowlinkscript')) && $outishtml) {
9713 // TODO Try to implement a hack for restricthtmlallowlinkscript by renaming tag <link> and <script> ?
9714 try {
9715 //var_dump($out);
9716
9717 // Try cleaning using tidy
9718 if (extension_loaded('tidy') && class_exists("tidy")) {
9719 //print "aaa".$out."\n";
9720
9721 // See options at https://tidy.sourceforge.net/docs/quickref.html
9722 $config = array(
9723 'clean' => false,
9724 // 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;
9725 'quote-marks' => false,
9726 'doctype' => 'strict',
9727 'show-body-only' => true,
9728 "indent-attributes" => false,
9729 "vertical-space" => false,
9730 //'ident' => false, // Not always supported
9731 "wrap" => 0,
9732 'preserve-entities' => true
9733 // HTML5 tags
9734 //'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',
9735 //'new-blocklevel-tags' => 'footer header section menu menuitem'
9736 //'new-empty-tags' => 'command embed keygen source track wbr',
9737 //'new-inline-tags' => 'audio command datalist embed keygen mark menuitem meter output progress source time video wbr',
9738 );
9739
9740 // Tidy
9741 $locale = setlocale(LC_NUMERIC, '0'); // Tidy has a bug and is changing the PHP locale. So we save it to restore it after.
9742
9743 $tidy = new tidy();
9744 $out = $tidy->repairString($out, $config, 'utf8');
9745
9746 setlocale(LC_NUMERIC, $locale); // Restore original local
9747
9748 //print "xxx".$out;exit;
9749 }
9750
9751 //var_dump($out);
9752 } catch (Exception $e) {
9753 // If error, invalid HTML string with no way to clean it
9754 //print $e->getMessage();
9755 $out = 'InvalidHTMLStringCantBeCleaned ' . $e->getMessage();
9756 }
9757 }
9758
9759 // Clear ZERO WIDTH NO-BREAK SPACE, ZERO WIDTH SPACE, ZERO WIDTH JOINER
9760 // TODO $out = preg_replace('/[\x{2000}-\x{200D}\x{FEFF}]/u', ' ', $out);
9761 $out = preg_replace('/[\x{200B}-\x{200D}\x{FEFF}]/u', ' ', $out);
9762
9763 // Clean some html entities that are useless so text is cleaner
9764 $out = preg_replace('/&(tab|newline);/i', ' ', $out);
9765
9766 // Ckeditor uses the numeric entity for apostrophe, so we force it to
9767 // the text entity (all other special chars are encoded using text entities) so we can then exclude all numeric entities.
9768 $out = preg_replace('/&#39;/i', '&apos;', $out);
9769
9770 // 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).
9771 // 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
9772 // using a non conventionnal way to be encoded, to not have them sanitized just after)
9773 if (function_exists('realCharForNumericEntities')) { // May not exist when main.inc.php not loaded, for example in a CLI context
9774 $out = preg_replace_callback(
9775 '/&#(x?[0-9][0-9a-f]+;?)/i',
9780 static function ($m) {
9781 return realCharForNumericEntities($m);
9782 },
9783 $out
9784 );
9785 }
9786
9787 // Now we remove all remaining HTML entities starting with a number. We don't want such entities.
9788 $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'.
9789
9790 // Keep only some html tags and remove also some 'javascript:' strings
9791 if ($check == 'restricthtmlallowlinkscript') {
9792 $out = dol_string_onlythesehtmltags($out, 0, 1, 0, 0, array(), 1, 1, 1, getDolGlobalInt("UNSECURED_restricthtmlallowlinkscript_ALLOW_PHP"));
9793 } elseif ($check == 'restricthtmlallowclass' || $check == 'restricthtmlallowunvalid') {
9794 $out = dol_string_onlythesehtmltags($out, 0, 0, 1);
9795 } elseif ($check == 'restricthtmlallowiframe') {
9796 $out = dol_string_onlythesehtmltags($out, 0, 0, 1, 1);
9797 } else {
9798 $out = dol_string_onlythesehtmltags($out, 0, 1, 1); // styles are allowed to allow rich text editor features of ckeditor managed by the "style=" attribute
9799 }
9800
9801 // Keep only some html attributes and exclude non expected HTML attributes and clean content of some attributes (keep only alt=, title=...).
9802 if (getDolGlobalString('MAIN_RESTRICTHTML_REMOVE_ALSO_BAD_ATTRIBUTES')) {
9804 }
9805
9806 // Restore entity &apos; into &#39; (restricthtml is for html content so we can use html entity) because it is
9807 // compatible with HTML 4 used y CKEditor, and HTML 5 (when &apos; works only with HTML5).
9808 $out = preg_replace('/&apos;/i', "&#39;", $out);
9809
9810 // Now remove js
9811 // 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
9812 $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)>
9813 $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);
9814 $out = preg_replace('/on(dblclick|drop|durationchange|emptied|end|ended|error|focus(in|out)?|formdata|gotpointercapture|hashchange|input|invalid)[a-z]*\s*=/i', '', $out);
9815 $out = preg_replace('/on(lost|offline|online|message|pagehide|pageshow)[a-z]*\s*=/i', '', $out);
9816 $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);
9817 $out = preg_replace('/on(timeupdate|toggle|unhandled|unload|volumechange|waiting|wheel)[a-z]*\s*=/i', '', $out);
9818 // More not into the previous list
9819 $out = preg_replace('/on(repeat|begin|finish|beforeinput)[a-z]*\s*=/i', '', $out);
9820 // Add also a generic removal of any onxxx= attribute
9821 $out = preg_replace('/\son[a-z]+\s*=/i', ' ', $out);
9822 } while ($oldstringtoclean != $out);
9823
9824 // Check the limit of external links that are automatically executed in a Rich text content. We count:
9825 // '<img' to avoid <img src="http...">, we can only accept "<img src="data:..."
9826 // 'url(' to avoid inline style like background: url(http...
9827 // '<link' to avoid <link href="http...">
9828 $reg = array();
9829 $tmpout = preg_replace('/<img src="data:/mi', '<__IMG_SRC_DATA__ src="data:', $out);
9830 preg_match_all('/(<img|url\‍(|<link)/i', $tmpout, $reg);
9831 $nblinks = count($reg[0]);
9832 if ($nblinks > getDolGlobalInt("MAIN_SECURITY_MAX_IMG_IN_HTML_CONTENT", 1000)) {
9833 $out = 'ErrorTooManyLinksIntoHTMLString';
9834 }
9835
9836 if (getDolGlobalInt('MAIN_DISALLOW_URL_INTO_DESCRIPTIONS') == 2 || $check == 'restricthtmlnolink') {
9837 if ($nblinks > 0) {
9838 $out = 'ErrorHTMLLinksNotAllowed';
9839 }
9840 } elseif (getDolGlobalInt('MAIN_DISALLOW_URL_INTO_DESCRIPTIONS') == 1) {
9841 // Refuse any links except it they are to the wrapper document.php or viewimage.php
9842 $nblinks = 0;
9843
9844 // Loop on each url in src= and url(
9845 $pattern = '/src=["\']?(http[^"\']+)|url\‍(["\']?(http[^\‍)]+)/';
9846
9848
9849 $matches = array();
9850 if (preg_match_all($pattern, $out, $matches)) {
9851 // URLs are into $matches[1] or $matches[2]
9852 $urls = array();
9853 foreach ($matches[1] as $tmpval) {
9854 if (!empty($tmpval)) {
9855 $urls[] = $tmpval;
9856 }
9857 }
9858 foreach ($matches[2] as $tmpval) {
9859 if (!empty($tmpval)) {
9860 $urls[] = $tmpval;
9861 }
9862 }
9863
9864 // Show URLs
9865 $firstexturl = '';
9866 $secondexturl = '';
9867 foreach ($urls as $url) {
9868 $urlok = 0;
9869 $parsedurl = parse_url($url);
9870 if (!empty($parsedurl)) {
9871 if (preg_match('/'.preg_quote($dolibarr_main_url_root, '/').'/', $url)
9872 //&& preg_match('/(document|viewimage)\.php$/', $parsedurl['path']) && preg_match('/modulepart=(media|mycompany)/', $parsedurl['query'])
9873 ) {
9874 $urlok = 1;
9875 }
9876 }
9877 if (!$urlok) {
9878 $nblinks++;
9879 if (empty($firstexturl)) {
9880 $firstexturl = $url;
9881 } elseif (empty($secondexturl)) {
9882 $secondexturl = $url;
9883 }
9884 //echo "Found url = ".$url . "\n";
9885 }
9886 }
9887 if ($nblinks > 0) {
9888 $out = 'ErrorHTMLExternalLinksNotAllowed (Example: '.$firstexturl.($secondexturl ? ' '.$secondexturl : '').')';
9889 }
9890 }
9891 }
9892
9893 return $out;
9894 }
9895}
9896
9917function dol_htmlentitiesbr($stringtoencode, $nl2brmode = 0, $pagecodefrom = 'UTF-8', $removelasteolbr = 1)
9918{
9919 if (is_null($stringtoencode)) {
9920 return '';
9921 }
9922
9923 $newstring = $stringtoencode;
9924 if (dol_textishtml($stringtoencode)) { // Check if text is already HTML or not
9925 $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.
9926 if ($removelasteolbr) {
9927 $newstring = preg_replace('/<br>$/i', '', $newstring); // Remove last <br> (remove only last one)
9928 }
9929 $newstring = preg_replace('/[\x{200B}-\x{200D}\x{FEFF}]/u', ' ', $newstring);
9930 $newstring = strtr($newstring, array('&' => '__PROTECTand__', '<' => '__PROTECTlt__', '>' => '__PROTECTgt__', '"' => '__PROTECTdquot__'));
9931 $newstring = dol_htmlentities($newstring, ENT_COMPAT, $pagecodefrom); // Make entity encoding
9932 $newstring = strtr($newstring, array('__PROTECTand__' => '&', '__PROTECTlt__' => '<', '__PROTECTgt__' => '>', '__PROTECTdquot__' => '"'));
9933 } else {
9934 if ($removelasteolbr) {
9935 $newstring = preg_replace('/(\r\n|\r|\n)$/i', '', $newstring); // Remove last \n (may remove several)
9936 }
9937 $newstring = dol_nl2br(dol_htmlentities($newstring, ENT_COMPAT, $pagecodefrom), $nl2brmode);
9938 }
9939 // Other substitutions that htmlentities does not do
9940 //$newstring=str_replace(chr(128),'&euro;',$newstring); // 128 = 0x80. Not in html entity table. // Seems useles with TCPDF. Make bug with UTF8 languages
9941 return $newstring;
9942}
9943
9951function dol_htmlentitiesbr_decode($stringtodecode, $pagecodeto = 'UTF-8')
9952{
9953 $ret = dol_html_entity_decode($stringtodecode, ENT_COMPAT | ENT_HTML5, $pagecodeto);
9954 $ret = preg_replace('/' . "\r\n" . '<br(\s[\sa-zA-Z_="]*)?\/?>/i', "<br>", $ret);
9955 $ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>' . "\r\n" . '/i', "\r\n", $ret);
9956 $ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>' . "\n" . '/i', "\n", $ret);
9957 $ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>/i', "\n", $ret);
9958 return $ret;
9959}
9960
9967function dol_htmlcleanlastbr($stringtodecode)
9968{
9969 $ret = preg_replace('/&nbsp;$/i', "", $stringtodecode); // Because wysiwyg editor may add a &nbsp; at end of last line
9970 $ret = preg_replace('/(<br>|<br(\s[\sa-zA-Z_="]*)?\/?>|' . "\n" . '|' . "\r" . ')+$/i', "", $ret);
9971 return $ret;
9972}
9973
9983function dol_html_entity_decode($a, $b, $c = 'UTF-8', $keepsomeentities = 0)
9984{
9985 $newstring = $a;
9986 if ($keepsomeentities) {
9987 $newstring = strtr($newstring, array('&amp;' => '__andamp__', '&lt;' => '__andlt__', '&gt;' => '__andgt__', '"' => '__dquot__'));
9988 }
9989 $newstring = html_entity_decode((string) $newstring, (int) $b, (string) $c);
9990 if ($keepsomeentities) {
9991 $newstring = strtr($newstring, array('__andamp__' => '&amp;', '__andlt__' => '&lt;', '__andgt__' => '&gt;', '__dquot__' => '"'));
9992 }
9993 return $newstring;
9994}
9995
10007function dol_htmlentities($string, $flags = ENT_QUOTES | ENT_SUBSTITUTE, $encoding = 'UTF-8', $double_encode = false)
10008{
10009 return htmlentities($string, $flags, $encoding, $double_encode);
10010}
10011
10023function dol_string_is_good_iso($s, $clean = 0)
10024{
10025 $len = dol_strlen($s);
10026 $out = '';
10027 $ok = 1;
10028 for ($scursor = 0; $scursor < $len; $scursor++) {
10029 $ordchar = ord($s[$scursor]);
10030 //print $scursor.'-'.$ordchar.'<br>';
10031 if ($ordchar < 32 && $ordchar != 13 && $ordchar != 10) {
10032 $ok = 0;
10033 break;
10034 } elseif ($ordchar > 126 && $ordchar < 160) {
10035 $ok = 0;
10036 break;
10037 } elseif ($clean) {
10038 $out .= $s[$scursor];
10039 }
10040 }
10041 if ($clean) {
10042 return $out;
10043 }
10044 return $ok;
10045}
10046
10055function dol_nboflines($s, $maxchar = 0)
10056{
10057 if ($s == '') {
10058 return 0;
10059 }
10060 $arraystring = explode("\n", $s);
10061 $nb = count($arraystring);
10062
10063 return $nb;
10064}
10065
10066
10076function dol_nboflines_bis($text, $maxlinesize = 0, $charset = 'UTF-8')
10077{
10078 $repTable = array("\t" => " ", "\n" => "<br>", "\r" => " ", "\0" => " ", "\x0B" => " ");
10079 if (dol_textishtml($text)) {
10080 $repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
10081 }
10082
10083 $text = strtr($text, $repTable);
10084 if ($charset == 'UTF-8') {
10085 $pattern = '/(<br[^>]*>)/Uu';
10086 } else {
10087 // /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
10088 $pattern = '/(<br[^>]*>)/U'; // /U is to have UNGREEDY regex to limit to one html tag.
10089 }
10090 $a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
10091
10092 $nblines = (int) floor((count($a) + 1) / 2);
10093 // count possible auto line breaks
10094 if ($maxlinesize) {
10095 foreach ($a as $line) {
10096 if (dol_strlen($line) > $maxlinesize) {
10097 //$line_dec = html_entity_decode(strip_tags($line));
10098 $line_dec = html_entity_decode($line);
10099 if (dol_strlen($line_dec) > $maxlinesize) {
10100 $line_dec = wordwrap($line_dec, $maxlinesize, '\n', true);
10101 $nblines += substr_count($line_dec, '\n');
10102 }
10103 }
10104 }
10105 }
10106
10107 unset($a);
10108 return $nblines;
10109}
10110
10119function dol_textishtml($msg, $option = 0)
10120{
10121 if (is_null($msg)) {
10122 return false;
10123 }
10124
10125 if ($option == 1) {
10126 if (preg_match('/<(html|link|script)/i', $msg)) {
10127 return true;
10128 } elseif (preg_match('/<body/i', $msg)) {
10129 return true;
10130 } elseif (preg_match('/<\/textarea/i', $msg)) {
10131 return true;
10132 } elseif (preg_match('/<(b|em|i|u)(\s+[^>]+)?>/i', $msg)) {
10133 return true;
10134 } elseif (preg_match('/<br/i', $msg)) {
10135 return true;
10136 }
10137 return false;
10138 } else {
10139 // Remove all urls because 'http://aa?param1=abc&amp;param2=def' must not be used inside detection
10140 $msg = preg_replace('/https?:\/\/[^"\'\s]+/i', '', $msg);
10141 if (preg_match('/<(html|link|script|body)/i', $msg)) {
10142 return true;
10143 } elseif (preg_match('/<\/textarea/i', $msg)) {
10144 return true;
10145 } elseif (preg_match('/<(b|em|i|u)(\s+[^>]+)?>/i', $msg)) {
10146 return true;
10147 } elseif (preg_match('/<(br|hr)\/>/i', $msg)) {
10148 return true;
10149 } elseif (preg_match('/<(br|hr|div|font|li|p|span|strong|table)>/i', $msg)) {
10150 return true;
10151 } elseif (preg_match('/<(br|hr|div|font|li|p|span|strong|table)\s+[^<>\/]*\/?>/i', $msg)) {
10152 return true;
10153 } elseif (preg_match('/<img\s+[^<>]*src[^<>]*>/i', $msg)) {
10154 return true; // must accept <img src="http://example.com/aaa.png" />
10155 } elseif (preg_match('/<a\s+[^<>]*href[^<>]*>/i', $msg)) {
10156 return true; // must accept <a href="http://example.com/aaa.png" />
10157 } elseif (preg_match('/<h[0-9]>/i', $msg)) {
10158 return true;
10159 } elseif (preg_match('/&[A-Z0-9]{1,6};/i', $msg)) {
10160 // TODO If content is 'A link https://aaa?param=abc&amp;param2=def', it return true but must be false
10161 return true; // Html entities names (http://www.w3schools.com/tags/ref_entities.asp)
10162 } elseif (preg_match('/&#[0-9]{2,3};/i', $msg)) {
10163 return true; // Html entities numbers (http://www.w3schools.com/tags/ref_entities.asp)
10164 } elseif (preg_match('/&#x[a-f0-9][a-f0-9];/i', $msg)) {
10165 return true; // Html entities numbers in hexa
10166 }
10167
10168 return false;
10169 }
10170}
10171
10186function dol_concatdesc($text1, $text2, $forxml = false, $invert = false)
10187{
10188 if (!empty($invert)) {
10189 $tmp = $text1;
10190 $text1 = $text2;
10191 $text2 = $tmp;
10192 }
10193
10194 $ret = '';
10195 $ret .= (!dol_textishtml($text1) && dol_textishtml($text2)) ? dol_nl2br(dol_escape_htmltag($text1, 0, 1, '', 1), 0, $forxml) : $text1;
10196 $ret .= (!empty($text1) && !empty($text2)) ? ((dol_textishtml($text1) || dol_textishtml($text2)) ? ($forxml ? "<br >\n" : "<br>\n") : "\n") : "";
10197 $ret .= (dol_textishtml($text1) && !dol_textishtml($text2)) ? dol_nl2br(dol_escape_htmltag($text2, 0, 1, '', 1), 0, $forxml) : $text2;
10198 return $ret;
10199}
10200
10209function dol_concat($text1, $text2)
10210{
10211 return $text1.$text2;
10212}
10213
10221function safeArrayMap($callback, array $array)
10222{
10223 if (!is_string($callback)) {
10224 throw new InvalidArgumentException("Les callbacks sont désactivés.");
10225 }
10226 // Check that $callback is a sure function
10227 $allowed_callbacks = ['strtolower', 'strtoupper', 'intval'];
10228 if (!in_array($callback, $allowed_callbacks, true)) {
10229 throw new InvalidArgumentException("Callback function not allowed.");
10230 }
10231 return array_map($callback, $array);
10232}
10233
10234
10248function getCommonSubstitutionArray($outputlangs, $onlykey = 0, $exclude = null, $object = null, $include = null)
10249{
10250 global $db, $conf, $mysoc, $user, $extrafields;
10251
10252 $substitutionarray = array();
10253
10254 if ((empty($exclude) || !in_array('user', $exclude)) && (empty($include) || in_array('user', $include)) && $user instanceof User) {
10255 // Add SIGNATURE into substitutionarray first, so, when we will make the substitution,
10256 // this will include signature content first and then replace var found into content of signature
10257 //var_dump($onlykey);
10258 $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()
10259 $usersignature = $user->signature;
10260 $substitutionarray = array_merge($substitutionarray, array(
10261 '__SENDEREMAIL_SIGNATURE__' => (string) ((!getDolGlobalString('MAIN_MAIL_DO_NOT_USE_SIGN')) ? ($onlykey == 2 ? dol_trunc('SignatureFromTheSelectedSenderProfile', 30) : $emailsendersignature) : ''),
10262 '__USER_SIGNATURE__' => (string) (($usersignature && !getDolGlobalString('MAIN_MAIL_DO_NOT_USE_SIGN')) ? ($onlykey == 2 ? dol_trunc(dol_string_nohtmltag($usersignature), 30) : $usersignature) : '')
10263 ));
10264
10265 if (is_object($user) && ($user instanceof User)) {
10266 $substitutionarray = array_merge($substitutionarray, array(
10267 '__USER_ID__' => (string) $user->id,
10268 '__USER_LOGIN__' => (string) $user->login,
10269 '__USER_EMAIL__' => (string) $user->email,
10270 '__USER_PHONE__' => (string) dol_print_phone($user->office_phone, '', 0, 0, '', " ", '', '', -1),
10271 '__USER_PHONEPRO__' => (string) dol_print_phone($user->user_mobile, '', 0, 0, '', " ", '', '', -1),
10272 '__USER_PHONEMOBILE__' => (string) dol_print_phone($user->personal_mobile, '', 0, 0, '', " ", '', '', -1),
10273 '__USER_FAX__' => (string) $user->office_fax,
10274 '__USER_LASTNAME__' => (string) $user->lastname,
10275 '__USER_FIRSTNAME__' => (string) $user->firstname,
10276 '__USER_FULLNAME__' => (string) $user->getFullName($outputlangs),
10277 '__USER_SUPERVISOR_ID__' => (string) ($user->fk_user ? $user->fk_user : '0'),
10278 '__USER_JOB__' => (string) $user->job,
10279 '__USER_REMOTE_IP__' => (string) getUserRemoteIP(),
10280 '__USER_VCARD_URL__' => (string) $user->getOnlineVirtualCardUrl('', 'external')
10281 ));
10282 }
10283 }
10284 if ((empty($exclude) || !in_array('mycompany', $exclude)) && is_object($mysoc) && (empty($include) || in_array('mycompany', $include))) {
10285 $substitutionarray = array_merge($substitutionarray, array(
10286 '__MYCOMPANY_NAME__' => $mysoc->name,
10287 '__MYCOMPANY_EMAIL__' => $mysoc->email,
10288 '__MYCOMPANY_URL__' => $mysoc->url,
10289 '__MYCOMPANY_PHONE__' => dol_print_phone((string) $mysoc->phone, '', 0, 0, '', " ", '', '', -1),
10290 '__MYCOMPANY_PHONEMOBILE__' => dol_print_phone((string) $mysoc->phone_mobile, '', 0, 0, '', " ", '', '', -1),
10291 '__MYCOMPANY_FAX__' => dol_print_phone((string) $mysoc->fax, '', 0, 0, '', " ", '', '', -1),
10292 '__MYCOMPANY_PROFID1__' => $mysoc->idprof1,
10293 '__MYCOMPANY_PROFID2__' => $mysoc->idprof2,
10294 '__MYCOMPANY_PROFID3__' => $mysoc->idprof3,
10295 '__MYCOMPANY_PROFID4__' => $mysoc->idprof4,
10296 '__MYCOMPANY_PROFID5__' => $mysoc->idprof5,
10297 '__MYCOMPANY_PROFID6__' => $mysoc->idprof6,
10298 '__MYCOMPANY_PROFID7__' => $mysoc->idprof7,
10299 '__MYCOMPANY_PROFID8__' => $mysoc->idprof8,
10300 '__MYCOMPANY_PROFID9__' => $mysoc->idprof9,
10301 '__MYCOMPANY_PROFID10__' => $mysoc->idprof10,
10302 '__MYCOMPANY_CAPITAL__' => $mysoc->capital,
10303 '__MYCOMPANY_FULLADDRESS__' => (method_exists($mysoc, 'getFullAddress') ? $mysoc->getFullAddress(1, ', ') : ''), // $mysoc may be stdClass
10304 '__MYCOMPANY_ADDRESS__' => $mysoc->address,
10305 '__MYCOMPANY_VATNUMBER__' => $mysoc->tva_intra,
10306 '__MYCOMPANY_ZIP__' => $mysoc->zip,
10307 '__MYCOMPANY_TOWN__' => $mysoc->town,
10308 '__MYCOMPANY_STATE__' => $mysoc->state,
10309 '__MYCOMPANY_COUNTRY__' => $mysoc->country,
10310 '__MYCOMPANY_COUNTRY_ID__' => $mysoc->country_id,
10311 '__MYCOMPANY_COUNTRY_CODE__' => $mysoc->country_code,
10312 '__MYCOMPANY_CURRENCY_CODE__' => $conf->currency
10313 ));
10314 }
10315
10316 if (($onlykey || is_object($object)) && (empty($exclude) || !in_array('object', $exclude)) && (empty($include) || in_array('object', $include))) {
10317 if ($onlykey) {
10318 $substitutionarray['__ID__'] = '__ID__';
10319 $substitutionarray['__REF__'] = '__REF__';
10320 $substitutionarray['__NEWREF__'] = '__NEWREF__';
10321 $substitutionarray['__LABEL__'] = '__LABEL__';
10322 $substitutionarray['__REF_CLIENT__'] = '__REF_CLIENT__';
10323 $substitutionarray['__REF_SUPPLIER__'] = '__REF_SUPPLIER__';
10324 $substitutionarray['__NOTE_PUBLIC__'] = '__NOTE_PUBLIC__';
10325 $substitutionarray['__NOTE_PRIVATE__'] = '__NOTE_PRIVATE__';
10326 $substitutionarray['__EXTRAFIELD_XXX__'] = '__EXTRAFIELD_XXX__';
10327
10328 if (isModEnabled("societe")) { // Most objects are concerned
10329 $substitutionarray['__THIRDPARTY_ID__'] = '__THIRDPARTY_ID__';
10330 $substitutionarray['__THIRDPARTY_NAME__'] = '__THIRDPARTY_NAME__';
10331 $substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = '__THIRDPARTY_NAME_ALIAS__';
10332 $substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = '__THIRDPARTY_CODE_CLIENT__';
10333 $substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = '__THIRDPARTY_CODE_FOURNISSEUR__';
10334 $substitutionarray['__THIRDPARTY_EMAIL__'] = '__THIRDPARTY_EMAIL__';
10335 //$substitutionarray['__THIRDPARTY_EMAIL_URLENCODED__'] = '__THIRDPARTY_EMAIL_URLENCODED__'; // We hide this one
10336 $substitutionarray['__THIRDPARTY_URL__'] = '__THIRDPARTY_URL__';
10337 //$substitutionarray['__THIRDPARTY_URL_URLENCODED__'] = '__THIRDPARTY_URL_URLENCODED__'; // We hide this one
10338 $substitutionarray['__THIRDPARTY_PHONE__'] = '__THIRDPARTY_PHONE__';
10339 $substitutionarray['__THIRDPARTY_FAX__'] = '__THIRDPARTY_FAX__';
10340 $substitutionarray['__THIRDPARTY_ADDRESS__'] = '__THIRDPARTY_ADDRESS__';
10341 $substitutionarray['__THIRDPARTY_ZIP__'] = '__THIRDPARTY_ZIP__';
10342 $substitutionarray['__THIRDPARTY_TOWN__'] = '__THIRDPARTY_TOWN__';
10343 $substitutionarray['__THIRDPARTY_STATE__'] = '__THIRDPARTY_STATE__';
10344 $substitutionarray['__THIRDPARTY_IDPROF1__'] = '__THIRDPARTY_IDPROF1__';
10345 $substitutionarray['__THIRDPARTY_IDPROF2__'] = '__THIRDPARTY_IDPROF2__';
10346 $substitutionarray['__THIRDPARTY_IDPROF3__'] = '__THIRDPARTY_IDPROF3__';
10347 $substitutionarray['__THIRDPARTY_IDPROF4__'] = '__THIRDPARTY_IDPROF4__';
10348 $substitutionarray['__THIRDPARTY_IDPROF5__'] = '__THIRDPARTY_IDPROF5__';
10349 $substitutionarray['__THIRDPARTY_IDPROF6__'] = '__THIRDPARTY_IDPROF6__';
10350 $substitutionarray['__THIRDPARTY_IDPROF7__'] = '__THIRDPARTY_IDPROF7__';
10351 $substitutionarray['__THIRDPARTY_IDPROF8__'] = '__THIRDPARTY_IDPROF8__';
10352 $substitutionarray['__THIRDPARTY_IDPROF9__'] = '__THIRDPARTY_IDPROF9__';
10353 $substitutionarray['__THIRDPARTY_IDPROF10__'] = '__THIRDPARTY_IDPROF10__';
10354 $substitutionarray['__THIRDPARTY_TVAINTRA__'] = '__THIRDPARTY_TVAINTRA__';
10355 $substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = '__THIRDPARTY_NOTE_PUBLIC__';
10356 $substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = '__THIRDPARTY_NOTE_PRIVATE__';
10357 }
10358 if (isModEnabled('member') && (!is_object($object) || $object->element == 'adherent') && (empty($exclude) || !in_array('member', $exclude)) && (empty($include) || in_array('member', $include))) {
10359 $substitutionarray['__MEMBER_ID__'] = '__MEMBER_ID__';
10360 $substitutionarray['__MEMBER_TITLE__'] = '__MEMBER_TITLE__';
10361 $substitutionarray['__MEMBER_FIRSTNAME__'] = '__MEMBER_FIRSTNAME__';
10362 $substitutionarray['__MEMBER_LASTNAME__'] = '__MEMBER_LASTNAME__';
10363 $substitutionarray['__MEMBER_USER_LOGIN_INFORMATION__'] = 'Login and pass of the external user account';
10364 /*$substitutionarray['__MEMBER_NOTE_PUBLIC__'] = '__MEMBER_NOTE_PUBLIC__';
10365 $substitutionarray['__MEMBER_NOTE_PRIVATE__'] = '__MEMBER_NOTE_PRIVATE__';*/
10366 }
10367 // add substitution variables for ticket
10368 if (isModEnabled('ticket') && (!is_object($object) || $object->element == 'ticket') && (empty($exclude) || !in_array('ticket', $exclude)) && (empty($include) || in_array('ticket', $include))) {
10369 $substitutionarray['__TICKET_TRACKID__'] = '__TICKET_TRACKID__';
10370 $substitutionarray['__TICKET_SUBJECT__'] = '__TICKET_SUBJECT__';
10371 $substitutionarray['__TICKET_TYPE__'] = '__TICKET_TYPE__';
10372 $substitutionarray['__TICKET_SEVERITY__'] = '__TICKET_SEVERITY__';
10373 $substitutionarray['__TICKET_CATEGORY__'] = '__TICKET_CATEGORY__';
10374 $substitutionarray['__TICKET_ANALYTIC_CODE__'] = '__TICKET_ANALYTIC_CODE__';
10375 $substitutionarray['__TICKET_MESSAGE__'] = '__TICKET_MESSAGE__';
10376 $substitutionarray['__TICKET_PROGRESSION__'] = '__TICKET_PROGRESSION__';
10377 $substitutionarray['__TICKET_USER_ASSIGN__'] = '__TICKET_USER_ASSIGN__';
10378 }
10379 if (isModEnabled('recruitment') && (!is_object($object) || $object->element == 'recruitmentcandidature') && (empty($exclude) || !in_array('recruitment', $exclude)) && (empty($include) || in_array('recruitment', $include))) {
10380 $substitutionarray['__CANDIDATE_FULLNAME__'] = '__CANDIDATE_FULLNAME__';
10381 $substitutionarray['__CANDIDATE_FIRSTNAME__'] = '__CANDIDATE_FIRSTNAME__';
10382 $substitutionarray['__CANDIDATE_LASTNAME__'] = '__CANDIDATE_LASTNAME__';
10383 }
10384 if (isModEnabled('holiday') && (!is_object($object) || $object->element == 'holiday') && (empty($exclude) || !in_array('holiday', $exclude)) && (empty($include) || in_array('holiday', $include))) {
10385 $substitutionarray['__HOLIDAY_ARRAY_PER_EMPLOYEE_FOR_PERIOD__'] = '__HOLIDAY_ARRAY_PER_EMPLOYEE_FOR_PERIOD__';
10386 }
10387 if (isModEnabled('project') && (empty($exclude) || !in_array('project', $exclude)) && (empty($include) || in_array('project', $include))) { // Most objects
10388 $substitutionarray['__PROJECT_ID__'] = '__PROJECT_ID__';
10389 $substitutionarray['__PROJECT_REF__'] = '__PROJECT_REF__';
10390 $substitutionarray['__PROJECT_NAME__'] = '__PROJECT_NAME__';
10391 /*$substitutionarray['__PROJECT_NOTE_PUBLIC__'] = '__PROJECT_NOTE_PUBLIC__';
10392 $substitutionarray['__PROJECT_NOTE_PRIVATE__'] = '__PROJECT_NOTE_PRIVATE__';*/
10393 }
10394 if (isModEnabled('contract') && (!is_object($object) || $object->element == 'contract') && (empty($exclude) || !in_array('contract', $exclude)) && (empty($include) || in_array('contract', $include))) {
10395 $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = 'Highest date planned for a service start';
10396 $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = 'Highest date and hour planned for service start';
10397 $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = 'Lowest data for planned expiration of service';
10398 $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = 'Lowest date and hour for planned expiration of service';
10399 }
10400 if (isModEnabled("propal") && (!is_object($object) || $object->element == 'propal') && (empty($exclude) || !in_array('propal', $exclude)) && (empty($include) || in_array('propal', $include))) {
10401 $substitutionarray['__ONLINE_SIGN_URL__'] = 'ToOfferALinkForOnlineSignature';
10402 }
10403 if (isModEnabled("intervention") && (!is_object($object) || $object->element == 'fichinter') && (empty($exclude) || !in_array('intervention', $exclude)) && (empty($include) || in_array('intervention', $include))) {
10404 $substitutionarray['__ONLINE_SIGN_FICHINTER_URL__'] = 'ToOfferALinkForOnlineSignature';
10405 }
10406 $substitutionarray['__ONLINE_PAYMENT_URL__'] = 'UrlToPayOnlineIfApplicable';
10407 $substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__'] = 'TextAndUrlToPayOnlineIfApplicable';
10408 $substitutionarray['__SECUREKEYPAYMENT__'] = 'Security key (if key is not unique per record)';
10409 $substitutionarray['__SECUREKEYPAYMENT_MEMBER__'] = 'Security key for payment on a member subscription (one key per member)';
10410 $substitutionarray['__SECUREKEYPAYMENT_ORDER__'] = 'Security key for payment on an order';
10411 $substitutionarray['__SECUREKEYPAYMENT_INVOICE__'] = 'Security key for payment on an invoice';
10412 $substitutionarray['__SECUREKEYPAYMENT_CONTRACTLINE__'] = 'Security key for payment on a service of a contract';
10413
10414 $substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = 'Direct download url of a proposal';
10415 $substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = 'Direct download url of an order';
10416 $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = 'Direct download url of an invoice';
10417 $substitutionarray['__DIRECTDOWNLOAD_URL_CONTRACT__'] = 'Direct download url of a contract';
10418 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_PROPOSAL__'] = 'Direct download url of a supplier proposal';
10419 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_ORDER__'] = 'Direct download url of a supplier order';
10420 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_INVOICE__'] = 'Direct download url of a supplier invoice';
10421
10422 if (isModEnabled("shipping") && (!is_object($object) || $object->element == 'shipping')) {
10423 $substitutionarray['__SHIPPINGTRACKNUM__'] = 'Shipping tracking number';
10424 $substitutionarray['__SHIPPINGTRACKNUMURL__'] = 'Shipping tracking url';
10425 $substitutionarray['__SHIPPINGMETHOD__'] = 'Shipping method';
10426 }
10427 if (isModEnabled("reception") && (!is_object($object) || $object->element == 'reception')) {
10428 $substitutionarray['__RECEPTIONTRACKNUM__'] = 'Shipping tracking number of shipment';
10429 $substitutionarray['__RECEPTIONTRACKNUMURL__'] = 'Shipping tracking url';
10430 }
10431 } else {
10432 '@phan-var-force Adherent|Delivery $object';
10434 $substitutionarray['__ID__'] = $object->id;
10435 $substitutionarray['__REF__'] = $object->ref;
10436 $substitutionarray['__NEWREF__'] = $object->newref;
10437 $substitutionarray['__LABEL__'] = (isset($object->label) ? $object->label : (isset($object->title) ? $object->title : null));
10438 $substitutionarray['__REF_CLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : null));
10439 $substitutionarray['__REF_SUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
10440 $substitutionarray['__NOTE_PUBLIC__'] = (isset($object->note_public) ? $object->note_public : null);
10441 $substitutionarray['__NOTE_PRIVATE__'] = (isset($object->note_private) ? $object->note_private : null);
10442
10443 $substitutionarray['__DATE_CREATION__'] = (isset($object->date_creation) ? dol_print_date($object->date_creation, 'day', false, $outputlangs) : '');
10444 $substitutionarray['__DATE_MODIFICATION__'] = (isset($object->date_modification) ? dol_print_date($object->date_modification, 'day', false, $outputlangs) : '');
10445 $substitutionarray['__DATE_VALIDATION__'] = (isset($object->date_validation) ? dol_print_date($object->date_validation, 'day', false, $outputlangs) : '');
10446
10447 // handle date_delivery: in customer order/supplier order, the property name is delivery_date, in shipment/reception it is date_delivery
10448 $date_delivery = null;
10449 if (property_exists($object, 'date_delivery')) {
10450 $date_delivery = $object->date_delivery;
10451 } elseif (property_exists($object, 'delivery_date')) {
10452 $date_delivery = $object->delivery_date;
10453 }
10454 $substitutionarray['__DATE_DELIVERY__'] = (isset($date_delivery) ? dol_print_date($date_delivery, 'day', false, $outputlangs) : '');
10455 $substitutionarray['__DATE_DELIVERY_DAY__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%d") : '');
10456 $substitutionarray['__DATE_DELIVERY_DAY_TEXT__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%A") : '');
10457 $substitutionarray['__DATE_DELIVERY_MON__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%m") : '');
10458 $substitutionarray['__DATE_DELIVERY_MON_TEXT__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%b") : '');
10459 $substitutionarray['__DATE_DELIVERY_YEAR__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%Y") : '');
10460 $substitutionarray['__DATE_DELIVERY_HH__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%H") : '');
10461 $substitutionarray['__DATE_DELIVERY_MM__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%M") : '');
10462 $substitutionarray['__DATE_DELIVERY_SS__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%S") : '');
10463
10464 // For backward compatibility (deprecated)
10465 $substitutionarray['__REFCLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : null));
10466 $substitutionarray['__REFSUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
10467
10468 $substitutionarray['__SUPPLIER_ORDER_DATE_DELIVERY__'] = (isset($date_delivery) ? dol_print_date($date_delivery, 'day', false, $outputlangs) : '');
10469 $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 : '')) : '');
10470 $substitutionarray['__EXPIRATION_DATE__'] = (isset($object->fin_validite) ? dol_print_date($object->fin_validite, 'daytext') : '');
10471
10472 if (is_object($object) && ($object->element == 'adherent' || $object->element == 'member') && $object->id > 0) {
10473 '@phan-var-force Adherent $object';
10475 $birthday = (empty($object->birth) ? '' : dol_print_date($object->birth, 'day'));
10476
10477 $substitutionarray['__MEMBER_ID__'] = (isset($object->id) ? $object->id : '');
10478 if (method_exists($object, 'getCivilityLabel')) {
10479 $substitutionarray['__MEMBER_TITLE__'] = $object->getCivilityLabel();
10480 }
10481 $substitutionarray['__MEMBER_FIRSTNAME__'] = (isset($object->firstname) ? $object->firstname : '');
10482 $substitutionarray['__MEMBER_LASTNAME__'] = (isset($object->lastname) ? $object->lastname : '');
10483 $substitutionarray['__MEMBER_USER_LOGIN_INFORMATION__'] = '';
10484 if (method_exists($object, 'getFullName')) {
10485 $substitutionarray['__MEMBER_FULLNAME__'] = $object->getFullName($outputlangs);
10486 }
10487 $substitutionarray['__MEMBER_COMPANY__'] = (isset($object->societe) ? $object->societe : '');
10488 $substitutionarray['__MEMBER_ADDRESS__'] = (isset($object->address) ? $object->address : '');
10489 $substitutionarray['__MEMBER_ZIP__'] = (isset($object->zip) ? $object->zip : '');
10490 $substitutionarray['__MEMBER_TOWN__'] = (isset($object->town) ? $object->town : '');
10491 $substitutionarray['__MEMBER_STATE__'] = (isset($object->state) ? $object->state : '');
10492 $substitutionarray['__MEMBER_COUNTRY__'] = (isset($object->country) ? $object->country : '');
10493 $substitutionarray['__MEMBER_EMAIL__'] = (isset($object->email) ? $object->email : '');
10494 $substitutionarray['__MEMBER_BIRTH__'] = (isset($birthday) ? $birthday : '');
10495 $substitutionarray['__MEMBER_PHOTO__'] = (isset($object->photo) ? $object->photo : '');
10496 $substitutionarray['__MEMBER_LOGIN__'] = (isset($object->login) ? $object->login : '');
10497 $substitutionarray['__MEMBER_PASSWORD__'] = (isset($object->pass) ? $object->pass : '');
10498 $substitutionarray['__MEMBER_PHONE__'] = (isset($object->phone) ? dol_print_phone($object->phone) : '');
10499 $substitutionarray['__MEMBER_PHONEPRO__'] = (isset($object->phone_perso) ? dol_print_phone($object->phone_perso) : '');
10500 $substitutionarray['__MEMBER_PHONEMOBILE__'] = (isset($object->phone_mobile) ? dol_print_phone($object->phone_mobile) : '');
10501 $substitutionarray['__MEMBER_TYPE__'] = (isset($object->type) ? $object->type : '');
10502 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE__'] = dol_print_date($object->first_subscription_date, 'day');
10503
10504 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_RFC__'] = dol_print_date($object->first_subscription_date, 'dayrfc');
10505 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_START__'] = (isset($object->first_subscription_date_start) ? dol_print_date($object->first_subscription_date_start, 'day') : '');
10506 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_START_RFC__'] = (isset($object->first_subscription_date_start) ? dol_print_date($object->first_subscription_date_start, 'dayrfc') : '');
10507 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_END__'] = (isset($object->first_subscription_date_end) ? dol_print_date($object->first_subscription_date_end, 'day') : '');
10508 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_END_RFC__'] = (isset($object->first_subscription_date_end) ? dol_print_date($object->first_subscription_date_end, 'dayrfc') : '');
10509 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE__'] = dol_print_date($object->last_subscription_date, 'day');
10510 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_RFC__'] = dol_print_date($object->last_subscription_date, 'dayrfc');
10511 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_START__'] = dol_print_date($object->last_subscription_date_start, 'day');
10512 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_START_RFC__'] = dol_print_date($object->last_subscription_date_start, 'dayrfc');
10513 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_END__'] = dol_print_date($object->last_subscription_date_end, 'day');
10514 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_END_RFC__'] = dol_print_date($object->last_subscription_date_end, 'dayrfc');
10515 }
10516
10517 if (is_object($object) && $object->element == 'societe') {
10519 '@phan-var-force Societe $object';
10520 $substitutionarray['__THIRDPARTY_ID__'] = $object->id ?? '';
10521 $substitutionarray['__THIRDPARTY_NAME__'] = $object->name ?? '';
10522 $substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = $object->name_alias ?? '';
10523 $substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = $object->code_client ?? '';
10524 $substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = $object->code_fournisseur ?? '';
10525 $substitutionarray['__THIRDPARTY_EMAIL__'] = $object->email ?? '';
10526 $substitutionarray['__THIRDPARTY_EMAIL_URLENCODED__'] = urlencode($object->email ?? '');
10527 $substitutionarray['__THIRDPARTY_URL__'] = $object->url ?? '';
10528 $substitutionarray['__THIRDPARTY_URL_URLENCODED__'] = urlencode($object->url ?? '');
10529 $substitutionarray['__THIRDPARTY_PHONE__'] = dol_print_phone($object->phone ?? '');
10530 $substitutionarray['__THIRDPARTY_FAX__'] = dol_print_phone($object->fax ?? '');
10531 $substitutionarray['__THIRDPARTY_ADDRESS__'] = $object->address ?? '';
10532 $substitutionarray['__THIRDPARTY_ZIP__'] = $object->zip ?? '';
10533 $substitutionarray['__THIRDPARTY_TOWN__'] = $object->town ?? '';
10534 $substitutionarray['__THIRDPARTY_STATE__'] = $object->state ?? '';
10535 $substitutionarray['__THIRDPARTY_COUNTRY_ID__'] = ($object->country_id > 0 ?: '');
10536 $substitutionarray['__THIRDPARTY_COUNTRY_CODE__'] = $object->country_code ?? '';
10537 $substitutionarray['__THIRDPARTY_IDPROF1__'] = $object->idprof1 ?? '';
10538 $substitutionarray['__THIRDPARTY_IDPROF2__'] = $object->idprof2 ?? '';
10539 $substitutionarray['__THIRDPARTY_IDPROF3__'] = $object->idprof3 ?? '';
10540 $substitutionarray['__THIRDPARTY_IDPROF4__'] = $object->idprof4 ?? '';
10541 $substitutionarray['__THIRDPARTY_IDPROF5__'] = $object->idprof5 ?? '';
10542 $substitutionarray['__THIRDPARTY_IDPROF6__'] = $object->idprof6 ?? '';
10543 $substitutionarray['__THIRDPARTY_TVAINTRA__'] = $object->tva_intra ?? '';
10544 $substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = dol_htmlentitiesbr($object->note_public ?? '');
10545 $substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = dol_htmlentitiesbr($object->note_private ?? '');
10546 } elseif (is_object($object) && is_object($object->thirdparty)) {
10547 $substitutionarray['__THIRDPARTY_ID__'] = $object->thirdparty->id ?? '';
10548 $substitutionarray['__THIRDPARTY_NAME__'] = $object->thirdparty->name ?? '';
10549 $substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = $object->thirdparty->name_alias ?? '';
10550 $substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = $object->thirdparty->code_client ?? '';
10551 $substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = $object->thirdparty->code_fournisseur ?? '';
10552 $substitutionarray['__THIRDPARTY_EMAIL__'] = $object->thirdparty->email ?? '';
10553 $substitutionarray['__THIRDPARTY_EMAIL_URLENCODED__'] = urlencode($object->thirdparty->email ?? '');
10554 $substitutionarray['__THIRDPARTY_PHONE__'] = dol_print_phone($object->thirdparty->phone ?? '');
10555 $substitutionarray['__THIRDPARTY_FAX__'] = dol_print_phone($object->thirdparty->fax ?? '');
10556 $substitutionarray['__THIRDPARTY_ADDRESS__'] = $object->thirdparty->address ?? '';
10557 $substitutionarray['__THIRDPARTY_ZIP__'] = $object->thirdparty->zip ?? '';
10558 $substitutionarray['__THIRDPARTY_TOWN__'] = $object->thirdparty->town ?? '';
10559 $substitutionarray['__THIRDPARTY_STATE__'] = $object->thirdparty->state ?? '';
10560 $substitutionarray['__THIRDPARTY_COUNTRY_ID__'] = ($object->thirdparty->country_id > 0 ?: '');
10561 $substitutionarray['__THIRDPARTY_COUNTRY_CODE__'] = $object->thirdparty->country_code ?? '';
10562 $substitutionarray['__THIRDPARTY_IDPROF1__'] = $object->thirdparty->idprof1 ?? '';
10563 $substitutionarray['__THIRDPARTY_IDPROF2__'] = $object->thirdparty->idprof2 ?? '';
10564 $substitutionarray['__THIRDPARTY_IDPROF3__'] = $object->thirdparty->idprof3 ?? '';
10565 $substitutionarray['__THIRDPARTY_IDPROF4__'] = $object->thirdparty->idprof4 ?? '';
10566 $substitutionarray['__THIRDPARTY_IDPROF5__'] = $object->thirdparty->idprof5 ?? '';
10567 $substitutionarray['__THIRDPARTY_IDPROF6__'] = $object->thirdparty->idprof6 ?? '';
10568 $substitutionarray['__THIRDPARTY_TVAINTRA__'] = $object->thirdparty->tva_intra ?? '';
10569 $substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = dol_htmlentitiesbr($object->thirdparty->note_public ?? '');
10570 $substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = dol_htmlentitiesbr($object->thirdparty->note_private ?? '');
10571 }
10572
10573 if (is_object($object) && $object->element == 'recruitmentcandidature') {
10574 '@phan-var-force RecruitmentCandidature $object';
10576 $substitutionarray['__CANDIDATE_FULLNAME__'] = $object->getFullName($outputlangs);
10577 $substitutionarray['__CANDIDATE_FIRSTNAME__'] = isset($object->firstname) ? $object->firstname : '';
10578 $substitutionarray['__CANDIDATE_LASTNAME__'] = isset($object->lastname) ? $object->lastname : '';
10579 }
10580 if (is_object($object) && $object->element == 'conferenceorboothattendee') {
10581 '@phan-var-force ConferenceOrBoothAttendee $object';
10583 $substitutionarray['__ATTENDEE_FULLNAME__'] = $object->getFullName($outputlangs);
10584 $substitutionarray['__ATTENDEE_FIRSTNAME__'] = isset($object->firstname) ? $object->firstname : '';
10585 $substitutionarray['__ATTENDEE_LASTNAME__'] = isset($object->lastname) ? $object->lastname : '';
10586 }
10587
10588 if (is_object($object) && $object->element == 'project') {
10589 '@phan-var-force Project $object';
10591 $substitutionarray['__PROJECT_ID__'] = $object->id;
10592 $substitutionarray['__PROJECT_REF__'] = $object->ref;
10593 $substitutionarray['__PROJECT_NAME__'] = $object->title;
10594 } elseif (is_object($object)) {
10595 $project = null;
10596 if (!empty($object->project)) {
10597 $project = $object->project;
10598 }
10599 if (!is_null($project) && is_object($project)) {
10600 $substitutionarray['__PROJECT_ID__'] = $project->id;
10601 $substitutionarray['__PROJECT_REF__'] = $project->ref;
10602 $substitutionarray['__PROJECT_NAME__'] = $project->title;
10603 } else {
10604 // can substitute variables for project : uses lazy load in "make_substitutions" method
10605 $project_id = 0;
10606 if (!empty($object->fk_project) && $object->fk_project > 0) {
10607 $project_id = $object->fk_project;
10608 } elseif (!empty($object->fk_projet) && $object->fk_projet > 0) {
10609 $project_id = $object->fk_project;
10610 }
10611 if ($project_id > 0) {
10612 // path:class:method:id
10613 $substitutionarray['__PROJECT_ID__@lazyload'] = '/projet/class/project.class.php:Project:fetchAndSetSubstitution:' . $project_id;
10614 $substitutionarray['__PROJECT_REF__@lazyload'] = '/projet/class/project.class.php:Project:fetchAndSetSubstitution:' . $project_id;
10615 $substitutionarray['__PROJECT_NAME__@lazyload'] = '/projet/class/project.class.php:Project:fetchAndSetSubstitution:' . $project_id;
10616 }
10617 }
10618 }
10619
10620 if (is_object($object) && $object->element == 'facture') {
10621 '@phan-var-force Facture $object';
10623 $substitutionarray['__INVOICE_SITUATION_NUMBER__'] = isset($object->situation_counter) ? $object->situation_counter : '';
10624 }
10625 if (is_object($object) && $object->element == 'shipping') {
10626 '@phan-var-force Expedition $object';
10628 $substitutionarray['__SHIPPINGTRACKNUM__'] = $object->tracking_number;
10629 $substitutionarray['__SHIPPINGTRACKNUMURL__'] = $object->tracking_url;
10630 $substitutionarray['__SHIPPINGMETHOD__'] = $object->shipping_method;
10631 }
10632 if (is_object($object) && $object->element == 'reception') {
10633 '@phan-var-force Reception $object';
10635 $substitutionarray['__RECEPTIONTRACKNUM__'] = $object->tracking_number;
10636 $substitutionarray['__RECEPTIONTRACKNUMURL__'] = $object->tracking_url;
10637 }
10638
10639 if (is_object($object) && $object->element == 'contrat' && $object->id > 0 && is_array($object->lines)) {
10640 '@phan-var-force Contrat $object';
10642 $dateplannedstart = '';
10643 $datenextexpiration = '';
10644 foreach ($object->lines as $line) {
10645 if ($line->date_start > $dateplannedstart) {
10646 $dateplannedstart = $line->date_start;
10647 }
10648 if ($line->statut == 4 && $line->date_end && (!$datenextexpiration || $line->date_end < $datenextexpiration)) {
10649 $datenextexpiration = $line->date_end;
10650 }
10651 }
10652 $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = dol_print_date($dateplannedstart, 'day');
10653 $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE_RFC__'] = dol_print_date($dateplannedstart, 'dayrfc');
10654 $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = dol_print_date($dateplannedstart, 'standard');
10655
10656 $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = dol_print_date($datenextexpiration, 'day');
10657 $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE_RFC__'] = dol_print_date($datenextexpiration, 'dayrfc');
10658 $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = dol_print_date($datenextexpiration, 'standard');
10659 }
10660 // add substitution variables for ticket
10661 if (is_object($object) && $object->element == 'ticket') {
10662 '@phan-var-force Ticket $object';
10664 $substitutionarray['__TICKET_TRACKID__'] = $object->track_id;
10665 $substitutionarray['__TICKET_SUBJECT__'] = $object->subject;
10666 $substitutionarray['__TICKET_TYPE__'] = $object->type_code;
10667 $substitutionarray['__TICKET_SEVERITY__'] = $object->severity_code;
10668 $substitutionarray['__TICKET_CATEGORY__'] = $object->category_code; // For backward compatibility
10669 $substitutionarray['__TICKET_ANALYTIC_CODE__'] = $object->category_code;
10670 $substitutionarray['__TICKET_MESSAGE__'] = $object->message;
10671 $substitutionarray['__TICKET_PROGRESSION__'] = $object->progress;
10672 $userstat = new User($db);
10673 if ($object->fk_user_assign > 0) {
10674 $userstat->fetch($object->fk_user_assign);
10675 $substitutionarray['__TICKET_USER_ASSIGN__'] = dolGetFirstLastname($userstat->firstname, $userstat->lastname);
10676 }
10677
10678 if ($object->fk_user_create > 0) {
10679 $userstat->fetch($object->fk_user_create);
10680 $substitutionarray['__USER_CREATE__'] = dolGetFirstLastname($userstat->firstname, $userstat->lastname);
10681 }
10682 }
10683
10684 // Create dynamic tags for __EXTRAFIELD_FIELD__
10685 if ($object->table_element && $object->id > 0) {
10686 if (!is_object($extrafields)) {
10687 $extrafields = new ExtraFields($db);
10688 }
10689 $extrafields->fetch_name_optionals_label($object->table_element, true);
10690
10691 if ($object->fetch_optionals() > 0) { // @FIXME: Remove this, the fetch should have been done already, by the caller of getCommonSubstitutionArray()
10692 if (is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label']) > 0) {
10693 foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $label) {
10694 if ($extrafields->attributes[$object->table_element]['type'][$key] == 'date') {
10695 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = dol_print_date($object->array_options['options_' . $key], 'day');
10696 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_LOCALE__'] = dol_print_date($object->array_options['options_' . $key], 'day', 'tzserver', $outputlangs);
10697 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_RFC__'] = dol_print_date($object->array_options['options_' . $key], 'dayrfc');
10698 } elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'datetime') {
10699 $datetime = $object->array_options['options_' . $key];
10700 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhour') : '');
10701 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_LOCALE__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhour', 'tzserver', $outputlangs) : '');
10702 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_DAY_LOCALE__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'day', 'tzserver', $outputlangs) : '');
10703 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_RFC__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhourrfc') : '');
10704 } elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'phone') {
10705 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = dol_print_phone($object->array_options['options_' . $key]);
10706 } elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'price') {
10707 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = $object->array_options['options_' . $key];
10708 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_FORMATED__'] = price($object->array_options['options_' . $key]); // For compatibility
10709 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_FORMATTED__'] = price($object->array_options['options_' . $key]);
10710 } elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'select') {
10711 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = !empty($object->array_options['options_' . $key]) ? $object->array_options['options_' . $key] : '';
10712 $val = $extrafields->attributes[$object->table_element]['param'][$key]['options'][$object->array_options['options_'.$key]] ?? $object->array_options['options_'.$key];
10713 $substitutionarray['__EXTRAFIELD_'.strtoupper($key).'_LABEL__'] = $val;
10714 } elseif ($extrafields->attributes[$object->table_element]['type'][$key] != 'separator') {
10715 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = !empty($object->array_options['options_' . $key]) ? $object->array_options['options_' . $key] : '';
10716 }
10717 }
10718 }
10719 }
10720 }
10721
10722 // Complete substitution array with the url to make online payment
10723 if (empty($substitutionarray['__REF__'])) {
10724 $paymenturl = '';
10725 } else {
10726 // Set the online payment url link into __ONLINE_PAYMENT_URL__ key
10727 require_once DOL_DOCUMENT_ROOT . '/core/lib/payments.lib.php';
10728 $outputlangs->loadLangs(array('paypal', 'other'));
10729
10730 $amounttouse = 0;
10731 $typeforonlinepayment = 'free';
10732 if (is_object($object) && $object->element == 'commande') {
10733 $typeforonlinepayment = 'order';
10734 }
10735 if (is_object($object) && $object->element == 'facture') {
10736 $typeforonlinepayment = 'invoice';
10737 }
10738 if (is_object($object) && $object->element == 'member') {
10739 $typeforonlinepayment = 'member';
10740 if (!empty($object->last_subscription_amount)) {
10741 $amounttouse = $object->last_subscription_amount;
10742 }
10743 }
10744 if (is_object($object) && $object->element == 'contrat') {
10745 $typeforonlinepayment = 'contract';
10746 }
10747 if (is_object($object) && $object->element == 'fichinter') {
10748 $typeforonlinepayment = 'ficheinter';
10749 }
10750
10751 $url = getOnlinePaymentUrl(0, $typeforonlinepayment, $substitutionarray['__REF__'], (float) $amounttouse);
10752 $paymenturl = $url;
10753 }
10754
10755 if ($object->id > 0) {
10756 $substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__'] = ($paymenturl ? str_replace('\n', "\n", $outputlangs->trans("PredefinedMailContentLink", $paymenturl)) : '');
10757 $substitutionarray['__ONLINE_PAYMENT_URL__'] = $paymenturl;
10758
10759 // Show structured communication
10760 if (getDolGlobalString('INVOICE_PAYMENT_ENABLE_STRUCTURED_COMMUNICATION') && $object->element == 'facture') {
10761 include_once DOL_DOCUMENT_ROOT . '/core/lib/functions_be.lib.php';
10762 $substitutionarray['__PAYMENT_STRUCTURED_COMMUNICATION__'] = dolBECalculateStructuredCommunication((string) $object->ref, $object->type);
10763 }
10764
10765 if (getDolGlobalString('PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'propal') {
10766 $substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = $object->getLastMainDocLink($object->element);
10767 } else {
10768 $substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = '';
10769 }
10770 if (getDolGlobalString('ORDER_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'commande') {
10771 $substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = $object->getLastMainDocLink($object->element);
10772 } else {
10773 $substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = '';
10774 }
10775 if (getDolGlobalString('INVOICE_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'facture') {
10776 $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = $object->getLastMainDocLink($object->element);
10777 } else {
10778 $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = '';
10779 }
10780 if (getDolGlobalString('CONTRACT_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'contrat') {
10781 $substitutionarray['__DIRECTDOWNLOAD_URL_CONTRACT__'] = $object->getLastMainDocLink($object->element);
10782 } else {
10783 $substitutionarray['__DIRECTDOWNLOAD_URL_CONTRACT__'] = '';
10784 }
10785 if (getDolGlobalString('FICHINTER_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'fichinter') {
10786 $substitutionarray['__DIRECTDOWNLOAD_URL_FICHINTER__'] = $object->getLastMainDocLink($object->element);
10787 } else {
10788 $substitutionarray['__DIRECTDOWNLOAD_URL_FICHINTER__'] = '';
10789 }
10790 if (getDolGlobalString('SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'supplier_proposal') {
10791 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_PROPOSAL__'] = $object->getLastMainDocLink($object->element);
10792 } else {
10793 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_PROPOSAL__'] = '';
10794 }
10795 if (getDolGlobalString('SUPPLIER_ORDER_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'order_supplier') {
10796 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_ORDER__'] = $object->getLastMainDocLink($object->element);
10797 } else {
10798 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_ORDER__'] = '';
10799 }
10800 if (getDolGlobalString('SUPPLIER_INVOICE_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'invoice_supplier') {
10801 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_INVOICE__'] = $object->getLastMainDocLink($object->element);
10802 } else {
10803 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_INVOICE__'] = '';
10804 }
10805
10806 if (is_object($object) && $object->element == 'propal') {
10807 '@phan-var-force Propal $object';
10809 $substitutionarray['__URL_PROPOSAL__'] = DOL_MAIN_URL_ROOT . "/comm/propal/card.php?id=" . $object->id;
10810 require_once DOL_DOCUMENT_ROOT . '/core/lib/signature.lib.php';
10811 $substitutionarray['__ONLINE_SIGN_URL__'] = getOnlineSignatureUrl(0, 'proposal', (string) $object->ref, 1, $object);
10812 }
10813 if (is_object($object) && $object->element == 'commande') {
10814 '@phan-var-force Commande $object';
10816 $substitutionarray['__URL_ORDER__'] = DOL_MAIN_URL_ROOT . "/commande/card.php?id=" . $object->id;
10817 }
10818 if (is_object($object) && $object->element == 'facture') {
10819 '@phan-var-force Facture $object';
10821 $substitutionarray['__URL_INVOICE__'] = DOL_MAIN_URL_ROOT . "/compta/facture/card.php?id=" . $object->id;
10822 }
10823 if (is_object($object) && $object->element == 'contrat') {
10824 '@phan-var-force Contrat $object';
10826 $substitutionarray['__URL_CONTRACT__'] = DOL_MAIN_URL_ROOT . "/contrat/card.php?id=" . $object->id;
10827 require_once DOL_DOCUMENT_ROOT . '/core/lib/signature.lib.php';
10828 $substitutionarray['__ONLINE_SIGN_URL__'] = getOnlineSignatureUrl(0, 'contract', (string) $object->ref, 1, $object);
10829 }
10830 if (is_object($object) && $object->element == 'fichinter') {
10831 '@phan-var-force Fichinter $object';
10833 $substitutionarray['__URL_FICHINTER__'] = DOL_MAIN_URL_ROOT . "/fichinter/card.php?id=" . $object->id;
10834 require_once DOL_DOCUMENT_ROOT . '/core/lib/signature.lib.php';
10835 $substitutionarray['__ONLINE_SIGN_FICHINTER_URL__'] = getOnlineSignatureUrl(0, 'fichinter', (string) $object->ref, 1, $object);
10836 }
10837 if (is_object($object) && $object->element == 'supplier_proposal') {
10838 '@phan-var-force SupplierProposal $object';
10840 $substitutionarray['__URL_SUPPLIER_PROPOSAL__'] = DOL_MAIN_URL_ROOT . "/supplier_proposal/card.php?id=" . $object->id;
10841 }
10842 if (is_object($object) && $object->element == 'invoice_supplier') {
10843 '@phan-var-force FactureFournisseur $object';
10845 $substitutionarray['__URL_SUPPLIER_INVOICE__'] = DOL_MAIN_URL_ROOT . "/fourn/facture/card.php?id=" . $object->id;
10846 }
10847 if (is_object($object) && $object->element == 'payment_supplier') {
10848 '@phan-var-force PaiementFourn $object';
10850 //print_r($object);
10851 $liste_factures = [];
10852 $total = 0;
10853
10854 // @FIXME We must not have any repeated SQL access into this function.
10855 $sql = 'SELECT f.ref,f.multicurrency_code as f_mccode, pf.*
10856 FROM '.MAIN_DB_PREFIX.'paiementfourn_facturefourn as pf
10857 JOIN '.MAIN_DB_PREFIX.'facture_fourn as f ON pf.fk_facturefourn = f.rowid
10858 WHERE pf.fk_paiementfourn = '.((int) $object->id);
10859
10860 $resql = $db->query($sql);
10861 if ($resql) {
10862 while ($objp = $db->fetch_object($resql)) {
10863 $liste_factures[] = ' - '.$outputlangs->trans('Invoice').' '. $objp->ref.' '.$outputlangs->trans('AmountPayed').' '.price($objp->multicurrency_amount, 0, $outputlangs, 0, -1, -1, $objp->multicurrency_code);
10864 }
10865 }
10866 $substitutionarray['__SUPPLIER_PAYMENT_INVOICES_LIST__'] = implode("\n", $liste_factures);
10867 ;
10868 $substitutionarray['__SUPPLIER_PAYMENT_INVOICES_TOTAL__'] = price($object->multicurrency_amount, 0, $outputlangs, 0, -1, -1, $object->multicurrency_code ? $object->multicurrency_code : $conf->currency);
10869 }
10870 if (is_object($object) && $object->element == 'shipping') {
10871 '@phan-var-force Expedition $object';
10873 $substitutionarray['__URL_SHIPMENT__'] = DOL_MAIN_URL_ROOT . "/expedition/card.php?id=" . $object->id;
10874 if (getDolGlobalInt('EXPEDITION_ALLOW_ONLINESIGN')) {
10875 require_once DOL_DOCUMENT_ROOT . '/core/lib/signature.lib.php';
10876 $substitutionarray['__ONLINE_SIGN_URL__'] = getOnlineSignatureUrl(0, 'expedition', (string) $object->ref, 1, $object);
10877 }
10878 }
10879 }
10880
10881 if (is_object($object) && $object->element == 'action') {
10882 '@phan-var-force ActionComm $object';
10884 $substitutionarray['__EVENT_LABEL__'] = $object->label;
10885 $substitutionarray['__EVENT_DESCRIPTION__'] = $object->note;
10886 $substitutionarray['__EVENT_TYPE__'] = $outputlangs->trans("Action" . $object->type_code);
10887 $substitutionarray['__EVENT_DATE__'] = dol_print_date($object->datep, 'day', 'auto', $outputlangs);
10888 $substitutionarray['__EVENT_TIME__'] = dol_print_date($object->datep, 'hour', 'auto', $outputlangs);
10889 $substitutionarray['__EVENT_DATE_TZUSER__'] = dol_print_date($object->datep, 'day', 'tzuserrel', $outputlangs);
10890 $substitutionarray['__EVENT_TIME_TZUSER__'] = dol_print_date($object->datep, 'hour', 'tzuserrel', $outputlangs);
10891 }
10892 }
10893 }
10894
10895 if ((empty($exclude) || !in_array('objectamount', $exclude)) && (empty($include) || in_array('objectamount', $include))) {
10896 '@phan-var-force Facture|FactureRec $object';
10898 include_once DOL_DOCUMENT_ROOT . '/core/lib/functionsnumtoword.lib.php';
10899
10900 $substitutionarray['__DATE_YMD__'] = is_object($object) ? (isset($object->date) ? dol_print_date($object->date, 'day', false, $outputlangs) : null) : '';
10901 $substitutionarray['__DATE_DUE_YMD__'] = is_object($object) ? (isset($object->date_lim_reglement) ? dol_print_date($object->date_lim_reglement, 'day', false, $outputlangs) : null) : '';
10902 $substitutionarray['__DATE_YMD_TEXT__'] = is_object($object) ? (isset($object->date) ? dol_print_date($object->date, 'daytext', false, $outputlangs) : null) : '';
10903 $substitutionarray['__DATE_DUE_YMD_TEXT__'] = is_object($object) ? (isset($object->date_lim_reglement) ? dol_print_date($object->date_lim_reglement, 'daytext', false, $outputlangs) : null) : '';
10904
10905 $already_payed_all = 0;
10906 if (is_object($object) && ($object instanceof Facture)) {
10907 $already_payed_all = $object->totalpaid + $object->totaldeposits + $object->totalcreditnotes;
10908 }
10909
10910 $substitutionarray['__SIMPLE_HTML_TABLE__'] = is_object($object) && !empty($object->lines) ? showSimpleHTMLTable($outputlangs, $object) : "";
10911 $substitutionarray['__AMOUNT_EXCL_TAX__'] = is_object($object) ? $object->total_ht : '';
10912 $substitutionarray['__AMOUNT_EXCL_TAX_TEXT__'] = is_object($object) ? dol_convertToWord($object->total_ht, $outputlangs, '', true) : '';
10913 $substitutionarray['__AMOUNT_EXCL_TAX_TEXTCURRENCY__'] = is_object($object) ? dol_convertToWord($object->total_ht, $outputlangs, $conf->currency, true) : '';
10914
10915 $substitutionarray['__AMOUNT__'] = is_object($object) ? $object->total_ttc : '';
10916 $substitutionarray['__AMOUNT_TEXT__'] = is_object($object) ? dol_convertToWord($object->total_ttc, $outputlangs, '', true) : '';
10917 $substitutionarray['__AMOUNT_TEXTCURRENCY__'] = is_object($object) ? dol_convertToWord($object->total_ttc, $outputlangs, $conf->currency, true) : '';
10918
10919 $substitutionarray['__DEPOSIT_PERCENT__'] = is_object($object) ? $object->deposit_percent : '';
10920 $substitutionarray['__DEPOSIT_AMOUNT__'] = is_object($object) ? price2num($object->total_ttc * ($object->deposit_percent / 100), 'MT') : '';
10921
10922 $substitutionarray['__AMOUNT_REMAIN__'] = is_object($object) ? price2num($object->total_ttc - $already_payed_all, 'MT') : '';
10923
10924 $substitutionarray['__AMOUNT_VAT__'] = is_object($object) ? (isset($object->total_vat) ? $object->total_vat : $object->total_tva) : '';
10925 $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)) : '';
10926 $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)) : '';
10927
10928 $mysocuselocaltax1 = false;
10929 $mysocuselocaltax2 = false;
10930 if ($mysoc instanceof Societe && !empty($mysoc->country_code)) {
10931 $tmparray = $mysoc->useLocalTax(-1);
10932 $mysocuselocaltax1 = $tmparray[1];
10933 $mysocuselocaltax2 = $tmparray[2];
10934 }
10935
10936 // Local taxes
10937 if ($onlykey != 2 || $mysocuselocaltax1) {
10938 $substitutionarray['__AMOUNT_TAX2__'] = is_object($object) ? $object->total_localtax1 : '';
10939 }
10940 if ($onlykey != 2 || $mysocuselocaltax2) {
10941 $substitutionarray['__AMOUNT_TAX3__'] = is_object($object) ? $object->total_localtax2 : '';
10942 }
10943
10944 // Amount keys formatted in a currency
10945 $substitutionarray['__AMOUNT_EXCL_TAX_FORMATTED__'] = is_object($object) ? ($object->total_ht ? price($object->total_ht, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
10946 $substitutionarray['__AMOUNT_FORMATTED__'] = is_object($object) ? ($object->total_ttc ? price($object->total_ttc, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
10947 $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) : '';
10948 $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)) : '';
10949 if ($onlykey != 2 || $mysocuselocaltax1) {
10950 $substitutionarray['__AMOUNT_TAX2_FORMATTED__'] = is_object($object) ? ($object->total_localtax1 ? price($object->total_localtax1, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
10951 }
10952 if ($onlykey != 2 || $mysocuselocaltax2) {
10953 $substitutionarray['__AMOUNT_TAX3_FORMATTED__'] = is_object($object) ? ($object->total_localtax2 ? price($object->total_localtax2, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
10954 }
10955 // Amount keys formatted in a currency (with the typo error for backward compatibility)
10956 if ($onlykey != 2) {
10957 $substitutionarray['__AMOUNT_EXCL_TAX_FORMATED__'] = $substitutionarray['__AMOUNT_EXCL_TAX_FORMATTED__'];
10958 $substitutionarray['__AMOUNT_FORMATED__'] = $substitutionarray['__AMOUNT_FORMATTED__'];
10959 $substitutionarray['__AMOUNT_REMAIN_FORMATED__'] = $substitutionarray['__AMOUNT_REMAIN_FORMATTED__'];
10960 $substitutionarray['__AMOUNT_VAT_FORMATED__'] = $substitutionarray['__AMOUNT_VAT_FORMATTED__'];
10961 if ($mysocuselocaltax1) {
10962 $substitutionarray['__AMOUNT_TAX2_FORMATED__'] = $substitutionarray['__AMOUNT_TAX2_FORMATTED__'];
10963 }
10964 if ($mysoc->useLocalTax2) {
10965 $substitutionarray['__AMOUNT_TAX3_FORMATED__'] = $substitutionarray['__AMOUNT_TAX3_FORMATTED__'];
10966 }
10967 }
10968
10969 $substitutionarray['__AMOUNT_MULTICURRENCY__'] = (is_object($object) && isset($object->multicurrency_total_ttc)) ? $object->multicurrency_total_ttc : '';
10970 $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) : '';
10971 $substitutionarray['__AMOUNT_MULTICURRENCY_TEXT__'] = (is_object($object) && isset($object->multicurrency_total_ttc)) ? dol_convertToWord($object->multicurrency_total_ttc, $outputlangs, '', true) : '';
10972 $substitutionarray['__AMOUNT_MULTICURRENCY_TEXTCURRENCY__'] = (is_object($object) && isset($object->multicurrency_total_ttc)) ? dol_convertToWord($object->multicurrency_total_ttc, $outputlangs, $object->multicurrency_code, true) : '';
10973 $substitutionarray['__MULTICURRENCY_CODE__'] = (is_object($object) && isset($object->multicurrency_code)) ? $object->multicurrency_code : '';
10974 // TODO Add other keys for foreign multicurrency
10975
10976 // For backward compatibility
10977 if ($onlykey != 2) {
10978 $substitutionarray['__TOTAL_TTC__'] = is_object($object) ? $object->total_ttc : '';
10979 $substitutionarray['__TOTAL_HT__'] = is_object($object) ? $object->total_ht : '';
10980 $substitutionarray['__TOTAL_VAT__'] = is_object($object) ? (isset($object->total_vat) ? $object->total_vat : $object->total_tva) : '';
10981 }
10982 }
10983
10984
10985 if ((empty($exclude) || !in_array('date', $exclude)) && (empty($include) || in_array('date', $include))) {
10986 include_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php';
10987
10988 $now = dol_now();
10989
10990 $tmp = dol_getdate($now, true);
10991 $tmp2 = dol_get_prev_day($tmp['mday'], $tmp['mon'], $tmp['year']);
10992 $tmp3 = dol_get_prev_month($tmp['mon'], $tmp['year']);
10993 $tmp4 = dol_get_next_day($tmp['mday'], $tmp['mon'], $tmp['year']);
10994 $tmp5 = dol_get_next_month($tmp['mon'], $tmp['year']);
10995
10996 $daytext = $outputlangs->trans('Day' . $tmp['wday']);
10997
10998 $substitutionarray = array_merge($substitutionarray, array(
10999 '__NOW_TMS__' => (string) $now, // Must be the string that represent the int
11000 '__NOW_TMS_YMD__' => dol_print_date($now, 'day', 'auto', $outputlangs),
11001 '__DAY__' => (string) $tmp['mday'],
11002 '__DAY_TEXT__' => $daytext, // Monday
11003 '__DAY_TEXT_SHORT__' => dol_trunc($daytext, 3, 'right', 'UTF-8', 1), // Mon
11004 '__DAY_TEXT_MIN__' => dol_trunc($daytext, 1, 'right', 'UTF-8', 1), // M
11005 '__MONTH__' => (string) $tmp['mon'],
11006 '__MONTH_TEXT__' => $outputlangs->transnoentitiesnoconv('Month' . sprintf("%02d", $tmp['mon'])),
11007 '__MONTH_TEXT_SHORT__' => $outputlangs->transnoentitiesnoconv('MonthShort' . sprintf("%02d", $tmp['mon'])),
11008 '__MONTH_TEXT_MIN__' => $outputlangs->transnoentitiesnoconv('MonthVeryShort' . sprintf("%02d", $tmp['mon'])),
11009 '__YEAR__' => (string) $tmp['year'],
11010 '__YEAR_PREVIOUS_MONTH__' => (string) $tmp3['year'],
11011 '__YEAR_NEXT_MONTH__' => (string) $tmp5['year'],
11012 '__PREVIOUS_DAY__' => (string) $tmp2['day'],
11013 '__PREVIOUS_MONTH__' => (string) $tmp3['month'],
11014 '__PREVIOUS_MONTH_TEXT__' => $outputlangs->transnoentitiesnoconv('Month' . sprintf("%02d", $tmp3['month'])),
11015 '__PREVIOUS_MONTH_TEXT_SHORT__' => $outputlangs->transnoentitiesnoconv('MonthShort' . sprintf("%02d", $tmp3['month'])),
11016 '__PREVIOUS_MONTH_TEXT_MIN__' => $outputlangs->transnoentitiesnoconv('MonthVeryShort' . sprintf("%02d", $tmp3['month'])),
11017 '__PREVIOUS_YEAR__' => (string) ($tmp['year'] - 1),
11018 '__NEXT_DAY__' => (string) $tmp4['day'],
11019 '__NEXT_MONTH__' => (string) $tmp5['month'],
11020 '__NEXT_MONTH_TEXT__' => $outputlangs->transnoentitiesnoconv('Month' . sprintf("%02d", $tmp5['month'])),
11021 '__NEXT_MONTH_TEXT_SHORT__' => $outputlangs->transnoentitiesnoconv('MonthShort' . sprintf("%02d", $tmp5['month'])),
11022 '__NEXT_MONTH_TEXT_MIN__' => $outputlangs->transnoentitiesnoconv('MonthVeryShort' . sprintf("%02d", $tmp5['month'])),
11023 '__NEXT_YEAR__' => (string) ($tmp['year'] + 1),
11024 ));
11025 }
11026
11027 if (isModEnabled('multicompany')) {
11028 $substitutionarray = array_merge($substitutionarray, array('__ENTITY_ID__' => $conf->entity));
11029 }
11030 if ((empty($exclude) || !in_array('system', $exclude)) && (empty($include) || in_array('user', $include))) {
11031 $substitutionarray['__DOL_MAIN_URL_ROOT__'] = DOL_MAIN_URL_ROOT;
11032 $substitutionarray['__(AnyTranslationKey)__'] = $outputlangs->transnoentitiesnoconv('TranslationOfKey');
11033 $substitutionarray['__(AnyTranslationKey|langfile)__'] = $outputlangs->transnoentitiesnoconv('TranslationOfKey') . ' (load also language file before)';
11034 $substitutionarray['__[AnyConstantKey]__'] = $outputlangs->transnoentitiesnoconv('ValueOfConstantKey');
11035 }
11036
11037 // Note: The lazyload variables are replaced only during the call by make_substitutions, and only if necessary
11038
11039 return $substitutionarray;
11040}
11041
11058function make_substitutions($text, $substitutionarray, $outputlangs = null, $converttextinhtmlifnecessary = 0)
11059{
11060 global $db, $langs;
11061
11062 if (!is_array($substitutionarray)) {
11063 return 'ErrorBadParameterSubstitutionArrayWhenCalling_make_substitutions';
11064 }
11065
11066 if (empty($outputlangs)) {
11067 $outputlangs = $langs;
11068 }
11069
11070 // Is initial text HTML or simple text ?
11071 $msgishtml = 0;
11072 if (dol_textishtml($text, 1)) {
11073 $msgishtml = 1;
11074 }
11075
11076 // Make substitution for language keys: __(AnyTranslationKey)__ or __(AnyTranslationKey|langfile)__
11077 if (is_object($outputlangs)) {
11078 $reg = array();
11079 while (preg_match('/__\‍(([^\‍)]+)\‍)__/', $text, $reg)) {
11080 // If key is __(TranslationKey|langfile)__, then force load of langfile.lang
11081 $tmp = explode('|', $reg[1]);
11082 if (!empty($tmp[1])) {
11083 $outputlangs->load($tmp[1]);
11084 }
11085
11086 $value = $outputlangs->transnoentitiesnoconv($reg[1]);
11087
11088 if (empty($converttextinhtmlifnecessary)) {
11089 // convert $newval into HTML is necessary
11090 $text = preg_replace('/__\‍(' . preg_quote($reg[1], '/') . '\‍)__/', $msgishtml ? dol_htmlentitiesbr($value) : $value, $text);
11091 } else {
11092 if (preg_match('/__\‍(' . preg_quote($reg[1], '/') . '\‍)__/', $text)) { // If found, so replacement will be done later
11093 if (! $msgishtml) {
11094 $valueishtml = dol_textishtml($value, 1);
11095 //var_dump("valueishtml=".$valueishtml);
11096
11097 if ($valueishtml) {
11098 $text = dol_htmlentitiesbr($text);
11099 $msgishtml = 1;
11100 }
11101 } else {
11102 $value = dol_nl2br((string) $value);
11103 }
11104 }
11105 $text = preg_replace('/__\‍(' . preg_quote($reg[1], '/') . '\‍)__/', $value, $text);
11106 }
11107 }
11108 }
11109
11110 // Make substitution for constant keys.
11111 // Must be after the substitution of translation, so if the text of translation contains a string __[xxx]__, it is also converted.
11112 $reg = array();
11113 while (preg_match('/__\[([^\]]+)\]__/', $text, $reg)) {
11114 $originalkeyfound = $reg[1];
11115 $keyfound = preg_replace('/\|urlencode$/', '', $originalkeyfound);
11116
11117 if (isASecretKey($keyfound)) {
11118 $value = '*****forbidden*****';
11119 } else {
11120 $value = getDolGlobalString($keyfound);
11121 // Execute some functions on value of substitution key
11122 if (preg_match('/\|urlencode$/', $originalkeyfound)) {
11123 $value = urlencode($value);
11124 }
11125 }
11126
11127 if (empty($converttextinhtmlifnecessary)) {
11128 // convert $newval into HTML is necessary
11129 $text = preg_replace('/__\[' . preg_quote($originalkeyfound, '/') . '\]__/', $msgishtml ? dol_htmlentitiesbr($value) : $value, $text);
11130 } else {
11131 if (preg_match('/__\[' . preg_quote($reg[1], '/') . '\]__/', $text)) { // If found, so replacement will be done later
11132 if (! $msgishtml) {
11133 $valueishtml = dol_textishtml($value, 1);
11134
11135 if ($valueishtml) {
11136 $text = dol_htmlentitiesbr($text);
11137 $msgishtml = 1;
11138 }
11139 } else {
11140 $value = dol_nl2br((string) $value);
11141 }
11142 }
11143 $text = preg_replace('/__\[' . preg_quote($originalkeyfound, '/') . '\]__/', $value, $text);
11144 }
11145 }
11146
11147 // Make substitution for array $substitutionarray
11148 foreach ($substitutionarray as $key => $value) {
11149 if (!isset($value)) {
11150 continue; // If value is null, it same than not having substitution key at all into array, we do not replace.
11151 }
11152
11153 if (getDolGlobalString('MAIN_MAIL_DO_NOT_USE_SIGN') && ($key == '__USER_SIGNATURE__' || $key == '__SENDEREMAIL_SIGNATURE__')) {
11154 $value = ''; // Protection
11155 }
11156 if (empty($converttextinhtmlifnecessary)) {
11157 $text = str_replace((string) $key, (string) $value, $text); // Cast to string is needed when value is 123.5 for example
11158 } else {
11159 if (strpos($text, (string) $key) !== false) { // If found, so replacement will be done later
11160 if (! $msgishtml) {
11161 $valueishtml = dol_textishtml($value, 1);
11162
11163 if ($valueishtml) {
11164 $text = dol_htmlentitiesbr($text);
11165 $msgishtml = 1;
11166 }
11167 } else {
11168 $value = dol_nl2br((string) $value);
11169 }
11170 }
11171 $text = str_replace((string) $key, (string) $value, $text); // Cast to string is needed, for 123.5 for example
11172 }
11173 }
11174
11175 /*
11176 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
11177 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.
11178 If no, we don't need to make replacement, so we do nothing.
11179 If yes, we can make the substitution:
11180
11181 include_once $path;
11182 $tmpobj = new $class($db);
11183 $valuetouseforsubstitution = $tmpobj->$method($id, '__XXX__');
11184 And make the replacement of "__XXX__@lazyload" with $valuetouseforsubstitution
11185 */
11186 $memory_object_list = array();
11187 foreach ($substitutionarray as $key => $value) {
11188 $lazy_load_arr = array();
11189 if (preg_match('/(__[A-Z\_]+__)@lazyload$/', $key, $lazy_load_arr)) {
11190 if (isset($lazy_load_arr[1]) && !empty($lazy_load_arr[1])) {
11191 $key_to_substitute = $lazy_load_arr[1];
11192 if (preg_match('/' . preg_quote($key_to_substitute, '/') . '/', $text)) {
11193 $param_arr = explode(':', (string) $value);
11194 // path:class:method:id
11195 if (count($param_arr) >= 4) {
11196 $path = $param_arr[0];
11197 $class = $param_arr[1];
11198 $method = $param_arr[2];
11199 $id = (int) $param_arr[3];
11200 $keyinarrayresult = empty($param_arr[4]) ? '' : $param_arr[4];
11201
11202 // load class file and init object list in memory
11203 if (!isset($memory_object_list[$class])) {
11204 if (dol_is_file(DOL_DOCUMENT_ROOT . $path)) {
11205 require_once DOL_DOCUMENT_ROOT . $path;
11206 if (class_exists($class)) {
11207 $memory_object_list[$class] = array(
11208 'list' => array(),
11209 );
11210 }
11211 }
11212 }
11213
11214 // fetch object and set substitution
11215 if (isset($memory_object_list[$class]) && isset($memory_object_list[$class]['list'])) {
11216 if (method_exists($class, $method)) {
11217 if (!isset($memory_object_list[$class]['list'][$id])) {
11218 $tmpobj = new $class($db);
11219 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
11220 $tmpvaluetouseforsubstitution = $tmpobj->$method($id, $key_to_substitute);
11221 $memory_object_list[$class]['list'][$id] = $tmpobj;
11222 } else {
11223 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
11224 $tmpobj = $memory_object_list[$class]['list'][$id];
11225 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
11226 $tmpvaluetouseforsubstitution = $tmpobj->$method($id, $key_to_substitute, true);
11227 }
11228
11229 if ($keyinarrayresult) {
11230 $valuetouseforsubstitution = (string) $tmpvaluetouseforsubstitution[$keyinarrayresult]; // Cast to string in case value is 123.5 for example
11231 } else {
11232 $valuetouseforsubstitution = (string) $tmpvaluetouseforsubstitution; // Cast to string in case value is 123.5 for example
11233 }
11234 $text = str_replace((string) $key_to_substitute, $valuetouseforsubstitution, $text);
11235 }
11236 }
11237 }
11238 }
11239 }
11240 }
11241 }
11242
11243 return $text;
11244}
11245
11258function complete_substitutions_array(&$substitutionarray, $outputlangs, $object = null, $parameters = null, $callfunc = "completesubstitutionarray")
11259{
11260 global $conf, $user;
11261
11262 require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
11263
11264 // Note: substitution key for each extrafields, using key __EXTRA_XXX__ is already available into the getCommonSubstitutionArray used to build the substitution array.
11265
11266 // Check if there is external substitution to do, requested by plugins
11267 $dirsubstitutions = array_merge(array(), (array) $conf->modules_parts['substitutions']);
11268
11269 foreach ($dirsubstitutions as $reldir) {
11270 $dir = dol_buildpath($reldir, 0);
11271
11272 // Check if directory exists
11273 if (!dol_is_dir($dir)) {
11274 continue;
11275 }
11276
11277 $substitfiles = dol_dir_list($dir, 'files', 0, 'functions_');
11278 foreach ($substitfiles as $substitfile) {
11279 $reg = array();
11280 if (preg_match('/functions_(.*)\.lib\.php/i', $substitfile['name'], $reg)) {
11281 $module = $reg[1];
11282
11283 dol_syslog("Library " . $substitfile['name'] . " found into " . $dir);
11284 // Include the user's functions file
11285 require_once $dir . $substitfile['name'];
11286 // Call the user's function, and only if it is defined
11287 $function_name = $module . "_" . $callfunc;
11288 if (function_exists($function_name)) {
11289 $function_name($substitutionarray, $outputlangs, $object, $parameters);
11290 }
11291 }
11292 }
11293 }
11294 if (getDolGlobalString('ODT_ENABLE_ALL_TAGS_IN_SUBSTITUTIONS')) {
11295 // to list all tags in odt template
11296 $tags = '';
11297 foreach ($substitutionarray as $key => $value) {
11298 $tags .= '{' . $key . '} => ' . $value . "\n";
11299 }
11300 $substitutionarray = array_merge($substitutionarray, array('__ALL_TAGS__' => $tags));
11301 }
11302}
11303
11313function print_date_range($date_start, $date_end, $format = '', $outputlangs = null)
11314{
11315 print get_date_range($date_start, $date_end, $format, $outputlangs);
11316}
11317
11328function get_date_range($date_start, $date_end, $format = '', $outputlangs = null, $withparenthesis = 1)
11329{
11330 global $langs;
11331
11332 $out = '';
11333
11334 if (!is_object($outputlangs)) {
11335 $outputlangs = $langs;
11336 }
11337
11338 if ($date_start && $date_end) {
11339 $out .= ($withparenthesis ? ($withparenthesis == 1 ? ' ' : '').'(' : '') . $outputlangs->transnoentitiesnoconv('DateFromTo', dol_print_date($date_start, $format, false, $outputlangs), dol_print_date($date_end, $format, false, $outputlangs)) . ($withparenthesis ? ')' : '');
11340 }
11341 if ($date_start && !$date_end) {
11342 $out .= ($withparenthesis ? ($withparenthesis == 1 ? ' ' : '').'(' : '') . $outputlangs->transnoentitiesnoconv('DateFrom', dol_print_date($date_start, $format, false, $outputlangs)) . ($withparenthesis ? ')' : '');
11343 }
11344 if (!$date_start && $date_end) {
11345 $out .= ($withparenthesis ? ($withparenthesis == 1 ? ' ' : '').'(' : '') . $outputlangs->transnoentitiesnoconv('DateUntil', dol_print_date($date_end, $format, false, $outputlangs)) . ($withparenthesis ? ')' : '');
11346 }
11347
11348 return $out;
11349}
11350
11359function dolGetFirstLastname($firstname, $lastname, $nameorder = -1)
11360{
11361 $ret = '';
11362 // If order not defined, we use the setup
11363 if ($nameorder < 0) {
11364 $nameorder = (!getDolGlobalString('MAIN_FIRSTNAME_NAME_POSITION') ? 1 : 0);
11365 }
11366 if ($nameorder == 1) {
11367 $ret .= $firstname;
11368 if ($firstname && $lastname) {
11369 $ret .= ' ';
11370 }
11371 $ret .= $lastname;
11372 } elseif ($nameorder == 2 || $nameorder == 3) {
11373 $ret .= $firstname;
11374 if (empty($ret) && $nameorder == 3) {
11375 $ret .= $lastname;
11376 }
11377 } else { // 0, 4 or 5
11378 $ret .= $lastname;
11379 if (empty($ret) && $nameorder == 5) {
11380 $ret .= $firstname;
11381 }
11382 if ($nameorder == 0) {
11383 if ($firstname && $lastname) {
11384 $ret .= ' ';
11385 }
11386 $ret .= $firstname;
11387 }
11388 }
11389 return $ret;
11390}
11391
11392
11405function setEventMessage($mesgs, $style = 'mesgs', $noduplicate = 0, $attop = 0)
11406{
11407 //dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING); This is not deprecated, it is used by setEventMessages function
11408 if (!is_array($mesgs)) {
11409 $mesgs = trim((string) $mesgs);
11410 // If mesgs is a not an empty string
11411 if ($mesgs) {
11412 if (!empty($noduplicate) && isset($_SESSION['dol_events'][$style]) && in_array($mesgs, $_SESSION['dol_events'][$style])) {
11413 return;
11414 }
11415 if ($attop) {
11416 array_unshift($_SESSION['dol_events'][$style], $mesgs);
11417 } else {
11418 $_SESSION['dol_events'][$style][] = $mesgs;
11419 }
11420 }
11421 } else {
11422 // If mesgs is an array
11423 foreach ($mesgs as $mesg) {
11424 $mesg = trim((string) $mesg);
11425 if ($mesg) {
11426 if (!empty($noduplicate) && isset($_SESSION['dol_events'][$style]) && in_array($mesg, $_SESSION['dol_events'][$style])) {
11427 return;
11428 }
11429 if ($attop) {
11430 array_unshift($_SESSION['dol_events'][$style], $mesgs);
11431 } else {
11432 $_SESSION['dol_events'][$style][] = $mesg;
11433 }
11434 }
11435 }
11436 }
11437}
11438
11452function setEventMessages($mesg, $mesgs, $style = 'mesgs', $messagekey = '', $noduplicate = 0, $attop = 0)
11453{
11454 if (empty($mesg) && empty($mesgs)) {
11455 dol_syslog("Try to add a message in stack, but value to add is empty message" . getCallerInfoString(), LOG_WARNING);
11456 } else {
11457 if ($messagekey) {
11458 // Complete message with a js link to set a cookie "DOLHIDEMESSAGE".$messagekey;
11459 // TODO
11460 $mesg .= '';
11461 }
11462 if (empty($messagekey) || empty($_COOKIE["DOLUSER_HIDEMESSAGE" . $messagekey])) {
11463 if (!in_array((string) $style, array('mesgs', 'warnings', 'errors'))) {
11464 dol_print_error(null, 'Bad parameter style=' . $style . ' for setEventMessages');
11465 }
11466 if (empty($mesgs)) {
11467 setEventMessage((string) $mesg, $style, $noduplicate, $attop);
11468 } else {
11469 if (!empty($mesg) && !in_array($mesg, $mesgs)) {
11470 setEventMessage($mesg, $style, $noduplicate, $attop); // Add message string if not already into array
11471 }
11472 setEventMessage($mesgs, $style, $noduplicate, $attop);
11473 }
11474 }
11475 }
11476}
11477
11487function dol_htmloutput_events($disabledoutputofmessages = 0)
11488{
11489 // Show mesgs
11490 if (isset($_SESSION['dol_events']['mesgs'])) {
11491 if (empty($disabledoutputofmessages)) {
11492 dol_htmloutput_mesg('', $_SESSION['dol_events']['mesgs']);
11493 }
11494 unset($_SESSION['dol_events']['mesgs']);
11495 }
11496 // Show errors
11497 if (isset($_SESSION['dol_events']['errors'])) {
11498 if (empty($disabledoutputofmessages)) {
11499 dol_htmloutput_mesg('', $_SESSION['dol_events']['errors'], 'error');
11500 }
11501 unset($_SESSION['dol_events']['errors']);
11502 }
11503
11504 // Show warnings
11505 if (isset($_SESSION['dol_events']['warnings'])) {
11506 if (empty($disabledoutputofmessages)) {
11507 dol_htmloutput_mesg('', $_SESSION['dol_events']['warnings'], 'warning');
11508 }
11509 unset($_SESSION['dol_events']['warnings']);
11510 }
11511}
11512
11527function get_htmloutput_mesg($mesgstring = '', $mesgarray = [], $style = 'ok', $keepembedded = 0)
11528{
11529 global $conf, $langs;
11530
11531 $ret = 0;
11532 $return = '';
11533 $out = '';
11534 $divstart = $divend = '';
11535
11536 // If inline message with no format, we add it.
11537 if ((empty($conf->use_javascript_ajax) || getDolGlobalString('MAIN_DISABLE_JQUERY_JNOTIFY') || $keepembedded) && !preg_match('/<div class=".*">/i', $out)) {
11538 $divstart = '<div class="' . $style . ' clearboth">';
11539 $divend = '</div>';
11540 }
11541
11542 if ((is_array($mesgarray) && count($mesgarray)) || $mesgstring) {
11543 $langs->load("errors");
11544 $out .= $divstart;
11545 if (is_array($mesgarray) && count($mesgarray)) {
11546 foreach ($mesgarray as $message) {
11547 $ret++;
11548 $out .= $langs->trans($message);
11549 if ($ret < count($mesgarray)) {
11550 $out .= "<br>\n";
11551 }
11552 }
11553 }
11554 if ($mesgstring) {
11555 $ret++;
11556 $out .= $langs->trans($mesgstring);
11557 }
11558 $out .= $divend;
11559 }
11560
11561 if ($out) {
11562 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_DISABLE_JQUERY_JNOTIFY') && empty($keepembedded)) {
11563 $return = '<script nonce="' . getNonce() . '">
11564 $(document).ready(function() {
11565 /* jnotify(message, preset of message type, keepmessage) */
11566 $.jnotify("' . dol_escape_js($out) . '", "' . ($style == "ok" ? 3000 : $style) . '", ' . ($style == "ok" ? "false" : "true") . ',{ remove: function (){} } );
11567 });
11568 </script>';
11569 } else {
11570 $return = $out;
11571 }
11572 }
11573
11574 return $return;
11575}
11576
11588function get_htmloutput_errors($mesgstring = '', $mesgarray = array(), $keepembedded = 0)
11589{
11590 return get_htmloutput_mesg($mesgstring, $mesgarray, 'error', $keepembedded);
11591}
11592
11606function dol_htmloutput_mesg($mesgstring = '', $mesgarray = array(), $style = 'ok', $keepembedded = 0)
11607{
11608 if (empty($mesgstring) && (!is_array($mesgarray) || count($mesgarray) == 0)) {
11609 return;
11610 }
11611
11612 $iserror = 0;
11613 $iswarning = 0;
11614 if (is_array($mesgarray)) {
11615 foreach ($mesgarray as $val) {
11616 if ($val && preg_match('/class="error"/i', $val)) {
11617 $iserror++;
11618 break;
11619 }
11620 if ($val && preg_match('/class="warning"/i', $val)) {
11621 $iswarning++;
11622 break;
11623 }
11624 }
11625 } elseif ($mesgstring && preg_match('/class="error"/i', $mesgstring)) {
11626 $iserror++;
11627 } elseif ($mesgstring && preg_match('/class="warning"/i', $mesgstring)) {
11628 $iswarning++;
11629 }
11630 if ($style == 'error' || $style == 'errors') {
11631 $iserror++;
11632 }
11633 if ($style == 'warning' || $style == 'warnings') {
11634 $iswarning++;
11635 }
11636
11637 if ($iserror || $iswarning) {
11638 // Remove div from texts
11639 $mesgstring = preg_replace('/<\/div><div class="(error|warning)">/', '<br>', $mesgstring);
11640 $mesgstring = preg_replace('/<div class="(error|warning)">/', '', $mesgstring);
11641 $mesgstring = preg_replace('/<\/div>/', '', $mesgstring);
11642 // Remove div from texts array
11643 if (is_array($mesgarray)) {
11644 $newmesgarray = array();
11645 foreach ($mesgarray as $val) {
11646 if (is_string($val)) {
11647 $tmpmesgstring = preg_replace('/<\/div><div class="(error|warning)">/', '<br>', $val);
11648 $tmpmesgstring = preg_replace('/<div class="(error|warning)">/', '', $tmpmesgstring);
11649 $tmpmesgstring = preg_replace('/<\/div>/', '', $tmpmesgstring);
11650 $newmesgarray[] = $tmpmesgstring;
11651 } else {
11652 dol_syslog("Error call of dol_htmloutput_mesg with an array with a value that is not a string", LOG_WARNING);
11653 }
11654 }
11655 $mesgarray = $newmesgarray;
11656 }
11657 print get_htmloutput_mesg($mesgstring, $mesgarray, ($iserror ? 'error' : 'warning'), $keepembedded);
11658 } else {
11659 print get_htmloutput_mesg($mesgstring, $mesgarray, 'ok', $keepembedded);
11660 }
11661}
11662
11674function dol_htmloutput_errors($mesgstring = '', $mesgarray = array(), $keepembedded = 0)
11675{
11676 dol_htmloutput_mesg($mesgstring, $mesgarray, 'error', $keepembedded);
11677}
11678
11686function dolSort($arraytosort)
11687{
11688 sort($arraytosort);
11689 return $arraytosort;
11690}
11691
11713function dol_sort_array(&$array, $index, $order = 'asc', $natsort = 0, $case_sensitive = 0, $keepindex = 0)
11714{
11715 // Clean parameters
11716 $order = strtolower($order);
11717
11718 if (is_array($array)) {
11719 $sizearray = count($array);
11720 if ($sizearray > 0) {
11721 // Build a temp array with sorting key as value
11722 $temp = array();
11723 foreach (array_keys($array) as $key) {
11724 $tmpmultikey = explode(',', $index);
11725 $newindex = $tmpmultikey[0];
11726 if (is_object($array[$key])) {
11727 $temp[$key] = empty($array[$key]->$newindex) ? 0 : $array[$key]->$newindex;
11728 // Add other keys
11729 if (!empty($tmpmultikey[1])) {
11730 $newindex = $tmpmultikey[1];
11731 $temp[$key] .= '__' . (empty($array[$key]->$newindex) ? 0 : $array[$key]->$newindex);
11732 }
11733 } else {
11734 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable,PhanTypeArraySuspicious,PhanTypeMismatchDimFetch
11735 $temp[$key] = empty($array[$key][$newindex]) ? 0 : $array[$key][$newindex];
11736 // Add other keys
11737 if (!empty($tmpmultikey[1])) {
11738 $newindex = $tmpmultikey[1];
11739 // @phan-suppress-next-line PhanTypeArraySuspicious,PhanTypeMismatchDimFetch
11740 $temp[$key] .= '__' . (empty($array[$key][$newindex]) ? 0 : $array[$key][$newindex]);
11741 }
11742 }
11743 if ($natsort == -1) {
11744 $temp[$key] = '___' . $temp[$key]; // We add a string at begin of value to force an alpha order when using asort.
11745 }
11746 }
11747 if (empty($natsort) || $natsort == -1) {
11748 if ($order == 'asc') {
11749 asort($temp);
11750 } else {
11751 arsort($temp);
11752 }
11753 } else {
11754 if ($case_sensitive) {
11755 natsort($temp);
11756 } else {
11757 natcasesort($temp); // natecasesort is not sensible to case
11758 }
11759 if ($order != 'asc') {
11760 $temp = array_reverse($temp, true);
11761 }
11762 }
11763
11764 $sorted = array();
11765
11766 foreach (array_keys($temp) as $key) {
11767 (is_numeric($key) && empty($keepindex)) ? $sorted[] = $array[$key] : $sorted[$key] = $array[$key];
11768 }
11769
11770 return $sorted;
11771 }
11772 }
11773 return $array;
11774}
11775
11776
11784function utf8_check($str)
11785{
11786 $str = (string) $str; // Sometimes string is an int.
11787
11788 // We must use here a binary strlen function (so not dol_strlen)
11789 $strLength = strlen($str);
11790 for ($i = 0; $i < $strLength; $i++) {
11791 if (ord($str[$i]) < 0x80) {
11792 continue; // 0bbbbbbb
11793 } elseif ((ord($str[$i]) & 0xE0) == 0xC0) {
11794 $n = 1; // 110bbbbb
11795 } elseif ((ord($str[$i]) & 0xF0) == 0xE0) {
11796 $n = 2; // 1110bbbb
11797 } elseif ((ord($str[$i]) & 0xF8) == 0xF0) {
11798 $n = 3; // 11110bbb
11799 } elseif ((ord($str[$i]) & 0xFC) == 0xF8) {
11800 $n = 4; // 111110bb
11801 } elseif ((ord($str[$i]) & 0xFE) == 0xFC) {
11802 $n = 5; // 1111110b
11803 } else {
11804 return false; // Does not match any model
11805 }
11806 for ($j = 0; $j < $n; $j++) { // n bytes matching 10bbbbbb follow ?
11807 if ((++$i == strlen($str)) || ((ord($str[$i]) & 0xC0) != 0x80)) {
11808 return false;
11809 }
11810 }
11811 }
11812 return true;
11813}
11814
11822function utf8_valid($str)
11823{
11824 /* 2 other methods to test if string is utf8
11825 $validUTF8 = mb_check_encoding($messagetext, 'UTF-8');
11826 $validUTF8b = ! (false === mb_detect_encoding($messagetext, 'UTF-8', true));
11827 */
11828 return preg_match('//u', $str) ? true : false;
11829}
11830
11831
11838function ascii_check($str)
11839{
11840 if (function_exists('mb_check_encoding')) {
11841 //if (mb_detect_encoding($str, 'ASCII', true) return false;
11842 if (!mb_check_encoding($str, 'ASCII')) {
11843 return false;
11844 }
11845 } else {
11846 if (preg_match('/[^\x00-\x7f]/', $str)) {
11847 return false; // Contains a byte > 7f
11848 }
11849 }
11850
11851 return true;
11852}
11853
11854
11862function dol_osencode($str)
11863{
11864 $tmp = ini_get("unicode.filesystem_encoding");
11865 if (empty($tmp) && !empty($_SERVER["WINDIR"])) {
11866 $tmp = 'iso-8859-1'; // By default for windows
11867 }
11868 if (empty($tmp)) {
11869 $tmp = 'utf-8'; // By default for other
11870 }
11871 if (getDolGlobalString('MAIN_FILESYSTEM_ENCODING')) {
11872 $tmp = getDolGlobalString('MAIN_FILESYSTEM_ENCODING');
11873 }
11874
11875 if ($tmp == 'iso-8859-1') {
11876 return mb_convert_encoding($str, 'ISO-8859-1', 'UTF-8');
11877 }
11878 return $str;
11879}
11880
11881
11897function dol_getIdFromCode($db, $key, $tablename, $fieldkey = 'code', $fieldid = 'id', $entityfilter = 0, $filters = '', $useCache = true)
11898{
11899 global $conf;
11900
11901 // If key empty
11902 if ($key == '') {
11903 return 0;
11904 }
11905
11906 // Check in cache
11907 if ($useCache && isset($conf->cache['codeid'][$tablename][$key][$fieldid])) { // Can be defined to 0 or ''
11908 return $conf->cache['codeid'][$tablename][$key][$fieldid]; // Found in cache
11909 }
11910
11911 dol_syslog('dol_getIdFromCode (value for field ' . $fieldid . ' from key ' . $key . ' not found into cache)', LOG_DEBUG);
11912
11913 $sql = "SELECT " . $db->sanitize($fieldid) . " as valuetoget";
11914 $sql .= " FROM " . MAIN_DB_PREFIX . $db->sanitize($tablename);
11915 if ($fieldkey == 'id' || $fieldkey == 'rowid') {
11916 $sql .= " WHERE " . $db->sanitize($fieldkey) . " = " . ((int) $key);
11917 } else {
11918 $sql .= " WHERE " . $db->sanitize($fieldkey) . " = '" . $db->escape($key) . "'";
11919 }
11920 if (!empty($entityfilter)) {
11921 $sql .= " AND entity IN (" . getEntity($tablename) . ")";
11922 }
11923 if ($filters) {
11924 $sql .= $filters; // @phan-suppress-current-line SqlInjection
11925 }
11926
11927 $resql = $db->query($sql);
11928 if ($resql) {
11929 $obj = $db->fetch_object($resql);
11930 $valuetoget = '';
11931 if ($obj) {
11932 $valuetoget = $obj->valuetoget;
11933 $conf->cache['codeid'][$tablename][$key][$fieldid] = $valuetoget;
11934 } else {
11935 $conf->cache['codeid'][$tablename][$key][$fieldid] = '';
11936 }
11937 $db->free($resql);
11938
11939 return $valuetoget;
11940 } else {
11941 return -1;
11942 }
11943}
11944
11954function isStringVarMatching($var, $regextext, $matchrule = 1)
11955{
11956 // Tolerate callers (custom modules, older code) that already pass a full regex with delimiters
11957 // like '/^(aaa|bbb)/' instead of the bare body. Without this, the function would build
11958 // '/^/^(aaa|bbb)//' which trips preg_match() with 'Unknown modifier ^'.
11959 $regextext = preg_replace('#^/\^?#', '', (string) $regextext);
11960 $regextext = preg_replace('#\$?/[imsxuADSUXJ]*$#', '', $regextext);
11961
11962 if ($matchrule == 1) {
11963 if ($var == 'mainmenu') {
11964 global $mainmenu;
11965 return (preg_match('/^' . $regextext . '/', $mainmenu));
11966 } elseif ($var == 'leftmenu') {
11967 global $leftmenu;
11968 return (preg_match('/^' . $regextext . '/', $leftmenu));
11969 } else {
11970 return 'This variable is not accessible with dol_eval';
11971 }
11972 } else {
11973 return 'This value '.$matchrule.' for param $matchrule is not yet implemented';
11974 }
11975}
11976
11977
11987function verifCond($strToEvaluate, $onlysimplestring = '1')
11988{
11989 //print $strToEvaluate."<br>\n";
11990 $rights = true;
11991 if (isset($strToEvaluate) && $strToEvaluate !== '') {
11992 //var_dump($strToEvaluate);
11993 //$rep = dol_eval($strToEvaluate, 1, 0, '1'); // to show the error
11994 $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
11995
11996 // On string syntax error, dol_eval may return a string that start with 'Bad call of ...' or 'Bad string syntax to evaluate...' !!!
11997 //var_dump($strToEvaluate, $rep);
11998 $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));
11999 //var_dump($rights);
12000 }
12001 return $rights;
12002}
12003
12019function dol_eval($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestring = '1')
12020{
12021 if ($returnvalue != 1) {
12022 dol_syslog("Use of dol_eval with parameter returnvalue = 0 is now forbidden. Please fix this", LOG_ERR);
12023 }
12024
12025 global $dolibarr_main_use_dol_eval_new; // experimental option, not yet ready
12026 if (!empty($dolibarr_main_use_dol_eval_new)) {
12027 return dol_eval_new($s);
12028 } else {
12029 return dol_eval_standard($s, $hideerrors, $onlysimplestring);
12030 }
12031}
12032
12043function dol_eval_new($s)
12044{
12045 // Only this global variables can be read by eval function and returned to caller
12046 global $conf, // Read of const is done with getDolGlobalString() but we need $conf->currency for example
12047 $db, $langs, $user, $website, $websitepage,
12048 $action, $mainmenu, $leftmenu,
12049 $mysoc,
12050 $objectoffield, // To allow the use of $objectoffield in computed fields
12051
12052 // Old variables used
12053 $object;
12054
12055 if (getDolGlobalString('MAIN_ALLOW_OLD_VAR_OBJ_IN_DOL_EVAL')) {
12056 global $obj; // To get $obj used into list when dol_eval() is used for computed fields and $obj is not yet $object
12057 }
12058
12059 // PHP < 7.4.0
12060 defined('T_COALESCE_EQUAL') || define('T_COALESCE_EQUAL', PHP_INT_MAX);
12061 defined('T_FN') || define('T_FN', PHP_INT_MAX);
12062
12063 // PHP < 8.0.0
12064 defined('T_ATTRIBUTE') || define('T_ATTRIBUTE', PHP_INT_MAX);
12065 defined('T_MATCH') || define('T_MATCH', PHP_INT_MAX);
12066 defined('T_NAME_FULLY_QUALIFIED') || define('T_NAME_FULLY_QUALIFIED', PHP_INT_MAX);
12067 defined('T_NAME_QUALIFIED') || define('T_NAME_QUALIFIED', PHP_INT_MAX);
12068 defined('T_NAME_RELATIVE') || define('T_NAME_RELATIVE', PHP_INT_MAX);
12069
12070 // PHP < 8.1.0
12071 defined('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG') || define('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG', PHP_INT_MAX);
12072 defined('T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG') || define('T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG', PHP_INT_MAX);
12073 defined('T_ENUM') || define('T_ENUM', PHP_INT_MAX);
12074 defined('T_READONLY') || define('T_READONLY', PHP_INT_MAX);
12075
12076 // PHP < 8.4.0
12077 defined('T_PRIVATE_SET') || define('T_PRIVATE_SET', PHP_INT_MAX);
12078 defined('T_PROTECTED_SET') || define('T_PROTECTED_SET', PHP_INT_MAX);
12079 defined('T_PUBLIC_SET') || define('T_PUBLIC_SET', PHP_INT_MAX);
12080
12081 $prohibited_token_ids = [
12082 /*
12083 * Prohibited int tokens
12084 */
12085
12086 // T_AND_EQUAL', 'T_ARRAY', 'T_ARRAY_CAST', 'T_AS',
12087 'T_ABSTRACT',
12088 'T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG',
12089 'T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG',
12090 'T_ATTRIBUTE',
12091 // 'T_BOOLEAN_AND', 'T_BOOLEAN_OR', 'T_BOOL_CAST', 'T_BREAK',
12092 'T_BAD_CHARACTER',
12093 // 'T_CASE', 'T_CLASS_C', 'T_CLONE', 'T_COALESCE', 'T_COALESCE_EQUAL', 'T_COMMENT', 'T_CONCAT_EQUAL',
12094 // 'T_CONSTANT_ENCAPSED_STRING', 'T_CONTINUE', 'T_CURLY_OPEN',
12095 'T_CALLABLE',
12096 'T_CATCH',
12097 'T_CLASS',
12098 'T_CLOSE_TAG',
12099 'T_CONST',
12100 // 'T_DEC', 'T_DEFAULT', 'T_DIV_EQUAL', 'T_DNUMBER', 'T_DO', 'T_DOC_COMMENT',
12101 // 'T_DOLLAR_OPEN_CURLY_BRACES', 'T_DOUBLE_ARROW', 'T_DOUBLE_CAST', 'T_DOUBLE_COLON',
12102 'T_DECLARE',
12103 'T_DIR',
12104 // 'T_ELLIPSIS', 'T_ELSE', 'T_ELSEIF', 'T_EMPTY', 'T_ENCAPSED_AND_WHITESPACE', 'T_ENDFOR',
12105 // 'T_ENDFOREACH', 'T_ENDIF', 'T_ENDSWITCH', 'T_ENDWHILE', 'T_END_HEREDOC',
12106 'T_ECHO',
12107 'T_ENDDECLARE',
12108 'T_ENUM',
12109 'T_EVAL',
12110 'T_EXIT',
12111 'T_EXTENDS',
12112 // 'T_FOR', 'T_FOREACH',
12113 'T_FILE',
12114 'T_FINAL',
12115 'T_FINALLY',
12116 'T_FN',
12117 'T_FUNCTION',
12118 'T_FUNC_C',
12119 'T_GLOBAL',
12120 'T_GOTO',
12121 'T_HALT_COMPILER',
12122 // 'T_IF', 'T_INC', 'T_INLINE_HTML', 'T_INSTANCEOF', 'T_INT_CAST', 'T_ISSET', 'T_IS_EQUAL', 'T_IS_GREATER_OR_EQUAL',
12123 // 'T_IS_IDENTICAL', 'T_IS_NOT_EQUAL', 'T_IS_NOT_IDENTICAL', 'T_IS_SMALLER_OR_EQUAL',
12124 'T_IMPLEMENTS',
12125 'T_INCLUDE',
12126 'T_INCLUDE_ONCE',
12127 'T_INSTEADOF',
12128 'T_INTERFACE',
12129 // 'T_LIST', 'T_LNUMBER', 'T_LOGICAL_AND', 'T_LOGICAL_OR', 'T_LOGICAL_XOR',
12130 'T_LINE',
12131 // 'T_MINUS_EQUAL', 'T_MOD_EQUAL', 'T_MUL_EQUAL',
12132 'T_METHOD_C',
12133 // 'T_NEW',
12134 // 'T_NS_SEPARATOR', 'T_NUM_STRING',
12135 'T_NAMESPACE',
12136 // 'T_NAME_FULLY_QUALIFIED', 'T_NAME_QUALIFIED', 'T_NAME_RELATIVE', 'T_NS_C',
12137 // 'T_OBJECT_CAST', 'T_OBJECT_OPERATOR', 'T_OR_EQUAL',
12138 'T_OPEN_TAG',
12139 'T_OPEN_TAG_WITH_ECHO',
12140 // 'T_PAAMAYIM_NEKUDOTAYIM', 'T_PLUS_EQUAL', 'T_POW', 'T_POW_EQUAL',
12141 'T_PRINT',
12142 'T_PRIVATE',
12143 'T_PROTECTED',
12144 'T_PUBLIC',
12145 // 'T_PROPERTY_C',
12146 'T_READONLY',
12147 'T_REQUIRE',
12148 'T_REQUIRE_ONCE',
12149 'T_RETURN',
12150 // 'T_SL', 'T_SL_EQUAL', 'T_SPACESHIP', 'T_SR', 'T_SR_EQUAL', 'T_START_HEREDOC', 'T_STATIC',
12151 // 'T_STRING', 'T_STRING_CAST', 'T_STRING_VARNAME', 'T_SWITCH',
12152 'T_STATIC',
12153 'T_THROW',
12154 'T_TRAIT',
12155 'T_TRAIT_C',
12156 'T_TRY',
12157 'T_UNSET',
12158 'T_UNSET_CAST',
12159 'T_USE',
12160 // 'T_VARIABLE',
12161 'T_VAR',
12162 // 'T_WHILE', 'T_WHITESPACE',
12163 // 'T_XOR_EQUAL',
12164 // 'T_YIELD', 'T_YIELD_FROM',
12165
12166 /*
12167 * Prohibited string tokens
12168 */
12169 ';',
12170 '`',
12171 ];
12172
12173 $prohibited_variables = [
12174 '$_COOKIE',
12175 '$_ENV',
12176 '$_FILES',
12177 '$GLOBALS',
12178 '$_GET',
12179 '$_POST',
12180 '$_REQUEST',
12181 '$_SERVER',
12182 '$_SESSION',
12183 ];
12184
12185 $forbiddenphpfunctions = array();
12186 $forbiddenphpmethods = array();
12187
12188 // Same list than in dol_eval
12189 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("override_function", "session_id", "session_create_id", "session_regenerate_id"));
12190 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("get_defined_functions", "get_defined_vars", "get_defined_constants", "get_declared_classes"));
12191 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("function"));
12192
12193 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("GETPOST")); // native dolibarr functions
12194
12195 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("ob_start"));
12196
12197 // Functions with callable parameters
12198 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("call_user_func", "call_user_func_array"));
12199 $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"));
12200 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("usort", "uasort", "uksort"));
12201 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("preg_replace_callback", "preg_replace_callback_array", "header_register_callback"));
12202 $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"));
12203 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("spl_autoload_register", "spl_autoload_unregister", "iterator_apply", "session_set_save_handler"));
12204 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("forward_static_call", "forward_static_call_array", "register_postsend_function"));
12205 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("readline_completion_function", "readline_callback_handler_install"));
12206
12207 // Exec functions
12208 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("exec", "passthru", "shell_exec", "system", "proc_open", "popen"));
12209 $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"));
12210 $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", ));
12211 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("putenv", "dl", "apache_child_terminate", "apache_setenv"));
12212 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_eval", "dol_eval_new", "dol_eval_standard", "executeCLI", "verifCond", "dolEncrypt", "dolDecrypt")); // native dolibarr functions
12213 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("eval", "create_function", "assert", "mb_ereg_replace")); // function with eval capabilities
12214
12215 // Include functions
12216 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("require", "include", "require_once", "include_once"));
12217
12218 $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
12219 $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"));
12220
12221 if (!getDolGlobalString('MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL')) { // We disallow all function that allow to obfuscate the real name of a function
12222 // @phpcs:ignore
12223 $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
12224 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_concat", "dol_concatdesc")); // native dolibarr functions
12225 }
12226
12227 $forbiddenphpmethods = array_merge($forbiddenphpmethods, array('invoke', 'invokeArgs')); // Methods of ReflectionFunction to execute a function
12228
12229 $prohibited_functions = array_merge($forbiddenphpfunctions, $forbiddenphpmethods);
12230
12231 $prohibited_token_arrangements = [
12232 // Variable functions "$a(", '"$a"(', "'FN_NAME'(", ('FN_NAME')()
12233 ' T_VARIABLE ( ',
12234 ' " ( ',
12235 ' \' ( ',
12236 ' T_CONSTANT_ENCAPSED_STRING ( ',
12237 ' ) ( ',
12238 ];
12239
12240 $tokens = token_get_all("<?php return {$s};", TOKEN_PARSE);
12241
12242 $tokens_arrangement = ' ';
12243
12244 for ($i = 2, $c = count($tokens) - 1; $i < $c; ++$i) { // ignore <?php return and ;
12245 if (is_array($tokens[$i])) {
12246 $token_id = $tokens[$i][0];
12247 $token_value = $tokens[$i][1];
12248 $token_name = token_name($tokens[$i][0]);
12249 } else {
12250 $token_id = $tokens[$i];
12251 $token_value = $tokens[$i];
12252 $token_name = $tokens[$i];
12253 }
12254
12255 // Ignore whitespaces
12256 if (T_WHITESPACE === $token_id) {
12257 continue;
12258 }
12259
12260 // Keep history to check arrangements
12261 $tokens_arrangement .= "{$token_name} ";
12262
12263 // Prohibited Variables
12264 if (
12265 T_VARIABLE === $token_id
12266 && in_array($token_value, $prohibited_variables, true)
12267 ) {
12268 return "Bad string syntax to evaluate. « {$token_value} » is prohibited in « {$s} »";
12269 }
12270
12271 // Prohibited Functions
12272 if (
12273 T_STRING === $token_id
12274 && in_array($token_value, $prohibited_functions, true)
12275 ) {
12276 return "Bad string syntax to evaluate. « {$token_value} » is prohibited in « {$s} »";
12277 }
12278 }
12279
12280 // Prohibited Token IDs
12281 $maxi = count($prohibited_token_ids);
12282 for ($i = 0; $i < $maxi; ++$i) {
12283 if (false !== strpos($tokens_arrangement, " {$prohibited_token_ids[$i]} ")) {
12284 return "Bad string syntax to evaluate. « {$prohibited_token_ids[$i]} » is prohibited in « {$s} »";
12285 }
12286 }
12287
12288 // Prohibited token arrangements
12289 $maxi = count($prohibited_token_arrangements);
12290 for ($i = 0; $i < $maxi; ++$i) {
12291 if (false !== strpos($tokens_arrangement, $prohibited_token_arrangements[$i])) {
12292 return "Bad string syntax to evaluate. « {$prohibited_token_arrangements[$i]} » is prohibited in « {$s} »";
12293 }
12294 }
12295
12296 // Return result
12297 try {
12298 return @eval("return {$s};") ?? '';
12299 } catch (Throwable $ex) {
12300 return "Bad string syntax to evaluate. Exception during evaluation: " . $s . " - " . $ex->getMessage();
12301 }
12302}
12303
12318function dol_eval_standard($s, $hideerrors = 1, $onlysimplestring = '1')
12319{
12320 // Only this global variables can be read by eval function and returned to caller
12321 // The less we have, the better it is.
12322
12323 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()
12324 global $db, $langs, $user, $website, $websitepage;
12325 global $action, $mainmenu, $leftmenu;
12326 global $mysoc;
12327 global $objectoffield; // To allow the use of $objectoffield in computed fields
12328 global $object;
12329
12330 // Old variables (deprecated since v23)
12331 if (getDolGlobalString('MAIN_ALLOW_OLD_VAR_OBJ_IN_DOL_EVAL')) {
12332 global $obj; // To get $obj used into list when dol_eval() is used for computed fields and $obj is not yet $objectoffield
12333 }
12334
12335 $isObBufferActive = false; // When true, the ObBuffer must be cleaned in the exception handler
12336 if ($onlysimplestring == '0') { // '0' is deprecated, we process it as the more secured '1'
12337 $onlysimplestring = '1';
12338 }
12339 if (!in_array($onlysimplestring, array('1', '2'))) {
12340 return "Bad call of dol_eval. Parameter onlysimplestring must be '1' or '2'.";
12341 }
12342 if (!is_scalar($s)) {
12343 return "Bad call of dol_eval. First parameter must be a string, found ".var_export($s, true);
12344 }
12345
12346 try {
12347 global $dolibarr_main_restrict_eval_methods;
12348
12349 // Set $dolibarr_main_restrict_eval_methods_array
12350 if (!isset($dolibarr_main_restrict_eval_methods)) {
12351 $dolibarr_main_restrict_eval_methods = 'getDolGlobalString, getDolGlobalInt, getDolCurrency, getDolEntity, getDolDBType, fetchNoCompute, hasRight, isAdmin, isExternalUser, isModEnabled, isStringVarMatching, abs, min, max, round, dol_now, preg_match';
12352 }
12353 //print '$dolibarr_main_restrict_eval_methods = '.$dolibarr_main_restrict_eval_methods."\n";
12354 $dolibarr_main_restrict_eval_methods_array = explode(',', str_replace(" ", "", $dolibarr_main_restrict_eval_methods));
12355
12356 // Test on dangerous char (used for RCE), we allow only characters to make PHP variable testing
12357 // We must accept with 1: '1 && getDolGlobalInt("doesnotexist1") && getDolGlobalString("MAIN_FEATURES_LEVEL")'
12358 // We must accept with 1: '$user->hasRight("cabinetmed", "read") && !$objectoffield->canvas == "patient@cabinetmed"'
12359 // 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"
12360
12361 // Check if there is dynamic call (first we check chars are all into a whitelist chars)
12362 $specialcharsallowed = '^$_+-.*>&|=!?():"\',/@';
12363 if ($onlysimplestring == '2') {
12364 $specialcharsallowed .= '<[]'; // Later we check that < has space before and after
12365 }
12366 global $dolibarr_main_allow_unsecured_special_chars_in_dol_eval;
12367 if (!empty($dolibarr_main_allow_unsecured_special_chars_in_dol_eval)) {
12368 $specialcharsallowed .= (string) $dolibarr_main_allow_unsecured_special_chars_in_dol_eval;
12369 }
12370 if (preg_match('/[^a-z0-9\s' . preg_quote($specialcharsallowed, '/') . ']/i', $s)) {
12371 return 'Bad string syntax to evaluate (found chars that are not chars for a simple one line clean eval string): ' . $s;
12372 }
12373
12374 // Check if we found a | without a space before and after
12375 /* Disabled to allow preg_match('/(AAA|BBB)/')
12376 $tmps = str_replace(' || ', '__XXX__', $s);
12377 if (strpos($tmps, '|') !== false) {
12378 return 'Bad string syntax to evaluate (The char | can be used only when duplicated || with a space before and after): ' . $s;
12379 }
12380 */
12381
12382 // Check if there is PHP comments (can be used to obfuscate code)
12383 if (strpos($s, '/*') !== false || strpos($s, '//') !== false) {
12384 return 'Bad string syntax to evaluate (The comment string /* and // are not allowed): ' . $s;
12385 }
12386
12387 // Check if we found a ? without a space before and after
12388 $tmps = str_replace(' ? ', '__XXX__', $s);
12389 if (strpos($tmps, '?') !== false) {
12390 return 'Bad string syntax to evaluate (The char ? can be used only with a space before and after): ' . $s;
12391 }
12392
12393 // Check if there is a < or <= without spaces after
12394 if (preg_match('/<=?[^\s]/', $s)) {
12395 return 'Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found a < or <= without space after): ' . $s;
12396 }
12397
12398 // Check if there is an include or a require
12399 if (preg_match('/(include|include_once|require|require_once)/', $s)) {
12400 return 'Bad string syntax to evaluate (found not allowed key include|include_once|require|require_once): ' . $s;
12401 }
12402
12403 // Check if there is dynamic call (first we use black list patterns)
12404 if (preg_match('/\$[\w]*\s*\‍(/', $s)) {
12405 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;
12406 }
12407
12408 if (empty($dolibarr_main_restrict_eval_methods)) {
12409 // If $dolibarr_main_restrict_eval_methods was set to '', we must check if we try dynamic call
12410
12411 // First we remove white list pattern of using parenthesis then testing if one open parenthesis exists
12412 $savescheck = '';
12413 $scheck = $s;
12414 while ($scheck && $savescheck != $scheck) {
12415 $savescheck = $scheck;
12416 $scheck = preg_replace('/->[a-zA-Z0-9_]+\‍(/', '->__METHOD__', $scheck); // accept parenthesis in '...->method(...'
12417 $scheck = preg_replace('/::[a-zA-Z0-9_]+\‍(/', '->__METHOD__', $scheck); // accept parenthesis in '...::method(...'
12418 $scheck = preg_replace('/^\‍(+/', '__PARENTHESIS__ ', $scheck); // accept parenthesis in '(...'. Must replace with "__PARENTHESIS__ with a space after "to allow following substitutions
12419 $scheck = preg_replace('/\&\&\s+\‍(/', '__ANDPARENTHESIS__ ', $scheck); // accept parenthesis in '&& ('. Must replace with "__PARENTHESIS__ with a space after" to allow following substitutions
12420 $scheck = preg_replace('/\|\|\s+\‍(/', '__ORPARENTHESIS__ ', $scheck); // accept parenthesis in '|| ('. Must replace with "__PARENTHESIS__ with a space after" to allow following substitutions
12421 $scheck = preg_replace('/^!?[a-zA-Z0-9_]+\‍(/', '__FUNCTION__', $scheck); // accept parenthesis in 'function(' and '!function('
12422 $scheck = preg_replace('/\s!?[a-zA-Z0-9_]+\‍(/', '__FUNCTION__', $scheck); // accept parenthesis in '... function(' and '... !function('
12423 $scheck = preg_replace('/^!\‍(/', '__NOTANDPARENTHESIS__', $scheck); // accept parenthesis in '!('
12424 $scheck = preg_replace('/\s!\‍(/', ' __NOTANDPARENTHESIS__', $scheck); // accept parenthesis in '... !('
12425 $scheck = preg_replace('/(\^|\')\‍(/', '__REGEXSTART__', $scheck); // To allow preg_match('/^(aaa|bbb)/'... or isStringVarMatching('leftmenu', '(aaa|bbb)')
12426 }
12427 //print 'scheck='.$scheck." : ".strpos($scheck, '(')."<br>\n";
12428
12429 // Now test if it remains 1 open parenthesis.
12430 if (strpos($scheck, '(') !== false) {
12431 return 'Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found call of a function or method without using the direct name of the function): ' . $s;
12432 }
12433 }
12434
12435 if (strpos($s, '`') !== false) {
12436 return 'Bad string syntax to evaluate (backtick char is forbidden): ' . $s;
12437 }
12438
12439 // Disallow also concat operator
12440 if (!getDolGlobalString('MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL')) {
12441 if (preg_match('/[^0-9]+\.[^0-9]+/', $s)) { // We refuse . if not between 2 numbers
12442 return 'Bad string syntax to evaluate (dot char is forbidden if not strictly between 2 numbers): ' . $s;
12443 }
12444 }
12445
12446 // We exclude string using a $ character that are not an expected global or temporary vars, so that are not:
12447 // $db, $langs, $leftmenu, $topmenu, $user, $langs, $objectoffield, $var....
12448 $savescheck = '';
12449 $scheck = $s;
12450 while ($scheck && $savescheck != $scheck) {
12451 $savescheck = $scheck;
12452 $scheck = preg_replace('/\$conf->[a-z\_]+->enabled/', '__VARCONFENABLED__', $scheck); // Remove this once $user->module->enabled has been replaced everywhere with isModEnabled.
12453 $scheck = preg_replace('/\$user->id/', '__VARUSERID__', $scheck);
12454 $scheck = preg_replace('/\$user->hasRight/', '__VARUSERHASRIGHT__', $scheck);
12455 $scheck = preg_replace('/\$user->rights/', '__VARUSERHASRIGHT__', $scheck); // Remove this once $user->rights->xxx is replaced everywhere with $user->hasRight()
12456 $scheck = preg_replace('/\$user->isAdmin/', '__VARUSERHASRIGHT__', $scheck);
12457 $scheck = preg_replace('/\$user->admin/', '__VARUSERISADMIN__', $scheck); // Remove this once $user->admin is replaced everywhere with $user->isAdmin()
12458 $scheck = preg_replace('/\$user->isExternalUser/', '__VARUSERSOCID__', $scheck);
12459 $scheck = preg_replace('/\$user->socid/', '__VARUSERSOCID__', $scheck); // Remove this once $user->admin is replaced everywhere with $user->isExternalUser()
12460 $scheck = preg_replace('/\‍(\$db\‍)/', '__VARDB__', $scheck);
12461 $scheck = preg_replace('/\$langs/', '__VARLANGSTRANS__', $scheck);
12462 $scheck = preg_replace('/\$mysoc/', '__VARMYSOC__', $scheck);
12463 $scheck = preg_replace('/\$action/', '__VARACTION__', $scheck);
12464 $scheck = preg_replace('/\$mainmenu/', '__VARMAINMENU__', $scheck); // Remove this once all tests on $mainmenu has been replaced with isStringVarMatching
12465 $scheck = preg_replace('/\$leftmenu/', '__VARLEFTMENU__', $scheck); // Remove this once all tests on $mainmenu has been replaced with isStringVarMatching
12466 $scheck = preg_replace('/\$websitepage/', '__VARWEBSITEPAGE__', $scheck);
12467 $scheck = preg_replace('/\$website/', '__VARWEBSITE__', $scheck);
12468 $scheck = preg_replace('/\$objectoffield/', '__VAROBJECTOFFIELD__', $scheck);
12469 $scheck = preg_replace('/\$object/', '__VAROBJECT__', $scheck);
12470 $scheck = preg_replace('/\$var/', '__VARVAR__', $scheck);
12471
12472 // deprecated (now we use $objecf->canvas or $objectoffield->canvas)
12473 $scheck = preg_replace('/\$soc->canvas/', '__VARSOCCANVAS__', $scheck);
12474 $scheck = preg_replace('/\$obj->canvas/', '__VAROBJCANVAS__', $scheck);
12475
12476 // Now test if it remains one '$'
12477 if (strpos($scheck, '$') !== false) {
12478 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);
12479 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;
12480 }
12481 }
12482
12483 // We block use of php exec or php file functions
12484 $forbiddenphpstrings = array('_ENV', '_SESSION', '_COOKIE', '_GET', '_GLOBAL', '_POST', '_REQUEST', 'ReflectionFunction', 'SplFileObject', 'SplTempFileObject');
12485
12486 if (empty($dolibarr_main_restrict_eval_methods)) { // If forced to ''
12487 // 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)
12488 // 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
12489 // like we can do with array_map and its callable parameter: dol_eval('json_encode(array_map(implode("",["ex","ec"]), ["id"]))', 1, 1, '0')
12490 $forbiddenphpfunctions = array();
12491 $forbiddenphpmethods = array();
12492
12493 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("override_function", "session_id", "session_create_id", "session_regenerate_id"));
12494 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("get_defined_functions", "get_defined_vars", "get_defined_constants", "get_declared_classes"));
12495 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("function"));
12496
12497 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("GETPOST")); // native dolibarr functions
12498
12499 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("ob_start"));
12500
12501 // Functions with callable parameters
12502 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("call_user_func", "call_user_func_array"));
12503 $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"));
12504 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("usort", "uasort", "uksort"));
12505 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("preg_replace_callback", "preg_replace_callback_array", "header_register_callback"));
12506 $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"));
12507 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("spl_autoload_register", "spl_autoload_unregister", "iterator_apply", "session_set_save_handler"));
12508 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("forward_static_call", "forward_static_call_array", "register_postsend_function"));
12509 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("readline_completion_function", "readline_callback_handler_install"));
12510
12511 // Exec functions
12512 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("exec", "passthru", "shell_exec", "system", "proc_open", "popen"));
12513 $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"));
12514 $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", ));
12515 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("putenv", "dl", "apache_child_terminate", "apache_setenv"));
12516 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_eval", "dol_eval_new", "dol_eval_standard", "executeCLI", "verifCond", "dolEncrypt", "dolDecrypt")); // native dolibarr functions
12517 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("eval", "create_function", "assert", "mb_ereg_replace")); // function with eval capabilities
12518
12519 // Include functions
12520 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("require", "include", "require_once", "include_once"));
12521
12522 $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
12523 $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"));
12524
12525 if (!getDolGlobalString('MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL')) { // We disallow all function that allow to obfuscate the real name of a function
12526 // @phpcs:ignore
12527 $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
12528 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_concat", "dol_concatdesc")); // native dolibarr functions
12529 }
12530 // Remove from blacklist the function that are into the whitelist
12531 /*foreach ($forbiddenphpfunctions as $key => $forbiddenphpfunction) {
12532 if (in_array($forbiddenphpfunction, $dolibarr_main_restrict_eval_methods_array)) {
12533 unset($forbiddenphpfunctions[$key]);
12534 }
12535 }*/
12536
12537 $forbiddenphpmethods = array_merge($forbiddenphpmethods, array('invoke', 'invokeArgs')); // Methods of ReflectionFunction to execute a function
12538 // Remove from blacklist the function that are into the whitelist
12539 /*foreach ($forbiddenphpmethods as $key => $forbiddenphpmethod) {
12540 if (in_array($forbiddenphpmethod, $dolibarr_main_restrict_eval_methods_array)) {
12541 unset($forbiddenphpmethods[$key]);
12542 }
12543 }*/
12544
12545 $forbiddenphpregex = 'global\s*\$';
12546 $forbiddenphpregex .= '|posix_[a-zA-Z0-9_]*|'; // All posix functions
12547 $forbiddenphpregex .= '\b(' . implode('|', $forbiddenphpfunctions) . ')\b';
12548
12549 $forbiddenphpmethodsregex = '->(' . implode('|', $forbiddenphpmethods) . ')';
12550
12551 // Now scan all forbidden patterns
12552 do {
12553 $oldstringtoclean = $s;
12554 $s = str_ireplace($forbiddenphpstrings, '__forbiddenstring__', $s);
12555 $s = preg_replace('/' . $forbiddenphpregex . '/i', '__forbiddenstring__', $s);
12556 $s = preg_replace('/' . $forbiddenphpmethodsregex . '/i', '__forbiddenstring__', $s);
12557 //$s = preg_replace('/\$[a-zA-Z0-9_\->\$]+\‍(/i', '', $s); // Remove $function( call and $mycall->mymethod(
12558 } while ($oldstringtoclean != $s);
12559
12560 if (strpos($s, '__forbiddenstring__') !== false) {
12561 dol_syslog('Bad string syntax to evaluate: ' . $s, LOG_WARNING);
12562 return 'Bad string syntax to evaluate: ' . $s;
12563 }
12564 }
12565
12566 if (!empty($dolibarr_main_restrict_eval_methods)) {
12567 // Accept only white-listed allowed function and classes
12568 // TODO Get all pattern '/([\s\w]+)\‍(/', then check that $matches[1] is a defined class or a function into a given list
12569 $pattern = '/([\s\w\'\]\"]+)\‍(/';
12570
12571 $matches = array();
12572 preg_match_all($pattern, $s, $matches);
12573
12574 if (count($matches)) {
12575 foreach ($matches[1] as $m) {
12576 $m = trim($m);
12577 if (empty($m)) {
12578 continue;
12579 }
12580 $reg = array();
12581 if (!preg_match('/new ([A-Z][\w]+)/i', $m, $reg)) {
12582 if (!in_array($m, $dolibarr_main_restrict_eval_methods_array)) {
12583 if ($m != "'" && $m != '"') {
12584 dol_syslog('Bad string syntax to evaluate: ' . $s, LOG_WARNING);
12585 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;
12586 }
12587 }
12588 } else {
12589 if (!class_exists($reg[1])) {
12590 dol_syslog('Bad string syntax to evaluate: Class "'.$reg[1].'" does not exist. ' . $s, LOG_WARNING);
12591 return 'Bad string syntax to evaluate. Class "'.$reg[1].'" does not exist. ' . $s;
12592 }
12593 $parents = class_parents($reg[1]); // Get list of parent classes of class we want to check
12594 if (!in_array('CommonObject', $parents)) { // Only classes that inherit CommonObject are ok. This forbid dangerous classes like ReflectionFunction, SplFileObject, ...
12595 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);
12596 return 'Bad string syntax to evaluate. Class "'.$reg[1].'" is not allowed because only classes extended CommonObject can be used in dynamic evaluation. ' . $s;
12597 }
12598 }
12599 }
12600 }
12601
12602 $forbiddenphpregex = 'global\s*\$';
12603 $forbiddenphpregex .= '|'; // or
12604 $forbiddenphpregex .= '}\s*\[';
12605 $forbiddenphpregex .= '|'; // or
12606 $forbiddenphpregex .= '\‍)\s*\‍(';
12607
12608 // Now scan all forbidden patterns
12609 do {
12610 $oldstringtoclean = $s;
12611 $s = str_ireplace($forbiddenphpstrings, '__forbiddenstring__', $s);
12612 $s = preg_replace('/' . $forbiddenphpregex . '/i', '__forbiddenstring__', $s);
12613 //$s = preg_replace('/' . $forbiddenphpmethodsregex . '/i', '__forbiddenstring__', $s);
12614 //$s = preg_replace('/\$[a-zA-Z0-9_\->\$]+\‍(/i', '', $s); // Remove $function( call and $mycall->mymethod(
12615 } while ($oldstringtoclean != $s);
12616
12617 if (strpos($s, '__forbiddenstring__') !== false) {
12618 dol_syslog('Bad string syntax to evaluate: ' . $s, LOG_WARNING);
12619 return 'Bad string syntax to evaluate: ' . $s;
12620 }
12621 }
12622
12623 //print $s."<br>\n";
12624 ob_start(); // An evaluation has no reason to output data
12625 $isObBufferActive = true;
12626 $tmps = $hideerrors ? @eval('return ' . $s . ';') : eval('return ' . $s . ';');
12627 $tmpo = ob_get_clean(); // This close the buffer
12628 $isObBufferActive = false;
12629 if ($tmpo) {
12630 print 'Bad string syntax to evaluate. Some data were output when it should not when evaluating: ' . $s;
12631 }
12632 return $tmps;
12633 } catch (Exception $e) {
12634 if ($isObBufferActive) {
12635 // Clean up buffer which was left behind due to exception.
12636 $tmpo = ob_get_clean(); // This close the buffer
12637 $isObBufferActive = false;
12638 }
12639 $error = 'dol_eval try/catch error for string: ' . $s . ' - Error: ';
12640 $error .= $e->getMessage();
12641 dol_syslog($error, LOG_WARNING);
12642 return 'Exception during evaluation: ' . $s;
12643 } catch (Error $e) {
12644 if ($isObBufferActive) {
12645 // Clean up buffer which was left behind due to exception.
12646 $tmpo = ob_get_clean(); // This close the buffer
12647 $isObBufferActive = false;
12648 }
12649 $error = 'dol_eval try/catch error for string: ' . $s . ' - Error: ';
12650 $error .= $e->getMessage();
12651 dol_syslog($error, LOG_WARNING);
12652 return 'Exception during evaluation: ' . $s;
12653 }
12654}
12655
12663function dol_validElement($element)
12664{
12665 return (trim($element) != '');
12666}
12667
12676function picto_from_langcode($codelang, $moreatt = '', $notitlealt = 0)
12677{
12678 if (empty($codelang)) {
12679 return '';
12680 }
12681
12682 if ($codelang == 'auto') {
12683 return '<span class="fa fa-language"></span>';
12684 }
12685
12686 $langtocountryflag = array(
12687 'ar_AR' => '',
12688 'ca_ES' => 'catalonia',
12689 'da_DA' => 'dk',
12690 'fr_CA' => 'mq',
12691 'sv_SV' => 'se',
12692 'sw_SW' => 'unknown',
12693 'AQ' => 'unknown',
12694 'CW' => 'unknown',
12695 'IM' => 'unknown',
12696 'JE' => 'unknown',
12697 'MF' => 'unknown',
12698 'BL' => 'unknown',
12699 'SX' => 'unknown'
12700 );
12701
12702 if (isset($langtocountryflag[$codelang])) {
12703 $flagImage = $langtocountryflag[$codelang];
12704 } else {
12705 $tmparray = explode('_', $codelang);
12706 $flagImage = empty($tmparray[1]) ? $tmparray[0] : $tmparray[1];
12707 }
12708
12709 $morecss = '';
12710 $reg = array();
12711 if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
12712 $morecss = $reg[1];
12713 $moreatt = "";
12714 }
12715
12716 // return img_picto_common($codelang, 'flags/'.strtolower($flagImage).'.png', $moreatt, 0, $notitlealt);
12717 return '<span class="flag-sprite ' . strtolower($flagImage) . ($morecss ? ' ' . $morecss : '') . '"' . ($moreatt ? ' ' . $moreatt : '') . (!$notitlealt ? ' title="' . $codelang . '"' : '') . '></span>';
12718}
12719
12727function getLanguageCodeFromCountryCode($countrycode)
12728{
12729 global $mysoc;
12730
12731 if (empty($countrycode)) {
12732 return null;
12733 }
12734
12735 if (strtoupper($countrycode) == 'MQ') {
12736 return 'fr_CA';
12737 }
12738 if (strtoupper($countrycode) == 'SE') {
12739 return 'sv_SE'; // se_SE is Sami/Sweden, and we want in priority sv_SE for SE country
12740 }
12741 if (strtoupper($countrycode) == 'CH') {
12742 if ($mysoc->country_code == 'FR') {
12743 return 'fr_CH';
12744 }
12745 if ($mysoc->country_code == 'DE') {
12746 return 'de_CH';
12747 }
12748 if ($mysoc->country_code == 'IT') {
12749 return 'it_CH';
12750 }
12751 }
12752
12753 // Locale list taken from:
12754 // http://stackoverflow.com/questions/3191664/
12755 // list-of-all-locales-and-their-short-codes
12756 $locales = array(
12757 'af-ZA',
12758 'am-ET',
12759 'ar-AE',
12760 'ar-BH',
12761 'ar-DZ',
12762 'ar-EG',
12763 'ar-IQ',
12764 'ar-JO',
12765 'ar-KW',
12766 'ar-LB',
12767 'ar-LY',
12768 'ar-MA',
12769 'ar-OM',
12770 'ar-QA',
12771 'ar-SA',
12772 'ar-SY',
12773 'ar-TN',
12774 'ar-YE',
12775 //'as-IN', // Moved after en-IN
12776 'ba-RU',
12777 'be-BY',
12778 'bg-BG',
12779 'bn-BD',
12780 //'bn-IN', // Moved after en-IN
12781 'bo-CN',
12782 'br-FR',
12783 'ca-ES',
12784 'co-FR',
12785 'cs-CZ',
12786 'cy-GB',
12787 'da-DK',
12788 'de-AT',
12789 'de-CH',
12790 'de-DE',
12791 'de-LI',
12792 'de-LU',
12793 'dv-MV',
12794 'el-GR',
12795 'en-AU',
12796 'en-BZ',
12797 'en-CA',
12798 'en-GB',
12799 'en-IE',
12800 'en-IN',
12801 'as-IN', // as-IN must be after en-IN (en in priority if country is IN)
12802 'bn-IN', // bn-IN must be after en-IN (en in priority if country is IN)
12803 'en-JM',
12804 'en-MY',
12805 'en-NZ',
12806 'en-PH',
12807 'en-SG',
12808 'en-TT',
12809 'en-US',
12810 'en-ZA',
12811 'en-ZW',
12812 'es-AR',
12813 'es-BO',
12814 'es-CL',
12815 'es-CO',
12816 'es-CR',
12817 'es-DO',
12818 'es-EC',
12819 'es-ES',
12820 'es-GT',
12821 'es-HN',
12822 'es-MX',
12823 'es-NI',
12824 'es-PA',
12825 'es-PE',
12826 'es-PR',
12827 'es-PY',
12828 'es-SV',
12829 'es-US',
12830 'es-UY',
12831 'es-VE',
12832 'et-EE',
12833 'eu-ES',
12834 'fa-IR',
12835 'fi-FI',
12836 'fo-FO',
12837 'fr-BE',
12838 'fr-CA',
12839 'fr-CH',
12840 'fr-FR',
12841 'fr-LU',
12842 'fr-MC',
12843 'fy-NL',
12844 'ga-IE',
12845 'gd-GB',
12846 'gl-ES',
12847 'gu-IN',
12848 'he-IL',
12849 'hi-IN',
12850 'hr-BA',
12851 'hr-HR',
12852 'hu-HU',
12853 'hy-AM',
12854 'id-ID',
12855 'ig-NG',
12856 'ii-CN',
12857 'is-IS',
12858 'it-CH',
12859 'it-IT',
12860 'ja-JP',
12861 'ka-GE',
12862 'kk-KZ',
12863 'kl-GL',
12864 'km-KH',
12865 'kn-IN',
12866 'ko-KR',
12867 'ky-KG',
12868 'lb-LU',
12869 'lo-LA',
12870 'lt-LT',
12871 'lv-LV',
12872 'mi-NZ',
12873 'mk-MK',
12874 'ml-IN',
12875 'mn-MN',
12876 'mr-IN',
12877 'ms-BN',
12878 'ms-MY',
12879 'mt-MT',
12880 'nb-NO',
12881 'ne-NP',
12882 'nl-BE',
12883 'nl-NL',
12884 'nn-NO',
12885 'oc-FR',
12886 'or-IN',
12887 'pa-IN',
12888 'pl-PL',
12889 'ps-AF',
12890 'pt-BR',
12891 'pt-PT',
12892 'rm-CH',
12893 'ro-MD',
12894 'ro-RO',
12895 'ru-RU',
12896 'rw-RW',
12897 'sa-IN',
12898 'se-FI',
12899 'se-NO',
12900 'se-SE',
12901 'si-LK',
12902 'sk-SK',
12903 'sl-SI',
12904 'sq-AL',
12905 'sv-FI',
12906 'sv-SE',
12907 'sw-KE',
12908 'ta-IN',
12909 'te-IN',
12910 'th-TH',
12911 'tk-TM',
12912 'tn-ZA',
12913 'tr-TR',
12914 'tt-RU',
12915 'ug-CN',
12916 'uk-UA',
12917 'ur-PK',
12918 'vi-VN',
12919 'wo-SN',
12920 'xh-ZA',
12921 'yo-NG',
12922 'zh-CN',
12923 'zh-HK',
12924 'zh-MO',
12925 'zh-SG',
12926 'zh-TW',
12927 'zu-ZA',
12928 );
12929
12930 $buildprimarykeytotest = strtolower($countrycode) . '-' . strtoupper($countrycode);
12931 if (in_array($buildprimarykeytotest, $locales)) {
12932 return strtolower($countrycode) . '_' . strtoupper($countrycode);
12933 }
12934
12935 if (function_exists('locale_get_primary_language') && function_exists('locale_get_region')) { // Need extension php-intl
12936 foreach ($locales as $locale) {
12937 $locale_language = locale_get_primary_language($locale);
12938 $locale_region = locale_get_region($locale);
12939 if (strtoupper($countrycode) == $locale_region) {
12940 //var_dump($locale.' - '.$locale_language.' - '.$locale_region);
12941 return strtolower($locale_language) . '_' . strtoupper($locale_region);
12942 }
12943 }
12944 } else {
12945 dol_syslog("Warning Extension php-intl is not available", LOG_WARNING);
12946 }
12947
12948 return null;
12949}
12950
12981function complete_head_from_modules($conf, $langs, $object, &$head, &$h, $type, $mode = 'add', $filterorigmodule = '')
12982{
12983 global $hookmanager, $db;
12984
12985 if (isset($conf->modules_parts['tabs'][$type]) && is_array($conf->modules_parts['tabs'][$type])) {
12986 foreach ($conf->modules_parts['tabs'][$type] as $value) {
12987 $values = explode(':', $value);
12988
12989 $reg = array();
12990 if ($mode == 'add' && !preg_match('/^\-/', $values[1])) {
12991 if (count($values) !== 6) {
12992 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);
12993 continue;
12994 }
12995
12996 // new declaration with permissions:
12997 // $value='objecttype:+tabname1:Title1:langfile@mymodule:$user->rights->mymodule->read:/mymodule/mynewtab1.php?id=__ID__'
12998 // $value='objecttype:+tabname1:Title1,class,pathfile,method:langfile@mymodule:$user->rights->mymodule->read:/mymodule/mynewtab1.php?id=__ID__'
12999 if ($values[0] != $type) {
13000 continue;
13001 }
13002
13003 $newtab = array();
13004 $postab = $h;
13005 // detect if position set in $values[1] ie : +(2)mytab@mymodule (first tab is 0, second is one, ...)
13006 $str = $values[1];
13007 $posstart = strpos($str, '(');
13008 if ($posstart > 0) {
13009 $posend = strpos($str, ')');
13010 if ($posstart > 0) {
13011 $res1 = substr($str, $posstart + 1, $posend - $posstart - 1);
13012 if (is_numeric($res1)) {
13013 $postab = (int) $res1;
13014 $values[1] = '+' . substr($str, $posend + 1);
13015 }
13016 }
13017 }
13018
13019 global $objectoffield; // So we can use $objectoffield int verifCond
13020 $objectoffield = $object;
13021
13022 if (!verifCond($values[4], '2')) {
13023 continue;
13024 }
13025
13026 if ($values[3]) {
13027 if ($filterorigmodule) { // If a filter of module origin has been requested
13028 if (strpos($values[3], '@')) { // This is an external module
13029 if ($filterorigmodule != 'external') {
13030 continue;
13031 }
13032 } else { // This looks a core module
13033 if ($filterorigmodule != 'core') {
13034 continue;
13035 }
13036 }
13037 }
13038 $langs->load($values[3]);
13039 }
13040
13041 if (preg_match('/SUBSTITUTION_([^_]+)/i', $values[2], $reg)) {
13042 // If label is "SUBSTITUION_..."
13043 $substitutionarray = array();
13044 complete_substitutions_array($substitutionarray, $langs, $object, array('needforkey' => $values[2]));
13045 $label = make_substitutions($reg[1], $substitutionarray);
13046 } else {
13047 // If label is "Label,Class,File,Method", we call the method to show content inside the badge
13048 $labeltemp = explode(',', $values[2]);
13049 $label = $langs->trans($labeltemp[0]);
13050
13051 if (!empty($labeltemp[1]) && is_object($object) && !empty($object->id)) {
13052 dol_include_once($labeltemp[2]);
13053 $classtoload = $labeltemp[1];
13054 if (class_exists($classtoload)) {
13055 $obj = new $classtoload($db);
13056 $function = $labeltemp[3];
13057 if ($obj && $function && method_exists($obj, $function)) {
13058 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
13059 $nbrec = $obj->$function($object->id, $obj);
13060 if (!empty($nbrec)) {
13061 $label .= '<span class="badge marginleftonlyshort">' . $nbrec . '</span>';
13062 }
13063 }
13064 }
13065 }
13066 }
13067 $url = preg_replace('/__ID__/i', ((is_object($object) && !empty($object->id)) ? $object->id : ''), $values[5]);
13068 $link = parse_url($url);
13069 $query = [];
13070 if (isset($link['query'])) {
13071 parse_str($link['query'], $query);
13072 }
13073 $newtab[0] = dolBuildUrl(dol_buildpath($link['path'], 1), $query);
13074 $newtab[1] = $label;
13075 $newtab[2] = str_replace('+', '', $values[1]);
13076 $h++;
13077
13078 // set tab at its position
13079 $head = array_merge(array_slice($head, 0, $postab), array($newtab), array_slice($head, $postab));
13080 } elseif ($mode == 'remove' && preg_match('/^\-/', $values[1])) {
13081 if ($values[0] != $type) {
13082 continue;
13083 }
13084 $tabname = str_replace('-', '', $values[1]);
13085 foreach ($head as $key => $val) {
13086 $condition = (!empty($values[3]) ? verifCond($values[3], '2') : 1);
13087 //var_dump($key.' - '.$tabname.' - '.$head[$key][2].' - '.$values[3].' - '.$condition);
13088 if ($head[$key][2] == $tabname && $condition) {
13089 unset($head[$key]);
13090 break;
13091 }
13092 }
13093 }
13094 }
13095 }
13096
13097 // No need to make a return $head. Var is modified as a reference
13098 if (!empty($hookmanager)) {
13099 $parameters = array('object' => $object, 'mode' => $mode, 'head' => &$head, 'filterorigmodule' => $filterorigmodule, 'type' => $type);
13100 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
13101 $reshook = $hookmanager->executeHooks('completeTabsHead', $parameters, $object);
13102 if ($reshook > 0) { // Hook ask to replace completely the array
13103 $head = $hookmanager->resArray;
13104 } else { // Hook
13105 $head = array_merge($head, $hookmanager->resArray);
13106 }
13107 $h = count($head);
13108 }
13109}
13110
13122function printCommonFooter($zone = 'private')
13123{
13124 global $conf, $hookmanager, $user, $langs;
13125 global $action;
13126 global $micro_start_time;
13127
13128 if ($zone == 'private') {
13129 print "\n" . '<!-- Common footer for private page -->' . "\n";
13130 } else {
13131 print "\n" . '<!-- Common footer for public page -->' . "\n";
13132 }
13133
13134 // A div to store page_y POST parameter so we can read it using javascript
13135 print "\n<!-- A div to store page_y POST parameter -->\n";
13136 print '<div id="page_y" style="display: none;">' . (GETPOST('page_y') ? GETPOST('page_y') : '') . '</div>' . "\n";
13137
13138 $parameters = array('zone' => $zone);
13139 $tmpobject = null;
13140 // @phan-suppress-next-line PhanPluginConstantVariableNull
13141 $reshook = $hookmanager->executeHooks('printCommonFooter', $parameters, $tmpobject, $action); // Note that $action and $object may have been modified by some hooks
13142 if (empty($reshook)) {
13143 if (getDolGlobalString('MAIN_HTML_FOOTER')) {
13144 print getDolGlobalString('MAIN_HTML_FOOTER') . "\n";
13145 }
13146
13147 print "\n";
13148 if (!empty($conf->use_javascript_ajax)) {
13149 print "\n<!-- A script section to add menuhider handler on backoffice, manage focus and mandatory fields, tuning info, ... -->\n";
13150 print '<script>' . "\n";
13151 print 'jQuery(document).ready(function() {' . "\n";
13152
13153 if ($zone == 'private' && empty($conf->dol_use_jmobile)) {
13154 print "\n";
13155 print '/* JS CODE TO ENABLE to manage handler to switch left menu page (menuhider) */' . "\n";
13156 print 'jQuery("li.menuhider").click(function(event) {';
13157 print ' if (!$( "body" ).hasClass( "sidebar-collapse" )){ event.preventDefault(); }' . "\n";
13158 print ' console.log("We click on .menuhider");' . "\n";
13159 print ' $("body").toggleClass("sidebar-collapse")' . "\n";
13160 print '});' . "\n";
13161 }
13162
13163 // Management of focus and mandatory for fields
13164 if ($action == 'create' || $action == 'add' || $action == 'edit' || (empty($action) && (preg_match('/new\.php/', $_SERVER["PHP_SELF"]))) || ((empty($action) || $action == 'addline') && (preg_match('/card\.php/', $_SERVER["PHP_SELF"])))) {
13165 print '/* JS CODE TO ENABLE to manage focus and mandatory form fields */' . "\n";
13166 $relativepathstring = $_SERVER["PHP_SELF"];
13167 // Clean $relativepathstring
13168 if (constant('DOL_URL_ROOT')) {
13169 $relativepathstring = preg_replace('/^' . preg_quote(constant('DOL_URL_ROOT'), '/') . '/', '', $relativepathstring);
13170 }
13171 $relativepathstring = preg_replace('/^\//', '', $relativepathstring);
13172 $relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
13173 //$tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
13174
13175 if (!empty($user->default_values[$relativepathstring]['focus'])) {
13176 foreach ($user->default_values[$relativepathstring]['focus'] as $defkey => $defval) {
13177 $qualified = 0;
13178 if ($defkey != '_noquery_') {
13179 $tmpqueryarraytohave = explode('&', $defkey);
13180 $foundintru = 0;
13181 foreach ($tmpqueryarraytohave as $tmpquerytohave) {
13182 $tmpquerytohaveparam = explode('=', $tmpquerytohave);
13183 //print "console.log('".$tmpquerytohaveparam[0]." ".$tmpquerytohaveparam[1]." ".GETPOST($tmpquerytohaveparam[0])."');";
13184 if (!GETPOSTISSET($tmpquerytohaveparam[0]) || ($tmpquerytohaveparam[1] != GETPOST($tmpquerytohaveparam[0]))) {
13185 $foundintru = 1;
13186 }
13187 }
13188 if (!$foundintru) {
13189 $qualified = 1;
13190 }
13191 //var_dump($defkey.'-'.$qualified);
13192 } else {
13193 $qualified = 1;
13194 }
13195
13196 if ($qualified) {
13197 print 'console.log("set the focus by executing jQuery(...).focus();")' . "\n";
13198 foreach ($defval as $paramkey => $paramval) {
13199 // Set focus on field
13200 print 'jQuery("input[name=\'' . $paramkey . '\']").focus();' . "\n";
13201 print 'jQuery("textarea[name=\'' . $paramkey . '\']").focus();' . "\n"; // TODO KO with ckeditor
13202 print 'jQuery("select[name=\'' . $paramkey . '\']").focus();' . "\n"; // Not really useful, but we keep it in case of.
13203 }
13204 }
13205 }
13206 }
13207 if (!empty($user->default_values[$relativepathstring]['mandatory'])) {
13208 foreach ($user->default_values[$relativepathstring]['mandatory'] as $defkey => $defval) {
13209 $qualified = 0;
13210 if ($defkey != '_noquery_') {
13211 $tmpqueryarraytohave = explode('&', $defkey);
13212 $foundintru = 0;
13213 foreach ($tmpqueryarraytohave as $tmpquerytohave) {
13214 $tmpquerytohaveparam = explode('=', $tmpquerytohave);
13215 //print "console.log('".$tmpquerytohaveparam[0]." ".$tmpquerytohaveparam[1]." ".GETPOST($tmpquerytohaveparam[0])."');";
13216 if (!GETPOSTISSET($tmpquerytohaveparam[0]) || ($tmpquerytohaveparam[1] != GETPOST($tmpquerytohaveparam[0]))) {
13217 $foundintru = 1;
13218 }
13219 }
13220 if (!$foundintru) {
13221 $qualified = 1;
13222 }
13223 //var_dump($defkey.'-'.$qualified);
13224 } else {
13225 $qualified = 1;
13226 }
13227
13228 if ($qualified) {
13229 print 'console.log("set the js code to manage fields that are set as mandatory");' . "\n";
13230
13231 foreach ($defval as $paramkey => $paramval) {
13232 // Solution 1: Add handler on submit to check if mandatory fields are empty
13233 print 'var form = $(\'[name="'.dol_escape_js($paramkey).'"]\').closest("form");'."\n";
13234 print "form.on('submit', function(event) {
13235 var submitter = \$(this).find(':submit:focus').get(0);
13236 var buttonName = submitter ? \$(submitter).attr('name') : 'save';
13237
13238 if (buttonName == 'cancel') {
13239 console.log('We click on cancel button so we accept submit with no need to check mandatory fields');
13240 return true;
13241 }
13242
13243 console.log('We did not click on cancel button but on something else, we check that field [name=".dol_escape_js($paramkey)."] is not empty');
13244
13245 var tmpvalue = jQuery('[name=\"".dol_escape_js($paramkey)."\"]').val();
13246 let tmptypefield = jQuery('[name=\"".dol_escape_js($paramkey)."\"]').prop('nodeName').toLowerCase(); // Get the tag name (div, section, footer...)
13247
13248 if (tmptypefield == 'textarea') {
13249 // We must instead check the content of ckeditor
13250 var tmpeditor = (typeof CKEDITOR !== 'undefined') ? CKEDITOR.instances['".dol_escape_js($paramkey)."'] : null;
13251 if (tmpeditor) {
13252 tmpvalue = tmpeditor.getData();
13253 console.log('For textarea tmpvalue is '+tmpvalue);
13254 }
13255 }
13256
13257 let tmpvalueisempty = false;
13258 if (tmpvalue === null || tmpvalue === undefined || tmpvalue === '' || tmpvalue === -1 || tmpvalue === '-1') {
13259 tmpvalueisempty = true;
13260 }
13261 if (tmpvalue === '0' && (tmptypefield == 'select' || tmptypefield == 'input')) {
13262 tmpvalueisempty = true;
13263 }
13264 if (tmpvalueisempty && buttonName !== 'cancel') {
13265 console.log('field has type '+tmptypefield+' and is empty, we cancel the submit');
13266 event.preventDefault(); // Stop submission of form to allow custom code to decide.
13267 event.stopPropagation(); // Stop other handlers.
13268
13269 alert('".dol_escape_js($langs->transnoentitiesnoconv("ErrorFieldRequired", $paramkey).' ('.$langs->transnoentitiesnoconv("CustomMandatoryFieldRule").')')."');
13270
13271 return false;
13272 }
13273 console.log('field has type '+tmptypefield+' and is defined to '+tmpvalue);
13274 return true;
13275 });
13276 \n";
13277
13278 // Solution 2: Add property 'required' on input
13279 // so browser will check value and try to focus on it when submitting the form.
13280 //print 'setTimeout(function() {'; // If we want to wait that ckeditor beuatifier has finished its job.
13281 //print 'jQuery("input[name=\''.$paramkey.'\']").prop(\'required\',true);'."\n";
13282 //print 'jQuery("textarea[id=\''.$paramkey.'\']").prop(\'required\',true);'."\n";
13283 //print 'jQuery("select[name=\''.$paramkey.'\']").prop(\'required\',true);'."\n";*/
13284 //print '// required on a select works only if key is "", so we add the required attributes but also we reset the key -1 or 0 to an empty string'."\n";
13285 //print 'jQuery("select[name=\''.$paramkey.'\'] option[value=\'-1\']").prop(\'value\', \'\');'."\n";
13286 //print 'jQuery("select[name=\''.$paramkey.'\'] option[value=\'0\']").prop(\'value\', \'\');'."\n";
13287 // Add 'field required' class on closest td for all input elements : input, textarea and select
13288 //print '}, 500);'; // 500 milliseconds delay
13289
13290 // Now set the class "fieldrequired"
13291 print 'jQuery(\':input[name="' . dol_escape_js($paramkey) . '"]\').closest("tr").find("td:first").addClass("fieldrequired");' . "\n";
13292 }
13293
13294 // If we submit using the cancel button, we remove the required attributes
13295 print 'jQuery("input[name=\'cancel\']").click(function() {
13296 console.log("We click on cancel button so removed all required attribute");
13297 jQuery("input, textarea, select").each(function(){this.removeAttribute(\'required\');});
13298 });' . "\n";
13299 }
13300 }
13301 }
13302 }
13303
13304 print '});' . "\n";
13305
13306 // End of tuning
13307 if (!empty($_SERVER['MAIN_SHOW_TUNING_INFO']) || getDolGlobalString('MAIN_SHOW_TUNING_INFO')) {
13308 print "\n";
13309 print "/* JS CODE TO ENABLE to add memory info */\n";
13310 print 'window.console && console.log("';
13311 if (getDolGlobalString('MEMCACHED_SERVER')) {
13312 print 'MEMCACHED_SERVER=' . getDolGlobalString('MEMCACHED_SERVER') . ' - ';
13313 }
13314 print 'MAIN_OPTIMIZE_SPEED=' . getDolGlobalString('MAIN_OPTIMIZE_SPEED', 'off');
13315 if (!empty($micro_start_time)) { // Works only if MAIN_SHOW_TUNING_INFO is defined at $_SERVER level. Not in global variable.
13316 $micro_end_time = microtime(true);
13317 print ' - Build time: ' . ceil(1000 * ($micro_end_time - $micro_start_time)) . ' ms';
13318 }
13319
13320 if (function_exists("memory_get_usage")) {
13321 print ' - Mem: ' . memory_get_usage(); // Do not use true here, it seems it takes the peak amount
13322 }
13323 if (function_exists("memory_get_peak_usage")) {
13324 print ' - Real mem peak: ' . memory_get_peak_usage(true);
13325 }
13326 if (function_exists("zend_loader_file_encoded")) {
13327 print ' - Zend encoded file: ' . (zend_loader_file_encoded() ? 'yes' : 'no');
13328 }
13329 print '");' . "\n";
13330 }
13331
13332 print "\n" . '</script>' . "\n";
13333
13334 // Google Analytics
13335 // TODO Remove this, can be replaced with the hook printCommonFooter
13336 if (isModEnabled('google') && getDolGlobalString('MAIN_GOOGLE_AN_ID')) {
13337 $tmptagarray = explode(',', getDolGlobalString('MAIN_GOOGLE_AN_ID'));
13338 foreach ($tmptagarray as $tmptag) {
13339 print "\n";
13340 print "<!-- JS CODE TO ENABLE for google analtics tag -->\n";
13341 print '
13342 <!-- Global site tag (gtag.js) - Google Analytics -->
13343 <script nonce="' . getNonce() . '" async src="https://www.googletagmanager.com/gtag/js?id=' . trim($tmptag) . '"></script>
13344 <script>
13345 window.dataLayer = window.dataLayer || [];
13346 function gtag(){dataLayer.push(arguments);}
13347 gtag(\'js\', new Date());
13348
13349 gtag(\'config\', \'' . trim($tmptag) . '\');
13350 </script>';
13351 print "\n";
13352 }
13353 }
13354 }
13355
13356 // Add Xdebug coverage of code
13357 if (defined('XDEBUGCOVERAGE')) {
13358 print_r(xdebug_get_code_coverage());
13359 }
13360
13361 // Output string from hooks
13362 if (!empty($hookmanager->resPrint)) {
13363 print $hookmanager->resPrint;
13364 }
13365
13366 // Add DebugBar data
13367 if ($user->hasRight('debugbar', 'read')) {
13368 global $debugbar;
13369 if ($debugbar instanceof DebugBar\DebugBar) {
13370 if (isset($debugbar['time'])) {
13371 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
13372 $debugbar['time']->stopMeasure('pageaftermaster');
13373 }
13374 print '<!-- Output debugbar data -->' . "\n";
13375 $renderer = $debugbar->getJavascriptRenderer();
13376 print $renderer->render();
13377 }
13378 } elseif (count($conf->logbuffer)) { // If there is some logs in buffer to show
13379 print "\n";
13380 print "<!-- Start of log output\n";
13381 //print '<div class="hidden">'."\n";
13382 foreach ($conf->logbuffer as $logline) {
13383 print $logline . "<br>\n";
13384 }
13385 //print '</div>'."\n";
13386 print "End of log output -->\n";
13387 }
13388 }
13389}
13390
13400function dolExplodeIntoArray($string, $delimiter = ';', $kv = '=')
13401{
13402 if (is_null($string)) {
13403 return array();
13404 }
13405
13406 if (preg_match('/^\[.*\]$/sm', $delimiter) || preg_match('/^\‍(.*\‍)$/sm', $delimiter)) {
13407 // This is a regex string
13408 $newdelimiter = $delimiter;
13409 } else {
13410 // This is a simple string
13411 // @phan-suppress-next-line PhanPluginSuspiciousParamPositionInternal
13412 $newdelimiter = preg_quote($delimiter, '/');
13413 }
13414
13415 if ($a = preg_split('/' . $newdelimiter . '/', $string)) {
13416 $ka = array();
13417 foreach ($a as $s) { // each part
13418 if ($s) {
13419 if ($pos = strpos($s, $kv)) { // key/value delimiter
13420 $ka[trim(substr($s, 0, $pos))] = trim(substr($s, $pos + strlen($kv)));
13421 } else { // key delimiter not found
13422 $ka[] = trim($s);
13423 }
13424 }
13425 }
13426 return $ka;
13427 }
13428
13429 return array();
13430}
13431
13439function dolExplodeKeepIfQuotes($input)
13440{
13441 // Use regexp to capture words and section in quotes
13442 $matches = array();
13443 preg_match_all('/"([^"]*)"|\'([^\']*)\'|(\S+)/', $input, $matches);
13444
13445 // Merge result and delete empty values
13446
13447 $result = array_map(
13454 static function ($a, $b, $c) {
13455 if ($a !== '') {
13456 return $a;
13457 }
13458 if ($b !== '') {
13459 return $b;
13460 }
13461 if ($c !== '') {
13462 return $c;
13463 }
13464 return '';
13465 },
13466 $matches[1],
13467 $matches[2],
13468 $matches[3]
13469 );
13470 return array_values(array_filter(
13471 $result,
13478 static function ($val) {
13479 return $val !== '';
13480 }
13481 ));
13482}
13483
13484
13491function dol_set_focus($selector)
13492{
13493 print "\n" . '<!-- Set focus onto a specific field -->' . "\n";
13494 print '<script nonce="' . getNonce() . '">jQuery(document).ready(function() { console.log("Force focus by dol_set_focus"); jQuery("' . dol_escape_js($selector) . '").focus(); });</script>' . "\n";
13495}
13496
13497
13505function dol_getmypid()
13506{
13507 if (!function_exists('getmypid')) {
13508 return mt_rand(99900000, 99965535);
13509 } else {
13510 return getmypid(); // May be a number on 64 bits (depending on OS)
13511 }
13512}
13513
13536function natural_search($fields, $value, $mode = 0, $nofirstand = 0, $sqltoadd = '')
13537{
13538 global $db, $langs;
13539
13540 $value = trim($value);
13541
13542 if ($mode == 0) {
13543 $value = preg_replace('/\*/', '%', $value); // Replace * with %
13544 }
13545 if ($mode == 1) {
13546 $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
13547 }
13548
13549 $value = preg_replace('/\s*\|\s*/', '|', $value);
13550
13551 // Split criteria on ' ' but not if we are inside quotes.
13552 // For mode 3, the split is done later on the , only and not on the ' '.
13553 if ($mode != -3 && $mode != 3) {
13554 $crits = dolExplodeKeepIfQuotes($value);
13555 } else {
13556 $crits = array($value);
13557 }
13558
13559 $res = '';
13560 if (!is_array($fields)) {
13561 $fields = array($fields);
13562 }
13563 $i1 = 0; // count the nb of "and" criteria added (all fields / criteria)
13564 foreach ($crits as $crit) { // Loop on each AND criteria
13565 $crit = trim($crit);
13566 $i2 = 0; // count the nb of valid criteria added for this this first criteria
13567 $newres = '';
13568
13569 foreach ($fields as $field) {
13570 if ($mode == 1) {
13571 $tmpcrits = explode('|', $crit);
13572 $i3 = 0; // count the nb of valid criteria added for this current field
13573 foreach ($tmpcrits as $tmpcrit) {
13574 if ($tmpcrit !== '0' && empty($tmpcrit)) {
13575 continue;
13576 }
13577 $tmpcrit = trim($tmpcrit);
13578
13579 $newres .= (($i2 > 0 || $i3 > 0) ? ' OR ' : '');
13580
13581 $operator = '=';
13582 $newcrit = preg_replace('/([!<>=]+)/', '', $tmpcrit);
13583
13584 $reg = array();
13585 preg_match('/([!<>=]+)/', $tmpcrit, $reg);
13586 if (!empty($reg[1])) {
13587 $operator = $reg[1];
13588 }
13589 if ($newcrit != '') {
13590 $numnewcrit = price2num($newcrit);
13591 if (is_numeric($numnewcrit)) {
13592 $newres .= $db->sanitize($field) . ' ' . $operator . ' ' . ((float) $numnewcrit); // should be a numeric
13593 } else {
13594 $newres .= '1 = 2'; // force false, we received a corrupted data
13595 }
13596 $i3++; // a criteria was added to string
13597 }
13598 }
13599 $i2++; // a criteria for 1 more field was added to string
13600 } elseif ($mode == 2 || $mode == -2) {
13601 $crit = preg_replace('/[^\-0-9,]/', '', $crit); // ID are always integer
13602 $newres .= ($i2 > 0 ? ' OR ' : '') . $db->sanitize($field) . " " . ($mode == -2 ? 'NOT ' : '');
13603 $newres .= $crit ? "IN (" . $db->sanitize($db->escape($crit)) . ")" : "IN (0)";
13604 if ($mode == -2) {
13605 $newres .= ' OR ' . $db->sanitize($field) . ' IS NULL';
13606 }
13607 $i2++; // a criteria for 1 more field was added to string
13608 } elseif ($mode == 3 || $mode == -3) {
13609 $tmparray = explode(',', $crit);
13610 if (count($tmparray)) {
13611 $listofcodes = '';
13612 $listofcodesnot = '';
13613 foreach ($tmparray as $val) {
13614 $val = trim($val);
13615 if ($val !== '') {
13616 if (preg_match('/^!/', $val)) {
13617 $listofcodesnot .= ($listofcodesnot ? ',' : '');
13618 $listofcodesnot .= "'" . $db->escape(preg_replace('/^!=?/', '', $val)) . "'";
13619 } else {
13620 $listofcodes .= ($listofcodes ? ',' : '');
13621 $listofcodes .= "'" . $db->escape($val) . "'";
13622 }
13623 }
13624 }
13625 $newres .= ($i2 > 0 ? ' OR ' : '');
13626 if ($listofcodes && $listofcodesnot) {
13627 $newres .= '(';
13628 }
13629 if ($listofcodes) {
13630 $newres .= $db->sanitize($field) . " " . ($mode == -3 ? 'NOT IN' : 'IN') . " (" . $db->sanitize($listofcodes, 1, 0, 1) . ")";
13631 }
13632 if ($listofcodes && $listofcodesnot) {
13633 $newres .= ' AND ';
13634 }
13635 if ($listofcodesnot) {
13636 $newres .= $db->sanitize($field) . " " . ($mode == -3 ? 'IN ' : 'NOT IN') . " (" . $db->sanitize($listofcodesnot, 1, 0, 1) . ")";
13637 }
13638 if ($listofcodes && $listofcodesnot) {
13639 $newres .= ')';
13640 }
13641 $i2++; // a criteria for 1 more field was added to string
13642 }
13643 if ($mode == -3) {
13644 $newres .= ' OR ' . $db->sanitize($field) . ' IS NULL';
13645 }
13646 } elseif ($mode == 4) {
13647 $tmparray = explode(',', $crit);
13648 if (count($tmparray)) {
13649 $listofcodes = '';
13650 foreach ($tmparray as $val) {
13651 $val = trim($val);
13652 if ($val) {
13653 $newres .= ($i2 > 0 ? " OR (" : "(") . $db->sanitize($field) . " LIKE '" . $db->escape($val) . ",%'";
13654 $newres .= ' OR ' . $db->sanitize($field) . " = '" . $db->escape($val) . "'";
13655 $newres .= ' OR ' . $db->sanitize($field) . " LIKE '%," . $db->escape($val) . "'";
13656 $newres .= ' OR ' . $db->sanitize($field) . " LIKE '%," . $db->escape($val) . ",%'";
13657 $newres .= ')';
13658 $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)
13659 }
13660 }
13661 }
13662 } else { // $mode=0
13663 $tmpcrits = explode('|', $crit);
13664 $i3 = 0; // count the nb of valid criteria added for the current couple criteria/field
13665 foreach ($tmpcrits as $tmpcrit) { // loop on each OR criteria
13666 if ($tmpcrit !== '0' && empty($tmpcrit)) {
13667 continue;
13668 }
13669 $tmpcrit = trim($tmpcrit);
13670
13671 if ($tmpcrit == '^$' || strpos($crit, '!') === 0) { // If we search empty, we must combined different OR fields with AND
13672 $newres .= (($i2 > 0 || $i3 > 0) ? ' AND ' : '');
13673 } else {
13674 $newres .= (($i2 > 0 || $i3 > 0) ? ' OR ' : '');
13675 }
13676
13677 $isSellist = false;
13678 $table = $label = $key = null;
13679
13680 if (strpos($field, 'ef.') === 0) {
13681 $extrafieldName = substr($field, 3);
13682 $extrafields = new ExtraFields($db);
13683 $extrafields->fetch_name_optionals_label('product');
13684
13685 if (isset($extrafields->attributes['product']['type'][$extrafieldName]) && $extrafields->attributes['product']['type'][$extrafieldName] === 'sellist') {
13686 $isSellist = true;
13687 $paramArray = $extrafields->attributes['product']['param'][$extrafieldName]['options'] ?? [];
13688 $param = array_key_first($paramArray);
13689 list($table, $label, $key) = explode(':', $param);
13690 }
13691 }
13692
13693 if (preg_match('/\.(id|rowid)$/', $field)) { // Special case for rowid that is sometimes a ref so used as a search field
13694 $newres .= $db->sanitize($field) . " = " . (is_numeric($tmpcrit) ? ((float) $tmpcrit) : '0');
13695 } else {
13696 $tmpcrit2 = $tmpcrit;
13697 $tmpbefore = '%';
13698 $tmpafter = '%';
13699 $tmps = '';
13700
13701 if ($isSellist && $key && $table && $label) {
13702 $newres .= $field . " IN (SELECT t." . $db->sanitize((string) $key) . " FROM " . $db->prefix() . $db->sanitize((string) $table) . " AS t WHERE t." . $db->sanitize((string) $label) . " LIKE '%" . $db->escape($tmpcrit2) . "%')";
13703 } else {
13704 if (preg_match('/^!/', $tmpcrit)) {
13705 $tmps .= $db->sanitize($field) . " NOT LIKE "; // ! as exclude character
13706 $tmpcrit2 = preg_replace('/^!/', '', $tmpcrit2);
13707 } else {
13708 $tmps .= $db->sanitize($field) . " LIKE ";
13709 }
13710 $tmps .= "'";
13711
13712 if (preg_match('/^[\^\$]/', $tmpcrit)) {
13713 $tmpbefore = '';
13714 $tmpcrit2 = preg_replace('/^[\^\$]/', '', $tmpcrit2);
13715 }
13716 if (preg_match('/[\^\$]$/', $tmpcrit)) {
13717 $tmpafter = '';
13718 $tmpcrit2 = preg_replace('/[\^\$]$/', '', $tmpcrit2);
13719 }
13720
13721 if ($tmpcrit2 == '' || preg_match('/^!/', $tmpcrit)) {
13722 $tmps = "(" . $tmps;
13723 }
13724 $newres .= $tmps;
13725 $newres .= $tmpbefore;
13726 $newres .= $db->escape($tmpcrit2);
13727 $newres .= $tmpafter;
13728 $newres .= "'";
13729 if ($tmpcrit2 == '' || preg_match('/^!/', $tmpcrit)) {
13730 $newres .= " OR " . $db->sanitize($field) . " IS NULL)";
13731 }
13732 }
13733 }
13734
13735 $i3++;
13736 }
13737
13738 $i2++; // a criteria for 1 more field was added to string
13739 }
13740 }
13741
13742 if ($sqltoadd) {
13743 $newres .= ($newres ? '' : ' OR ').str_replace('__KEYTOSEARCH__', $db->escape($crit), $sqltoadd);
13744 }
13745
13746 if ($newres) {
13747 $res = $res . ($res ? ' AND ' : '') . ($i2 > 1 ? '(' : '') . $newres . ($i2 > 1 ? ')' : '');
13748 }
13749 $i1++;
13750 }
13751 $res = ($nofirstand ? "" : " AND ") . "(" . $res . ")";
13752
13753 return $res;
13754}
13755
13763function showSimpleHTMLTable($outputlangs, $object)
13764{
13765 global $conf;
13766
13767 $discountIsAvailable = false;
13768 $orderPositionHasNoPrice = false;
13769
13770 if (!property_exists($object->lines[0], "remise_percent") ||
13771 !property_exists($object->lines[0], "fk_unit") ||
13772 !property_exists($object->lines[0], "multicurrency_total_ttc") ||
13773 !property_exists($object->lines[0], "description") ||
13774 !property_exists($object->lines[0], "qty")) {
13775 return"";
13776 }
13777
13778 foreach ($object->lines as $order_position) {
13779 if (!property_exists($order_position, "price")) {
13780 $orderPositionHasNoPrice = true;
13781 break;
13782 }
13783
13784 if (!empty($order_position->remise_percent)) {
13785 $discountIsAvailable = true;
13786 break;
13787 }
13788 };
13789
13790 if ($orderPositionHasNoPrice) {
13791 return "";
13792 }
13793
13794 $discountHeader = $discountIsAvailable ? '<th style="width:120px">'.$outputlangs->trans("Discount").'</th>' : '';
13795
13796 $table = '<table border="0" cellpadding="1" cellspacing="1">';
13797 $table .= '
13798 <thead>
13799 <tr>
13800 <th style="width:50px; text-align:left">#</th>
13801 <th style="text-align:left">'.$outputlangs->trans("Description").'</th>
13802 <th style="width:120px; text-align:right;">'.$outputlangs->trans("Price").'</th>
13803 <th style="width:100px; text-align:right;">'.$outputlangs->trans("Quantity").'</th>
13804 <th style="width:120px; text-align:right;">'.$outputlangs->trans("Unit").'</th>'.
13805 $discountHeader.'
13806 <th style="width:120px; text-align:right;">'.$outputlangs->trans("Sum").'</th>
13807 </tr>
13808 </thead>
13809 <tbody>';
13810
13811 foreach ($object->lines as $index => $order_position) {
13812 $position = $index + 1;
13813 $price = price($order_position->price, 0, $outputlangs, 0, -1, -1, $conf->currency);
13814 $unit = measuringUnitString($order_position->fk_unit, '', null, 1);
13815 $total = price($order_position->multicurrency_total_ttc, 0, $outputlangs, 0, -1, -1, $conf->currency);
13816 $discount = $discountIsAvailable ? '<td style="text-align:center">'.$order_position->remise_percent.'%</td>' : "";
13817
13818 $table .= '
13819 <tr>
13820 <td>'.$position.'</td>
13821 <td>'.$order_position->description.'</td>
13822 <td style="text-align:right">'.$price.'</td>
13823 <td style="text-align:right">'.$order_position->qty.'</td>
13824 <td style="text-align:right">'.$unit.'</td>'.
13825 $discount.'
13826 <td style="text-align:right">'.$total.'</td>
13827 </tr>';
13828 }
13829 $table .= '</tbody></table>';
13830
13831 return $table;
13832}
13833
13840function showDirectDownloadLink($object)
13841{
13842 global $langs;
13843
13844 $out = '';
13845 $url = $object->getLastMainDocLink($object->element);
13846
13847 $out .= img_picto($langs->trans("PublicDownloadLinkDesc"), 'globe') . ' <span class="opacitymedium">' . $langs->trans("DirectDownloadLink") . '</span><br>';
13848 if ($url) {
13849 $out .= '<div class="urllink"><input type="text" id="directdownloadlink" class="quatrevingtpercent" value="' . $url . '"></div>';
13850 $out .= ajax_autoselect("directdownloadlink", '');
13851 } else {
13852 $out .= '<div class="urllink">' . $langs->trans("FileNotShared") . '</div>';
13853 }
13854
13855 return $out;
13856}
13857
13866function getImageFileNameForSize($file, $extName, $extImgTarget = '')
13867{
13868 $dirName = dirname($file);
13869 if ($dirName == '.') {
13870 $dirName = '';
13871 }
13872
13873 if (!in_array($extName, array('', '_small', '_mini'))) {
13874 return 'Bad parameter extName';
13875 }
13876
13877 $fileName = preg_replace('/(\.gif|\.jpeg|\.jpg|\.png|\.bmp|\.webp|\.avif)$/i', '', $file); // We remove image extension, whatever is its case
13878 $fileName = basename($fileName);
13879
13880 if (empty($extImgTarget)) {
13881 $extImgTarget = (preg_match('/\.jpg$/i', $file) ? '.jpg' : '');
13882 }
13883 if (empty($extImgTarget)) {
13884 $extImgTarget = (preg_match('/\.jpeg$/i', $file) ? '.jpeg' : '');
13885 }
13886 if (empty($extImgTarget)) {
13887 $extImgTarget = (preg_match('/\.gif$/i', $file) ? '.gif' : '');
13888 }
13889 if (empty($extImgTarget)) {
13890 $extImgTarget = (preg_match('/\.png$/i', $file) ? '.png' : '');
13891 }
13892 if (empty($extImgTarget)) {
13893 $extImgTarget = (preg_match('/\.bmp$/i', $file) ? '.bmp' : '');
13894 }
13895 if (empty($extImgTarget)) {
13896 $extImgTarget = (preg_match('/\.webp$/i', $file) ? '.webp' : '');
13897 }
13898 if (empty($extImgTarget)) {
13899 $extImgTarget = (preg_match('/\.avif$/i', $file) ? '.avif' : '');
13900 }
13901
13902 if (!$extImgTarget) {
13903 return $file;
13904 }
13905
13906 $subdir = '';
13907 if ($extName) {
13908 $subdir = 'thumbs/';
13909 }
13910
13911 return ($dirName ? $dirName . '/' : '') . $subdir . $fileName . $extName . $extImgTarget; // New filename for thumb
13912}
13913
13914
13924function getAdvancedPreviewUrl($modulepart, $relativepath, $alldata = 0, $param = '')
13925{
13926 global $conf, $langs;
13927
13928 if (empty($conf->use_javascript_ajax)) {
13929 return '';
13930 }
13931
13932 $isAllowedForPreview = dolIsAllowedForPreview($relativepath);
13933
13934 if ($alldata == 1) {
13935 if ($isAllowedForPreview) {
13936 return array('target' => '_blank', 'css' => 'documentpreview', 'url' => DOL_URL_ROOT . '/document.php?modulepart=' . urlencode($modulepart) . '&attachment=0&file=' . urlencode($relativepath) . ($param ? '&' . $param : ''), 'mime' => dol_mimetype($relativepath));
13937 } else {
13938 return array();
13939 }
13940 }
13941
13942 // old behavior, return a string
13943 if ($isAllowedForPreview) {
13944 $tmpurl = DOL_URL_ROOT . '/document.php?modulepart=' . urlencode($modulepart) . '&attachment=0&file=' . urlencode($relativepath) . ($param ? '&' . $param : '');
13945 $title = $langs->transnoentities("Preview");
13946 //$title = '%27-alert(document.domain)-%27'; // An example of js injection into a corrupted title string, that should be blocked by the dol_escape_uri().
13947 //$tmpurl = 'file='.urlencode("'-alert(document.domain)-'_small.jpg"); // An example of tmpurl that should be blocked by the dol_escape_uri()
13948
13949 // We need to do a dol_escape_uri() on the full string after the javascript: because such parts are the URI and when we click on such links, a RFC3986 decode is done,
13950 // by the browser, converting the %27 (like when having param file=abc%27def), or when having a corrupted title), into a ', BEFORE interpreting the content that can be a js code.
13951 // Using the dol_escape_uri guarantee that we encode for URI so decode retrieve original expected value.
13952 return 'javascript:' . dol_escape_uri('document_preview(\'' . dol_escape_js($tmpurl) . '\', \'' . dol_escape_js(dol_mimetype($relativepath)) . '\', \'' . dol_escape_js($title) . '\')');
13953 } else {
13954 return '';
13955 }
13956}
13957
13964function getLabelSpecialCode($idcode)
13965{
13966 global $langs;
13967
13968 $arrayspecialines = array(1 => 'Transport', 2 => 'EcoTax', 3 => 'Option');
13969 if ($idcode > 10) {
13970 return 'Module ID ' . $idcode;
13971 }
13972 if (!empty($arrayspecialines[$idcode])) {
13973 return $langs->trans($arrayspecialines[$idcode]);
13974 }
13975 return '';
13976}
13977
13987function ajax_autoselect($htmlname, $addlink = '', $textonlink = 'Link')
13988{
13989 global $langs;
13990 $out = '<script nonce="' . getNonce() . '">
13991 jQuery(document).ready(function () {
13992 jQuery("' . ((strpos($htmlname, '.') === 0 ? '' : '#') . $htmlname) . '").click(function() { jQuery(this).select(); } );
13993 });
13994 </script>';
13995 if ($addlink) {
13996 if ($textonlink === 'image') {
13997 $out .= ' <a href="' . $addlink . '" target="_blank" rel="noopener noreferrer">' . img_picto('', 'globe') . '</a>';
13998 } else {
13999 $out .= ' <a href="' . $addlink . '" target="_blank" rel="noopener noreferrer">' . $langs->trans("Link") . '</a>';
14000 }
14001 }
14002 return $out;
14003}
14004
14012function dolIsAllowedForPreview($file)
14013{
14014 // Check .noexe extension in filename
14015 if (preg_match('/\.noexe$/i', $file)) {
14016 return 0;
14017 }
14018
14019 // Check mime types
14020 $mime_preview = array('avif', 'bmp', 'jpeg', 'png', 'gif', 'tiff', 'pdf', 'plain', 'css', 'webp', 'webm', 'mp4');
14021 if (getDolGlobalString('MAIN_ALLOW_SVG_FILES_AS_IMAGES')) {
14022 $mime_preview[] = 'svg+xml';
14023 }
14024 if (getDolGlobalString('MAIN_ALLOW_XML_FILES_AS_PREVIEW')) {
14025 $mime_preview[] = 'xml';
14026 }
14027
14028 //$mime_preview[]='vnd.oasis.opendocument.presentation';
14029 //$mime_preview[]='archive';
14030 $num_mime = array_search(dol_mimetype($file, '', 1), $mime_preview);
14031 if ($num_mime !== false) {
14032 return 1;
14033 }
14034
14035 // By default, not allowed for preview
14036 return 0;
14037}
14038
14039
14049function dol_mimetype($file, $default = 'application/octet-stream', $mode = 0)
14050{
14051 $mime = $default;
14052 $imgmime = 'other.png';
14053 $famime = 'file-o';
14054 $srclang = '';
14055
14056 $tmpfile = preg_replace('/\.noexe$/', '', $file);
14057
14058 // Plain text files
14059 if (preg_match('/\.txt$/i', $tmpfile)) {
14060 $mime = 'text/plain';
14061 $imgmime = 'text.png';
14062 $famime = 'file-alt';
14063 } elseif (preg_match('/\.rtx$/i', $tmpfile)) {
14064 $mime = 'text/richtext';
14065 $imgmime = 'text.png';
14066 $famime = 'file-alt';
14067 } elseif (preg_match('/\.csv$/i', $tmpfile)) {
14068 $mime = 'text/csv';
14069 $imgmime = 'text.png';
14070 $famime = 'file-csv';
14071 } elseif (preg_match('/\.tsv$/i', $tmpfile)) {
14072 $mime = 'text/tab-separated-values';
14073 $imgmime = 'text.png';
14074 $famime = 'file-alt';
14075 } elseif (preg_match('/\.(cf|conf|log)$/i', $tmpfile)) {
14076 $mime = 'text/plain';
14077 $imgmime = 'text.png';
14078 $famime = 'file-alt';
14079 } elseif (preg_match('/\.ini$/i', $tmpfile)) {
14080 $mime = 'text/plain';
14081 $imgmime = 'text.png';
14082 $srclang = 'ini';
14083 $famime = 'file-alt';
14084 } elseif (preg_match('/\.md$/i', $tmpfile)) {
14085 $mime = 'text/plain';
14086 $imgmime = 'text.png';
14087 $srclang = 'md';
14088 $famime = 'file-alt';
14089 } elseif (preg_match('/\.css$/i', $tmpfile)) {
14090 $mime = 'text/css';
14091 $imgmime = 'css.png';
14092 $srclang = 'css';
14093 $famime = 'file-alt';
14094 } elseif (preg_match('/\.lang$/i', $tmpfile)) {
14095 $mime = 'text/plain';
14096 $imgmime = 'text.png';
14097 $srclang = 'lang';
14098 $famime = 'file-alt';
14099 } elseif (preg_match('/\.(crt|cer|key|pub)$/i', $tmpfile)) { // Certificate files
14100 $mime = 'text/plain';
14101 $imgmime = 'text.png';
14102 $famime = 'file-alt';
14103 } elseif (preg_match('/\.(html|htm|shtml)$/i', $tmpfile)) { // XML based (HTML/XML/XAML)
14104 $mime = 'text/html';
14105 $imgmime = 'html.png';
14106 $srclang = 'html';
14107 $famime = 'file-alt';
14108 } elseif (preg_match('/\.(xml|xhtml)$/i', $tmpfile)) {
14109 $mime = 'text/xml';
14110 $imgmime = 'other.png';
14111 $srclang = 'xml';
14112 $famime = 'file-alt';
14113 } elseif (preg_match('/\.xaml$/i', $tmpfile)) {
14114 $mime = 'text/xml';
14115 $imgmime = 'other.png';
14116 $srclang = 'xaml';
14117 $famime = 'file-alt';
14118 } elseif (preg_match('/\.bas$/i', $tmpfile)) { // Languages
14119 $mime = 'text/plain';
14120 $imgmime = 'text.png';
14121 $srclang = 'bas';
14122 $famime = 'file-code';
14123 } elseif (preg_match('/\.(c)$/i', $tmpfile)) {
14124 $mime = 'text/plain';
14125 $imgmime = 'text.png';
14126 $srclang = 'c';
14127 $famime = 'file-code';
14128 } elseif (preg_match('/\.(cpp)$/i', $tmpfile)) {
14129 $mime = 'text/plain';
14130 $imgmime = 'text.png';
14131 $srclang = 'cpp';
14132 $famime = 'file-code';
14133 } elseif (preg_match('/\.cs$/i', $tmpfile)) {
14134 $mime = 'text/plain';
14135 $imgmime = 'text.png';
14136 $srclang = 'cs';
14137 $famime = 'file-code';
14138 } elseif (preg_match('/\.(h)$/i', $tmpfile)) {
14139 $mime = 'text/plain';
14140 $imgmime = 'text.png';
14141 $srclang = 'h';
14142 $famime = 'file-code';
14143 } elseif (preg_match('/\.(java|jsp)$/i', $tmpfile)) {
14144 $mime = 'text/plain';
14145 $imgmime = 'text.png';
14146 $srclang = 'java';
14147 $famime = 'file-code';
14148 } elseif (preg_match('/\.php([0-9]{1})?$/i', $tmpfile)) {
14149 $mime = 'text/plain';
14150 $imgmime = 'php.png';
14151 $srclang = 'php';
14152 $famime = 'file-code';
14153 } elseif (preg_match('/\.phtml$/i', $tmpfile)) {
14154 $mime = 'text/plain';
14155 $imgmime = 'php.png';
14156 $srclang = 'php';
14157 $famime = 'file-code';
14158 } elseif (preg_match('/\.(pl|pm)$/i', $tmpfile)) {
14159 $mime = 'text/plain';
14160 $imgmime = 'pl.png';
14161 $srclang = 'perl';
14162 $famime = 'file-code';
14163 } elseif (preg_match('/\.sql$/i', $tmpfile)) {
14164 $mime = 'text/plain';
14165 $imgmime = 'text.png';
14166 $srclang = 'sql';
14167 $famime = 'file-code';
14168 } elseif (preg_match('/\.js$/i', $tmpfile)) {
14169 $mime = 'text/x-javascript';
14170 $imgmime = 'jscript.png';
14171 $srclang = 'js';
14172 $famime = 'file-code';
14173 } elseif (preg_match('/\.odp$/i', $tmpfile)) { // Open office
14174 $mime = 'application/vnd.oasis.opendocument.presentation';
14175 $imgmime = 'ooffice.png';
14176 $famime = 'file-powerpoint';
14177 } elseif (preg_match('/\.ods$/i', $tmpfile)) {
14178 $mime = 'application/vnd.oasis.opendocument.spreadsheet';
14179 $imgmime = 'ooffice.png';
14180 $famime = 'file-excel';
14181 } elseif (preg_match('/\.odt$/i', $tmpfile)) {
14182 $mime = 'application/vnd.oasis.opendocument.text';
14183 $imgmime = 'ooffice.png';
14184 $famime = 'file-word';
14185 } elseif (preg_match('/\.mdb$/i', $tmpfile)) { // MS Office
14186 $mime = 'application/msaccess';
14187 $imgmime = 'mdb.png';
14188 $famime = 'file';
14189 } elseif (preg_match('/\.doc[xm]?$/i', $tmpfile)) {
14190 $mime = 'application/msword';
14191 $imgmime = 'doc.png';
14192 $famime = 'file-word';
14193 } elseif (preg_match('/\.dot[xm]?$/i', $tmpfile)) {
14194 $mime = 'application/msword';
14195 $imgmime = 'doc.png';
14196 $famime = 'file-word';
14197 } elseif (preg_match('/\.xlt(x)?$/i', $tmpfile)) {
14198 $mime = 'application/vnd.ms-excel';
14199 $imgmime = 'xls.png';
14200 $famime = 'file-excel';
14201 } elseif (preg_match('/\.xla(m)?$/i', $tmpfile)) {
14202 $mime = 'application/vnd.ms-excel';
14203 $imgmime = 'xls.png';
14204 $famime = 'file-excel';
14205 } elseif (preg_match('/\.xls$/i', $tmpfile)) {
14206 $mime = 'application/vnd.ms-excel';
14207 $imgmime = 'xls.png';
14208 $famime = 'file-excel';
14209 } elseif (preg_match('/\.xls[bmx]$/i', $tmpfile)) {
14210 $mime = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
14211 $imgmime = 'xls.png';
14212 $famime = 'file-excel';
14213 } elseif (preg_match('/\.pps[mx]?$/i', $tmpfile)) {
14214 $mime = 'application/vnd.ms-powerpoint';
14215 $imgmime = 'ppt.png';
14216 $famime = 'file-powerpoint';
14217 } elseif (preg_match('/\.ppt[mx]?$/i', $tmpfile)) {
14218 $mime = 'application/x-mspowerpoint';
14219 $imgmime = 'ppt.png';
14220 $famime = 'file-powerpoint';
14221 } elseif (preg_match('/\.pdf$/i', $tmpfile)) { // Other
14222 $mime = 'application/pdf';
14223 $imgmime = 'pdf.png';
14224 $famime = 'file-pdf';
14225 } elseif (preg_match('/\.bat$/i', $tmpfile)) { // Scripts
14226 $mime = 'text/x-bat';
14227 $imgmime = 'script.png';
14228 $srclang = 'dos';
14229 $famime = 'file-code';
14230 } elseif (preg_match('/\.sh$/i', $tmpfile)) {
14231 $mime = 'text/x-sh';
14232 $imgmime = 'script.png';
14233 $srclang = 'bash';
14234 $famime = 'file-code';
14235 } elseif (preg_match('/\.ksh$/i', $tmpfile)) {
14236 $mime = 'text/x-ksh';
14237 $imgmime = 'script.png';
14238 $srclang = 'bash';
14239 $famime = 'file-code';
14240 } elseif (preg_match('/\.bash$/i', $tmpfile)) {
14241 $mime = 'text/x-bash';
14242 $imgmime = 'script.png';
14243 $srclang = 'bash';
14244 $famime = 'file-code';
14245 } elseif (preg_match('/\.ico$/i', $tmpfile)) { // Images
14246 $mime = 'image/x-icon';
14247 $imgmime = 'image.png';
14248 $famime = 'file-image';
14249 } elseif (preg_match('/\.(jpg|jpeg)$/i', $tmpfile)) {
14250 $mime = 'image/jpeg';
14251 $imgmime = 'image.png';
14252 $famime = 'file-image';
14253 } elseif (preg_match('/\.png$/i', $tmpfile)) {
14254 $mime = 'image/png';
14255 $imgmime = 'image.png';
14256 $famime = 'file-image';
14257 } elseif (preg_match('/\.gif$/i', $tmpfile)) {
14258 $mime = 'image/gif';
14259 $imgmime = 'image.png';
14260 $famime = 'file-image';
14261 } elseif (preg_match('/\.bmp$/i', $tmpfile)) {
14262 $mime = 'image/bmp';
14263 $imgmime = 'image.png';
14264 $famime = 'file-image';
14265 } elseif (preg_match('/\.(tif|tiff)$/i', $tmpfile)) {
14266 $mime = 'image/tiff';
14267 $imgmime = 'image.png';
14268 $famime = 'file-image';
14269 } elseif (preg_match('/\.svg$/i', $tmpfile)) {
14270 $mime = 'image/svg+xml';
14271 $imgmime = 'image.png';
14272 $famime = 'file-image';
14273 } elseif (preg_match('/\.webp$/i', $tmpfile)) {
14274 $mime = 'image/webp';
14275 $imgmime = 'image.png';
14276 $famime = 'file-image';
14277 } elseif (preg_match('/\.vcs$/i', $tmpfile)) { // Calendar
14278 $mime = 'text/calendar';
14279 $imgmime = 'other.png';
14280 $famime = 'file-alt';
14281 } elseif (preg_match('/\.ics$/i', $tmpfile)) {
14282 $mime = 'text/calendar';
14283 $imgmime = 'other.png';
14284 $famime = 'file-alt';
14285 } elseif (preg_match('/\.torrent$/i', $tmpfile)) { // Other
14286 $mime = 'application/x-bittorrent';
14287 $imgmime = 'other.png';
14288 $famime = 'file-o';
14289 } elseif (preg_match('/\.(mp3|ogg|au|wav|wma|mid)$/i', $tmpfile)) { // Audio
14290 $mime = 'audio';
14291 $imgmime = 'audio.png';
14292 $famime = 'file-audio';
14293 } elseif (preg_match('/\.mp4$/i', $tmpfile)) { // Video
14294 $mime = 'video/mp4';
14295 $imgmime = 'video.png';
14296 $famime = 'file-video';
14297 } elseif (preg_match('/\.ogv$/i', $tmpfile)) {
14298 $mime = 'video/ogg';
14299 $imgmime = 'video.png';
14300 $famime = 'file-video';
14301 } elseif (preg_match('/\.webm$/i', $tmpfile)) {
14302 $mime = 'video/webm';
14303 $imgmime = 'video.png';
14304 $famime = 'file-video';
14305 } elseif (preg_match('/\.avif$/i', $tmpfile)) {
14306 $mime = 'image/avif';
14307 $imgmime = 'image.png';
14308 $famime = 'file-image';
14309 } elseif (preg_match('/\.avi$/i', $tmpfile)) {
14310 $mime = 'video/x-msvideo';
14311 $imgmime = 'video.png';
14312 $famime = 'file-video';
14313 } elseif (preg_match('/\.divx$/i', $tmpfile)) {
14314 $mime = 'video/divx';
14315 $imgmime = 'video.png';
14316 $famime = 'file-video';
14317 } elseif (preg_match('/\.xvid$/i', $tmpfile)) {
14318 $mime = 'video/xvid';
14319 $imgmime = 'video.png';
14320 $famime = 'file-video';
14321 } elseif (preg_match('/\.(wmv|mpg|mpeg)$/i', $tmpfile)) {
14322 $mime = 'video';
14323 $imgmime = 'video.png';
14324 $famime = 'file-video';
14325 } elseif (preg_match('/\.(zip|rar|gz|tgz|xz|z|cab|bz2|7z|tar|lzh|zst)$/i', $tmpfile)) { // Archive
14326 // application/xxx where zzz is zip, ...
14327 $mime = 'archive';
14328 $imgmime = 'archive.png';
14329 $famime = 'file-archive';
14330 } elseif (preg_match('/\.(exe|com)$/i', $tmpfile)) { // Exe
14331 $mime = 'application/octet-stream';
14332 $imgmime = 'other.png';
14333 $famime = 'file-o';
14334 } elseif (preg_match('/\.(dll|lib|o|so|a)$/i', $tmpfile)) { // Lib
14335 $mime = 'library';
14336 $imgmime = 'library.png';
14337 $famime = 'file-o';
14338 } elseif (preg_match('/\.err$/i', $tmpfile)) { // phpcs:ignore
14339 $mime = 'error';
14340 $imgmime = 'error.png';
14341 $famime = 'file-alt';
14342 }
14343
14344 if ($famime == 'file-o') {
14345 // file-o seems to not work in fontawesome 5
14346 $famime = 'file';
14347 }
14348
14349 // Return mimetype string
14350 switch ((int) $mode) {
14351 case 1:
14352 $tmp = explode('/', $mime);
14353 return (!empty($tmp[1]) ? $tmp[1] : $tmp[0]);
14354 case 2:
14355 return $imgmime;
14356 case 3:
14357 return $srclang;
14358 case 4:
14359 return $famime;
14360 }
14361 return $mime;
14362}
14363
14375function getDictionaryValue($tablename, $field, $id, $checkentity = false, $rowidfield = 'rowid')
14376{
14377 global $conf, $db;
14378
14379 $tablename = preg_replace('/^' . preg_quote(MAIN_DB_PREFIX, '/') . '/', '', $tablename); // Clean name of table for backward compatibility.
14380
14381 $dictvalues = (isset($conf->cache['dictvalues_' . $tablename]) ? $conf->cache['dictvalues_' . $tablename] : null);
14382
14383 if (is_null($dictvalues)) {
14384 $dictvalues = array();
14385
14386 $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
14387 if ($checkentity) {
14388 $sql .= ' AND entity IN (0,' . getEntity($tablename) . ')';
14389 }
14390
14391 $resql = $db->query($sql);
14392 if ($resql) {
14393 while ($obj = $db->fetch_object($resql)) {
14394 $dictvalues[$obj->$rowidfield] = $obj; // $obj is stdClass
14395 }
14396 } else {
14397 dol_print_error($db);
14398 }
14399
14400 $conf->cache['dictvalues_' . $tablename] = $dictvalues;
14401 }
14402
14403 if (!empty($dictvalues[$id])) {
14404 // Found
14405 $tmp = $dictvalues[$id];
14406 return (property_exists($tmp, $field) ? $tmp->$field : '');
14407 } else {
14408 // Not found
14409 return '';
14410 }
14411}
14412
14419function colorIsLight($stringcolor)
14420{
14421 $stringcolor = str_replace('#', '', $stringcolor);
14422 $res = -1;
14423 if (!empty($stringcolor)) {
14424 $res = 0;
14425 $tmp = explode(',', $stringcolor);
14426 if (count($tmp) > 1) { // This is a comma RGB ('255','255','255')
14427 $r = $tmp[0];
14428 $g = $tmp[1];
14429 $b = $tmp[2];
14430 } else {
14431 $hexr = $stringcolor[0] . $stringcolor[1];
14432 $hexg = $stringcolor[2] . $stringcolor[3];
14433 $hexb = $stringcolor[4] . $stringcolor[5];
14434 $r = hexdec($hexr);
14435 $g = hexdec($hexg);
14436 $b = hexdec($hexb);
14437 }
14438 $bright = (max($r, $g, $b) + min($r, $g, $b)) / 510.0; // HSL algorithm
14439 if ($bright > 0.6) {
14440 $res = 1;
14441 }
14442 }
14443 return $res;
14444}
14445
14454function isVisibleToUserType($type_user, &$menuentry, &$listofmodulesforexternal)
14455{
14456 //print 'type_user='.$type_user.' module='.$menuentry['module'].' enabled='.$menuentry['enabled'].' perms='.$menuentry['perms'];
14457 //print 'ok='.in_array($menuentry['module'], $listofmodulesforexternal);
14458 if (empty($menuentry['enabled'])) {
14459 return 0; // Entry disabled by condition
14460 }
14461 if ($type_user && array_key_exists('module', $menuentry) && $menuentry['module']) {
14462 $tmploops = explode('|', $menuentry['module']);
14463 $found = 0;
14464 foreach ($tmploops as $tmploop) {
14465 if (in_array($tmploop, $listofmodulesforexternal)) {
14466 $found++;
14467 break;
14468 }
14469 }
14470 if (!$found) {
14471 return 0; // Entry is for menus all excluded to external users
14472 }
14473 }
14474 if (!$menuentry['perms'] && $type_user) {
14475 return 0; // No permissions and user is external
14476 }
14477 if (!$menuentry['perms'] && getDolGlobalString('MAIN_MENU_HIDE_UNAUTHORIZED')) {
14478 return 0; // No permissions and option to hide when not allowed, even for internal user, is on
14479 }
14480 if (!$menuentry['perms']) {
14481 return 2; // No permissions and user is external
14482 }
14483 return 1;
14484}
14485
14493function roundUpToNextMultiple($n, $x = 5)
14494{
14495 $result = (ceil($n) % $x === 0) ? ceil($n) : (round(($n + $x / 2) / $x) * $x);
14496 return (int) $result;
14497}
14498
14510function dolGetBadge($label, $html = '', $type = 'primary', $mode = '', $url = '', $params = array())
14511{
14512 $csstouse = 'badge';
14513 $csstouse .= (!empty($mode) ? ' badge-' . $mode : '');
14514 $csstouse .= (!empty($type) ? ' badge-' . $type : '');
14515 $csstouse .= (empty($params['css']) ? '' : ' ' . $params['css']);
14516
14517 $attr = array(
14518 'class' => $csstouse
14519 );
14520
14521 if (empty($html)) {
14522 $html = $label;
14523 }
14524
14525 if (!empty($url)) {
14526 $attr['href'] = $url;
14527 }
14528
14529 if ($mode === 'dot') {
14530 $attr['class'] .= ' classfortooltip';
14531 $attr['title'] = $html;
14532 $attr['aria-label'] = $label;
14533 $html = '';
14534 }
14535
14536 // Override attr
14537 if (!empty($params['attr']) && is_array($params['attr'])) {
14538 foreach ($params['attr'] as $key => $value) {
14539 if ($key == 'class') {
14540 $attr['class'] .= ' ' . $value;
14541 } elseif ($key == 'classOverride') {
14542 $attr['class'] = $value;
14543 } else {
14544 $attr[$key] = $value;
14545 }
14546 }
14547 }
14548
14549 // TODO: add hook
14550
14551 // escape all attribute
14552 $attr = array_map('dolPrintHTMLForAttribute', $attr);
14553
14554 $TCompiledAttr = array();
14555 foreach ($attr as $key => $value) {
14556 $TCompiledAttr[] = $key . '="' . $value . '"';
14557 }
14558
14559 $compiledAttributes = !empty($TCompiledAttr) ? implode(' ', $TCompiledAttr) : '';
14560
14561 $tag = !empty($url) ? 'a' : 'span';
14562
14563 return '<' . $tag . ' ' . $compiledAttributes . '>' . $html . '</' . $tag . '>';
14564}
14565
14566
14579function dolGetStatus($statusLabel = '', $statusLabelShort = '', $html = '', $statusType = 'status0', $displayMode = 0, $url = '', $params = array())
14580{
14581 global $conf;
14582
14583 $return = '';
14584 $dolGetBadgeParams = array();
14585
14586 if (!empty($params['badgeParams'])) {
14587 $dolGetBadgeParams = $params['badgeParams'];
14588 }
14589
14590 // TODO : add a hook
14591 if ($displayMode == 0) {
14592 $return = !empty($html) ? $html : (empty($conf->dol_optimize_smallscreen) ? $statusLabel : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort));
14593 } elseif ($displayMode == 1) {
14594 $return = !empty($html) ? $html : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort);
14595 } elseif (getDolGlobalString('MAIN_STATUS_USES_IMAGES')) {
14596 // Use status with images (for backward compatibility)
14597 $return = '';
14598 $htmlLabel = (in_array($displayMode, array(1, 2, 5)) ? '<span class="hideonsmartphone">' : '') . (!empty($html) ? $html : $statusLabel) . (in_array($displayMode, array(1, 2, 5)) ? '</span>' : '');
14599 $htmlLabelShort = (in_array($displayMode, array(1, 2, 5)) ? '<span class="hideonsmartphone">' : '') . (!empty($html) ? $html : (!empty($statusLabelShort) ? $statusLabelShort : $statusLabel)) . (in_array($displayMode, array(1, 2, 5)) ? '</span>' : '');
14600
14601 // For small screen, we always use the short label instead of long label.
14602 if (!empty($conf->dol_optimize_smallscreen)) {
14603 if ($displayMode == 0) {
14604 $displayMode = 1;
14605 } elseif ($displayMode == 4) {
14606 $displayMode = 2;
14607 } elseif ($displayMode == 6) {
14608 $displayMode = 5;
14609 }
14610 }
14611
14612 // For backward compatibility. Image's filename are still in French, so we use this array to convert
14613 $statusImg = array(
14614 'status0' => 'statut0',
14615 'status1' => 'statut1',
14616 'status2' => 'statut2',
14617 'status3' => 'statut3',
14618 'status4' => 'statut4',
14619 'status5' => 'statut5',
14620 'status6' => 'statut6',
14621 'status7' => 'statut7',
14622 'status8' => 'statut8',
14623 'status9' => 'statut9'
14624 );
14625
14626 if (!empty($statusImg[$statusType])) {
14627 $htmlImg = img_picto($statusLabel, $statusImg[$statusType]);
14628 } else {
14629 $htmlImg = img_picto($statusLabel, $statusType);
14630 }
14631
14632 if ($displayMode === 2) {
14633 $return = $htmlImg . ' ' . $htmlLabelShort;
14634 } elseif ($displayMode === 3) {
14635 $return = $htmlImg;
14636 } elseif ($displayMode === 4) {
14637 $return = $htmlImg . ' ' . $htmlLabel;
14638 } elseif ($displayMode === 5) {
14639 $return = $htmlLabelShort . ' ' . $htmlImg;
14640 } else { // $displayMode >= 6
14641 $return = $htmlLabel . ' ' . $htmlImg;
14642 }
14643 } elseif (!getDolGlobalString('MAIN_STATUS_USES_IMAGES') && !empty($displayMode)) {
14644 // Use new badge
14645 $statusLabelShort = (empty($statusLabelShort) ? $statusLabel : $statusLabelShort);
14646
14647 $dolGetBadgeParams['attr']['class'] = 'badge-status';
14648 if (empty($dolGetBadgeParams['attr']['title'])) {
14649 $dolGetBadgeParams['attr']['title'] = empty($params['tooltip']) ? $statusLabel : ($params['tooltip'] != 'no' ? $params['tooltip'] : '');
14650 } else { // If a title was forced from $params['badgeParams']['attr']['title'], we set the class to get it as a tooltip.
14651 $dolGetBadgeParams['attr']['class'] .= ' classfortooltip';
14652 // And if we use tooltip, we can output title in HTML @phan-suppress-next-line PhanTypeInvalidDimOffset
14653 $dolGetBadgeParams['attr']['title'] = dol_htmlentitiesbr((string) $dolGetBadgeParams['attr']['title'], 1);
14654 }
14655
14656 if ($displayMode == 3) {
14657 $return = dolGetBadge((empty($conf->dol_optimize_smallscreen) ? $statusLabel : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort)), '', $statusType, 'dot', $url, $dolGetBadgeParams);
14658 } elseif ($displayMode === 5) {
14659 $return = dolGetBadge($statusLabelShort, $html, $statusType, '', $url, $dolGetBadgeParams);
14660 } else {
14661 $return = dolGetBadge(((empty($conf->dol_optimize_smallscreen) && $displayMode != 2) ? $statusLabel : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort)), $html, $statusType, '', $url, $dolGetBadgeParams);
14662 }
14663 }
14664
14665 return $return;
14666}
14667
14668
14704function dolGetButtonAction($label, $text = '', $actionType = 'default', $url = '', $id = '', $userRight = 1, $params = array())
14705{
14706 global $hookmanager, $action, $object, $langs;
14707
14708 // If $url is an array, we must build a dropdown button or recursively iterate over each value
14709 if (is_array($url)) {
14710 // Loop on $url array to remove entries of disabled modules
14711 foreach ($url as $key => $subbutton) {
14712 if (isset($subbutton['enabled']) && empty($subbutton['enabled'])) {
14713 unset($url[$key]);
14714 }
14715 }
14716
14717 $out = '';
14718
14719 if (array_key_exists('areDropdownButtons', $params) && $params["areDropdownButtons"] === false) { // @phan-suppress-current-line PhanTypeInvalidDimOffset
14720 foreach ($url as $button) {
14721 if (!empty($button['lang'])) {
14722 $langs->load($button['lang']);
14723 }
14724 $label = $langs->trans($button['label']);
14725 $text = $button['text'] ?? '';
14726 $actionType = $button['actionType'] ?? '';
14727 $tmpUrl = DOL_URL_ROOT . $button['url'] . (empty($params['backtopage']) ? '' : '&amp;backtopage=' . urlencode($params['backtopage']));
14728 $id = $button['id'] ?? '';
14729 $userRight = $button['perm'] ?? 1;
14730 $button['params'] = $button['params'] ?? []; // @phan-suppress-current-line PhanPluginDuplicateExpressionAssignmentOperation
14731
14732 $out .= dolGetButtonAction($label, $text, $actionType, $tmpUrl, $id, $userRight, $button['params']);
14733 }
14734 return $out;
14735 }
14736
14737 if (count($url) > 1 || !empty($params["forceDropdownButtons"])) {
14738 $out .= '<div class="dropdown inline-block dropdown-holder">';
14739 $out .= '<a style="margin-right: auto;" class="dropdown-toggle classfortooltip butAction' . ($userRight ? '' : 'Refused') . '" title="' . dol_escape_htmltag($label) . '" data-toggle="dropdown">' . ($text ? $text : $label) . '</a>';
14740 $out .= '<div class="dropdown-content">';
14741 foreach ($url as $subbutton) {
14742 if (!empty($subbutton['lang'])) {
14743 $langs->load($subbutton['lang']);
14744 }
14745
14746 if (!empty($subbutton['urlraw'])) {
14747 $tmpurl = $subbutton['urlraw']; // Use raw url, no url completion, use only what developer send
14748 } else {
14749 $tmpurl = !empty($subbutton['urlroot']) ? $subbutton['urlroot'] : $subbutton['url'];
14750 $tmpurl = dolCompletUrlForDropdownButton($tmpurl, $params, empty($subbutton['urlroot']));
14751 }
14752
14753 $subbuttonparam = array();
14754 if (!empty($subbutton['attr'])) {
14755 $subbuttonparam['attr'] = $subbutton['attr'];
14756 }
14757 $subbuttonparam['isDropDown'] = (empty($params['isDropDown']) ? ($subbutton['isDropDown'] ?? false) : $params['isDropDown']);
14758
14759 $out .= dolGetButtonAction($subbutton['text'] ?? '', $langs->trans($subbutton['label']), 'default', $tmpurl, $subbutton['id'] ?? '', $subbutton['perm'], $subbuttonparam);
14760 }
14761 $out .= "</div>";
14762 $out .= "</div>";
14763 } else {
14764 foreach ($url as $subbutton) { // Should loop on 1 record only
14765 if (!empty($subbutton['lang'])) {
14766 $langs->load($subbutton['lang']);
14767 }
14768
14769 if (!empty($subbutton['urlraw'])) {
14770 $tmpurl = $subbutton['urlraw']; // Use raw url, no url completion, use only what developer send
14771 } else {
14772 $tmpurl = !empty($subbutton['urlroot']) ? $subbutton['urlroot'] : $subbutton['url'];
14773 $tmpurl = dolCompletUrlForDropdownButton($tmpurl, $params, empty($subbutton['urlroot']));
14774 }
14775
14776 $label = $langs->trans($subbutton['label']);
14777 $text = $subbutton['text'] ?? '';
14778 if (empty($text)) {
14779 $text = $label;
14780 $label = '';
14781 }
14782
14783 $out .= dolGetButtonAction($label, $text, 'default', $tmpurl, '', $subbutton['perm'], $params);
14784 }
14785 }
14786
14787 return $out;
14788 }
14789
14790 // Here, $url is a simple link
14791 if (!empty($params['isDropdown']) || !empty($params['isDropDown'])) { // Use the dropdown-item style (not for action button)
14792 $class = "dropdown-item";
14793 } else {
14794 $class = 'butAction';
14795 if ($actionType == 'edit') {
14796 $class = 'butAction butActionEdit';
14797 } elseif ($actionType == 'email') {
14798 $class = 'butAction butActionEmail';
14799 } elseif ($actionType == 'clone') {
14800 $class = 'butAction butActionClone';
14801 } elseif ($actionType == 'cancel') {
14802 $class = 'butAction butActionDelete';
14803 } elseif ($actionType == 'danger' || $actionType == 'delete') {
14804 $class = 'butAction butActionDelete';
14805 if (!empty($url) && strpos($url, 'token=') === false) {
14806 $url .= '&token=' . newToken();
14807 }
14808 }
14809 }
14810 $attr = array(
14811 'class' => $class,
14812 'href' => empty($url) ? '' : $url,
14813 'title' => $label
14814 );
14815
14816 if (empty($text)) {
14817 $text = $label;
14818 $attr['title'] = ''; // if html not set, using label on title is redundant
14819 } else {
14820 $attr['title'] = $label;
14821 $attr['aria-label'] = $label;
14822 }
14823
14824 if (empty($userRight) || $userRight < 0) {
14825 $attr['class'] = 'butActionRefused';
14826 $attr['href'] = '';
14827 $attr['title'] = (($label && $text && $label != $text) ? $label : '');
14828 $attr['title'] = ($attr['title'] ? $attr['title'] . (empty($userRight) ? '<br>' : '') : '');
14829 $attr['title'] .= ((empty($userRight) && empty($label)) ? $langs->trans('NotEnoughPermissions') : '');
14830 }
14831
14832 if (!empty($id)) {
14833 $attr['id'] = $id;
14834 }
14835
14836 // Override attr
14837 if (!empty($params['attr']) && is_array($params['attr'])) {
14838 foreach ($params['attr'] as $key => $value) {
14839 if ($key == 'class') {
14840 $attr['class'] .= ' ' . $value;
14841 } elseif ($key == 'classOverride') {
14842 $attr['class'] = $value;
14843 } else {
14844 $attr[$key] = $value;
14845 }
14846 }
14847 }
14848
14849 // automatic add tooltip when title is detected
14850 if (!empty($attr['title']) && !empty($attr['class']) && strpos($attr['class'], 'classfortooltip') === false) {
14851 $attr['class'] .= ' classfortooltip';
14852 }
14853
14854 // Js Confirm button
14855 if ($userRight && !empty($params['confirm'])) {
14856 if (!is_array($params['confirm'])) {
14857 $params['confirm'] = array();
14858 }
14859
14860 if (empty($params['confirm']['url'])) {
14861 $params['confirm']['url'] = $url . (strpos($url, '?') > 0 ? '&' : '?') . 'confirm=yes';
14862 }
14863
14864 // for js disabled compatibility set $url as call to confirm action and $params['confirm']['url'] to confirmed action
14865 $attr['data-confirm-url'] = $params['confirm']['url'];
14866 $attr['data-confirm-title'] = !empty($params['confirm']['title']) ? $params['confirm']['title'] : $langs->trans('ConfirmBtnCommonTitle', $label);
14867 $attr['data-confirm-content'] = !empty($params['confirm']['content']) ? $params['confirm']['content'] : $langs->trans('ConfirmBtnCommonContent', $label);
14868 $attr['data-confirm-content'] = preg_replace("/\r|\n/", "", $attr['data-confirm-content']);
14869 $attr['data-confirm-action-btn-label'] = !empty($params['confirm']['action-btn-label']) ? $params['confirm']['action-btn-label'] : $langs->trans('Confirm');
14870 $attr['data-confirm-cancel-btn-label'] = !empty($params['confirm']['cancel-btn-label']) ? $params['confirm']['cancel-btn-label'] : $langs->trans('CloseDialog');
14871 $attr['data-confirm-modal'] = !empty($params['confirm']['modal']) ? $params['confirm']['modal'] : true;
14872
14873 $attr['class'] .= ' butActionConfirm';
14874 }
14875
14876 if (isset($attr['href']) && empty($attr['href'])) {
14877 unset($attr['href']);
14878 }
14879
14880 // TODO replace this $TCompiledAttr generation block by commonHtmlAttributeBuilder like line below
14881 // $TCompiledAttr = commonHtmlAttributeBuilder($attr, $params['use_unsecured_unescapedattr'] ?? []);
14882 $TCompiledAttr = array();
14883 foreach ($attr as $key => $value) {
14884 if (!empty($params['use_unsecured_unescapedattr']) && is_array($params['use_unsecured_unescapedattr']) && in_array($key, $params['use_unsecured_unescapedattr'])) {
14885 // Deprecated, forbidden.
14886 $value = dol_htmlentities($value, ENT_QUOTES | ENT_SUBSTITUTE);
14887 } elseif (in_array($key, ['href', 'data-confirm-url'])) {
14888 $value = dolPrintHTMLForAttributeUrl($value);
14889 } else {
14890 $value = dolPrintHTMLForAttribute($value);
14891 }
14892
14893 $TCompiledAttr[] = $key . '="' . $value . '"'; // $value has been escaped by the dolPrintHTMLForAttribute... just before
14894 }
14895 $compiledAttributes = empty($TCompiledAttr) ? '' : implode(' ', $TCompiledAttr);
14896
14897 $tag = !empty($attr['href']) ? 'a' : 'span';
14898
14899 $parameters = array(
14900 'TCompiledAttr' => $TCompiledAttr, // array
14901 'compiledAttributes' => $compiledAttributes, // string
14902 'attr' => $attr,
14903 'tag' => $tag,
14904 'label' => $label,
14905 'html' => $text,
14906 'actionType' => $actionType,
14907 'url' => $url,
14908 'id' => $id,
14909 'userRight' => $userRight,
14910 'params' => $params
14911 );
14912
14913 $reshook = $hookmanager->executeHooks('dolGetButtonAction', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
14914 if ($reshook < 0) {
14915 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
14916 }
14917
14918 if (empty($reshook)) {
14919 if (dol_textishtml($text)) { // If content already HTML encoded
14920 return '<' . $tag . ' ' . $compiledAttributes . '><span class="textbutton">' . $text . '</span></' . $tag . '>';
14921 } else {
14922 return '<' . $tag . ' ' . $compiledAttributes . '><span class="textbutton">' . dol_escape_htmltag($text) . '</span></' . $tag . '>';
14923 }
14924 } else {
14925 return $hookmanager->resPrint;
14926 }
14927}
14928
14959function commonHtmlAttributeBuilder($attr, array $unescapedAttr = [])
14960{
14961 $TCompiledAttr = array();
14962 if (empty($attr)) {
14963 return [];
14964 }
14965
14966 foreach ($attr as $key => $value) {
14967 // special boolean attributes case
14968 if (in_array($key, getListOfHtmlBooleanAttributes())) {
14969 if ($value) {
14970 $TCompiledAttr[$key] = $key;
14971 }
14972 continue;
14973 }
14974
14975 if (!empty($unescapedAttr) && in_array($key, $unescapedAttr)) {
14976 // Not recommended
14977 $value = dol_htmlentities((string) $value, ENT_QUOTES | ENT_SUBSTITUTE);
14978 } elseif ($key == 'href') {
14979 $value = dolPrintHTMLForAttributeUrl((string) $value);
14980 } else {
14981 $value = dolPrintHTMLForAttribute((string) $value);
14982 }
14983
14984 $TCompiledAttr[$key] = $key . '="' . $value . '"'; // $value has been escaped by the dolPrintHTMLForAttribute... just before
14985 }
14986
14987 return $TCompiledAttr;
14988}
14989
15004function getListOfHtmlBooleanAttributes(): array
15005{
15006 return [
15007 // Input / Form
15008 'checked',
15009 'disabled',
15010 'readonly',
15011 'required',
15012 'autofocus',
15013 'multiple',
15014
15015 // Option
15016 'selected',
15017
15018 // Form / General
15019 'novalidate',
15020 'formnovalidate',
15021
15022 // Media
15023 'autoplay',
15024 'controls',
15025 'loop',
15026 'muted',
15027 'playsinline',
15028
15029 // Other elements
15030 'hidden',
15031 'open',
15032 'ismap',
15033 'reversed',
15034 'allowfullscreen',
15035 'itemscope',
15036 'nomodule',
15037 'defer',
15038 'async',
15039 'default',
15040 'inert',
15041 ];
15042}
15043
15044
15053function dolCompletUrlForDropdownButton(string $url, array $params, bool $addDolUrlRoot = true)
15054{
15055 if (empty($url)) {
15056 return '';
15057 }
15058
15059 $parsedUrl = parse_url($url);
15060 if ((isset($parsedUrl['scheme']) && in_array($parsedUrl['scheme'], ['javascript', 'mailto', 'tel'])) || strpos($url, '#') === 0) {
15061 return $url;
15062 }
15063
15064 if (!empty($parsedUrl['query'])) {
15065 // Use parse_str() function to parse the string passed via URL
15066 $urlQuery = '';
15067 parse_str($parsedUrl['query'], $urlQuery);
15068 if (!isset($urlQuery['backtopage']) && isset($params['backtopage'])) {
15069 $url .= '&amp;backtopage=' . urlencode($params['backtopage']);
15070 }
15071 }
15072
15073 if (!isset($parsedUrl['scheme']) && $addDolUrlRoot) {
15074 $url = DOL_URL_ROOT . $url;
15075 }
15076
15077 return $url;
15078}
15079
15080
15087function dolGetButtonTitleSeparator($moreClass = "")
15088{
15089 return '<span class="button-title-separator ' . $moreClass . '" ></span>';
15090}
15091
15098function getFieldErrorIcon($fieldValidationErrorMsg)
15099{
15100 $out = '';
15101 if (!empty($fieldValidationErrorMsg)) {
15102 $out .= '<span class="field-error-icon classfortooltip" title="' . dol_escape_htmltag($fieldValidationErrorMsg, 1) . '" role="alert" >'; // role alert is used for accessibility
15103 $out .= '<span class="fa fa-exclamation-circle" aria-hidden="true" ></span>'; // For accessibility icon is separated and aria-hidden
15104 $out .= '</span>';
15105 }
15106
15107 return $out;
15108}
15109
15122function dolGetButtonTitle($label, $helpText = '', $iconClass = 'fa fa-file', $url = '', $id = '', $status = 1, $params = array())
15123{
15124 global $langs, $user;
15125
15126 // Actually this conf is used in css too for external module compatibility and smooth transition to this function
15127 if (getDolGlobalString('MAIN_BUTTON_HIDE_UNAUTHORIZED') && (!$user->admin) && $status <= 0) {
15128 return '';
15129 }
15130 // Fix old picto fa-th-list to use fa-grid-vertical instead
15131 if ($iconClass == 'fa fa-th-list imgforviewmode') {
15132 $iconClass = ' fa fa-grip-horizontal imgforviewmode';
15133 }
15134
15135 $class = 'btnTitle';
15136 if (in_array($iconClass, array('fa fa-plus-circle', 'fa fa-plus-circle size15x', 'fa fa-comment-dots', 'fa fa-paper-plane'))) {
15137 $class .= ' btnTitlePlus';
15138 }
15139 $useclassfortooltip = 1;
15140
15141 if (!empty($params['morecss'])) {
15142 $class .= ' ' . $params['morecss'];
15143 }
15144
15145 $attr = array(
15146 'class' => $class,
15147 'href' => empty($url) ? '' : $url
15148 );
15149
15150 if (!empty($helpText)) {
15151 $attr['title'] = $helpText;
15152 } elseif ($label) { // empty($attr['title']) &&
15153 $attr['title'] = $label;
15154 $useclassfortooltip = 0;
15155 }
15156
15157 if ($status == 2) {
15158 $attr['class'] .= ' btnTitleSelected';
15159 } elseif ($status <= 0) {
15160 $attr['class'] .= ' refused';
15161
15162 $attr['href'] = '';
15163
15164 if ($status == -1) { // disable
15165 $attr['title'] = $langs->transnoentitiesnoconv("FeatureDisabled");
15166 } elseif ($status == 0) { // Not enough permissions
15167 $attr['title'] = $langs->transnoentitiesnoconv("NotEnoughPermissions");
15168 }
15169 }
15170
15171 if (!empty($attr['title']) && $useclassfortooltip) {
15172 $attr['class'] .= ' classfortooltip';
15173 }
15174
15175 if (!empty($id)) {
15176 $attr['id'] = $id;
15177 }
15178
15179 // Override attr
15180 if (!empty($params['attr']) && is_array($params['attr'])) {
15181 foreach ($params['attr'] as $key => $value) {
15182 if ($key == 'class') {
15183 $attr['class'] .= ' ' . $value;
15184 } elseif ($key == 'classOverride') {
15185 $attr['class'] = $value;
15186 } else {
15187 $attr[$key] = $value;
15188 }
15189 }
15190 }
15191
15192 if (isset($attr['href']) && empty($attr['href'])) {
15193 unset($attr['href']);
15194 }
15195
15196 // TODO : add a hook
15197
15198 // Generate attributes with escapement
15199 $TCompiledAttr = array();
15200 foreach ($attr as $key => $value) {
15201 $TCompiledAttr[] = $key . '="' . dol_escape_htmltag($value) . '"'; // Do not use dolPrintHTMLForAttribute() here, we must accept "javascript:string"
15202 }
15203
15204 $compiledAttributes = (empty($TCompiledAttr) ? '' : implode(' ', $TCompiledAttr));
15205
15206 $tag = (empty($attr['href']) ? 'span' : 'a');
15207
15208 $button = '<' . $tag . ' ' . $compiledAttributes . '>';
15209 $button .= '<span class="' . $iconClass . ' valignmiddle btnTitle-icon"></span>';
15210 if (!empty($params['forcenohideoftext'])) {
15211 $button .= '<span class="valignmiddle text-plus-circle btnTitle-label' . (empty($params['forcenohideoftext']) ? ' hideonsmartphone' : '') . '">' . $label . '</span>';
15212 }
15213 $button .= '</' . $tag . '>';
15214
15215 return $button;
15216}
15217
15228function getElementProperties($elementType)
15229{
15230 global $conf, $db, $hookmanager;
15231
15232 $regs = array();
15233
15234 //$element_type='facture';
15235
15236 $classfile = $classname = $classpath = $subdir = $dir_output = $dir_temp = $parent_element = '';
15237
15238 // Parse element/subelement
15239 $module = $elementType;
15240 $element = $elementType;
15241 $subelement = $elementType;
15242 $table_element = $elementType;
15243
15244 // If we ask a resource form external module (instead of default path)
15245 if (preg_match('/^([^@]+)@([^@]+)$/i', $elementType, $regs)) { // 'myobject@mymodule'
15246 $element = $subelement = $regs[1];
15247 $module = $regs[2];
15248 }
15249
15250 // If we ask a resource for a string with an element and a subelement
15251 // Example 'project_task'
15252 if (preg_match('/^([^_]+)_([^_]+)/i', $element, $regs)) { // 'myobject_mysubobject' with myobject=mymodule
15253 $module = $element = $regs[1];
15254 $subelement = $regs[2];
15255 }
15256
15257 // Object lines will use parent classpath and module ref
15258 if (substr($elementType, -3) == 'det') {
15259 $module = preg_replace('/det$/', '', $element);
15260 $subelement = preg_replace('/det$/', '', $subelement);
15261 $classpath = $module . '/class';
15262 $classfile = $module;
15263 $classname = preg_replace('/det$/', 'Line', $element);
15264 if (in_array($module, array('expedition', 'propale', 'facture', 'contrat', 'fichinter', 'supplier_order', 'commandefournisseur'))) {
15265 $classname = preg_replace('/det$/', 'Ligne', $element);
15266 }
15267 }
15268 // For compatibility and to work with non standard path
15269 if ($elementType == "action" || $elementType == "actioncomm") {
15270 $classpath = 'comm/action/class';
15271 $subelement = 'Actioncomm';
15272 $module = 'agenda';
15273 $table_element = 'actioncomm';
15274 } elseif ($elementType == 'cronjob') {
15275 $classpath = 'cron/class';
15276 $module = 'cron';
15277 $table_element = 'cron';
15278 } elseif ($elementType == 'adherent_type') {
15279 $classpath = 'adherents/class';
15280 $classfile = 'adherent_type';
15281 $module = 'adherent';
15282 $subelement = 'adherent_type';
15283 $classname = 'AdherentType';
15284 $table_element = 'adherent_type';
15285 } elseif ($elementType == 'bank_account') {
15286 $classpath = 'compta/bank/class';
15287 $module = 'bank'; // We need $conf->bank->dir_output and not $conf->banque->dir_output
15288 $classfile = 'account';
15289 $classname = 'Account';
15290 } elseif ($elementType == 'bank_line') {
15291 $classpath = 'compta/bank/class';
15292 $module = 'bank'; // We need $conf->bank->dir_output and not $conf->banque->dir_output
15293 $classfile = 'account';
15294 $classname = 'AccountLine';
15295 } elseif ($elementType == 'category') {
15296 $classpath = 'categories/class';
15297 $module = 'categorie';
15298 $subelement = 'categorie';
15299 $table_element = 'categorie';
15300 } elseif ($elementType == 'contact') {
15301 $classpath = 'contact/class';
15302 $classfile = 'contact';
15303 $module = 'societe';
15304 $subelement = 'contact';
15305 $table_element = 'socpeople';
15306 } elseif ($elementType == 'inventory') {
15307 $module = 'product';
15308 $classpath = 'product/inventory/class';
15309 } elseif ($elementType == 'inventoryline') {
15310 $module = 'product';
15311 $classpath = 'product/inventory/class';
15312 $table_element = 'inventorydet';
15313 $parent_element = 'inventory';
15314 } elseif ($elementType == 'stock' || $elementType == 'entrepot' || $elementType == 'warehouse') {
15315 $module = 'stock';
15316 $classpath = 'product/stock/class';
15317 $classfile = 'entrepot';
15318 $classname = 'Entrepot';
15319 $table_element = 'entrepot';
15320 } elseif ($elementType == 'project') {
15321 $classpath = 'projet/class';
15322 $module = 'projet';
15323 $table_element = 'projet';
15324 } elseif ($elementType == 'project_task') {
15325 $classpath = 'projet/class';
15326 $module = 'projet';
15327 $subelement = 'task';
15328 $table_element = 'projet_task';
15329 } elseif ($elementType == 'mo') {
15330 $classpath = 'mrp/class';
15331 $module = 'mrp';
15332 $classfile = 'mo';
15333 $classname = 'Mo';
15334 $table_element = 'mrp_mo';
15335 } elseif ($elementType == 'facture' || $elementType == 'invoice') {
15336 $classpath = 'compta/facture/class';
15337 $module = 'facture';
15338 $subelement = 'facture';
15339 $table_element = 'facture';
15340 } elseif ($elementType == 'facturedet') {
15341 $classpath = 'compta/facture/class';
15342 $classfile = 'facture';
15343 $classname = 'FactureLigne';
15344 $module = 'facture';
15345 $table_element = 'facturedet';
15346 $parent_element = 'facture';
15347 } elseif ($elementType == 'facturerec' || $elementType == 'facture_rec') {
15348 $classpath = 'compta/facture/class';
15349 $classfile = 'facture-rec';
15350 $module = 'facture';
15351 $classname = 'FactureRec';
15352 } elseif ($elementType == 'commande' || $elementType == 'order') {
15353 $classpath = 'commande/class';
15354 $module = 'commande';
15355 $subelement = 'commande';
15356 $table_element = 'commande';
15357 } elseif ($elementType == 'commandedet') {
15358 $classpath = 'commande/class';
15359 $classfile = 'commande';
15360 $classname = 'OrderLine';
15361 $module = 'commande';
15362 $table_element = 'commandedet';
15363 $parent_element = 'commande';
15364 } elseif ($elementType == 'propal') {
15365 $classpath = 'comm/propal/class';
15366 $table_element = 'propal';
15367 } elseif ($elementType == 'propaldet') {
15368 $classpath = 'comm/propal/class';
15369 $classfile = 'propal';
15370 $subelement = 'propaleligne';
15371 $module = 'propal';
15372 $table_element = 'propaldet';
15373 $parent_element = 'propal';
15374 } elseif ($elementType == 'shipping' || $elementType == 'shipment') {
15375 $classpath = 'expedition/class';
15376 $classfile = 'expedition';
15377 $classname = 'Expedition';
15378 $module = 'expedition';
15379 $table_element = 'expedition';
15380 } elseif ($elementType == 'expeditiondet' || $elementType == 'shippingdet') {
15381 $classpath = 'expedition/class';
15382 $classfile = 'expedition';
15383 $classname = 'ExpeditionLigne';
15384 $module = 'expedition';
15385 $table_element = 'expeditiondet';
15386 $parent_element = 'expedition';
15387 } elseif ($elementType == 'delivery_note') {
15388 $classpath = 'delivery/class';
15389 $subelement = 'delivery';
15390 $module = 'expedition';
15391 } elseif ($elementType == 'delivery') {
15392 $classpath = 'delivery/class';
15393 $subelement = 'delivery';
15394 $module = 'expedition';
15395 } elseif ($elementType == 'deliverydet') {
15396 // @todo
15397 } elseif ($elementType == 'supplier_proposal') {
15398 $classpath = 'supplier_proposal/class';
15399 $module = 'supplier_proposal';
15400 $element = 'supplierproposal';
15401 $classfile = 'supplier_proposal';
15402 $subelement = 'supplierproposal';
15403 } elseif ($elementType == 'supplier_proposaldet') {
15404 $classpath = 'supplier_proposal/class';
15405 $module = 'supplier_proposal';
15406 $classfile = 'supplier_proposal';
15407 $classname = 'SupplierProposalLine';
15408 $table_element = 'supplier_proposaldet';
15409 $parent_element = 'supplier_proposal';
15410 } elseif ($elementType == 'contract') {
15411 $classpath = 'contrat/class';
15412 $module = 'contrat';
15413 $subelement = 'contrat';
15414 $table_element = 'contract';
15415 } elseif ($elementType == 'contratdet') {
15416 $classpath = 'contrat/class';
15417 $module = 'contrat';
15418 $table_element = 'contratdet';
15419 $parent_element = 'contrat';
15420 } elseif ($elementType == 'mailing') {
15421 $classpath = 'comm/mailing/class';
15422 $module = 'mailing';
15423 $classfile = 'mailing';
15424 $classname = 'Mailing';
15425 $subelement = '';
15426 } elseif ($elementType == 'member' || $elementType == 'adherent') {
15427 $classpath = 'adherents/class';
15428 $module = 'adherent';
15429 $subelement = 'adherent';
15430 $table_element = 'adherent';
15431 } elseif ($elementType == 'subscription') {
15432 $classpath = 'adherents/class';
15433 $classfile = 'subscription';
15434 $module = 'adherent';
15435 $subelement = 'subscription';
15436 $classname = 'Subscription';
15437 $table_element = 'subscription';
15438 } elseif ($elementType == 'usergroup') {
15439 $classpath = 'user/class';
15440 $module = 'user';
15441 } elseif ($elementType == 'mo' || $elementType == 'mrp') {
15442 $classpath = 'mrp/class';
15443 $classfile = 'mo';
15444 $classname = 'Mo';
15445 $module = 'mrp';
15446 $subelement = '';
15447 $table_element = 'mrp_mo';
15448 } elseif ($elementType == 'mrp_production') {
15449 $classpath = 'mrp/class';
15450 $classfile = 'mo';
15451 $classname = 'MoLine';
15452 $module = 'mrp';
15453 $subelement = '';
15454 $table_element = 'mrp_production';
15455 $parent_element = 'mo';
15456 } elseif ($elementType == 'cabinetmed_cons') {
15457 $classpath = 'cabinetmed/class';
15458 $module = 'cabinetmed';
15459 $subelement = 'cabinetmedcons';
15460 $table_element = 'cabinetmedcons';
15461 } elseif ($elementType == 'fichinter') {
15462 $classpath = 'fichinter/class';
15463 $module = 'ficheinter';
15464 $subelement = 'fichinter';
15465 $table_element = 'fichinter';
15466 } elseif ($elementType == 'dolresource' || $elementType == 'resource') {
15467 $classpath = 'resource/class';
15468 $module = 'resource';
15469 $subelement = 'dolresource';
15470 $table_element = 'resource';
15471 } elseif ($elementType == 'opensurvey_sondage') {
15472 $classpath = 'opensurvey/class';
15473 $module = 'opensurvey';
15474 $subelement = 'opensurveysondage';
15475 } elseif ($elementType == 'order_supplier' || $elementType == 'supplier_order' || $elementType == 'commande_fournisseur' || $elementType == 'commandefournisseur') {
15476 $classpath = 'fourn/class';
15477 $module = 'fournisseur';
15478 $classfile = 'fournisseur.commande';
15479 $element = 'order_supplier';
15480 $subelement = '';
15481 $classname = 'CommandeFournisseur';
15482 $table_element = 'commande_fournisseur';
15483 } elseif ($elementType == 'commande_fournisseurdet') {
15484 $classpath = 'fourn/class';
15485 $module = 'fournisseur';
15486 $classfile = 'fournisseur.commande';
15487 $element = 'commande_fournisseurdet';
15488 $subelement = '';
15489 $classname = 'CommandeFournisseurLigne';
15490 $table_element = 'commande_fournisseurdet';
15491 $parent_element = 'commande_fournisseur';
15492 } elseif ($elementType == 'invoice_supplier' || $elementType == 'supplier_invoice' || $elementType == 'facture_fourn') {
15493 $classpath = 'fourn/class';
15494 $module = 'fournisseur';
15495 $classfile = 'fournisseur.facture';
15496 $element = 'invoice_supplier';
15497 $subelement = '';
15498 $classname = 'FactureFournisseur';
15499 $table_element = 'facture_fourn';
15500 } elseif ($elementType == 'facture_fourn_det') {
15501 $classpath = 'fourn/class';
15502 $module = 'fournisseur';
15503 $classfile = 'fournisseur.facture';
15504 $element = 'facture_fourn_det';
15505 $subelement = '';
15506 $classname = 'SupplierInvoiceLine';
15507 $table_element = 'facture_fourn_det';
15508 $parent_element = 'invoice_supplier';
15509 } elseif ($elementType == "service") {
15510 $classpath = 'product/class';
15511 $subelement = 'product';
15512 $table_element = 'product';
15513 } elseif ($elementType == 'product_attribute') {
15514 $module = 'variants';
15515 $element = 'product_attribute';
15516 $subelement = 'product_attribute';
15517 $classpath = 'variants/class';
15518 $classfile = 'ProductAttribute';
15519 $classname = 'ProductAttribute';
15520 $table_element = 'product_attribute';
15521 } elseif ($elementType == 'product_attribute_value') {
15522 $module = 'variants';
15523 $element = 'product_attribute_value';
15524 $subelement = 'product_attribute_value';
15525 $classpath = 'variants/class';
15526 $classfile = 'ProductAttributeValue';
15527 $classname = 'ProductAttributeValue';
15528 $table_element = 'product_attribute_value';
15529 $parent_element = 'product_attribute';
15530 } elseif ($elementType == 'salary') {
15531 $classpath = 'salaries/class';
15532 $module = 'salaries';
15533 } elseif ($elementType == 'payment_salary') {
15534 $classpath = 'salaries/class';
15535 $classfile = 'paymentsalary';
15536 $classname = 'PaymentSalary';
15537 $module = 'salaries';
15538 } elseif ($elementType == 'productlot') {
15539 $module = 'productbatch';
15540 $classpath = 'product/stock/class';
15541 $classfile = 'productlot';
15542 $classname = 'Productlot';
15543 $element = 'productlot';
15544 $subelement = '';
15545 $table_element = 'product_lot';
15546 } elseif ($elementType == 'societeaccount') {
15547 $classpath = 'societe/class';
15548 $classfile = 'societeaccount';
15549 $classname = 'SocieteAccount';
15550 $module = 'societe';
15551 } elseif ($elementType == 'websitepage' || $elementType == 'website_page') {
15552 $classpath = 'website/class';
15553 $classfile = 'websitepage';
15554 $classname = 'Websitepage';
15555 $module = 'website';
15556 $subelement = 'websitepage';
15557 $table_element = 'website_page';
15558 } elseif ($elementType == 'fiscalyear') {
15559 $classpath = 'core/class';
15560 $module = 'accounting';
15561 $subelement = 'fiscalyear';
15562 } elseif ($elementType == 'chargesociales') {
15563 $classpath = 'compta/sociales/class';
15564 $module = 'tax';
15565 $table_element = 'chargesociales';
15566 } elseif ($elementType == 'tva') {
15567 $classpath = 'compta/tva/class';
15568 $module = 'tax';
15569 $subdir = '/vat';
15570 $table_element = 'tva';
15571 } elseif ($elementType == 'emailsenderprofile') {
15572 $module = '';
15573 $classpath = 'core/class';
15574 $classfile = 'emailsenderprofile';
15575 $classname = 'EmailSenderProfile';
15576 $table_element = 'c_email_senderprofile';
15577 $subelement = '';
15578 } elseif ($elementType == 'conferenceorboothattendee') {
15579 $classpath = 'eventorganization/class';
15580 $classfile = 'conferenceorboothattendee';
15581 $classname = 'ConferenceOrBoothAttendee';
15582 $module = 'eventorganization';
15583 } elseif ($elementType == 'conferenceorbooth') {
15584 $classpath = 'eventorganization/class';
15585 $classfile = 'conferenceorbooth';
15586 $classname = 'ConferenceOrBooth';
15587 $module = 'eventorganization';
15588 } elseif ($elementType == 'ccountry') {
15589 $module = '';
15590 $classpath = 'core/class';
15591 $classfile = 'ccountry';
15592 $classname = 'Ccountry';
15593 $table_element = 'c_country';
15594 $subelement = '';
15595 } elseif ($elementType == 'ecmfiles') {
15596 $module = 'ecm';
15597 $classpath = 'ecm/class';
15598 $classfile = 'ecmfiles';
15599 $classname = 'Ecmfiles';
15600 $table_element = 'ecmfiles';
15601 $subelement = '';
15602 } elseif ($elementType == 'knowledgerecord' || $elementType == 'knowledgemanagement') {
15603 $module = 'knowledgemanagement';
15604 $classpath = 'knowledgemanagement/class';
15605 $classfile = 'knowledgerecord';
15606 $classname = 'KnowledgeRecord';
15607 $table_element = 'knowledgemanagement_knowledgerecord';
15608 $subelement = '';
15609 } elseif ($elementType == 'customer') {
15610 $module = 'societe';
15611 $classpath = 'societe/class';
15612 $classfile = 'client';
15613 $classname = 'Client';
15614 $table_element = 'societe';
15615 $subelement = '';
15616 } elseif ($elementType == 'fournisseur' || $elementType == 'supplier') {
15617 $module = 'societe';
15618 $classpath = 'fourn/class';
15619 $classfile = 'fournisseur';
15620 $classname = 'Fournisseur';
15621 $table_element = 'societe';
15622 $subelement = '';
15623 } elseif ($elementType == 'recruitmentcandidature') {
15624 $module = 'recruitment';
15625 $classfile = 'recruitmentcandidature';
15626 $classpath = 'recruitment/class';
15627 $classname = 'RecruitmentCandidature';
15628 $subelement = 'recruitmentcandidature';
15629 $subdir = '/recruitmentcandidature';
15630 } elseif ($elementType == 'recruitmentjobposition') {
15631 $module = 'recruitment';
15632 $classfile = 'recruitmentjobposition';
15633 $classpath = 'recruitment/class';
15634 $classname = 'RecruitmentJobPosition';
15635 $subelement = 'recruitmentjobposition';
15636 $subdir = '/recruitmentjobposition';
15637 }
15638
15639
15640 if (empty($classfile)) {
15641 $classfile = strtolower($subelement);
15642 }
15643 if (empty($classname)) {
15644 $classname = ucfirst($subelement);
15645 }
15646 if (empty($classpath)) {
15647 $classpath = $module . '/class';
15648 }
15649
15650 //print 'getElementProperties subdir='.$subdir;
15651
15652 // Set dir_output
15653 if ($module && isset($conf->$module)) { // The generic case
15654 if (!empty($conf->$module->multidir_output[$conf->entity])) {
15655 $dir_output = $conf->$module->multidir_output[$conf->entity];
15656 } elseif (!empty($conf->$module->output[$conf->entity])) {
15657 $dir_output = $conf->$module->output[$conf->entity];
15658 } elseif (!empty($conf->$module->dir_output)) {
15659 $dir_output = $conf->$module->dir_output;
15660 }
15661 if (!empty($conf->$module->multidir_temp[$conf->entity])) {
15662 $dir_temp = $conf->$module->multidir_temp[$conf->entity];
15663 } elseif (!empty($conf->$module->temp[$conf->entity])) {
15664 $dir_temp = $conf->$module->temp[$conf->entity];
15665 } elseif (!empty($conf->$module->dir_temp)) {
15666 $dir_temp = $conf->$module->dir_temp;
15667 }
15668 }
15669
15670 // Overwrite value for special cases
15671 if ($element == 'order_supplier' && isModEnabled('fournisseur')) {
15672 $dir_output = $conf->fournisseur->commande->dir_output;
15673 $dir_temp = $conf->fournisseur->commande->dir_temp;
15674 } elseif ($element == 'invoice_supplier' && isModEnabled('fournisseur')) {
15675 $dir_output = $conf->fournisseur->facture->dir_output;
15676 $dir_temp = $conf->fournisseur->facture->dir_temp;
15677 }
15678 $dir_output .= $subdir;
15679 $dir_temp .= $subdir;
15680
15681 $elementProperties = array(
15682 'module' => $module,
15683 'element' => $element,
15684 'table_element' => $table_element,
15685 'subelement' => $subelement,
15686 'classpath' => $classpath,
15687 'classfile' => $classfile,
15688 'classname' => $classname,
15689 'dir_output' => $dir_output,
15690 'dir_temp' => $dir_temp,
15691 'parent_element' => $parent_element,
15692 );
15693
15694
15695 // Add hook
15696 if (!is_object($hookmanager)) {
15697 include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
15698 $hookmanager = new HookManager($db);
15699 }
15700 $hookmanager->initHooks(array('elementproperties'));
15701
15702
15703 // Hook params
15704 $parameters = array(
15705 'elementType' => $elementType,
15706 'elementProperties' => $elementProperties
15707 );
15708
15709 $reshook = $hookmanager->executeHooks('getElementProperties', $parameters);
15710
15711 if ($reshook) {
15712 $elementProperties = $hookmanager->resArray;
15713 } elseif (!empty($hookmanager->resArray) && is_array($hookmanager->resArray)) { // resArray is always an array but for security against misconfigured external modules
15714 $elementProperties = array_replace($elementProperties, $hookmanager->resArray);
15715 }
15716
15717 // context of elementproperties doesn't need to exist out of this function so delete it to avoid elementproperties context is equal to all
15718 if (($key = array_search('elementproperties', $hookmanager->contextarray)) !== false) {
15719 unset($hookmanager->contextarray[$key]);
15720 }
15721
15722 return $elementProperties;
15723}
15724
15738function fetchObjectByElement($element_id, $element_type, $element_ref = '', $useCache = 0, $maxCacheByType = 10)
15739{
15740 global $db, $conf;
15741
15742 $ret = 0;
15743
15744 $element_prop = getElementProperties($element_type);
15745 //var_dump($element_prop); exit;
15746
15747 if ($element_prop['module'] == 'product' || $element_prop['module'] == 'service') {
15748 // For example, for an extrafield 'product' (shared for both product and service) that is a link to an object,
15749 // this is called with $element_type = 'product' when we need element properties of a service, we must return a product. If we create the
15750 // extrafield for a service, it is not supported and not found when editing the product/service card. So we must keep 'product' for extrafields
15751 // of service and we will return properties of a product.
15752 $ismodenabled = (isModEnabled('product') || isModEnabled('service'));
15753 } elseif ($element_prop['module'] == 'societeaccount') {
15754 $ismodenabled = isModEnabled('website') || isModEnabled('webportal');
15755 } else {
15756 $ismodenabled = isModEnabled($element_prop['module']);
15757 }
15758 //var_dump('element_type='.$element_type);
15759 //var_dump($element_prop);
15760 //var_dump($element_prop['module'].' '.$ismodenabled);
15761 if (is_array($element_prop) && (empty($element_prop['module']) || $ismodenabled)) {
15762 if ($useCache === 1 && $element_id > 0
15763 && !empty($conf->cache['fetchObjectByElement'][$element_type])
15764 && !empty($conf->cache['fetchObjectByElement'][$element_type][$element_id])
15765 && is_object($conf->cache['fetchObjectByElement'][$element_type][$element_id])
15766 ) {
15767 return $conf->cache['fetchObjectByElement'][$element_type][$element_id];
15768 }
15769
15770 dol_include_once('/' . $element_prop['classpath'] . '/' . $element_prop['classfile'] . '.class.php');
15771
15772 if (class_exists($element_prop['classname'])) {
15773 $className = $element_prop['classname'];
15774 $objecttmp = new $className($db);
15775 '@phan-var-force CommonObject $objecttmp';
15778 if ($element_id > 0 || !empty($element_ref)) {
15779 $ret = $objecttmp->fetch($element_id, $element_ref);
15780 if ($ret >= 0) {
15781 if (empty($objecttmp->module)) {
15782 $objecttmp->module = $element_prop['module'];
15783 }
15784
15785 if ($useCache > 0) {
15786 if (!isset($conf->cache['fetchObjectByElement'][$element_type])) {
15787 $conf->cache['fetchObjectByElement'][$element_type] = [];
15788 }
15789
15790 // Manage cache limit
15791 if (! empty($conf->cache['fetchObjectByElement'][$element_type]) && is_array($conf->cache['fetchObjectByElement'][$element_type]) && count($conf->cache['fetchObjectByElement'][$element_type]) >= $maxCacheByType) {
15792 array_shift($conf->cache['fetchObjectByElement'][$element_type]);
15793 }
15794
15795 $conf->cache['fetchObjectByElement'][$element_type][$element_id] = $objecttmp;
15796 }
15797
15798 return $objecttmp;
15799 }
15800 } else {
15801 return $objecttmp; // returned an object without fetch
15802 }
15803 } else {
15804 dol_syslog($element_prop['classname'] . ' doesn\'t exists in /' . $element_prop['classpath'] . '/' . $element_prop['classfile'] . '.class.php');
15805 return -1;
15806 }
15807 }
15808
15809 return $ret;
15810}
15811
15817function getExecutableContent()
15818{
15819 $arrayofregexextension = array(
15820 'htm',
15821 'html',
15822 'shtml',
15823 'js',
15824 'phar',
15825 'php',
15826 'php3',
15827 'php4',
15828 'php5',
15829 'phtml',
15830 'pht',
15831 'pl',
15832 'py',
15833 'cgi',
15834 'ksh',
15835 'sh',
15836 'shtml',
15837 'bash',
15838 'bat',
15839 'cmd',
15840 'wpk',
15841 'exe',
15842 'dmg',
15843 'appimage'
15844 );
15845
15846 return $arrayofregexextension;
15847}
15848
15855function isAFileWithExecutableContent($filename)
15856{
15857 $arrayofregexextension = getExecutableContent();
15858
15859 foreach ($arrayofregexextension as $fileextension) {
15860 if (preg_match('/\.' . preg_quote($fileextension, '/') . '$/i', $filename)) {
15861 return true;
15862 }
15863 }
15864
15865 return false;
15866}
15867
15875function newToken()
15876{
15877 return empty($_SESSION['newtoken']) ? '' : $_SESSION['newtoken'];
15878}
15879
15887function currentToken()
15888{
15889 return isset($_SESSION['token']) ? $_SESSION['token'] : '';
15890}
15891
15897function getNonce()
15898{
15899 global $conf;
15900
15901 if (empty($conf->cache['nonce'])) {
15902 include_once DOL_DOCUMENT_ROOT . '/core/lib/security.lib.php';
15903 $conf->cache['nonce'] = dolGetRandomBytes(8);
15904 }
15905
15906 return $conf->cache['nonce'];
15907}
15908
15909
15923function startSimpleTable($header, $link = "", $arguments = "", $emptyColumns = 0, $number = -1, $pictofulllist = '')
15924{
15925 global $langs;
15926
15927 print '<div class="div-table-responsive-no-min">';
15928 print '<table class="noborder centpercent">';
15929 print '<tr class="liste_titre">';
15930
15931 print ($emptyColumns < 1) ? '<th>' : '<th colspan="' . ($emptyColumns + 1) . '">';
15932
15933 print '<span class="valignmiddle">' . $langs->trans($header) . '</span>';
15934
15935 if (!empty($link)) {
15936 if (!empty($arguments)) {
15937 print '<a href="' . DOL_URL_ROOT . '/' . $link . '?' . $arguments . '">';
15938 } else {
15939 print '<a href="' . DOL_URL_ROOT . '/' . $link . '">';
15940 }
15941 }
15942
15943 if ($number > -1) {
15944 print '<span class="badge marginleftonlyshort">' . $number . '</span>';
15945 } elseif (!empty($link)) {
15946 print '<span class="badge marginleftonlyshort">...</span>';
15947 }
15948
15949 if (!empty($link)) {
15950 print '</a>';
15951 }
15952
15953 print '</th>';
15954
15955 if ($number < 0 && !empty($link)) {
15956 print '<th class="right">';
15957 print '</th>';
15958 }
15959
15960 print '</tr>';
15961}
15962
15971function finishSimpleTable($addLineBreak = false)
15972{
15973 print '</table>';
15974 print '</div>';
15975
15976 if ($addLineBreak) {
15977 print '<br>';
15978 }
15979}
15980
15992function addSummaryTableLine($tableColumnCount, $num, $nbofloop = 0, $total = 0, $noneWord = "None", $extraRightColumn = false)
15993{
15994 global $langs;
15995
15996 if ($num === 0) {
15997 print '<tr class="oddeven">';
15998 print '<td colspan="' . $tableColumnCount . '"><span class="opacitymedium">' . $langs->trans($noneWord) . '</span></td>';
15999 print '</tr>';
16000 return;
16001 }
16002
16003 if ($nbofloop === 0) {
16004 // don't show a summary line
16005 return;
16006 }
16007
16008 /* Case already handled above, commented to satisfy phpstan.
16009 if ($num === 0) {
16010 $colspan = $tableColumnCount;
16011 } else
16012 */
16013 if ($num > $nbofloop) {
16014 $colspan = $tableColumnCount;
16015 } else {
16016 $colspan = $tableColumnCount - 1;
16017 }
16018
16019 if ($extraRightColumn) {
16020 $colspan--;
16021 }
16022
16023 print '<tr class="liste_total">';
16024
16025 if ($nbofloop > 0 && $num > $nbofloop) {
16026 print '<td colspan="' . $colspan . '" class="right">' . $langs->trans("XMoreLines", ($num - $nbofloop)) . '</td>';
16027 } else {
16028 print '<td colspan="' . $colspan . '" class="right"> ' . $langs->trans("Total") . '</td>';
16029 print '<td class="right centpercent">' . price($total) . '</td>';
16030 }
16031
16032 if ($extraRightColumn) {
16033 print '<td></td>';
16034 }
16035
16036 print '</tr>';
16037}
16038
16047function readfileLowMemory($fullpath_original_file_osencoded, $method = -1)
16048{
16049 if ($method == -1) {
16050 $method = 0;
16051 if (getDolGlobalString('MAIN_FORCE_READFILE_WITH_FREAD')) {
16052 $method = 1;
16053 }
16054 if (getDolGlobalString('MAIN_FORCE_READFILE_WITH_STREAM_COPY')) {
16055 $method = 2;
16056 }
16057 }
16058
16059 // Be sure we don't have output buffering enabled to have readfile working correctly
16060 $level = ob_get_level();
16061 ob_start();
16062 while (ob_get_level() > $level) {
16063 ob_end_clean();
16064 }
16065
16066 // Solution 0
16067 if ($method == 0) {
16068 readfile($fullpath_original_file_osencoded);
16069 } elseif ($method == 1) {
16070 // Solution 1
16071 $handle = fopen($fullpath_original_file_osencoded, "rb");
16072 while (!feof($handle)) {
16073 print fread($handle, 8192);
16074 }
16075 fclose($handle);
16076 } elseif ($method == 2) {
16077 // Solution 2
16078 $handle1 = fopen($fullpath_original_file_osencoded, "rb");
16079 $handle2 = fopen("php://output", "wb");
16080 stream_copy_to_stream($handle1, $handle2);
16081 fclose($handle1);
16082 fclose($handle2);
16083 }
16084}
16085
16095function showValueWithClipboardCPButton($valuetocopy, $showonlyonhover = 1, $texttoshow = '')
16096{
16097 global $langs;
16098
16099 $tag = 'span'; // Using div (like any style of type 'block') does not work when using the js copy code.
16100
16101 $result = '<span class="clipboardCP' . ($showonlyonhover ? ' clipboardCPShowOnHover valignmiddle' : '') . '">';
16102 if ($texttoshow === 'none') {
16103 $result .= '<' . $tag . ' class="clipboardCPValue hidewithsize">' . dol_escape_htmltag($valuetocopy, 1, 1) . '</' . $tag . '>';
16104 $result .= '<span class="clipboardCPValueToPrint"></span>';
16105 } elseif ($texttoshow) {
16106 $result .= '<' . $tag . ' class="clipboardCPValue hidewithsize">' . dol_escape_htmltag($valuetocopy, 1, 1) . '</' . $tag . '>';
16107 $result .= '<span class="clipboardCPValueToPrint">' . dol_escape_htmltag($texttoshow, 1, 1) . '</span>';
16108 } else {
16109 $result .= '<' . $tag . ' class="clipboardCPValue">' . dol_escape_htmltag($valuetocopy, 1, 1) . '</' . $tag . '>';
16110 }
16111 $result .= '<span class="clipboardCPButton far fa-clipboard opacitymedium paddingleft pictomodule" title="' . dolPrintHTML($langs->trans("ClickToCopyToClipboard")) . '"></span>';
16112 $result .= img_picto('', 'tick', 'class="clipboardCPTick hidden paddingleft pictomodule"');
16113 $result .= '<span class="clipboardCPText"></span>';
16114 $result .= '</span>';
16115
16116 return $result;
16117}
16118
16119
16127function jsonOrUnserialize($stringtodecode, $assoc = true)
16128{
16129 $result = json_decode($stringtodecode, $assoc);
16130 if ($result === null) {
16131 $result = unserialize($stringtodecode, ['allowed_classes' => false]); // For backward compatibility. Is no more used in recent versions.
16132 }
16133
16134 return $result;
16135}
16136
16137
16155function forgeSQLFromUniversalSearchCriteria($filter, &$errorstr = '', $noand = 0, $nopar = 0, $noerror = 0, $forbiddenfields = array())
16156{
16157 global $db, $user;
16158
16159 if (is_null($filter) || !is_string($filter) || $filter === '') {
16160 return '';
16161 }
16162 if (!preg_match('/^\‍(.*\‍)$/', $filter)) { // If $filter does not start and end with ()
16163 $filter = '(' . $filter . ')';
16164 }
16165
16166 $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'
16167 $firstandlastparenthesis = 0;
16168
16169 if (!dolCheckFilters($filter, $errorstr, $firstandlastparenthesis)) {
16170 if ($noerror) {
16171 return '1 = 2';
16172 } else {
16173 return 'Filter syntax error - ' . $errorstr; // Bad balance of parenthesis, we return an error message or force a SQL not found
16174 }
16175 }
16176
16177 // Test the filter syntax
16178 $t = preg_replace_callback('/' . $regexstring . '/i', 'dolForgeDummyCriteriaCallback', $filter);
16179 $t = str_ireplace(array('and', 'or', ' '), '', $t); // Remove the only strings allowed between each () criteria
16180
16181 // If the string result contains something else than '()', the syntax was wrong
16182 if (preg_match('/[^\‍(\‍)]/', $t)) {
16183 $tmperrorstr = 'Bad syntax of the search string';
16184 $errorstr = 'Bad syntax of the search string: ' . $filter;
16185 if ($noerror) {
16186 return '1 = 2';
16187 } else {
16188 dol_syslog("forgeSQLFromUniversalSearchCriteria Filter error - " . $errorstr, LOG_WARNING);
16189 return 'Filter error - ' . $tmperrorstr; // Bad syntax of the search string, we return an error message or force a SQL not found
16190 }
16191 }
16192
16193 global $globalforbiddenfields; // For use by dolForgeSQLCriteriaCallback()
16194 $globalforbiddenfields = $forbiddenfields;
16195
16196 $ret = ($noand ? "" : " AND ") . ($nopar ? "" : '(');
16197 $ret .= preg_replace_callback('/' . $regexstring . '/i', 'dolForgeSQLCriteriaCallback', $filter);
16198 $ret .= ($nopar ? "" : ')');
16199
16200 if (is_object($db)) {
16201 $ret = str_replace('__NOW__', "'" . $db->idate(dol_now()) . "'", $ret);
16202 }
16203 if (is_object($user)) {
16204 $ret = str_replace('__USER_ID__', (string) $user->id, $ret);
16205 }
16206
16207 return $ret;
16208}
16209
16217function dolForgeExplodeAnd($sqlfilters)
16218{
16219 $arrayofandtags = array();
16220 $nbofchars = dol_strlen($sqlfilters);
16221
16222 $error = '';
16223 $parenthesislevel = 0;
16224 $result = dolCheckFilters($sqlfilters, $error, $parenthesislevel);
16225 if (!$result) {
16226 return array();
16227 }
16228 if ($parenthesislevel >= 1) {
16229 $sqlfilters = preg_replace('/^\‍(/', '', preg_replace('/\‍)$/', '', $sqlfilters));
16230 }
16231
16232 $i = 0;
16233 $s = '';
16234 $countparenthesis = 0;
16235 while ($i < $nbofchars) {
16236 $char = dol_substr($sqlfilters, $i, 1);
16237
16238 if ($char == '(') {
16239 $countparenthesis++;
16240 } elseif ($char == ')') {
16241 $countparenthesis--;
16242 }
16243
16244 if ($countparenthesis == 0) {
16245 $char2 = dol_substr($sqlfilters, $i + 1, 1);
16246 $char3 = dol_substr($sqlfilters, $i + 2, 1);
16247 if ($char == 'A' && $char2 == 'N' && $char3 == 'D') {
16248 // We found a AND
16249 $s = trim($s);
16250 if (!preg_match('/^\‍(.*\‍)$/', $s)) {
16251 $s = '(' . $s . ')';
16252 }
16253 $arrayofandtags[] = $s;
16254 $s = '';
16255 $i += 2;
16256 } else {
16257 $s .= $char;
16258 }
16259 } else {
16260 $s .= $char;
16261 }
16262 $i++;
16263 }
16264 if ($s) {
16265 $s = trim($s);
16266 if (!preg_match('/^\‍(.*\‍)$/', $s)) {
16267 $s = '(' . $s . ')';
16268 }
16269 $arrayofandtags[] = $s;
16270 }
16271
16272 return $arrayofandtags;
16273}
16274
16284function dolCheckFilters($sqlfilters, &$error = '', &$parenthesislevel = 0)
16285{
16286 //$regexstring='\‍(([^:\'\‍(\‍)]+:[^:\'\‍(\‍)]+:[^:\‍(\‍)]+)\‍)';
16287 //$tmp=preg_replace_all('/'.$regexstring.'/', '', $sqlfilters);
16288 $tmp = $sqlfilters;
16289
16290 $nb = dol_strlen($tmp);
16291 $counter = 0;
16292 $parenthesislevel = 0;
16293
16294 $error = '';
16295
16296 $i = 0;
16297 while ($i < $nb) {
16298 $char = dol_substr($tmp, $i, 1);
16299
16300 if ($char == '(') {
16301 if ($i == $parenthesislevel && $parenthesislevel == $counter) {
16302 // We open a parenthesis and it is the first char
16303 $parenthesislevel++;
16304 }
16305 $counter++;
16306 } elseif ($char == ')') {
16307 $nbcharremaining = ($nb - $i - 1);
16308 if ($nbcharremaining >= $counter) {
16309 $parenthesislevel = min($parenthesislevel, $counter - 1);
16310 }
16311 if ($parenthesislevel > $counter && $nbcharremaining >= $counter) {
16312 $parenthesislevel = $counter;
16313 }
16314 $counter--;
16315 }
16316
16317 if ($counter < 0) {
16318 $error = "Wrong balance of parenthesis in sqlfilters=" . $sqlfilters;
16319 $parenthesislevel = 0;
16320 dol_syslog($error, LOG_WARNING);
16321 return false;
16322 }
16323
16324 $i++;
16325 }
16326
16327 if ($counter > 0) {
16328 $error = "Wrong balance of parenthesis in sqlfilters=" . $sqlfilters;
16329 $parenthesislevel = 0;
16330 dol_syslog($error, LOG_WARNING);
16331 return false;
16332 }
16333
16334 return true;
16335}
16336
16344function dolForgeDummyCriteriaCallback($matches)
16345{
16346 //dol_syslog("Convert matches ".$matches[1]);
16347 if (empty($matches[1])) {
16348 return '';
16349 }
16350 $tmp = explode(':', $matches[1]);
16351 if (count($tmp) < 3) {
16352 return '';
16353 }
16354
16355 return '()'; // An empty criteria
16356}
16357
16367function dolForgeSQLCriteriaCallback($matches)
16368{
16369 global $db;
16370 global $globalforbiddenfields;
16371
16372 //dol_syslog("Convert matches ".$matches[1]);
16373 if (empty($matches[1])) {
16374 return '';
16375 }
16376 $tmp = explode(':', $matches[1], 3);
16377 if (count($tmp) < 3) {
16378 return '';
16379 }
16380
16381 // Add fields that are forbidden by the caller when using USF search criteria
16382 // so we can't guess them using sequantial comparisons
16383 $newforbiddenfields = $globalforbiddenfields;
16384 if (!is_array($newforbiddenfields)) {
16385 $newforbiddenfields = array();
16386 }
16387 // Add fields that are ALWAYS forbidden when using USF search criteria
16388 $newforbiddenfields[] = 'pass';
16389 $newforbiddenfields[] = 'pass_crypted';
16390 $newforbiddenfields[] = 'api_key';
16391
16392 $operand = preg_replace('/[^a-z0-9\._]/i', '', trim($tmp[0]));
16393
16394 // Test that operand is not a forbidden search field
16395 if (!empty($newforbiddenfields)) {
16396 $operandwithoutprefix = preg_replace('/^[a-z0-9_]+\./i', '', $operand); // Remove prefix like t. or o. or s. or u. or d. or ...
16397 if (in_array(strtolower($operandwithoutprefix), $newforbiddenfields)) {
16398 return '1=1';
16399 }
16400 }
16401
16402 $operator = strtoupper(preg_replace('/[^a-z<>!=]/i', '', trim($tmp[1])));
16403
16404 $realOperator = [
16405 'NOTLIKE' => 'NOT LIKE',
16406 'ISNOT' => 'IS NOT',
16407 'NOTIN' => 'NOT IN',
16408 '!=' => '<>',
16409 ];
16410
16411 if (array_key_exists($operator, $realOperator)) {
16412 $operator = $realOperator[$operator];
16413 }
16414
16415 $tmpescaped = $tmp[2];
16416
16417 //print "Case: ".$operator." ".$operand." ".$tmpescaped."\n";
16418
16419 $regbis = array();
16420
16421 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)
16422 global $dolibarr_allow_unsecured_select_in_extrafields_filter;
16423
16424 //if (!preg_match('/^\‍(.*\‍)$/', $tmpescaped)) {
16425 $tmpescaped2 = '(';
16426 // Explode and sanitize each element in list
16427 $tmpelemarray = explode(',', $tmpescaped);
16428 foreach ($tmpelemarray as $tmpkey => $tmpelem) {
16429 $reg = array();
16430 $tmpelem = trim($tmpelem);
16431 if (preg_match('/^\'(.*)\'$/', $tmpelem, $reg)) {
16432 $tmpelemarray[$tmpkey] = "'" . $db->escape($db->sanitize($reg[1], 2, 1, 1, 1)) . "'";
16433 $tmpelemarray[$tmpkey] = "'".$db->escape($db->sanitize($reg[1], 2, 1, 1, 1))."'";
16434 } elseif (preg_match('/^[0-9]+$/', (string) $tmpelem)) { // if only 0-9 chars, no .
16435 $tmpelemarray[$tmpkey] = (int) $tmpelem;
16436 } elseif (is_numeric((string) $tmpelem)) { // it can be a float with a .
16437 $tmpelemarray[$tmpkey] = (float) $tmpelem;
16438 } elseif (!empty($dolibarr_allow_unsecured_select_in_extrafields_filter)) {
16439 $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)
16440 } else {
16441 $tmpelemarray[$tmpkey] = preg_replace('/[^a-z0-9_]/i', '', $tmpelem); // it can be a name of field or a substitution variable like '__NOW__'
16442 }
16443 }
16444 $tmpescaped2 .= implode(',', $tmpelemarray);
16445 $tmpescaped2 .= ')';
16446
16447 $tmpescaped = $tmpescaped2;
16448 } elseif ($operator == 'LIKE' || $operator == 'NOT LIKE') {
16449 if (preg_match('/^\'([^\']*)\'$/', $tmpescaped, $regbis)) {
16450 $tmpescaped = $regbis[1];
16451 }
16452 //$tmpescaped = "'".$db->escape($db->escapeforlike($regbis[1]))."'";
16453 $tmpescaped = "'" . $db->escape($tmpescaped) . "'"; // We do not escape the _ and % so the LIKE will work as expected
16454 } elseif (preg_match('/^\'(.*)\'$/', $tmpescaped, $regbis)) {
16455 // TODO Retrieve type of field for $operand field name.
16456 // So we can complete format. For example we could complete a year with month and day.
16457 $tmpescaped = "'" . $db->escape($regbis[1]) . "'";
16458 } else {
16459 if (strtoupper($tmpescaped) == 'NULL') {
16460 $tmpescaped = 'NULL';
16461 } elseif (preg_match('/^[0-9]+$/', (string) $tmpescaped)) { // if only 0-9 chars, no .
16462 $tmpescaped = (int) $tmpescaped;
16463 } elseif (is_numeric((string) $tmpescaped)) { // it can be a float with a .
16464 $tmpescaped = (float) $tmpescaped;
16465 } else {
16466 $tmpescaped = preg_replace('/[^a-z0-9_]/i', '', $tmpescaped); // it can be a name of field or a substitution variable like '__NOW__'
16467 }
16468 }
16469
16470 return '(' . $db->escape($operand) . ' ' . strtoupper($operator) . ' ' . $tmpescaped . ')';
16471}
16472
16473
16483function getTimelineIcon($actionstatic, &$histo, $key)
16484{
16485 dol_syslog('getTimelineIcon::begin', LOG_DEBUG);
16486 global $langs;
16487
16488 $out = '<!-- timeline icon -->' . "\n";
16489 $iconClass = 'fa fa-comments';
16490 $img_picto = '';
16491 $colorClass = '';
16492 $pictoTitle = '';
16493
16494 if ($histo[$key]['percent'] == -1) {
16495 $colorClass = 'timeline-icon-not-applicble';
16496 $pictoTitle = $langs->trans('StatusNotApplicable');
16497 } elseif ($histo[$key]['percent'] == 0) {
16498 $colorClass = 'timeline-icon-todo';
16499 $pictoTitle = $langs->trans('StatusActionToDo') . ' (0%)';
16500 } elseif ($histo[$key]['percent'] > 0 && $histo[$key]['percent'] < 100) {
16501 $colorClass = 'timeline-icon-in-progress';
16502 $pictoTitle = $langs->trans('StatusActionInProcess') . ' (' . $histo[$key]['percent'] . '%)';
16503 } elseif ($histo[$key]['percent'] >= 100) {
16504 $colorClass = 'timeline-icon-done';
16505 $pictoTitle = $langs->trans('StatusActionDone') . ' (100%)';
16506 }
16507
16508 if ($actionstatic->code == 'AC_TICKET_CREATE') {
16509 $iconClass = 'fa fa-ticket';
16510 } elseif ($actionstatic->code == 'AC_TICKET_MODIFY') {
16511 $iconClass = 'fa fa-pencilxxx';
16512 } elseif (preg_match('/^TICKET_MSG/', $actionstatic->code)) {
16513 $iconClass = 'fa fa-comments';
16514 } elseif (preg_match('/^TICKET_MSG_PRIVATE/', $actionstatic->code)) {
16515 $iconClass = 'fa fa-mask';
16516 } elseif (getDolGlobalString('AGENDA_USE_EVENT_TYPE')) {
16517 if ($actionstatic->type_picto) {
16518 $img_picto = img_picto('', $actionstatic->type_picto);
16519 } else {
16520 if ($actionstatic->type_code == 'AC_RDV') {
16521 $iconClass = 'fa fa-handshake';
16522 } elseif ($actionstatic->type_code == 'AC_TEL') {
16523 $iconClass = 'fa fa-phone';
16524 } elseif ($actionstatic->type_code == 'AC_FAX') {
16525 $iconClass = 'fa fa-fax';
16526 } elseif ($actionstatic->type_code == 'AC_EMAIL') {
16527 $iconClass = 'fa fa-envelope';
16528 } elseif ($actionstatic->type_code == 'AC_INT') {
16529 $iconClass = 'fa fa-shipping-fast';
16530 } elseif ($actionstatic->type_code == 'AC_OTH_AUTO') {
16531 $iconClass = 'fa fa-robot';
16532 } elseif (!preg_match('/_AUTO/', $actionstatic->type_code)) {
16533 $iconClass = 'fa fa-robot';
16534 }
16535 }
16536 }
16537
16538 $out .= '<i class="' . $iconClass . ' ' . $colorClass . '" title="' . $pictoTitle . '">' . $img_picto . '</i>' . "\n";
16539 return $out;
16540}
16541
16549{
16550 global $db;
16551
16552 $documents = array();
16553
16554 $sql = 'SELECT ecm.rowid as id, ecm.src_object_type, ecm.src_object_id, ecm.filepath, ecm.filename, ecm.agenda_id';
16555 $sql .= ' FROM ' . MAIN_DB_PREFIX . 'ecm_files ecm';
16556 $sql .= " WHERE ecm.filepath = 'agenda/" . ((int) $object->id) . "'";
16557 //$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
16558 $sql .= ' OR ecm.agenda_id = ' . (int) $object->id;
16559 $sql .= ' ORDER BY ecm.position ASC';
16560
16561 $resql = $db->query($sql);
16562 if ($resql) {
16563 if ($db->num_rows($resql)) {
16564 while ($obj = $db->fetch_object($resql)) {
16565 $documents[$obj->id] = $obj;
16566 }
16567 }
16568 }
16569
16570 return $documents;
16571}
16572
16573
16591function show_actions_messaging($conf, $langs, $db, $filterobj, $objcon = null, $noprint = 0, $actioncode = '', $donetodo = 'done', $filters = array(), $sortfield = 'a.datep,a.id', $sortorder = 'DESC')
16592{
16593 dol_syslog('show_actions_messaging::begin', LOG_DEBUG);
16594 global $user, $conf;
16595 global $form;
16596
16597 global $param, $massactionbutton;
16598
16599 require_once DOL_DOCUMENT_ROOT . '/comm/action/class/actioncomm.class.php';
16600
16601 // Check parameters
16602 if (!is_object($filterobj) && !is_object($objcon)) {
16603 dol_print_error(null, 'BadParameter');
16604 }
16605
16606 $histo = array();
16607 '@phan-var-force array<int,array{type:string,tododone:string,id:string,datestart:int|string,dateend:int|string,fulldayevent:int,note:string,message:string,percent:string,userid:string,login:string,userfirstname:string,userlastname:string,userphoto:string,msg_from?:string,contact_id?:string,socpeopleassigned?:int[],lastname?:string,firstname?:string,fk_element?:int,elementtype?:string,acode:string,alabel?:string,libelle?:string,apicto?:string}> $histo';
16608
16609 $numaction = 0;
16610 $now = dol_now();
16611
16612 $sortfield_list = explode(',', $sortfield);
16613 $sortfield_label_list = array('a.id' => 'id', 'a.datep' => 'dp', 'a.percent' => 'percent');
16614 $sanitized_sortfield_new_list = array();
16615 foreach ($sortfield_list as $sortfield_value) {
16616 $sanitized_sortfield_new_list[] = $sortfield_label_list[trim($sortfield_value)]; //@phan-suppress-current-line SqlInjection
16617 }
16618 $sanitized_sortfield_new = implode(',', $sanitized_sortfield_new_list);
16619
16620 $sql = null;
16621 $sql2 = null;
16622
16623 if (isModEnabled('agenda')) {
16624 // Search histo on actioncomm
16625 if (is_object($objcon) && $objcon->id > 0) {
16626 $sql = "SELECT DISTINCT a.id, a.label as label,";
16627 } else {
16628 $sql = "SELECT a.id, a.label as label,";
16629 }
16630 $sql .= " a.datep as dp,";
16631 $sql .= " a.note as message,";
16632 $sql .= " a.datep2 as dp2,";
16633 $sql .= " a.percent as percent, 'action' as type,";
16634 $sql .= " a.fk_element, a.elementtype,";
16635 $sql .= " a.fk_contact, a.fulldayevent,";
16636 $sql .= " a.email_from as msg_from,";
16637 $sql .= " c.code as acode, c.libelle as alabel, c.picto as apicto,";
16638 $sql .= " u.rowid as user_id, u.login as user_login, u.photo as user_photo, u.firstname as user_firstname, u.lastname as user_lastname";
16639 if (is_object($filterobj) && get_class($filterobj) == 'Societe') {
16640 $sql .= ", sp.lastname, sp.firstname";
16641 } elseif (is_object($filterobj) && get_class($filterobj) == 'Adherent') {
16642 $sql .= ", m.lastname, m.firstname";
16643 } elseif (is_object($filterobj) && in_array(get_class($filterobj), array('Commande', 'CommandeFournisseur', 'Product', 'Ticket', 'BOM', 'Contrat', 'Facture', 'FactureFournisseur', 'Propal', 'Expedition'))) {
16644 $sql .= ", o.ref";
16645 } else {
16646 if (is_object($filterobj) && !empty($filterobj->table_element) && !empty($filterobj->element) && !empty($filterobj->id) && array_key_exists('ref', $filterobj->fields)) {
16647 $sql .= ", o.ref";
16648 }
16649 }
16650 $sql .= " FROM " . MAIN_DB_PREFIX . "actioncomm as a";
16651 $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "user as u on u.rowid = a.fk_user_action";
16652 $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "c_actioncomm as c ON a.fk_action = c.id";
16653
16654 $force_filter_contact = $filterobj instanceof User;
16655
16656 if (is_object($objcon) && $objcon->id > 0) {
16657 $force_filter_contact = true;
16658 $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "actioncomm_resources as r ON a.id = r.fk_actioncomm";
16659 $sql .= " AND r.element_type = '" . $db->escape($objcon->table_element) . "' AND r.fk_element = " . ((int) $objcon->id);
16660 }
16661
16662 if ((is_object($filterobj) && get_class($filterobj) == 'Societe') || (is_object($filterobj) && get_class($filterobj) == 'Contact')) {
16663 $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "socpeople as sp ON a.fk_contact = sp.rowid";
16664 } elseif (is_object($filterobj) && get_class($filterobj) == 'Dolresource') {
16665 $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "element_resources as er";
16666 $sql .= " ON er.resource_type = 'dolresource'";
16667 $sql .= " AND er.element_id = a.id";
16668 $sql .= " AND er.resource_id = " . ((int) $filterobj->id);
16669 } elseif (is_object($filterobj) && get_class($filterobj) == 'Adherent') {
16670 $sql .= ", " . MAIN_DB_PREFIX . "adherent as m";
16671 } elseif (is_object($filterobj) && get_class($filterobj) == 'CommandeFournisseur') {
16672 $sql .= ", " . MAIN_DB_PREFIX . "commande_fournisseur as o";
16673 } elseif (is_object($filterobj) && get_class($filterobj) == 'Product') {
16674 $sql .= ", " . MAIN_DB_PREFIX . "product as o";
16675 } elseif (is_object($filterobj) && get_class($filterobj) == 'Ticket') {
16676 $sql .= ", " . MAIN_DB_PREFIX . "ticket as o";
16677 } elseif (is_object($filterobj) && get_class($filterobj) == 'BOM') {
16678 $sql .= ", " . MAIN_DB_PREFIX . "bom_bom as o";
16679 } elseif (is_object($filterobj) && get_class($filterobj) == 'Contrat') {
16680 $sql .= ", " . MAIN_DB_PREFIX . "contrat as o";
16681 } elseif (is_object($filterobj) && get_class($filterobj) == 'Facture') {
16682 $sql .= ", " . MAIN_DB_PREFIX . "facture as o";
16683 } elseif (is_object($filterobj) && get_class($filterobj) == 'FactureFournisseur') {
16684 $sql .= ", " . MAIN_DB_PREFIX . "facture_fourn as o";
16685 } elseif (is_object($filterobj) && get_class($filterobj) == 'Commande') {
16686 $sql .= ", " . MAIN_DB_PREFIX . "commande as o";
16687 } elseif (is_object($filterobj) && get_class($filterobj) == 'Expedition') {
16688 $sql .= ", " . MAIN_DB_PREFIX . "expedition as o";
16689 } elseif (is_object($filterobj) && get_class($filterobj) == 'Propal') {
16690 $sql .= ", " . MAIN_DB_PREFIX . "propal as o";
16691 } else {
16692 if (is_object($filterobj) && !empty($filterobj->table_element) && !empty($filterobj->element) && !empty($filterobj->id) && array_key_exists('ref', $filterobj->fields)) {
16693 $sql .= ", " . MAIN_DB_PREFIX . $filterobj->table_element . " as o";
16694 }
16695 }
16696 $sql .= " WHERE a.entity IN (" . getEntity('agenda') . ")";
16697 if (!$force_filter_contact) {
16698 if (is_object($filterobj) && in_array(get_class($filterobj), array('Societe', 'Client', 'Fournisseur')) && $filterobj->id) {
16699 $sql .= " AND a.fk_soc = " . ((int) $filterobj->id);
16700 } elseif (is_object($filterobj) && get_class($filterobj) == 'Project' && $filterobj->id) {
16701 $sql .= " AND a.fk_project = o.rowid AND a.fk_project = " . ((int) $filterobj->id);
16702 } elseif (is_object($filterobj) && get_class($filterobj) == 'Adherent') {
16703 $sql .= " AND a.fk_element = m.rowid AND a.elementtype = 'member'";
16704 if ($filterobj->id) {
16705 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16706 }
16707 } elseif (is_object($filterobj) && get_class($filterobj) == 'Commande') {
16708 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'order'";
16709 if ($filterobj->id) {
16710 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16711 }
16712 } elseif (is_object($filterobj) && get_class($filterobj) == 'Expedition') {
16713 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'shipping'";
16714 if ($filterobj->id) {
16715 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16716 }
16717 } elseif (is_object($filterobj) && get_class($filterobj) == 'Propal') {
16718 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'propal'";
16719 if ($filterobj->id) {
16720 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16721 }
16722 } elseif (is_object($filterobj) && get_class($filterobj) == 'CommandeFournisseur') {
16723 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'order_supplier'";
16724 if ($filterobj->id) {
16725 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16726 }
16727 } elseif (is_object($filterobj) && get_class($filterobj) == 'Product') {
16728 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'product'";
16729 if ($filterobj->id) {
16730 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16731 }
16732 } elseif (is_object($filterobj) && get_class($filterobj) == 'Ticket') {
16733 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'ticket'";
16734 if ($filterobj->id) {
16735 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16736 }
16737 } elseif (is_object($filterobj) && get_class($filterobj) == 'BOM') {
16738 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'bom'";
16739 if ($filterobj->id) {
16740 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16741 }
16742 } elseif (is_object($filterobj) && get_class($filterobj) == 'Contrat') {
16743 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'contract'";
16744 if ($filterobj->id) {
16745 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16746 }
16747 } elseif (is_object($filterobj) && get_class($filterobj) == 'Contact' && $filterobj->id) {
16748 $sql .= " AND a.fk_contact = sp.rowid";
16749 if ($filterobj->id) {
16750 $sql .= " AND a.fk_contact = " . ((int) $filterobj->id);
16751 }
16752 } elseif (is_object($filterobj) && get_class($filterobj) == 'Facture') {
16753 $sql .= " AND a.fk_element = o.rowid";
16754 if ($filterobj->id) {
16755 $sql .= " AND a.fk_element = " . ((int) $filterobj->id) . " AND a.elementtype = 'invoice'";
16756 }
16757 } elseif (is_object($filterobj) && get_class($filterobj) == 'FactureFournisseur') {
16758 $sql .= " AND a.fk_element = o.rowid";
16759 if ($filterobj->id) {
16760 $sql .= " AND a.fk_element = " . ((int) $filterobj->id) . " AND a.elementtype = 'invoice_supplier'";
16761 }
16762 } else {
16763 if (is_object($filterobj) && !empty($filterobj->element) && !empty($filterobj->id) && array_key_exists('ref', $filterobj->fields)) {
16764 $sql .= " AND a.fk_element = o.rowid";
16765 $sql .= " AND a.elementtype = '" . $db->escape($filterobj->element) . "'";
16766 if ($filterobj->id) {
16767 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16768 }
16769 }
16770 }
16771 } else {
16772 $sql .= " AND u.rowid = " . ((int) $filterobj->id);
16773 }
16774
16775 // Condition on actioncode
16776 if (!empty($actioncode) && $actioncode != '-1') {
16777 if (!getDolGlobalString('AGENDA_USE_EVENT_TYPE')) {
16778 if ($actioncode == 'AC_NON_AUTO') {
16779 $sql .= " AND c.type != 'systemauto'";
16780 } elseif ($actioncode == 'AC_ALL_AUTO') {
16781 $sql .= " AND c.type = 'systemauto'";
16782 } else {
16783 if ($actioncode == 'AC_OTH') {
16784 $sql .= " AND c.type != 'systemauto'";
16785 } elseif ($actioncode == 'AC_OTH_AUTO') {
16786 $sql .= " AND c.type = 'systemauto'";
16787 }
16788 }
16789 } else {
16790 if ($actioncode == 'AC_NON_AUTO') {
16791 $sql .= " AND c.type != 'systemauto'";
16792 } elseif ($actioncode == 'AC_ALL_AUTO') {
16793 $sql .= " AND c.type = 'systemauto'";
16794 } else {
16795 $sql .= " AND c.code = '" . $db->escape($actioncode) . "'";
16796 }
16797 }
16798 }
16799 if ($donetodo == 'todo') {
16800 $sql .= " AND ((a.percent >= 0 AND a.percent < 100) OR (a.percent = -1 AND a.datep > '" . $db->idate($now) . "'))";
16801 } elseif ($donetodo == 'done') {
16802 $sql .= " AND (a.percent = 100 OR (a.percent = -1 AND a.datep <= '" . $db->idate($now) . "'))";
16803 }
16804 if (is_array($filters) && $filters['search_agenda_label']) {
16805 $sql .= natural_search('a.label', $filters['search_agenda_label']);
16806 }
16807 }
16808
16809 // Add also event from emailings. TODO This should be replaced by an automatic event ? May be it's too much for very large emailing.
16810 if (
16811 isModEnabled('mailing') && !empty($objcon->email)
16812 && (empty($actioncode) || $actioncode == 'AC_OTH_AUTO' || $actioncode == 'AC_EMAILING')
16813 ) {
16814 $langs->load("mails");
16815
16816 $sql2 = "SELECT m.rowid as id, m.titre as label, mc.date_envoi as dp, mc.date_envoi as dp2, '100' as percent, 'mailing' as type";
16817 $sql2 .= ", null as fk_element, '' as elementtype, null as contact_id";
16818 $sql2 .= ", 'AC_EMAILING' as acode, '' as alabel, '' as apicto";
16819 $sql2 .= ", u.rowid as user_id, u.login as user_login, u.photo as user_photo, u.firstname as user_firstname, u.lastname as user_lastname"; // User that valid action
16820 if (is_object($filterobj) && get_class($filterobj) == 'Societe') {
16821 $sql2 .= ", '' as lastname, '' as firstname";
16822 } elseif (is_object($filterobj) && get_class($filterobj) == 'Adherent') {
16823 $sql2 .= ", '' as lastname, '' as firstname";
16824 } elseif (is_object($filterobj) && get_class($filterobj) == 'CommandeFournisseur') {
16825 $sql2 .= ", '' as ref";
16826 } elseif (is_object($filterobj) && get_class($filterobj) == 'Product') {
16827 $sql2 .= ", '' as ref";
16828 } elseif (is_object($filterobj) && get_class($filterobj) == 'Ticket') {
16829 $sql2 .= ", '' as ref";
16830 }
16831 $sql2 .= " FROM " . MAIN_DB_PREFIX . "mailing as m, " . MAIN_DB_PREFIX . "mailing_cibles as mc, " . MAIN_DB_PREFIX . "user as u";
16832 $sql2 .= " WHERE mc.email = '" . $db->escape($objcon->email) . "'"; // Search is done on email.
16833 $sql2 .= " AND mc.statut = 1";
16834 $sql2 .= " AND u.rowid = m.fk_user_valid";
16835 $sql2 .= " AND mc.fk_mailing=m.rowid";
16836 }
16837
16838 $num = 0;
16839 $MAXWITHOUTPAGINATION = getDolGlobalInt('AGENDA_MAX_EVENTS_ON_PAGE_WITHOUT_PAGINATION', 100);
16840
16841 if ($sql || $sql2) { // May not be defined if module Agenda is not enabled and mailing module disabled too
16842 if (!empty($sql) && !empty($sql2)) {
16843 $sql = $sql . " UNION " . $sql2;
16844 } elseif (empty($sql) && !empty($sql2)) {
16845 $sql = $sql2;
16846 }
16847
16848 //TODO Add navigation with this limits...
16849 $offset = 0;
16850 $limit = $MAXWITHOUTPAGINATION;
16851
16852 // Complete request and execute it with limit
16853 $sql .= $db->order($sanitized_sortfield_new, $sortorder);
16854 if ($limit) {
16855 $sql .= $db->plimit($limit + 1, $offset);
16856 }
16857
16858 dol_syslog("function.lib::show_actions_messaging", LOG_DEBUG);
16859
16860 $resql = $db->query($sql);
16861 if ($resql) {
16862 $i = 0;
16863 $num = $db->num_rows($resql);
16864
16865 $imaxinloop = ($limit ? min($num, $limit) : $num);
16866 while ($i < $imaxinloop) {
16867 $obj = $db->fetch_object($resql);
16868
16869 if ($obj->type == 'action') {
16870 $contactaction = new ActionComm($db);
16871 $contactaction->id = $obj->id;
16872 $result = $contactaction->fetchResources();
16873 if ($result < 0) {
16875 setEventMessage("actions.lib::show_actions_messaging Error fetch resource", 'errors');
16876 }
16877
16878 //if ($donetodo == 'todo') $sql.= " AND ((a.percent >= 0 AND a.percent < 100) OR (a.percent = -1 AND a.datep > '".$db->idate($now)."'))";
16879 //elseif ($donetodo == 'done') $sql.= " AND (a.percent = 100 OR (a.percent = -1 AND a.datep <= '".$db->idate($now)."'))";
16880 $tododone = '';
16881 if (($obj->percent >= 0 and $obj->percent < 100) || ($obj->percent == -1 && $obj->dp > $now)) {
16882 $tododone = 'todo';
16883 }
16884
16885 $histo[$numaction] = array(
16886 'type' => $obj->type,
16887 'tododone' => $tododone,
16888 'id' => $obj->id,
16889 'datestart' => $db->jdate($obj->dp),
16890 'dateend' => $db->jdate($obj->dp2),
16891 'fulldayevent' => (int) $obj->fulldayevent,
16892 'note' => $obj->label,
16893 'message' => $obj->message,
16894 'percent' => $obj->percent,
16895
16896 'userid' => $obj->user_id,
16897 'login' => $obj->user_login,
16898 'userfirstname' => $obj->user_firstname,
16899 'userlastname' => $obj->user_lastname,
16900 'userphoto' => $obj->user_photo,
16901 'msg_from' => $obj->msg_from,
16902
16903 'contact_id' => $obj->fk_contact,
16904 'socpeopleassigned' => $contactaction->socpeopleassigned,
16905 'lastname' => (empty($obj->lastname) ? '' : $obj->lastname),
16906 'firstname' => (empty($obj->firstname) ? '' : $obj->firstname),
16907 'fk_element' => $obj->fk_element,
16908 'elementtype' => $obj->elementtype,
16909 // Type of event
16910 'acode' => $obj->acode,
16911 'alabel' => $obj->alabel,
16912 'libelle' => $obj->alabel, // deprecated
16913 'apicto' => $obj->apicto
16914 );
16915 } else {
16916 $histo[$numaction] = array(
16917 'type' => $obj->type,
16918 'tododone' => 'done',
16919 'id' => $obj->id,
16920 'datestart' => $db->jdate($obj->dp),
16921 'dateend' => $db->jdate($obj->dp2),
16922 'fulldayevent' => (int) $obj->fulldayevent,
16923 'note' => $obj->label,
16924 'message' => $obj->message,
16925 'percent' => $obj->percent,
16926 'acode' => $obj->acode,
16927
16928 'userid' => $obj->user_id,
16929 'login' => $obj->user_login,
16930 'userfirstname' => $obj->user_firstname,
16931 'userlastname' => $obj->user_lastname,
16932 'userphoto' => $obj->user_photo
16933 );
16934 }
16935
16936 $numaction++;
16937 $i++;
16938 }
16939 } else {
16941 }
16942 }
16943
16944 // Set $out to show events
16945 $out = '';
16946
16947 if (!isModEnabled('agenda')) {
16948 $langs->loadLangs(array("admin", "errors"));
16949 $out = info_admin($langs->trans("WarningModuleXDisabledSoYouMayMissEventHere", $langs->transnoentitiesnoconv("Module2400Name")), 0, 0, 'warning');
16950 }
16951
16952 if (isModEnabled('agenda') || (isModEnabled('mailing') && !empty($objcon->email))) {
16953 $delay_warning = getDolGlobalInt('MAIN_DELAY_ACTIONS_TODO') * 24 * 60 * 60;
16954
16955 require_once DOL_DOCUMENT_ROOT . '/comm/action/class/actioncomm.class.php';
16956 include_once DOL_DOCUMENT_ROOT . '/core/lib/functions2.lib.php';
16957 require_once DOL_DOCUMENT_ROOT . '/core/class/html.formactions.class.php';
16958 require_once DOL_DOCUMENT_ROOT . '/contact/class/contact.class.php';
16959
16960 $formactions = new FormActions($db);
16961
16962 $actionstatic = new ActionComm($db);
16963 $userstatic = new User($db);
16964 $contactstatic = new Contact($db);
16965 $userGetNomUrlCache = array();
16966 $contactGetNomUrlCache = array();
16967
16968 $out .= '<div class="filters-container" >';
16969 $out .= '<form name="listactionsfilter" class="listactionsfilter" action="' . $_SERVER["PHP_SELF"] . '" method="POST">';
16970 $out .= '<input type="hidden" name="token" value="' . newToken() . '">';
16971
16972 if (
16973 $objcon && get_class($objcon) == 'Contact' &&
16974 (is_null($filterobj) || get_class($filterobj) == 'Societe')
16975 ) {
16976 $out .= '<input type="hidden" name="id" value="' . $objcon->id . '" />';
16977 } else {
16978 $out .= '<input type="hidden" name="id" value="' . $filterobj->id . '" />';
16979 }
16980 if (($filterobj && get_class($filterobj) == 'Societe')) {
16981 $out .= '<input type="hidden" name="socid" value="' . $filterobj->id . '" />';
16982 } else {
16983 $out .= '<input type="hidden" name="userid" value="' . $filterobj->id . '" />';
16984 }
16985
16986 $out .= "\n";
16987
16988 $out .= '<div class="div-table-responsive-no-min">';
16989 $out .= '<table class="noborder borderbottom centpercent">';
16990
16991 $out .= '<tr class="liste_titre">';
16992
16993 // Action column
16994 if ($conf->main_checkbox_left_column) {
16995 $out .= '<th class="liste_titre width50 middle">';
16996 $searchpicto = $form->showFilterAndCheckAddButtons($massactionbutton ? 1 : 0, 'checkforselect', 1);
16997 $out .= $searchpicto;
16998 $out .= '</th>';
16999 }
17000
17001 // Date
17002 $out .= getTitleFieldOfList('Date', 0, $_SERVER["PHP_SELF"], 'a.datep', '', $param, '', $sortfield, $sortorder, 'nowraponall nopaddingleftimp ') . "\n";
17003
17004 $out .= '<th class="liste_titre hideonsmartphone"><strong class="hideonsmartphone">' . $langs->trans("Search") . ' : </strong></th>';
17005 if ($donetodo) {
17006 $out .= '<th class="liste_titre"></th>';
17007 }
17008 // Type of event
17009 $out .= '<th class="liste_titre">';
17010 $out .= '<span class="fas fa-square inline-block fawidth30 hideonsmartphone" style="color: #ddd;" title="' . $langs->trans("ActionType") . '"></span>';
17011 $out .= $formactions->select_type_actions($actioncode, "actioncode", '', getDolGlobalString('AGENDA_USE_EVENT_TYPE') ? -1 : 1, 0, 0, 1, 'selecttype minwidth100', $langs->trans("Type"));
17012 $out .= '</th>';
17013 // Label
17014 $out .= '<th class="liste_titre maxwidth100onsmartphone">';
17015 $out .= '<input type="text" class="maxwidth100onsmartphone" name="search_agenda_label" value="' . $filters['search_agenda_label'] . '" placeholder="' . $langs->trans("Label") . '">';
17016 $out .= '</th>';
17017
17018 // Action column
17019 if (!$conf->main_checkbox_left_column) {
17020 $out .= '<th class="liste_titre width50 middle">';
17021 $searchpicto = $form->showFilterAndCheckAddButtons($massactionbutton ? 1 : 0, 'checkforselect', 1);
17022 $out .= $searchpicto;
17023 $out .= '</th>';
17024 }
17025
17026 $out .= '</tr>';
17027
17028 $out .= '</table>';
17029
17030 $out .= '</form>';
17031 $out .= '</div>';
17032
17033 $out .= "\n";
17034
17035 $out .= '<ul class="timeline">';
17036
17037 if ($donetodo) {
17038 $tmp = '';
17039 if ($filterobj instanceof Societe) {
17040 $tmp .= '<a href="' . DOL_URL_ROOT . '/comm/action/list.php?mode=show_list&socid=' . $filterobj->id . '&status=done">';
17041 }
17042 if ($filterobj instanceof User) {
17043 $tmp .= '<a href="' . DOL_URL_ROOT . '/comm/action/list.php?mode=show_list&socid=' . $filterobj->id . '&status=done">';
17044 }
17045 $tmp .= ($donetodo != 'done' ? $langs->trans("ActionsToDoShort") : '');
17046 $tmp .= ($donetodo != 'done' && $donetodo != 'todo' ? ' / ' : '');
17047 $tmp .= ($donetodo != 'todo' ? $langs->trans("ActionsDoneShort") : '');
17048 //$out.=$langs->trans("ActionsToDoShort").' / '.$langs->trans("ActionsDoneShort");
17049 if ($filterobj instanceof Societe) {
17050 $tmp .= '</a>';
17051 }
17052 if ($filterobj instanceof User) {
17053 $tmp .= '</a>';
17054 }
17055 $out .= getTitleFieldOfList($tmp);
17056 }
17057
17058 require_once DOL_DOCUMENT_ROOT . '/comm/action/class/cactioncomm.class.php';
17059 $caction = new CActionComm($db);
17060 $arraylist = $caction->liste_array(1, 'code', '', (!getDolGlobalString('AGENDA_USE_EVENT_TYPE') ? 1 : 0), '', 1);
17061
17062 $actualCycleDate = false;
17063
17064 // Loop on each event to show it
17065 foreach ($histo as $key => $value) {
17066 $actionstatic->fetch($histo[$key]['id']); // TODO Do we need this, we already have a lot of data of line into $histo
17067
17068 $actionstatic->type_picto = $histo[$key]['apicto'];
17069 $actionstatic->type_code = $histo[$key]['acode'];
17070
17071 $labeltype = $actionstatic->type_code;
17072 if (!getDolGlobalString('AGENDA_USE_EVENT_TYPE') && empty($arraylist[$labeltype])) {
17073 $labeltype = 'AC_OTH';
17074 }
17075 if (!empty($actionstatic->code) && preg_match('/^TICKET_MSG/', $actionstatic->code)) {
17076 $labeltype = $langs->trans("Message");
17077 } else {
17078 if (!empty($arraylist[$labeltype])) {
17079 $labeltype = $arraylist[$labeltype];
17080 }
17081 if ($actionstatic->type_code == 'AC_OTH_AUTO' && ($actionstatic->type_code != $actionstatic->code) && $labeltype && !empty($arraylist[$actionstatic->code])) {
17082 $labeltype .= ' - ' . $arraylist[$actionstatic->code]; // Use code in priority on type_code
17083 }
17084 }
17085
17086 $url = DOL_URL_ROOT . '/comm/action/card.php?id=' . $histo[$key]['id'];
17087
17088 $tmpa = dol_getdate($histo[$key]['datestart'], false);
17089
17090 if (isset($tmpa['year']) && isset($tmpa['yday']) && $actualCycleDate !== $tmpa['year'] . '-' . $tmpa['yday']) {
17091 $actualCycleDate = $tmpa['year'] . '-' . $tmpa['yday'];
17092 $out .= '<!-- timeline time label -->';
17093 $out .= '<li class="time-label">';
17094 $out .= '<span class="timeline-badge-date">';
17095 $out .= dol_print_date($histo[$key]['datestart'], 'daytext', 'tzuserrel', $langs);
17096 $out .= '</span>';
17097 $out .= '</li>';
17098 $out .= '<!-- /.timeline-label -->';
17099 }
17100
17101
17102 $out .= '<!-- timeline item -->' . "\n";
17103 $out .= '<li class="timeline-code-' . (!empty($actionstatic->code) ? strtolower($actionstatic->code) : "none") . '">';
17104
17105 //$timelineicon = getTimelineIcon($actionstatic, $histo, $key);
17106 $typeicon = $actionstatic->getTypePicto('pictofixedwidth timeline-icon-not-applicble', $labeltype);
17107 //$out .= $timelineicon;
17108 //var_dump($timelineicon);
17109 $out .= $typeicon;
17110
17111 $out .= '<div class="timeline-item">' . "\n";
17112
17113 $out .= '<span class="time timeline-header-action2">';
17114
17115 if (isset($histo[$key]['type']) && $histo[$key]['type'] == 'mailing') {
17116 $out .= '<a class="paddingleft paddingright timeline-btn2 editfielda" href="' . DOL_URL_ROOT . '/comm/mailing/card.php?id=' . $histo[$key]['id'] . '">' . img_object($langs->trans("ShowEMailing"), "email") . ' ';
17117 $out .= $histo[$key]['id'];
17118 $out .= '</a> ';
17119 } else {
17120 $out .= $actionstatic->getNomUrl(1, -1, 'valignmiddle') . ' ';
17121 }
17122
17123 if (
17124 $user->hasRight('agenda', 'allactions', 'create') ||
17125 (($actionstatic->authorid == $user->id || $actionstatic->userownerid == $user->id) && $user->hasRight('agenda', 'myactions', 'create'))
17126 ) {
17127 $out .= '<a class="paddingleft paddingright timeline-btn2 editfielda" href="' . DOL_MAIN_URL_ROOT . '/comm/action/card.php?action=edit&token=' . newToken() . '&id=' . $actionstatic->id . '&backtopage=' . urlencode($_SERVER["PHP_SELF"] . '?' . $param) . '">';
17128 //$out .= '<i class="fa fa-pencil" title="'.$langs->trans("Modify").'" ></i>';
17129 $out .= img_picto($langs->trans("Modify"), 'edit', 'class="edita"');
17130 $out .= '</a>';
17131 }
17132
17133 $out .= '</span>';
17134
17135 // Date
17136 $out .= '<span class="time"><i class="fa fa-clock valignmiddle"></i> ';
17137 $out .= '<span class="valignmiddle marginrightonly">';
17138 $out .= dol_print_date($histo[$key]['datestart'], 'day', 'tzuserrel');
17139 //$out .= '</span>';
17140 //$out .= '<span class="valignmiddle">'.
17141 $out .= ' '.dol_print_date($histo[$key]['datestart'], 'hour', 'tzuserrel', null, false, 'opacitymedium');
17142 //$out .= '</span>';
17143 if ($histo[$key]['dateend'] && $histo[$key]['dateend'] != $histo[$key]['datestart']) {
17144 $tmpa = dol_getdate($histo[$key]['datestart'], true);
17145 $tmpb = dol_getdate($histo[$key]['dateend'], true);
17146 if ($tmpa['mday'] == $tmpb['mday'] && $tmpa['mon'] == $tmpb['mon'] && $tmpa['year'] == $tmpb['year']) {
17147 $out .= ' - ' . dol_print_date($histo[$key]['dateend'], 'hour', 'tzuserrel', null, false, 1);
17148 } else {
17149 $out .= ' - ' . dol_print_date($histo[$key]['dateend'], 'day', 'tzuserrel');
17150 //$out .= '<span class="valignmiddle marginrightonly">';
17151 $out .= ' '.dol_print_date($histo[$key]['dateend'], 'hour', 'tzuserrel', null, false, 'opacitymedium');
17152 //$out .= '</span>';
17153 }
17154 }
17155 $late = 0;
17156 if ($histo[$key]['percent'] == 0 && $histo[$key]['datestart'] && $histo[$key]['datestart'] < ($now - $delay_warning)) {
17157 $late = 1;
17158 }
17159 if ($histo[$key]['percent'] == 0 && !$histo[$key]['datestart'] && $histo[$key]['dateend'] && $histo[$key]['datestart'] < ($now - $delay_warning)) {
17160 $late = 1;
17161 }
17162 if ($histo[$key]['percent'] > 0 && $histo[$key]['percent'] < 100 && $histo[$key]['dateend'] && $histo[$key]['dateend'] < ($now - $delay_warning)) {
17163 $late = 1;
17164 }
17165 if ($histo[$key]['percent'] > 0 && $histo[$key]['percent'] < 100 && !$histo[$key]['dateend'] && $histo[$key]['datestart'] && $histo[$key]['datestart'] < ($now - $delay_warning)) {
17166 $late = 1;
17167 }
17168 if ($late) {
17169 $out .= img_warning($langs->trans("Late")) . ' ';
17170 }
17171 $out .= "</span></span>\n";
17172
17173 $out .= '<span class="time">';
17174 $out .= $actionstatic->getLibStatut(2);
17175 $out .= '</span>';
17176
17177 // Ref
17178 $out .= '<h3 class="timeline-header">';
17179
17180 // Author of event
17181 $out .= '<div class="messaging-author inline-block tdoverflowmax150 valignmiddle marginrightonly">';
17182 if ($histo[$key]['userid'] > 0) {
17183 if (!isset($userGetNomUrlCache[$histo[$key]['userid']])) { // is in cache ?
17184 $userstatic->fetch($histo[$key]['userid']);
17185 $userGetNomUrlCache[$histo[$key]['userid']] = $userstatic->getNomUrl(-1, '', 0, 0, 16, 0, 'firstelselast', '');
17186 }
17187 $out .= $userGetNomUrlCache[$histo[$key]['userid']];
17188 } elseif (!empty($histo[$key]['msg_from']) && $actionstatic->code == 'TICKET_MSG') {
17189 if (!isset($contactGetNomUrlCache[$histo[$key]['msg_from']])) {
17190 if ($contactstatic->fetch(0, null, '', $histo[$key]['msg_from']) > 0) {
17191 $contactGetNomUrlCache[$histo[$key]['msg_from']] = $contactstatic->getNomUrl(-1, '', 16);
17192 } else {
17193 $contactGetNomUrlCache[$histo[$key]['msg_from']] = $histo[$key]['msg_from'];
17194 }
17195 }
17196 $out .= $contactGetNomUrlCache[$histo[$key]['msg_from']];
17197 } else {
17198 $out .= '<img class="photomemberphoto userphoto" alt="" src="/public/theme/common/user_anonymous.png">'.$langs->trans("Anonymous");
17199 }
17200 $out .= '</div>';
17201
17202 // Title
17203 $out .= ' <div class="messaging-title inline-block">';
17204 //$out .= $actionstatic->getTypePicto(); // The type of event is already into the timeline on left.
17205 if (empty($conf->dol_optimize_smallscreen) && $actionstatic->type_code != 'AC_OTH_AUTO') {
17206 $out .= $labeltype . ' - ';
17207 }
17208
17209 $libelle = '';
17210
17211 if (!empty($actionstatic->code) && preg_match('/^TICKET_MSG_PRIVATE/', $actionstatic->code)) {
17212 $out .= $langs->trans('TicketNewMessage').' - <em>'.img_picto($langs->trans('Private'), 'lock', 'class="valignmiddle"').' '.$langs->trans('Private').'</em>';
17213 $summary = preg_replace('/\[[^\]]*\]\s*/', '', $actionstatic->label);
17214 //if ($summary != $object->title) {
17215 $out .= ' - '.dolPrintHTML($summary);
17216 //}
17217 } elseif (!empty($actionstatic->code) && preg_match('/^TICKET_MSG/', $actionstatic->code)) {
17218 $out .= $langs->trans('TicketNewMessage');
17219 } elseif (isset($histo[$key]['type'])) {
17220 if ($histo[$key]['type'] == 'action') {
17221 $transcode = $langs->transnoentitiesnoconv("Action" . $histo[$key]['acode']);
17222 $libelle = ($transcode != "Action" . $histo[$key]['acode'] ? $transcode : $histo[$key]['alabel']);
17223 $libelle = $histo[$key]['note'];
17224 $actionstatic->id = $histo[$key]['id'];
17225 if ($libelle != $labeltype) {
17226 $out .= dol_escape_htmltag(dol_trunc($libelle, 120));
17227 }
17228 } elseif ($histo[$key]['type'] == 'mailing') {
17229 $out .= '<a href="' . DOL_URL_ROOT . '/comm/mailing/card.php?id=' . $histo[$key]['id'] . '">' . img_object($langs->trans("ShowEMailing"), "email") . ' ';
17230 $transcode = $langs->transnoentitiesnoconv("Action" . $histo[$key]['acode']);
17231 $libelle = ($transcode != "Action" . $histo[$key]['acode'] ? $transcode : 'Send mass mailing');
17232 $out .= dol_escape_htmltag(dol_trunc($libelle, 120));
17233 } else {
17234 $libelle .= $histo[$key]['note'];
17235 $out .= dol_escape_htmltag(dol_trunc($libelle, 120));
17236 }
17237 }
17238 $out = preg_replace('/ - $/', '', $out); // Remove ending ' - '
17239
17240 if (isset($histo[$key]['elementtype']) && !empty($histo[$key]['fk_element'])) {
17241 if (isset($conf->cache['elementlinkcache'][$histo[$key]['elementtype']]) && isset($conf->cache['elementlinkcache'][$histo[$key]['elementtype']][$histo[$key]['fk_element']])) {
17242 $link = $conf->cache['elementlinkcache'][$histo[$key]['elementtype']][$histo[$key]['fk_element']];
17243 } else {
17244 if (!isset($conf->cache['elementlinkcache'][$histo[$key]['elementtype']])) {
17245 $conf->cache['elementlinkcache'][$histo[$key]['elementtype']] = array();
17246 }
17247 $link = dolGetElementUrl($histo[$key]['fk_element'], $histo[$key]['elementtype'], 1);
17248 $conf->cache['elementlinkcache'][$histo[$key]['elementtype']][$histo[$key]['fk_element']] = $link;
17249 }
17250
17251 // We do not show if link if on object we are filtering on (no need to show the link to ticket X when we are on page of events for the ticket X)
17252 $showlink = 1;
17253 if (is_object($filterobj) && get_class($filterobj) == 'Ticket') {
17254 if ($histo[$key]['elementtype'] == 'ticket') {
17255 $showlink = 0;
17256 }
17257 }
17258
17259 if ($link && $showlink) {
17260 $out .= ' - ' . $link;
17261 }
17262 }
17263
17264 $out .= '</div>';
17265
17266 $out .= '</h3>';
17267
17268 // Message
17269 if ($actionstatic->code == 'AC_TICKET_CREATE') {
17270 $newmess = $filterobj->message;
17271 } else {
17272 $newmess = $histo[$key]['message'];
17273 }
17274 if (
17275 !empty($newmess && $newmess != $libelle)
17276 && $actionstatic->code != 'AC_TICKET_MODIFY'
17277 ) {
17278 $out .= '<div class="timeline-body wordbreak small">';
17279 $truncateLines = getDolGlobalInt('MAIN_TRUNCATE_TIMELINE_MESSAGE', 3);
17280 $truncatedText = dolGetFirstLineOfText($newmess, $truncateLines);
17281 // dolGetFirstLineOfText() cuts on <br> without caring about tag balance, so a message wrapped in
17282 // a block tag leaves the excerpt with an unclosed tag. The browser then nests the read more link
17283 // and the full text inside the excerpt, and hiding the excerpt hides the whole message (#39035).
17284 $truncatedText = dolCloseUnclosedHtmlTags($truncatedText);
17285 if ($truncateLines > 0 && strlen($newmess) > strlen($truncatedText)) {
17286 $out .= '<div class="readmore-block --closed" >';
17287 $out .= ' <div class="readmore-block__excerpt">';
17288 $out .= dolPrintHTML($truncatedText, 0, array('pre', 'code'));
17289 $out .= ' <br><a class="read-more-link" data-read-more-action="open" href="' . DOL_MAIN_URL_ROOT . '/comm/action/card.php?id=' . $actionstatic->id . '&backtopage=' . urlencode($_SERVER["PHP_SELF"] . '?' . $param) . '" >' . $langs->trans("ReadMore") . ' <span class="fa fa-chevron-right" aria-hidden="true"></span></a>';
17290 $out .= ' </div>';
17291 $out .= ' <div class="readmore-block__full-text" >';
17292
17293 $out .= dolPrintHTML($newmess, 0, array('pre', 'code'));
17294
17295 $out .= ' <a class="read-less-link" data-read-more-action="close" href="#" ><span class="fa fa-chevron-up" aria-hidden="true"></span> ' . $langs->trans("ReadLess") . '</a>';
17296 $out .= ' </div>';
17297 $out .= '</div>';
17298 } else {
17299 $out .= dolPrintHTML($newmess, 0, array('pre', 'code'));
17300 }
17301 $out .= '</div>';
17302 }
17303
17304 // Timeline footer
17305 $footer = '';
17306
17307 // Contact for this action
17308 if (isset($histo[$key]['socpeopleassigned']) && is_array($histo[$key]['socpeopleassigned']) && count($histo[$key]['socpeopleassigned']) > 0) {
17309 $contactList = '';
17310 foreach ($histo[$key]['socpeopleassigned'] as $cid => $Tab) {
17311 if (empty($conf->cache['contact'][$cid])) {
17312 $contact = new Contact($db);
17313 $contact->fetch($cid);
17314 $conf->cache['contact'][$cid] = $contact;
17315 } else {
17316 $contact = $conf->cache['contact'][$cid];
17317 }
17318
17319 if ($contact) {
17320 $contactList .= !empty($contactList) ? ', ' : '';
17321 $contactList .= $contact->getNomUrl(1);
17322 if (isset($histo[$key]['acode']) && $histo[$key]['acode'] == 'AC_TEL') {
17323 if (!empty($contact->phone_pro)) {
17324 $contactList .= '(' . dol_print_phone($contact->phone_pro) . ')';
17325 }
17326 }
17327 }
17328 }
17329
17330 $footer .= $langs->trans('ActionOnContact') . ' : ' . $contactList;
17331 } elseif (empty($objcon->id) && isset($histo[$key]['contact_id']) && $histo[$key]['contact_id'] > 0) {
17332 if (empty($conf->cache['contact'][$histo[$key]['contact_id']])) {
17333 $contact = new Contact($db);
17334 $result = $contact->fetch($histo[$key]['contact_id']);
17335 $conf->cache['contact'][$histo[$key]['contact_id']] = $contact;
17336 } else {
17337 $contact = $conf->cache['contact'][$histo[$key]['contact_id']];
17338 $result = ($contact instanceof Contact) ? $contact->id : 0;
17339 }
17340
17341 if ($result > 0) {
17342 $footer .= $contact->getNomUrl(1);
17343 if (isset($histo[$key]['acode']) && $histo[$key]['acode'] == 'AC_TEL') {
17344 if (!empty($contact->phone_pro)) {
17345 $footer .= '(' . dol_print_phone($contact->phone_pro) . ')';
17346 }
17347 }
17348 }
17349 }
17350
17351 $documents = getActionCommEcmList($actionstatic);
17352 if (!empty($documents)) {
17353 $footer .= '<div class="timeline-documents-container">';
17354 foreach ($documents as $doc) {
17355 $footer .= '<span id="document_' . $doc->id . '" class="timeline-documents" ';
17356 $footer .= ' data-id="' . $doc->id . '" ';
17357 $footer .= ' data-path="' . $doc->filepath . '"';
17358 $footer .= ' data-filename="' . dol_escape_htmltag($doc->filename) . '" ';
17359 $footer .= '>';
17360
17361 $filePath = DOL_DATA_ROOT . '/' . $doc->filepath . '/' . $doc->filename;
17362 $mime = dol_mimetype($filePath);
17363 if (empty($doc->agenda_id)) {
17364 $dir_ref = $actionstatic->id;
17365 $modulepart = 'actions';
17366 } else {
17367 $split_dir = explode('/', $doc->filepath);
17368 $modulepart = array_shift($split_dir);
17369 $dir_ref = implode('/', $split_dir);
17370 }
17371
17372 $file = $dir_ref . '/' . $doc->filename;
17373 $thumb = $dir_ref . '/thumbs/' . substr($doc->filename, 0, strrpos($doc->filename, '.')) . '_mini' . substr($doc->filename, strrpos($doc->filename, '.'));
17374 $doclink = dol_buildpath('document.php', 1) . '?modulepart=' . $modulepart . '&attachment=0&file=' . urlencode($file) . '&entity=' . $conf->entity;
17375 $viewlink = dol_buildpath('viewimage.php', 1) . '?modulepart=' . $modulepart . '&file=' . urlencode($thumb) . '&entity=' . $conf->entity;
17376
17377
17378
17379 $mimeAttr = ' mime="' . $mime . '" ';
17380 $class = '';
17381 if (in_array($mime, array('image/png', 'image/jpeg', 'application/pdf'))) {
17382 $class .= ' documentpreview';
17383 }
17384
17385 $footer .= '<a href="' . $doclink . '" class="btn-link ' . $class . '" target="_blank" rel="noopener noreferrer" ' . $mimeAttr . ' >';
17386 $footer .= img_mime($filePath) . ' ' . $doc->filename;
17387 $footer .= '</a>';
17388
17389 $footer .= '</span>';
17390 }
17391 $footer .= '</div>';
17392 }
17393
17394 if (!empty($footer)) {
17395 $out .= '<div class="timeline-footer">' . $footer . '</div>';
17396 }
17397
17398 $out .= '</div>' . "\n"; // end timeline-item
17399
17400 $out .= '</li>';
17401 $out .= '<!-- END timeline item -->';
17402 }
17403
17404 $out .= "</ul>\n";
17405
17406 // Code to manage the click on button data-read-more-action to show full description of an event
17407 $out .= '<script>
17408 jQuery(document).ready(function () {
17409 $(document).on("click", "[data-read-more-action]", function(e){
17410 console.log("We click on data-read-more-action");
17411 let readMoreBloc = $(this).closest(".readmore-block");
17412 if(readMoreBloc.length > 0){
17413 e.preventDefault();
17414 if($(this).attr("data-read-more-action") == "close"){
17415 readMoreBloc.addClass("--closed").removeClass("--open");
17416 $("html, body").animate({
17417 scrollTop: readMoreBloc.offset().top - 200
17418 }, 100);
17419 }else{
17420 readMoreBloc.addClass("--open").removeClass("--closed");
17421 }
17422 }
17423 });
17424 });
17425 </script>';
17426
17427
17428 if (empty($histo)) {
17429 $out .= '<span class="opacitymedium">' . $langs->trans("NoRecordFound") . '</span>';
17430 }
17431
17432 if ($num > $MAXWITHOUTPAGINATION) {
17433 $langs->load("errors");
17434 $out .= '<center><span class="opacitymedium">...' . $langs->trans("WarningTooManyDataPleaseUseMoreFilters", $MAXWITHOUTPAGINATION) . '...</span></center>';
17435 }
17436 }
17437
17438 if ($noprint) {
17439 return $out;
17440 } else {
17441 print $out;
17442 return null;
17443 }
17444}
17445
17457function buildParamDate($prefix, $timestamp = null, $hourTime = '', $gm = 'auto')
17458{
17459 if ($timestamp === null) {
17460 $timestamp = GETPOSTDATE($prefix, $hourTime, $gm);
17461 }
17462 $TParam = array(
17463 $prefix . 'day' => intval(dol_print_date($timestamp, '%d')),
17464 $prefix . 'month' => intval(dol_print_date($timestamp, '%m')),
17465 $prefix . 'year' => intval(dol_print_date($timestamp, '%Y')),
17466 );
17467 if ($hourTime === 'getpost' || ($timestamp !== null && dol_print_date($timestamp, '%H:%M:%S') !== '00:00:00')) {
17468 $TParam = array_merge($TParam, array(
17469 $prefix . 'hour' => intval(dol_print_date($timestamp, '%H')),
17470 $prefix . 'min' => intval(dol_print_date($timestamp, '%M')),
17471 $prefix . 'sec' => intval(dol_print_date($timestamp, '%S'))
17472 ));
17473 }
17474
17475 return '&' . http_build_query($TParam);
17476}
17477
17496function recordNotFound($message = '', $printheader = 1, $printfooter = 1, $showonlymessage = 0, $params = null)
17497{
17498 global $conf, $db, $langs, $hookmanager;
17499 global $action, $object;
17500
17501 if (!is_object($langs)) {
17502 include_once DOL_DOCUMENT_ROOT . '/core/class/translate.class.php';
17503 $langs = new Translate('', $conf);
17504 $langs->setDefaultLang();
17505 }
17506
17507 $langs->load("errors");
17508
17509 if ($printheader) {
17510 if (function_exists("llxHeader")) {
17511 llxHeader('');
17512 } elseif (function_exists("llxHeaderVierge")) {
17513 llxHeaderVierge('');
17514 }
17515 }
17516
17517 print '<div class="error">';
17518 if (empty($message)) {
17519 print $langs->trans("ErrorRecordNotFound");
17520 } else {
17521 print $langs->trans($message);
17522 }
17523 print '</div>';
17524 print '<br>';
17525
17526 if (empty($showonlymessage)) {
17527 if (empty($hookmanager)) {
17528 include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
17529 $hookmanager = new HookManager($db);
17530 // Initialize a technical object to manage hooks of page. Note that conf->hooks_modules contains an array of hook context
17531 $hookmanager->initHooks(array('main'));
17532 }
17533
17534 $parameters = array('message' => $message, 'params' => $params);
17535 $reshook = $hookmanager->executeHooks('getErrorRecordNotFound', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
17536 print $hookmanager->resPrint;
17537 }
17538
17539 if ($printfooter && function_exists("llxFooter")) {
17540 llxFooter();
17541 if (is_object($db)) {
17542 $db->close();
17543 }
17544 }
17545 exit(0);
17546}
17547
17578function array_merge_recursive_distinct(array $array1, array $array2): array
17579{
17580 $merged = $array1;
17581
17582 foreach ($array2 as $key => $value) {
17583 if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
17584 $merged[$key] = array_merge_recursive_distinct($merged[$key], $value);
17585 } else {
17586 $merged[$key] = $value;
17587 }
17588 }
17589
17590 return $merged;
17591}
17592
17599function getObjectSocId($obj)
17600{
17601 if (!empty($obj->socid)) {
17602 return (int) $obj->socid;
17603 } elseif (!empty($obj->soc_id)) {
17604 return (int) $obj->soc_id;
17605 } elseif (!empty($obj->societe_id)) {
17606 return (int) $obj->societe_id;
17607 }
17608 return null;
17609}
17610
17617{
17618 $default = 10;
17619 if (!empty($_SESSION['dol_screenheight']) && $_SESSION['dol_screenheight'] < 700) {
17620 $default = 8;
17621 } elseif (!empty($_SESSION['dol_screenheight']) && $_SESSION['dol_screenheight'] < 950) {
17622 $default = 10;
17623 } elseif (!empty($_SESSION['dol_screenheight']) && $_SESSION['dol_screenheight'] > 1130) {
17624 $default = 15;
17625 }
17626
17627 return $default;
17628}
$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.
ajax_combobox($htmlname, $events=array(), $minLengthToAutocomplete=0, $forcefocus=0, $widthTypeOfAutocomplete='resolve', $idforemptyvalue='-1', $morecss='')
Convert a html select field into an ajax combobox.
Definition ajax.lib.php:476
ajax_object_onoff($object, $code, $field, $text_on, $text_off, $input=array(), $morecss='', $htmlname='', $forcenojs=0, $moreparam='', $readonly=0)
On/off button to change a property status of an object This uses the ajax service objectonoff....
Definition ajax.lib.php:804
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
Class to manage agenda events (actions)
Class to manage different types of events.
static getValidAddress($address, $format, $encode=0, $maxnumberofemail=0)
Return a formatted address string for SMTP protocol.
Class to manage contact/addresses.
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 building of HTML components.
Class to manage generation of HTML components Only common components must be here.
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_convert_file($fileinput, $ext='png', $fileoutput='', $page='')
Convert a PDF file into another image format.
dragAndDropFileUpload($htmlname)
Function to manage the drag and drop of a file.
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:64
dol_is_dir($folder)
Test if filename is a directory.
$date_start
Variables from include:
dolGetElementUrl($objectid, $objecttype, $withpicto=0, $option='')
Return link url to an object.
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.
dol_fiche_end($notab=0)
Show tab footer of a card.
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.
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 ...
img_weather($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $morecss='')
Show weather picto.
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.
show_actions_messaging($conf, $langs, $db, $filterobj, $objcon=null, $noprint=0, $actioncode='', $donetodo='done', $filters=array(), $sortfield='a.datep, a.id', $sortorder='DESC')
Show html area with actions in messaging format.
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.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0, $attop=0)
Set event messages in dol_events session object.
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.
img_credit_card($brand, $morecss='fa-2x inline-block valignmiddle')
Return image of a credit card according to its brand name.
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.
picto_from_langcode($codelang, $moreatt='', $notitlealt=0)
Return img flag of country for a language code or country code.
dol_ucfirst($string, $encoding="UTF-8")
Convert first character of the first word of a string to upper.
print_liste_field_titre($name, $file="", $field="", $begin="", $param="", $moreattrib="", $sortfield="", $sortorder="", $prefix="", $tooltip="", $forcenowrapcolumntitle=0)
Show title line of an array.
img_right($titlealt='default', $selected=0, $moreatt='')
Show right arrow logo.
dol_print_phone($phone, $countrycode='', $contactid=0, $socid=0, $addlink='', $separ="&nbsp;", $withpicto='', $titlealt='', $adddivfloat=0, $morecss='paddingright')
Format phone numbers according to country.
print_barre_liste($title, $page, $file, $options='', $sortfield='', $sortorder='', $morehtmlcenter='', $num=-1, $totalnboflines='', $picto='generic', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limit=-1, $selectlimitsuffix=0, $hidenavigation=0, $pagenavastextinput=0, $morehtmlrightbeforearrow='')
Print a title with navigation controls for pagination.
img_help($usehelpcursor=1, $usealttitle=1)
Show help logo with cursor "?".
dol_strtolower($string, $encoding="UTF-8")
Convert a string to lower.
showValueWithClipboardCPButton($valuetocopy, $showonlyonhover=1, $texttoshow='')
Create a button to copy $valuetocopy in the clipboard (for copy and paste feature).
dol_htmlentitiesbr_decode($stringtodecode, $pagecodeto='UTF-8')
This function is called to decode a HTML string (it decodes entities and br tags)
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_left($titlealt='default', $selected=0, $moreatt='')
Show left arrow logo.
img_delete($titlealt='default', $other='class="pictodelete"', $morecss='')
Show delete logo.
dol_mimetype($file, $default='application/octet-stream', $mode=0)
Return MIME type of a file from its name with extension.
dolPrintHTML($s, $allowiframe=0, $moreallowedtags=array())
Return a string (that can be on several lines) ready to be output on a HTML page.
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.
dol_get_fiche_head($links=array(), $active='', $title='', $notab=0, $picto='', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limittoshow=0, $moretabssuffix='', $dragdropfile=0, $morecssdiv='')
Show tabs of a record.
isASecretKey($keyname)
Return if string has a name dedicated to store a secret.
dolPrintHTMLForTextArea($s, $allowiframe=0)
Return a string ready to be output on input textarea.
dol_string_nohtmltag($stringtoclean, $removelinefeed=1, $pagecodeto='UTF-8', $strip_tags=0, $removedoublespaces=1)
Clean a string from all HTML tags and entities.
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.
get_htmloutput_errors($mesgstring='', $mesgarray=array(), $keepembedded=0)
Get formatted error messages to output (Used to show messages on html output).
dol_user_country()
Return country code for current user.
dol_string_onlythesehtmlattributes($stringtoclean, $allowed_attributes=null)
Clean a string from some undesirable HTML tags.
getMultidirTemp($object, $module='', $forobject=0)
Return the full path of the directory where a module (or an object of a module) stores its temporary ...
dolBuildUrl($url, $params=[], $addtoken=false, $anchor='')
Return path of url.
dolOutputDates($datep, $datef=null, $fullday=0, $addseconds=0, $pictotoadd='', $tzoutput='tzuserrel', $reduceformat=0)
Print decorated date-hour.
dol_get_fiche_end($notab=0)
Return tab footer of a card.
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.
img_action($titlealt, $numaction, $picto='', $moreatt='')
Show logo action.
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
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.
setEventMessage($mesgs, $style='mesgs', $noduplicate=0, $attop=0)
Set event message in dol_events session object.
printCommonFooter($zone='private')
Print common footer : conf->global->MAIN_HTML_FOOTER js for switch of menu hider js for conf->global-...
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.
img_down($titlealt='default', $selected=0, $moreclass='')
Show down arrow logo.
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.
dolPrintText($s)
Return a string label (possible on several lines and that should not contains any HTML) ready to be o...
utf8_valid($str)
Check if a string is in UTF8.
getPictoForType($key, $morecss='')
Return the picto for a data type.
getDolUserString($key, $default='', $tmpuser=null)
Return Dolibarr user constant string value.
getDolOptimizeSmallScreen()
Return if render must be optimized for small screen.
img_allow($allow, $titlealt='default')
Show tick logo if allowed.
isValidMXRecord($domain)
Return if the domain name has a valid MX record.
dolButtonToOpenExportDialog($name, $label, $buttonstring, $exportSiteName, $overwriteGitUrl, $website)
Create a dialog with two buttons for export and overwrite of a website.
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.
dol_fiche_head($links=array(), $active='0', $title='', $notab=0, $picto='', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limittoshow=0, $moretabssuffix='')
Show tab header of a card.
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...
img_mime($file, $titlealt='', $morecss='')
Show MIME img of a file.
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.
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.
ajax_autoselect($htmlname, $addlink='', $textonlink='Link')
Make content of an input box selected when we click into input field.
img_view($titlealt='default', $float=0, $other='class="valignmiddle"')
Show logo view card.
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.
dol_set_focus($selector)
Set focus onto field with selector (similar behaviour of 'autofocus' HTML5 tag)
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.
showDimensionInBestUnit($dimension, $unit, $type, $outputlangs, $round=-1, $forceunitoutput='no', $use_short_label=0)
Output a dimension with best unit.
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.
img_picto_common($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $notitle=0)
Show picto (generic function)
GETPOSTFLOAT($paramname, $rounding='', $option=2)
Return the value of a $_GET or $_POST supervariable, converted into float.
img_search($titlealt='default', $other='')
Show search logo.
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.
img_previous($titlealt='default', $moreatt='')
Show previous logo.
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.
dolPrintHTMLForAttribute($s, $escapeonlyhtmltags=0, $allowothertags=array())
Return a string ready to be output into an HTML attribute (alt, title, data-html, ....
fieldLabel($langkey, $fieldkey, $fieldrequired=0)
Show a string with the label tag dedicated to the HTML edit field.
getBrowserInfo($user_agent)
Return information about user browser.
dolGetFirstLetters($s, $nbofchar=1)
Return first letters of a strings.
dolPrintLabel($s, $escapeonlyhtmltags=0)
Return a string label (so on 1 line only and that should not contains any HTML) ready to be output on...
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)
yn($yesno, $format=1, $color=0)
Return yes or no in current language.
img_printer($titlealt="default", $other='')
Show printer logo.
dol_htmloutput_events($disabledoutputofmessages=0)
Print formatted messages to output (Used to show messages on html output).
getTitleFieldOfList($name, $thead=0, $file="", $field="", $begin="", $moreparam="", $moreattrib="", $sortfield="", $sortorder="", $prefix="", $disablesortlink=0, $tooltip='', $forcenowrapcolumntitle=0)
Get title line of an array.
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...
getImgPictoConv($mode='fa')
Get array to convert the Dolibarr picto keys into Font awesome keys.
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...)
img_split($titlealt='default', $other='class="pictosplit"')
Show split logo.
dolGetCountryCodeFromIp($ip)
Return a country code from IP.
dol_textishtml($msg, $option=0)
Return if a text is a html content.
dolPrintPassword($s)
Return a string ready to be output on an HTML attribute (alt, title, ...)
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.
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_print_error_email($prefixcode, $errormessage='', $errormessages=array(), $morecss='error', $email='')
Show a public email and error code to contact if technical error.
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.
print_titre($title)
Show a title.
showSimpleHTMLTable($outputlangs, $object)
Returns simple order table template as string.
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.
img_error($titlealt='default')
Show error logo.
getTimelineIcon($actionstatic, &$histo, $key)
Get timeline icon.
dol_htmloutput_mesg($mesgstring='', $mesgarray=array(), $style='ok', $keepembedded=0)
Print formatted messages to output (Used to show messages on html output).
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,...
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
img_next($titlealt='default', $moreatt='')
Show next logo.
load_fiche_titre($title, $morehtmlright='', $picto='generic', $pictoisfullpath=0, $id='', $morecssontable='', $morehtmlcenter='', $morecssonpicto='widthpictotitle')
Load a title with picto.
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '...' if string larger than length.
dol_string_is_good_iso($s, $clean=0)
Check if a string is a correct iso string If not, it will not be considered as HTML encoded even if i...
getNonce()
Return a random string to be used as a nonce value for js.
GETPOSTISSET($paramname)
Return true if we are in a context of submitting the parameter $paramname from a POST of a form.
isStringVarMatching($var, $regextext, $matchrule=1)
Check if a variable with name $var start with $regextext.
dolSlugify($stringtoslugify)
Returns text slugified (lowercase and no special char, separator is "-").
dol_concat($text1, $text2)
Concat 2 strings.
complete_head_from_modules($conf, $langs, $object, &$head, &$h, $type, $mode='add', $filterorigmodule='')
Complete or removed entries into a head array (used to build tabs).
get_htmloutput_mesg($mesgstring='', $mesgarray=[], $style='ok', $keepembedded=0)
Get formatted messages to output (Used to show messages on html output).
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...
print_fleche_navigation($page, $file, $options='', $nextpage=0, $betweenarrows='', $afterarrows='', $limit=-1, $totalnboflines=0, $selectlimitsuffix='', $beforearrows='', $hidenavigation=0)
Function to show navigation arrows into lists.
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.
img_edit_add($titlealt='default', $other='')
Show logo "+".
print_fiche_titre($title, $mesg='', $picto='generic', $pictoisfullpath=0, $id='')
Show a title with picto.
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.
img_searchclear($titlealt='default', $other='')
Show search logo.
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.
img_edit($titlealt='default', $float=0, $other='')
Show logo edit/modify fiche.
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...
img_up($titlealt='default', $selected=0, $moreclass='')
Show top arrow logo.
dol_htmloutput_errors($mesgstring='', $mesgarray=array(), $keepembedded=0)
Print formatted error messages to output (Used to show messages on html output).
info_admin($text, $infoonimgalt=0, $nodiv=0, $admin='1', $morecss='hideonsmartphone', $textfordropdown='', $picto='', $textonpictotooltip='', $cssfordropdown='info_admin')
Show information in HTML for admin users or standard users.
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.
dolPrintHTMLForAttributeUrl($s)
Return a string ready to be output on a href attribute (this one need a special because we need conte...
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)
img_edit_remove($titlealt='default', $other='')
Show logo "-".
img_info($titlealt='default')
Show info logo.
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...
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...
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.
multi select button
0 = Do not include form tag and submit button -1 = Do not include form tag but include submit button
treeview li table
No Email.
div refaddress div address
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
buildzip.php
if(!defined( 'NOREQUIREMENU')) if(!empty(GETPOST('seteventmessages', 'alpha'))) if(!function_exists("llxHeader")) top_httphead($contenttype='text/html', $forcenocache=0)
Show HTTP header.
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.
measuringUnitString($unitid, $measuring_style='', $unitscale=null, $use_short_label=0, $outputlangs=null)
Return translation label of a unit key.
if(preg_match('/(crypted|dolcrypt):/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
'integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]',...
Definition repair.php:130
$conf db name
Only used if Module[ID]Name translation string is not found.
Definition repair.php:133
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