dolibarr 24.0.0-beta
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 $subdirectory = '/sending';
196 break;
197 case 'company':
198 $module = 'societe';
199 break;
200 case 'service':
201 case 'produit':
202 $module = 'product';
203 break;
204 case 'action':
205 case 'actioncomm':
206 case 'event':
207 $module = 'agenda';
208 break;
209 default:
210 break;
211 }
212
213 // Get the relative path of directory
214 if ($mode == 'output' || $mode == 'outputrel' || $mode == 'version') {
215 if (isset($conf->$module) && property_exists($conf->$module, 'multidir_output')) {
216 $s = '';
217 if ($mode != 'outputrel') {
218 $s = $conf->$module->multidir_output[(empty($object->entity) ? $conf->entity : $object->entity)] . $subdirectory;
219 }
220 if ($forobject && $object->id > 0) {
221 $s .= ($mode != 'outputrel' ? '/' : '') . get_exdir(0, 0, 0, 0, $object);
222 }
223 return $s;
224 } elseif (isset($conf->$module) && property_exists($conf->$module, 'dir_output')) {
225 $s = '';
226 if ($mode != 'outputrel') {
227 $s = $conf->$module->dir_output . $subdirectory;
228 }
229 if ($forobject && $object->id > 0) {
230 $s .= ($mode != 'outputrel' ? '/' : '') . get_exdir(0, 0, 0, 0, $object);
231 }
232 return $s;
233 } else {
234 return 'error-diroutput-not-defined-for-this-object=' . $module;
235 }
236 } elseif ($mode == 'temp') {
237 if (isset($conf->$module) && property_exists($conf->$module, 'multidir_temp')) {
238 return $conf->$module->multidir_temp[(empty($object->entity) ? $conf->entity : $object->entity)];
239 } elseif (isset($conf->$module) && property_exists($conf->$module, 'dir_temp')) {
240 return $conf->$module->dir_temp;
241 } else {
242 return 'error-dirtemp-not-defined-for-this-object=' . $module;
243 }
244 } else {
245 return 'error-bad-value-for-mode';
246 }
247}
248
258function getMultidirTemp($object, $module = '', $forobject = 0)
259{
260 return getMultidirOutput($object, $module, $forobject, 'temp');
261}
262
272function getMultidirVersion($object, $module = '', $forobject = 0)
273{
274 return getMultidirOutput($object, $module, $forobject, 'version');
275}
276
277
286function getDolGlobalString($key, $default = '')
287{
288 global $conf;
289 return (string) (isset($conf->global->$key) ? $conf->global->$key : $default);
290}
291
298{
299 global $dolibarr_login_badcharunauthorized;
300
301 if (isset($dolibarr_login_badcharunauthorized)) {
302 if ($dolibarr_login_badcharunauthorized === 'MAIN_LOGIN_BADCHARUNAUTHORIZED') {
303 return getDolGlobalString('MAIN_LOGIN_BADCHARUNAUTHORIZED', ',@<>"\'');
304 }
305
306 return (string) $dolibarr_login_badcharunauthorized;
307 }
308
309 return ',@<>"\'';
310}
311
321function getDolGlobalInt($key, $default = 0)
322{
323 global $conf;
324 return (int) (isset($conf->global->$key) ? $conf->global->$key : $default);
325}
326
336function getDolGlobalFloat($key, $default = 0)
337{
338 global $conf;
339 return (float) (isset($conf->global->$key) ? $conf->global->$key : $default);
340}
341
350function getDolGlobalBool($key, $default = false)
351{
352 global $conf;
353 return (bool) ($conf->global->$key ?? $default);
354}
355
362{
363 global $conf;
364 return (string) $conf->currency;
365}
366
373{
374 global $conf;
375 return (string) $conf->dol_optimize_smallscreen;
376}
377
383function getDolEntity()
384{
385 global $conf;
386 return (int) $conf->entity;
387}
388
394function getDolDBType()
395{
396 global $conf;
397 return $conf->db->type;
398}
399
407{
408 return str_replace('_', '', basename(dirname($s)).basename($s, '.php'));
409}
410
420function getDolUserString($key, $default = '', $tmpuser = null)
421{
422 if (empty($tmpuser)) {
423 global $user;
424 $tmpuser = $user;
425 }
426
427 return (string) (isset($tmpuser->conf->$key) ? $tmpuser->conf->$key : $default);
428}
429
438function getDolUserInt($key, $default = 0, $tmpuser = null)
439{
440 if (empty($tmpuser)) {
441 global $user;
442 $tmpuser = $user;
443 }
444
445 return (int) (isset($tmpuser->conf->$key) ? $tmpuser->conf->$key : $default);
446}
447
448
458define(
459 'MODULE_MAPPING',
460 array(
461 // Map deprecated names to new names
462 'adherent' => 'member', // Has new directory
463 'member_type' => 'adherent_type', // No directory, but file called adherent_type
464 'banque' => 'bank', // Has new directory
465 'contrat' => 'contract', // Has new directory
466 'entrepot' => 'stock', // Has new directory
467 'projet' => 'project', // Has new directory
468 'categorie' => 'category', // Has old directory
469 'commande' => 'order', // Has old directory
470 'expedition' => 'shipping', // Has old directory
471 'facture' => 'invoice', // Has old directory
472 'fichinter' => 'intervention', // Has old directory
473 'ficheinter' => 'intervention', // Backup for 'fichinter'
474 'propale' => 'propal', // Has old directory
475 'societe' => 'thirdparty', // Has old directory
476 'socpeople' => 'contact', // Has old directory
477 'fournisseur' => 'supplier', // Has old directory
478
479 'actioncomm' => 'agenda', // NO module directory (public dir agenda)
480 'product_price' => 'productprice', // NO directory
481 'product_fournisseur_price' => 'productsupplierprice', // NO directory
482 )
483);
484
491function isModEnabled($module)
492{
493 global $conf;
494
495 // Fix old names (map to new names)
496 $arrayconv = MODULE_MAPPING;
497 $arrayconvbis = array_flip(MODULE_MAPPING);
498
499 if (!getDolGlobalString('MAIN_USE_NEW_SUPPLIERMOD')) {
500 // Special cases: both use the same module.
501 $arrayconv['supplier_order'] = 'fournisseur';
502 $arrayconv['supplier_invoice'] = 'fournisseur';
503 }
504
505 $module_alt = $module;
506 if (!empty($arrayconv[$module])) {
507 $module_alt = $arrayconv[$module];
508 }
509 $module_bis = $module;
510 if (!empty($arrayconvbis[$module])) {
511 $module_bis = $arrayconvbis[$module];
512 }
513
514 return !empty($conf->modules[$module]) || !empty($conf->modules[$module_alt]) || !empty($conf->modules[$module_bis]);
515}
516
527function getWarningDelay($module, $parmlevel1, $parmlevel2 = '')
528{
529 global $conf;
530
531 // For compatibility with bad naming on module
532 $moduletomoduletouse = array(
533 'invoice' => 'facture',
534 );
535 $moduleParmsMapping = array(
536 'product' => 'produit',
537 );
538
539 if (!empty($moduletomoduletouse[$module])) {
540 $module = $moduletomoduletouse[$module];
541 }
542
543 $warningDelayPath = $parmlevel1;
544 if (!empty($moduleParmsMapping[$warningDelayPath])) {
545 $warningDelayPath = $moduleParmsMapping[$warningDelayPath];
546 }
547
548 if ($parmlevel2) {
549 if (!empty($conf->$module) && !empty($conf->$module->$warningDelayPath) && !empty($conf->$module->$warningDelayPath->$parmlevel2) && !empty($conf->$module->$warningDelayPath->$parmlevel2->warning_delay)) {
550 return (int) $conf->$module->$warningDelayPath->$parmlevel2->warning_delay;
551 }
552 } else {
553 if (!empty($conf->$module) && !empty($conf->$module->$warningDelayPath) && !empty($conf->$module->$warningDelayPath->warning_delay)) {
554 return (int) $conf->$module->$warningDelayPath->warning_delay;
555 }
556 }
557
558 return 0;
559}
560
567function isDolTms($timestamp)
568{
569 if ($timestamp === '') {
570 dol_syslog('Using empty string for a timestamp is deprecated, prefer use of null when calling page ' . $_SERVER["PHP_SELF"] . getCallerInfoString(), LOG_NOTICE);
571 return false;
572 }
573 if (is_null($timestamp) || !is_numeric($timestamp)) {
574 return false;
575 }
576
577 return true;
578}
579
591function getDoliDBInstance($type, $host, $user, $pass, $name, $port)
592{
593 require_once DOL_DOCUMENT_ROOT . "/core/db/" . $type . '.class.php';
594
595 $class = 'DoliDB' . ucfirst($type);
596 $db = new $class($type, $host, $user, $pass, $name, $port);
597 return $db;
598}
599
617function getEntity($element, $shared = 1, $currentobject = null)
618{
619 global $conf, $mc, $hookmanager, $object, $action, $db;
620
621 if (!is_object($hookmanager)) {
622 include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
623 $hookmanager = new HookManager($db);
624 }
625
626 // fix different element names (France to English)
627 switch ($element) {
628 case 'projet':
629 $element = 'project';
630 break;
631 case 'contrat':
632 $element = 'contract';
633 break; // "/contrat/class/contrat.class.php"
634 case 'order_supplier':
635 $element = 'supplier_order';
636 break; // "/fourn/class/fournisseur.commande.class.php"
637 case 'invoice_supplier':
638 $element = 'supplier_invoice';
639 break; // "/fourn/class/fournisseur.facture.class.php"
640 }
641
642 if (is_object($mc)) {
643 $out = $mc->getEntity($element, $shared, $currentobject);
644 } else {
645 $out = '';
646 $addzero = array('user', 'usergroup', 'cronjob', 'c_email_templates', 'email_template', 'default_values', 'overwrite_trans');
647 if (getDolGlobalString('HOLIDAY_ALLOW_ZERO_IN_DIC')) { // this constant break the dictionary admin without Multicompany
648 $addzero[] = 'c_holiday_types';
649 }
650 if (in_array($element, $addzero)) {
651 $out .= '0,';
652 }
653 $out .= ((int) $conf->entity);
654 }
655
656 // Manipulate entities to query on the fly
657 $parameters = array(
658 'element' => $element,
659 'shared' => $shared,
660 'object' => $object,
661 'currentobject' => $currentobject,
662 'out' => $out
663 );
664 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
665 $reshook = $hookmanager->executeHooks('hookGetEntity', $parameters, $currentobject, $action); // Note that $action and $object may have been modified by some hooks
666
667 if (is_numeric($reshook)) {
668 if ($reshook == 0 && !empty($hookmanager->resPrint)) {
669 $out .= ',' . $hookmanager->resPrint; // add
670 } elseif ($reshook == 1) {
671 $out = $hookmanager->resPrint; // replace
672 }
673 }
674
675 return $out;
676}
677
684function setEntity($currentobject)
685{
686 global $conf, $mc;
687
688 if (is_object($mc) && method_exists($mc, 'setEntity')) {
689 return $mc->setEntity($currentobject);
690 } else {
691 return ((is_object($currentobject) && $currentobject->id > 0 && ((int) $currentobject->entity) > 0) ? (int) $currentobject->entity : $conf->entity);
692 }
693}
694
701function isASecretKey($keyname)
702{
703 return preg_match('/(_pass|password|_pw|_key|securekey|serverkey|secret\d?|p12key|exportkey|_PW_[a-z]+|token)$/i', $keyname);
704}
705
706
713function num2Alpha($n)
714{
715 $r = '';
716 for ($r = ""; $n >= 0; $n = intval($n / 26) - 1) {
717 $r = chr($n % 26 + 0x41) . $r;
718 }
719 return $r;
720}
721
722
739function getBrowserInfo($user_agent)
740{
741 include_once DOL_DOCUMENT_ROOT . '/includes/mobiledetect/mobiledetectlib/Mobile_Detect.php';
742
743 $name = 'unknown';
744 $version = '';
745 $os = 'unknown';
746 $phone = '';
747
748 $user_agent = substr($user_agent, 0, 512); // Avoid to process too large user agent
749
750 // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal Bad definition of Mobile_Detect function
751 $detectmobile = new Mobile_Detect(null, $user_agent);
752 $tablet = $detectmobile->isTablet();
753
754 if ($detectmobile->isMobile()) {
755 $phone = 'unknown';
756
757 // If phone/smartphone, we set phone os name.
758 if ($detectmobile->is('AndroidOS')) {
759 $os = $phone = 'android';
760 } elseif ($detectmobile->is('BlackBerryOS')) {
761 $os = $phone = 'blackberry';
762 } elseif ($detectmobile->is('iOS')) {
763 $os = 'ios';
764 $phone = 'iphone';
765 } elseif ($detectmobile->is('PalmOS')) {
766 $os = $phone = 'palm';
767 } elseif ($detectmobile->is('SymbianOS')) {
768 $os = 'symbian';
769 } elseif ($detectmobile->is('webOS')) {
770 $os = 'webos';
771 } elseif ($detectmobile->is('MaemoOS')) {
772 $os = 'maemo';
773 } elseif ($detectmobile->is('WindowsMobileOS') || $detectmobile->is('WindowsPhoneOS')) {
774 $os = 'windows';
775 }
776 }
777
778 // OS
779 if (preg_match('/linux/i', $user_agent)) {
780 $os = 'linux';
781 } elseif (preg_match('/macintosh/i', $user_agent)) {
782 $os = 'macintosh';
783 } elseif (preg_match('/windows/i', $user_agent)) {
784 $os = 'windows';
785 }
786
787 // Name
788 $reg = array();
789 if (preg_match('/firefox(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
790 $name = 'firefox';
791 $version = empty($reg[2]) ? '' : $reg[2];
792 } elseif (preg_match('/edge(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
793 $name = 'edge';
794 $version = empty($reg[2]) ? '' : $reg[2];
795 } elseif (preg_match('/chrome(\/|\s)([\d\.]+)/i', $user_agent, $reg)) {
796 $name = 'chrome';
797 $version = empty($reg[2]) ? '' : $reg[2];
798 } elseif (preg_match('/chrome/i', $user_agent, $reg)) {
799 // we can have 'chrome (Mozilla...) chrome x.y' in one string
800 $name = 'chrome';
801 } elseif (preg_match('/iceweasel/i', $user_agent)) {
802 $name = 'iceweasel';
803 } elseif (preg_match('/epiphany/i', $user_agent)) {
804 $name = 'epiphany';
805 } elseif (preg_match('/safari(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
806 $name = 'safari';
807 $version = empty($reg[2]) ? '' : $reg[2];
808 } elseif (preg_match('/opera(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
809 // Safari is often present in string for mobile but its not.
810 $name = 'opera';
811 $version = empty($reg[2]) ? '' : $reg[2];
812 } elseif (preg_match('/(MSIE\s([0-9]+\.[0-9]))|.*(Trident\/[0-9]+.[0-9];.*rv:([0-9]+\.[0-9]+))/i', $user_agent, $reg)) {
813 $name = 'ie';
814 $version = end($reg);
815 } elseif (preg_match('/(Windows NT\s([0-9]+\.[0-9])).*(Trident\/[0-9]+.[0-9];.*rv:([0-9]+\.[0-9]+))/i', $user_agent, $reg)) {
816 // MS products at end
817 $name = 'ie';
818 $version = end($reg);
819 } elseif (preg_match('/l[iy]n(x|ks)(\‍(|\/|\s)*([\d\.]+)/i', $user_agent, $reg)) {
820 // MS products at end
821 $name = 'textbrowser';
822 $version = empty($reg[3]) ? '' : $reg[3];
823 } elseif (preg_match('/w3m\/([\d\.]+)/i', $user_agent, $reg)) {
824 // MS products at end
825 $name = 'textbrowser';
826 $version = empty($reg[1]) ? '' : $reg[1];
827 }
828
829 if ($tablet) {
830 $layout = 'tablet';
831 } elseif ($phone) {
832 $layout = 'phone';
833 } else {
834 $layout = 'classic';
835 }
836
837 return array(
838 'browsername' => $name,
839 'browserversion' => $version,
840 'browseros' => $os,
841 'browserua' => $user_agent,
842 'layout' => $layout, // tablet, phone, classic
843 'phone' => $phone, // deprecated
844 'tablet' => $tablet // deprecated
845 );
846}
847
853function dol_shutdown()
854{
855 global $db;
856 $disconnectdone = false;
857 $depth = 0;
858 if (is_object($db) && !empty($db->connected)) {
859 $depth = $db->transaction_opened;
860 $disconnectdone = $db->close();
861 }
862 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));
863}
864
874function GETPOSTISSET($paramname)
875{
876 $isset = false;
877
878 $relativepathstring = $_SERVER["PHP_SELF"];
879 // Clean $relativepathstring
880 if (constant('DOL_URL_ROOT')) {
881 $relativepathstring = preg_replace('/^' . preg_quote(constant('DOL_URL_ROOT'), '/') . '/', '', $relativepathstring);
882 }
883 $relativepathstring = ltrim($relativepathstring, '/');
884 $relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
885
886 // Code for search criteria persistence.
887 // Retrieve values if restore_lastsearch_values
888 if (!empty($_GET['restore_lastsearch_values'])) { // Use $_GET here and not GETPOST
889 if (!empty($_SESSION['lastsearch_values_' . $relativepathstring])) { // If there is saved values
890 $tmp = json_decode($_SESSION['lastsearch_values_' . $relativepathstring], true);
891 if (is_array($tmp)) {
892 foreach ($tmp as $key => $val) {
893 if ($key == $paramname) { // We are on the requested parameter
894 $isset = true;
895 break;
896 }
897 }
898 }
899 }
900 // If there is saved contextpage, limit, page or mode
901 if ($paramname == 'contextpage' && !empty($_SESSION['lastsearch_contextpage_' . $relativepathstring])) {
902 $isset = true;
903 } elseif ($paramname == 'limit' && !empty($_SESSION['lastsearch_limit_' . $relativepathstring])) {
904 $isset = true;
905 } elseif ($paramname == 'page' && !empty($_SESSION['lastsearch_page_' . $relativepathstring])) {
906 $isset = true;
907 } elseif ($paramname == 'mode' && !empty($_SESSION['lastsearch_mode_' . $relativepathstring])) {
908 $isset = true;
909 }
910 } else {
911 $isset = (isset($_POST[$paramname]) || isset($_GET[$paramname])); // We must keep $_POST and $_GET here
912 }
913
914 return $isset;
915}
916
925function GETPOSTISARRAY($paramname, $method = 0)
926{
927 // for $method test need return the same $val as GETPOST
928 if (empty($method)) {
929 $val = isset($_GET[$paramname]) ? $_GET[$paramname] : (isset($_POST[$paramname]) ? $_POST[$paramname] : '');
930 } elseif ($method == 1) {
931 $val = isset($_GET[$paramname]) ? $_GET[$paramname] : '';
932 } elseif ($method == 2) {
933 $val = isset($_POST[$paramname]) ? $_POST[$paramname] : '';
934 } elseif ($method == 3) {
935 $val = isset($_POST[$paramname]) ? $_POST[$paramname] : (isset($_GET[$paramname]) ? $_GET[$paramname] : '');
936 } else {
937 $val = 'BadFirstParameterForGETPOST';
938 }
939
940 return is_array($val);
941}
942
943
953function GETPOSTINT($paramname, $method = 0)
954{
955 return (int) GETPOST($paramname, 'int', $method, null, null, 0);
956}
957
971function GETPOSTFLOAT($paramname, $rounding = '', $option = 2)
972{
973 // 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.)
974 return (float) price2num(GETPOST($paramname), $rounding, $option);
975}
976
992function GETPOSTDATE($prefix, $hourTime = '', $gm = 'auto', $saverestore = '')
993{
994 $m = array();
995 if ($hourTime === 'getpost' || $hourTime === 'getpostend') {
996 $hour = (GETPOSTISSET($prefix . 'hour') && GETPOSTINT($prefix . 'hour') >= 0) ? GETPOSTINT($prefix . 'hour') : ($hourTime === 'getpostend' ? 23 : 0);
997 $minute = (GETPOSTISSET($prefix . 'min') && GETPOSTINT($prefix . 'min') >= 0) ? GETPOSTINT($prefix . 'min') : ($hourTime === 'getpostend' ? 59 : 0);
998 $second = (GETPOSTISSET($prefix . 'sec') && GETPOSTINT($prefix . 'sec') >= 0) ? GETPOSTINT($prefix . 'sec') : ($hourTime === 'getpostend' ? 59 : 0);
999 } elseif (preg_match('/^(\d\d):(\d\d):(\d\d)$/', $hourTime, $m)) {
1000 $hour = intval($m[1]);
1001 $minute = intval($m[2]);
1002 $second = intval($m[3]);
1003 } elseif ($hourTime === 'end') {
1004 $hour = 23;
1005 $minute = 59;
1006 $second = 59;
1007 } else {
1008 $hour = $minute = $second = 0;
1009 }
1010
1011 if (
1012 $saverestore
1013 && !GETPOSTISSET($prefix . 'day')
1014 && !GETPOSTISSET($prefix . 'month')
1015 && !GETPOSTISSET($prefix . 'year')
1016 && isset($_SESSION['DOLDATE_' . $saverestore . '_day'])
1017 && isset($_SESSION['DOLDATE_' . $saverestore . '_month'])
1018 && isset($_SESSION['DOLDATE_' . $saverestore . '_year'])
1019 ) {
1020 $day = $_SESSION['DOLDATE_' . $saverestore . '_day'];
1021 $month = $_SESSION['DOLDATE_' . $saverestore . '_month'];
1022 $year = $_SESSION['DOLDATE_' . $saverestore . '_year'];
1023 } else {
1024 $month = GETPOSTINT($prefix . 'month');
1025 $day = GETPOSTINT($prefix . 'day');
1026 $year = GETPOSTINT($prefix . 'year');
1027 }
1028
1029 // normalize out of range values
1030 $hour = (int) min($hour, 23);
1031 $minute = (int) min($minute, 59);
1032 $second = (int) min($second, 59);
1033
1034 if ($saverestore) {
1035 $_SESSION['DOLDATE_' . $saverestore . '_day'] = $day;
1036 $_SESSION['DOLDATE_' . $saverestore . '_month'] = $month;
1037 $_SESSION['DOLDATE_' . $saverestore . '_year'] = $year;
1038 }
1039
1040 //print "$hour, $minute, $second, $month, $day, $year, $gm<br>";
1041 return dol_mktime($hour, $minute, $second, $month, $day, $year, $gm);
1042}
1043
1084function GETPOST($paramname, $check = 'alphanohtml', $method = 0, $filter = null, $options = null, $noreplace = 0)
1085{
1086 global $langs, $mysoc, $user, $conf;
1087
1088 if (empty($paramname)) { // Explicit test for null for phan.
1089 return 'BadFirstParameterForGETPOST';
1090 }
1091 if (empty($check)) {
1092 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);
1093 // Enable this line to know who call the GETPOST with '' $check parameter.
1094 //var_dump(getCallerInfoString());
1095 }
1096 if (in_array($paramname, array('sortfield', 'sortorder'))) { // Force the $check to a more appropriated value
1097 $check = 'aZ09comma';
1098 }
1099
1100 if (empty($method)) {
1101 $out = isset($_GET[$paramname]) ? $_GET[$paramname] : (isset($_POST[$paramname]) ? $_POST[$paramname] : '');
1102 } elseif ($method == 1) {
1103 $out = isset($_GET[$paramname]) ? $_GET[$paramname] : '';
1104 } elseif ($method == 2) {
1105 $out = isset($_POST[$paramname]) ? $_POST[$paramname] : '';
1106 } elseif ($method == 3) {
1107 $out = isset($_POST[$paramname]) ? $_POST[$paramname] : (isset($_GET[$paramname]) ? $_GET[$paramname] : '');
1108 } else {
1109 return 'BadThirdParameterForGETPOST';
1110 }
1111
1112 $relativepathstring = ''; // For static analysis - looks possibly undefined if not set.
1113
1114 if (empty($method) || $method == 3 || $method == 4) {
1115 $relativepathstring = (empty($_SERVER["PHP_SELF"]) ? '' : $_SERVER["PHP_SELF"]);
1116 // Clean $relativepathstring
1117 if (constant('DOL_URL_ROOT')) {
1118 $relativepathstring = preg_replace('/^' . preg_quote(constant('DOL_URL_ROOT'), '/') . '/', '', $relativepathstring);
1119 }
1120 $relativepathstring = ltrim($relativepathstring, '/');
1121 $relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
1122
1123 // Code for search criteria persistence.
1124 // Retrieve saved values if restore_lastsearch_values is set
1125 if (!empty($_GET['restore_lastsearch_values'])) { // Use $_GET here and not GETPOST
1126 if (!empty($_SESSION['lastsearch_values_' . $relativepathstring])) { // If there is saved values
1127 $tmp = json_decode($_SESSION['lastsearch_values_' . $relativepathstring], true);
1128 if (is_array($tmp)) {
1129 foreach ($tmp as $key => $val) {
1130 if ($key == $paramname) { // We are on the requested parameter
1131 $out = $val;
1132 break;
1133 }
1134 }
1135 }
1136 }
1137 // If there is saved contextpage, page or limit
1138 if ($paramname == 'contextpage' && !empty($_SESSION['lastsearch_contextpage_' . $relativepathstring])) {
1139 $out = $_SESSION['lastsearch_contextpage_' . $relativepathstring];
1140 } elseif ($paramname == 'limit' && !empty($_SESSION['lastsearch_limit_' . $relativepathstring])) {
1141 $out = $_SESSION['lastsearch_limit_' . $relativepathstring];
1142 } elseif ($paramname == 'page' && !empty($_SESSION['lastsearch_page_' . $relativepathstring])) {
1143 $out = $_SESSION['lastsearch_page_' . $relativepathstring];
1144 } elseif ($paramname == 'mode' && !empty($_SESSION['lastsearch_mode_' . $relativepathstring])) {
1145 $out = $_SESSION['lastsearch_mode_' . $relativepathstring];
1146 }
1147 } elseif (!isset($_GET['sortfield'])) {
1148 // Else, retrieve default values if we are not doing a sort
1149 // 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
1150 if (!empty($_GET['action']) && $_GET['action'] == 'create' && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) {
1151 // Search default value from $object->field
1152 global $object;
1153 '@phan-var-force CommonObject $object'; // Suppose it's a CommonObject for analysis, but other objects have the $fields field as well
1154 if (is_object($object) && isset($object->fields[$paramname]['default'])) {
1155 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
1156 $out = $object->fields[$paramname]['default'];
1157 }
1158 }
1159 if (getDolGlobalString('MAIN_ENABLE_DEFAULT_VALUES')) {
1160 if (!empty($_GET['action']) && (preg_match('/^create/', $_GET['action']) || preg_match('/^presend/', $_GET['action'])) && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) {
1161 // Now search in setup to overwrite default values
1162 if (!empty($user->default_values)) { // $user->default_values defined from menu 'Setup - Default values'
1163 if (isset($user->default_values[$relativepathstring]['createform'])) {
1164 foreach ($user->default_values[$relativepathstring]['createform'] as $defkey => $defval) {
1165 $qualified = 0;
1166 if ($defkey != '_noquery_') {
1167 $tmpqueryarraytohave = explode('&', $defkey);
1168 $tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
1169 $foundintru = 0;
1170 foreach ($tmpqueryarraytohave as $tmpquerytohave) {
1171 if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) {
1172 $foundintru = 1;
1173 }
1174 }
1175 if (!$foundintru) {
1176 $qualified = 1;
1177 }
1178 } else {
1179 $qualified = 1;
1180 }
1181
1182 if ($qualified) {
1183 if (isset($user->default_values[$relativepathstring]['createform'][$defkey][$paramname])) {
1184 $out = $user->default_values[$relativepathstring]['createform'][$defkey][$paramname];
1185 break;
1186 }
1187 }
1188 }
1189 }
1190 }
1191 } elseif (!empty($paramname) && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) {
1192 // Management of default search_filters and sort order
1193 if (!empty($user->default_values)) {
1194 // $user->default_values defined from menu 'Setup - Default values'
1195 //var_dump($user->default_values[$relativepathstring]);
1196 if ($paramname == 'sortfield' || $paramname == 'sortorder') {
1197 // Sorted on which fields ? ASC or DESC ?
1198 if (isset($user->default_values[$relativepathstring]['sortorder'])) {
1199 // Even if paramname is sortfield, data are stored into ['sortorder...']
1200 foreach ($user->default_values[$relativepathstring]['sortorder'] as $defkey => $defval) {
1201 $qualified = 0;
1202 if ($defkey != '_noquery_') {
1203 $tmpqueryarraytohave = explode('&', $defkey);
1204 $tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
1205 $foundintru = 0;
1206 foreach ($tmpqueryarraytohave as $tmpquerytohave) {
1207 if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) {
1208 $foundintru = 1;
1209 }
1210 }
1211 if (!$foundintru) {
1212 $qualified = 1;
1213 }
1214 } else {
1215 $qualified = 1;
1216 }
1217
1218 if ($qualified) {
1219 $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
1220 foreach ($user->default_values[$relativepathstring]['sortorder'][$defkey] as $key => $val) {
1221 if ($out) {
1222 $out .= ', ';
1223 }
1224 if ($paramname == 'sortfield') {
1225 $out .= dol_string_nospecial($key, '', $forbidden_chars_to_replace);
1226 }
1227 if ($paramname == 'sortorder') {
1228 $out .= dol_string_nospecial($val, '', $forbidden_chars_to_replace);
1229 }
1230 }
1231 //break; // No break for sortfield and sortorder so we can cumulate fields (is it really useful ?)
1232 }
1233 }
1234 }
1235 } elseif (isset($user->default_values[$relativepathstring]['filters'])) {
1236 foreach ($user->default_values[$relativepathstring]['filters'] as $defkey => $defval) { // $defkey is a querystring like 'a=b&c=d', $defval is key of user
1237 if (!empty($_GET['disabledefaultvalues'])) { // If set of default values has been disabled by a request parameter
1238 continue;
1239 }
1240 $qualified = 0;
1241 if ($defkey != '_noquery_') {
1242 $tmpqueryarraytohave = explode('&', $defkey);
1243 $tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
1244 $foundintru = 0;
1245 foreach ($tmpqueryarraytohave as $tmpquerytohave) {
1246 if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) {
1247 $foundintru = 1;
1248 }
1249 }
1250 if (!$foundintru) {
1251 $qualified = 1;
1252 }
1253 } else {
1254 $qualified = 1;
1255 }
1256
1257 if ($qualified && isset($user->default_values[$relativepathstring]['filters'][$defkey][$paramname])) {
1258 // We must keep $_POST and $_GET here
1259 if (isset($_POST['search_all']) || isset($_GET['search_all'])) {
1260 // We made a search from quick search menu, do we still use default filter ?
1261 if (!getDolGlobalString('MAIN_DISABLE_DEFAULT_FILTER_FOR_QUICK_SEARCH')) {
1262 $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
1263 $out = dol_string_nospecial($user->default_values[$relativepathstring]['filters'][$defkey][$paramname], '', $forbidden_chars_to_replace);
1264 }
1265 } else {
1266 $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
1267 $out = dol_string_nospecial($user->default_values[$relativepathstring]['filters'][$defkey][$paramname], '', $forbidden_chars_to_replace);
1268 }
1269 break;
1270 }
1271 }
1272 }
1273 }
1274 }
1275 }
1276 }
1277 }
1278
1279 // 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)
1280 // Example of variables: __DAY__, __MONTH__, __YEAR__, __MYCOMPANY_COUNTRY_ID__, __USER_ID__, ...
1281 // 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.
1282 '@phan-var-force string $paramname';
1283 if (!is_array($out) && empty($_POST[$paramname]) && empty($noreplace)) {
1284 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
1285 $substitutionarray = getCommonSubstitutionArray($langs, 0, null, $user, array('mycompany', 'date', 'system', 'user'));
1286 complete_substitutions_array($substitutionarray, $langs, $user);
1287
1288 $out = make_substitutions($out, $substitutionarray, $langs);
1289 }
1290 }
1291
1292 // Check type of variable and make sanitization according to this
1293 if (preg_match('/^array/', $check)) { // If 'array' or 'array:restricthtml' or 'array:aZ09' or 'array:intcomma'
1294 $tmpcheck = 'alphanohtml';
1295 if ($out === null || $out === '') {
1296 $out = array();
1297 } elseif (!is_array($out)) {
1298 $out = explode(',', $out);
1299 } else {
1300 $tmparray = explode(':', $check);
1301 if (!empty($tmparray[1])) {
1302 $tmpcheck = $tmparray[1];
1303 }
1304 }
1305 foreach ($out as $outkey => $outval) {
1306 $out[$outkey] = sanitizeVal($outval, $tmpcheck, $filter, $options);
1307 }
1308 } else {
1309 // If field name is 'search_xxx' then we force the add of space after each < and > (when following char is numeric) because it means
1310 // 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
1311 if (strpos($paramname, 'search_') === 0) {
1312 $out = preg_replace('/([<>])([-+]?\d)/', '\1 \2', $out);
1313 }
1314
1315 // @phan-suppress-next-line UnknownSanitizeType
1316 $out = sanitizeVal($out, $check, $filter, $options);
1317 }
1318
1319 // Sanitizing for special parameters.
1320 // 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.
1321 // @TODO Merge backtopage with backtourl
1322 // @TODO Rename backtolist into backtopagelist
1323 if (preg_match('/^backto/i', $paramname)) {
1324 $out = str_replace('\\', '/', $out); // Can be before the loop because only 1 char is replaced. No risk to get it after other replacements.
1325 $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.
1326 do {
1327 $oldstringtoclean = $out;
1328 $out = str_ireplace(array('javascript', 'vbscript', '&colon', '&#'), '', $out);
1329 $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'
1330 $out = preg_replace(array('/^[a-z]*\/\s*\/+/i'), '', $out); // We remove schema*// to remove external URL
1331 } while ($oldstringtoclean != $out);
1332 }
1333
1334 // Code for search criteria persistence.
1335 // Save data into session if key start with 'search_'
1336 if (empty($method) || $method == 3 || $method == 4) {
1337 if (preg_match('/^search_/', $paramname) || in_array($paramname, array('sortorder', 'sortfield'))) {
1338 //var_dump($paramname.' - '.$out.' '.$user->default_values[$relativepathstring]['filters'][$paramname]);
1339
1340 // We save search key only if $out not empty that means:
1341 // - posted value not empty, or
1342 // - 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).
1343
1344 if ($out != '' && isset($user)) { // $out = '0' or 'abc', it is a search criteria to keep
1345 $user->lastsearch_values_tmp[$relativepathstring][$paramname] = $out;
1346 }
1347 }
1348 }
1349
1350 return $out;
1351}
1352
1362function sanitizeVal($out = '', $check = 'alphanohtml', $filter = null, $options = null)
1363{
1364 // TODO : use class "Validate" to perform tests (and add missing tests) if needed for factorize
1365 // Check is done after replacement
1366 if ($out === null) {
1367 $out = '';
1368 }
1369 switch ($check) {
1370 case 'none':
1371 case 'password':
1372 break;
1373 case 'int': // Check param is a numeric value (integer but also float or hexadecimal)
1374 if (!is_numeric($out)) {
1375 $out = '';
1376 }
1377 break;
1378 case 'intcomma':
1379 if (is_array($out)) {
1380 $out = implode(',', $out);
1381 }
1382 if (preg_match('/[^0-9,-]+/i', $out)) {
1383 $out = '';
1384 }
1385 break;
1386 case 'san_alpha':
1387 dol_syslog("Use of parameter value 'san_alpha' in GETPOST is deprecated. Use 'alphanohtml', 'aZ09comma', ...", LOG_WARNING);
1388 $out = filter_var($out, FILTER_SANITIZE_STRING);
1389 break;
1390 case 'email':
1391 $out = filter_var($out, FILTER_SANITIZE_EMAIL);
1392 break;
1393 case 'url':
1394 //$out = filter_var($out, FILTER_SANITIZE_URL); // Not reliable, replaced with FILTER_VALIDATE_URL
1395 $out = preg_replace('/[^:\/\[\]a-z0-9@\$\'\*\~\.\-_,;\?\!=%&+#]+/i', '', $out);
1396 // TODO Allow ( ) but only into password of https://login:password@domain...
1397 break;
1398 case 'aZ':
1399 if (!is_array($out)) {
1400 $out = trim($out);
1401 if (preg_match('/[^a-z]+/i', $out)) {
1402 $out = '';
1403 }
1404 }
1405 break;
1406 case 'aZ09':
1407 if (!is_array($out)) {
1408 $out = trim($out);
1409 if (preg_match('/[^a-z0-9_\-\.]+/i', $out)) {
1410 $out = '';
1411 }
1412 }
1413 break;
1414 case 'aZ09arobase': // great to sanitize $objecttype parameter
1415 if (!is_array($out)) {
1416 $out = trim($out);
1417 if (preg_match('/[^a-z0-9_\-\.@]+/i', $out)) {
1418 $out = '';
1419 }
1420 }
1421 break;
1422 case 'aZ09comma': // great to sanitize $sortfield or $sortorder params that can be 't.abc,t.def_gh'
1423 if (!is_array($out)) {
1424 $out = trim($out);
1425 if (preg_match('/[^a-z0-9_\-\.,]+/i', $out)) {
1426 $out = '';
1427 }
1428 }
1429 break;
1430 case 'alpha': // No html and no ../ and "
1431 case 'alphanohtml': // Recommended for most scalar parameters and search parameters. Not valid for json string.
1432 if (!is_array($out)) {
1433 $out = trim($out);
1434 do {
1435 $oldstringtoclean = $out;
1436 // Remove html tags
1437 $out = dol_string_nohtmltag($out, 0);
1438 // 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).
1439 $out = preg_replace('/\\\‍([0-9xu])/', '/\1', $out);
1440 // Remove also other dangerous string sequences
1441 // '../' or '..\' is dangerous because it allows dir transversals
1442 // '&#38', '&#0000038', '&#x26'... is a the char '&' alone but there is no reason to accept such way to encode input char
1443 // '"' = '&#34' = '&#0000034' = '&#x22' is dangerous because param in url can close the href= or src= and add javascript functions.
1444 // '&#47', '&#0000047', '&#x2F' is the char '/' but there is no reason to accept such way to encode this input char
1445 // '&#92' = '&#0000092' = '&#x5C' is the char '\' but there is no reason to accept such way to encode this input char
1446 $out = str_ireplace(array('../', '..\\', '&#38', '&#0000038', '&#x26', '&quot', '"', '&#34', '&#0000034', '&#x22', '&#47', '&#0000047', '&#x2F', '&#92', '&#0000092', '&#x5C'), '', $out);
1447 } while ($oldstringtoclean != $out);
1448 // keep lines feed
1449 }
1450 break;
1451 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'
1452 if (!is_array($out)) {
1453 $out = trim($out);
1454 do {
1455 $oldstringtoclean = $out;
1456 // Decode html entities
1457 $out = dol_html_entity_decode($out, ENT_COMPAT | ENT_HTML5, 'UTF-8');
1458 // 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).
1459 $out = preg_replace('/\\\‍([0-9xu])/', '/\1', $out);
1460 // Remove also other dangerous string sequences
1461 // '../' or '..\' is dangerous because it allows dir transversals
1462 // '&#38', '&#0000038', '&#x26'... is a the char '&' alone but there is no reason to accept such way to encode input char
1463 // '"' = '&#34' = '&#0000034' = '&#x22' is dangerous because param in url can close the href= or src= and add javascript functions.
1464 // '&#47', '&#0000047', '&#x2F' is the char '/' but there is no reason to accept such way to encode this input char
1465 // '&#92' = '&#0000092' = '&#x5C' is the char '\' but there is no reason to accept such way to encode this input char
1466 $out = str_ireplace(array('../', '..\\', '&#38', '&#0000038', '&#x26', '&quot', '"', '&#34', '&#0000034', '&#x22', '&#47', '&#0000047', '&#x2F', '&#92', '&#0000092', '&#x5C'), '', $out);
1467 } while ($oldstringtoclean != $out);
1468 }
1469 break;
1470 case 'nohtml': // No html. Valid for JSON strings.
1471 $out = dol_string_nohtmltag($out, 0);
1472 break;
1473 case 'restricthtmlnolink':
1474 case 'restricthtml': // Recommended for most html textarea
1475 case 'restricthtmlallowclass':
1476 case 'restricthtmlallowiframe':
1477 case 'restricthtmlallowlinkscript': // Allow link and script tag for head section.
1478 case 'restricthtmlallowunvalid':
1479 $out = dol_htmlwithnojs($out, 1, $check);
1480 break;
1481 case 'custom':
1482 if (!empty($out)) {
1483 if (empty($filter)) {
1484 return 'BadParameterForGETPOST - Param 3 of sanitizeVal()';
1485 }
1486 if (is_null($options)) {
1487 $options = 0;
1488 }
1489 $out = filter_var($out, $filter, $options);
1490 }
1491 break;
1492 default:
1493 dol_syslog("Error, you call sanitizeVal() with a bad value for the check type. Data will be sanitized with alphanohtml.", LOG_ERR);
1494 $out = GETPOST($out, 'alphanohtml');
1495 break;
1496 }
1497
1498 return $out;
1499}
1500
1509function dolSetCookie(string $cookiename, string $cookievalue, int $expire = -1)
1510{
1511 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/securitycore.lib.php';
1512
1513 global $dolibarr_main_force_https;
1514
1515 if ($expire == -1) {
1516 $expire = (time() + (86400 * 354)); // keep cookie 1 year.
1517 }
1518
1519 if (PHP_VERSION_ID < 70300) {
1520 setcookie($cookiename, empty($cookievalue) ? '' : $cookievalue, empty($cookievalue) ? 0 : $expire, '/', '', !(empty($dolibarr_main_force_https) && isHTTPS() === false), true); // add tag httponly
1521 } else {
1522 // Only available for php >= 7.3
1523 $cookieparams = array(
1524 'expires' => empty($cookievalue) ? 0 : $expire,
1525 'path' => '/',
1526 //'domain' => '.mywebsite.com', // the dot at the beginning allows compatibility with subdomains
1527 'secure' => !(empty($dolibarr_main_force_https) && isHTTPS() === false),
1528 'httponly' => true,
1529 'samesite' => 'Lax' // None || Lax || Strict
1530 );
1531 setcookie($cookiename, empty($cookievalue) ? '' : $cookievalue, $cookieparams);
1532 }
1533 if (empty($cookievalue)) {
1534 unset($_COOKIE[$cookiename]);
1535 }
1536}
1537
1538if (!function_exists('dol_getprefix')) {
1549 function dol_getprefix($mode = '')
1550 {
1551 // If prefix is for email (we need to have $conf already loaded for this case)
1552 if ($mode == 'email') {
1553 global $conf;
1554
1555 if (getDolGlobalString('MAIL_PREFIX_FOR_EMAIL_ID')) { // If MAIL_PREFIX_FOR_EMAIL_ID is set
1556 if (getDolGlobalString('MAIL_PREFIX_FOR_EMAIL_ID') != 'SERVER_NAME') {
1557 return getDolGlobalString('MAIL_PREFIX_FOR_EMAIL_ID');
1558 } elseif (isset($_SERVER["SERVER_NAME"])) { // If MAIL_PREFIX_FOR_EMAIL_ID is set to 'SERVER_NAME'
1559 return $_SERVER["SERVER_NAME"];
1560 }
1561 }
1562
1563 // The recommended value if MAIL_PREFIX_FOR_EMAIL_ID is not defined (may be not defined for old versions)
1564 if (!empty($conf->file->instance_unique_id)) {
1565 return sha1('dolibarr' . $conf->file->instance_unique_id);
1566 }
1567
1568 // For backward compatibility when instance_unique_id is not set
1569 return sha1(DOL_DOCUMENT_ROOT . DOL_URL_ROOT);
1570 }
1571
1572 // If prefix is for session (no need to have $conf loaded)
1573 global $dolibarr_main_instance_unique_id, $dolibarr_main_cookie_cryptkey; // This is loaded by filefunc.inc.php
1574 $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
1575
1576 // The recommended value (may be not defined for old versions)
1577 if (!empty($tmp_instance_unique_id)) {
1578 return sha1('dolibarr' . $tmp_instance_unique_id);
1579 }
1580
1581 // For backward compatibility when instance_unique_id is not set
1582 if (isset($_SERVER["SERVER_NAME"]) && isset($_SERVER["DOCUMENT_ROOT"])) {
1583 return sha1($_SERVER["SERVER_NAME"] . $_SERVER["DOCUMENT_ROOT"] . DOL_DOCUMENT_ROOT . DOL_URL_ROOT);
1584 } else {
1585 return sha1(DOL_DOCUMENT_ROOT . DOL_URL_ROOT);
1586 }
1587 }
1588}
1589
1600function dol_include_once($relpath, $classname = '')
1601{
1602 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']
1603
1604 if (strpos($relpath, '..') !== false) {
1605 // Found a not valid path
1606 dol_syslog('functions::dol_include_once Tried to load a file with a path including a forbidden sequence ".." : ' . $relpath, LOG_WARNING);
1607 return false;
1608 }
1609 if (!preg_match('/\.php$/', $relpath)) {
1610 // Found a not valid path
1611 dol_syslog('functions::dol_include_once Tried to load a file that is not a PHP file : ' . $relpath, LOG_WARNING);
1612 return false;
1613 }
1614
1615 $fullpath = dol_buildpath($relpath);
1616
1617 if (!file_exists($fullpath)) {
1618 dol_syslog('functions::dol_include_once Tried to load unexisting file: ' . $relpath, LOG_WARNING);
1619 return false;
1620 }
1621 if (!empty($classname) && !class_exists($classname)) {
1622 return include $fullpath;
1623 } else {
1624 return include_once $fullpath;
1625 }
1626}
1627
1628
1642function dol_buildpath($path, $type = 0, $returnemptyifnotfound = 0)
1643{
1644 global $conf;
1645
1646 $path = preg_replace('/^\//', '', $path);
1647
1648 if (empty($type)) { // For a filesystem path
1649 $res = DOL_DOCUMENT_ROOT . '/' . $path; // Standard default path
1650 if (is_array($conf->file->dol_document_root)) {
1651 foreach ($conf->file->dol_document_root as $key => $dirroot) { // ex: array("main"=>"/home/main/htdocs", "alt0"=>"/home/dirmod/htdocs", ...)
1652 if ($key == 'main') {
1653 continue;
1654 }
1655 // if (@file_exists($dirroot.'/'.$path)) {
1656 if (@file_exists($dirroot . '/' . $path)) { // avoid [php:warn]
1657 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/...'
1658 continue;
1659 }
1660 $res = $dirroot . '/' . $path;
1661 return $res;
1662 }
1663 }
1664 }
1665 if ($returnemptyifnotfound) {
1666 // Not found into alternate dir
1667 if ($returnemptyifnotfound == 1 || !file_exists($res)) {
1668 return '';
1669 }
1670 }
1671 } else {
1672 // For an url path
1673 // We try to get local path of file on filesystem from url
1674 // Note that trying to know if a file on disk exist by forging path on disk from url
1675 // works only for some web server and some setup. This is bugged when
1676 // using proxy, rewriting, virtual path, etc...
1677 $res = '';
1678 if ($type == 1) {
1679 $res = DOL_URL_ROOT . '/' . $path; // Standard value
1680 }
1681 if ($type == 2) {
1682 $res = DOL_MAIN_URL_ROOT . '/' . $path; // Standard value
1683 }
1684 if ($type == 3) {
1685 $res = DOL_URL_ROOT . '/' . $path;
1686 }
1687
1688 foreach ($conf->file->dol_document_root as $key => $dirroot) { // ex: array(["main"]=>"/home/main/htdocs", ["alt0"]=>"/home/dirmod/htdocs", ...)
1689 if ($key == 'main') {
1690 if ($type == 3) {
1691 /*global $dolibarr_main_url_root;*/
1692
1693 // Define $urlwithroot
1694 $urlwithouturlroot = preg_replace('/' . preg_quote(DOL_URL_ROOT, '/') . '$/i', '', trim($conf->file->dol_main_url_root));
1695 $urlwithroot = $urlwithouturlroot . DOL_URL_ROOT; // This is to use external domain name found into config file
1696 //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current
1697
1698 $res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : $urlwithroot) . '/' . $path; // Test on start with http is for old conf syntax
1699 }
1700 continue;
1701 }
1702 $regs = array();
1703 preg_match('/^([^\?]+(\.css\.php|\.css|\.js\.php|\.js|\.png|\.jpg|\.php)?)/i', $path, $regs); // Take part before '?'
1704 if (!empty($regs[1])) {
1705 //print $key.'-'.$dirroot.'/'.$path.'-'.$conf->file->dol_url_root[$type].'<br>'."\n";
1706 //if (file_exists($dirroot.'/'.$regs[1])) {
1707 if (@file_exists($dirroot . '/' . $regs[1])) { // avoid [php:warn]
1708 if ($type == 1) {
1709 $res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : DOL_URL_ROOT) . $conf->file->dol_url_root[$key] . '/' . $path;
1710 } elseif ($type == 2) {
1711 $res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : DOL_MAIN_URL_ROOT) . $conf->file->dol_url_root[$key] . '/' . $path;
1712 } elseif ($type == 3) {
1713 /*global $dolibarr_main_url_root;*/
1714
1715 // Define $urlwithroot
1716 $urlwithouturlroot = preg_replace('/' . preg_quote(DOL_URL_ROOT, '/') . '$/i', '', trim($conf->file->dol_main_url_root));
1717 $urlwithroot = $urlwithouturlroot . DOL_URL_ROOT; // This is to use external domain name found into config file
1718 //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current
1719
1720 $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
1721 }
1722 break;
1723 }
1724 }
1725 }
1726 }
1727
1728 return $res;
1729}
1730
1740function dolBuildUrl($url, $params = [], $addtoken = false, $anchor = '')
1741{
1742 global $db, $hookmanager;
1743
1744 if (!is_object($hookmanager)) {
1745 include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
1746 $hookmanager = new HookManager($db);
1747 }
1748 if ((!isset($params['mainmenu']) || empty($params['mainmenu'])) && GETPOSTISSET('mainmenu')) {
1749 $params = array_merge($params, ['mainmenu' => (GETPOST('mainmenu', 'restricthtml'))]);
1750 }
1751 if ((!isset($params['leftmenu'])/* || empty($params['leftmenu']) */) && GETPOSTISSET('leftmenu')) { // do not fill leftmenu if we have leftmenu=
1752 $params = array_merge($params, ['leftmenu' => (GETPOST('leftmenu', 'restricthtml'))]);
1753 }
1754 $parameters = [
1755 'path' => &$url,
1756 'params' => &$params,
1757 'addtoken' => &$addtoken,
1758 ];
1759 $hookmanager->executeHooks('buildurl', $parameters);
1760 if ($addtoken) {
1761 $params = array_merge($params, ['token' => newToken()]);
1762 }
1763 if ($params) {
1764 $url .= '?' . http_build_query($params);
1765 }
1766 if ($anchor) {
1767 $url .= '#' . preg_replace('/[^a-z]/i', '', $anchor);
1768 }
1769
1770 return $url;
1771}
1772
1783function dol_get_object_properties($obj, $properties = [])
1784{
1785 // Get real properties using get_object_vars() if $properties is empty
1786 if (empty($properties)) {
1787 return get_object_vars($obj);
1788 }
1789
1790 $existingProperties = [];
1791 $realProperties = get_object_vars($obj);
1792
1793 // Get the real or magic property values
1794 foreach ($properties as $property) {
1795 if (array_key_exists($property, $realProperties)) {
1796 // Real property, add the value
1797 $existingProperties[$property] = $obj->{$property};
1798 } elseif (property_exists($obj, $property)) {
1799 // Magic property
1800 $existingProperties[$property] = $obj->{$property};
1801 }
1802 }
1803
1804 return $existingProperties;
1805}
1806
1807
1825function dol_clone($srcobject, $native = 2)
1826{
1827 if ($native == 0) {
1828 // deprecated method, use the method with native = 2 instead
1829 dol_syslog("Warning, call to dol_clone() with the deprecated parameter native=0, use 2 instead", LOG_WARNING);
1830
1831 $tmpsavdb = null;
1832 if (isset($srcobject->db) && isset($srcobject->db->db) && is_object($srcobject->db->db) && get_class($srcobject->db->db) == 'PgSql\Connection') {
1833 $tmpsavdb = $srcobject->db;
1834 unset($srcobject->db); // Such property can not be serialized with pgsl (when object->db->db = 'PgSql\Connection')
1835 }
1836
1837 $myclone = unserialize(serialize($srcobject)); // serialize then unserialize is a hack to be sure to have a new object for all fields
1838
1839 if (!empty($tmpsavdb)) {
1840 $srcobject->db = $tmpsavdb;
1841 }
1842 } elseif ($native == 2) {
1843 // recommended method to have a full secured isolated cloned object
1844 $myclone = new stdClass();
1845 $tmparray = get_object_vars($srcobject); // return only public properties
1846
1847 if (is_array($tmparray)) {
1848 foreach ($tmparray as $propertykey => $propertyval) {
1849 if (is_scalar($propertyval) || is_array($propertyval)) {
1850 $myclone->$propertykey = $propertyval;
1851 }
1852 }
1853 }
1854 } else {
1855 $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)
1856 }
1857
1858 return $myclone;
1859}
1860
1861
1870function dol_clone_in_array($srcobject, $startlevel = 0)
1871{
1872 if (is_object($srcobject)) {
1873 $srcobject = get_object_vars($srcobject); // exclude private/protected properties
1874 }
1875
1876 if (is_array($srcobject)) {
1877 $result = [];
1878 foreach ($srcobject as $key => $value) {
1879 if (in_array($key, array('db', 'fields', 'error', 'errorhidden', 'errors', 'oldcopy', 'linkedObjects', 'linked_objects'))) {
1880 continue;
1881 }
1882 $result[$key] = dol_clone_in_array($value, $startlevel + 1);
1883 }
1884 return $result;
1885 }
1886
1887 return $srcobject;
1888}
1889
1890
1900function dol_size($size, $type = '')
1901{
1902 global $conf;
1903 if (empty($conf->dol_optimize_smallscreen)) {
1904 return $size;
1905 }
1906 if ($type == 'width' && $size > 250) {
1907 return 250;
1908 } else {
1909 return 10;
1910 }
1911}
1912
1913
1927function dol_sanitizeFileName($str, $newstr = '_', $unaccent = 1, $includequotes = 0, $allowdash = 0)
1928{
1929 $str = (string) $str;
1930
1931 // List of special chars for filenames in windows are defined on page https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
1932 // Char '>' '<' '|' '$' and ';' are special chars for shells.
1933 // Char '/' and '\' are file delimiters.
1934 // 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
1935 $filesystem_forbidden_chars = array('<', '>', '/', '\\', '?', '*', '|', '"', ':', '°', '$', ';', '`');
1936 if ($includequotes) {
1937 $filesystem_forbidden_chars[] = "'";
1938 }
1939 $tmp = dol_string_nospecial($unaccent ? dol_string_unaccent($str) : $str, $newstr, $filesystem_forbidden_chars);
1940 $tmp = preg_replace('/\-\-+/', '_', $tmp);
1941 if (empty($allowdash)) {
1942 $tmp = preg_replace('/\s+\-([^\s])/', ' _$1', $tmp);
1943 $tmp = preg_replace('/\s+\-$/', '', $tmp);
1944 }
1945 $tmp = str_replace('..', '', $tmp);
1946 $tmp = str_replace('~', $newstr, $tmp);
1947 $tmp = preg_replace('/\s{2,}/', ' ', $tmp);
1948
1949 return $tmp;
1950}
1951
1952
1965function dol_sanitizePathName($str, $newstr = '_', $unaccent = 0, $allowdash = 0)
1966{
1967 // List of special chars for filenames in windows are defined on page https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
1968 // Char '>' '<' '|' '$' ';' and '`' are special chars for shells.
1969 // Char '?' and '*' are for wild card chars.
1970 // Char '"' is dangerous.
1971 // Char '°' is just not expected.
1972 // 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
1973 // Chars '--' and '~' can be used for path transversal
1974 $filesystem_forbidden_chars = array('<', '>', '?', '*', '|', '"', '°', '$', ';', '`');
1975
1976 $tmp = $str;
1977 if ($unaccent) {
1978 $tmp = dol_string_unaccent($tmp);
1979 }
1980 $tmp = dol_string_nospecial($tmp, $newstr, $filesystem_forbidden_chars);
1981 $tmp = preg_replace('/\-\-+/', $newstr, $tmp);
1982 if (empty($allowdash)) {
1983 $tmp = preg_replace('/\s+\-([^\s])/', ' '.$newstr.'$1', $tmp);
1984 $tmp = preg_replace('/\s+\-$/', '', $tmp);
1985 }
1986 $tmp = str_replace('..', $newstr, $tmp);
1987 $tmp = str_replace('~', $newstr, $tmp);
1988 $tmp = preg_replace('/\s{2,}/', ' ', $tmp);
1989
1990 return $tmp;
1991}
1992
2000function dol_sanitizeUrl($stringtoclean, $type = 1)
2001{
2002 // 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)
2003 // We should use dol_string_nounprintableascii but function may not be yet loaded/available
2004 $stringtoclean = preg_replace('/[\x00-\x1F\x7F]/u', '', $stringtoclean); // /u operator makes UTF8 valid characters being ignored so are not included into the replace
2005 // We clean html comments because some hacks try to obfuscate evil strings by inserting HTML comments. Example: on<!-- -->error=alert(1)
2006 $stringtoclean = preg_replace('/<!--[^>]*-->/', '', $stringtoclean);
2007
2008 $stringtoclean = str_replace('\\', '/', $stringtoclean);
2009 if ($type == 1) {
2010 // removing : should disable links to external url like http:aaa)
2011 // removing ';' should disable "named" html entities encode into an url (we should not have this into an url)
2012 $stringtoclean = str_replace(array(':', ';', '@'), '', $stringtoclean);
2013 }
2014
2015 do {
2016 $oldstringtoclean = $stringtoclean;
2017 // removing '&colon' should disable links to external url like http:aaa)
2018 // removing '&#' should disable "numeric" html entities encode into an url (we should not have this into an url)
2019 $stringtoclean = str_ireplace(array('javascript', 'vbscript', '&colon', '&#'), '', $stringtoclean);
2020 } while ($oldstringtoclean != $stringtoclean);
2021
2022 if ($type == 1) {
2023 // removing '//' should disable links to external url like //aaa or http//)
2024 $stringtoclean = preg_replace(array('/^[a-z]*\/\/+/i'), '', $stringtoclean);
2025 }
2026
2027 return $stringtoclean;
2028}
2029
2036function dol_sanitizeEmail($stringtoclean)
2037{
2038 do {
2039 $oldstringtoclean = $stringtoclean;
2040 $stringtoclean = str_ireplace(array('"', ':', '[', ']', "\n", "\r", '\\', '\/'), '', $stringtoclean);
2041 } while ($oldstringtoclean != $stringtoclean);
2042
2043 return $stringtoclean;
2044}
2045
2054function dol_sanitizeKeyCode($str)
2055{
2056 return preg_replace('/[^\w]+/', '', $str);
2057}
2058
2059
2068function dol_string_unaccent($str)
2069{
2070 if (is_null($str)) {
2071 return '';
2072 }
2073
2074 if (utf8_check($str)) {
2075 if (extension_loaded('intl') && getDolGlobalString('MAIN_UNACCENT_USE_TRANSLITERATOR')) {
2076 $transliterator = Transliterator::createFromRules(':: Any-Latin; :: Latin-ASCII; :: NFD; :: [:Nonspacing Mark:] Remove; :: NFC;', Transliterator::FORWARD);
2077 return $transliterator->transliterate($str);
2078 }
2079 // See http://www.utf8-chartable.de/
2080 $string = rawurlencode($str);
2081 $replacements = array(
2082 '%C3%80' => 'A',
2083 '%C3%81' => 'A',
2084 '%C3%82' => 'A',
2085 '%C3%83' => 'A',
2086 '%C3%84' => 'A',
2087 '%C3%85' => 'A',
2088 '%C3%87' => 'C',
2089 '%C3%88' => 'E',
2090 '%C3%89' => 'E',
2091 '%C3%8A' => 'E',
2092 '%C3%8B' => 'E',
2093 '%C3%8C' => 'I',
2094 '%C3%8D' => 'I',
2095 '%C3%8E' => 'I',
2096 '%C3%8F' => 'I',
2097 '%C3%91' => 'N',
2098 '%C3%92' => 'O',
2099 '%C3%93' => 'O',
2100 '%C3%94' => 'O',
2101 '%C3%95' => 'O',
2102 '%C3%96' => 'O',
2103 '%C5%A0' => 'S',
2104 '%C3%99' => 'U',
2105 '%C3%9A' => 'U',
2106 '%C3%9B' => 'U',
2107 '%C3%9C' => 'U',
2108 '%C3%9D' => 'Y',
2109 '%C5%B8' => 'y',
2110 '%C3%A0' => 'a',
2111 '%C3%A1' => 'a',
2112 '%C3%A2' => 'a',
2113 '%C3%A3' => 'a',
2114 '%C3%A4' => 'a',
2115 '%C3%A5' => 'a',
2116 '%C3%A7' => 'c',
2117 '%C3%A8' => 'e',
2118 '%C3%A9' => 'e',
2119 '%C3%AA' => 'e',
2120 '%C3%AB' => 'e',
2121 '%C3%AC' => 'i',
2122 '%C3%AD' => 'i',
2123 '%C3%AE' => 'i',
2124 '%C3%AF' => 'i',
2125 '%C3%B1' => 'n',
2126 '%C3%B2' => 'o',
2127 '%C3%B3' => 'o',
2128 '%C3%B4' => 'o',
2129 '%C3%B5' => 'o',
2130 '%C3%B6' => 'o',
2131 '%C5%A1' => 's',
2132 '%C3%B9' => 'u',
2133 '%C3%BA' => 'u',
2134 '%C3%BB' => 'u',
2135 '%C3%BC' => 'u',
2136 '%C3%BD' => 'y',
2137 '%C3%BF' => 'y',
2138 '%CC%80' => '',
2139 '%CC%81' => '',
2140 '%CC%82' => '',
2141 '%CC%83' => '',
2142 '%CC%84' => '',
2143 '%CC%85' => '',
2144 '%CC%86' => '',
2145 '%CC%87' => '',
2146 '%CC%88' => '',
2147 '%CC%89' => '',
2148 '%CC%8A' => '',
2149 '%CC%8B' => '',
2150 '%CC%8C' => '',
2151 '%CC%8D' => '',
2152 '%CC%8E' => '',
2153 '%CC%8F' => '',
2154 '%CC%90' => '',
2155 '%CC%91' => '',
2156 '%CC%A7' => '',
2157 );
2158 $string = strtr($string, $replacements);
2159 return rawurldecode($string);
2160 } else {
2161 // See http://www.ascii-code.com/
2162 $string = strtr(
2163 $str,
2164 "\xC0\xC1\xC2\xC3\xC4\xC5\xC7
2165 \xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1
2166 \xD2\xD3\xD4\xD5\xD8\xD9\xDA\xDB\xDD
2167 \xE0\xE1\xE2\xE3\xE4\xE5\xE7\xE8\xE9\xEA\xEB
2168 \xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF8
2169 \xF9\xFA\xFB\xFC\xFD\xFF",
2170 "AAAAAAC
2171 EEEEIIIIDN
2172 OOOOOUUUY
2173 aaaaaaceeee
2174 iiiidnooooo
2175 uuuuyy"
2176 );
2177 $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"));
2178 return $string;
2179 }
2180}
2181
2195function dol_string_nospecial($str, $newstr = '_', $badcharstoreplace = '', $badcharstoremove = '', $keepspaces = 0)
2196{
2197 $forbidden_chars_to_replace = array("'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ",", ";", "=", '°', '$', ';'); // more complete than dol_sanitizeFileName
2198 if (empty($keepspaces)) {
2199 $forbidden_chars_to_replace[] = " ";
2200 }
2201 $forbidden_chars_to_remove = array();
2202 //$forbidden_chars_to_remove=array("(",")");
2203
2204 if (is_array($badcharstoreplace)) {
2205 $forbidden_chars_to_replace = $badcharstoreplace;
2206 }
2207 if (is_array($badcharstoremove)) {
2208 $forbidden_chars_to_remove = $badcharstoremove;
2209 }
2210
2211 // @phan-suppress-next-line PhanPluginSuspiciousParamOrderInternal
2212 return str_replace($forbidden_chars_to_replace, $newstr, str_replace($forbidden_chars_to_remove, "", $str));
2213}
2214
2215
2229function dol_string_nounprintableascii($str, $removetabcrlf = 1)
2230{
2231 if ($removetabcrlf) {
2232 return preg_replace('/[\x00-\x1F\x7F]/u', '', $str); // /u operator makes UTF8 valid characters being ignored so are not included into the replace
2233 } else {
2234 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
2235 }
2236}
2237
2244function dolSlugify($stringtoslugify)
2245{
2246 $slug = dol_string_unaccent($stringtoslugify);
2247
2248 // Convert special characters to their ASCII equivalents
2249 if (function_exists('iconv')) {
2250 $slug = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $slug);
2251 }
2252
2253 // Convert to lowercase
2254 $slug = strtolower($slug);
2255
2256 // Replace non-alphanumeric characters with hyphens
2257 $slug = preg_replace('/[^a-z0-9]+/', '-', $slug);
2258
2259 // Remove leading and trailing hyphens
2260 $slug = trim($slug, '-');
2261
2262 return $slug;
2263}
2264
2273function dol_escape_js($stringtoescape, $mode = 0, $noescapebackslashn = 0)
2274{
2275 if (is_null($stringtoescape)) {
2276 return '';
2277 }
2278
2279 // escape quotes and backslashes, newlines, etc.
2280 $substitjs = array("&#039;" => "\\'", "\r" => '\\r');
2281 //$substitjs['</']='<\/'; // We removed this. Should be useless.
2282 if (empty($noescapebackslashn)) {
2283 $substitjs["\n"] = '\\n';
2284 $substitjs['\\'] = '\\\\';
2285 }
2286 if (empty($mode)) {
2287 $substitjs["'"] = "\\'";
2288 $substitjs['"'] = "\\'";
2289 } elseif ($mode == 1) {
2290 $substitjs["'"] = "\\'";
2291 } elseif ($mode == 2) {
2292 $substitjs['"'] = '\\"';
2293 } elseif ($mode == 3) {
2294 $substitjs["'"] = "\\'";
2295 $substitjs['"'] = "\\\"";
2296 }
2297 return strtr((string) $stringtoescape, $substitjs);
2298}
2299
2309function dol_escape_uri($stringtoescape)
2310{
2311 return rawurlencode($stringtoescape);
2312}
2313
2320function dol_escape_json($stringtoescape)
2321{
2322 return str_replace('"', '\"', $stringtoescape);
2323}
2324
2332function dol_escape_php($stringtoescape, $stringforquotes = 2)
2333{
2334 if (is_null($stringtoescape)) {
2335 return '';
2336 }
2337
2338 if ($stringforquotes == 2) {
2339 return str_replace('"', "'", $stringtoescape);
2340 } elseif ($stringforquotes == 1) {
2341 // We remove the \ char.
2342 // If we allow the \ char, we can have $stringtoescape =
2343 // abc\';phpcodedanger; so the escapement will become
2344 // abc\\';phpcodedanger; and injecting this into
2345 // $a='...' will give $ac='abc\\';phpcodedanger;
2346 $stringtoescape = str_replace('\\', '', $stringtoescape);
2347 return str_replace("'", "\'", str_replace('"', "'", $stringtoescape));
2348 }
2349
2350 return 'Bad parameter for stringforquotes in dol_escape_php';
2351}
2352
2359function dol_escape_all($stringtoescape)
2360{
2361 return preg_replace('/[^a-z0-9_]/i', '', $stringtoescape);
2362}
2363
2370function dol_escape_xml($stringtoescape)
2371{
2372 return $stringtoescape;
2373}
2374
2384function dolPrintLabel($s, $escapeonlyhtmltags = 0)
2385{
2386 return dol_escape_htmltag(dol_string_nohtmltag($s, 1, 'UTF-8', 0, 0), 0, 0, '', $escapeonlyhtmltags, 1);
2387}
2388
2397function dolPrintText($s)
2398{
2399 return dol_escape_htmltag(dol_string_nohtmltag($s, 2, 'UTF-8', 0, 0), 0, 1, '', 0, 1);
2400}
2401
2413function dolPrintHTML($s, $allowiframe = 0, $moreallowedtags = array())
2414{
2415 // If text is already HTML, we want to escape only dangerous chars else we want to escape all content.
2416 //$isAlreadyHTML = dol_textishtml($s);
2417
2418 // dol_htmlentitiesbr encode all chars except "'" if string is not already HTML, but
2419 // encode only special char like é but not &, <, >, ", ' if already HTML.
2420 $stringWithEntitesForSpecialChar = dol_htmlentitiesbr((string) $s);
2421
2422 $allowedtags = 'common';
2423 if (!empty($moreallowedtags)) {
2424 $allowedtags .= ','.implode(',', $moreallowedtags);
2425 }
2426 return dol_escape_htmltag(dol_htmlwithnojs(dol_string_onlythesehtmltags($stringWithEntitesForSpecialChar, 1, 1, 1, $allowiframe, $allowedtags)), 1, 1, $allowedtags, 0, 1);
2427}
2428
2439function dolPrintHTMLForAttribute($s, $escapeonlyhtmltags = 0, $allowothertags = array())
2440{
2441 $allowedtags = array('br', 'b', 'font', 'hr', 'span');
2442 if (!empty($allowothertags) && is_array($allowothertags)) {
2443 $allowedtags = array_merge($allowedtags, $allowothertags);
2444 }
2445 // The dol_htmlentitiesbr will convert simple text into html, including switching accent into HTML entities
2446 // The dol_escape_htmltag will escape html tags.
2447 if ($escapeonlyhtmltags) {
2448 return dol_escape_htmltag(dol_string_onlythesehtmltags($s, 1, 0, 0, 0, $allowedtags), 1, -1, '', 1, 1);
2449 } else {
2450 return dol_escape_htmltag(dol_string_onlythesehtmltags(dol_htmlentitiesbr($s), 1, 0, 0, 0, $allowedtags), 1, -1, '', 0, 1);
2451 }
2452}
2453
2462function dolPrintHTMLForAttributeUrl($s)
2463{
2464 // 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)
2465 // The dol_escape_htmltag will escape html chars.
2466 $escapeonlyhtmltags = 1;
2467 return dol_escape_htmltag(dol_string_onlythesehtmltags($s, 1, 1, 1, 0, array()), 0, 0, '', $escapeonlyhtmltags, 1);
2468}
2469
2479function dolPrintHTMLForTextArea($s, $allowiframe = 0)
2480{
2481 return dol_escape_htmltag(dol_htmlwithnojs(dol_string_onlythesehtmltags(dol_htmlentitiesbr($s), 1, 1, 1, $allowiframe)), 1, 1, '', 0, 1);
2482}
2483
2490function dolPrintPassword($s)
2491{
2492 return htmlspecialchars($s, ENT_HTML5, 'UTF-8');
2493}
2494
2495
2512function dol_escape_htmltag($stringtoescape, $keepb = 0, $keepn = 0, $noescapetags = '', $escapeonlyhtmltags = 0, $cleanalsojavascript = 0)
2513{
2514 $reg = array();
2515 if (preg_match('/^common([a-z,]*)/', $noescapetags, $reg)) {
2516 $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';
2517 // Add also html5 tags
2518 $noescapetags .= ',header,footer,nav,section,menu,menuitem';
2519 if (!empty($reg[1])) {
2520 $noescapetags .= $reg[1];
2521 }
2522 }
2523 if ($cleanalsojavascript) {
2524 $stringtoescape = dol_string_onlythesehtmltags($stringtoescape, 0, 0, $cleanalsojavascript, 0, array(), 0);
2525 }
2526
2527 // escape quotes and backslashes, newlines, etc.
2528 if ($escapeonlyhtmltags) {
2529 $tmp = htmlspecialchars_decode((string) $stringtoescape, ENT_COMPAT);
2530 } else {
2531 // We make a manipulation by calling the html_entity_decode() to convert content into NON HTML UTF8 string.
2532 // Because content can be or not already HTML.
2533 // For example, this decode &egrave; into è so string is UTF8 (but numbers entities like &#39; is not decoded).
2534 // In a future, we should not need this
2535
2536 $tmp = (string) $stringtoescape;
2537
2538 // We protect the 6 special entities that we don't want to decode.
2539 $tmp = str_ireplace('&lt', '__DONOTDECODELT', $tmp);
2540 $tmp = str_ireplace('&gt', '__DONOTDECODEGT', $tmp);
2541 $tmp = str_ireplace('&amp', '__DONOTDECODEAMP', $tmp);
2542 $tmp = str_ireplace('&quot', '__DONOTDECODEQUOT', $tmp);
2543 $tmp = str_ireplace('&apos', '__DONOTDECODEAPOS', $tmp);
2544 $tmp = str_ireplace('&#39', '__DONOTDECODE39', $tmp);
2545
2546 $tmp = html_entity_decode((string) $tmp, ENT_COMPAT, 'UTF-8'); // Convert entities into UTF8
2547
2548 // We restore the 6 special entities that we don't want to have been decoded by previous command
2549 $tmp = str_ireplace('__DONOTDECODELT', '&lt', $tmp);
2550 $tmp = str_ireplace('__DONOTDECODEGT', '&gt', $tmp);
2551 $tmp = str_ireplace('__DONOTDECODEAMP', '&amp', $tmp);
2552 $tmp = str_ireplace('__DONOTDECODEQUOT', '&quot', $tmp);
2553 $tmp = str_ireplace('__DONOTDECODEAPOS', '&apos', $tmp);
2554 $tmp = str_ireplace('__DONOTDECODE39', '&#39', $tmp);
2555
2556 $tmp = str_ireplace('&#39;', '__SIMPLEQUOTE__', $tmp); // HTML 4
2557 }
2558 if (!$keepb) {
2559 $tmp = strtr($tmp, array("<b>" => '', '</b>' => '', '<strong>' => '', '</strong>' => ''));
2560 }
2561 if (!$keepn) {
2562 $tmp = strtr($tmp, array("\r" => '\\r', "\n" => '\\n'));
2563 } elseif ($keepn == -1) {
2564 $tmp = strtr($tmp, array("\r" => '', "\n" => ''));
2565 }
2566
2567 if ($escapeonlyhtmltags) {
2568 $tmp = htmlspecialchars($tmp, ENT_COMPAT, 'UTF-8');
2569 return $tmp;
2570 } else {
2571 // Now we protect all the tags we want to keep
2572 $tmparrayoftags = array();
2573 if ($noescapetags) {
2574 $tmparrayoftags = explode(',', $noescapetags);
2575 }
2576
2577 if (count($tmparrayoftags)) {
2578 // Now we will protect tags (defined into $tmparrayoftags) that we want to keep untouched
2579
2580 $reg = array();
2581 // Remove reserved keywords. They are forbidden in a source string
2582 $tmp = str_ireplace(array('__DOUBLEQUOTE', '__BEGINTAGTOREPLACE', '__ENDTAGTOREPLACE', '__BEGINENDTAGTOREPLACE'), '', $tmp);
2583
2584 foreach ($tmparrayoftags as $tagtoreplace) {
2585 // For case of tag without attributes '<abc>', '</abc>', '<abc />', we protect them to avoid transformation by htmlentities() later
2586 $tmp = preg_replace('/<' . preg_quote($tagtoreplace, '/') . '>/', '__BEGINTAGTOREPLACE' . $tagtoreplace . '__', $tmp);
2587 $tmp = str_ireplace('</' . $tagtoreplace . '>', '__ENDTAGTOREPLACE' . $tagtoreplace . '__', $tmp);
2588 $tmp = preg_replace('/<' . preg_quote($tagtoreplace, '/') . ' \/>/', '__BEGINENDTAGTOREPLACE' . $tagtoreplace . '__', $tmp);
2589
2590 // For case of tag with attributes
2591 do {
2592 $tmpold = $tmp;
2593
2594 if (preg_match('/<' . preg_quote($tagtoreplace, '/') . '(\s+)([^>]+)>/', $tmp, $reg)) {
2595 // We want to protect the attribute part ... in '<xxx ...>' to avoid transformation by htmlentities() later
2596 $tmpattributes = str_ireplace(array('[', ']'), '_', $reg[2]); // We must never have [ ] inside the attribute string
2597 $tmpattributes = str_ireplace('"', '__DOUBLEQUOTE__', $tmpattributes);
2598 $tmpattributes = preg_replace('/[^a-z0-9_%,\/\?\;\s=&\.\-@:\.#\+]/i', '', $tmpattributes);
2599 //$tmpattributes = preg_replace("/float:\s*(left|right)/", "", $tmpattributes); // Disabled: we must not remove content
2600 $tmp = str_replace('<' . $tagtoreplace . $reg[1] . $reg[2] . '>', '__BEGINTAGTOREPLACE' . $tagtoreplace . '[' . $tmpattributes . ']__', $tmp);
2601 }
2602
2603 $diff = strcmp($tmpold, $tmp);
2604 } while ($diff);
2605 }
2606
2607 $tmp = str_ireplace('&amp', '__ANDNOSEMICOLON__', $tmp);
2608 $tmp = str_ireplace('&quot', '__DOUBLEQUOTENOSEMICOLON__', $tmp);
2609 $tmp = str_ireplace('&lt', '__LESSTHAN__', $tmp);
2610 $tmp = str_ireplace('&gt', '__GREATERTHAN__', $tmp);
2611 }
2612
2613 // Warning: htmlentities encode all special chars that remains (except "'" with ENT_COMPAT).
2614 $result = htmlentities($tmp, ENT_COMPAT, 'UTF-8');
2615
2616 //print $result;
2617
2618 if (count($tmparrayoftags)) {
2619 // Restore protected tags
2620 foreach ($tmparrayoftags as $tagtoreplace) {
2621 $result = str_ireplace('__BEGINTAGTOREPLACE' . $tagtoreplace . '__', '<' . $tagtoreplace . '>', $result);
2622 $result = preg_replace('/__BEGINTAGTOREPLACE' . $tagtoreplace . '\[([^\]]*)\]__/', '<' . $tagtoreplace . ' \1>', $result);
2623 $result = str_ireplace('__ENDTAGTOREPLACE' . $tagtoreplace . '__', '</' . $tagtoreplace . '>', $result);
2624 $result = str_ireplace('__BEGINENDTAGTOREPLACE' . $tagtoreplace . '__', '<' . $tagtoreplace . ' />', $result);
2625 $result = preg_replace('/__BEGINENDTAGTOREPLACE' . $tagtoreplace . '\[([^\]]*)\]__/', '<' . $tagtoreplace . ' \1 />', $result);
2626 }
2627
2628 $result = str_ireplace('__DOUBLEQUOTE__', '"', $result);
2629
2630 $result = str_ireplace('__ANDNOSEMICOLON__', '&amp', $result);
2631 $result = str_ireplace('__DOUBLEQUOTENOSEMICOLON__', '&quot', $result);
2632 $result = str_ireplace('__LESSTHAN__', '&lt', $result);
2633 $result = str_ireplace('__GREATERTHAN__', '&gt', $result);
2634 }
2635
2636 $result = str_ireplace('__SIMPLEQUOTE__', '&#39;', $result);
2637
2638 //$result="\n\n\n".var_export($tmp, true)."\n\n\n".var_export($result, true);
2639
2640 return $result;
2641 }
2642}
2643
2651function dol_strtolower($string, $encoding = "UTF-8")
2652{
2653 if (function_exists('mb_strtolower')) {
2654 return mb_strtolower($string, $encoding);
2655 } else {
2656 return strtolower($string);
2657 }
2658}
2659
2668function dol_strtoupper($string, $encoding = "UTF-8")
2669{
2670 if (function_exists('mb_strtoupper')) {
2671 return mb_strtoupper($string, $encoding);
2672 } else {
2673 return strtoupper($string);
2674 }
2675}
2676
2685function dol_ucfirst($string, $encoding = "UTF-8")
2686{
2687 if (function_exists('mb_substr')) {
2688 return mb_strtoupper(mb_substr($string, 0, 1, $encoding), $encoding) . mb_substr($string, 1, null, $encoding);
2689 } else {
2690 return ucfirst($string);
2691 }
2692}
2693
2702function dol_ucwords($string, $encoding = "UTF-8")
2703{
2704 if (function_exists('mb_convert_case')) {
2705 return mb_convert_case($string, MB_CASE_TITLE, $encoding);
2706 } else {
2707 return ucwords($string);
2708 }
2709}
2710
2711
2717function getCallerInfoString()
2718{
2719 $backtrace = debug_backtrace();
2720 $msg = "";
2721 if (count($backtrace) >= 1) {
2722 $pos = 1;
2723 if (count($backtrace) == 1) {
2724 $pos = 0;
2725 }
2726 $trace = $backtrace[$pos];
2727 if (isset($trace['file'], $trace['line'])) {
2728 $msg = " From {$trace['file']}:{$trace['line']}.";
2729 }
2730 }
2731 return $msg;
2732}
2733
2756function dol_syslog($message, $level = LOG_INFO, $ident = 0, $suffixinfilename = '', $restricttologhandler = '', $logcontext = null)
2757{
2758 global $conf, $user, $debugbar;
2759
2760 // If syslog module enabled
2761 if (!isModEnabled('syslog')) {
2762 return;
2763 }
2764
2765 // Check if we are into execution of code of a website
2766 if (defined('USEEXTERNALSERVER') && !defined('USEDOLIBARRSERVER') && !defined('USEDOLIBARREDITOR')) {
2767 global $website, $websitekey;
2768 if (is_object($website) && !empty($website->ref)) {
2769 $suffixinfilename .= '_website_' . $website->ref;
2770 } elseif (!empty($websitekey)) {
2771 $suffixinfilename .= '_website_' . $websitekey;
2772 }
2773 }
2774
2775 // Check if we have a forced suffix
2776 if (defined('USESUFFIXINLOG')) {
2777 $suffixinfilename .= constant('USESUFFIXINLOG');
2778 }
2779
2780 if ($ident < 0) {
2781 foreach ($conf->loghandlers as $loghandlerinstance) {
2782 $loghandlerinstance->setIdent($ident);
2783 }
2784 }
2785
2786 if (!empty($message)) {
2787 // Test log level
2788 // @phan-suppress-next-line PhanPluginDuplicateArrayKey
2789 $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');
2790
2791 if (!array_key_exists($level, $logLevels)) {
2792 dol_syslog('Error Bad Log Level ' . $level, LOG_ERR);
2793 $level = LOG_ERR;
2794 }
2795 if ($level > getDolGlobalInt('SYSLOG_LEVEL')) {
2796 return;
2797 }
2798
2799 if (!getDolGlobalString('MAIN_SHOW_PASSWORD_INTO_LOG')) {
2800 $message = preg_replace('/password=\'[^\']*\'/', 'password=\'hidden\'', $message); // protection to avoid to have value of password in log
2801 }
2802
2803 // If adding log inside HTML page is required
2804 if ((!empty($_REQUEST['logtohtml']) && getDolGlobalString('MAIN_ENABLE_LOG_TO_HTML'))
2805 || (is_object($user) && $user->hasRight('debugbar', 'read') && is_object($debugbar))
2806 ) {
2807 $ospid = sprintf("%7s", dol_trunc((string) getmypid(), 7, 'right', 'UTF-8', 1));
2808 $osuser = " " . sprintf("%6s", dol_trunc(function_exists('posix_getuid') ? posix_getuid() : '', 6, 'right', 'UTF-8', 1));
2809
2810 $conf->logbuffer[] = dol_print_date(time(), "%Y-%m-%d %H:%M:%S") . " " . sprintf("%-7s", $logLevels[$level]) . " " . $ospid . " " . $osuser . " " . $message;
2811 }
2812
2813 //TODO: Remove this. MAIN_ENABLE_LOG_INLINE_HTML should be deprecated and use a log handler dedicated to HTML output
2814 // If html log tag enabled and url parameter log defined, we show output log on HTML comments
2815 if (getDolGlobalString('MAIN_ENABLE_LOG_INLINE_HTML') && GETPOSTINT("log")) {
2816 print "\n\n<!-- Log start\n";
2817 print dol_escape_htmltag($message) . "\n";
2818 print "Log end -->\n";
2819 }
2820
2821 $data = array(
2822 'message' => $message,
2823 'script' => (isset($_SERVER['PHP_SELF']) ? basename($_SERVER['PHP_SELF'], '.php') : ''),
2824 'level' => $level,
2825 'user' => ((is_object($user) && $user->id) ? $user->login : ''),
2826 'ip' => '',
2827 'osuser' => function_exists('posix_getuid') ? (string) posix_getuid() : '',
2828 'ospid' => (string) getmypid() // on linux, max value is defined into cat /proc/sys/kernel/pid_max
2829 );
2830
2831 // For log, we want the reliable IP first.
2832 $remoteip = getUserRemoteIP(1); // Get ip when page run on a web server
2833 if (!empty($remoteip)) {
2834 $data['ip'] = $remoteip;
2835 // This is when server run behind a reverse proxy
2836 // A HTTP_X_FORWARDED_FOR as format "ip real of user, ip of proxy1, ip of proxy2, ..."
2837 // $data['ip'] is last
2838 if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
2839 $tmpips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
2840 $data['ip'] = '';
2841 $foundremoteip = 0;
2842 $j = 0;
2843 foreach ($tmpips as $tmpip) {
2844 $tmpip = trim($tmpip);
2845 if (strtolower($tmpip) == strtolower($remoteip)) {
2846 $foundremoteip = 1;
2847 }
2848 if (empty($data['ip'])) {
2849 $data['ip'] = $tmpip;
2850 } else {
2851 $j++;
2852 $data['ip'] .= (($j == 1) ? ' [via ' : ',') . $tmpip;
2853 }
2854 }
2855 if (!$foundremoteip) {
2856 $j++;
2857 $data['ip'] .= (($j == 1) ? ' [via ' : ',') . $remoteip;
2858 }
2859 $data['ip'] .= (($j > 0) ? ']' : '');
2860 } elseif (!empty($_SERVER['HTTP_CLIENT_IP'])) {
2861 $tmpips = explode(',', $_SERVER['HTTP_CLIENT_IP']);
2862 $data['ip'] = '';
2863 $foundremoteip = 0;
2864 $j = 0;
2865 foreach ($tmpips as $tmpip) {
2866 $tmpip = trim($tmpip);
2867 if (strtolower($tmpip) == strtolower($remoteip)) {
2868 $foundremoteip = 1;
2869 }
2870 if (empty($data['ip'])) {
2871 $data['ip'] = $tmpip;
2872 } else {
2873 $j++;
2874 $data['ip'] .= (($j == 1) ? ' [via ' : ',') . $tmpip;
2875 }
2876 }
2877 if (!$foundremoteip) {
2878 $j++;
2879 $data['ip'] .= (($j == 1) ? ' [via ' : ',') . $remoteip;
2880 }
2881 $data['ip'] .= (($j > 0) ? ']' : '');
2882 }
2883 } elseif (!empty($_SERVER['SERVER_ADDR'])) {
2884 // This is when PHP session is ran inside a web server but not inside a client request (example: init code of apache)
2885 $data['ip'] = (string) $_SERVER['SERVER_ADDR'];
2886 } elseif (!empty($_SERVER['COMPUTERNAME'])) {
2887 // 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).
2888 $data['ip'] = (string) $_SERVER['COMPUTERNAME'];
2889 } else {
2890 $data['ip'] = '???';
2891 }
2892
2893 if (!empty($_SERVER['USERNAME'])) {
2894 // 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).
2895 $data['osuser'] = (string) $_SERVER['USERNAME'];
2896 } elseif (!empty($_SERVER['LOGNAME'])) {
2897 // 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).
2898 $data['osuser'] = (string) $_SERVER['LOGNAME'];
2899 }
2900
2901 // Loop on each log handler and send output
2902 foreach ($conf->loghandlers as $loghandlerinstance) {
2903 if ($restricttologhandler && $loghandlerinstance->code != $restricttologhandler) {
2904 continue;
2905 }
2906 $loghandlerinstance->export($data, $suffixinfilename);
2907 }
2908 unset($data);
2909 }
2910
2911 if ($ident > 0) {
2912 foreach ($conf->loghandlers as $loghandlerinstance) {
2913 $loghandlerinstance->setIdent($ident);
2914 }
2915 }
2916}
2917
2929function dolButtonToOpenExportDialog($name, $label, $buttonstring, $exportSiteName, $overwriteGitUrl, $website)
2930{
2931 global $langs, $db;
2932
2933 $form = new Form($db);
2934
2935 $templatenameforexport = $website->name_template; // Example 'website_template-corporate'
2936 if (empty($templatenameforexport)) {
2937 $templatenameforexport = 'website_' . $website->ref;
2938 }
2939
2940 $out = '';
2941 $out .= '<input type="button" class="cursorpointer button bordertransp" id="open-dialog-' . $name . '" value="' . dol_escape_htmltag($buttonstring) . '"/>';
2942
2943 // for generate popup
2944 $out .= '<script nonce="' . getNonce() . '" type="text/javascript">';
2945 $out .= 'jQuery(document).ready(function () {';
2946 $out .= ' jQuery("#open-dialog-' . $name . '").click(function () {';
2947 $out .= ' var dialogHtml = \'';
2948
2949 $dialogcontent = ' <div id="custom-dialog-' . $name . '">';
2950 $dialogcontent .= ' <div style="margin-top: 20px;">';
2951 $dialogcontent .= ' <label for="export-site-' . $name . '"><strong>' . $langs->trans("ExportSiteLabel") . '...</label><br>';
2952 $dialogcontent .= ' <button class="button smallpaddingimp" id="export-site-' . $name . '">' . dol_escape_htmltag($langs->trans("DownloadZip")) . '</button>';
2953 $dialogcontent .= ' </div>';
2954 $dialogcontent .= ' <br>';
2955 $dialogcontent .= ' <div style="margin-top: 20px;">';
2956 $dialogcontent .= ' <strong>' . $langs->trans("ExportSiteGitLabel") . ' ' . $form->textwithpicto('', $langs->trans("SourceFiles"), 1, 'help', '', 0, 3, '') . '</strong><br>';
2957 $dialogcontent .= ' <form action="' . dol_escape_htmltag($overwriteGitUrl) . '" method="POST">';
2958 $dialogcontent .= ' <input type="hidden" name="action" value="overwritesite">';
2959 $dialogcontent .= ' <input type="hidden" name="token" value="' . newToken() . '">';
2960 $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>';
2961 $dialogcontent .= ' <button type="submit" class="button smallpaddingimp" id="overwrite-git-' . $name . '">' . dol_escape_htmltag($langs->trans("ExportIntoGIT")) . '</button>';
2962 $dialogcontent .= ' </form>';
2963 $dialogcontent .= ' </div>';
2964 $dialogcontent .= ' </div>';
2965
2966 $out .= dol_escape_js($dialogcontent);
2967
2968 $out .= '\';';
2969
2970
2971 // Add the content of the dialog to the body of the page
2972 $out .= ' var $dialog = jQuery("#custom-dialog-' . $name . '");';
2973 $out .= ' if ($dialog.length > 0) {
2974 $dialog.remove();
2975 }
2976 jQuery("body").append(dialogHtml);';
2977
2978 // Configuration of popup
2979 $out .= ' jQuery("#custom-dialog-' . $name . '").dialog({';
2980 $out .= ' autoOpen: false,';
2981 $out .= ' modal: true,';
2982 $out .= ' height: 290,';
2983 $out .= ' width: "40%",';
2984 $out .= ' title: "' . dol_escape_js($label) . '",';
2985 $out .= ' });';
2986
2987 // Simulate a click on the original "submit" input to export the site.
2988 $out .= ' jQuery("#export-site-' . $name . '").click(function () {';
2989 $out .= ' console.log("Clic on exportsite.");';
2990 $out .= ' var target = jQuery("input[name=\'' . dol_escape_js($exportSiteName) . '\']");';
2991 $out .= ' console.log("element founded:", target.length > 0);';
2992 $out .= ' if (target.length > 0) { target.click(); }';
2993 $out .= ' jQuery("#custom-dialog-' . $name . '").dialog("close");';
2994 $out .= ' });';
2995
2996 // open popup
2997 $out .= ' jQuery("#custom-dialog-' . $name . '").dialog("open");';
2998 $out .= ' return false;';
2999 $out .= ' });';
3000 $out .= '});';
3001 $out .= '</script>';
3002
3003 return $out;
3004}
3005
3006
3023function dolButtonToOpenUrlInDialogPopup($name, $label, $buttonstring, $url, $disabled = '', $morecss = 'classlink button bordertransp', $jsonopen = '', $jsonclose = '', $accesskey = '')
3024{
3025 global $conf;
3026
3027 if (strpos($url, '?') > 0) {
3028 $url .= '&dol_hide_topmenu=1&dol_hide_leftmenu=1&dol_openinpopup=' . urlencode($name);
3029 } else {
3030 $url .= '?dol_hide_topmenu=1&dol_hide_leftmenu=1&dol_openinpopup=' . urlencode($name);
3031 }
3032
3033 if (preg_match('/^https/i', $url)) {
3034 $urltoopen = $url;
3035 } else {
3036 $urltoopen = DOL_URL_ROOT . $url;
3037 }
3038
3039 $out = '';
3040
3041 //print '<input type="submit" class="button bordertransp"'.$disabled.' value="'.dol_escape_htmltag($langs->trans("MediaFiles")).'" name="file_manager">';
3042 $out .= '<!-- a link for button to open url into a dialog popup -->';
3043 $out .= '<a ' . ($accesskey ? ' accesskey="' . $accesskey . '"' : '') . ' class="cursorpointer reposition button_' . $name . ($morecss ? ' ' . $morecss : '') . '"' . $disabled . ' title="' . dol_escape_htmltag($label) . '"';
3044 if (empty($conf->use_javascript_ajax)) {
3045 $out .= ' href="' . $urltoopen . '" target="_blank"';
3046 } elseif ($jsonopen) {
3047 $out .= ' href="#" onclick="' . $jsonopen . '"';
3048 } else {
3049 $out .= ' href="#"';
3050 }
3051 $out .= '>' . $buttonstring . '</a>';
3052
3053 if (!empty($conf->use_javascript_ajax)) {
3054 // Add code to open url using the popup.
3055 $out .= '<!-- code to open popup and variables to retrieve returned variables -->';
3056 $out .= '<div id="idfordialog' . $name . '" class="hidden">' . (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2 ? 'div for dialog' : '') . '</div>';
3057
3058 $out .= '<!-- Add js code to open dialog popup on dialog -->';
3059 $out .= '<script nonce="' . getNonce() . '" type="text/javascript">
3060 jQuery(document).ready(function () {
3061 jQuery(".button_' . $name . '").click(function () {
3062 console.log(\'Open popup with jQuery(...).dialog() on URL ' . dol_escape_js($urltoopen) . '\');
3063 var $tmpdialog = $(\'#idfordialog' . $name . '\');
3064 $tmpdialog.html(\'<iframe class="iframedialog" id="iframedialog' . $name . '" style="border: 0px;" src="' . $urltoopen . '" width="100%" height="98%"></iframe>\');
3065 $tmpdialog.dialog({
3066 autoOpen: false,
3067 modal: true,
3068 height: (window.innerHeight - 150),
3069 width: \'80%\',
3070 title: \'' . dol_escape_js($label) . '\',
3071 open: function (event, ui) {
3072 console.log("open popup name=' . $name . '");
3073 },
3074 close: function (event, ui) {
3075 console.log("Popup is closed, run jsonclose = ' . $jsonclose . '");
3076 ' . (empty($jsonclose) ? '' : $jsonclose . ';') . '
3077 }
3078 });
3079
3080 $tmpdialog.dialog(\'open\');
3081 return false;
3082 });
3083 });
3084 </script>';
3085 }
3086 return $out;
3087}
3088
3105function dol_fiche_head($links = array(), $active = '0', $title = '', $notab = 0, $picto = '', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '', $limittoshow = 0, $moretabssuffix = '')
3106{
3107 print dol_get_fiche_head($links, $active, $title, $notab, $picto, $pictoisfullpath, $morehtmlright, $morecss, $limittoshow, $moretabssuffix);
3108}
3109
3127function dol_get_fiche_head($links = array(), $active = '', $title = '', $notab = 0, $picto = '', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '', $limittoshow = 0, $moretabssuffix = '', $dragdropfile = 0, $morecssdiv = '')
3128{
3129 global $conf, $langs, $hookmanager;
3130
3131 // Show title
3132 $showtitle = 1;
3133 if (!empty($conf->dol_optimize_smallscreen)) {
3134 $showtitle = 0;
3135 }
3136
3137 $out = "\n" . '<!-- dol_fiche_head - dol_get_fiche_head -->';
3138
3139 if ((!empty($title) && $showtitle) || $morehtmlright || !empty($links)) {
3140 $out .= '<div class="tabs' . ($picto ? '' : ' nopaddingleft') . '" data-role="controlgroup" data-type="horizontal">' . "\n";
3141 }
3142
3143 // Show right part
3144 if ($morehtmlright) {
3145 $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.
3146 }
3147
3148 // Show tabs
3149
3150 // Define max of key (max may be higher than sizeof because of hole due to module disabling some tabs).
3151 $maxkey = -1;
3152 if (is_array($links) && !empty($links)) {
3153 $keys = array_keys($links);
3154 if (count($keys)) {
3155 $maxkey = max($keys);
3156 }
3157 }
3158
3159 // Show tabs
3160 // if =0 we don't use the feature
3161 if (empty($limittoshow)) {
3162 $limittoshow = getDolGlobalInt('MAIN_MAXTABS_IN_CARD', 99);
3163 }
3164 if (!empty($conf->dol_optimize_smallscreen)) { // If on smartphone, we limit to 1 tab to show
3165 $limittoshow = 1;
3166 }
3167
3168 $displaytab = 0;
3169 $nbintab = 0;
3170 $popuptab = 0;
3171 $outmore = '';
3172 for ($i = 0; $i <= $maxkey; $i++) {
3173 if ((is_numeric($active) && $i == $active) || (!empty($links[$i][2]) && !is_numeric($active) && $active == $links[$i][2])) {
3174 // If active tab is already present
3175 if ($i >= $limittoshow) {
3176 $limittoshow--;
3177 }
3178 }
3179 }
3180
3181 for ($i = 0; $i <= $maxkey; $i++) {
3182 if ((is_numeric($active) && $i == $active) || (!empty($links[$i][2]) && !is_numeric($active) && $active == $links[$i][2])) {
3183 $isactive = true;
3184 } else {
3185 $isactive = false;
3186 }
3187
3188 if ($i < $limittoshow || $isactive) {
3189 // Output entry with a visible tab
3190 $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])) . ' -->';
3191
3192 if (isset($links[$i][2]) && $links[$i][2] == 'image') {
3193 if (!empty($links[$i][0])) {
3194 $out .= '<a class="tabimage' . ($morecss ? ' ' . $morecss : '') . '" href="' . $links[$i][0] . '">' . $links[$i][1] . '</a>' . "\n";
3195 } else {
3196 $out .= '<span class="tabspan">' . $links[$i][1] . '</span>' . "\n";
3197 }
3198 } elseif (!empty($links[$i][1])) {
3199 //print "x $i $active ".$links[$i][2]." z";
3200 $out .= '<div class="tab tab' . ($isactive ? 'active' : 'unactive') . '" style="margin: 0 !important">';
3201
3202 if (!empty($links[$i][0])) {
3203 $titletoshow = preg_replace('/<.*$/', '', $links[$i][1]);
3204 $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) . '">';
3205 }
3206
3207 if ($displaytab == 0 && $picto) {
3208 $out .= img_picto($title, $picto, '', $pictoisfullpath, 0, 0, '', 'imgTabTitle paddingright marginrightonlyshort');
3209 }
3210
3211 $out .= $links[$i][1];
3212 if (!empty($links[$i][0])) {
3213 $out .= '</a>' . "\n";
3214 }
3215 $out .= empty($links[$i][4]) ? '' : $links[$i][4];
3216 $out .= '</div>';
3217 }
3218
3219 $out .= '</div>';
3220 } else {
3221 // Add entry into the combo popup with the other tabs
3222 if (!$popuptab) {
3223 $popuptab = 1;
3224 $outmore .= '<div class="popuptabset wordwrap">'; // The css used to hide/show popup
3225 }
3226 $outmore_content = '';
3227
3228 if (isset($links[$i][2]) && $links[$i][2] == 'image') {
3229 if (!empty($links[$i][0])) {
3230 $outmore_content .= '<a class="tabimage' . ($morecss ? ' ' . $morecss : '') . '" href="' . $links[$i][0] . '">' . $links[$i][1] . '</a>' . "\n";
3231 } else {
3232 $outmore_content .= '<span class="tabspan">' . $links[$i][1] . '</span>' . "\n";
3233 }
3234 } elseif (!empty($links[$i][1])) {
3235 $outmore_content .= '<a' . (!empty($links[$i][2]) ? ' id="' . $links[$i][2] . '"' : '') . ' class="wordwrap inline-block' . ($morecss ? ' ' . $morecss : '') . '" href="' . $links[$i][0] . '">';
3236 $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.
3237 $outmore_content .= '</a>' . "\n";
3238 }
3239 if ($outmore_content !== '') {
3240 $outmore .= '<div class="popuptab wordwrap" style="display:inherit;">' . $outmore_content . '</div>';
3241 }
3242
3243 $nbintab++;
3244 }
3245
3246 $displaytab = $i + 1;
3247 }
3248 if ($popuptab) {
3249 $outmore .= '</div>';
3250 }
3251
3252 if ($popuptab) { // If there is some tabs not shown
3253 $left = ($langs->trans("DIRECTION") == 'rtl' ? 'right' : 'left');
3254 $right = ($langs->trans("DIRECTION") == 'rtl' ? 'left' : 'right');
3255 $widthofpopup = 240;
3256
3257 $tabsname = $moretabssuffix;
3258 if (empty($tabsname)) {
3259 $tabsname = str_replace("@", "", $picto);
3260 }
3261 $out .= '<div id="moretabs' . $tabsname . '" class="inline-block tabsElem valignmiddle">';
3262 if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2) {
3263 $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".
3264 }
3265 $out .= '<div id="moretabsList' . $tabsname . '" style="width: ' . $widthofpopup . 'px; position: absolute; ' . $left . ': -999em; text-align: ' . $left . '; margin:0px; padding:2px; z-index:10;">';
3266 $out .= $outmore;
3267 $out .= '</div>';
3268 $out .= '<div></div>';
3269 $out .= "</div>\n";
3270
3271 $out .= '<script nonce="' . getNonce() . '">';
3272 $out .= "$('#moretabs" . $tabsname . "').mouseenter( function() {
3273 var x = this.offsetLeft, y = this.offsetTop;
3274 console.log('mouseenter " . $left . " x='+x+' y='+y+' window.innerWidth='+window.innerWidth);
3275 if ((window.innerWidth - x) < " . ($widthofpopup + 10) . ") {
3276 $('#moretabsList" . $tabsname . "').css('" . $right . "','8px');
3277 }
3278 $('#moretabsList" . $tabsname . "').css('" . $left . "','auto');
3279 });
3280 ";
3281 $out .= "$('#moretabs" . $tabsname . "').mouseleave( function() { console.log('mouseleave " . $left . "'); $('#moretabsList" . $tabsname . "').css('" . $left . "','-999em');});";
3282 $out .= "</script>";
3283 }
3284
3285 if ((!empty($title) && $showtitle) || $morehtmlright || !empty($links)) {
3286 $out .= "</div>\n";
3287 }
3288
3289 if (!$notab || $notab == -1 || $notab == -2 || $notab == -3 || $notab == -4) {
3290 $out .= "\n" . '<div id="dragDropAreaTabBar" class="tabBar' . ($notab == -1 ? '' : ($notab == -2 ? ' tabBarNoTop' : ((($notab == -3 || $notab == -4) ? ' noborderbottom' : '') . ($notab == -4 ? '' : ' tabBarWithBottom'))));
3291 $out .= ($morecssdiv ? ' ' . $morecssdiv : '');
3292 $out .= '">' . "\n";
3293 }
3294 if (!empty($dragdropfile)) {
3295 include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
3296 $out .= dragAndDropFileUpload("dragDropAreaTabBar");
3297 }
3298 $parameters = array('tabname' => $active, 'out' => $out);
3299 $reshook = $hookmanager->executeHooks('printTabsHead', $parameters); // This hook usage is called just before output the head of tabs. Take also a look at "completeTabsHead"
3300 if ($reshook > 0) {
3301 $out = $hookmanager->resPrint;
3302 }
3303
3304 return $out;
3305}
3306
3314function dol_fiche_end($notab = 0)
3315{
3316 print dol_get_fiche_end($notab);
3317}
3318
3325function dol_get_fiche_end($notab = 0)
3326{
3327 if (!$notab || $notab == -1) {
3328 return "\n</div>\n";
3329 } else {
3330 return '';
3331 }
3332}
3333
3353function dol_banner_tab($object, $paramid, $morehtml = '', $shownav = 1, $fieldid = 'rowid', $fieldref = 'ref', $morehtmlref = '', $moreparam = '', $nodbprefix = 0, $morehtmlleft = '', $morehtmlstatus = '', $onlybanner = 0, $morehtmlright = '')
3354{
3355 global $conf, $form, $user, $langs, $hookmanager, $action;
3356
3357 $error = 0;
3358
3359 $maxvisiblephotos = 1;
3360 $showimage = 1;
3361 $entity = (empty($object->entity) ? $conf->entity : $object->entity);
3362 // @phan-suppress-next-line PhanUndeclaredMethod
3363 $showbarcode = !isModEnabled('barcode') ? 0 : (empty($object->barcode) ? 0 : 1);
3364 if (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && !$user->hasRight('barcode', 'lire_advance')) {
3365 $showbarcode = 0;
3366 }
3367 $modulepart = 'unknown';
3368
3369 if (in_array($object->element, ['societe', 'contact', 'product', 'ticket', 'bom'])) {
3370 $modulepart = $object->element;
3371 } elseif ($object->element == 'member') {
3372 $modulepart = 'memberphoto';
3373 } elseif ($object->element == 'user') {
3374 $modulepart = 'userphoto';
3375 }
3376
3377 if (class_exists("Imagick")) {
3378 if ($object->element == 'expensereport' || $object->element == 'propal' || $object->element == 'commande' || $object->element == 'facture' || $object->element == 'supplier_proposal') {
3379 $modulepart = $object->element;
3380 } elseif ($object->element == 'fichinter' || $object->element == 'intervention') {
3381 $modulepart = 'ficheinter';
3382 } elseif ($object->element == 'contrat' || $object->element == 'contract') {
3383 $modulepart = 'contract';
3384 } elseif ($object->element == 'order_supplier') {
3385 $modulepart = 'supplier_order';
3386 } elseif ($object->element == 'invoice_supplier') {
3387 $modulepart = 'supplier_invoice';
3388 }
3389 }
3390
3391 if ($object->element == 'product') {
3393 '@phan-var-force Product $object';
3394 $width = 80;
3395 $cssclass = 'photowithmargin photoref';
3396 $showimage = $object->is_photo_available($conf->product->multidir_output[$entity]);
3397 $maxvisiblephotos = getDolGlobalInt('PRODUCT_MAX_VISIBLE_PHOTO', 5);
3398 if ($conf->browser->layout == 'phone') {
3399 $maxvisiblephotos = 1;
3400 }
3401 $useLinkPathPhoto = getDolGlobalInt('PRODUCT_USE_LINK_PATH_FOR_PHOTO');
3402 if ($showimage || $useLinkPathPhoto) {
3403 $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>';
3404 } else {
3405 if (getDolGlobalString('PRODUCT_NODISPLAYIFNOPHOTO')) {
3406 $nophoto = '';
3407 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
3408 } else { // Show no photo link
3409 $nophoto = '/public/theme/common/nophoto.png';
3410 $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>';
3411 }
3412 }
3413 } elseif ($object->element == 'category') {
3415 '@phan-var-force Categorie $object';
3416 $width = 80;
3417 $cssclass = 'photowithmargin photoref';
3418 $showimage = $object->isAnyPhotoAvailable($conf->categorie->multidir_output[$entity]);
3419 $maxvisiblephotos = getDolGlobalInt('CATEGORY_MAX_VISIBLE_PHOTO', 5);
3420 if ($conf->browser->layout == 'phone') {
3421 $maxvisiblephotos = 1;
3422 }
3423 if ($showimage) {
3424 $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>';
3425 } else {
3426 if (getDolGlobalString('CATEGORY_NODISPLAYIFNOPHOTO')) {
3427 $nophoto = '';
3428 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
3429 } else { // Show no photo link
3430 $nophoto = '/public/theme/common/nophoto.png';
3431 $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>';
3432 }
3433 }
3434 } elseif ($object->element == 'bom') {
3436 '@phan-var-force Bom $object';
3437 $width = 80;
3438 $cssclass = 'photowithmargin photoref';
3439 $showimage = $object->is_photo_available($conf->bom->multidir_output[$entity]);
3440 $maxvisiblephotos = getDolGlobalInt('BOM_MAX_VISIBLE_PHOTO', 5);
3441 if ($conf->browser->layout == 'phone') {
3442 $maxvisiblephotos = 1;
3443 }
3444 if ($showimage) {
3445 $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>';
3446 } else {
3447 if (getDolGlobalString('BOM_NODISPLAYIFNOPHOTO')) {
3448 $nophoto = '';
3449 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
3450 } else { // Show no photo link
3451 $nophoto = '/public/theme/common/nophoto.png';
3452 $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>';
3453 }
3454 }
3455 } elseif ($object->element == 'ticket') {
3456 $width = 80;
3457 $cssclass = 'photoref';
3459 '@phan-var-force Ticket $object';
3460 $showimage = $object->is_photo_available($conf->ticket->multidir_output[$entity] . '/' . $object->ref);
3461 $maxvisiblephotos = getDolGlobalInt('TICKET_MAX_VISIBLE_PHOTO', 2);
3462 if ($conf->browser->layout == 'phone') {
3463 $maxvisiblephotos = 1;
3464 }
3465
3466 if ($showimage) {
3467 $showphoto = $object->show_photos('ticket', $conf->ticket->multidir_output[$entity], 'small', $maxvisiblephotos, 0, 0, 0, $width, 0);
3468 if ($object->nbphoto > 0) {
3469 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">' . $showphoto . '</div>';
3470 } else {
3471 $showimage = 0;
3472 }
3473 }
3474 if (!$showimage) {
3475 if (getDolGlobalString('TICKET_NODISPLAYIFNOPHOTO')) {
3476 $nophoto = '';
3477 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
3478 } else { // Show no photo link
3479 $nophoto = img_picto('No photo', 'object_ticket');
3480 $morehtmlleft .= '<!-- No photo to show -->';
3481 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref">';
3482 $morehtmlleft .= $nophoto;
3483 $morehtmlleft .= '</div></div>';
3484 }
3485 }
3486 } else {
3487 if ($modulepart != 'unknown' || method_exists($object, 'getDataToShowPhoto')) {
3488 $phototoshow = '';
3489 // Check if a preview file is available
3490 if (in_array($modulepart, array('propal', 'commande', 'facture', 'ficheinter', 'contract', 'supplier_order', 'supplier_proposal', 'supplier_invoice', 'expensereport')) && class_exists("Imagick")) {
3491 $objectref = dol_sanitizeFileName($object->ref);
3492 $dir_output = (empty($conf->$modulepart->multidir_output[$entity]) ? $conf->$modulepart->dir_output : $conf->$modulepart->multidir_output[$entity]) . "/";
3493 if (in_array($modulepart, array('invoice_supplier', 'supplier_invoice'))) {
3494 $subdir = get_exdir($object->id, 2, 0, 1, $object, $modulepart);
3495 $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
3496 } else {
3497 $subdir = get_exdir($object->id, 0, 0, 1, $object, $modulepart);
3498 }
3499 if (empty($subdir)) {
3500 $subdir = 'errorgettingsubdirofobject'; // Protection to avoid to return empty path
3501 }
3502
3503 $filepath = $dir_output . $subdir . "/";
3504
3505 $filepdf = $filepath . $objectref . ".pdf";
3506 $relativepath = $subdir . '/' . $objectref . '.pdf';
3507
3508 // Define path to preview pdf file (preview precompiled "file.ext" are "file.ext_preview.png")
3509 $fileimage = $filepdf . '_preview.png';
3510 $relativepathimage = $relativepath . '_preview.png';
3511
3512 $pdfexists = file_exists($filepdf);
3513
3514 // If PDF file exists
3515 if ($pdfexists) {
3516 // Conversion du PDF en image png si fichier png non existent
3517 if (!file_exists($fileimage) || (filemtime($fileimage) < filemtime($filepdf))) {
3518 if (!getDolGlobalString('MAIN_DISABLE_PDF_THUMBS')) { // If you experience trouble with pdf thumb generation and imagick, you can disable here.
3519 include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
3520 $ret = dol_convert_file($filepdf, 'png', $fileimage, '0'); // Convert first page of PDF into a file _preview.png
3521 if ($ret < 0) {
3522 $error++;
3523 }
3524 }
3525 }
3526 }
3527
3528 if ($pdfexists && !$error) {
3529 $heightforphotref = 80;
3530 if (!empty($conf->dol_optimize_smallscreen)) {
3531 $heightforphotref = 60;
3532 }
3533 // If the preview file is found
3534 if (file_exists($fileimage)) {
3535 $phototoshow = '<div class="photoref">';
3536 $phototoshow .= '<img height="' . $heightforphotref . '" class="photo photowithborder" src="' . DOL_URL_ROOT . '/viewimage.php?modulepart=apercu' . $modulepart . '&amp;file=' . urlencode($relativepathimage) . '">';
3537 $phototoshow .= '</div>';
3538 }
3539 }
3540 } elseif (!$phototoshow) { // example if modulepart = 'societe' or 'photo' or 'memberphoto'
3541 $phototoshow .= $form->showphoto($modulepart, $object, 0, 0, 0, 'photowithmargin photoref', 'small', 1, 0);
3542 }
3543
3544 if ($phototoshow) {
3545 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">';
3546 $morehtmlleft .= $phototoshow;
3547 $morehtmlleft .= '</div>';
3548 }
3549 }
3550
3551 if (empty($phototoshow)) { // Show No photo link (picto of object)
3552 if ($object->element == 'action') {
3553 $width = 80;
3554 $cssclass = 'photorefcenter';
3555 $nophoto = img_picto('No photo', 'title_agenda');
3556 } else {
3557 $width = 14;
3558 $cssclass = 'photorefcenter';
3559 $picto = $object->picto; // @phan-suppress-current-line PhanUndeclaredProperty
3560 $prefix = 'object_';
3561 if ($object->element == 'project' && !$object->public) { // @phan-suppress-current-line PhanUndeclaredProperty
3562 $picto = 'project'; // instead of projectpub
3563 }
3564 if (strpos($picto, 'fontawesome_') !== false) {
3565 $prefix = '';
3566 }
3567 $nophoto = img_picto('No photo', $prefix . $picto);
3568 }
3569 $morehtmlleft .= '<!-- No photo to show -->';
3570 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref">';
3571 $morehtmlleft .= $nophoto;
3572 $morehtmlleft .= '</div></div>';
3573 }
3574 }
3575
3576 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)) {
3577 $morehtmlref .= '<div style="clear: both;"></div>';
3578 $morehtmlref .= '<div class="smallimp refidno opacitymedium banner-object-technical-id">';
3579 $morehtmlref .= $langs->trans("TechnicalID") . ': ' . ((int) $object->id);
3580 $morehtmlref .= '</div>';
3581 }
3582
3583
3584 // Show barcode
3585 if ($showbarcode) {
3586 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">' . $form->showbarcode($object, 100, 'photoref valignmiddle') . '</div>';
3587 }
3588
3589 if ($object->element == 'societe') {
3591 if (!empty($conf->use_javascript_ajax) && $user->hasRight('societe', 'creer') && getDolGlobalString('MAIN_DIRECT_STATUS_UPDATE')) {
3592 $morehtmlstatus .= ajax_object_onoff($object, 'status', 'status', 'InActivity', 'ActivityCeased');
3593 } else {
3594 $morehtmlstatus .= $object->getLibStatut(6);
3595 }
3596 } elseif ($object->element == 'product') {
3598 //$morehtmlstatus.=$langs->trans("Status").' ('.$langs->trans("Sell").') ';
3599 if (!empty($conf->use_javascript_ajax) && $user->hasRight('produit', 'creer') && getDolGlobalString('MAIN_DIRECT_STATUS_UPDATE')) {
3600 $morehtmlstatus .= ajax_object_onoff($object, 'status', 'status', 'ProductStatusOnSell', 'ProductStatusNotOnSell');
3601 } else {
3602 $morehtmlstatus .= '<span class="statusrefsell">' . $object->getLibStatut(6, 0) . '</span>';
3603 }
3604 $morehtmlstatus .= ' &nbsp; ';
3605 //$morehtmlstatus.=$langs->trans("Status").' ('.$langs->trans("Buy").') ';
3606 if (!empty($conf->use_javascript_ajax) && $user->hasRight('produit', 'creer') && getDolGlobalString('MAIN_DIRECT_STATUS_UPDATE')) {
3607 $morehtmlstatus .= ajax_object_onoff($object, 'status_buy', 'status_buy', 'ProductStatusOnBuy', 'ProductStatusNotOnBuy');
3608 } else {
3609 $morehtmlstatus .= '<span class="statusrefbuy">' . $object->getLibStatut(6, 1) . '</span>';
3610 }
3611 } elseif (in_array($object->element, array('salary'))) {
3613 '@phan-var-force Salary $object';
3614 $tmptxt = $object->getLibStatut(6, $object->alreadypaid);
3615 if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) {
3616 $tmptxt = $object->getLibStatut(5, $object->alreadypaid);
3617 }
3618 $morehtmlstatus .= $tmptxt;
3619 } elseif (in_array($object->element, array('facture', 'invoice', 'invoice_supplier'))) {
3621 '@phan-var-force Facture|FactureFournisseur|CommonInvoice $object';
3622 if (!isset($object->alreadypaid)) {
3623 $object->totalpaid = $object->getSommePaiement(0);
3624 $object->totalcreditnotes = $object->getSumCreditNotesUsed(0);
3625 $object->totaldeposits = $object->getSumDepositsUsed(0);
3626 $object->alreadypaid = $object->totalpaid + $object->totalcreditnotes + $object->totaldeposits;
3627 }
3628 $tmptxt = $object->getLibStatut(6, (float) $object->alreadypaid);
3629 if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) {
3630 $tmptxt = $object->getLibStatut(5, (float) $object->alreadypaid);
3631 }
3632 $morehtmlstatus .= $tmptxt;
3633 } elseif (in_array($object->element, array('chargesociales', 'loan', 'tva'))) { // TODO Move this to use ->alreadypaid like for invoices
3635 '@phan-var-force ChargeSociales|Loan|Tva $object';
3636 $tmptxt = $object->getLibStatut(6, $object->totalpaid);
3637 if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) {
3638 $tmptxt = $object->getLibStatut(5, $object->totalpaid);
3639 }
3640 $morehtmlstatus .= $tmptxt;
3641 } elseif ($object->element == 'contrat' || $object->element == 'contract') {
3643 if ($object->status == 0) {
3644 $morehtmlstatus .= $object->getLibStatut(5);
3645 } else {
3646 $morehtmlstatus .= $object->getLibStatut(4);
3647 }
3648 } elseif ($object->element == 'facturerec') {
3650 '@phan-var-force FactureRec $object';
3651 if ($object->frequency == 0) {
3652 $morehtmlstatus .= $object->getLibStatut(2);
3653 } else {
3654 $morehtmlstatus .= $object->getLibStatut(5);
3655 }
3656 } elseif ($object->element == 'project_task') {
3658 $tmptxt = $object->getLibStatut(4);
3659 $morehtmlstatus .= $tmptxt;
3660 } elseif (method_exists($object, 'getLibStatut')) { // Generic case for status
3661 $tmptxt = $object->getLibStatut(6);
3662 if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) {
3663 $tmptxt = $object->getLibStatut(5);
3664 }
3665 $morehtmlstatus .= $tmptxt;
3666 }
3667
3668 // Say if object was dispatched/transferred "into accountancy"
3669 if (isModEnabled('accounting') && in_array($object->element, array('bank', 'paiementcharge', 'facture', 'invoice', 'invoice_supplier', 'expensereport', 'payment_various'))) {
3670 // Note: For 'chargesociales', 'salaries'... this is the payments that are dispatched (so element = 'bank')
3671 if (method_exists($object, 'getVentilExportCompta')) {
3672 $accounted = $object->getVentilExportCompta(1);
3673 $langs->load("accountancy");
3674 $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>';
3675 }
3676 }
3677
3678 // Add alias for thirdparty
3679 if (!empty($object->name_alias)) {
3681 '@phan-var-force Societe $object';
3682 $morehtmlref .= '<div class="refidno opacitymedium banner-object-name-alias">' . dol_escape_htmltag($object->name_alias) . '</div>';
3683 }
3684
3685 // Add label
3686 if (in_array($object->element, array('product', 'bank_account', 'project_task'))) {
3688 if (!empty($object->label)) {
3689 $morehtmlref .= '<div class="refidno banner-object-label">' . $object->label . '</div>';
3690 }
3691 }
3692 // Show address and email
3693 if (method_exists($object, 'getBannerAddress') && !in_array($object->element, array('product', 'bookmark', 'ecm_directories', 'ecm_files'))) {
3694 $moreaddress = $object->getBannerAddress('refaddress', $object); // address, email, url, social networks
3695 if ($moreaddress) {
3696 $morehtmlref .= '<div class="refidno refaddress">';
3697 $morehtmlref .= $moreaddress;
3698 $morehtmlref .= '</div>';
3699 }
3700 }
3701
3702 $parameters = array('morehtmlref' => &$morehtmlref, 'moreparam' => &$moreparam, 'morehtmlleft' => &$morehtmlleft, 'morehtmlstatus' => &$morehtmlstatus, 'morehtmlright' => &$morehtmlright);
3703 $reshook = $hookmanager->executeHooks('formDolBanner', $parameters, $object, $action);
3704 if ($reshook < 0) {
3705 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
3706 } elseif (empty($reshook)) {
3707 $morehtmlref .= $hookmanager->resPrint;
3708 } elseif ($reshook > 0) {
3709 $morehtmlref = $hookmanager->resPrint;
3710 }
3711
3712 // $morehtml is the right part (link "Back to list")
3713 // $morehtmlref is the part after the ref
3714 // $morehtmlleft is the picto or photo of banner
3715 // $morehtmlstatus is part under the status
3716 // $morehtmlright is part of htmlright
3717
3718 print '<div class="' . ($onlybanner ? 'arearefnobottom ' : 'arearef ') . 'heightref valignmiddle centpercent object-banner-tab-container" data-module-part="'.dolPrintHTMLForAttribute($modulepart).'">';
3719 print $form->showrefnav($object, $paramid, $morehtml, $shownav, $fieldid, $fieldref, $morehtmlref, $moreparam, $nodbprefix, $morehtmlleft, $morehtmlstatus, $morehtmlright);
3720 print '</div>';
3721 print '<div class="underrefbanner clearboth"></div>';
3722}
3723
3733function fieldLabel($langkey, $fieldkey, $fieldrequired = 0)
3734{
3735 global $langs;
3736 $ret = '';
3737 if ($fieldrequired) {
3738 $ret .= '<span class="fieldrequired">';
3739 }
3740 $ret .= '<label for="' . $fieldkey . '">';
3741 $ret .= $langs->trans($langkey);
3742 $ret .= '</label>';
3743 if ($fieldrequired) {
3744 $ret .= '</span>';
3745 }
3746 return $ret;
3747}
3748
3762function dol_format_address($object, $withcountry = 0, $sep = "\n", $outputlangs = null, $mode = 0, $extralangcode = '')
3763{
3764 global $langs, $hookmanager;
3765
3766 $ret = '';
3767 $countriesusingstate = array('AU', 'CA', 'US', 'IN', 'GB', 'ES', 'UK', 'TR', 'CN'); // See also MAIN_FORCE_STATE_INTO_ADDRESS
3768
3769 // See format of addresses on https://en.wikipedia.org/wiki/Address
3770 // Address
3771 if (empty($mode)) {
3772 $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)));
3773 }
3774 // Zip/Town/State
3775 if (isset($object->country_code) && in_array($object->country_code, array('AU', 'CA', 'US', 'CN')) || getDolGlobalString('MAIN_FORCE_STATE_INTO_ADDRESS')) {
3776 // US: title firstname name \n address lines \n town, state, zip \n country
3777 $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
3778 $ret .= (($ret && $town) ? $sep : '') . $town;
3779
3780 if (!empty($object->state)) {
3781 $ret .= ($ret ? ($town ? ", " : $sep) : '') . $object->state;
3782 }
3783 if (!empty($object->zip)) {
3784 $ret .= ($ret ? (($town || $object->state) ? ", " : $sep) : '') . $object->zip;
3785 }
3786 } elseif (isset($object->country_code) && in_array($object->country_code, array('GB', 'UK'))) {
3787 // UK: title firstname name \n address lines \n town state \n zip \n country
3788 $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
3789 $ret .= ($ret ? $sep : '') . $town;
3790 if (!empty($object->state)) {
3791 $ret .= ($ret ? ", " : '') . $object->state;
3792 }
3793 if (!empty($object->zip)) {
3794 $ret .= ($ret ? $sep : '') . $object->zip;
3795 }
3796 } elseif (isset($object->country_code) && in_array($object->country_code, array('ES', 'TR'))) {
3797 // ES: title firstname name \n address lines \n zip town \n state \n country
3798 $ret .= ($ret ? $sep : '') . $object->zip;
3799 $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
3800 $ret .= ($town ? (($object->zip ? ' ' : '') . $town) : '');
3801 if (!empty($object->state)) {
3802 $ret .= $sep . $object->state;
3803 }
3804 } elseif (isset($object->country_code) && in_array($object->country_code, array('JP'))) {
3805 // JP: In romaji, title firstname name\n address lines \n [state,] town zip \n country
3806 // See https://www.sljfaq.org/afaq/addresses.html
3807 $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
3808 $ret .= ($ret ? $sep : '') . ($object->state ? $object->state . ', ' : '') . $town . ($object->zip ? ' ' : '') . $object->zip;
3809 } elseif (isset($object->country_code) && in_array($object->country_code, array('IT'))) {
3810 // IT: title firstname name\n address lines \n zip town state_code \n country
3811 $ret .= ($ret ? $sep : '') . $object->zip;
3812 $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
3813 $ret .= ($town ? (($object->zip ? ' ' : '') . $town) : '');
3814 $ret .= (empty($object->state_code) ? '' : (' ' . $object->state_code));
3815 } else {
3816 // Other: title firstname name \n address lines \n zip town[, state] \n country
3817 $town = (($extralangcode && !empty($object->array_languages['address'][$extralangcode])) ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
3818 $ret .= !empty($object->zip) ? (($ret ? $sep : '') . $object->zip) : '';
3819 $ret .= ($town ? (($object->zip ? ' ' : ($ret ? $sep : '')) . $town) : '');
3820 if (!empty($object->state) && in_array($object->country_code, $countriesusingstate)) {
3821 $ret .= ($ret ? ", " : '') . $object->state;
3822 }
3823 }
3824
3825 if (!is_object($outputlangs)) {
3826 $outputlangs = $langs;
3827 }
3828 if ($withcountry) {
3829 $langs->load("dict");
3830 $ret .= (empty($object->country_code) ? '' : ($ret ? $sep : '') . $outputlangs->convToOutputCharset($outputlangs->transnoentitiesnoconv("Country" . $object->country_code)));
3831 }
3832 if ($hookmanager) {
3833 $parameters = array('withcountry' => $withcountry, 'sep' => $sep, 'outputlangs' => $outputlangs, 'mode' => $mode, 'extralangcode' => $extralangcode);
3834 $reshook = $hookmanager->executeHooks('formatAddress', $parameters, $object);
3835 if ($reshook > 0) {
3836 $ret = '';
3837 }
3838 $ret .= $hookmanager->resPrint;
3839 }
3840
3841 return $ret;
3842}
3843
3844
3845
3855function dol_strftime($fmt, $ts = false, $is_gmt = false)
3856{
3857 if ((abs($ts) <= 0x7FFFFFFF)) { // check if number in 32-bit signed range
3858 return dol_print_date($ts, $fmt, $is_gmt);
3859 } else {
3860 return 'Error date outside supported range';
3861 }
3862}
3863
3886function dol_print_date($time, $format = '', $tzoutput = 'auto', $outputlangs = null, $encodetooutput = false, $decorate = 0)
3887{
3888 global $conf, $langs;
3889
3890 // If date undefined or "", we return ""
3891 if (dol_strlen((string) $time) == 0) {
3892 return ''; // $time=0 allowed (it means 01/01/1970 00:00:00)
3893 }
3894
3895 if ($tzoutput === 'auto') {
3896 $tzoutput = (empty($conf) ? 'tzserver' : (isset($conf->tzuserinputkey) ? $conf->tzuserinputkey : 'tzserver'));
3897 }
3898
3899 // Clean parameters
3900 $to_gmt = false; // false if we want date in server timezone, true if we want to add offset
3901 $offsettz = $offsetdst = 0;
3902 if ($tzoutput) {
3903 $to_gmt = true; // For backward compatibility
3904 if (is_string($tzoutput)) {
3905 if ($tzoutput == 'tzserver') {
3906 $to_gmt = false;
3907 $offsettzstring = @date_default_timezone_get(); // Example 'Europe/Berlin' or 'Indian/Reunion'
3908 // @phan-suppress-next-line PhanPluginRedundantAssignment
3909 $offsettz = 0; // Timezone offset with server timezone (because to_gmt is false), so 0
3910 // @phan-suppress-next-line PhanPluginRedundantAssignment
3911 $offsetdst = 0; // Dst offset with server timezone (because to_gmt is false), so 0
3912 } elseif ($tzoutput == 'tzuser' || $tzoutput == 'tzuserrel') {
3913 $to_gmt = true;
3914 // if no session (by example in cron) may use MAIN_DOLIBARR_USER_TIMEZONE instead UTC
3915 $offsettzstring = (empty($_SESSION['dol_tz_string']) ? getDolGlobalString('MAIN_DOLIBARR_USER_TIMEZONE', 'UTC') : $_SESSION['dol_tz_string']); // Example 'Europe/Berlin' or 'Indian/Reunion'
3916
3917 if (class_exists('DateTimeZone')) {
3918 try {
3919 $user_date_tz = new DateTimeZone($offsettzstring);
3920 } catch (Exception $e) {
3921 // Bad value for $offsettzstring
3922 dol_syslog("DateInvalidTimeZoneException for timezone string '".$offsettzstring."'. Falling back to UTC.", LOG_ERR);
3923 $user_date_tz = new DateTimeZone('UTC'); // Force valid timezone as UTC
3924 }
3925 $user_dt = new DateTime();
3926 $user_dt->setTimezone($user_date_tz);
3927 $user_dt->setTimestamp($tzoutput == 'tzuser' ? dol_now() : (int) $time);
3928 $offsettz = $user_dt->getOffset(); // should include dst ?
3929 } else { // with old method (The 'tzuser' was processed like the 'tzuserrel')
3930 $offsettz = (empty($_SESSION['dol_tz']) ? 0 : $_SESSION['dol_tz']) * 60 * 60; // Will not be used anymore
3931 $offsetdst = (empty($_SESSION['dol_dst']) ? 0 : $_SESSION['dol_dst']) * 60 * 60; // Will not be used anymore
3932 }
3933 }
3934 }
3935 }
3936 if (!is_object($outputlangs)) {
3937 $outputlangs = $langs;
3938 }
3939 if (!$format) {
3940 $format = 'daytextshort';
3941 }
3942
3943 // Do we have to reduce the length of date (year on 2 chars) to save space.
3944 // Note: dayinputnoreduce is same than day but no reduction of year length will be done
3945 $reduceformat = (!empty($conf->dol_optimize_smallscreen) && in_array($format, array('day', 'dayhour', 'dayhoursec'))) ? 1 : 0; // Test on original $format param.
3946 $format = preg_replace('/inputnoreduce/', '', $format); // so format 'dayinputnoreduce' is processed like day
3947 $formatwithoutreduce = preg_replace('/reduceformat/', '', $format);
3948 if ($formatwithoutreduce != $format) {
3949 $format = $formatwithoutreduce;
3950 $reduceformat = 1;
3951 } // so format 'dayreduceformat' is processed like day
3952
3953 // Change predefined format into computer format. If found translation in lang file we use it, otherwise we use default.
3954 // TODO Add format daysmallyear and dayhoursmallyear
3955 if ($format == 'day') {
3956 $format = ($outputlangs->trans("FormatDateShort") != "FormatDateShort" ? $outputlangs->trans("FormatDateShort") : $conf->format_date_short);
3957 } elseif ($format == 'hour') {
3958 $format = ($outputlangs->trans("FormatHourShort") != "FormatHourShort" ? $outputlangs->trans("FormatHourShort") : $conf->format_hour_short);
3959 } elseif ($format == 'hoursec') {
3960 $s1 = $outputlangs->trans("FormatDateShort");
3961 $s2 = $outputlangs->trans("FormatDateHourSecShort");
3962 $s3 = trim(preg_replace('/'.preg_quote($s1, '/').'/', '', $s2)); // Try to guess the format for FormatHourSecShort using FormatDateShort and FormatDateHourSecShort
3963 $format = $s3;
3964 //$format = ($outputlangs->trans("FormatHourSecShort") != "FormatHourSecShort" ? $outputlangs->trans("FormatHourSecShort") : ($s3 ? $s3 : $conf->format_hour_sec_short));
3965 } elseif ($format == 'hourduration') {
3966 $format = ($outputlangs->trans("FormatHourShortDuration") != "FormatHourShortDuration" ? $outputlangs->trans("FormatHourShortDuration") : $conf->format_hour_short_duration);
3967 } elseif ($format == 'daytext') {
3968 $format = ($outputlangs->trans("FormatDateText") != "FormatDateText" ? $outputlangs->trans("FormatDateText") : $conf->format_date_text);
3969 } elseif ($format == 'daytextshort') {
3970 $format = ($outputlangs->trans("FormatDateTextShort") != "FormatDateTextShort" ? $outputlangs->trans("FormatDateTextShort") : $conf->format_date_text_short);
3971 } elseif ($format == 'dayhour') {
3972 $format = ($outputlangs->trans("FormatDateHourShort") != "FormatDateHourShort" ? $outputlangs->trans("FormatDateHourShort") : $conf->format_date_hour_short);
3973 } elseif ($format == 'dayhoursec') {
3974 $format = ($outputlangs->trans("FormatDateHourSecShort") != "FormatDateHourSecShort" ? $outputlangs->trans("FormatDateHourSecShort") : $conf->format_date_hour_sec_short);
3975 } elseif ($format == 'dayhourtext') {
3976 $format = ($outputlangs->trans("FormatDateHourText") != "FormatDateHourText" ? $outputlangs->trans("FormatDateHourText") : $conf->format_date_hour_text);
3977 } elseif ($format == 'dayhourtextshort') {
3978 $format = ($outputlangs->trans("FormatDateHourTextShort") != "FormatDateHourTextShort" ? $outputlangs->trans("FormatDateHourTextShort") : $conf->format_date_hour_text_short);
3979 } elseif ($format == 'dayhourlog') {
3980 // Format not sensitive to language
3981 $format = '%Y%m%d%H%M%S';
3982 } elseif ($format == 'dayhourlogsmall') {
3983 // Format not sensitive to language
3984 $format = '%y%m%d%H%M';
3985 } elseif ($format == 'dayhourldap') {
3986 $format = '%Y%m%d%H%M%SZ';
3987 } elseif ($format == 'dayhourxcard') {
3988 $format = '%Y%m%dT%H%M%SZ';
3989 } elseif ($format == 'dayxcard') {
3990 $format = '%Y%m%d';
3991 } elseif ($format == 'dayrfc') {
3992 $format = '%Y-%m-%d'; // DATE_RFC3339
3993 } elseif ($format == 'dayhourrfc') {
3994 $format = '%Y-%m-%dT%H:%M:%SZ'; // DATETIME RFC3339
3995 } elseif ($format == 'standard') {
3996 $format = '%Y-%m-%d %H:%M:%S';
3997 }
3998
3999 if ($reduceformat) {
4000 $format = str_replace('%Y', '%y', $format);
4001 $format = str_replace('yyyy', 'yy', $format);
4002 }
4003
4004 // Clean format
4005 if (preg_match('/%b/i', $format)) { // There is some text to translate
4006 // We inhibit translation to text made by strftime functions. We will use trans instead later.
4007 $format = str_replace('%b', '__b__', $format);
4008 $format = str_replace('%B', '__B__', $format);
4009 }
4010 if (preg_match('/%a/i', $format)) { // There is some text to translate
4011 // We inhibit translation to text made by strftime functions. We will use trans instead later.
4012 $format = str_replace('%a', '__a__', $format);
4013 $format = str_replace('%A', '__A__', $format);
4014 }
4015
4016 // Analyze date
4017 $reg = array();
4018 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
4019 dol_print_error(null, "Functions.lib::dol_print_date function called with a bad value" . getCallerInfoString());
4020 return '';
4021 } 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
4022 // This part of code should not be used anymore.
4023 dol_syslog("Functions.lib::dol_print_date function called with a bad value" . getCallerInfoString(), LOG_WARNING);
4024 // Date has format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'
4025 $syear = (!empty($reg[1]) ? $reg[1] : '');
4026 $smonth = (!empty($reg[2]) ? $reg[2] : '');
4027 $sday = (!empty($reg[3]) ? $reg[3] : '');
4028 $shour = (!empty($reg[4]) ? $reg[4] : '');
4029 $smin = (!empty($reg[5]) ? $reg[5] : '');
4030 $ssec = (!empty($reg[6]) ? $reg[6] : '');
4031
4032 $time = dol_mktime((int) $shour, (int) $smin, (int) $ssec, (int) $smonth, (int) $sday, (int) $syear, true);
4033
4034 if ($to_gmt) {
4035 $tzo = new DateTimeZone('UTC'); // when to_gmt is true, base for offsettz and offsetdst (so timetouse) is UTC
4036 } else {
4037 $tzo = new DateTimeZone(date_default_timezone_get()); // when to_gmt is false, base for offsettz and offsetdst (so timetouse) is PHP server
4038 }
4039 $dtts = new DateTime();
4040 $dtts->setTimestamp($time);
4041 $dtts->setTimezone($tzo);
4042 $newformat = str_replace(
4043 array('%Y', '%y', '%m', '%d', '%H', '%I', '%M', '%S', '%p', 'T', 'Z', '__a__', '__A__', '__b__', '__B__'),
4044 array('Y', 'y', 'm', 'd', 'H', 'h', 'i', 's', 'A', '__£__', '__$__', '__{__', '__}__', '__[__', '__]__'),
4045 $format
4046 );
4047 $ret = $dtts->format($newformat);
4048 $ret = str_replace(
4049 array('__£__', '__$__', '__{__', '__}__', '__[__', '__]__'),
4050 array('T', 'Z', '__a__', '__A__', '__b__', '__B__'),
4051 $ret
4052 );
4053 } else {
4054 // Date is a timestamps
4055 if ($time < 100000000000) { // Protection against bad date values
4056 $dtts = new DateTime();
4057 //var_dump($tzoutput.' '.$offsettzstring.' '.$offsettz.$offsetdst.' x '.$to_gmt);
4058 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
4059 $timetouse = (int) $time + $offsettz + $offsetdst; // TODO We could be able to disable use of offsettz and offsetdst to use only offsettzstring.
4060
4061 $tzo = new DateTimeZone('UTC'); // when to_gmt is true, base for offsettz and offsetdst (so timetouse) is UTC
4062 $dtts->setTimezone($tzo); // important: must be before the setTimestamp
4063 $dtts->setTimestamp($timetouse);
4064 } else {
4065 $timetouse = (int) $time + $offsettz + $offsetdst; // TODO We could be able to disable use of offsettz and offsetdst to use only offsettzstring.
4066
4067 $tzo = new DateTimeZone(date_default_timezone_get()); // when to_gmt is false, base for offsettz and offsetdst (so timetouse) is PHP server
4068 $dtts->setTimestamp($timetouse); // TODO May be we can invert setTimestamp and setTimezone
4069 $dtts->setTimezone($tzo);
4070 }
4071
4072 $newformat = str_replace(
4073 array('%Y', '%y', '%m', '%d', '%H', '%I', '%M', '%S', '%p', '%w', 'T', 'Z', '__a__', '__A__', '__b__', '__B__'),
4074 array('Y', 'y', 'm', 'd', 'H', 'h', 'i', 's', 'A', 'w', '__£__', '__$__', '__{__', '__}__', '__[__', '__]__'),
4075 $format
4076 );
4077
4078 $ret = $dtts->format($newformat);
4079 //var_dump($timetouse, $offsettz, $offsetdst, $tzo, $newformat, $ret);
4080 $ret = str_replace(
4081 array('__£__', '__$__', '__{__', '__}__', '__[__', '__]__'),
4082 array('T', 'Z', '__a__', '__A__', '__b__', '__B__'),
4083 $ret
4084 );
4085 } else {
4086 $ret = 'Bad value ' . $time . ' for date';
4087 }
4088 }
4089
4090 if (preg_match('/__b__/i', $format)) {
4091 $timetouse = $time + $offsettz + $offsetdst; // TODO We could be able to disable use of offsettz and offsetdst to use only offsettzstring.
4092
4093 if ($to_gmt) {
4094 $tzo = new DateTimeZone('UTC'); // when to_gmt is true, base for offsettz and offsetdst (so timetouse) is UTC
4095 } else {
4096 $tzo = new DateTimeZone(date_default_timezone_get()); // when to_gmt is false, base for offsettz and offsetdst (so timetouse) is PHP server
4097 }
4098 $dtts = new DateTime();
4099 $dtts->setTimestamp($timetouse);
4100 $dtts->setTimezone($tzo);
4101 $month = (int) $dtts->format("m");
4102 $month = sprintf("%02d", $month); // $month may be return with format '06' on some installation and '6' on other, so we force it to '06'.
4103 if ($encodetooutput) {
4104 $monthtext = $outputlangs->transnoentities('Month' . $month);
4105 $monthtextshort = $outputlangs->transnoentities('MonthShort' . $month);
4106 } else {
4107 $monthtext = $outputlangs->transnoentitiesnoconv('Month' . $month);
4108 $monthtextshort = $outputlangs->transnoentitiesnoconv('MonthShort' . $month);
4109 }
4110 //print 'monthtext='.$monthtext.' monthtextshort='.$monthtextshort;
4111 $ret = str_replace('__b__', $monthtextshort, $ret);
4112 $ret = str_replace('__B__', $monthtext, $ret);
4113 //print 'x'.$outputlangs->charset_output.'-'.$ret.'x';
4114 //return $ret;
4115 }
4116 if (preg_match('/__a__/i', $format)) {
4117 //print "time=$time offsettz=$offsettz offsetdst=$offsetdst offsettzstring=$offsettzstring";
4118 $timetouse = $time + $offsettz + $offsetdst; // TODO Replace this with function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
4119
4120 if ($to_gmt) {
4121 $tzo = new DateTimeZone('UTC');
4122 } else {
4123 $tzo = new DateTimeZone(date_default_timezone_get());
4124 }
4125 $dtts = new DateTime();
4126 $dtts->setTimestamp($timetouse);
4127 $dtts->setTimezone($tzo);
4128 $w = $dtts->format("w");
4129 $dayweek = $outputlangs->transnoentitiesnoconv('Day' . $w);
4130
4131 $ret = str_replace('__A__', $dayweek, $ret);
4132 $ret = str_replace('__a__', dol_substr($dayweek, 0, 3), $ret);
4133 }
4134
4135 if ($decorate) {
4136 $ret = preg_replace('/(\d\d:\d\d [AP]M)$/', '<span class="'.($decorate === 1 ? 'opacitymedium' : $decorate).'">\1</span>', $ret);
4137 $ret = preg_replace('/(\d\d:\d\d)$/', '<span class="'.($decorate === 1 ? 'opacitymedium' : $decorate).'">\1</span>', $ret);
4138 }
4139
4140 return $ret;
4141}
4142
4143
4164function dol_getdate($timestamp, $fast = false, $forcetimezone = '')
4165{
4166 if ($timestamp === '') {
4167 return array();
4168 }
4169
4170 $datetimeobj = new DateTime();
4171 $datetimeobj->setTimestamp($timestamp); // Use local PHP server timezone
4172 if ($forcetimezone) {
4173 $datetimeobj->setTimezone(new DateTimeZone($forcetimezone == 'gmt' ? 'UTC' : $forcetimezone)); // (add timezone relative to the date entered)
4174 }
4175 $arrayinfo = array(
4176 'year' => ((int) date_format($datetimeobj, 'Y')),
4177 'mon' => ((int) date_format($datetimeobj, 'm')),
4178 'mday' => ((int) date_format($datetimeobj, 'd')),
4179 'wday' => ((int) date_format($datetimeobj, 'w')),
4180 'yday' => ((int) date_format($datetimeobj, 'z')),
4181 'hours' => ((int) date_format($datetimeobj, 'H')),
4182 'minutes' => ((int) date_format($datetimeobj, 'i')),
4183 'seconds' => ((int) date_format($datetimeobj, 's')),
4184 '0' => $timestamp
4185 );
4186
4187 return $arrayinfo;
4188}
4189
4211function dol_mktime($hour, $minute, $second, $month, $day, $year, $gm = 'auto', $check = 1)
4212{
4213 global $conf;
4214 //print "- ".$hour.",".$minute.",".$second.",".$month.",".$day.",".$year.",".$_SERVER["WINDIR"]." -";
4215
4216 if ($gm === 'auto') {
4217 $gm = (empty($conf) ? 'tzserver' : $conf->tzuserinputkey);
4218 }
4219 //print 'gm:'.$gm.' gm === auto:'.($gm === 'auto').'<br>';exit;
4220
4221 // Clean parameters
4222 if ($hour == -1 || empty($hour)) {
4223 $hour = 0;
4224 }
4225 if ($minute == -1 || empty($minute)) {
4226 $minute = 0;
4227 }
4228 if ($second == -1 || empty($second)) {
4229 $second = 0;
4230 }
4231
4232 // Check parameters
4233 if ($check) {
4234 if (!$month || !$day) {
4235 return '';
4236 }
4237 if ($day > 31) {
4238 return '';
4239 }
4240 if ($month > 12) {
4241 return '';
4242 }
4243 if ($hour < 0 || $hour > 24) {
4244 return '';
4245 }
4246 if ($minute < 0 || $minute > 60) {
4247 return '';
4248 }
4249 if ($second < 0 || $second > 60) {
4250 return '';
4251 }
4252 }
4253
4254 if (empty($gm) || ($gm === 'server' || $gm === 'tzserver')) {
4255 $default_timezone = @date_default_timezone_get(); // Example 'Europe/Berlin'
4256 $localtz = new DateTimeZone($default_timezone);
4257 } elseif ($gm === 'user' || $gm === 'tzuser' || $gm === 'tzuserrel') {
4258 // We use dol_tz_string first because it is more reliable.
4259 $default_timezone = (empty($_SESSION["dol_tz_string"]) ? @date_default_timezone_get() : $_SESSION["dol_tz_string"]); // Example 'Europe/Berlin'
4260 try {
4261 $localtz = new DateTimeZone($default_timezone);
4262 } catch (Exception $e) {
4263 dol_syslog("Warning dol_tz_string contains an invalid value " . json_encode($_SESSION["dol_tz_string"] ?? null), LOG_WARNING);
4264 $default_timezone = @date_default_timezone_get();
4265 }
4266 } elseif (strrpos($gm, "tz,") !== false) {
4267 $timezone = (string) str_replace("tz,", "", $gm); // Example 'tz,Europe/Berlin'
4268 try {
4269 $localtz = new DateTimeZone($timezone);
4270 } catch (Exception $e) {
4271 dol_syslog("Warning passed timezone contains an invalid value " . $timezone, LOG_WARNING);
4272 }
4273 }
4274
4275 if (empty($localtz)) {
4276 $localtz = new DateTimeZone('UTC');
4277 }
4278 $dt = new DateTime('now', $localtz);
4279 $dt->setDate((int) $year, (int) $month, (int) $day);
4280 $dt->setTime((int) $hour, (int) $minute, (int) $second);
4281 $date = $dt->getTimestamp(); // should include daylight saving time
4282
4283 return $date;
4284}
4285
4286
4297function dol_now($mode = 'gmt')
4298{
4299 $ret = 0;
4300
4301 if ($mode === 'auto') {
4302 $mode = 'gmt';
4303 }
4304
4305 if ($mode == 'gmt') {
4306 $ret = time(); // Time for now at greenwich.
4307 } elseif ($mode == 'tzserver') { // Time for now with PHP server timezone added
4308 require_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php';
4309 $tzsecond = getServerTimeZoneInt('now'); // Contains tz+dayling saving time
4310 $ret = (int) (dol_now('gmt') + ($tzsecond * 3600));
4311 // } elseif ($mode == 'tzref') {// Time for now with parent company timezone is added
4312 // require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
4313 // $tzsecond=getParentCompanyTimeZoneInt(); // Contains tz+dayling saving time
4314 // $ret=dol_now('gmt')+($tzsecond*3600);
4315 } elseif ($mode == 'tzuser' || $mode == 'tzuserrel') {
4316 // Time for now with user timezone added
4317 // print 'time: '.time();
4318 $offsettz = (empty($_SESSION['dol_tz']) ? 0 : $_SESSION['dol_tz']) * 60 * 60;
4319 $offsetdst = (empty($_SESSION['dol_dst']) ? 0 : $_SESSION['dol_dst']) * 60 * 60;
4320 $ret = (int) (dol_now('gmt') + ($offsettz + $offsetdst));
4321 }
4322
4323 return $ret;
4324}
4325
4326
4335function dol_print_size($size, $shortvalue = 0, $shortunit = 0)
4336{
4337 global $conf, $langs;
4338 $level = 1024;
4339
4340 if (!empty($conf->dol_optimize_smallscreen)) {
4341 $shortunit = 1;
4342 }
4343
4344 // Set value text
4345 if (empty($shortvalue) || $size < ($level * 10)) {
4346 $ret = $size;
4347 $textunitshort = $langs->trans("b");
4348 $textunitlong = $langs->trans("Bytes");
4349 } else {
4350 $ret = round($size / $level, 0);
4351 $textunitshort = $langs->trans("Kb");
4352 $textunitlong = $langs->trans("KiloBytes");
4353 }
4354 // Use long or short text unit
4355 if (empty($shortunit)) {
4356 $ret .= ' ' . $textunitlong;
4357 } else {
4358 $ret .= ' ' . $textunitshort;
4359 }
4360
4361 return $ret;
4362}
4363
4374function dol_print_url($url, $target = '_blank', $max = 32, $withpicto = 0, $morecss = '')
4375{
4376 global $langs;
4377
4378 if (empty($url)) {
4379 return '';
4380 }
4381
4382 $linkstart = '<a href="';
4383 if (!preg_match('/^http/i', $url)) {
4384 $linkstart .= 'http://';
4385 }
4386 $linkstart .= $url;
4387 $linkstart .= '"';
4388 if ($target) {
4389 $linkstart .= ' target="' . $target . '"';
4390 }
4391 $linkstart .= ' title="' . $langs->trans("URL") . ': ' . $url . '"';
4392 $linkstart .= '>';
4393
4394 $link = '';
4395 if (!preg_match('/^http/i', $url)) {
4396 $link .= 'http://';
4397 }
4398 $link .= dol_trunc($url, $max);
4399
4400 $linkend = '</a>';
4401
4402 if ($morecss == 'float') { // deprecated
4403 return '<div class="nospan' . ($morecss ? ' ' . $morecss : '') . '" style="margin-right: 10px">' . ($withpicto ? img_picto($langs->trans("Url"), 'globe', 'class="paddingrightonly"') : '') . $link . '</div>';
4404 } else {
4405 return $linkstart . '<span class="nospan' . ($morecss ? ' ' . $morecss : '') . '" style="margin-right: 10px">' . ($withpicto ? img_picto('', 'globe', 'class="paddingrightonly"') : '') . $link . '</span>' . $linkend;
4406 }
4407}
4408
4422function dol_print_email($email, $contactid = 0, $socid = 0, $addlink = 0, $max = 0, $showinvalid = 1, $withpicto = 0, $morecss = 'paddingrightonly')
4423{
4424 global $user, $langs, $hookmanager;
4425
4426 //global $conf; $conf->global->AGENDA_ADDACTIONFOREMAIL = 1;
4427 //$showinvalid = 1; $email = 'rrrrr';
4428
4429 $newemail = dol_escape_htmltag($email);
4430
4431 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER') && $withpicto) {
4432 $withpicto = 0;
4433 }
4434
4435 if (empty($email)) {
4436 return '&nbsp;';
4437 }
4438
4439 if ($addlink == 1) {
4440 $newemail = '<a class="' . ($morecss ? $morecss : '') . '" style="text-overflow: ellipsis;" href="';
4441 if (!preg_match('/^mailto:/i', $email)) {
4442 $newemail .= 'mailto:';
4443 }
4444 $newemail .= $email;
4445 $newemail .= '" target="_blank">';
4446
4447 $newemail .= ($withpicto ? img_picto($langs->trans("EMail") . ' : ' . $email, (is_numeric($withpicto) ? 'email' : $withpicto), 'class="paddingrightonly"') : '');
4448
4449 if ($max > 0) {
4450 $newemail .= dol_trunc($email, $max);
4451 } else {
4452 $newemail .= $email;
4453 }
4454 $newemail .= '</a>';
4455
4456 if ($showinvalid) {
4457 include_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
4458 include_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
4459 $emailonly = CMailFile::getValidAddress($email, 2);
4460 if (!isValidEmail($emailonly)) {
4461 $langs->load("errors");
4462 $newemail .= img_warning($langs->transnoentitiesnoconv("ErrorBadEMail", $emailonly), '', 'paddingrightonly');
4463 } elseif (!isValidMailDomain($emailonly)) {
4464 $langs->load("errors");
4465 $newemail .= img_warning($langs->transnoentitiesnoconv("ErrorBadMXDomain", $emailonly), '', 'paddingrightonly');
4466 }
4467 }
4468
4469 if (($contactid || $socid) && isModEnabled('agenda') && $user->hasRight("agenda", "myactions", "create")) {
4470 $type = 'AC_EMAIL';
4471 $linktoaddaction = '';
4472 if (getDolGlobalString('AGENDA_ADDACTIONFOREMAIL')) {
4473 $linktoaddaction = '<a href="' . DOL_URL_ROOT . '/comm/action/card.php?action=create&amp;backtopage=1&amp;actioncode=' . urlencode($type) . '&amp;contactid=' . ((int) $contactid) . '&amp;socid=' . ((int) $socid) . '">' . img_object($langs->trans("AddAction"), "calendar") . '</a>';
4474 }
4475 if ($linktoaddaction) {
4476 $newemail = '<div>' . $newemail . ' ' . $linktoaddaction . '</div>';
4477 }
4478 }
4479 } elseif ($addlink === 'thirdparty') {
4480 $tmpnewemail = '<a class="' . ($morecss ? $morecss : '') . '" style="text-overflow: ellipsis;" href="' . DOL_URL_ROOT . '/societe/card.php?socid=' . $socid . '&action=presend&mode=init#formmailbeforetitle">';
4481 $tmpnewemail .= ($withpicto ? img_picto($langs->trans("EMail") . ' : ' . $email, (is_numeric($withpicto) ? 'email' : $withpicto), 'class="paddingrightonly"') : '');
4482 if ($withpicto == 1) {
4483 $tmpnewemail .= $newemail;
4484 }
4485 $tmpnewemail .= '</a>';
4486
4487 $newemail = $tmpnewemail;
4488 } else {
4489 $newemail = ($withpicto ? img_picto($langs->trans("EMail") . ' : ' . $email, (is_numeric($withpicto) ? 'email' : $withpicto), 'class="paddingrightonly"') : '') . $newemail;
4490
4491 if ($showinvalid) {
4492 include_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
4493 include_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
4494 $emailonly = CMailFile::getValidAddress($email, 2);
4495 if (!isValidEmail($emailonly)) {
4496 $langs->load("errors");
4497 $newemail .= img_warning($langs->transnoentitiesnoconv("ErrorBadEMail", $email));
4498 } elseif (!isValidMailDomain($emailonly)) {
4499 $langs->load("errors");
4500 $newemail .= img_warning($langs->transnoentitiesnoconv("ErrorBadMXDomain", $emailonly));
4501 }
4502 }
4503 }
4504
4505 //$rep = '<div class="nospan" style="margin-right: 10px">';
4506 //$rep = ($withpicto ? img_picto($langs->trans("EMail").' : '.$email, (is_numeric($withpicto) ? 'email' : $withpicto), 'class="paddingrightonly"') : '').$newemail;
4507 //$rep .= '</div>';
4508 $rep = $newemail;
4509 if (getDolGlobalString('MAIN_MAIL_COPY_ON_CLICK')) {
4510 $rep .= showValueWithClipboardCPButton($newemail, 0, 'none');
4511 }
4512
4513 if ($hookmanager) {
4514 $parameters = array('cid' => $contactid, 'socid' => $socid, 'addlink' => $addlink, 'picto' => $withpicto);
4515
4516 $reshook = $hookmanager->executeHooks('printEmail', $parameters, $email);
4517 if ($reshook > 0) {
4518 $rep = '';
4519 }
4520 $rep .= $hookmanager->resPrint;
4521 }
4522
4523 return $rep;
4524}
4525
4526
4542function dolOutputDates($datep, $datef = null, $fullday = 0, $addseconds = 0, $pictotoadd = '', $tzoutput = 'tzuserrel', $reduceformat = 0)
4543{
4544 $tmpa = dol_getdate($datep);
4545 if (empty($datef)) {
4546 $tmpb = $tmpa;
4547 } else {
4548 $tmpb = dol_getdate($datef);
4549 }
4550
4551 $s = '';
4552
4553 if ($tmpa['mday'] == $tmpb['mday'] && $tmpa['mon'] == $tmpb['mon'] && $tmpa['year'] == $tmpb['year']) {
4554 // The same day
4555 $s .= '<div class="center inline-block">';
4556 if ($tmpa['hours'] != $tmpb['hours'] || $tmpa['minutes'] != $tmpb['minutes']) {
4557 // Not the same hour
4558 $s .= dol_print_date($datep, 'day'.($reduceformat ? 'reduceformat' : ''), $tzoutput);
4559 $s .= $pictotoadd;
4560 if (empty($fullday)) {
4561 $s .= '<br><span class="small opacitymedium">';
4562 $s .= dol_print_date($datep, 'hour'.($addseconds ? 'sec' : '').'reduceformat', $tzoutput);
4563 $s .= '-'.dol_print_date($datef, 'hour'.($addseconds ? 'sec' : '').'reduceformat', $tzoutput);
4564 $s .= '</span>';
4565 }
4566 } else {
4567 // The same hour
4568 $s .= dol_print_date($datep, 'day'.($reduceformat ? 'reduceformat' : ''), 'tzuserrel');
4569 $s .= $pictotoadd;
4570 if (empty($fullday)) {
4571 $s .= '<br><span class="small opacitymedium">';
4572 $s .= dol_print_date($datep, 'hour'.($addseconds ? 'sec' : '').'reduceformat', $tzoutput);
4573 $s .= '</span>';
4574 }
4575 }
4576 $s .= '</div>';
4577 } else {
4578 // Not the same day
4579 $s .= '<div class="center inline-block dateborderright">';
4580 $s .= dol_print_date($datep, 'day'.($reduceformat ? 'reduceformat' : ''), $tzoutput);
4581 if (empty($fullday)) {
4582 $s .= '<br><span class="small opacitymedium">';
4583 $s .= dol_print_date($datep, 'hour'.($addseconds ? 'sec' : '').'reduceformat', $tzoutput);
4584 $s .= '</span>';
4585 }
4586 $s .= '</div>';
4587 $s .= '<div class="center inline-block dateborderleft">';
4588 $s .= dol_print_date($datef, 'day'.($reduceformat ? 'reduceformat' : ''), 'tzuserrel');
4589 $s .= $pictotoadd;
4590 if (empty($fullday)) {
4591 $s .= '<br><span class="small opacitymedium">';
4592 $s .= dol_print_date($datef, 'hour'.($addseconds ? 'sec' : '').'reduceformat', $tzoutput);
4593 $s .= '</span>';
4594 }
4595 $s .= '</div>';
4596 }
4597
4598 return $s;
4599}
4600
4601
4607function getArrayOfSocialNetworks()
4608{
4609 global $db;
4610
4611 $socialnetworks = array();
4612 // Enable caching of array
4613 require_once DOL_DOCUMENT_ROOT . '/core/lib/memory.lib.php';
4614 $cachekey = dol_sanitizeKeyCode(str_replace(',', '_', 'socialnetworks_'.getEntity('c_socialnetworks')));
4615 $dataretrieved = dol_getcache($cachekey);
4616
4617 if (!is_null($dataretrieved)) {
4618 $socialnetworks = $dataretrieved;
4619 } else {
4620 $sql = "SELECT rowid, code, label, url, icon, active FROM " . MAIN_DB_PREFIX . "c_socialnetworks";
4621 $sql .= " WHERE entity IN (" . getEntity('c_socialnetworks').")";
4622
4623 $resql = $db->query($sql);
4624 if ($resql) {
4625 while ($obj = $db->fetch_object($resql)) {
4626 $socialnetworks[$obj->code] = array(
4627 'rowid' => $obj->rowid,
4628 'label' => $obj->label,
4629 'url' => $obj->url,
4630 'icon' => $obj->icon,
4631 'active' => $obj->active,
4632 );
4633 }
4634 }
4635 dol_setcache($cachekey, $socialnetworks); // If setting cache fails, this is not a problem, so we do not test result.
4636 }
4637
4638 return (is_array($socialnetworks) ? $socialnetworks : array());
4639}
4640
4651function dol_print_socialnetworks($value, $contactid, $socid, $type, $dictsocialnetworks = array())
4652{
4653 global $hookmanager, $langs, $user;
4654
4655 $htmllink = $value;
4656
4657 if (empty($value)) {
4658 return '&nbsp;';
4659 }
4660
4661 if (!empty($type)) {
4662 $htmllink = '<div class="divsocialnetwork inline-block valignmiddle">';
4663 // Use dictionary definition for picto $dictsocialnetworks[$type]['icon']
4664 $htmllink .= '<span class="fab pictofixedwidth ' . ($dictsocialnetworks[$type]['icon'] ? $dictsocialnetworks[$type]['icon'] : 'fa-link') . '"></span>';
4665 if ($type == 'skype') {
4666 $htmllink .= dol_escape_htmltag($value);
4667 $htmllink .= '&nbsp; <a href="skype:';
4668 $htmllink .= dol_string_nospecial($value, '_', '', array('@'));
4669 $htmllink .= '?call" alt="' . $langs->trans("Call") . '&nbsp;' . $value . '" title="' . dol_escape_htmltag($langs->trans("Call") . ' ' . $value) . '">';
4670 $htmllink .= '<img src="' . DOL_URL_ROOT . '/theme/common/skype_callbutton.png" border="0">';
4671 $htmllink .= '</a><a href="skype:';
4672 $htmllink .= dol_string_nospecial($value, '_', '', array('@'));
4673 $htmllink .= '?chat" alt="' . $langs->trans("Chat") . '&nbsp;' . $value . '" title="' . dol_escape_htmltag($langs->trans("Chat") . ' ' . $value) . '">';
4674 $htmllink .= '<img class="paddingleft" src="' . DOL_URL_ROOT . '/theme/common/skype_chatbutton.png" border="0">';
4675 $htmllink .= '</a>';
4676 if (($contactid || $socid) && isModEnabled('agenda') && $user->hasRight('agenda', 'myactions', 'create')) {
4677 $addlink = 'AC_SKYPE';
4678 $link = '';
4679 if (getDolGlobalString('AGENDA_ADDACTIONFORSKYPE')) {
4680 $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>';
4681 }
4682 $htmllink .= ($link ? ' ' . $link : '');
4683 }
4684 } else {
4685 if (!empty($dictsocialnetworks[$type]['url'])) {
4686 $tmpvirginurl = preg_replace('/\/?{socialid}/', '', $dictsocialnetworks[$type]['url']);
4687 if ($tmpvirginurl) {
4688 $value = preg_replace('/^www\.' . preg_quote($tmpvirginurl, '/') . '\/?/', '', $value);
4689 $value = preg_replace('/^' . preg_quote($tmpvirginurl, '/') . '\/?/', '', $value);
4690
4691 $tmpvirginurl3 = preg_replace('/^https:\/\//i', 'https://www.', $tmpvirginurl);
4692 if ($tmpvirginurl3) {
4693 $value = preg_replace('/^www\.' . preg_quote($tmpvirginurl3, '/') . '\/?/', '', $value);
4694 $value = preg_replace('/^' . preg_quote($tmpvirginurl3, '/') . '\/?/', '', $value);
4695 }
4696
4697 $tmpvirginurl2 = preg_replace('/^https?:\/\//i', '', $tmpvirginurl);
4698 if ($tmpvirginurl2) {
4699 $value = preg_replace('/^www\.' . preg_quote($tmpvirginurl2, '/') . '\/?/', '', $value);
4700 $value = preg_replace('/^' . preg_quote($tmpvirginurl2, '/') . '\/?/', '', $value);
4701 }
4702 }
4703 if (preg_match('/^https?:\/\//i', $value)) {
4704 $link = $value;
4705 } else {
4706 $link = str_replace('{socialid}', $value, $dictsocialnetworks[$type]['url']);
4707 }
4708 $valuetoshow = $value;
4709 $valuetoshow = preg_replace('/https:\/\/www\.(twitter|x|linkedin)\.com\/?/', '', $valuetoshow);
4710 if (preg_match('/^https?:\/\//i', $link)) {
4711 $htmllink .= '<a href="' . dol_sanitizeUrl($link, 0) . '" target="_blank" rel="noopener noreferrer">' . dol_escape_htmltag($valuetoshow) . '</a>';
4712 } else {
4713 $htmllink .= '<a href="' . dol_sanitizeUrl($link, 1) . '" target="_blank" rel="noopener noreferrer">' . dol_escape_htmltag($valuetoshow) . '</a>';
4714 }
4715 } else {
4716 $htmllink .= dol_escape_htmltag($value);
4717 }
4718 }
4719 $htmllink .= '</div>';
4720 } else {
4721 $langs->load("errors");
4722 $htmllink .= img_warning($langs->trans("ErrorBadSocialNetworkValue", $value));
4723 }
4724
4725 if ($hookmanager) {
4726 $parameters = array(
4727 'value' => $value,
4728 'cid' => $contactid,
4729 'socid' => $socid,
4730 'type' => $type,
4731 'dictsocialnetworks' => $dictsocialnetworks,
4732 );
4733
4734 $reshook = $hookmanager->executeHooks('printSocialNetworks', $parameters);
4735 if ($reshook > 0) {
4736 $htmllink = '';
4737 }
4738 $htmllink .= $hookmanager->resPrint;
4739 }
4740
4741 return $htmllink;
4742}
4743
4753function dol_print_profids($profID, $profIDtype, $countrycode = '', $addcpButton = 1)
4754{
4755 global $mysoc;
4756
4757 if (empty($profID) || empty($profIDtype)) {
4758 return '';
4759 }
4760 if (empty($countrycode)) {
4761 $countrycode = $mysoc->country_code;
4762 }
4763 $newProfID = $profID;
4764 $id = substr($profIDtype, -1);
4765 $ret = '';
4766 if (strtoupper($countrycode) == 'FR') {
4767 // France
4768 // (see https://www.economie.gouv.fr/entreprises/numeros-identification-entreprise)
4769
4770 if ($id == 1 && dol_strlen($newProfID) == 9) {
4771 // SIREN (ex: 123 123 123)
4772 $newProfID = substr($newProfID, 0, 3) . ' ' . substr($newProfID, 3, 3) . ' ' . substr($newProfID, 6, 3);
4773 }
4774 if ($id == 2 && dol_strlen($newProfID) == 14) {
4775 // SIRET (ex: 123 123 123 12345)
4776 $newProfID = substr($newProfID, 0, 3) . ' ' . substr($newProfID, 3, 3) . ' ' . substr($newProfID, 6, 3) . ' ' . substr($newProfID, 9, 5);
4777 }
4778 if ($id == 3 && dol_strlen($newProfID) == 5) {
4779 // NAF/APE (ex: 69.20Z)
4780 $newProfID = substr($newProfID, 0, 2) . '.' . substr($newProfID, 2, 3);
4781 }
4782 if ($profIDtype === 'VAT' && dol_strlen($newProfID) == 13) {
4783 // TVA intracommunautaire (ex: FR12 123 123 123)
4784 $newProfID = substr($newProfID, 0, 4) . ' ' . substr($newProfID, 4, 3) . ' ' . substr($newProfID, 7, 3) . ' ' . substr($newProfID, 10, 3);
4785 }
4786 }
4787 if (!empty($addcpButton)) {
4788 $ret = showValueWithClipboardCPButton(dol_escape_htmltag($profID), ($addcpButton == 1 ? 1 : 0), $newProfID);
4789 } else {
4790 $ret = $newProfID;
4791 }
4792 return $ret;
4793}
4794
4810function dol_print_phone($phone, $countrycode = '', $contactid = 0, $socid = 0, $addlink = '', $separ = "&nbsp;", $withpicto = '', $titlealt = '', $adddivfloat = 0, $morecss = 'paddingright')
4811{
4812 global $conf, $user, $langs, $mysoc, $hookmanager;
4813
4814 // Clean phone parameter
4815 $phone = is_null($phone) ? '' : preg_replace("/[\s.-]/", "", trim($phone));
4816 if (empty($phone)) {
4817 return '';
4818 }
4819 if (getDolGlobalString('MAIN_PHONE_SEPAR')) {
4820 $separ = getDolGlobalString('MAIN_PHONE_SEPAR');
4821 }
4822 if (empty($countrycode) && is_object($mysoc)) {
4823 $countrycode = $mysoc->country_code;
4824 }
4825
4826 // Short format for small screens
4827 if (!empty($conf->dol_optimize_smallscreen) && $separ != 'hidenum') {
4828 $separ = '';
4829 }
4830
4831 $newphone = $phone;
4832 $newphonewa = $phone;
4833 if (strtoupper($countrycode) == "FR") {
4834 // France
4835 if (dol_strlen($phone) == 10) {
4836 $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);
4837 } elseif (dol_strlen($phone) == 7) {
4838 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 2);
4839 } elseif (dol_strlen($phone) == 9) {
4840 $newphone = substr($newphone, 0, 2) . $separ . substr($newphone, 2, 3) . $separ . substr($newphone, 5, 2) . $separ . substr($newphone, 7, 2);
4841 } elseif (dol_strlen($phone) == 11) {
4842 $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);
4843 } elseif (dol_strlen($phone) == 12) {
4844 $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);
4845 } elseif (dol_strlen($phone) == 13) {
4846 $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);
4847 }
4848 } elseif (strtoupper($countrycode) == "CA") {
4849 if (dol_strlen($phone) == 10) {
4850 $newphone = ($separ != '' ? '(' : '') . substr($newphone, 0, 3) . ($separ != '' ? ')' : '') . $separ . substr($newphone, 3, 3) . ($separ != '' ? '-' : '') . substr($newphone, 6, 4);
4851 }
4852 } elseif (strtoupper($countrycode) == "PT") { //Portugal
4853 if (dol_strlen($phone) == 13) { //ex: +351_ABC_DEF_GHI
4854 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 3);
4855 }
4856 } elseif (strtoupper($countrycode) == "SR") { //Suriname
4857 if (dol_strlen($phone) == 10) { //ex: +597_ABC_DEF
4858 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3);
4859 } elseif (dol_strlen($phone) == 11) { //ex: +597_ABC_DEFG
4860 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 4);
4861 }
4862 } elseif (strtoupper($countrycode) == "DE") { //Deutschland
4863 if (dol_strlen($phone) == 14) { //ex: +49_ABCD_EFGH_IJK
4864 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 4) . $separ . substr($newphone, 7, 4) . $separ . substr($newphone, 11, 3);
4865 } elseif (dol_strlen($phone) == 13) { //ex: +49_ABC_DEFG_HIJ
4866 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 4) . $separ . substr($newphone, 10, 3);
4867 }
4868 } elseif (strtoupper($countrycode) == "ES") { //Spain
4869 if (dol_strlen($phone) == 12) { //ex: +34_ABC_DEF_GHI
4870 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
4871 }
4872 } elseif (strtoupper($countrycode) == "BF") { // Burkina Faso
4873 if (dol_strlen($phone) == 12) { //ex : +22 A BC_DE_FG_HI
4874 $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);
4875 }
4876 } elseif (strtoupper($countrycode) == "RO") { // Roumanie
4877 if (dol_strlen($phone) == 12) { //ex : +40 AB_CDE_FG_HI
4878 $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);
4879 }
4880 } elseif (strtoupper($countrycode) == "TR") { //Turquie
4881 if (dol_strlen($phone) == 13) { //ex : +90 ABC_DEF_GHIJ
4882 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 4);
4883 }
4884 } elseif (strtoupper($countrycode) == "US") { //Etat-Unis
4885 if (dol_strlen($phone) == 12) { //ex: +1 ABC_DEF_GHIJ
4886 $newphone = substr($newphone, 0, 2) . $separ . substr($newphone, 2, 3) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 4);
4887 }
4888 } elseif (strtoupper($countrycode) == "MX") { //Mexique
4889 if (dol_strlen($phone) == 12) { //ex: +52 ABCD_EFG_HI
4890 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 4) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 2);
4891 } elseif (dol_strlen($phone) == 11) { //ex: +52 AB_CD_EF_GH
4892 $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);
4893 } elseif (dol_strlen($phone) == 13) { //ex: +52 ABC_DEF_GHIJ
4894 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 4);
4895 }
4896 } elseif (strtoupper($countrycode) == "ML") { //Mali
4897 if (dol_strlen($phone) == 12) { //ex: +223 AB_CD_EF_GH
4898 $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);
4899 }
4900 } elseif (strtoupper($countrycode) == "TH") { //Thaïlande
4901 if (dol_strlen($phone) == 11) { //ex: +66_ABC_DE_FGH
4902 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 3);
4903 } elseif (dol_strlen($phone) == 12) { //ex: +66_A_BCD_EF_GHI
4904 $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);
4905 }
4906 } elseif (strtoupper($countrycode) == "MU") {
4907 //Maurice
4908 if (dol_strlen($phone) == 11) { //ex: +230_ABC_DE_FG
4909 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2);
4910 } elseif (dol_strlen($phone) == 12) { //ex: +230_ABCD_EF_GH
4911 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 4) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
4912 }
4913 } elseif (strtoupper($countrycode) == "ZA") { //Afrique du sud
4914 if (dol_strlen($phone) == 12) { //ex: +27_AB_CDE_FG_HI
4915 $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);
4916 }
4917 } elseif (strtoupper($countrycode) == "SY") { //Syrie
4918 if (dol_strlen($phone) == 12) { //ex: +963_AB_CD_EF_GH
4919 $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);
4920 } elseif (dol_strlen($phone) == 13) { //ex: +963_AB_CD_EF_GHI
4921 $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);
4922 }
4923 } elseif (strtoupper($countrycode) == "AE") { //Emirats Arabes Unis
4924 if (dol_strlen($phone) == 12) { //ex: +971_ABC_DEF_GH
4925 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 2);
4926 } elseif (dol_strlen($phone) == 13) { //ex: +971_ABC_DEF_GHI
4927 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 3);
4928 } elseif (dol_strlen($phone) == 14) { //ex: +971_ABC_DEF_GHIK
4929 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 4);
4930 }
4931 } elseif (strtoupper($countrycode) == "DZ") { //Algérie
4932 if (dol_strlen($phone) == 13) { //ex: +213_ABC_DEF_GHI
4933 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 3);
4934 }
4935 } elseif (strtoupper($countrycode) == "BE") { //Belgique
4936 if (dol_strlen($phone) == 11) { //ex: +32_ABC_DE_FGH
4937 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 3);
4938 } elseif (dol_strlen($phone) == 12) { //ex: +32_ABC_DEF_GHI
4939 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
4940 }
4941 } elseif (strtoupper($countrycode) == "PF") { //Polynésie française
4942 if (dol_strlen($phone) == 12) { //ex: +689_AB_CD_EF_GH
4943 $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);
4944 }
4945 } elseif (strtoupper($countrycode) == "CO") { //Colombie
4946 if (dol_strlen($phone) == 13) { //ex: +57_ABC_DEF_GH_IJ
4947 $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);
4948 }
4949 } elseif (strtoupper($countrycode) == "JO") { //Jordanie
4950 if (dol_strlen($phone) == 12) { //ex: +962_A_BCD_EF_GH
4951 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 1) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2);
4952 }
4953 } elseif (strtoupper($countrycode) == "JM") { //Jamaïque
4954 if (dol_strlen($newphone) == 12) { //ex: +1867_ABC_DEFG
4955 $newphone = substr($newphone, 0, 5) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 4);
4956 }
4957 } elseif (strtoupper($countrycode) == "MG") { //Madagascar
4958 if (dol_strlen($phone) == 13) { //ex: +261_AB_CD_EFG_HI
4959 $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);
4960 }
4961 } elseif (strtoupper($countrycode) == "GB") { //Royaume uni
4962 if (dol_strlen($phone) == 13) { //ex: +44_ABCD_EFG_HIJ
4963 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 4) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 3);
4964 }
4965 } elseif (strtoupper($countrycode) == "CH") { //Suisse
4966 if (dol_strlen($phone) == 12) { //ex: +41_AB_CDE_FG_HI
4967 $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);
4968 } elseif (dol_strlen($phone) == 15) { // +41_AB_CDE_FGH_IJKL
4969 $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);
4970 }
4971 } elseif (strtoupper($countrycode) == "TN") { //Tunisie
4972 if (dol_strlen($phone) == 12) { //ex: +216_AB_CDE_FGH
4973 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
4974 }
4975 } elseif (strtoupper($countrycode) == "GF") { //Guyane francaise
4976 if (dol_strlen($phone) == 13) { //ex: +594_ABC_DE_FG_HI (ABC=594 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) == "GP") { //Guadeloupe
4980 if (dol_strlen($phone) == 13) { //ex: +590_ABC_DE_FG_HI (ABC=590 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) == "MQ") { //Martinique
4984 if (dol_strlen($phone) == 13) { //ex: +596_ABC_DE_FG_HI (ABC=596 de nouveau)
4985 $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);
4986 }
4987 } elseif (strtoupper($countrycode) == "IT") { //Italie
4988 if (dol_strlen($phone) == 12) { //ex: +39_ABC_DEF_GHI
4989 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
4990 } elseif (dol_strlen($phone) == 13) { //ex: +39_ABC_DEF_GH_IJ
4991 $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);
4992 }
4993 } elseif (strtoupper($countrycode) == "AU") {
4994 //Australie
4995 if (dol_strlen($phone) == 12) {
4996 //ex: +61_A_BCDE_FGHI
4997 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 1) . $separ . substr($newphone, 4, 4) . $separ . substr($newphone, 8, 4);
4998 }
4999 } elseif (strtoupper($countrycode) == "LU") {
5000 // Luxembourg
5001 if (dol_strlen($phone) == 10) { // fix 6 digits +352_AA_BB_CC
5002 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2);
5003 } elseif (dol_strlen($phone) == 11) { // fix 7 digits +352_AA_BB_CC_D
5004 $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);
5005 } elseif (dol_strlen($phone) == 12) { // fix 8 digits +352_AA_BB_CC_DD
5006 $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);
5007 } elseif (dol_strlen($phone) == 13) { // mobile +352_AAA_BB_CC_DD
5008 $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);
5009 }
5010 } elseif (strtoupper($countrycode) == "PE") {
5011 // Peru
5012 if (dol_strlen($phone) == 7) { // fix 7 numbers without code AAA_BBBB
5013 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 4);
5014 } elseif (dol_strlen($phone) == 9) { // mobile add code and fix 9 numbers +51_AAA_BBB_CCC
5015 $newphonewa = '+51' . $newphone;
5016 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 10, 3);
5017 } elseif (dol_strlen($phone) == 11) { // fix 11 numbers +511_AAA_BBBB
5018 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 8, 4);
5019 } elseif (dol_strlen($phone) == 12) { // mobile +51_AAA_BBB_CCC
5020 $newphonewa = $newphone;
5021 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 10, 3) . $separ . substr($newphone, 14, 3);
5022 }
5023 } elseif (strtoupper($countrycode) == "IN") { //India
5024 if (dol_strlen($phone) == 13) {
5025 if ($withpicto == 'phone') { //ex: +91_AB_CDEF_GHIJ
5026 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 4) . $separ . substr($newphone, 9, 4);
5027 } else { //ex: +91_ABCDE_FGHIJ
5028 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 5) . $separ . substr($newphone, 8, 5);
5029 }
5030 }
5031 }
5032
5033 $newphoneastart = $newphoneaend = '';
5034 if (!empty($addlink)) { // Link on phone number (+ link to add action if conf->global->AGENDA_ADDACTIONFORPHONE set)
5035 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
5036 $newphoneastart = '<a href="tel:' . urlencode($phone) . '">';
5037 $newphoneaend .= '</a>';
5038 } elseif (isModEnabled('clicktodial') && $addlink == 'AC_TEL') { // If click to dial, we use click to dial url
5039 if (empty($user->clicktodial_loaded)) {
5040 $user->fetch_clicktodial();
5041 }
5042
5043 // Define urlmask
5044 $urlmask = getDolGlobalString('CLICKTODIAL_URL', 'ErrorClickToDialModuleNotConfigured');
5045 if (!empty($user->clicktodial_url)) {
5046 $urlmask = $user->clicktodial_url;
5047 }
5048
5049 $clicktodial_poste = (!empty($user->clicktodial_poste) ? urlencode($user->clicktodial_poste) : '');
5050 $clicktodial_login = (!empty($user->clicktodial_login) ? urlencode($user->clicktodial_login) : '');
5051 $clicktodial_password = (!empty($user->clicktodial_password) ? urlencode($user->clicktodial_password) : '');
5052 // This line is for backward compatibility @phan-suppress-next-line PhanPluginPrintfVariableFormatString
5053 $url = sprintf($urlmask, urlencode($phone), $clicktodial_poste, $clicktodial_login, $clicktodial_password);
5054 // Those lines are for substitution
5055 $substitarray = array(
5056 '__PHONEFROM__' => $clicktodial_poste,
5057 '__PHONETO__' => urlencode($phone),
5058 '__LOGIN__' => $clicktodial_login,
5059 '__PASS__' => $clicktodial_password
5060 );
5061 $url = make_substitutions($url, $substitarray);
5062 if (!getDolGlobalString('CLICKTODIAL_DO_NOT_USE_AJAX_CALL')) {
5063 // Default and recommended: New method using ajax without submitting a page making a javascript history.go(-1) back
5064 $newphoneastart = '<a href="' . $url . '" class="cssforclicktodial">'; // Call of ajax is handled by the lib_foot.js.php on class 'cssforclicktodial'
5065 $newphoneaend = '</a>';
5066 } else {
5067 // Old method
5068 $newphoneastart = '<a href="' . $url . '"';
5069 if (getDolGlobalString('CLICKTODIAL_FORCENEWTARGET')) {
5070 $newphoneastart .= ' target="_blank" rel="noopener noreferrer"';
5071 }
5072 $newphoneastart .= '>';
5073 $newphoneaend .= '</a>';
5074 }
5075 }
5076
5077 //if (($contactid || $socid) && isModEnabled('agenda') && $user->hasRight('agenda', 'myactions', 'create'))
5078 if (isModEnabled('agenda') && $user->hasRight("agenda", "myactions", "create")) {
5079 $type = 'AC_TEL';
5080 $addlinktoagenda = '';
5081 if ($addlink == 'AC_FAX') {
5082 $type = 'AC_FAX';
5083 }
5084 if (getDolGlobalString('AGENDA_ADDACTIONFORPHONE')) {
5085 $addlinktoagenda = '<a href="' . DOL_URL_ROOT . '/comm/action/card.php?action=create&amp;backtopage=' . urlencode($_SERVER['REQUEST_URI']) . '&amp;actioncode=' . $type . ($contactid ? '&amp;contactid=' . $contactid : '') . ($socid ? '&amp;socid=' . $socid : '') . '">' . img_object($langs->trans("AddAction"), "calendar") . '</a>';
5086 }
5087 if ($addlinktoagenda) {
5088 $newphone = '<span>' . $newphone . ' ' . $addlinktoagenda . '</span>';
5089 }
5090 }
5091 }
5092
5093 if (getDolGlobalString('CONTACT_PHONEMOBILE_SHOW_LINK_TO_WHATSAPP') && $withpicto == 'mobile') {
5094 // Link to Whatsapp
5095 $newphone .= ' <a href="https://wa.me/' . $newphonewa . '" target="_blank"'; // Use api to whatasapp contacts
5096 $newphone .= '><span class="paddingright fab fa-whatsapp" style="color:#25D366;" title="WhatsApp"></span></a>';
5097 }
5098
5099 if (empty($titlealt)) {
5100 $titlealt = ($withpicto == 'fax' ? $langs->trans("Fax") : $langs->trans("Phone"));
5101 }
5102 $rep = '';
5103
5104 if ($hookmanager) {
5105 $parameters = array('countrycode' => $countrycode, 'cid' => $contactid, 'socid' => $socid, 'titlealt' => $titlealt, 'picto' => $withpicto);
5106 $reshook = $hookmanager->executeHooks('printPhone', $parameters, $phone);
5107 $rep .= $hookmanager->resPrint;
5108 }
5109 if (empty($reshook)) {
5110 $picto = '';
5111 if ($withpicto) {
5112 if ($withpicto == 'fax') {
5113 $picto = 'phoning_fax';
5114 } elseif ($withpicto == 'phone') {
5115 $picto = 'phone';
5116 } elseif ($withpicto == 'mobile') {
5117 $picto = 'phoning_mobile';
5118 } else {
5119 $picto = '';
5120 }
5121 }
5122 if ($adddivfloat == 1) {
5123 $rep .= '<div class="nospan float' . ($morecss ? ' ' . $morecss : '') . '">';
5124 } elseif (empty($adddivfloat)) {
5125 $rep .= '<span' . ($morecss ? ' class="' . $morecss . '"' : '') . '>';
5126 }
5127
5128 $rep .= $newphoneastart;
5129 $rep .= ($withpicto ? img_picto($titlealt, $picto) : '');
5130 if ($separ != 'hidenum') {
5131 $rep .= ($withpicto ? ' ' : '') . $newphone;
5132 }
5133 $rep .= $newphoneaend;
5134
5135 if ($adddivfloat == 1) {
5136 $rep .= '</div>';
5137 } elseif (empty($adddivfloat)) {
5138 $rep .= '</span>';
5139 }
5140 }
5141
5142 return $rep;
5143}
5144
5153function dol_print_ip($ip, $mode = 0, $showname = 0)
5154{
5155 global $conf;
5156
5157 $ret = '';
5158 if (!isset($conf->cache['resolveips'])) {
5159 $conf->cache['resolveips'] = array();
5160 }
5161
5162 if ($mode != 2) {
5163 $countrycode = dolGetCountryCodeFromIp($ip);
5164 if ($countrycode) { // If success, countrycode is us, fr, ...
5165 if (file_exists(DOL_DOCUMENT_ROOT . '/theme/common/flags/' . $countrycode . '.png')) {
5166 $ret .= picto_from_langcode($countrycode);
5167 } else {
5168 $ret .= '(' . $countrycode . ')';
5169 }
5170 $ret .= '&nbsp;';
5171 } else {
5172 // Nothing
5173 }
5174 }
5175
5176 if (in_array($mode, [0, 2])) {
5177 $domain = '';
5178 if ($showname) {
5179 if (!array_key_exists($ip, $conf->cache['resolveips'])) {
5180 $domain = gethostbyaddr($ip);
5181 $conf->cache['resolveips'][$ip] = $domain; // false or domain
5182 } else {
5183 $domain = $conf->cache['resolveips'][$ip];
5184 }
5185 }
5186 if ($domain) {
5187 $ret .= $domain;
5188 } else {
5189 $ret .= $ip;
5190 }
5191 }
5192
5193 return $ret;
5194}
5195
5208function getUserRemoteIP($trusted = 0)
5209{
5210 if ($trusted) { // Return only IP we can rely on (not spoofable by the client)
5211 $ip = (empty($_SERVER['REMOTE_ADDR']) ? '' : $_SERVER['REMOTE_ADDR']); // value may be the IP of a proxy
5212 // 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)
5213 // This can happen if the proxy were added in the list of trusted proxy.
5214 return $ip;
5215 }
5216
5217 // Try to guess the real IP of client (but this may not be reliable)
5218 if (empty($_SERVER['HTTP_X_FORWARDED_FOR']) || preg_match('/[^0-9\.\:,\[\]\s]/', $_SERVER['HTTP_X_FORWARDED_FOR'])) {
5219 if (empty($_SERVER['HTTP_CLIENT_IP']) || preg_match('/[^0-9\.\:,\[\]\s]/', $_SERVER['HTTP_CLIENT_IP'])) {
5220 if (empty($_SERVER["HTTP_CF_CONNECTING_IP"])) {
5221 $ip = (empty($_SERVER['REMOTE_ADDR']) ? '' : $_SERVER['REMOTE_ADDR']); // value may be the IP of the proxy and not the client
5222 } else {
5223 $ip = $_SERVER["HTTP_CF_CONNECTING_IP"]; // value here may have been forged by client
5224 }
5225 } else {
5226 $ip = preg_replace('/,.*$/', '', $_SERVER['HTTP_CLIENT_IP']); // value is clean here but may have been forged by proxy
5227 }
5228 } else {
5229 $ip = preg_replace('/,.*$/', '', $_SERVER['HTTP_X_FORWARDED_FOR']); // value is clean here but may have been forged by proxy
5230 }
5231 return $ip;
5232}
5233
5240function dolGetCountryCodeFromIp($ip)
5241{
5242 $countrycode = '';
5243
5244 if (isModEnabled('geoipmaxmind')) {
5245 if (getDolGlobalString('GEOIP_VERSION') == 'php') {
5246 $datafile = getDolGlobalString('GEOIPMAXMIND_COUNTRY_DATAFILE');
5247 } else {
5248 $diroffile = getMultidirOutput(null, 'geoipmaxmind');
5249 $datafile = $diroffile . '/' . getDolGlobalString('GEOIPMAXMIND_COUNTRY_DATAFILE_EMBEDDED');
5250 }
5251 //$ip='24.24.24.24';
5252 //$datafile='/usr/share/GeoIP/GeoIP.dat'; Note that this must be downloaded datafile (not same than datafile provided with ubuntu packages)
5253 if ($datafile) {
5254 try {
5255 include_once DOL_DOCUMENT_ROOT . '/core/class/dolgeoip.class.php';
5256 $geoip = new DolGeoIP('country', $datafile);
5257 //print 'ip='.$ip.' databaseType='.$geoip->gi->databaseType." GEOIP_CITY_EDITION_REV1=".GEOIP_CITY_EDITION_REV1."\n";
5258 $countrycode = $geoip->getCountryCodeFromIP($ip);
5259 } catch (Exception $e) {
5260 //print 'Error with GeoIP database: '.$e->getMessage();
5261 }
5262 }
5263 }
5264
5265 return $countrycode;
5266}
5267
5268
5275function dol_user_country()
5276{
5277 global $conf, $langs, $user;
5278
5279 //$ret=$user->xxx;
5280 $ret = '';
5281 if (isModEnabled('geoipmaxmind')) {
5282 $ip = getUserRemoteIP();
5283 $datafile = getDolGlobalString('GEOIPMAXMIND_COUNTRY_DATAFILE');
5284 //$ip='24.24.24.24';
5285 //$datafile='E:\Mes Sites\Web\Admin1\awstats\maxmind\GeoIP.dat';
5286 include_once DOL_DOCUMENT_ROOT . '/core/class/dolgeoip.class.php';
5287 $geoip = new DolGeoIP('country', $datafile);
5288 $countrycode = $geoip->getCountryCodeFromIP($ip);
5289 $ret = $countrycode;
5290 }
5291 return $ret;
5292}
5293
5306function dol_print_address($address, $htmlid, $element, $id, $noprint = 0, $charfornl = '')
5307{
5308 global $hookmanager;
5309
5310 $out = '';
5311
5312 if ($address) {
5313 if ($hookmanager) {
5314 $parameters = array('element' => $element, 'id' => $id);
5315 $reshook = $hookmanager->executeHooks('printAddress', $parameters, $address);
5316 $out .= $hookmanager->resPrint;
5317 }
5318 if (empty($reshook)) {
5319 if (empty($charfornl)) {
5320 $out .= nl2br((string) $address);
5321 } else {
5322 $out .= preg_replace('/[\r\n]+/', $charfornl, (string) $address);
5323 }
5324
5325 // TODO Remove this block, we can add this using the hook now
5326 $showgmap = $showomap = 0;
5327 if (($element == 'thirdparty' || $element == 'societe') && isModEnabled('google') && getDolGlobalString('GOOGLE_ENABLE_GMAPS')) {
5328 $showgmap = 1;
5329 }
5330 if ($element == 'contact' && isModEnabled('google') && getDolGlobalString('GOOGLE_ENABLE_GMAPS_CONTACTS')) {
5331 $showgmap = 1;
5332 }
5333 if ($element == 'member' && isModEnabled('google') && getDolGlobalString('GOOGLE_ENABLE_GMAPS_MEMBERS')) {
5334 $showgmap = 1;
5335 }
5336 if ($element == 'user' && isModEnabled('google') && getDolGlobalString('GOOGLE_ENABLE_GMAPS_USERS')) {
5337 $showgmap = 1;
5338 }
5339 if (($element == 'thirdparty' || $element == 'societe') && isModEnabled('openstreetmap') && getDolGlobalString('OPENSTREETMAP_ENABLE_MAPS')) {
5340 $showomap = 1;
5341 }
5342 if ($element == 'contact' && isModEnabled('openstreetmap') && getDolGlobalString('OPENSTREETMAP_ENABLE_MAPS_CONTACTS')) {
5343 $showomap = 1;
5344 }
5345 if ($element == 'member' && isModEnabled('openstreetmap') && getDolGlobalString('OPENSTREETMAP_ENABLE_MAPS_MEMBERS')) {
5346 $showomap = 1;
5347 }
5348 if ($element == 'user' && isModEnabled('openstreetmap') && getDolGlobalString('OPENSTREETMAP_ENABLE_MAPS_USERS')) {
5349 $showomap = 1;
5350 }
5351 if ($showgmap) {
5352 $url = dol_buildpath('/google/gmaps.php?mode=' . $element . '&id=' . $id, 1);
5353 $out .= ' <a href="' . $url . '" target="_gmaps"><img id="' . $htmlid . '" class="valigntextbottom" src="' . DOL_URL_ROOT . '/theme/common/gmap.png"></a>';
5354 }
5355 if ($showomap) {
5356 $url = dol_buildpath('/openstreetmap/maps.php?mode=' . $element . '&id=' . $id, 1);
5357 $out .= ' <a href="' . $url . '" target="_gmaps"><img id="' . $htmlid . '_openstreetmap" class="valigntextbottom" src="' . DOL_URL_ROOT . '/theme/common/gmap.png"></a>';
5358 }
5359 }
5360 }
5361 if ($noprint) {
5362 return $out;
5363 } else {
5364 print $out;
5365 return null;
5366 }
5367}
5368
5369
5379function isValidEmail($address, $acceptsupervisorkey = 0, $acceptuserkey = 0)
5380{
5381 if ($acceptsupervisorkey && $address == '__SUPERVISOREMAIL__') {
5382 return true;
5383 }
5384 if ($acceptuserkey && $address == '__USER_EMAIL__') {
5385 return true;
5386 }
5387 if (filter_var($address, FILTER_VALIDATE_EMAIL)) {
5388 return true;
5389 }
5390
5391 return false;
5392}
5393
5403function isValidMXRecord($domain)
5404{
5405 if (function_exists('idn_to_ascii') && function_exists('checkdnsrr')) {
5406 if (!checkdnsrr(idn_to_ascii($domain), 'MX')) {
5407 return 0;
5408 }
5409 if (function_exists('getmxrr')) {
5410 $mxhosts = array();
5411 $weight = array();
5412 getmxrr(idn_to_ascii($domain), $mxhosts, $weight);
5413 if (count($mxhosts) > 1) {
5414 return 1;
5415 }
5416 if (count($mxhosts) == 1 && !in_array((string) $mxhosts[0], array('', '.'))) {
5417 return 1;
5418 }
5419
5420 return 0;
5421 }
5422 }
5423
5424 // function idn_to_ascii or checkdnsrr or getmxrr does not exists
5425 return -1;
5426}
5427
5435function isValidPhone($phone)
5436{
5437 return true;
5438}
5439
5440
5450function dolGetFirstLetters($s, $nbofchar = 1)
5451{
5452 $ret = '';
5453 $tmparray = explode(' ', $s);
5454 foreach ($tmparray as $tmps) {
5455 $ret .= dol_substr($tmps, 0, $nbofchar);
5456 }
5457
5458 return $ret;
5459}
5460
5461
5469function dol_strlen($string, $stringencoding = 'UTF-8')
5470{
5471 if (is_null($string)) {
5472 return 0;
5473 }
5474
5475 if (function_exists('mb_strlen')) {
5476 return mb_strlen($string, $stringencoding);
5477 } else {
5478 return strlen($string);
5479 }
5480}
5481
5492function dol_substr($string, $start, $length = null, $stringencoding = '', $trunconbytes = 0)
5493{
5494 global $langs;
5495
5496 if (empty($stringencoding)) {
5497 $stringencoding = (empty($langs) ? 'UTF-8' : $langs->charset_output);
5498 }
5499
5500 $ret = '';
5501 if (empty($trunconbytes)) {
5502 if (function_exists('mb_substr')) {
5503 $ret = mb_substr($string, $start, $length, $stringencoding);
5504 } else {
5505 $ret = substr($string, $start, $length);
5506 }
5507 } else {
5508 if (function_exists('mb_strcut')) {
5509 $ret = mb_strcut($string, $start, $length, $stringencoding);
5510 } else {
5511 $ret = substr($string, $start, $length);
5512 }
5513 }
5514 return $ret;
5515}
5516
5517
5531function dol_trunc($string, $size = 40, $trunc = 'right', $stringencoding = 'UTF-8', $nodot = 0, $display = 0)
5532{
5533 global $conf;
5534
5535 if (empty($size) || getDolGlobalString('MAIN_DISABLE_TRUNC')) {
5536 return $string;
5537 }
5538
5539 if (empty($stringencoding)) {
5540 $stringencoding = 'UTF-8';
5541 }
5542 // reduce for small screen
5543 if (!empty($conf->dol_optimize_smallscreen) && $conf->dol_optimize_smallscreen == 1 && $display == 1) {
5544 $size = round($size / 3);
5545 }
5546
5547 // We go always here
5548 if ($trunc == 'right') {
5549 $newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
5550 if (dol_strlen($newstring, $stringencoding) > ($size + ($nodot ? 0 : 1))) {
5551 // If nodot is 0 and size is 1 chars more, we don't trunc and don't add …
5552 return dol_substr($newstring, 0, $size, $stringencoding) . ($nodot ? '' : '…');
5553 } else {
5554 //return 'u'.$size.'-'.$newstring.'-'.dol_strlen($newstring,$stringencoding).'-'.$string;
5555 return $string;
5556 }
5557 } elseif ($trunc == 'middle') {
5558 $newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
5559 if (dol_strlen($newstring, $stringencoding) > 2 && dol_strlen($newstring, $stringencoding) > ($size + 1)) {
5560 $size1 = (int) round($size / 2);
5561 $size2 = (int) round($size / 2);
5562 return dol_substr($newstring, 0, $size1, $stringencoding) . '…' . dol_substr($newstring, dol_strlen($newstring, $stringencoding) - $size2, $size2, $stringencoding);
5563 } else {
5564 return $string;
5565 }
5566 } elseif ($trunc == 'left') {
5567 $newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
5568 if (dol_strlen($newstring, $stringencoding) > ($size + ($nodot ? 0 : 1))) {
5569 // If nodot is 0 and size is 1 chars more, we don't trunc and don't add …
5570 return '…' . dol_substr($newstring, dol_strlen($newstring, $stringencoding) - $size, $size, $stringencoding);
5571 } else {
5572 return $string;
5573 }
5574 } elseif ($trunc == 'wrap') {
5575 $newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
5576 if (dol_strlen($newstring, $stringencoding) > ($size + 1)) {
5577 return dol_substr($newstring, 0, $size, $stringencoding) . "\n" . dol_trunc(dol_substr($newstring, $size, dol_strlen($newstring, $stringencoding) - $size, $stringencoding), $size, $trunc);
5578 } else {
5579 return $string;
5580 }
5581 } else {
5582 return 'BadParam3CallingDolTrunc';
5583 }
5584}
5585
5593function getPictoForType($key, $morecss = '')
5594{
5595 // Set array with type -> picto
5596 $type2picto = array(
5597 'varchar' => 'font',
5598 'text' => 'font',
5599 'html' => 'code',
5600 'int' => 'sort-numeric-down',
5601 'double' => 'sort-numeric-down',
5602 'price' => 'currency',
5603 'pricecy' => 'multicurrency',
5604 'password' => 'key',
5605 'boolean' => 'check-square',
5606 'date' => 'calendar',
5607 'datetime' => 'calendar',
5608 'duration' => 'hourglass',
5609 'phone' => 'phone',
5610 'mail' => 'email',
5611 'url' => 'url',
5612 'ip' => 'country',
5613 'select' => 'list',
5614 'sellist' => 'list',
5615 'stars' => 'fontawesome_star_fas',
5616 'radio' => 'check-circle',
5617 'checkbox' => 'list',
5618 'chkbxlst' => 'list',
5619 'link' => 'link',
5620 'icon' => "question",
5621 'point' => "country",
5622 'multipts' => 'country',
5623 'linestrg' => "country",
5624 'polygon' => "country",
5625 'separate' => 'minus'
5626 );
5627
5628 if (!empty($type2picto[$key])) {
5629 return img_picto('', $type2picto[$key], 'class="pictofixedwidth' . ($morecss ? ' ' . $morecss : '') . '"');
5630 }
5631
5632 return img_picto('', 'generic', 'class="pictofixedwidth' . ($morecss ? ' ' . $morecss : '') . '"');
5633}
5634
5635
5659function img_picto($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0, $srconly = 0, $notitle = 0, $alt = '', $morecss = '', $marginleftonlyshort = 2, $allowothertags = array())
5660{
5661 global $conf;
5662
5663 // We forge fullpathpicto for image to $path/img/$picto. By default, we take DOL_URL_ROOT/theme/$conf->theme/img/$picto
5664 $url = DOL_URL_ROOT;
5665 $theme = isset($conf->theme) ? $conf->theme : null;
5666 $path = 'theme/' . $theme;
5667 if (empty($picto)) {
5668 $picto = 'generic';
5669 }
5670
5671 // Define fullpathpicto to use into src
5672 if ($pictoisfullpath) {
5673 // Clean parameters
5674 if (!preg_match('/(\.png|\.gif|\.svg)$/i', $picto)) {
5675 $picto .= '.png';
5676 }
5677 $fullpathpicto = $picto;
5678 $reg = array();
5679 if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
5680 $morecss .= ($morecss ? ' ' : '') . $reg[1];
5681 $moreatt = str_replace('class="' . $reg[1] . '"', '', $moreatt);
5682 }
5683 } else {
5684 // $picto can not be null since replaced with 'generic' in that case
5685 // $pictowithouttext = preg_replace('/(\.png|\.gif|\.svg)$/', '', (is_null($picto) ? '' : $picto));
5686 $pictowithouttext = preg_replace('/(\.png|\.gif|\.svg)$/', '', $picto);
5687 $pictowithouttext = str_replace('object_', '', $pictowithouttext);
5688 $pictowithouttext = str_replace('_nocolor', '', $pictowithouttext);
5689
5690 // Fix some values of $pictowithouttext
5691 $pictoconvertkey = array(
5692 'facture' => 'bill',
5693 'shipping' => 'shipment',
5694 'fichinter' => 'intervention',
5695 'agenda' => 'calendar',
5696 'invoice_supplier' => 'supplier_invoice',
5697 'order_supplier' => 'supplier_order');
5698 if (in_array($pictowithouttext, array_keys($pictoconvertkey))) {
5699 $pictowithouttext = $pictoconvertkey[$pictowithouttext];
5700 }
5701
5702 if (strpos($pictowithouttext, 'fontawesome_') === 0 || strpos($pictowithouttext, 'fa-') === 0) {
5703 // This is a font awesome image 'fontawesome_xxx' or 'fa-xxx'
5704 $pictowithouttext = str_replace('fontawesome_', '', $pictowithouttext);
5705 $pictowithouttext = str_replace('fa-', '', $pictowithouttext);
5706
5707 // Compatibility with old fontawesome versions
5708 if ($pictowithouttext == 'file-o') {
5709 $pictowithouttext = 'file';
5710 }
5711
5712 $pictowithouttextarray = explode('_', $pictowithouttext);
5713 $marginleftonlyshort = 0;
5714
5715 if (!empty($pictowithouttextarray[1])) {
5716 // Syntax is 'fontawesome_fakey_faprefix_facolor_fasize' or 'fa-fakey_faprefix_facolor_fasize'
5717 $fakey = 'fa-' . $pictowithouttextarray[0];
5718 $faprefix = empty($pictowithouttextarray[1]) ? 'fas' : $pictowithouttextarray[1];
5719 $facolor = empty($pictowithouttextarray[2]) ? '' : $pictowithouttextarray[2];
5720 $fasize = empty($pictowithouttextarray[3]) ? '' : $pictowithouttextarray[3];
5721 } else {
5722 $fakey = 'fa-' . $pictowithouttext;
5723 $faprefix = 'fas';
5724 $facolor = '';
5725 $fasize = '';
5726 }
5727
5728 // This snippet only needed since function img_edit accepts only one additional parameter: no separate one for css only.
5729 // class/style need to be extracted to avoid duplicate class/style validation errors when $moreatt is added to the end of the attributes.
5730 $morestyle = '';
5731 $reg = array();
5732 if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
5733 $morecss .= ($morecss ? ' ' : '') . $reg[1];
5734 $moreatt = str_replace('class="' . $reg[1] . '"', '', $moreatt);
5735 }
5736 if (preg_match('/style="([^"]+)"/', $moreatt, $reg)) {
5737 $morestyle = $reg[1];
5738 $moreatt = str_replace('style="' . $reg[1] . '"', '', $moreatt);
5739 }
5740 $moreatt = trim($moreatt);
5741
5742 $enabledisablehtml = '<span class="' . $faprefix . ' ' . $fakey . ($marginleftonlyshort ? ($marginleftonlyshort == 1 ? ' marginleftonlyshort' : ' marginleftonly') : '');
5743 $enabledisablehtml .= ($morecss ? ' ' . $morecss : '') . '" style="' . ($fasize ? ('font-size: ' . $fasize . ';') : '') . ($facolor ? (' color: ' . $facolor . ';') : '') . ($morestyle ? ' ' . $morestyle : '') . '"' . (($notitle || empty($titlealt)) ? '' : ' title="' . dol_escape_htmltag($titlealt) . '"') . ($moreatt ? ' ' . $moreatt : '') . '>';
5744 $enabledisablehtml .= '</span>';
5745
5746 return $enabledisablehtml;
5747 }
5748
5749 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
5750 $fakey = $pictowithouttext;
5751 $facolor = '';
5752 $fasize = '';
5753 $fa = getDolGlobalString('MAIN_FONTAWESOME_ICON_STYLE', 'fas');
5754 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'))) {
5755 $fa = 'far';
5756 }
5757 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'))) {
5758 $fa = 'fab';
5759 }
5760
5761 $arrayconvpictotofa = getImgPictoConv('fa');
5762
5763 if ($pictowithouttext == 'off') {
5764 $fakey = 'fa-square';
5765 $fasize = '1.3em';
5766 } elseif ($pictowithouttext == 'on') {
5767 $fakey = 'fa-check-square';
5768 $fasize = '1.3em';
5769 } elseif ($pictowithouttext == 'listlight') {
5770 $fakey = 'fa-download';
5771 $marginleftonlyshort = 1;
5772 } elseif ($pictowithouttext == 'printer') {
5773 $fakey = 'fa-print';
5774 $fasize = '1.2em';
5775 } elseif ($pictowithouttext == 'note') {
5776 $fakey = 'fa-sticky-note';
5777 $marginleftonlyshort = 1;
5778 } elseif (in_array($pictowithouttext, array('1uparrow', '1downarrow', '1leftarrow', '1rightarrow', '1uparrow_selected', '1downarrow_selected', '1leftarrow_selected', '1rightarrow_selected'))) {
5779 $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');
5780 $fakey = 'fa-' . $convertarray[$pictowithouttext];
5781 if (preg_match('/selected/', $pictowithouttext)) {
5782 $facolor = '#888';
5783 }
5784 $marginleftonlyshort = 1;
5785 } elseif (!empty($arrayconvpictotofa[$pictowithouttext])) {
5786 $fakey = 'fa-' . $arrayconvpictotofa[$pictowithouttext];
5787 } else {
5788 $fakey = 'fa-' . $pictowithouttext;
5789 }
5790
5791 if (in_array($pictowithouttext, array('dollyrevert', 'member', 'members', 'contract', 'group', 'resource', 'shipment', 'reception'))) {
5792 $morecss .= ' em092';
5793 }
5794 if (in_array($pictowithouttext, array('conferenceorbooth', 'eventorganization', 'holiday', 'info', 'info_black', 'project', 'workstation'))) {
5795 $morecss .= ' em088';
5796 }
5797 if (in_array($pictowithouttext, array('asset', 'intervention', 'payment', 'loan', 'partnership', 'stock', 'technic'))) {
5798 $morecss .= ' em080';
5799 }
5800
5801 // Define $marginleftonlyshort
5802 $arrayconvpictotomarginleftonly = array(
5803 'bank',
5804 'check',
5805 'delete',
5806 'generic',
5807 'grip',
5808 'grip_title',
5809 'jabber',
5810 'grip_title',
5811 'grip',
5812 'listlight',
5813 'note',
5814 'on',
5815 'off',
5816 'playdisabled',
5817 'printer',
5818 'resize',
5819 'sign-out',
5820 'stats',
5821 'switch_on',
5822 'switch_on_grey',
5823 'switch_on_red',
5824 'switch_off',
5825 'switch_off_grey',
5826 'switch_off_red',
5827 'uparrow',
5828 '1uparrow',
5829 '1downarrow',
5830 '1leftarrow',
5831 '1rightarrow',
5832 '1uparrow_selected',
5833 '1downarrow_selected',
5834 '1leftarrow_selected',
5835 '1rightarrow_selected'
5836 );
5837 if (!array_key_exists($pictowithouttext, $arrayconvpictotomarginleftonly)) {
5838 $marginleftonlyshort = 0;
5839 }
5840
5841 // Add CSS
5842 $arrayconvpictotomorcess = array(
5843 'action' => 'infobox-action',
5844 'account' => 'infobox-bank_account',
5845 'accounting_account' => 'infobox-bank_account',
5846 'accountline' => 'infobox-bank_account',
5847 'accountancy' => 'infobox-bank_account',
5848 'admin' => 'opacitymedium',
5849 'asset' => 'infobox-bank_account',
5850 'bank_account' => 'infobox-bank_account',
5851 'bill' => 'infobox-commande',
5852 'billa' => 'infobox-commande',
5853 'billr' => 'infobox-commande',
5854 'billd' => 'infobox-commande',
5855 'bookcal' => 'infobox-portal',
5856 'margin' => 'infobox-bank_account',
5857 'conferenceorbooth' => 'infobox-project',
5858 'cash-register' => 'infobox-portal',
5859 'contract' => 'infobox-contrat',
5860 'check' => 'font-status4',
5861 'conversation' => 'infobox-contrat',
5862 'donation' => 'infobox-commande',
5863 'dolly' => 'infobox-commande',
5864 'dollyrevert' => 'flip infobox-order_supplier',
5865 'ecm' => 'infobox-action',
5866 'eventorganization' => 'infobox-project',
5867 'hrm' => 'infobox-adherent',
5868 'group' => 'infobox-adherent',
5869 'intervention' => 'infobox-contrat',
5870 'incoterm' => 'infobox-supplier_proposal',
5871 'intracommreport' => 'infobox-bank_account',
5872 'currency' => 'infobox-bank_account',
5873 'multicurrency' => 'infobox-bank_account',
5874 'members' => 'infobox-adherent',
5875 'member' => 'infobox-adherent',
5876 'money-bill-alt' => 'infobox-bank_account',
5877 'order' => 'infobox-commande',
5878 'user' => 'infobox-adherent',
5879 'users' => 'infobox-adherent',
5880 'error' => 'pictoerror',
5881 'warning' => 'pictowarning',
5882 'switch_on' => 'font-status4',
5883 'switch_on_warning' => 'font-status4 warning',
5884 'switch_on_red' => 'font-status8',
5885 'switch_off_warning' => 'font-status4 warning',
5886 'switch_off_red' => 'font-status8',
5887 'holiday' => 'infobox-holiday',
5888 'info' => 'opacityhigh',
5889 'info_black' => 'purple',
5890 'invoice' => 'infobox-commande',
5891 'knowledgemanagement' => 'infobox-contrat rotate90',
5892 'loan' => 'infobox-commande',
5893 'payment' => 'infobox-bank_account',
5894 'payment_vat' => 'infobox-bank_account',
5895 'poll' => 'infobox-portal',
5896 'pos' => 'infobox-bank_account',
5897 'project' => 'infobox-project',
5898 'projecttask' => 'infobox-project',
5899 'propal' => 'infobox-propal',
5900 'proposal' => 'infobox-propal',
5901 'private' => 'infobox-project',
5902 'reception' => 'flip infobox-order_supplier',
5903 'recruitmentjobposition' => 'infobox-adherent',
5904 'recruitmentcandidature' => 'infobox-adherent',
5905 'resource' => 'infobox-action',
5906 'salary' => 'infobox-commande',
5907 'shapes' => 'infobox-adherent',
5908 'shipment' => 'infobox-commande',
5909 'store' => 'infobox-portal',
5910 'stripe' => 'infobox-bank_account',
5911 'supplier_invoice' => 'infobox-order_supplier',
5912 'supplier_invoicea' => 'infobox-order_supplier',
5913 'supplier_invoiced' => 'infobox-order_supplier',
5914 'supplier_invoicer' => 'infobox-order_supplier',
5915 'supplier' => 'infobox-order_supplier',
5916 'supplier_order' => 'infobox-order_supplier',
5917 'supplier_proposal' => 'infobox-supplier_proposal',
5918 'ticket' => 'infobox-contrat',
5919 'title_accountancy' => 'infobox-bank_account',
5920 'title_hrm' => 'infobox-holiday',
5921 'expensereport' => 'infobox-expensereport',
5922 'trip' => 'infobox-expensereport',
5923 'title_agenda' => 'infobox-action',
5924 'vat' => 'infobox-bank_account',
5925 'webportal' => 'infobox-portal',
5926 'website' => 'infobox-portal',
5927 //'title_setup'=>'infobox-action', 'tools'=>'infobox-action',
5928 'list-alt' => 'imgforviewmode',
5929 'calendar' => 'imgforviewmode',
5930 'calendarweek' => 'imgforviewmode',
5931 'calendarmonth' => 'imgforviewmode',
5932 'calendarday' => 'imgforviewmode',
5933 'calendarperuser' => 'imgforviewmode',
5934 'calendarpertype' => 'imgforviewmode'
5935 );
5936 if (!empty($arrayconvpictotomorcess[$pictowithouttext]) && strpos($picto, '_nocolor') === false) {
5937 $morecss .= ($morecss ? ' ' : '') . $arrayconvpictotomorcess[$pictowithouttext];
5938 }
5939
5940 // Define $color
5941 $arrayconvpictotocolor = array(
5942 'address' => '#6c6aa8',
5943 'building' => '#6c6aa8',
5944 'bom' => '#a69944',
5945 'clone' => '#999',
5946 'cog' => '#999',
5947 'companies' => '#6c6aa8',
5948 'company' => '#6c6aa8',
5949 'contact' => '#6c6aa8',
5950 'cron' => '#555',
5951 'dynamicprice' => '#a69944',
5952 'edit' => '#444',
5953 'note' => '#999',
5954 'error' => '',
5955 'help' => '#bbb',
5956 'listlight' => '#999',
5957 'language' => '#555',
5958 //'dolly'=>'#a69944', 'dollyrevert'=>'#a69944',
5959 'lock' => '#ddd',
5960 'lot' => '#a69944',
5961 'map-marker-alt' => '#aaa',
5962 'mrp' => '#a69944',
5963 'product' => '#a69944',
5964 'service' => '#a69944',
5965 'inventory' => '#a69944',
5966 'stock' => '#a69944',
5967 'movement' => '#a69944',
5968 'other' => '#ddd',
5969 'world' => '#986c6a',
5970 'partnership' => '#6c6aa8',
5971 'playdisabled' => '#ccc',
5972 'printer' => '#444',
5973 'projectpub' => '#986c6a',
5974 'resize' => '#444',
5975 'rss' => '#cba',
5976 //'shipment'=>'#a69944',
5977 'search-plus' => '#808080',
5978 'security' => '#999',
5979 'square' => '#888',
5980 'stop-circle' => '#888',
5981 'stats' => '#444',
5982 'superadmin' => '#600',
5983 'switch_off' => '#999',
5984 'technic' => '#999',
5985 'tick' => '#282',
5986 'timespent' => '#555',
5987 'uncheck' => '#800',
5988 'uparrow' => '#555',
5989 'user-cog' => '#999',
5990 'country' => '#aaa',
5991 'globe-americas' => '#aaa',
5992 'region' => '#aaa',
5993 'state' => '#aaa',
5994 //'website' => '#304',
5995 'workstation' => '#a69944'
5996 );
5997 if (isset($arrayconvpictotocolor[$pictowithouttext]) && strpos($picto, '_nocolor') === false) {
5998 $facolor = $arrayconvpictotocolor[$pictowithouttext];
5999 }
6000
6001 // This snippet only needed since function img_edit accepts only one additional parameter: no separate one for css only.
6002 // class/style need to be extracted to avoid duplicate class/style validation errors when $moreatt is added to the end of the attributes.
6003 $morestyle = '';
6004 $reg = array();
6005 if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
6006 $morecss .= ($morecss ? ' ' : '') . $reg[1];
6007 $moreatt = str_replace('class="' . $reg[1] . '"', '', $moreatt);
6008 }
6009 if (preg_match('/style="([^"]+)"/', $moreatt, $reg)) {
6010 $morestyle = $reg[1];
6011 $moreatt = str_replace('style="' . $reg[1] . '"', '', $moreatt);
6012 }
6013 $moreatt = trim($moreatt);
6014
6015 $enabledisablehtml = '<span class="' . $fa . ' ' . $fakey . ($marginleftonlyshort ? ($marginleftonlyshort == 1 ? ' marginleftonlyshort' : ' marginleftonly') : '');
6016 $enabledisablehtml .= ($morecss ? ' ' . $morecss : '') . '" style="' . ($fasize ? ('font-size: ' . $fasize . ';') : '') . ($facolor ? (' color: ' . $facolor . ';') : '') . ($morestyle ? ' ' . $morestyle : '') . '"' . (($notitle || empty($titlealt)) ? '' : ' title="' . dol_escape_htmltag($titlealt) . '"') . ($moreatt ? ' ' . $moreatt : '') . '>';
6017 $enabledisablehtml .= '</span>';
6018
6019 return $enabledisablehtml;
6020 }
6021
6022 if (getDolGlobalString('MAIN_OVERWRITE_THEME_PATH')) {
6023 $path = getDolGlobalString('MAIN_OVERWRITE_THEME_PATH') . '/theme/' . $theme; // If the theme does not have the same name as the module
6024 } elseif (getDolGlobalString('MAIN_OVERWRITE_THEME_RES')) {
6025 $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
6026 } elseif (!empty($conf->modules_parts['theme']) && array_key_exists($theme, $conf->modules_parts['theme'])) {
6027 $path = $theme . '/theme/' . $theme; // If the theme have the same name as the module
6028 }
6029
6030 // If we ask an image into $url/$mymodule/img (instead of default path)
6031 $regs = array();
6032 if (preg_match('/^([^@]+)@([^@]+)$/i', $picto, $regs)) {
6033 $picto = $regs[1];
6034 $path = $regs[2]; // $path is $mymodule
6035 }
6036
6037 // Clean parameters
6038 if (!preg_match('/(\.png|\.gif|\.svg)$/i', $picto)) {
6039 $picto .= '.png';
6040 }
6041 // If alt path are defined, define url where img file is, according to physical path
6042 // ex: array(["main"]=>"/home/maindir/htdocs", ["alt0"]=>"/home/moddir0/htdocs", ...)
6043 foreach ($conf->file->dol_document_root as $type => $dirroot) {
6044 if ($type == 'main') {
6045 continue;
6046 }
6047 // This consumes a lot of time, that's why enabling alternative dir like "custom" dir should be avoid
6048 if (file_exists($dirroot . '/' . $path . '/img/' . $picto) && !empty($conf->file->dol_url_root)) {
6049 $url = DOL_URL_ROOT . $conf->file->dol_url_root[$type];
6050 break;
6051 }
6052 }
6053
6054 // $url is '' or '/custom', $path is current theme or
6055 $fullpathpicto = $url . '/' . $path . '/img/' . $picto;
6056 }
6057
6058 if ($srconly) {
6059 return $fullpathpicto;
6060 }
6061
6062 // tag title is used for tooltip on <a>, tag alt can be used with very simple text on image for blind people
6063 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
6064}
6065
6073function getImgPictoConv($mode = 'fa')
6074{
6075 global $conf;
6076
6077 if (empty($mode) || $mode == 'fa') {
6078 // Array when the fa picto key is different than the Dolibarr picto key.
6079 $arrayconvpictotofa = array(
6080 'account' => 'university',
6081 'accounting_account' => 'clipboard-list',
6082 'accountline' => 'receipt',
6083 'accountancy' => 'search-dollar',
6084 'action' => 'calendar-alt',
6085 'add' => 'plus-circle',
6086 'address' => 'address-book',
6087 'ai' => 'magic',
6088 'admin' => 'star',
6089 'asset' => 'money-check-alt',
6090 'autofill' => 'fill',
6091 'back' => 'arrow-left',
6092 'bank_account' => 'university',
6093 'bill' => 'file-invoice-dollar',
6094 'billa' => 'file-excel',
6095 'billr' => 'file-invoice-dollar',
6096 'billd' => 'file-medical',
6097 'blockedlog' => 'file-archive',
6098 'bookcal' => 'calendar-check',
6099 'supplier_invoice' => 'file-invoice-dollar',
6100 'supplier_invoicea' => 'file-excel',
6101 'supplier_invoicer' => 'file-invoice-dollar',
6102 'supplier_invoiced' => 'file-medical',
6103 'bom' => 'shapes',
6104 'card' => 'address-card',
6105 'chart' => 'chart-line',
6106 'company' => 'building',
6107 'contact' => 'address-book',
6108 'contract' => 'suitcase',
6109 'collab' => 'people-arrows',
6110 'conversation' => 'comments',
6111 'country' => 'globe-americas',
6112 'cron' => 'business-time',
6113 'cross' => 'times',
6114 'chevron-double-left' => 'angle-double-left',
6115 'chevron-double-right' => 'angle-double-right',
6116 'chevron-double-down' => 'angle-double-down',
6117 'chevron-double-top' => 'angle-double-up',
6118 'donation' => 'gift',
6119 'dynamicprice' => 'hand-holding-usd',
6120 'setup' => 'cog',
6121 'companies' => 'building',
6122 'products' => 'cube',
6123 'commercial' => 'suitcase',
6124 'invoicing' => 'coins',
6125 'accounting' => 'search-dollar',
6126 'category' => 'tag',
6127 'dollyrevert' => 'dolly',
6128 'file-o' => 'file',
6129 'generate' => 'plus-square',
6130 'hrm' => 'user-tie',
6131 'incoterm' => 'truck-loading',
6132 'margin' => 'calculator',
6133 'members' => 'user-friends',
6134 'ticket' => 'ticket-alt',
6135 'globe' => 'external-link-alt',
6136 'lot' => 'barcode',
6137 'email' => 'at',
6138 'establishment' => 'building',
6139 'edit' => 'pencil-alt',
6140 'entity' => 'globe',
6141 'graph' => 'chart-line',
6142 'grip_title' => 'arrows-alt',
6143 'grip' => 'arrows-alt',
6144 'help' => 'question-circle',
6145 'generic' => 'file',
6146 'holiday' => 'umbrella-beach',
6147 'info' => 'info-circle',
6148 'info_black' => 'info-circle',
6149 'inventory' => 'boxes',
6150 'intracommreport' => 'globe-europe',
6151 'jobprofile' => 'cogs',
6152 'knowledgemanagement' => 'ticket-alt',
6153 'label' => 'layer-group',
6154 'layout' => 'columns',
6155 'line' => 'bars',
6156 'loan' => 'money-bill-alt',
6157 'member' => 'user-alt',
6158 'meeting' => 'chalkboard-teacher',
6159 'mrp' => 'cubes',
6160 'next' => 'arrow-alt-circle-right',
6161 'trip' => 'wallet',
6162 'expensereport' => 'wallet',
6163 'group' => 'users',
6164 'movement' => 'people-carry',
6165 'sign-out' => 'sign-out-alt',
6166 'superadmin' => 'star',
6167 'switch_off' => 'toggle-off',
6168 'switch_off_grey' => 'toggle-off',
6169 'switch_off_warning' => 'toggle-off',
6170 'switch_off_red' => 'toggle-off',
6171 'switch_on' => 'toggle-on',
6172 'switch_on_grey' => 'toggle-on',
6173 'switch_on_warning' => 'toggle-on',
6174 'switch_on_red' => 'toggle-on',
6175 'check' => 'check',
6176 'bookmark' => 'star',
6177 'bank' => 'university',
6178 'close_title' => 'times',
6179 'delete' => 'trash',
6180 'filter' => 'filter',
6181 'list-alt' => 'list-alt',
6182 'calendarlist' => 'bars',
6183 'calendar' => 'calendar-alt',
6184 'calendarmonth' => 'calendar-alt',
6185 'calendarweek' => 'calendar-week',
6186 'calendarday' => 'calendar-day',
6187 'calendarperuser' => 'table',
6188 'calendarpertype' => 'table',
6189 'intervention' => 'ambulance',
6190 'invoice' => 'file-invoice-dollar',
6191 'order' => 'file-invoice',
6192 'error' => 'exclamation-triangle',
6193 'warning' => 'exclamation-triangle',
6194 'other' => 'square',
6195 'playdisabled' => 'play',
6196 'pdf' => 'file-pdf',
6197 'poll' => 'check-double',
6198 'pos' => 'cash-register',
6199 'preview' => 'binoculars',
6200 'project' => 'project-diagram',
6201 'projectpub' => 'project-diagram',
6202 'projecttask' => 'tasks',
6203 'propal' => 'file-signature',
6204 'proposal' => 'file-signature',
6205 'partnership' => 'handshake',
6206 'payment' => 'money-check-alt',
6207 'payment_vat' => 'money-check-alt',
6208 'pictoconfirm' => 'check-square',
6209 'phoning' => 'phone',
6210 'phoning_mobile' => 'mobile-alt',
6211 'phoning_fax' => 'fax',
6212 'previous' => 'arrow-alt-circle-left',
6213 'printer' => 'print',
6214 'product' => 'cube',
6215 'puce' => 'angle-right',
6216 'recent' => 'check-square',
6217 'reception' => 'dolly',
6218 'recruitmentjobposition' => 'id-card-alt',
6219 'recruitmentcandidature' => 'id-badge',
6220 'resize' => 'crop',
6221 'supplier_order' => 'dol-order_supplier',
6222 'supplier_proposal' => 'file-signature',
6223 'refresh' => 'redo',
6224 'region' => 'map-marked',
6225 'replacement' => 'exchange-alt',
6226 'resource' => 'laptop-house',
6227 'recurring' => 'history',
6228 'service' => 'concierge-bell',
6229 'skill' => 'shapes',
6230 'state' => 'map-marked-alt',
6231 'security' => 'key',
6232 'salary' => 'wallet',
6233 'shipment' => 'dolly',
6234 'stock' => 'box-open',
6235 'stats' => 'chart-bar',
6236 'split' => 'code-branch',
6237 'status' => 'stop-circle',
6238 'stripe' => 'stripe-s',
6239 'supplier' => 'building',
6240 'technic' => 'cogs',
6241 'tick' => 'check',
6242 'timespent' => 'clock',
6243 'title_setup' => 'tools',
6244 'title_accountancy' => 'money-check-alt',
6245 'title_bank' => 'university',
6246 'title_hrm' => 'umbrella-beach',
6247 'title_agenda' => 'calendar-alt',
6248 'uncheck' => 'times',
6249 'uparrow' => 'share',
6250 'url' => 'external-link-alt',
6251 'vat' => 'money-check-alt',
6252 'vcard' => 'arrow-alt-circle-down',
6253 'jabber' => 'comment',
6254 'website' => 'globe-americas',
6255 'workstation' => 'pallet',
6256 'webhook' => 'bullseye',
6257 'world' => 'globe',
6258 'private' => 'user-lock',
6259 'conferenceorbooth' => 'chalkboard-teacher',
6260 'eventorganization' => 'project-diagram',
6261 'webportal' => 'door-open'
6262 );
6263
6264 if ($conf->currency == 'EUR') {
6265 $arrayconvpictotofa['currency'] = 'euro-sign';
6266 $arrayconvpictotofa['multicurrency'] = 'dollar-sign';
6267 } else {
6268 $arrayconvpictotofa['currency'] = 'dollar-sign';
6269 $arrayconvpictotofa['multicurrency'] = 'euro-sign';
6270 }
6271 } else {
6272 $arrayconvpictotofa = array();
6273 }
6274
6275 return $arrayconvpictotofa;
6276}
6277
6278
6293function img_object($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0, $srconly = 0, $notitle = 0, $allowothertags = array())
6294{
6295 if (strpos($picto, '^') === 0) {
6296 return img_picto($titlealt, str_replace('^', '', $picto), $moreatt, $pictoisfullpath, $srconly, $notitle, '', '', 2, $allowothertags);
6297 } else {
6298 return img_picto($titlealt, 'object_' . $picto, $moreatt, $pictoisfullpath, $srconly, $notitle, '', '', 2, $allowothertags);
6299 }
6300}
6301
6313function img_weather($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0, $morecss = '')
6314{
6315 global $conf;
6316
6317 if (is_numeric($picto)) {
6318 //$leveltopicto = array(0=>'weather-clear.png', 1=>'weather-few-clouds.png', 2=>'weather-clouds.png', 3=>'weather-many-clouds.png', 4=>'weather-storm.png');
6319 //$picto = $leveltopicto[$picto];
6320 return '<i class="fa fa-weather-level' . $picto . '"></i>';
6321 } elseif (!preg_match('/(\.png|\.gif)$/i', $picto)) {
6322 $picto .= '.png';
6323 }
6324
6325 $path = DOL_URL_ROOT . '/theme/' . $conf->theme . '/img/weather/' . $picto;
6326
6327 return img_picto($titlealt, $path, $moreatt, 1, 0, 0, '', $morecss);
6328}
6329
6341function img_picto_common($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0, $notitle = 0)
6342{
6343 global $conf;
6344
6345 if (!preg_match('/(\.png|\.gif)$/i', $picto)) {
6346 $picto .= '.png';
6347 }
6348
6349 if ($pictoisfullpath) {
6350 $path = $picto;
6351 } else {
6352 $path = DOL_URL_ROOT . '/theme/common/' . $picto;
6353
6354 if (getDolGlobalInt('MAIN_MODULE_CAN_OVERWRITE_COMMONICONS')) {
6355 $themepath = DOL_DOCUMENT_ROOT . '/theme/' . $conf->theme . '/img/' . $picto;
6356
6357 if (file_exists($themepath)) {
6358 $path = $themepath;
6359 }
6360 }
6361 }
6362
6363 return img_picto($titlealt, $path, $moreatt, 1, 0, $notitle);
6364}
6365
6379function img_action($titlealt, $numaction, $picto = '', $moreatt = '')
6380{
6381 global $langs;
6382
6383 if (empty($titlealt) || $titlealt == 'default') {
6384 if ($numaction == '-1' || $numaction == 'ST_NO') {
6385 $numaction = -1;
6386 $titlealt = $langs->transnoentitiesnoconv('ChangeDoNotContact');
6387 } elseif ($numaction == '0' || $numaction == 'ST_NEVER') {
6388 $numaction = 0;
6389 $titlealt = $langs->transnoentitiesnoconv('ChangeNeverContacted');
6390 } elseif ($numaction == '1' || $numaction == 'ST_TODO') {
6391 $numaction = 1;
6392 $titlealt = $langs->transnoentitiesnoconv('ChangeToContact');
6393 } elseif ($numaction == '2' || $numaction == 'ST_PEND') {
6394 $numaction = 2;
6395 $titlealt = $langs->transnoentitiesnoconv('ChangeContactInProcess');
6396 } elseif ($numaction == '3' || $numaction == 'ST_DONE') {
6397 $numaction = 3;
6398 $titlealt = $langs->transnoentitiesnoconv('ChangeContactDone');
6399 } else {
6400 $titlealt = $langs->transnoentitiesnoconv('ChangeStatus ' . $numaction);
6401 $numaction = 0;
6402 }
6403 }
6404 if (!is_numeric($numaction)) {
6405 $numaction = 0;
6406 }
6407
6408 return img_picto($titlealt, (empty($picto) ? 'stcomm' . $numaction . '.png' : $picto), $moreatt);
6409}
6410
6418function img_edit_add($titlealt = 'default', $other = '')
6419{
6420 global $langs;
6421
6422 if ($titlealt == 'default') {
6423 $titlealt = $langs->trans('Add');
6424 }
6425
6426 return img_picto($titlealt, 'edit_add.png', $other);
6427}
6435function img_edit_remove($titlealt = 'default', $other = '')
6436{
6437 global $langs;
6438
6439 if ($titlealt == 'default') {
6440 $titlealt = $langs->trans('Remove');
6441 }
6442
6443 return img_picto($titlealt, 'edit_remove.png', $other);
6444}
6445
6454function img_edit($titlealt = 'default', $float = 0, $other = '')
6455{
6456 global $langs;
6457
6458 if ($titlealt == 'default') {
6459 $titlealt = $langs->trans('Modify');
6460 }
6461
6462 return img_picto($titlealt, 'edit', ($float ? 'style="float: ' . ($langs->tab_translate["DIRECTION"] == 'rtl' ? 'left' : 'right') . '"' : "") . ($other ? ' ' . $other : ''));
6463}
6464
6473function img_view($titlealt = 'default', $float = 0, $other = 'class="valignmiddle"')
6474{
6475 global $langs;
6476
6477 if ($titlealt == 'default') {
6478 $titlealt = $langs->trans('View');
6479 }
6480
6481 $moreatt = ($float ? 'style="float: right" ' : '') . $other;
6482
6483 return img_picto($titlealt, 'eye', $moreatt);
6484}
6485
6494function img_delete($titlealt = 'default', $other = 'class="pictodelete"', $morecss = '')
6495{
6496 global $langs;
6497
6498 if ($titlealt == 'default') {
6499 $titlealt = $langs->trans('Delete');
6500 }
6501
6502 return img_picto($titlealt, 'delete', $other, 0, 0, 0, '', $morecss);
6503}
6504
6512function img_printer($titlealt = "default", $other = '')
6513{
6514 global $langs;
6515 if ($titlealt == "default") {
6516 $titlealt = $langs->trans("Print");
6517 }
6518 return img_picto($titlealt, 'printer', $other);
6519}
6520
6528function img_split($titlealt = 'default', $other = 'class="pictosplit"')
6529{
6530 global $langs;
6531
6532 if ($titlealt == 'default') {
6533 $titlealt = $langs->trans('Split');
6534 }
6535
6536 return img_picto($titlealt, 'split', $other);
6537}
6538
6546function img_help($usehelpcursor = 1, $usealttitle = 1)
6547{
6548 global $langs;
6549
6550 if ($usealttitle) {
6551 if (is_string($usealttitle)) {
6552 $usealttitle = dol_escape_htmltag($usealttitle);
6553 } else {
6554 $usealttitle = $langs->trans('Info');
6555 }
6556 }
6557
6558 return img_picto($usealttitle, 'info', 'style="vertical-align: middle;' . ($usehelpcursor == 1 ? ' cursor: help' : ($usehelpcursor == 2 ? ' cursor: pointer' : '')) . '"');
6559}
6560
6567function img_info($titlealt = 'default')
6568{
6569 global $langs;
6570
6571 if ($titlealt == 'default') {
6572 $titlealt = $langs->trans('Informations');
6573 }
6574
6575 return img_picto($titlealt, 'info', 'style="vertical-align: middle;"');
6576}
6577
6586function img_warning($titlealt = 'default', $moreatt = '', $morecss = 'pictowarning')
6587{
6588 global $langs;
6589
6590 if ($titlealt == 'default') {
6591 $titlealt = $langs->trans('Warning');
6592 }
6593
6594 //return '<div class="imglatecoin">'.img_picto($titlealt, 'warning_white.png', 'class="pictowarning valignmiddle"'.($moreatt ? ($moreatt == '1' ? ' style="float: right"' : ' '.$moreatt): '')).'</div>';
6595 return img_picto($titlealt, 'warning', 'class="' . $morecss . '"' . ($moreatt ? ($moreatt == '1' ? ' style="float: right"' : ' ' . $moreatt) : ''));
6596}
6597
6604function img_error($titlealt = 'default')
6605{
6606 global $langs;
6607
6608 if ($titlealt == 'default') {
6609 $titlealt = $langs->trans('Error');
6610 }
6611
6612 return img_picto($titlealt, 'error');
6613}
6614
6622function img_next($titlealt = 'default', $moreatt = '')
6623{
6624 global $langs;
6625
6626 if ($titlealt == 'default') {
6627 $titlealt = $langs->trans('Next');
6628 }
6629
6630 //return img_picto($titlealt, 'next.png', $moreatt);
6631 return '<span class="fa fa-chevron-right paddingright paddingleft" title="' . dol_escape_htmltag($titlealt) . '"></span>';
6632}
6633
6641function img_previous($titlealt = 'default', $moreatt = '')
6642{
6643 global $langs;
6644
6645 if ($titlealt == 'default') {
6646 $titlealt = $langs->trans('Previous');
6647 }
6648
6649 //return img_picto($titlealt, 'previous.png', $moreatt);
6650 return '<span class="fa fa-chevron-left paddingright paddingleft" title="' . dol_escape_htmltag($titlealt) . '"></span>';
6651}
6652
6661function img_down($titlealt = 'default', $selected = 0, $moreclass = '')
6662{
6663 global $langs;
6664
6665 if ($titlealt == 'default') {
6666 $titlealt = $langs->trans('Down');
6667 }
6668
6669 return img_picto($titlealt, ($selected ? '1downarrow_selected' : '1downarrow'), 'class="imgdown' . ($moreclass ? " " . $moreclass : "") . '"');
6670}
6671
6680function img_up($titlealt = 'default', $selected = 0, $moreclass = '')
6681{
6682 global $langs;
6683
6684 if ($titlealt == 'default') {
6685 $titlealt = $langs->trans('Up');
6686 }
6687
6688 return img_picto($titlealt, ($selected ? '1uparrow_selected' : '1uparrow'), 'class="imgup' . ($moreclass ? " " . $moreclass : "") . '"');
6689}
6690
6699function img_left($titlealt = 'default', $selected = 0, $moreatt = '')
6700{
6701 global $langs;
6702
6703 if ($titlealt == 'default') {
6704 $titlealt = $langs->trans('Left');
6705 }
6706
6707 return img_picto($titlealt, ($selected ? '1leftarrow_selected' : '1leftarrow'), $moreatt);
6708}
6709
6718function img_right($titlealt = 'default', $selected = 0, $moreatt = '')
6719{
6720 global $langs;
6721
6722 if ($titlealt == 'default') {
6723 $titlealt = $langs->trans('Right');
6724 }
6725
6726 return img_picto($titlealt, ($selected ? '1rightarrow_selected' : '1rightarrow'), $moreatt);
6727}
6728
6736function img_allow($allow, $titlealt = 'default')
6737{
6738 global $langs;
6739
6740 if ($titlealt == 'default') {
6741 $titlealt = $langs->trans('Active');
6742 }
6743
6744 if ($allow == 1) {
6745 return img_picto($titlealt, 'tick');
6746 }
6747
6748 return '-';
6749}
6750
6758function img_credit_card($brand, $morecss = 'fa-2x inline-block valignmiddle')
6759{
6760 if (is_null($morecss)) {
6761 $morecss = 'fa-2x';
6762 }
6763
6764 if ($brand == 'visa' || $brand == 'Visa') {
6765 $brand = 'cc-visa';
6766 } elseif ($brand == 'mastercard' || $brand == 'MasterCard') {
6767 $brand = 'cc-mastercard';
6768 } elseif ($brand == 'amex' || $brand == 'American Express') {
6769 $brand = 'cc-amex';
6770 } elseif ($brand == 'discover' || $brand == 'Discover') {
6771 $brand = 'cc-discover';
6772 } elseif ($brand == 'jcb' || $brand == 'JCB') {
6773 $brand = 'cc-jcb';
6774 } elseif ($brand == 'diners' || $brand == 'Diners club') {
6775 $brand = 'cc-diners-club';
6776 } elseif (!in_array($brand, array('cc-visa', 'cc-mastercard', 'cc-amex', 'cc-discover', 'cc-jcb', 'cc-diners-club'))) {
6777 $brand = 'credit-card';
6778 }
6779
6780 return '<span class="fa fa-' . $brand . ' fa-fw' . ($morecss ? ' ' . $morecss : '') . '"></span>';
6781}
6782
6791function img_mime($file, $titlealt = '', $morecss = '')
6792{
6793 require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
6794
6795 $mimetype = dol_mimetype($file, '', 1);
6796 //$mimeimg = dol_mimetype($file, '', 2);
6797 $mimefa = dol_mimetype($file, '', 4);
6798
6799 if (empty($titlealt)) {
6800 $titlealt = 'Mime type: ' . $mimetype;
6801 }
6802
6803 //return img_picto_common($titlealt, 'mime/'.$mimeimg, 'class="'.$morecss.'"');
6804 return '<i class="fa fa-' . $mimefa . ' ' . (preg_match('/pictofixedwidth/', $morecss) ? '' : 'paddingright ') . ($morecss ? ' ' . $morecss : '') . '"' . ($titlealt ? ' title="' . dolPrintHTMLForAttribute($titlealt) . '"' : '') . '></i>';
6805}
6806
6807
6815function img_search($titlealt = 'default', $other = '')
6816{
6817 global $langs;
6818
6819 if ($titlealt == 'default') {
6820 $titlealt = $langs->trans('Search');
6821 }
6822
6823 $img = img_picto($titlealt, 'search', $other, 0, 1);
6824
6825 $input = '<input type="image" class="liste_titre" name="button_search" src="' . $img . '" ';
6826 $input .= 'value="' . dol_escape_htmltag($titlealt) . '" title="' . dol_escape_htmltag($titlealt) . '" >';
6827
6828 return $input;
6829}
6830
6838function img_searchclear($titlealt = 'default', $other = '')
6839{
6840 global $langs;
6841
6842 if ($titlealt == 'default') {
6843 $titlealt = $langs->trans('Search');
6844 }
6845
6846 $img = img_picto($titlealt, 'searchclear.png', $other, 0, 1);
6847
6848 $input = '<input type="image" class="liste_titre" name="button_removefilter" src="' . $img . '" ';
6849 $input .= 'value="' . dol_escape_htmltag($titlealt) . '" title="' . dol_escape_htmltag($titlealt) . '" >';
6850
6851 return $input;
6852}
6853
6867function info_admin($text, $infoonimgalt = 0, $nodiv = 0, $admin = '1', $morecss = 'hideonsmartphone', $textfordropdown = '', $picto = '', $textonpictotooltip = '')
6868{
6869 global $conf, $langs;
6870
6871 if ($infoonimgalt) {
6872 $result = img_picto($text, 'info', 'class="' . ($morecss ? ' ' . $morecss : '') . '"');
6873 } else {
6874 if (empty($conf->use_javascript_ajax)) {
6875 $textfordropdown = '';
6876 }
6877
6878 $class = (empty($admin) ? 'undefined' : ((string) $admin == '1' ? 'info' : $admin));
6879 $fa = 'info-circle';
6880 if ($picto == 'warning') {
6881 $fa = 'exclamation-triangle';
6882 }
6883 $result = ($nodiv ? '' : '<div class="wordbreak ' . $class . ($morecss ? ' ' . $morecss : '') . ($textfordropdown ? ' hidden' : '') . '">');
6884 $result .= img_picto(((string) $admin ? $langs->trans('InfoAdmin') : $langs->trans('Note')).($textonpictotooltip ? ' : '.$textonpictotooltip : ''), $fa);
6885 $result .= ' ';
6886 $result .= dol_escape_htmltag($text, 1, 0, 'div,span,b,br,a');
6887 $result .= ($nodiv ? '' : '</div>');
6888
6889 if ($textfordropdown) {
6890 $tmpresult = '<span class="' . $class . 'text opacitymedium cursorpointer">' . $langs->trans($textfordropdown) . ' ' . img_picto($langs->trans($textfordropdown), '1downarrow') . '</span>';
6891 $tmpresult .= '<script nonce="' . getNonce() . '" type="text/javascript">
6892 jQuery(document).ready(function() {
6893 jQuery(".' . $class . 'text").click(function() {
6894 console.log("toggle text");
6895 jQuery(".' . $class . '").toggle();
6896 });
6897 });
6898 </script>';
6899
6900 $result = $tmpresult . $result;
6901 }
6902 }
6903
6904 return $result;
6905}
6906
6907
6919function dol_print_error($db = null, $error = '', $errors = null)
6920{
6921 global $conf, $langs, $user, $argv;
6922 global $dolibarr_main_prod;
6923
6924 $out = '';
6925 $syslog = '';
6926
6927 // If error occurs before the $lang object was loaded
6928 if (!$langs) {
6929 require_once DOL_DOCUMENT_ROOT . '/core/class/translate.class.php';
6930 $langs = new Translate('', $conf);
6931 $langs->load("main");
6932 }
6933
6934 // Load translation files required by the error messages
6935 $langs->loadLangs(array('main', 'errors'));
6936
6937 if ($_SERVER['DOCUMENT_ROOT']) { // Mode web
6938 $out .= $langs->trans("DolibarrHasDetectedError") . ".<br>\n";
6939 if (getDolGlobalInt('MAIN_FEATURES_LEVEL') > 0) {
6940 $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";
6941 }
6942 $out .= $langs->trans("InformationToHelpDiagnose") . ":<br>\n";
6943
6944 $out .= "<b>" . $langs->trans("Date") . ":</b> " . dol_print_date(time(), 'dayhourlog') . "<br>\n";
6945 $out .= "<b>" . $langs->trans("Dolibarr") . ":</b> " . DOL_VERSION . " - https://www.dolibarr.org<br>\n";
6946 if (isset($conf->global->MAIN_FEATURES_LEVEL)) {
6947 $out .= "<b>" . $langs->trans("LevelOfFeature") . ":</b> " . getDolGlobalInt('MAIN_FEATURES_LEVEL') . "<br>\n";
6948 }
6949 if ($user instanceof User) {
6950 $out .= "<b>" . $langs->trans("Login") . ":</b> " . $user->login . "<br>\n";
6951 }
6952 if (function_exists("phpversion")) {
6953 $out .= "<b>" . $langs->trans("PHP") . ":</b> " . phpversion() . "<br>\n";
6954 }
6955 $out .= "<b>" . $langs->trans("Server") . ":</b> " . (isset($_SERVER["SERVER_SOFTWARE"]) ? dol_htmlentities($_SERVER["SERVER_SOFTWARE"], ENT_COMPAT) : '') . "<br>\n";
6956 if (function_exists("php_uname")) {
6957 $out .= "<b>" . $langs->trans("OS") . ":</b> " . php_uname() . "<br>\n";
6958 }
6959 $out .= "<b>" . $langs->trans("UserAgent") . ":</b> " . (isset($_SERVER["HTTP_USER_AGENT"]) ? dol_htmlentities($_SERVER["HTTP_USER_AGENT"], ENT_COMPAT) : '') . "<br>\n";
6960 $out .= "<br>\n";
6961 $out .= "<b>" . $langs->trans("RequestedUrl") . ":</b> " . (isset($_SERVER["REQUEST_URI"]) ? dol_htmlentities($_SERVER["REQUEST_URI"], ENT_COMPAT) : '') . "<br>\n";
6962 $out .= "<b>" . $langs->trans("Referer") . ":</b> " . (isset($_SERVER["HTTP_REFERER"]) ? dol_htmlentities($_SERVER["HTTP_REFERER"], ENT_COMPAT) : '') . "<br>\n";
6963 $out .= "<b>" . $langs->trans("MenuManager") . ":</b> " . (isset($conf->standard_menu) ? dol_htmlentities($conf->standard_menu, ENT_COMPAT) : '') . "<br>\n";
6964 $out .= "<br>\n";
6965 $syslog .= "url=" . (isset($_SERVER["REQUEST_URI"]) ? dol_escape_htmltag($_SERVER["REQUEST_URI"]) : '');
6966 $syslog .= ", query_string=" . (isset($_SERVER["QUERY_STRING"]) ? dol_escape_htmltag($_SERVER["QUERY_STRING"]) : '');
6967 } else { // Mode CLI
6968 $out .= '> ' . $langs->transnoentities("ErrorInternalErrorDetected") . ":\n" . $argv[0] . "\n";
6969 $syslog .= "pid=" . dol_getmypid();
6970 }
6971
6972 if (!empty($conf->modules)) {
6973 $out .= "<b>" . $langs->trans("Modules") . ":</b> " . implode(', ', $conf->modules) . "<br>\n";
6974 }
6975
6976 if (is_object($db)) {
6977 if ($_SERVER['DOCUMENT_ROOT']) { // Mode web
6978 $out .= "<b>" . $langs->trans("DatabaseTypeManager") . ":</b> " . $db->type . "<br>\n";
6979 $lastqueryerror = $db->lastqueryerror();
6980 if (!utf8_check($lastqueryerror)) {
6981 $lastqueryerror = "SQL error string is not a valid UTF8 string. We can't show it.";
6982 }
6983 $out .= "<b>" . $langs->trans("RequestLastAccessInError") . ":</b> " . ($lastqueryerror ? dol_escape_htmltag($lastqueryerror) : $langs->trans("ErrorNoRequestInError")) . "<br>\n";
6984 $out .= "<b>" . $langs->trans("ReturnCodeLastAccessInError") . ":</b> " . ($db->lasterrno() ? dol_escape_htmltag($db->lasterrno()) : $langs->trans("ErrorNoRequestInError")) . "<br>\n";
6985 $out .= "<b>" . $langs->trans("InformationLastAccessInError") . ":</b> " . ($db->lasterror() ? dol_escape_htmltag($db->lasterror()) : $langs->trans("ErrorNoRequestInError")) . "<br>\n";
6986 $out .= "<br>\n";
6987 } else { // Mode CLI
6988 // No dol_escape_htmltag for output, we are in CLI mode
6989 $out .= '> ' . $langs->transnoentities("DatabaseTypeManager") . ":\n" . $db->type . "\n";
6990 $out .= '> ' . $langs->transnoentities("RequestLastAccessInError") . ":\n" . ($db->lastqueryerror() ? $db->lastqueryerror() : $langs->transnoentities("ErrorNoRequestInError")) . "\n";
6991 $out .= '> ' . $langs->transnoentities("ReturnCodeLastAccessInError") . ":\n" . ($db->lasterrno() ? $db->lasterrno() : $langs->transnoentities("ErrorNoRequestInError")) . "\n";
6992 $out .= '> ' . $langs->transnoentities("InformationLastAccessInError") . ":\n" . ($db->lasterror() ? $db->lasterror() : $langs->transnoentities("ErrorNoRequestInError")) . "\n";
6993 }
6994 $syslog .= ", sql=" . $db->lastquery();
6995 $syslog .= ", db_error=" . $db->lasterror();
6996 }
6997
6998 if ($error || $errors) {
6999 // Merge all into $errors array
7000 if (is_array($error) && is_array($errors)) {
7001 $errors = array_merge($error, $errors);
7002 } elseif (is_array($error)) { // deprecated, use second parameters
7003 $errors = $error;
7004 } elseif (is_array($errors) && !empty($error)) {
7005 $errors = array_merge(array($error), $errors);
7006 } elseif (!empty($error)) {
7007 $errors = array_merge(array($error), array($errors));
7008 }
7009
7010 $langs->load("errors");
7011
7012 foreach ($errors as $msg) {
7013 if (empty($msg)) {
7014 continue;
7015 }
7016 if ($_SERVER['DOCUMENT_ROOT']) { // Mode web
7017 $out .= "<b>" . $langs->trans("Message") . ":</b> " . dol_escape_htmltag($msg) . "<br>\n";
7018 } else { // Mode CLI
7019 $out .= '> ' . $langs->transnoentities("Message") . ":\n" . $msg . "\n";
7020 }
7021 $syslog .= ", msg=" . $msg;
7022 }
7023 }
7024 if (empty($dolibarr_main_prod) && $_SERVER['DOCUMENT_ROOT'] && function_exists('xdebug_print_function_stack') && function_exists('xdebug_call_file')) {
7025 xdebug_print_function_stack();
7026 $out .= '<b>XDebug information:</b>' . "<br>\n";
7027 $out .= 'File: ' . xdebug_call_file() . "<br>\n";
7028 $out .= 'Line: ' . xdebug_call_line() . "<br>\n";
7029 $out .= 'Function: ' . xdebug_call_function() . "<br>\n";
7030 $out .= "<br>\n";
7031 }
7032
7033 // Return a http header with error code if possible
7034 if (!headers_sent()) {
7035 if (function_exists('top_httphead')) { // In CLI context, the method does not exists
7036 top_httphead();
7037 }
7038 //http_response_code(500); // If we use 500, message is not output with some command line tools
7039 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
7040 }
7041
7042 if (empty($dolibarr_main_prod)) {
7043 print $out;
7044 } else {
7045 if (empty($langs->defaultlang)) {
7046 $langs->setDefaultLang();
7047 }
7048 $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.
7049 // This should not happen, except if there is a bug somewhere. Enabled and check log in such case.
7050 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";
7051 print $langs->trans("DolibarrHasDetectedError") . '. ';
7052 print $langs->trans("YouCanSetOptionDolibarrMainProdToZero");
7053 if (!defined("MAIN_CORE_ERROR")) {
7054 define("MAIN_CORE_ERROR", 1);
7055 }
7056 }
7057
7058 dol_syslog("Error " . $syslog, LOG_ERR);
7059}
7060
7071function dol_print_error_email($prefixcode, $errormessage = '', $errormessages = array(), $morecss = 'error', $email = '')
7072{
7073 global $langs;
7074
7075 if (empty($email)) {
7076 $email = getDolGlobalString('MAIN_INFO_SOCIETE_MAIL');
7077 }
7078
7079 $langs->load("errors");
7080 $now = dol_now();
7081
7082 print '<br><div class="center login_main_message"><div class="' . $morecss . '">';
7083 print $langs->trans("ErrorContactEMail", $email, $prefixcode . '-' . dol_print_date($now, '%Y%m%d%H%M%S'));
7084 if ($errormessage) {
7085 print '<br><br>' . $errormessage;
7086 }
7087 if (is_array($errormessages) && count($errormessages)) {
7088 foreach ($errormessages as $mesgtoshow) {
7089 print '<br><br>' . $mesgtoshow;
7090 }
7091 }
7092 print '</div></div>';
7093}
7094
7111function print_liste_field_titre($name, $file = "", $field = "", $begin = "", $param = "", $moreattrib = "", $sortfield = "", $sortorder = "", $prefix = "", $tooltip = "", $forcenowrapcolumntitle = 0)
7112{
7113 print getTitleFieldOfList($name, 0, $file, $field, $begin, $param, $moreattrib, $sortfield, $sortorder, $prefix, 0, $tooltip, $forcenowrapcolumntitle);
7114}
7115
7134function getTitleFieldOfList($name, $thead = 0, $file = "", $field = "", $begin = "", $moreparam = "", $moreattrib = "", $sortfield = "", $sortorder = "", $prefix = "", $disablesortlink = 0, $tooltip = '', $forcenowrapcolumntitle = 0)
7135{
7136 global $langs, $form;
7137 //print "$name, $file, $field, $begin, $options, $moreattrib, $sortfield, $sortorder<br>\n";
7138
7139 if ($moreattrib == 'class="right"') {
7140 $prefix .= 'right '; // For backward compatibility
7141 }
7142
7143 $tooltip = (string) $tooltip; // In case $tooltip is null
7144
7145 $sortorder = strtoupper((string) $sortorder);
7146 $out = '';
7147 $sortimg = '';
7148
7149 $tag = 'th';
7150 if ($thead == 2) {
7151 $tag = 'div';
7152 }
7153
7154 $tmpsortfield = explode(',', (string) $sortfield);
7155 $sortfield1 = trim($tmpsortfield[0]); // If $sortfield is 'd.datep,d.id', it becomes 'd.datep'
7156 $tmpfield = explode(',', $field);
7157 $field1 = trim($tmpfield[0]); // If $field is 'd.datep,d.id', it becomes 'd.datep'
7158
7159 if (strpos((string) $tooltip, ':') !== false) {
7160 $tmptooltip = explode(':', (string) $tooltip);
7161 } else {
7162 $tmptooltip = array($tooltip);
7163 }
7164
7165 $wrapcolumntitle = (empty($forcenowrapcolumntitle) || (!empty($tmptooltip[2]) && $tmptooltip[2] == '-1'));
7166
7167 if (!getDolGlobalString('MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE') && $wrapcolumntitle) {
7168 $prefix = 'wrapcolumntitle ' . $prefix;
7169 }
7170
7171 //var_dump('field='.$field.' field1='.$field1.' sortfield='.$sortfield.' sortfield1='.$sortfield1);
7172 // If field is used as sort criteria we use a specific css class liste_titre_sel
7173 // Example if (sortfield,field)=("nom","xxx.nom") or (sortfield,field)=("nom","nom")
7174 $liste_titre = 'liste_titre';
7175 if ($field1 && ($sortfield1 == $field1 || $sortfield1 == preg_replace("/^[^\.]+\./", "", $field1))) {
7176 $liste_titre = 'liste_titre_sel';
7177 }
7178
7179 $tagstart = '<' . $tag . ' class="' . $prefix . $liste_titre . '" ' . $moreattrib;
7180 //$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)).'"' : '');
7181 $tagstart .= ($name && !getDolGlobalString('MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE') && $wrapcolumntitle && !dol_textishtml($name)) ? ' title="' . dolPrintHTMLForAttribute($langs->trans($name)) . '"' : '';
7182 $tagstart .= '>';
7183
7184 if (empty($thead) && $field && empty($disablesortlink)) { // If this is a sort field
7185 $options = preg_replace('/sortfield=([a-zA-Z0-9,\s\.]+)/i', '', (is_scalar($moreparam) ? $moreparam : ''));
7186 $options = preg_replace('/sortorder=([a-zA-Z0-9,\s\.]+)/i', '', $options);
7187 $options = preg_replace('/&+/i', '&', $options);
7188 if (!preg_match('/^&/', $options)) {
7189 $options = '&' . $options;
7190 }
7191
7192 $sortordertouseinlink = '';
7193 if ($field1 != $sortfield1) { // We are on another field than current sorted field
7194 if (preg_match('/^DESC/i', $sortorder)) {
7195 $sortordertouseinlink .= str_repeat('desc,', count(explode(',', $field)));
7196 } else { // We reverse the var $sortordertouseinlink
7197 $sortordertouseinlink .= str_repeat('asc,', count(explode(',', $field)));
7198 }
7199 } else { // We are on field that is the first current sorting criteria
7200 if (preg_match('/^ASC/i', $sortorder)) { // We reverse the var $sortordertouseinlink
7201 $sortordertouseinlink .= str_repeat('desc,', count(explode(',', $field)));
7202 } else {
7203 $sortordertouseinlink .= str_repeat('asc,', count(explode(',', $field)));
7204 }
7205 }
7206 $sortordertouseinlink = preg_replace('/,$/', '', $sortordertouseinlink);
7207 $out .= '<a class="reposition" href="' . dolBuildUrl($file, ['sortfield' => $field, 'sortorder' => $sortordertouseinlink, 'begin' => $begin]) . $options . '"';
7208 //$out .= (getDolGlobalString('MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE') ? '' : ' title="'.dol_escape_htmltag($langs->trans($name)).'"');
7209 $out .= '>';
7210 }
7211 if ($tooltip && $tmptooltip[0]) {
7212 // You can also use 'TranslationString:[keyfortooltiponclick]:[tooltipdirection]' for a tooltip on click or to change tooltip position.
7213 $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]));
7214 } else {
7215 $out .= $langs->trans((string) $name);
7216 }
7217
7218 if (empty($thead) && $field && empty($disablesortlink)) { // If this is a sort field
7219 $out .= '</a>';
7220 }
7221
7222 if (empty($thead) && $field) { // If this is a sort field
7223 $options = preg_replace('/sortfield=([a-zA-Z0-9,\s\.]+)/i', '', (is_scalar($moreparam) ? $moreparam : ''));
7224 $options = preg_replace('/sortorder=([a-zA-Z0-9,\s\.]+)/i', '', $options);
7225 $options = preg_replace('/&+/i', '&', $options);
7226 if (!preg_match('/^&/', $options)) {
7227 $options = '&' . $options;
7228 }
7229
7230 if (!$sortorder || ($field1 != $sortfield1)) {
7231 // Nothing
7232 } else {
7233 if (preg_match('/^DESC/', $sortorder)) {
7234 $sortimg .= '<span class="nowrap">' . img_up("Z-A", 0, 'paddingright') . '</span>';
7235 }
7236 if (preg_match('/^ASC/', $sortorder)) {
7237 $sortimg .= '<span class="nowrap">' . img_down("A-Z", 0, 'paddingright') . '</span>';
7238 }
7239 }
7240 }
7241
7242 $tagend = '</' . $tag . '>';
7243
7244 $out = $tagstart . $sortimg . $out . $tagend;
7245
7246 return $out;
7247}
7248
7257function print_titre($title)
7258{
7259 dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING);
7260
7261 print '<div class="titre">' . $title . '</div>';
7262}
7263
7275function print_fiche_titre($title, $mesg = '', $picto = 'generic', $pictoisfullpath = 0, $id = '')
7276{
7277 print load_fiche_titre($title, $mesg, $picto, $pictoisfullpath, $id);
7278}
7279
7294function load_fiche_titre($title, $morehtmlright = '', $picto = 'generic', $pictoisfullpath = 0, $id = '', $morecssontable = '', $morehtmlcenter = '', $morecssonpicto = 'widthpictotitle')
7295{
7296 $return = '';
7297
7298 if ($picto == 'setup') {
7299 $picto = 'generic';
7300 }
7301
7302 $return .= "\n";
7303 $return .= '<table ' . ($id ? 'id="' . $id . '" ' : '') . 'class="centpercent notopnoleftnoright table-fiche-title' . ($morecssontable ? ' ' . $morecssontable : '') . '">'; // margin bottom must be same than into print_barre_list
7304 $return .= '<tr class="toptitle">';
7305 if ($picto) {
7306 $return .= '<td class="nobordernopadding widthpictotitle valignmiddle col-picto">' . img_picto('', $picto, 'class="valignmiddle pictotitle'.($morecssonpicto ? ' '.$morecssonpicto : '').'"', $pictoisfullpath) . '</td>';
7307 }
7308 $return .= '<td class="nobordernopadding valignmiddle col-title">';
7309 $return .= '<div class="titre inline-block">';
7310 $return .= '<span class="inline-block valignmiddle print-barre-liste">' . $title . '</span>'; // $title is already HTML sanitized content
7311 $return .= '</div>';
7312 $return .= '</td>';
7313 if (dol_strlen($morehtmlcenter)) {
7314 $return .= '<td class="nobordernopadding center valignmiddle col-center">' . $morehtmlcenter . '</td>';
7315 }
7316 if (dol_strlen($morehtmlright)) {
7317 $return .= '<td class="nobordernopadding titre_right wordbreakimp right valignmiddle col-right">' . $morehtmlright . '</td>';
7318 }
7319 $return .= '</tr></table>' . "\n";
7320
7321 return $return;
7322}
7323
7347function 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 = '')
7348{
7349 global $conf, $langs;
7350
7351 $savlimit = $limit;
7352 $savtotalnboflines = $totalnboflines;
7353 if (is_numeric($totalnboflines)) {
7354 $totalnboflines = abs($totalnboflines);
7355 }
7356
7357 // Detect if there is a subtitle
7358 $subtitle = '';
7359 $tmparray = preg_split('/<br>/i', $title, 2);
7360 if (!empty($tmparray[1])) {
7361 $title = $tmparray[0];
7362 $subtitle = $tmparray[1];
7363 }
7364
7365 $page = (int) $page;
7366
7367 if ($picto == 'setup') {
7368 $picto = 'title_setup';
7369 }
7370 if (($conf->browser->name == 'ie') && $picto == 'generic') {
7371 $picto = 'title.gif';
7372 }
7373 if ($limit < 0) {
7374 $limit = $conf->liste_limit;
7375 }
7376
7377 if ($savlimit != 0 && (($num > $limit) || ($num == -1) || ($limit == 0))) {
7378 $nextpage = 1;
7379 } else {
7380 $nextpage = 0;
7381 }
7382 //print 'totalnboflines='.$totalnboflines.'-savlimit='.$savlimit.'-limit='.$limit.'-num='.$num.'-nextpage='.$nextpage.'-selectlimitsuffix='.$selectlimitsuffix.'-hidenavigation='.$hidenavigation;
7383
7384 print "\n";
7385 print "<!-- Begin print_barre_liste -->\n";
7386 print '<table class="centpercent notopnoleftnoright table-fiche-title' . ($morecss ? ' ' . $morecss : '') . '">';
7387 print '<tr class="toptitle">'; // margin bottom must be same than into load_fiche_tire
7388
7389 // Left
7390
7391 if ($picto && $title) {
7392 print '<td class="nobordernopadding widthpictotitle valignmiddle col-picto">';
7393 print img_picto('', $picto, 'class="valignmiddle pictotitle widthpictotitle"', $pictoisfullpath);
7394 print '</td>';
7395 }
7396
7397 print '<td class="nobordernopadding valignmiddle col-title">';
7398 print '<div class="titre inline-block nowrap">';
7399 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()
7400 if (!empty($title) && $savtotalnboflines >= 0 && (string) $savtotalnboflines != '') {
7401 if (is_numeric($totalnboflines) && (int) $totalnboflines > 0) {
7402 print '<span class="opacitymedium colorblack marginleftonly totalnboflines valignmiddle" title="' . $langs->trans("NbRecordQualified") . '">(' . $totalnboflines . ')</span>';
7403 } else {
7404 print '<span class="opacitymedium colorblack marginleftonly totalnboflines valignmiddle">(' . $totalnboflines . ')</span>';
7405 }
7406 }
7407 print '</div>';
7408 if (!empty($subtitle)) {
7409 print '<br><div class="subtitle inline-block hideonsmartphone">' . $subtitle . '</div>';
7410 }
7411 print '</td>';
7412
7413 // Center
7414 if ($morehtmlcenter && empty($conf->dol_optimize_smallscreen)) {
7415 print '<td class="nobordernopadding center valignmiddle col-center">' . $morehtmlcenter . '</td>';
7416 }
7417
7418 // Right
7419 print '<td class="nobordernopadding valignmiddle right col-right">';
7420 print '<input type="hidden" name="pageplusoneold" value="' . ((int) $page + 1) . '">';
7421 $query = [];
7422 parse_str($options, $query);
7423 if ($sortfield) {
7424 $query += ['sortfield' => $sortfield];
7425 }
7426 if ($sortorder) {
7427 $query += ['sortorder' => $sortorder];
7428 }
7429
7430 $options = '&' . http_build_query($query);
7431 if ($page) {
7432 $query = array_merge($query, ['page' => $page]);
7433 }
7434 // Show navigation bar
7435 $pagelist = '';
7436 if ($savlimit != 0 && ($page > 0 || $num > $limit)) {
7437 if ($totalnboflines) { // If we know total nb of lines
7438 // Define nb of extra page links before and after selected page + ... + first or last
7439 $maxnbofpage = (empty($conf->dol_optimize_smallscreen) ? 4 : 0);
7440
7441 if ($limit > 0) {
7442 $nbpages = ceil($totalnboflines / $limit);
7443 } else {
7444 $nbpages = 1;
7445 }
7446 $cpt = ($page - $maxnbofpage);
7447 if ($cpt < 0) {
7448 $cpt = 0;
7449 }
7450
7451 if ($cpt >= 1) {
7452 if (empty($pagenavastextinput)) {
7453 $query['page'] = 0;
7454 $pagelist .= '<li class="pagination"><a class="reposition" href="' . dolBuildUrl($file, $query) . '">1</a></li>';
7455 if ($cpt > 2) {
7456 $pagelist .= '<li class="pagination"><span class="inactive">...</span></li>';
7457 } elseif ($cpt == 2) {
7458 $query['page'] = 0;
7459 $pagelist .= '<li class="pagination"><a class="reposition" href="' . dolBuildUrl($file, $query) . '">2</a></li>';
7460 }
7461 }
7462 }
7463
7464 do {
7465 if ($pagenavastextinput) {
7466 if ($cpt == $page) {
7467 $pagelist .= '<li class="pagination pageplusone valignmiddle"><input type="text" class="' . ($totalnboflines > 100 ? 'width40' : 'width25') . ' center pageplusone heightofcombo" name="pageplusone" value="' . ($page + 1) . '"></li>';
7468 $pagelist .= '/';
7469 }
7470 } else {
7471 if ($cpt == $page) {
7472 $pagelist .= '<li class="pagination"><span class="active">' . ($page + 1) . '</span></li>';
7473 } else {
7474 $query['page'] = $cpt;
7475 $pagelist .= '<li class="pagination"><a class="reposition" href="' . dolBuildUrl($file, $query) . '">' . ($cpt + 1) . '</a></li>';
7476 }
7477 }
7478 $cpt++;
7479 } while ($cpt < $nbpages && $cpt <= ($page + $maxnbofpage));
7480
7481 if (empty($pagenavastextinput)) {
7482 if ($cpt < $nbpages) {
7483 if ($cpt < $nbpages - 2) {
7484 $pagelist .= '<li class="pagination"><span class="inactive">...</span></li>';
7485 } elseif ($cpt == $nbpages - 2) {
7486 $query['page'] = ($nbpages - 2);
7487 $pagelist .= '<li class="pagination"><a class="reposition" href="' . dolBuildUrl($file, $query) . '">' . ($nbpages - 1) . '</a></li>';
7488 }
7489 $query['page'] = ($nbpages - 1);
7490 $pagelist .= '<li class="pagination"><a class="reposition" href="' . dolBuildUrl($file, $query) . '">' . $nbpages . '</a></li>';
7491 }
7492 } else {
7493 $query['page'] = ($nbpages - 1);
7494 $pagelist .= '<li class="pagination paginationlastpage"><a class="reposition" href="' . dolBuildUrl($file, $query) . '">' . $nbpages . '</a></li>';
7495 }
7496 } else {
7497 $pagelist .= '<li class="pagination"><span class="active">' . ($page + 1) . "</li>";
7498 }
7499 }
7500
7501 if ($savlimit || $morehtmlright || $morehtmlrightbeforearrow) {
7502 // Show the combolist to select number of record per page and the navigation arrows.
7503 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
7504 }
7505
7506 // js to autoselect page field on focus
7507 if ($pagenavastextinput) {
7508 print ajax_autoselect('.pageplusone');
7509 }
7510
7511 print '</td>';
7512 print '</tr>';
7513
7514 print "</table>\n";
7515
7516 // Center
7517 if ($morehtmlcenter && !empty($conf->dol_optimize_smallscreen)) {
7518 print '<div class="nobordernopadding marginbottomonly center valignmiddle col-center centpercent">' . $morehtmlcenter . '</div>';
7519 }
7520
7521 print "<!-- End title -->\n\n";
7522}
7523
7540function print_fleche_navigation($page, $file, $options = '', $nextpage = 0, $betweenarrows = '', $afterarrows = '', $limit = -1, $totalnboflines = 0, $selectlimitsuffix = '', $beforearrows = '', $hidenavigation = 0)
7541{
7542 global $conf, $langs;
7543
7544 print '<div class="pagination"><ul>';
7545 if ($beforearrows) {
7546 print '<li class="paginationbeforearrows">';
7547 print $beforearrows;
7548 print '</li>';
7549 }
7550
7551 if (empty($hidenavigation)) {
7552 if ((int) $limit > 0 && (empty($selectlimitsuffix) || !is_numeric($selectlimitsuffix))) {
7553 $pagesizechoices = '10:10,15:15,20:20,25:25,50:50,100:100,250:250,500:500,1000:1000';
7554 $pagesizechoices .= ',5000:5000';
7555 //$pagesizechoices .= ',10000:10000'; // Memory trouble on most browsers
7556 //$pagesizechoices .= ',20000:20000'; // Memory trouble on most browsers
7557 //$pagesizechoices .= ',0:'.$langs->trans("All"); // Not yet supported
7558 //$pagesizechoices .= ',2:2';
7559 if (getDolGlobalString('MAIN_PAGESIZE_CHOICES')) {
7560 $pagesizechoices = getDolGlobalString('MAIN_PAGESIZE_CHOICES');
7561 }
7562
7563 if (getDolGlobalString('MAIN_USE_HTML5_LIMIT_SELECTOR')) {
7564 print '<li class="pagination">';
7565 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 . '">';
7566 print '<datalist id="limitlist">';
7567 } else {
7568 print '<li class="paginationcombolimit valignmiddle">';
7569 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")) . '">';
7570 }
7571 $tmpchoice = explode(',', $pagesizechoices);
7572 $tmpkey = $limit . ':' . $limit;
7573 if (!in_array($tmpkey, $tmpchoice)) {
7574 $tmpchoice[$tmpkey] = $tmpkey;
7575 }
7576 $tmpkey = $conf->liste_limit . ':' . $conf->liste_limit;
7577 if (!in_array($tmpkey, $tmpchoice)) {
7578 $tmpchoice[$tmpkey] = $tmpkey;
7579 }
7580 asort($tmpchoice, SORT_NUMERIC);
7581 foreach ($tmpchoice as $val) {
7582 $selected = '';
7583 $tmp = explode(':', $val);
7584 $key = $tmp[0];
7585 $val = $tmp[1];
7586 if ($key != '' && $val != '') {
7587 if ((int) $key == (int) $limit) {
7588 $selected = ' selected="selected"';
7589 }
7590 print '<option name="' . $key . '"' . $selected . '>' . dol_escape_htmltag($val) . '</option>' . "\n";
7591 }
7592 }
7593 if (getDolGlobalString('MAIN_USE_HTML5_LIMIT_SELECTOR')) {
7594 print '</datalist>';
7595 } else {
7596 print '</select>';
7597 print ajax_combobox("limit" . (is_numeric($selectlimitsuffix) ? '' : $selectlimitsuffix), array(), 0, 0, 'resolve', '-1', 'limit');
7598 //print ajax_combobox("limit");
7599 }
7600
7601 if ($conf->use_javascript_ajax) {
7602 print '<!-- JS CODE TO ENABLE select limit to launch submit of page -->
7603 <script>
7604 jQuery(document).ready(function () {
7605 jQuery(".selectlimit").change(function() {
7606 console.log("We change limit so we submit the form");
7607 $(this).parents(\'form:first\').submit();
7608 });
7609 });
7610 </script>
7611 ';
7612 }
7613 print '</li>';
7614 }
7615 if ($page > 0) {
7616 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>';
7617 }
7618 if ($betweenarrows) {
7619 print '<!--<div class="betweenarrows nowraponall inline-block">-->';
7620 print $betweenarrows;
7621 print '<!--</div>-->';
7622 }
7623 if ($nextpage > 0) {
7624 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>';
7625 }
7626 if ($afterarrows) {
7627 print '<li class="paginationafterarrows">';
7628 print $afterarrows;
7629 print '</li>';
7630 }
7631 }
7632 print '</ul></div>' . "\n";
7633}
7634
7635
7647function vatrate($rate, $addpercent = false, $info_bits = 0, $usestarfornpr = 0, $html = 0)
7648{
7649 $morelabel = '';
7650
7651 if (preg_match('/%/', $rate)) {
7652 $rate = str_replace('%', '', $rate);
7653 $addpercent = true;
7654 }
7655 $reg = array();
7656 if (preg_match('/\‍((.*)\‍)/', $rate, $reg)) {
7657 $morelabel = ' (' . $reg[1] . ')';
7658 $rate = preg_replace('/\s*' . preg_quote($morelabel, '/') . '/', '', $rate);
7659 $morelabel = ' ' . ($html ? '<span class="opacitymedium small">' : '') . '(' . $reg[1] . ')' . ($html ? '</span>' : '');
7660 }
7661 if (preg_match('/\*/', $rate)) {
7662 $rate = str_replace('*', '', $rate);
7663 $info_bits |= 1;
7664 }
7665
7666 // If rate is '9/9/9' we don't change it. If rate is '9.000' we apply price()
7667 if (!preg_match('/\//', $rate)) {
7668 $ret = price($rate, 0, '', 0, 0) . ($addpercent ? '%' : '');
7669 } else {
7670 // TODO Split on / and output with a price2num to have clean numbers without ton of 000.
7671 $ret = $rate . ($addpercent ? '%' : '');
7672 }
7673 if (($info_bits & 1) && $usestarfornpr >= 0) {
7674 $ret .= ' *';
7675 }
7676 $ret .= $morelabel;
7677 return $ret;
7678}
7679
7680
7695function price($amount, $form = 0, $outlangs = '', $trunc = 1, $rounding = -1, $forcerounding = -1, $currency_code = '')
7696{
7697 global $langs, $conf;
7698
7699 // Clean parameters
7700 if (empty($amount)) {
7701 $amount = 0; // To have a numeric value if amount not defined or = ''
7702 }
7703 $amount = (is_numeric($amount) ? $amount : 0); // Check if amount is numeric, for example, an error occurred when amount value = o (letter) instead 0 (number)
7704 if ($rounding == -1) {
7705 $rounding = min(getDolGlobalString('MAIN_MAX_DECIMALS_UNIT'), getDolGlobalString('MAIN_MAX_DECIMALS_TOT'));
7706 }
7707 $nbdecimal = $rounding;
7708
7709 if ($outlangs === 'none') {
7710 // Use international separators
7711 $dec = '.';
7712 $thousand = '';
7713 } else {
7714 // Output separators by default (french)
7715 $dec = ',';
7716 $thousand = ' ';
7717
7718 // If $outlangs not forced, we use use language
7719 if (!($outlangs instanceof Translate)) {
7720 $outlangs = $langs;
7721 }
7722
7723 if ($outlangs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal") {
7724 $dec = $outlangs->transnoentitiesnoconv("SeparatorDecimal");
7725 }
7726 if ($outlangs->transnoentitiesnoconv("SeparatorThousand") != "SeparatorThousand") {
7727 $thousand = $outlangs->transnoentitiesnoconv("SeparatorThousand");
7728 }
7729 if ($thousand == 'None') {
7730 $thousand = '';
7731 } elseif ($thousand == 'Space') {
7732 $thousand = ' ';
7733 }
7734 }
7735 //print "outlangs=".$outlangs->defaultlang." amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
7736
7737 //print "amount=".$amount."-";
7738 $amount = str_replace(',', '.', $amount); // should be useless
7739 //print $amount."-";
7740 $data = explode('.', $amount);
7741 $decpart = isset($data[1]) ? $data[1] : '';
7742 $decpart = preg_replace('/0+$/i', '', $decpart); // Remove 0 at end of decimal part
7743 //print "decpart=".$decpart."<br>";
7744 $end = '';
7745
7746 // We increase nbdecimal if there is more decimal than asked (to not loose information)
7747 if (dol_strlen($decpart) > $nbdecimal) {
7748 $nbdecimal = dol_strlen($decpart);
7749 }
7750
7751 // If nbdecimal is higher than max to show
7752 $nbdecimalmaxshown = (int) str_replace('...', '', getDolGlobalString('MAIN_MAX_DECIMALS_SHOWN'));
7753 if ($trunc && $nbdecimal > $nbdecimalmaxshown) {
7754 $nbdecimal = $nbdecimalmaxshown;
7755 if (preg_match('/\.\.\./i', getDolGlobalString('MAIN_MAX_DECIMALS_SHOWN'))) {
7756 // If output is truncated, we show ...
7757 $end = '...';
7758 }
7759 }
7760
7761 // If force rounding
7762 if ((string) $forcerounding != '-1' && (string) $forcerounding != '') {
7763 if ($forcerounding === 'MU') {
7764 $nbdecimal = getDolGlobalInt('MAIN_MAX_DECIMALS_UNIT');
7765 } elseif ($forcerounding === 'MT') {
7766 $nbdecimal = getDolGlobalInt('MAIN_MAX_DECIMALS_TOT');
7767 } elseif ($forcerounding >= 0) {
7768 $nbdecimal = (int) $forcerounding;
7769 }
7770 }
7771
7772 // Format number
7773 $output = number_format((float) $amount, $nbdecimal, $dec, $thousand);
7774 // Add symbol of currency if requested
7775 $cursymbolbefore = $cursymbolafter = '';
7776 if ($currency_code && is_object($outlangs)) {
7777 if ($currency_code == 'auto') {
7778 $currency_code = $conf->currency;
7779 }
7780
7781 $listofcurrenciesbefore = array('AUD', 'CAD', 'CNY', 'COP', 'CLP', 'GBP', 'HKD', 'MXN', 'PEN', 'USD', 'CRC', 'ZAR');
7782 $listoflanguagesbefore = array('nl_NL');
7783 if (in_array($currency_code, $listofcurrenciesbefore) || in_array($outlangs->defaultlang, $listoflanguagesbefore)) {
7784 $cursymbolbefore .= $outlangs->getCurrencySymbol($currency_code);
7785 } else {
7786 $tmpcur = $outlangs->getCurrencySymbol($currency_code);
7787 $cursymbolafter .= ($tmpcur == $currency_code ? ' ' . $tmpcur : $tmpcur);
7788 }
7789 }
7790 if ($form) {
7791 $output = preg_replace('/\s/', '&nbsp;', $output);
7792 $output = $cursymbolbefore . $output . $end . ($cursymbolafter ? ' <span class="small">'.$cursymbolafter.'</span>' : '');
7793 $output = preg_replace('/\'/', '&#039;', $output);
7794 } else {
7795 $output = $cursymbolbefore . $output . $end . ($cursymbolafter ? ' '.$cursymbolafter : '');
7796 }
7797
7798 return $output;
7799}
7800
7826function price2num($amount, $rounding = '', $option = 0)
7827{
7828 global $langs;
7829
7830 // Clean parameters
7831 if (is_null($amount)) {
7832 $amount = '';
7833 }
7834
7835 // Round PHP function does not allow number like '1,234.56' nor '1.234,56' nor '1 234,56'
7836 // Numbers must be '1234.56'
7837 // Decimal delimiter for PHP and database SQL requests must be '.'
7838 $dec = ',';
7839 $thousand = ' ';
7840 if (is_null($langs)) { // $langs is not defined, we use english values.
7841 $dec = '.';
7842 $thousand = ',';
7843 } else {
7844 if ($langs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal") {
7845 $dec = $langs->transnoentitiesnoconv("SeparatorDecimal");
7846 }
7847 if ($langs->transnoentitiesnoconv("SeparatorThousand") != "SeparatorThousand") {
7848 $thousand = $langs->transnoentitiesnoconv("SeparatorThousand");
7849 }
7850 }
7851 if ($thousand == 'None') {
7852 $thousand = '';
7853 } elseif ($thousand == 'Space') {
7854 $thousand = ' ';
7855 }
7856 //print "amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
7857
7858 // Convert value to universal number format (no thousand separator, '.' as decimal separator)
7859 if ($option != 1) { // If not a PHP number or unknown, we change or clean format
7860 //print "\n".'PP'.$amount.' - '.$dec.' - '.$thousand.' - '.intval($amount).'<br>';
7861 if (!is_numeric($amount)) {
7862 $amount = preg_replace('/[a-zA-Z\/\\\*\‍(\‍)<>\_]/', '', $amount);
7863 }
7864
7865 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
7866 $amount = str_replace($thousand, '', $amount);
7867 }
7868
7869 // Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
7870 // to format defined by LC_NUMERIC after a calculation and we want source format to be like defined by Dolibarr setup.
7871 // So if number was already a good number, it is converted into local Dolibarr setup.
7872 if (is_numeric($amount)) {
7873 // We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
7874 $temps = sprintf("%10.10F", $amount - intval($amount)); // temps=0.0000000000 or 0.0000200000 or 9999.1000000000
7875 $temps = preg_replace('/([\.1-9])0+$/', '\\1', $temps); // temps=0. or 0.00002 or 9999.1
7876 $nbofdec = max(0, dol_strlen($temps) - 2); // -2 to remove "0."
7877 $amount = number_format($amount, $nbofdec, $dec, $thousand);
7878 }
7879 //print "QQ".$amount."<br>\n";
7880
7881 // Now make replaceents (the main goal of function)
7882
7883 if ($thousand != ',' && $thousand != '.') {
7884 // Accept the two types of decimal points french users (i.e., using ' ' for thousands)
7885
7886 // REGEX: Find the integral and decimal parts.
7887 //
7888 // We require that the decimal point only appears once in $amount.
7889 // The regex `/^(?<int>[^,]*,|[^.]*\.)(?<dec>[^.,]*)$/u` can be broken down as follows:
7890 // - `(?<int>[^,]*,|[^.]*\.)` is any accepted sequence up to the last potential decimal point '.' or ',' and named `int`.
7891 // It covers two cases:
7892 // - `[^,]*,`: Any sequence of characters that is not ',' with ',' accepted as the decimal point (from start of string because of earlier `^`);
7893 // - `[^.]*\.`: Any sequence of characters that is not a '.' with '.' accepted as the decimal point (from start of string.
7894 // - `(?<dec>[^.,]*)`: The sequence after the character accepted as the decimal point, not including it.
7895 $matches = array();
7896 if (preg_match('/^(?<int>[^,]*,|[^.]*\.)(?<dec>[^.,]*)$/u', $amount, $matches)) {
7897 $intPart = $matches['int'];
7898 $decPart = $matches['dec'];
7899
7900 // Remove all commas and dots from intPart
7901 $intPart = str_replace(['.', ','], '', $intPart);
7902
7903 // Combine intPart and decPart with a dot
7904 $amount = $intPart . $dec . $decPart;
7905 }
7906 }
7907
7908 $amount = str_replace(' ', '', $amount); // To avoid spaces
7909 $amount = str_replace($thousand, '', $amount); // Replace of thousand before replace of dec to avoid pb if thousand is .
7910 $amount = str_replace($dec, '.', $amount);
7911
7912 $amount = preg_replace('/[^0-9\-\.]/', '', $amount); // Clean non numeric chars (so it clean some UTF8 spaces for example.
7913 }
7914 //print ' XX'.$amount.' '.$rounding;
7915
7916 // Now, $amount is a real PHP float number. We make a rounding if required.
7917 if ($rounding) {
7918 $nbofdectoround = '';
7919 if ($rounding == 'MU') {
7920 $nbofdectoround = getDolGlobalInt('MAIN_MAX_DECIMALS_UNIT'); // usually 5
7921 } elseif ($rounding == 'MT') {
7922 $nbofdectoround = getDolGlobalInt('MAIN_MAX_DECIMALS_TOT'); // usually 2 or 3
7923 } elseif ($rounding == 'MS') {
7924 $nbofdectoround = getDolGlobalInt('MAIN_MAX_DECIMALS_STOCK', 5);
7925 } elseif ($rounding == 'CU') {
7926 $nbofdectoround = getDolGlobalInt('MAIN_MAX_DECIMALS_CURRENCY_UNIT', getDolGlobalInt('MAIN_MAX_DECIMALS_UNIT')); // TODO Use param of currency
7927 } elseif ($rounding == 'CT') {
7928 $nbofdectoround = getDolGlobalInt('MAIN_MAX_DECIMALS_CURRENCY_TOT', getDolGlobalInt('MAIN_MAX_DECIMALS_TOT')); // TODO Use param of currency
7929 } elseif (is_numeric($rounding)) {
7930 $nbofdectoround = (int) $rounding;
7931 }
7932
7933 //print " RR".$amount.' - '.$nbofdectoround.'<br>';
7934 if (dol_strlen($nbofdectoround)) {
7935 $amount = round(is_string($amount) ? (float) $amount : $amount, $nbofdectoround); // $nbofdectoround can be 0.
7936 } else {
7937 return 'ErrorBadParameterProvidedToFunction';
7938 }
7939 //print ' SS'.$amount.' - '.$nbofdec.' - '.$dec.' - '.$thousand.' - '.$nbofdectoround.'<br>';
7940
7941 // Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
7942 // to format defined by LC_NUMERIC after a calculation and we want source format to be defined by Dolibarr setup.
7943 if (is_numeric($amount)) {
7944 // We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
7945 $temps = sprintf("%10.10F", $amount - intval($amount)); // temps=0.0000000000 or 0.0000200000 or 9999.1000000000
7946 $temps = preg_replace('/([\.1-9])0+$/', '\\1', $temps); // temps=0. or 0.00002 or 9999.1
7947 $nbofdec = max(0, dol_strlen($temps) - 2); // -2 to remove "0."
7948 $amount = number_format($amount, min($nbofdec, $nbofdectoround), $dec, $thousand); // Convert amount to format with dolibarr dec and thousand
7949 }
7950 //print "TT".$amount.'<br>';
7951
7952 // Always make replace because each math function (like round) replace
7953 // with local values and we want a number that has a SQL string format x.y
7954 if ($thousand != ',' && $thousand != '.') {
7955 $amount = str_replace(',', '.', $amount); // To accept 2 notations for french users
7956 }
7957
7958 $amount = str_replace(' ', '', $amount); // To avoid spaces
7959 $amount = str_replace($thousand, '', $amount); // Replace of thousand before replace of dec to avoid pb if thousand is .
7960 $amount = str_replace($dec, '.', $amount);
7961
7962 $amount = preg_replace('/[^0-9\-\.]/', '', $amount); // Clean non numeric chars (so it clean some UTF8 spaces for example.
7963 }
7964
7965 return $amount;
7966}
7967
7980function showDimensionInBestUnit($dimension, $unit, $type, $outputlangs, $round = -1, $forceunitoutput = 'no', $use_short_label = 0)
7981{
7982 require_once DOL_DOCUMENT_ROOT . '/core/lib/product.lib.php';
7983
7984 if (($forceunitoutput == 'no' && $dimension < 1 / 10000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == -6)) {
7985 $dimension *= 1000000;
7986 $unit -= 6;
7987 } elseif (($forceunitoutput == 'no' && $dimension < 1 / 10 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == -3)) {
7988 $dimension *= 1000;
7989 $unit -= 3;
7990 } elseif (($forceunitoutput == 'no' && $dimension > 100000000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == 6)) {
7991 $dimension /= 1000000;
7992 $unit += 6;
7993 } elseif (($forceunitoutput == 'no' && $dimension > 100000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == 3)) {
7994 $dimension /= 1000;
7995 $unit += 3;
7996 }
7997 // Special case when we want output unit into pound or ounce
7998 /* TODO
7999 if ($unit < 90 && $type == 'weight' && is_numeric($forceunitoutput) && (($forceunitoutput == 98) || ($forceunitoutput == 99))
8000 {
8001 $dimension = // convert dimension from standard unit into ounce or pound
8002 $unit = $forceunitoutput;
8003 }
8004 if ($unit > 90 && $type == 'weight' && is_numeric($forceunitoutput) && $forceunitoutput < 90)
8005 {
8006 $dimension = // convert dimension from standard unit into ounce or pound
8007 $unit = $forceunitoutput;
8008 }*/
8009
8010 $ret = price($dimension, 0, $outputlangs, 0, 0, $round);
8011 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
8012 $ret .= ' ' . measuringUnitString(0, $type, $unit, $use_short_label, $outputlangs);
8013
8014 return $ret;
8015}
8016
8017
8030function get_localtax($vatrate, $local, $thirdparty_buyer = null, $thirdparty_seller = null, $vatnpr = 0)
8031{
8032 global $db, $conf, $mysoc;
8033
8034 if (empty($thirdparty_seller) || !is_object($thirdparty_seller)) {
8035 $thirdparty_seller = $mysoc;
8036 }
8037
8038 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);
8039
8040 $vatratecleaned = $vatrate;
8041 $reg = array();
8042 if (preg_match('/^(.*)\s*\‍((.*)\‍)$/', (string) $vatrate, $reg)) { // If vat is "xx (yy)"
8043 $vatratecleaned = trim($reg[1]);
8044 $vatratecode = $reg[2];
8045 }
8046
8047 /*if ($thirdparty_buyer->country_code != $thirdparty_seller->country_code)
8048 {
8049 return 0;
8050 }*/
8051
8052 // Some test to guess with no need to make database access
8053 if ($mysoc->country_code == 'ES') { // For spain localtaxes 1 and 2, tax is qualified if buyer use local tax
8054 if ($local == 1) {
8055 if (!$mysoc->localtax1_assuj || (string) $vatratecleaned == "0") {
8056 return 0;
8057 }
8058 if ($thirdparty_seller->id == $mysoc->id) {
8059 if (!$thirdparty_buyer->localtax1_assuj) {
8060 return 0;
8061 }
8062 } else {
8063 if (!$thirdparty_seller->localtax1_assuj) {
8064 return 0;
8065 }
8066 }
8067 }
8068
8069 if ($local == 2) {
8070 //if (! $mysoc->localtax2_assuj || (string) $vatratecleaned == "0") return 0;
8071 if (!$mysoc->localtax2_assuj) {
8072 return 0; // If main vat is 0, IRPF may be different than 0.
8073 }
8074 if ($thirdparty_seller->id == $mysoc->id) {
8075 if (!$thirdparty_buyer->localtax2_assuj) {
8076 return 0;
8077 }
8078 } else {
8079 if (!$thirdparty_seller->localtax2_assuj) {
8080 return 0;
8081 }
8082 }
8083 }
8084 } else {
8085 if ($local == 1 && !$thirdparty_seller->localtax1_assuj) {
8086 return 0;
8087 }
8088 if ($local == 2 && !$thirdparty_seller->localtax2_assuj) {
8089 return 0;
8090 }
8091 }
8092
8093 // For some country MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY is forced to on.
8094 if (in_array($mysoc->country_code, array('ES'))) {
8095 $conf->global->MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY = 1;
8096 }
8097
8098 // Search local taxes
8099 if (getDolGlobalString('MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY')) {
8100 if ($local == 1) {
8101 if ($thirdparty_seller != $mysoc) {
8102 if (!isOnlyOneLocalTax($local)) { // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
8103 return $thirdparty_seller->localtax1_value;
8104 }
8105 } else { // i am the seller
8106 if (!isOnlyOneLocalTax($local)) { // TODO If seller is me, why not always returning this, even if there is only one locatax vat.
8107 return getDolGlobalString('MAIN_INFO_VALUE_LOCALTAX1');
8108 }
8109 }
8110 }
8111 if ($local == 2) {
8112 if ($thirdparty_seller != $mysoc) {
8113 if (!isOnlyOneLocalTax($local)) { // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
8114 // TODO We should also return value defined on thirdparty only if defined
8115 return $thirdparty_seller->localtax2_value;
8116 }
8117 } else { // i am the seller
8118 if (in_array($mysoc->country_code, array('ES'))) {
8119 return $thirdparty_buyer->localtax2_value;
8120 } else {
8121 return getDolGlobalString('MAIN_INFO_VALUE_LOCALTAX2');
8122 }
8123 }
8124 }
8125 }
8126
8127 // By default, search value of local tax on line of common tax
8128 $sql = "SELECT t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
8129 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t, " . MAIN_DB_PREFIX . "c_country as c";
8130 $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '" . $db->escape($thirdparty_seller->country_code) . "'";
8131 $sql .= " AND t.taux = " . ((float) $vatratecleaned) . " AND t.active = 1";
8132 $sql .= " AND t.entity IN (" . getEntity('c_tva') . ")";
8133 if (!empty($vatratecode)) {
8134 $sql .= " AND t.code ='" . $db->escape($vatratecode) . "'"; // If we have the code, we use it in priority
8135 } else {
8136 $sql .= " AND t.recuperableonly = '" . $db->escape((string) $vatnpr) . "'";
8137 }
8138
8139 $resql = $db->query($sql);
8140
8141 if ($resql) {
8142 $obj = $db->fetch_object($resql);
8143 if ($obj) {
8144 if ($local == 1) {
8145 return $obj->localtax1;
8146 } elseif ($local == 2) {
8147 return $obj->localtax2;
8148 }
8149 }
8150 }
8151
8152 return 0;
8153}
8154
8155
8164function isOnlyOneLocalTax($local)
8165{
8166 $tax = get_localtax_by_third($local);
8167
8168 $valors = explode(":", $tax);
8169
8170 if (count($valors) > 1) {
8171 return false;
8172 } else {
8173 return true;
8174 }
8175}
8176
8183function get_localtax_by_third($local)
8184{
8185 global $db, $mysoc;
8186
8187 $sql = " SELECT t.localtax" . ((int) $local) . " as localtax";
8188 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t INNER JOIN " . MAIN_DB_PREFIX . "c_country as c ON c.rowid = t.fk_pays";
8189 $sql .= " WHERE c.code = '" . $db->escape($mysoc->country_code) . "' AND t.active = 1 AND t.entity IN (" . getEntity('c_tva') . ") AND t.taux = (";
8190 $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";
8191 $sql .= " WHERE c.code = '" . $db->escape($mysoc->country_code) . "' AND t.entity IN (" . getEntity('c_tva') . ") AND tt.active = 1)";
8192 $sql .= " AND t.localtax" . ((int) $local) . "_type <> '0'";
8193 $sql .= " ORDER BY t.rowid DESC";
8194
8195 $resql = $db->query($sql);
8196 if ($resql) {
8197 $obj = $db->fetch_object($resql);
8198 if ($obj) {
8199 return $obj->localtax;
8200 } else {
8201 return '0';
8202 }
8203 }
8204
8205 return 'Error';
8206}
8207
8208
8220function getTaxesFromId($vatrate, $buyer = null, $seller = null, $firstparamisid = 1)
8221{
8222 global $db;
8223
8224 dol_syslog("getTaxesFromId vat id or rate = " . $vatrate);
8225
8226 // Search local taxes
8227 $sql = "SELECT t.rowid, t.code, t.taux as rate, t.recuperableonly as npr, t.accountancy_code_sell, t.accountancy_code_buy,";
8228 $sql .= " t.localtax1, t.localtax1_type, t.localtax2, t.localtax2_type";
8229 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t";
8230 if ($firstparamisid) {
8231 $sql .= " WHERE t.rowid = " . (int) $vatrate;
8232 } else {
8233 $vatratecleaned = $vatrate;
8234 $vatratecode = '';
8235 $reg = array();
8236 if (preg_match('/^(.*)\s*\‍((.*)\‍)$/', $vatrate, $reg)) { // If vat is "xx (yy)"
8237 $vatratecleaned = $reg[1];
8238 $vatratecode = $reg[2];
8239 }
8240
8241 $sql .= ", " . MAIN_DB_PREFIX . "c_country as c";
8242 /*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 ??
8243 else $sql.= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($seller->country_code)."'";*/
8244 $sql .= " WHERE t.fk_pays = c.rowid";
8245 if (getDolGlobalString('SERVICE_ARE_ECOMMERCE_200238EC')) {
8246 $sql .= " AND c.code = '" . $db->escape($buyer->country_code) . "'";
8247 } else {
8248 $sql .= " AND c.code = '" . $db->escape($seller->country_code) . "'";
8249 }
8250 $sql .= " AND t.taux = " . ((float) $vatratecleaned) . " AND t.active = 1";
8251 $sql .= " AND t.entity IN (" . getEntity('c_tva') . ")";
8252 if ($vatratecode) {
8253 $sql .= " AND t.code = '" . $db->escape($vatratecode) . "'";
8254 }
8255 }
8256
8257 $resql = $db->query($sql);
8258 if ($resql) {
8259 $obj = $db->fetch_object($resql);
8260 if ($obj) {
8261 return array(
8262 'rowid' => $obj->rowid,
8263 'code' => $obj->code,
8264 'rate' => $obj->rate,
8265 'localtax1' => $obj->localtax1,
8266 'localtax1_type' => $obj->localtax1_type,
8267 'localtax2' => $obj->localtax2,
8268 'localtax2_type' => $obj->localtax2_type,
8269 'npr' => $obj->npr,
8270 'accountancy_code_sell' => $obj->accountancy_code_sell,
8271 'accountancy_code_buy' => $obj->accountancy_code_buy
8272 );
8273 } else {
8274 return array();
8275 }
8276 } else {
8278 }
8279
8280 return array();
8281}
8282
8299function getLocalTaxesFromRate($vatrate, $local, $buyer, $seller, $firstparamisid = 0)
8300{
8301 global $db, $mysoc;
8302
8303 dol_syslog("getLocalTaxesFromRate vatrate=" . $vatrate . " local=" . $local);
8304
8305 // Search local taxes
8306 $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";
8307 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t";
8308 if ($firstparamisid) {
8309 $sql .= " WHERE t.rowid = " . (int) $vatrate;
8310 } else {
8311 $vatratecleaned = $vatrate;
8312 $vatratecode = '';
8313 $reg = array();
8314 if (preg_match('/^(.*)\s*\‍((.*)\‍)$/', $vatrate, $reg)) { // If vat is "x.x (yy)"
8315 $vatratecleaned = $reg[1];
8316 $vatratecode = $reg[2];
8317 }
8318
8319 $sql .= ", " . MAIN_DB_PREFIX . "c_country as c";
8320 if (!empty($mysoc) && $mysoc->country_code == 'ES') {
8321 $countrycodetouse = ((empty($buyer) || empty($buyer->country_code)) ? $mysoc->country_code : $buyer->country_code);
8322 $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '" . $db->escape($countrycodetouse) . "'"; // local tax in spain use the buyer country ??
8323 } else {
8324 $countrycodetouse = ((empty($seller) || empty($seller->country_code)) ? $mysoc->country_code : $seller->country_code);
8325 $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '" . $db->escape($countrycodetouse) . "'";
8326 }
8327 $sql .= " AND t.taux = " . ((float) $vatratecleaned) . " AND t.active = 1";
8328 if ($vatratecode) {
8329 $sql .= " AND t.code = '" . $db->escape($vatratecode) . "'";
8330 }
8331 }
8332
8333 $resql = $db->query($sql);
8334 if ($resql) {
8335 $obj = $db->fetch_object($resql);
8336
8337 if ($obj) {
8338 $vateratestring = $obj->rate . ($obj->code ? ' (' . $obj->code . ')' : '');
8339
8340 if ($local == 1) {
8341 return array($obj->localtax1_type, get_localtax($vateratestring, $local, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
8342 } elseif ($local == 2) {
8343 return array($obj->localtax2_type, get_localtax($vateratestring, $local, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
8344 } else {
8345 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);
8346 }
8347 }
8348 }
8349
8350 return array();
8351}
8352
8363function get_product_vat_for_country($idprod, $thirdpartytouseforcountry, $idprodfournprice = 0)
8364{
8365 global $db, $mysoc;
8366
8367 require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
8368
8369 $ret = 0;
8370 $found = 0;
8371
8372 if ($idprod > 0) {
8373 // Load product
8374 $product = new Product($db);
8375 $product->fetch($idprod);
8376
8377 if (($mysoc->country_code == $thirdpartytouseforcountry->country_code)
8378 || (in_array($mysoc->country_code, array('FR', 'MC')) && in_array($thirdpartytouseforcountry->country_code, array('FR', 'MC')))
8379 || (in_array($mysoc->country_code, array('MQ', 'GP')) && in_array($thirdpartytouseforcountry->country_code, array('MQ', 'GP')))
8380 ) {
8381 // If country of thirdparty to consider is ours
8382 if ($idprodfournprice > 0) { // We want vat for product for a "supplier" object
8383 $result = $product->get_buyprice($idprodfournprice, 0, 0, '');
8384 if ($result > 0) {
8385 $ret = $product->vatrate_supplier;
8386 if ($product->default_vat_code_supplier) {
8387 $ret .= ' (' . $product->default_vat_code_supplier . ')';
8388 }
8389 $found = 1;
8390 }
8391 }
8392 if (!$found) {
8393 $ret = $product->tva_tx; // Default sales vat of product
8394 if ($product->default_vat_code) {
8395 $ret .= ' (' . $product->default_vat_code . ')';
8396 }
8397 $found = 1;
8398 }
8399 } else {
8400 // TODO Read default product vat according to product and an other countrycode.
8401 // Vat for couple anothercountrycode/product is data that is not managed and store yet, so we will fallback on next rule.
8402 }
8403 }
8404
8405 if (!$found) {
8406 if (!getDolGlobalString('MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS')) {
8407 // 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).
8408 $sql = "SELECT t.taux as vat_rate, t.code as default_vat_code";
8409 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t, " . MAIN_DB_PREFIX . "c_country as c";
8410 $sql .= " WHERE t.active = 1 AND t.fk_pays = c.rowid AND c.code = '" . $db->escape($thirdpartytouseforcountry->country_code) . "'";
8411 $sql .= " AND t.entity IN (" . getEntity('c_tva') . ")";
8412 $sql .= " ORDER BY t.use_default DESC, t.taux DESC, t.code ASC, t.recuperableonly ASC";
8413 $sql .= $db->plimit(1);
8414
8415 $resql = $db->query($sql);
8416 if ($resql) {
8417 $obj = $db->fetch_object($resql);
8418 if ($obj) {
8419 $ret = $obj->vat_rate;
8420 if ($obj->default_vat_code) {
8421 $ret .= ' (' . $obj->default_vat_code . ')';
8422 }
8423 }
8424 $db->free($resql);
8425 } else {
8427 }
8428 } else {
8429 // Forced value if autodetect fails. MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS can be
8430 // '1.23'
8431 // or '1.23 (CODE)'
8432 $defaulttx = '';
8433 if (getDolGlobalString('MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS') != 'none') {
8434 $defaulttx = getDolGlobalString('MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS');
8435 }
8436 /*if (preg_match('/\‍((.*)\‍)/', $defaulttx, $reg)) {
8437 $defaultcode = $reg[1];
8438 $defaulttx = preg_replace('/\s*\‍(.*\‍)/', '', $defaulttx);
8439 }*/
8440
8441 $ret = $defaulttx;
8442 }
8443 }
8444
8445 dol_syslog("get_product_vat_for_country: ret=" . $ret);
8446
8447 return $ret;
8448}
8449
8459function get_product_localtax_for_country($idprod, $local, $thirdpartytouseforcountry)
8460{
8461 global $db, $mysoc;
8462
8463 if (!class_exists('Product')) {
8464 require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
8465 }
8466
8467 $ret = 0;
8468 $found = 0;
8469
8470 if ($idprod > 0) {
8471 // Load product
8472 $product = new Product($db);
8473 $result = $product->fetch($idprod);
8474
8475 if ($mysoc->country_code == $thirdpartytouseforcountry->country_code) { // If selling country is ours
8476 /* Not defined yet, so we don't use this
8477 if ($local==1) $ret=$product->localtax1_tx;
8478 elseif ($local==2) $ret=$product->localtax2_tx;
8479 $found=1;
8480 */
8481 } else {
8482 // TODO Read default product vat according to product and another countrycode.
8483 // Vat for couple anothercountrycode/product is data that is not managed and store yet, so we will fallback on next rule.
8484 }
8485 }
8486
8487 if (!$found) {
8488 // If vat of product for the country not found or not defined, we return higher vat of country.
8489 $sql = "SELECT taux as vat_rate, localtax1, localtax2";
8490 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t, " . MAIN_DB_PREFIX . "c_country as c";
8491 $sql .= " WHERE t.active=1 AND t.fk_pays = c.rowid AND c.code='" . $db->escape($thirdpartytouseforcountry->country_code) . "'";
8492 $sql .= " AND t.entity IN (" . getEntity('c_tva') . ")";
8493 $sql .= " ORDER BY t.taux DESC, t.recuperableonly ASC";
8494 $sql .= $db->plimit(1);
8495
8496 $resql = $db->query($sql);
8497 if ($resql) {
8498 $obj = $db->fetch_object($resql);
8499 if ($obj) {
8500 if ($local == 1) {
8501 $ret = $obj->localtax1;
8502 } elseif ($local == 2) {
8503 $ret = $obj->localtax2;
8504 }
8505 }
8506 } else {
8508 }
8509 }
8510
8511 dol_syslog("get_product_localtax_for_country: ret=" . $ret);
8512 return $ret;
8513}
8514
8533function get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0)
8534{
8535 global $mysoc, $db, $hookmanager;
8536
8537 require_once DOL_DOCUMENT_ROOT . '/core/lib/company.lib.php';
8538
8539 // Note: possible values for tva_assuj are 0/1 or franchise/reel
8540 $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;
8541
8542 if (empty($thirdparty_seller->country_code)) {
8543 $thirdparty_seller->country_code = $mysoc->country_code;
8544 }
8545 $seller_country_code = $thirdparty_seller->country_code;
8546 $seller_in_cee = isInEEC($thirdparty_seller);
8547
8548 if (empty($thirdparty_buyer->country_code)) {
8549 $thirdparty_buyer->country_code = $mysoc->country_code;
8550 }
8551 $buyer_country_code = $thirdparty_buyer->country_code;
8552 $buyer_in_cee = isInEEC($thirdparty_buyer);
8553
8554 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'));
8555
8556 $vatvalue = 0;
8557 $vatrule = '';
8558
8559 // 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)
8560 // we use the buyer VAT.
8561 if (getDolGlobalString('SERVICE_ARE_ECOMMERCE_200238EC')) {
8562 if ($seller_in_cee && $buyer_in_cee) {
8563 $isacompany = $thirdparty_buyer->isACompany();
8564 if ($isacompany && !getDolGlobalString('MAIN_USE_VAT_ZERO_FOR_COMPANIES_IN_EEC_EVEN_IF_VAT_ID_UNKNOWN')) {
8565 require_once DOL_DOCUMENT_ROOT . '/core/lib/functions2.lib.php';
8566 if (!isValidVATID($thirdparty_buyer)) {
8567 $isacompany = 0;
8568 }
8569 }
8570
8571 if (!$isacompany) {
8572 $vatvalue = get_product_vat_for_country($idprod, $thirdparty_buyer, $idprodfournprice);
8573 $vatrule = 'VATRULE 0';
8574 }
8575 }
8576 }
8577
8578 // If seller does not use VAT, default VAT is 0. End of rule.
8579 if (empty($vatrule) && !$seller_use_vat) {
8580 //print 'VATRULE 1';
8581 // TODO get the VAT Code of exemption asked into setup if country isInEEC (from an array list of possible
8582 // values like VATEX-EU-132-*, VATEX-FR-FRANCHISE, VATEX-EU-AE...
8583 // When we had recorded it, we also added a corresponding entry into table of vat code if it does not exists yet.
8584 // Here we test if entry for the VAT exemption code exists in llx_vat, we can return '0 (VATEX-EU-132-xx)'
8585 // If not, we add it and we return '0 (VATEX-EU-132-xx)'
8586 $vatvalue = 0;
8587 $vatrule = 'VATRULE 1';
8588 }
8589
8590 // 'VATRULE 2' - Force VAT if a buyer department is defined on vat rates dictionary
8591 if (empty($vatrule) && !empty($thirdparty_buyer->state_id)) {
8592 $sql = "SELECT d.rowid, t.taux as vat_default_rate, t.code as vat_default_code ";
8593 $sql .= " FROM " . $db->prefix() . "c_tva as t";
8594 $sql .= " INNER JOIN " . $db->prefix() . "c_departements as d ON t.fk_department_buyer = d.rowid";
8595 $sql .= " WHERE d.rowid = " . ((int) $thirdparty_buyer->state_id);
8596 $sql .= " ORDER BY t.use_default DESC, t.taux DESC, t.code ASC, t.recuperableonly ASC";
8597
8598 $res = $db->query($sql);
8599 if ($res) {
8600 if ($db->num_rows($res)) {
8601 $obj = $db->fetch_object($res);
8602
8603 $vatvalue = $obj->vat_default_rate . ' (' . $obj->vat_default_code . ')';
8604 $vatrule = 'VATRULE 2';
8605 }
8606 $db->free($res);
8607 }
8608 }
8609
8610 // If the (seller country = buyer country) then the default VAT = VAT of the product sold. End of rule.
8611 if (empty($vatrule) && (
8612 ($seller_country_code == $buyer_country_code)
8613 || (in_array($seller_country_code, array('FR', 'MC')) && in_array($buyer_country_code, array('FR', 'MC')))
8614 || (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.
8615 )) { // Warning ->country_code not always defined
8616 //print 'VATRULE 3';
8617 $tmpvat = get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
8618
8619 if ($seller_country_code == 'IN' && getDolGlobalString('MAIN_SALETAX_AUTOSWITCH_I_CS_FOR_INDIA')) {
8620 // Special case for india.
8621 //print 'VATRULE 3b';
8622 $reg = array();
8623 if (preg_match('/C+S-(\d+)/', $tmpvat, $reg) && $thirdparty_seller->state_id != $thirdparty_buyer->state_id) {
8624 // we must revert the C+S into I
8625 $tmpvat = str_replace("C+S", "I", $tmpvat);
8626 } elseif (preg_match('/I-(\d+)/', $tmpvat, $reg) && $thirdparty_seller->state_id == $thirdparty_buyer->state_id) {
8627 // we must revert the I into C+S
8628 $tmpvat = str_replace("I", "C+S", $tmpvat);
8629 }
8630 }
8631
8632 $vatvalue = $tmpvat;
8633 $vatrule = 'VATRULE 3b';
8634 }
8635
8636 // 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.
8637 // 'VATRULE 4' - Not supported
8638
8639 // If (seller and buyer in the European Community) and (buyer = individual) then VAT by default = VAT of the product sold. End of rule
8640 // If (seller and buyer in European Community) and (buyer = company) then VAT by default=0. End of rule
8641 if (empty($vatrule) && ($seller_in_cee && $buyer_in_cee)) {
8642 $isacompany = $thirdparty_buyer->isACompany();
8643 if ($isacompany && !getDolGlobalString('MAIN_USE_VAT_ZERO_FOR_COMPANIES_IN_EEC_EVEN_IF_VAT_ID_UNKNOWN')) {
8644 require_once DOL_DOCUMENT_ROOT . '/core/lib/functions2.lib.php';
8645 if (!isValidVATID($thirdparty_buyer)) {
8646 $isacompany = 0;
8647 }
8648 }
8649
8650 if (!$isacompany) {
8651 //print 'VATRULE 5';
8652 $vatvalue = get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
8653 $vatrule = 'VATRULE 5';
8654 } else {
8655 //print 'VATRULE 6';
8656 // TODO This is the case of VAT exemption 'VATEX-EU-IC'
8657 // If entry for the VAT exemption code exists in llx_vat, we can return '0 (VATEX-EU-IC)'
8658 // If not, we add it and we return '0 (VATEX-EU-IC)'
8659 $vatvalue = 0;
8660 $vatrule = 'VATRULE 6';
8661 }
8662 }
8663
8664 // 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
8665 // 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
8666 if (empty($vatrule) && getDolGlobalString('MAIN_USE_VAT_OF_PRODUCT_FOR_INDIVIDUAL_CUSTOMER_OUT_OF_EEC') && empty($buyer_in_cee)) {
8667 $isacompany = $thirdparty_buyer->isACompany();
8668 if (!$isacompany) {
8669 $vatvalue = get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
8670 $vatrule = 'VATRULE extra';
8671 //print 'VATRULE extra';
8672 }
8673 }
8674
8675 // Otherwise the VAT proposed by default=0. End of rule.
8676 // Rem: This means that at least one of the 2 is outside the European Community and the country differs
8677 //print 'VATRULE 7';
8678 // TODO This is the case of VAT exemption 'VATEX-EU-G'
8679 // If entry for the VAT exemption code exists in llx_vat, we can return '0 (VATEX-xxx)'
8680 // If not, we add it and we return '0 (VATEX-xxx)'
8681
8682 // Allow an external module to bypass the calculation of prices
8683 $parameters = array('vatvalue' => $vatvalue, 'vatrule' => $vatrule);
8684 $tmpobject = null;
8685 $tmpaction = '';
8686 // @phan-suppress-next-line PhanPluginConstantVariableNull
8687 $reshook = $hookmanager->executeHooks('get_default_tva', $parameters, $tmpobject, $tmpaction); // @phan-suppress-current-line PhanPluginConstantVariableNull
8688 if ($reshook > 0 && !empty($hookmanager->resArray['vatvalue'])) {
8689 $vatvalue = $hookmanager->resArray['vatvalue'];
8690 $vatrule = $hookmanager->resArray['vatrule']; // For information
8691 }
8692
8693 return $vatvalue;
8694}
8695
8696
8707function get_default_npr(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0)
8708{
8709 global $db;
8710
8711 if ($idprodfournprice > 0) {
8712 if (!class_exists('ProductFournisseur')) {
8713 require_once DOL_DOCUMENT_ROOT . '/fourn/class/fournisseur.product.class.php';
8714 }
8715 $prodprice = new ProductFournisseur($db);
8716 $prodprice->fetch_product_fournisseur_price($idprodfournprice);
8717 return $prodprice->fourn_tva_npr;
8718 } elseif ($idprod > 0) {
8719 if (!class_exists('Product')) {
8720 require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
8721 }
8722 $prod = new Product($db);
8723 $prod->fetch($idprod);
8724 return $prod->tva_npr;
8725 }
8726
8727 return 0;
8728}
8729
8743function get_default_localtax($thirdparty_seller, $thirdparty_buyer, $local, $idprod = 0)
8744{
8745 global $mysoc;
8746
8747 if (!is_object($thirdparty_seller)) {
8748 return -1;
8749 }
8750 if (!is_object($thirdparty_buyer)) {
8751 return -1;
8752 }
8753
8754 if (empty($thirdparty_seller->country_code)) {
8755 $thirdparty_seller->country_code = $mysoc->country_code;
8756 }
8757 $seller_country_code = $thirdparty_seller->country_code;
8758 //$seller_in_cee = isInEEC($thirdparty_seller);
8759
8760 if (empty($thirdparty_buyer->country_code)) {
8761 $thirdparty_buyer->country_code = $mysoc->country_code;
8762 }
8763 $buyer_country_code = $thirdparty_buyer->country_code;
8764 //$buyer_in_cee = isInEEC($thirdparty_buyer);
8765
8766 if ($local == 1) { // Localtax 1
8767 if ($mysoc->country_code == 'ES') {
8768 if (is_numeric($thirdparty_buyer->localtax1_assuj) && !$thirdparty_buyer->localtax1_assuj) {
8769 return 0;
8770 }
8771 } else {
8772 // Si vendeur non assujeti a Localtax1, localtax1 par default=0
8773 if (is_numeric($thirdparty_seller->localtax1_assuj) && !$thirdparty_seller->localtax1_assuj) {
8774 return 0;
8775 }
8776 if (!is_numeric($thirdparty_seller->localtax1_assuj) && $thirdparty_seller->localtax1_assuj == 'localtax1off') {
8777 return 0;
8778 }
8779 }
8780 } elseif ($local == 2) { //I Localtax 2
8781 // Si vendeur non assujeti a Localtax2, localtax2 par default=0
8782 if (is_numeric($thirdparty_seller->localtax2_assuj) && !$thirdparty_seller->localtax2_assuj) {
8783 return 0;
8784 }
8785 if (!is_numeric($thirdparty_seller->localtax2_assuj) && $thirdparty_seller->localtax2_assuj == 'localtax2off') {
8786 return 0;
8787 }
8788 }
8789
8790 if ($seller_country_code == $buyer_country_code) {
8791 return get_product_localtax_for_country($idprod, $local, $thirdparty_seller);
8792 }
8793
8794 return 0;
8795}
8796
8805function yn($yesno, $format = 1, $color = 0)
8806{
8807 global $langs;
8808
8809 $result = 'unknown';
8810 $classname = '';
8811 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'
8812 $result = $langs->trans('yes');
8813 if ($format == 1 || $format == 3) {
8814 $result = $langs->trans("Yes");
8815 }
8816 if ($format == 2) {
8817 $result = '<input type="checkbox" value="1" checked disabled>';
8818 }
8819 if ($format == 3) {
8820 $result = '<input type="checkbox" value="1" checked disabled> ' . $result;
8821 }
8822 if ($format == 4 || !is_numeric($format)) {
8823 $result = img_picto(is_numeric($format) ? '' : $format, 'check');
8824 }
8825
8826 $classname = 'ok';
8827 } else {
8828 $result = $langs->trans("no");
8829 if ($format == 1 || $format == 3) {
8830 $result = $langs->trans("No");
8831 }
8832 if ($format == 2) {
8833 $result = '<input type="checkbox" value="0" disabled>';
8834 }
8835 if ($format == 3) {
8836 $result = '<input type="checkbox" value="0" disabled> ' . $result;
8837 }
8838 if ($format == 4 || !is_numeric($format)) {
8839 $result = img_picto(is_numeric($format) ? '' : $format, 'uncheck');
8840 }
8841
8842 if ($color == 2) {
8843 $classname = 'ok';
8844 } else {
8845 $classname = 'error';
8846 }
8847 }
8848 if ($color) {
8849 return '<span class="' . $classname . '">' . $result . '</span>';
8850 }
8851 return $result;
8852}
8853
8872function get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart = '')
8873{
8874 if (empty($modulepart) && is_object($object)) {
8875 if (!empty($object->module)) {
8876 $modulepart = $object->module;
8877 } elseif (!empty($object->element)) {
8878 $modulepart = $object->element;
8879 }
8880 }
8881
8882 $path = '';
8883
8884 // Define $arrayforoldpath that is module path using a hierarchy on more than 1 level.
8885 $arrayforoldpath = array('cheque' => 2, 'category' => 2, 'supplier_invoice' => 2, 'invoice_supplier' => 2, 'mailing' => 2, 'supplier_payment' => 2);
8886 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
8887 $arrayforoldpath['product'] = 2;
8888 }
8889
8890 if (empty($level) && array_key_exists($modulepart, $arrayforoldpath)) {
8891 $level = $arrayforoldpath[$modulepart];
8892 }
8893 if (!empty($level) && array_key_exists($modulepart, $arrayforoldpath)) {
8894 // This part should be removed once all code is using "get_exdir" to forge path, with parameter $object and $modulepart provided.
8895 if (empty($num) && is_object($object)) {
8896 $num = $object->id;
8897 }
8898 if (empty($alpha)) {
8899 $num = preg_replace('/([^0-9])/i', '', $num);
8900 } else {
8901 $num = preg_replace('/^.*\-/i', '', $num);
8902 }
8903 $num = substr("000" . $num, -$level);
8904 if ($level == 1) {
8905 $path = substr($num, 0, 1);
8906 }
8907 if ($level == 2) {
8908 $path = substr($num, 1, 1) . '/' . substr($num, 0, 1);
8909 }
8910 if ($level == 3) {
8911 $path = substr($num, 2, 1) . '/' . substr($num, 1, 1) . '/' . substr($num, 0, 1);
8912 }
8913 } else {
8914 // We will enhance here a common way of forging path for document storage.
8915 // In a future, we may distribute directories on several levels depending on setup and object.
8916 // Here, $object->id, $object->ref and $modulepart are required.
8917 if (in_array($modulepart, array('societe', 'thirdparty')) && $object instanceof Societe) {
8918 // 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
8919 $path = dol_sanitizeFileName((string) $object->id);
8920 } else {
8921 $path = dol_sanitizeFileName(empty($object->ref) ? (string) ((is_object($object) && property_exists($object, 'id')) ? $object->id : '') : $object->ref);
8922 }
8923 }
8924
8925 if (empty($withoutslash) && !empty($path)) {
8926 $path .= '/';
8927 }
8928
8929 return $path;
8930}
8931
8940function dol_mkdir($dir, $dataroot = '', $newmask = '')
8941{
8942 dol_syslog("functions.lib::dol_mkdir: dir=" . $dir, LOG_INFO);
8943
8944 $dir = dol_sanitizePathName($dir, '_', 0);
8945
8946 $dir_osencoded = dol_osencode($dir);
8947 if (@is_dir($dir_osencoded)) {
8948 return 0;
8949 }
8950
8951 $nberr = 0;
8952 $nbcreated = 0;
8953
8954 $ccdir = '';
8955 if (!empty($dataroot)) {
8956 // Remove data root from loop
8957 $dir = str_replace($dataroot . '/', '', $dir);
8958 $ccdir = $dataroot . '/';
8959 }
8960
8961 $cdir = explode("/", $dir);
8962 $num = count($cdir);
8963 for ($i = 0; $i < $num; $i++) {
8964 if ($i > 0) {
8965 $ccdir .= '/' . $cdir[$i];
8966 } else {
8967 $ccdir .= $cdir[$i];
8968 }
8969 $regs = array();
8970 if (preg_match("/^.:$/", $ccdir, $regs)) {
8971 continue; // If the Windows path is incomplete, continue with next directory
8972 }
8973
8974 // Attention, is_dir() can fail event if the directory exists
8975 // (i.e. according the open_basedir configuration)
8976 if ($ccdir) {
8977 $ccdir_osencoded = dol_osencode($ccdir);
8978 if (!@is_dir($ccdir_osencoded)) {
8979 dol_syslog("functions.lib::dol_mkdir: Directory '" . $ccdir . "' is not found (does not exists or is outside open_basedir PHP setting).", LOG_DEBUG);
8980
8981 umask(0);
8982 $dirmaskdec = octdec((string) $newmask);
8983 if (empty($newmask)) {
8984 $dirmaskdec = octdec(getDolGlobalString('MAIN_UMASK', '0755'));
8985 }
8986 $dirmaskdec |= octdec('0111'); // Set x bit required for directories
8987 if (!@mkdir($ccdir_osencoded, $dirmaskdec)) {
8988 // If the is_dir has returned a false information, we arrive here
8989 dol_syslog("functions.lib::dol_mkdir: Fails to create directory '" . $ccdir . "' (no permission to write into parent or directory already exists).", LOG_WARNING);
8990 $nberr++;
8991 } else {
8992 dol_syslog("functions.lib::dol_mkdir: Directory '" . $ccdir . "' created", LOG_DEBUG);
8993 $nberr = 0; // At this point in the code, the previous failures can be ignored -> set $nberr to 0
8994 $nbcreated++;
8995 }
8996 } else {
8997 $nberr = 0; // At this point in the code, the previous failures can be ignored -> set $nberr to 0
8998 }
8999 }
9000 }
9001 return ($nberr ? -$nberr : $nbcreated);
9002}
9003
9004
9012function dolChmod($filepath, $newmask = '')
9013{
9014 if (!empty($newmask)) {
9015 @chmod($filepath, octdec($newmask));
9016 } elseif (getDolGlobalString('MAIN_UMASK')) {
9017 @chmod($filepath, octdec(getDolGlobalString('MAIN_UMASK')));
9018 }
9019}
9020
9021
9027function picto_required()
9028{
9029 return '<span class="fieldrequired">*</span>';
9030}
9031
9032
9049function dol_string_nohtmltag($stringtoclean, $removelinefeed = 1, $pagecodeto = 'UTF-8', $strip_tags = 0, $removedoublespaces = 1)
9050{
9051 if (is_null($stringtoclean)) {
9052 return '';
9053 }
9054
9055 if ($removelinefeed == 2) {
9056 $stringtoclean = preg_replace('/<br[^>]*>(\n|\r)+/ims', '<br>', $stringtoclean);
9057 }
9058 $temp = preg_replace('/<br[^>]*>/i', "\n", $stringtoclean);
9059
9060 // 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)
9061 $temp = dol_html_entity_decode($temp, ENT_COMPAT | ENT_HTML5, $pagecodeto);
9062
9063 $temp = str_replace('< ', '__ltspace__', $temp);
9064 $temp = str_replace('<:', '__lttwopoints__', $temp);
9065
9066 if ($strip_tags) {
9067 $temp = strip_tags($temp);
9068 } else {
9069 // 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).
9070 $pattern = "/<[^<>]+>/";
9071 // Example of $temp: <a href="/myurl" title="<u>A title</u>">0000-021</a>
9072 // pass 1 - $temp after pass 1: <a href="/myurl" title="A title">0000-021
9073 // pass 2 - $temp after pass 2: 0000-021
9074 $tempbis = $temp;
9075 do {
9076 $temp = $tempbis;
9077 $tempbis = str_replace('<>', '', $temp); // No reason to have this into a text, except if value is to try bypass the next html cleaning
9078 $tempbis = preg_replace($pattern, '', $tempbis);
9079 //$idowhile++; print $temp.'-'.$tempbis."\n"; if ($idowhile > 100) break;
9080 } while ($tempbis != $temp);
9081
9082 $temp = $tempbis;
9083
9084 // 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).
9085 $temp = preg_replace('/<+([a-z]+)/i', '\1', $temp);
9086 }
9087
9088 $temp = dol_html_entity_decode($temp, ENT_COMPAT, $pagecodeto);
9089
9090 // Remove also carriage returns
9091 if ($removelinefeed == 1) {
9092 $temp = str_replace(array("\r\n", "\r", "\n"), " ", $temp);
9093 }
9094
9095 // And double quotes
9096 if ($removedoublespaces) {
9097 while (strpos($temp, " ")) {
9098 $temp = str_replace(" ", " ", $temp);
9099 }
9100 }
9101
9102 $temp = str_replace('__ltspace__', '< ', $temp);
9103 $temp = str_replace('__lttwopoints__', '<:', $temp);
9104
9105 return trim($temp);
9106}
9107
9127function dol_string_onlythesehtmltags($stringtoclean, $cleanalsosomestyles = 1, $removeclassattribute = 1, $cleanalsojavascript = 0, $allowiframe = 0, $allowed_tags = array(), $allowlink = 0, $allowscript = 0, $allowstyle = 0, $allowphp = 0)
9128{
9129 $sav_allowed_tags = $allowed_tags;
9130
9131 if (empty($allowed_tags) || (is_string($allowed_tags) && preg_match('/^common/', $allowed_tags))) {
9132 $allowed_tags = array(
9133 // HTML 4
9134 "html",
9135 "head",
9136 "body",
9137 "article",
9138 "a",
9139 "abbr",
9140 "b",
9141 "blockquote",
9142 "br",
9143 "cite",
9144 "div",
9145 "dl",
9146 "dd",
9147 "dt",
9148 "em",
9149 "font",
9150 "img",
9151 "ins",
9152 "hr",
9153 "i",
9154 "li",
9155 "ol",
9156 "p",
9157 "q",
9158 "s",
9159 "span",
9160 "strike",
9161 "strong",
9162 "title",
9163 "table",
9164 "tr",
9165 "th",
9166 "td",
9167 "u",
9168 "ul",
9169 "sup",
9170 "sub",
9171 "blockquote",
9172 "pre",
9173 "h1",
9174 "h2",
9175 "h3",
9176 "h4",
9177 "h5",
9178 "h6",
9179
9180 // HTML 5
9181 "footer",
9182 "header",
9183 "menu",
9184 "menuitem",
9185 "nav",
9186 "section"
9187 );
9188 }
9189 $allowed_tags[] = "comment"; // this tags is added to manage comment <!--...--> that are replaced into <comment>...</comment>
9190 if ($allowiframe) {
9191 if (!in_array('iframe', $allowed_tags)) {
9192 $allowed_tags[] = "iframe";
9193 }
9194 }
9195 if ($allowlink) {
9196 if (!in_array('link', $allowed_tags)) {
9197 $allowed_tags[] = "link";
9198 }
9199 if (!in_array('meta', $allowed_tags)) {
9200 $allowed_tags[] = "meta";
9201 }
9202 }
9203 if ($allowscript) {
9204 if (!in_array('script', $allowed_tags)) {
9205 $allowed_tags[] = "script";
9206 }
9207 }
9208 if ($allowstyle) {
9209 if (!in_array('style', $allowed_tags)) {
9210 $allowed_tags[] = "style";
9211 }
9212 }
9213 if (is_string($sav_allowed_tags)) {
9214 $tmptags = explode(',', $sav_allowed_tags);
9215 foreach ($tmptags as $tag) {
9216 if ($tag != 'common') {
9217 $allowed_tags[] = $tag;
9218 }
9219 }
9220 }
9221
9222
9223 $allowed_tags_string = implode("><", $allowed_tags);
9224 $allowed_tags_string = '<' . $allowed_tags_string . '>';
9225
9226 $stringtoclean = str_replace('<!DOCTYPE html>', '__!DOCTYPE_HTML__', $stringtoclean); // Replace DOCTYPE to avoid to have it removed by the strip_tags
9227
9228 $stringtoclean = dol_string_nounprintableascii($stringtoclean, 0);
9229
9230 //$stringtoclean = preg_replace('/<!--[^>]*-->/', '', $stringtoclean);
9231 $stringtoclean = preg_replace('/<!--([^>]*)-->/', '<comment>\1</comment>', $stringtoclean);
9232
9233 if ($allowphp) {
9234 $allowed_tags[] = "commentphp";
9235 $stringtoclean = preg_replace('/^<\?php([^"]+)\?>$/i', '<commentphp>\1__</commentphp>', $stringtoclean); // Note: <?php ... > is allowed only if on the same line
9236 $stringtoclean = preg_replace('/"<\?php([^"]+)\?>"/i', '"<commentphp>\1</commentphp>"', $stringtoclean); // Note: "<?php ... >" is allowed only if on the same line
9237 }
9238
9239 $stringtoclean = preg_replace('/&colon;/i', ':', $stringtoclean);
9240 $stringtoclean = preg_replace('/&#58;|&#0+58|&#x3A/i', '', $stringtoclean); // refused string ':' encoded (no reason to have a : encoded like this) to disable 'javascript:...'
9241
9242 // Remove all HTML tags
9243 $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
9244
9245 if ($cleanalsosomestyles) { // Clean for remaining html tags
9246 //$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
9247 $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
9248 $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
9249 }
9250 if ($removeclassattribute) { // Clean for remaining html tags
9251 $temp = preg_replace('/(<[^>]+)\s+class=((["\']).*?\\3|\\w*)/i', '\\1', $temp);
9252 }
9253
9254 // Remove 'javascript:' that we should not find into a text
9255 // 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)).
9256 if ($cleanalsojavascript) {
9257 $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);
9258 }
9259
9260 $temp = str_replace('__!DOCTYPE_HTML__', '<!DOCTYPE html>', $temp); // Restore the DOCTYPE
9261
9262 if ($allowphp) {
9263 $temp = preg_replace('/<commentphp>(.*)<\/commentphp>/', '<?php\1?>', $temp); // Restore php code
9264 }
9265
9266 $temp = preg_replace('/<comment>([^>]*)<\/comment>/', '<!--\1-->', $temp); // Restore html comments
9267
9268
9269 return $temp;
9270}
9271
9272
9285function dol_string_onlythesehtmlattributes($stringtoclean, $allowed_attributes = null)
9286{
9287 if (is_null($allowed_attributes)) {
9288 $allowed_attributes = array(
9289 // HTML 4
9290 "allow",
9291 "allowfullscreen",
9292 "alt",
9293 "async",
9294 "class",
9295 "contenteditable",
9296 "crossorigin",
9297 "data-html",
9298 "frameborder",
9299 "height",
9300 "href",
9301 "id",
9302 "name",
9303 "property",
9304 "rel",
9305 "src",
9306 "style",
9307 "target",
9308 "title",
9309 "type",
9310 "width",
9311
9312 // HTML5
9313 "footer",
9314 "header",
9315 "menu",
9316 "menuitem",
9317 "nav",
9318 "section"
9319 );
9320 }
9321 // Always add content and http-equiv for meta tags, required to force encoding and keep html content in utf8 by load/saveHTML functions.
9322 if (!in_array("content", $allowed_attributes)) {
9323 $allowed_attributes[] = "content";
9324 }
9325 if (!in_array("http-equiv", $allowed_attributes)) {
9326 $allowed_attributes[] = "http-equiv";
9327 }
9328
9329 if (class_exists('DOMDocument') && !empty($stringtoclean)) {
9330 //$stringtoclean = '<?xml encoding="UTF-8"><html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>'.$stringtoclean.'</body></html>';
9331 $stringtoclean = '<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>' . $stringtoclean . '</body></html>';
9332
9333 // Warning: loadHTML does not support HTML5 on old libxml versions.
9334 $dom = new DOMDocument('', 'UTF-8');
9335 // If $stringtoclean is wrong, it will generates warnings. So we disable warnings and restore them later.
9336 $savwarning = error_reporting();
9337 error_reporting(E_ALL & ~E_WARNING & ~E_NOTICE);
9338 $dom->loadHTML($stringtoclean, LIBXML_ERR_NONE | LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD | LIBXML_NONET | LIBXML_NOWARNING | LIBXML_NOXMLDECL);
9339 error_reporting($savwarning);
9340
9341 if ($dom instanceof DOMDocument) {
9342 for ($els = $dom->getElementsByTagname('*'), $i = $els->length - 1; $i >= 0; $i--) {
9343 $el = $els->item($i);
9344 if (!$el instanceof DOMElement) {
9345 continue;
9346 }
9347 $attrs = $el->attributes;
9348 for ($ii = $attrs->length - 1; $ii >= 0; $ii--) {
9349 //var_dump($attrs->item($ii));
9350 if (!empty($attrs->item($ii)->name)) {
9351 if (! in_array($attrs->item($ii)->name, $allowed_attributes)) {
9352 // Delete attribute if not into allowed_attributes @phan-suppress-next-line PhanUndeclaredMethod
9353 $els->item($i)->removeAttribute($attrs->item($ii)->name);
9354 } elseif (in_array($attrs->item($ii)->name, array('style'))) {
9355 // If attribute is 'style'
9356 $valuetoclean = $attrs->item($ii)->value;
9357
9358 if (isset($valuetoclean)) {
9359 do {
9360 $oldvaluetoclean = $valuetoclean;
9361 $valuetoclean = preg_replace('/\/\*.*\*\//m', '', $valuetoclean); // clean css comments
9362 $valuetoclean = preg_replace('/position\s*:\s*[a-z]+/mi', '', $valuetoclean);
9363 if ($els->item($i)->tagName == 'a') { // more paranoiac cleaning for clickable tags.
9364 $valuetoclean = preg_replace('/display\s*:/mi', '', $valuetoclean);
9365 $valuetoclean = preg_replace('/z-index\s*:/mi', '', $valuetoclean);
9366 $valuetoclean = preg_replace('/\s+(top|left|right|bottom)\s*:/mi', '', $valuetoclean);
9367 }
9368
9369 // We do not allow logout|passwordforgotten.php and action= into the content of a "style" tag
9370 $valuetoclean = preg_replace('/(logout|passwordforgotten)\.php/mi', '', $valuetoclean);
9371 $valuetoclean = preg_replace('/action=/mi', '', $valuetoclean);
9372 } while ($oldvaluetoclean != $valuetoclean);
9373 }
9374
9375 $attrs->item($ii)->value = $valuetoclean;
9376 }
9377 }
9378 }
9379 }
9380 }
9381
9382 $dom->encoding = 'UTF-8';
9383
9384 $return = $dom->saveHTML(); // This may add a LF at end of lines, so we will trim later
9385 //$return = '<html><body>aaaa</p>bb<p>ssdd</p>'."\n<p>aaa</p>aa<p>bb</p>";
9386
9387 //$return = preg_replace('/^'.preg_quote('<?xml encoding="UTF-8">', '/').'/', '', $return);
9388 $return = preg_replace('/^' . preg_quote('<html><head><', '/') . '[^<>]*' . preg_quote('></head><body>', '/') . '/', '', $return);
9389 $return = preg_replace('/' . preg_quote('</body></html>', '/') . '$/', '', trim($return));
9390
9391 return trim($return);
9392 } else {
9393 return $stringtoclean;
9394 }
9395}
9396
9408function dol_string_neverthesehtmltags($stringtoclean, $disallowed_tags = array('textarea'), $cleanalsosomestyles = 0)
9409{
9410 $temp = $stringtoclean;
9411 foreach ($disallowed_tags as $tagtoremove) {
9412 $temp = preg_replace('/<\/?' . $tagtoremove . '>/', '', $temp);
9413 $temp = preg_replace('/<\/?' . $tagtoremove . '\s+[^>]*>/', '', $temp);
9414 }
9415
9416 if ($cleanalsosomestyles) {
9417 $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
9418 }
9419
9420 return $temp;
9421}
9422
9423
9433function dolGetFirstLineOfText($text, $nboflines = 1, $charset = 'UTF-8')
9434{
9435 if ($nboflines == 1) {
9436 if (dol_textishtml($text)) {
9437 $firstline = preg_replace('/<br[^>]*>.*$/s', '', $text); // The s pattern modifier means the . can match newline characters
9438 $firstline = preg_replace('/<div[^>]*>.*$/s', '', $firstline); // The s pattern modifier means the . can match newline characters
9439 } else {
9440 if (isset($text)) {
9441 $firstline = preg_replace('/[\n\r].*/', '', $text);
9442 } else {
9443 $firstline = '';
9444 }
9445 }
9446 return $firstline . (isset($firstline) && isset($text) && (strlen($firstline) != strlen($text)) ? '...' : '');
9447 } else {
9448 $ishtml = 0;
9449 if (dol_textishtml($text)) {
9450 $text = preg_replace('/\n/', '', $text);
9451 $ishtml = 1;
9452 $repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
9453 } else {
9454 $repTable = array("\t" => " ", "\n" => "<br>", "\r" => " ", "\0" => " ", "\x0B" => " ");
9455 }
9456
9457 $text = strtr($text, $repTable);
9458 if ($charset == 'UTF-8') {
9459 $pattern = '/(<br[^>]*>)/Uu';
9460 } else {
9461 // /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
9462 $pattern = '/(<br[^>]*>)/U'; // /U is to have UNGREEDY regex to limit to one html tag.
9463 }
9464 $a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
9465
9466 $firstline = '';
9467 $i = 0;
9468 $countline = 0;
9469 $lastaddediscontent = 1;
9470 while ($countline < $nboflines && isset($a[$i])) {
9471 if (preg_match('/<br[^>]*>/', $a[$i])) {
9472 if (array_key_exists($i + 1, $a) && !empty($a[$i + 1])) {
9473 $firstline .= ($ishtml ? "<br>\n" : "\n");
9474 // Is it a br for a new line of after a printed line ?
9475 if (!$lastaddediscontent) {
9476 $countline++;
9477 }
9478 $lastaddediscontent = 0;
9479 }
9480 } else {
9481 $firstline .= $a[$i];
9482 $lastaddediscontent = 1;
9483 $countline++;
9484 }
9485 $i++;
9486 }
9487
9488 $adddots = (isset($a[$i]) && (!preg_match('/<br[^>]*>/', $a[$i]) || (array_key_exists($i + 1, $a) && !empty($a[$i + 1]))));
9489 //unset($a);
9490 $ret = $firstline . ($adddots ? '...' : '');
9491 //exit;
9492 return $ret;
9493 }
9494}
9495
9496
9508function dol_nl2br($stringtoencode, $nl2brmode = 0, $forxml = false)
9509{
9510 if (is_null($stringtoencode)) {
9511 return '';
9512 }
9513
9514 if (!$nl2brmode) {
9515 return nl2br($stringtoencode, $forxml);
9516 } else {
9517 $ret = preg_replace('/(\r\n|\r|\n)/i', ($forxml ? '<br />' : '<br>'), $stringtoencode);
9518 return $ret;
9519 }
9520}
9521
9531function dol_htmlwithnojs($stringtoencode, $nouseofiframesandbox = 0, $check = 'restricthtml')
9532{
9533 if (empty($nouseofiframesandbox) && getDolGlobalString('MAIN_SECURITY_USE_SANDBOX_FOR_HTMLWITHNOJS')) {
9534 // TODO using sandbox on inline html content is not possible yet with current browsers
9535 //$s = '<iframe class="iframewithsandbox" sandbox><html><body>';
9536 //$s .= $stringtoencode;
9537 //$s .= '</body></html></iframe>';
9538 return $stringtoencode;
9539 } else {
9540 $out = $stringtoencode;
9541
9542 // First clean HTML content
9543 do {
9544 $oldstringtoclean = $out;
9545
9546 $outishtml = 0;
9547 if (dol_textishtml($out)) {
9548 $outishtml = 1;
9549 }
9550
9551 // HTML sanitizer by DOMDocument
9552 if (!empty($out) && getDolGlobalInt('MAIN_RESTRICTHTML_ONLY_VALID_HTML') && $check != 'restricthtmlallowunvalid') {
9553 try {
9554 libxml_use_internal_errors(false); // Avoid to fill memory with xml errors
9555 if (LIBXML_VERSION < 20900) {
9556 // Avoid load of external entities (security problem).
9557 // Required only if LIBXML_VERSION < 20900
9558 // @phan-suppress-next-line PhanDeprecatedFunctionInternal
9559 libxml_disable_entity_loader(true);
9560 }
9561
9562 $dom = new DOMDocument();
9563 // Add a trick '<div class="tricktoremove">' to solve pb with text without parent tag
9564 // like '<h1>Foo</h1><p>bar</p>' that wrongly ends up, without the trick, with '<h1>Foo<p>bar</p></h1>'
9565 // like 'abc' that wrongly ends up, without the trick, with '<p>abc</p>'
9566 // Add also a trick <html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"> to solve utf8 lost.
9567 // I don't know what the xml encoding is the trick for
9568 if ($outishtml) {
9569 //$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>';
9570 $out = '<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body><div class="tricktoremove">' . $out . '</div></body></html>';
9571 //$out = '<html><head><meta charset="utf-8"></head><body><div class="tricktoremove">'.$out.'</div></body></html>';
9572 } else {
9573 //$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>';
9574 $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>';
9575 //$out = '<html><head><meta charset="utf-8"></head><body><div class="tricktoremove">'.dol_nl2br($out).'</div></body></html>';
9576 }
9577
9578 // Note: <a href="https://__[aaa]__/aaa.html"> is transformed into <a href="https://__[aaa]__/aaa.html">
9579 // We don't want that, so we protect __[xxx]__ by replacing [ and ] before loadHTML and restore them after saveHTML
9580 $out = preg_replace_callback(
9581 '/__\[([0-9a-zA-Z_]+)\]__/',
9586 function ($m) {
9587 return '__BRACKETSTART' . $m[1] . 'BRACKETEND__';
9588 },
9589 $out
9590 );
9591
9592 $dom->loadHTML($out, LIBXML_HTML_NODEFDTD | LIBXML_ERR_NONE | LIBXML_HTML_NOIMPLIED | LIBXML_NONET | LIBXML_NOWARNING | LIBXML_NOERROR | LIBXML_NOXMLDECL);
9593
9594 $dom->encoding = 'UTF-8';
9595
9596 // Add a layer to remove some styles
9597 if (getDolGlobalInt('MAIN_RESTRICTHTML_ONLY_VALID_HTML') == 2) {
9598 foreach ($dom->getElementsByTagName('*') as $el) {
9599 if (!$el instanceof DOMElement) {
9600 continue;
9601 }
9602
9603 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
9604 if ($el->hasAttribute('style')) {
9605 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
9606 $style = $el->getAttribute('style');
9607
9608 // delete some styles
9609 $style = preg_replace('/z-index\s*:/i', '', $style);
9610 $style = preg_replace('/position\s*:/i', '', $style);
9611 $style = preg_replace('/top\s*:/i', '', $style);
9612 $style = preg_replace('/left\s*:/i', '', $style);
9613 $style = preg_replace('/background\s*:/i', '', $style);
9614 /*
9615 $style = preg_replace('/width\s*:/i', '', $style);
9616 $style = preg_replace('/height\s*:/i', '', $style);
9617 $style = preg_replace('/backdrop-filter\s*:/i', '', $style);
9618 */
9619 if (trim($style) === '') {
9620 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
9621 $el->removeAttribute('style');
9622 } else {
9623 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
9624 $el->setAttribute('style', $style);
9625 }
9626 }
9627 }
9628 }
9629
9630 $out = trim($dom->saveHTML());
9631
9632 // Restore [ and ] that were protected before loadHTML
9633 $out = preg_replace_callback(
9634 '/__BRACKETSTART([0-9a-zA-Z_]+)BRACKETEND__/',
9639 function ($m) {
9640 return '__[' . $m[1] . ']__';
9641 },
9642 $out
9643 );
9644
9645 // Remove the trick added to solve pb with text in utf8 and text without parent tag
9646 //$out = preg_replace('/^'.preg_quote('<?xml encoding="UTF-8">', '/').'/', '', $out);
9647 $out = preg_replace('/^' . preg_quote('<html><head><', '/') . '[^<>]+' . preg_quote('></head><body><div class="tricktoremove">', '/') . '/', '', $out);
9648 $out = preg_replace('/' . preg_quote('</div></body></html>', '/') . '$/', '', trim($out));
9649 //$out = preg_replace('/^<\?xml encoding="UTF-8"><div class="tricktoremove">/', '', $out);
9650 //$out = preg_replace('/<\/div>$/', '', $out);
9651
9652 if (!$outishtml) { // If $out was not HTML content we made before a dol_nl2br so we must do the opposite operation now
9653 $out = str_replace('<br>', '', $out);
9654 }
9655 } catch (Exception $e) {
9656 // If error, invalid HTML string with no way to clean it
9657 //print $e->getMessage();
9658 $out = 'InvalidHTMLStringCantBeCleaned ' . $e->getMessage();
9659 }
9660 }
9661
9662 // HTML sanitizer by Tidy
9663 // Tidy can't be used for restricthtmlallowunvalid and restricthtmlallowlinkscript
9664 // Tidy can't be used for non html text content as it is corrupting the new lines fields.
9665 if (!empty($out) && getDolGlobalInt('MAIN_RESTRICTHTML_ONLY_VALID_HTML_TIDY') && !in_array($check, array('restricthtmlallowunvalid', 'restricthtmlallowlinkscript')) && $outishtml) {
9666 // TODO Try to implement a hack for restricthtmlallowlinkscript by renaming tag <link> and <script> ?
9667 try {
9668 //var_dump($out);
9669
9670 // Try cleaning using tidy
9671 if (extension_loaded('tidy') && class_exists("tidy")) {
9672 //print "aaa".$out."\n";
9673
9674 // See options at https://tidy.sourceforge.net/docs/quickref.html
9675 $config = array(
9676 'clean' => false,
9677 // 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;
9678 'quote-marks' => false,
9679 'doctype' => 'strict',
9680 'show-body-only' => true,
9681 "indent-attributes" => false,
9682 "vertical-space" => false,
9683 //'ident' => false, // Not always supported
9684 "wrap" => 0,
9685 'preserve-entities' => true
9686 // HTML5 tags
9687 //'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',
9688 //'new-blocklevel-tags' => 'footer header section menu menuitem'
9689 //'new-empty-tags' => 'command embed keygen source track wbr',
9690 //'new-inline-tags' => 'audio command datalist embed keygen mark menuitem meter output progress source time video wbr',
9691 );
9692
9693 // Tidy
9694 $tidy = new tidy();
9695 $out = $tidy->repairString($out, $config, 'utf8');
9696
9697 //print "xxx".$out;exit;
9698 }
9699
9700 //var_dump($out);
9701 } catch (Exception $e) {
9702 // If error, invalid HTML string with no way to clean it
9703 //print $e->getMessage();
9704 $out = 'InvalidHTMLStringCantBeCleaned ' . $e->getMessage();
9705 }
9706 }
9707
9708 // Clear ZERO WIDTH NO-BREAK SPACE, ZERO WIDTH SPACE, ZERO WIDTH JOINER
9709 // TODO $out = preg_replace('/[\x{2000}-\x{200D}\x{FEFF}]/u', ' ', $out);
9710 $out = preg_replace('/[\x{200B}-\x{200D}\x{FEFF}]/u', ' ', $out);
9711
9712 // Clean some html entities that are useless so text is cleaner
9713 $out = preg_replace('/&(tab|newline);/i', ' ', $out);
9714
9715 // Ckeditor uses the numeric entity for apostrophe, so we force it to
9716 // the text entity (all other special chars are encoded using text entities) so we can then exclude all numeric entities.
9717 $out = preg_replace('/&#39;/i', '&apos;', $out);
9718
9719 // 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).
9720 // 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
9721 // using a non conventionnal way to be encoded, to not have them sanitized just after)
9722 if (function_exists('realCharForNumericEntities')) { // May not exist when main.inc.php not loaded, for example in a CLI context
9723 $out = preg_replace_callback(
9724 '/&#(x?[0-9][0-9a-f]+;?)/i',
9729 static function ($m) {
9730 return realCharForNumericEntities($m);
9731 },
9732 $out
9733 );
9734 }
9735
9736 // Now we remove all remaining HTML entities starting with a number. We don't want such entities.
9737 $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'.
9738
9739 // Keep only some html tags and remove also some 'javascript:' strings
9740 if ($check == 'restricthtmlallowlinkscript') {
9741 $out = dol_string_onlythesehtmltags($out, 0, 1, 0, 0, array(), 1, 1, 1, getDolGlobalInt("UNSECURED_restricthtmlallowlinkscript_ALLOW_PHP"));
9742 } elseif ($check == 'restricthtmlallowclass' || $check == 'restricthtmlallowunvalid') {
9743 $out = dol_string_onlythesehtmltags($out, 0, 0, 1);
9744 } elseif ($check == 'restricthtmlallowiframe') {
9745 $out = dol_string_onlythesehtmltags($out, 0, 0, 1, 1);
9746 } else {
9747 $out = dol_string_onlythesehtmltags($out, 0, 1, 1); // styles are allowed to allow rich text editor features of ckeditor managed by the "style=" attribute
9748 }
9749
9750 // Keep only some html attributes and exclude non expected HTML attributes and clean content of some attributes (keep only alt=, title=...).
9751 if (getDolGlobalString('MAIN_RESTRICTHTML_REMOVE_ALSO_BAD_ATTRIBUTES')) {
9753 }
9754
9755 // Restore entity &apos; into &#39; (restricthtml is for html content so we can use html entity) because it is
9756 // compatible with HTML 4 used y CKEditor, and HTML 5 (when &apos; works only with HTML5).
9757 $out = preg_replace('/&apos;/i', "&#39;", $out);
9758
9759 // Now remove js
9760 // 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
9761 $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)>
9762 $out = preg_replace('/on(abort|after|animation|auxclick|before|blur|cancel|canplay|canplaythrough|change|click|close|contextmenu|cuechange|copy|cut)[a-z]*\s*=/i', '', $out);
9763 $out = preg_replace('/on(dblclick|drop|durationchange|emptied|end|ended|error|focus(in|out)?|formdata|gotpointercapture|hashchange|input|invalid)[a-z]*\s*=/i', '', $out);
9764 $out = preg_replace('/on(lostpointercapture|offline|online|pagehide|pageshow)[a-z]*\s*=/i', '', $out);
9765 $out = preg_replace('/on(paste|pause|play|playing|progress|ratechange|reset|resize|scroll|search|seeked|seeking|show|stalled|start|submit|suspend)[a-z]*\s*=/i', '', $out);
9766 $out = preg_replace('/on(timeupdate|toggle|unload|volumechange|waiting|wheel)[a-z]*\s*=/i', '', $out);
9767 // More not into the previous list
9768 $out = preg_replace('/on(repeat|begin|finish|beforeinput)[a-z]*\s*=/i', '', $out);
9769 } while ($oldstringtoclean != $out);
9770
9771 // Check the limit of external links that are automatically executed in a Rich text content. We count:
9772 // '<img' to avoid <img src="http...">, we can only accept "<img src="data:..."
9773 // 'url(' to avoid inline style like background: url(http...
9774 // '<link' to avoid <link href="http...">
9775 $reg = array();
9776 $tmpout = preg_replace('/<img src="data:/mi', '<__IMG_SRC_DATA__ src="data:', $out);
9777 preg_match_all('/(<img|url\‍(|<link)/i', $tmpout, $reg);
9778 $nblinks = count($reg[0]);
9779 if ($nblinks > getDolGlobalInt("MAIN_SECURITY_MAX_IMG_IN_HTML_CONTENT", 1000)) {
9780 $out = 'ErrorTooManyLinksIntoHTMLString';
9781 }
9782
9783 if (getDolGlobalInt('MAIN_DISALLOW_URL_INTO_DESCRIPTIONS') == 2 || $check == 'restricthtmlnolink') {
9784 if ($nblinks > 0) {
9785 $out = 'ErrorHTMLLinksNotAllowed';
9786 }
9787 } elseif (getDolGlobalInt('MAIN_DISALLOW_URL_INTO_DESCRIPTIONS') == 1) {
9788 // Refuse any links except it they are to the wrapper document.php or viewimage.php
9789 $nblinks = 0;
9790
9791 // Loop on each url in src= and url(
9792 $pattern = '/src=["\']?(http[^"\']+)|url\‍(["\']?(http[^\‍)]+)/';
9793
9795
9796 $matches = array();
9797 if (preg_match_all($pattern, $out, $matches)) {
9798 // URLs are into $matches[1] or $matches[2]
9799 $urls = array();
9800 foreach ($matches[1] as $tmpval) {
9801 if (!empty($tmpval)) {
9802 $urls[] = $tmpval;
9803 }
9804 }
9805 foreach ($matches[2] as $tmpval) {
9806 if (!empty($tmpval)) {
9807 $urls[] = $tmpval;
9808 }
9809 }
9810
9811 // Show URLs
9812 $firstexturl = '';
9813 $secondexturl = '';
9814 foreach ($urls as $url) {
9815 $urlok = 0;
9816 $parsedurl = parse_url($url);
9817 if (!empty($parsedurl)) {
9818 if (preg_match('/'.preg_quote($dolibarr_main_url_root, '/').'/', $url)
9819 //&& preg_match('/(document|viewimage)\.php$/', $parsedurl['path']) && preg_match('/modulepart=(media|mycompany)/', $parsedurl['query'])
9820 ) {
9821 $urlok = 1;
9822 }
9823 }
9824 if (!$urlok) {
9825 $nblinks++;
9826 if (empty($firstexturl)) {
9827 $firstexturl = $url;
9828 } elseif (empty($secondexturl)) {
9829 $secondexturl = $url;
9830 }
9831 //echo "Found url = ".$url . "\n";
9832 }
9833 }
9834 if ($nblinks > 0) {
9835 $out = 'ErrorHTMLExternalLinksNotAllowed (Example: '.$firstexturl.($secondexturl ? ' '.$secondexturl : '').')';
9836 }
9837 }
9838 }
9839
9840 return $out;
9841 }
9842}
9843
9864function dol_htmlentitiesbr($stringtoencode, $nl2brmode = 0, $pagecodefrom = 'UTF-8', $removelasteolbr = 1)
9865{
9866 if (is_null($stringtoencode)) {
9867 return '';
9868 }
9869
9870 $newstring = $stringtoencode;
9871 if (dol_textishtml($stringtoencode)) { // Check if text is already HTML or not
9872 $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.
9873 if ($removelasteolbr) {
9874 $newstring = preg_replace('/<br>$/i', '', $newstring); // Remove last <br> (remove only last one)
9875 }
9876 $newstring = preg_replace('/[\x{200B}-\x{200D}\x{FEFF}]/u', ' ', $newstring);
9877 $newstring = strtr($newstring, array('&' => '__PROTECTand__', '<' => '__PROTECTlt__', '>' => '__PROTECTgt__', '"' => '__PROTECTdquot__'));
9878 $newstring = dol_htmlentities($newstring, ENT_COMPAT, $pagecodefrom); // Make entity encoding
9879 $newstring = strtr($newstring, array('__PROTECTand__' => '&', '__PROTECTlt__' => '<', '__PROTECTgt__' => '>', '__PROTECTdquot__' => '"'));
9880 } else {
9881 if ($removelasteolbr) {
9882 $newstring = preg_replace('/(\r\n|\r|\n)$/i', '', $newstring); // Remove last \n (may remove several)
9883 }
9884 $newstring = dol_nl2br(dol_htmlentities($newstring, ENT_COMPAT, $pagecodefrom), $nl2brmode);
9885 }
9886 // Other substitutions that htmlentities does not do
9887 //$newstring=str_replace(chr(128),'&euro;',$newstring); // 128 = 0x80. Not in html entity table. // Seems useles with TCPDF. Make bug with UTF8 languages
9888 return $newstring;
9889}
9890
9898function dol_htmlentitiesbr_decode($stringtodecode, $pagecodeto = 'UTF-8')
9899{
9900 $ret = dol_html_entity_decode($stringtodecode, ENT_COMPAT | ENT_HTML5, $pagecodeto);
9901 $ret = preg_replace('/' . "\r\n" . '<br(\s[\sa-zA-Z_="]*)?\/?>/i', "<br>", $ret);
9902 $ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>' . "\r\n" . '/i', "\r\n", $ret);
9903 $ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>' . "\n" . '/i', "\n", $ret);
9904 $ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>/i', "\n", $ret);
9905 return $ret;
9906}
9907
9914function dol_htmlcleanlastbr($stringtodecode)
9915{
9916 $ret = preg_replace('/&nbsp;$/i', "", $stringtodecode); // Because wysiwyg editor may add a &nbsp; at end of last line
9917 $ret = preg_replace('/(<br>|<br(\s[\sa-zA-Z_="]*)?\/?>|' . "\n" . '|' . "\r" . ')+$/i', "", $ret);
9918 return $ret;
9919}
9920
9930function dol_html_entity_decode($a, $b, $c = 'UTF-8', $keepsomeentities = 0)
9931{
9932 $newstring = $a;
9933 if ($keepsomeentities) {
9934 $newstring = strtr($newstring, array('&amp;' => '__andamp__', '&lt;' => '__andlt__', '&gt;' => '__andgt__', '"' => '__dquot__'));
9935 }
9936 $newstring = html_entity_decode((string) $newstring, (int) $b, (string) $c);
9937 if ($keepsomeentities) {
9938 $newstring = strtr($newstring, array('__andamp__' => '&amp;', '__andlt__' => '&lt;', '__andgt__' => '&gt;', '__dquot__' => '"'));
9939 }
9940 return $newstring;
9941}
9942
9954function dol_htmlentities($string, $flags = ENT_QUOTES | ENT_SUBSTITUTE, $encoding = 'UTF-8', $double_encode = false)
9955{
9956 return htmlentities($string, $flags, $encoding, $double_encode);
9957}
9958
9970function dol_string_is_good_iso($s, $clean = 0)
9971{
9972 $len = dol_strlen($s);
9973 $out = '';
9974 $ok = 1;
9975 for ($scursor = 0; $scursor < $len; $scursor++) {
9976 $ordchar = ord($s[$scursor]);
9977 //print $scursor.'-'.$ordchar.'<br>';
9978 if ($ordchar < 32 && $ordchar != 13 && $ordchar != 10) {
9979 $ok = 0;
9980 break;
9981 } elseif ($ordchar > 126 && $ordchar < 160) {
9982 $ok = 0;
9983 break;
9984 } elseif ($clean) {
9985 $out .= $s[$scursor];
9986 }
9987 }
9988 if ($clean) {
9989 return $out;
9990 }
9991 return $ok;
9992}
9993
10002function dol_nboflines($s, $maxchar = 0)
10003{
10004 if ($s == '') {
10005 return 0;
10006 }
10007 $arraystring = explode("\n", $s);
10008 $nb = count($arraystring);
10009
10010 return $nb;
10011}
10012
10013
10023function dol_nboflines_bis($text, $maxlinesize = 0, $charset = 'UTF-8')
10024{
10025 $repTable = array("\t" => " ", "\n" => "<br>", "\r" => " ", "\0" => " ", "\x0B" => " ");
10026 if (dol_textishtml($text)) {
10027 $repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
10028 }
10029
10030 $text = strtr($text, $repTable);
10031 if ($charset == 'UTF-8') {
10032 $pattern = '/(<br[^>]*>)/Uu';
10033 } else {
10034 // /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
10035 $pattern = '/(<br[^>]*>)/U'; // /U is to have UNGREEDY regex to limit to one html tag.
10036 }
10037 $a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
10038
10039 $nblines = (int) floor((count($a) + 1) / 2);
10040 // count possible auto line breaks
10041 if ($maxlinesize) {
10042 foreach ($a as $line) {
10043 if (dol_strlen($line) > $maxlinesize) {
10044 //$line_dec = html_entity_decode(strip_tags($line));
10045 $line_dec = html_entity_decode($line);
10046 if (dol_strlen($line_dec) > $maxlinesize) {
10047 $line_dec = wordwrap($line_dec, $maxlinesize, '\n', true);
10048 $nblines += substr_count($line_dec, '\n');
10049 }
10050 }
10051 }
10052 }
10053
10054 unset($a);
10055 return $nblines;
10056}
10057
10066function dol_textishtml($msg, $option = 0)
10067{
10068 if (is_null($msg)) {
10069 return false;
10070 }
10071
10072 if ($option == 1) {
10073 if (preg_match('/<(html|link|script)/i', $msg)) {
10074 return true;
10075 } elseif (preg_match('/<body/i', $msg)) {
10076 return true;
10077 } elseif (preg_match('/<\/textarea/i', $msg)) {
10078 return true;
10079 } elseif (preg_match('/<(b|em|i|u)(\s+[^>]+)?>/i', $msg)) {
10080 return true;
10081 } elseif (preg_match('/<br/i', $msg)) {
10082 return true;
10083 }
10084 return false;
10085 } else {
10086 // Remove all urls because 'http://aa?param1=abc&amp;param2=def' must not be used inside detection
10087 $msg = preg_replace('/https?:\/\/[^"\'\s]+/i', '', $msg);
10088 if (preg_match('/<(html|link|script|body)/i', $msg)) {
10089 return true;
10090 } elseif (preg_match('/<\/textarea/i', $msg)) {
10091 return true;
10092 } elseif (preg_match('/<(b|em|i|u)(\s+[^>]+)?>/i', $msg)) {
10093 return true;
10094 } elseif (preg_match('/<(br|hr)\/>/i', $msg)) {
10095 return true;
10096 } elseif (preg_match('/<(br|hr|div|font|li|p|span|strong|table)>/i', $msg)) {
10097 return true;
10098 } elseif (preg_match('/<(br|hr|div|font|li|p|span|strong|table)\s+[^<>\/]*\/?>/i', $msg)) {
10099 return true;
10100 } elseif (preg_match('/<img\s+[^<>]*src[^<>]*>/i', $msg)) {
10101 return true; // must accept <img src="http://example.com/aaa.png" />
10102 } elseif (preg_match('/<a\s+[^<>]*href[^<>]*>/i', $msg)) {
10103 return true; // must accept <a href="http://example.com/aaa.png" />
10104 } elseif (preg_match('/<h[0-9]>/i', $msg)) {
10105 return true;
10106 } elseif (preg_match('/&[A-Z0-9]{1,6};/i', $msg)) {
10107 // TODO If content is 'A link https://aaa?param=abc&amp;param2=def', it return true but must be false
10108 return true; // Html entities names (http://www.w3schools.com/tags/ref_entities.asp)
10109 } elseif (preg_match('/&#[0-9]{2,3};/i', $msg)) {
10110 return true; // Html entities numbers (http://www.w3schools.com/tags/ref_entities.asp)
10111 } elseif (preg_match('/&#x[a-f0-9][a-f0-9];/i', $msg)) {
10112 return true; // Html entities numbers in hexa
10113 }
10114
10115 return false;
10116 }
10117}
10118
10133function dol_concatdesc($text1, $text2, $forxml = false, $invert = false)
10134{
10135 if (!empty($invert)) {
10136 $tmp = $text1;
10137 $text1 = $text2;
10138 $text2 = $tmp;
10139 }
10140
10141 $ret = '';
10142 $ret .= (!dol_textishtml($text1) && dol_textishtml($text2)) ? dol_nl2br(dol_escape_htmltag($text1, 0, 1, '', 1), 0, $forxml) : $text1;
10143 $ret .= (!empty($text1) && !empty($text2)) ? ((dol_textishtml($text1) || dol_textishtml($text2)) ? ($forxml ? "<br >\n" : "<br>\n") : "\n") : "";
10144 $ret .= (dol_textishtml($text1) && !dol_textishtml($text2)) ? dol_nl2br(dol_escape_htmltag($text2, 0, 1, '', 1), 0, $forxml) : $text2;
10145 return $ret;
10146}
10147
10156function dol_concat($text1, $text2)
10157{
10158 return $text1.$text2;
10159}
10160
10168function safeArrayMap($callback, array $array)
10169{
10170 if (!is_string($callback)) {
10171 throw new InvalidArgumentException("Les callbacks sont désactivés.");
10172 }
10173 // Check that $callback is a sure function
10174 $allowed_callbacks = ['strtolower', 'strtoupper', 'intval'];
10175 if (!in_array($callback, $allowed_callbacks, true)) {
10176 throw new InvalidArgumentException("Callback function not allowed.");
10177 }
10178 return array_map($callback, $array);
10179}
10180
10181
10195function getCommonSubstitutionArray($outputlangs, $onlykey = 0, $exclude = null, $object = null, $include = null)
10196{
10197 global $db, $conf, $mysoc, $user, $extrafields;
10198
10199 $substitutionarray = array();
10200
10201 if ((empty($exclude) || !in_array('user', $exclude)) && (empty($include) || in_array('user', $include)) && $user instanceof User) {
10202 // Add SIGNATURE into substitutionarray first, so, when we will make the substitution,
10203 // this will include signature content first and then replace var found into content of signature
10204 //var_dump($onlykey);
10205 $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()
10206 $usersignature = $user->signature;
10207 $substitutionarray = array_merge($substitutionarray, array(
10208 '__SENDEREMAIL_SIGNATURE__' => (string) ((!getDolGlobalString('MAIN_MAIL_DO_NOT_USE_SIGN')) ? ($onlykey == 2 ? dol_trunc('SignatureFromTheSelectedSenderProfile', 30) : $emailsendersignature) : ''),
10209 '__USER_SIGNATURE__' => (string) (($usersignature && !getDolGlobalString('MAIN_MAIL_DO_NOT_USE_SIGN')) ? ($onlykey == 2 ? dol_trunc(dol_string_nohtmltag($usersignature), 30) : $usersignature) : '')
10210 ));
10211
10212 if (is_object($user) && ($user instanceof User)) {
10213 $substitutionarray = array_merge($substitutionarray, array(
10214 '__USER_ID__' => (string) $user->id,
10215 '__USER_LOGIN__' => (string) $user->login,
10216 '__USER_EMAIL__' => (string) $user->email,
10217 '__USER_PHONE__' => (string) dol_print_phone($user->office_phone, '', 0, 0, '', " ", '', '', -1),
10218 '__USER_PHONEPRO__' => (string) dol_print_phone($user->user_mobile, '', 0, 0, '', " ", '', '', -1),
10219 '__USER_PHONEMOBILE__' => (string) dol_print_phone($user->personal_mobile, '', 0, 0, '', " ", '', '', -1),
10220 '__USER_FAX__' => (string) $user->office_fax,
10221 '__USER_LASTNAME__' => (string) $user->lastname,
10222 '__USER_FIRSTNAME__' => (string) $user->firstname,
10223 '__USER_FULLNAME__' => (string) $user->getFullName($outputlangs),
10224 '__USER_SUPERVISOR_ID__' => (string) ($user->fk_user ? $user->fk_user : '0'),
10225 '__USER_JOB__' => (string) $user->job,
10226 '__USER_REMOTE_IP__' => (string) getUserRemoteIP(),
10227 '__USER_VCARD_URL__' => (string) $user->getOnlineVirtualCardUrl('', 'external')
10228 ));
10229 }
10230 }
10231 if ((empty($exclude) || !in_array('mycompany', $exclude)) && is_object($mysoc) && (empty($include) || in_array('mycompany', $include))) {
10232 $substitutionarray = array_merge($substitutionarray, array(
10233 '__MYCOMPANY_NAME__' => $mysoc->name,
10234 '__MYCOMPANY_EMAIL__' => $mysoc->email,
10235 '__MYCOMPANY_URL__' => $mysoc->url,
10236 '__MYCOMPANY_PHONE__' => dol_print_phone((string) $mysoc->phone, '', 0, 0, '', " ", '', '', -1),
10237 '__MYCOMPANY_PHONEMOBILE__' => dol_print_phone((string) $mysoc->phone_mobile, '', 0, 0, '', " ", '', '', -1),
10238 '__MYCOMPANY_FAX__' => dol_print_phone((string) $mysoc->fax, '', 0, 0, '', " ", '', '', -1),
10239 '__MYCOMPANY_PROFID1__' => $mysoc->idprof1,
10240 '__MYCOMPANY_PROFID2__' => $mysoc->idprof2,
10241 '__MYCOMPANY_PROFID3__' => $mysoc->idprof3,
10242 '__MYCOMPANY_PROFID4__' => $mysoc->idprof4,
10243 '__MYCOMPANY_PROFID5__' => $mysoc->idprof5,
10244 '__MYCOMPANY_PROFID6__' => $mysoc->idprof6,
10245 '__MYCOMPANY_PROFID7__' => $mysoc->idprof7,
10246 '__MYCOMPANY_PROFID8__' => $mysoc->idprof8,
10247 '__MYCOMPANY_PROFID9__' => $mysoc->idprof9,
10248 '__MYCOMPANY_PROFID10__' => $mysoc->idprof10,
10249 '__MYCOMPANY_CAPITAL__' => $mysoc->capital,
10250 '__MYCOMPANY_FULLADDRESS__' => (method_exists($mysoc, 'getFullAddress') ? $mysoc->getFullAddress(1, ', ') : ''), // $mysoc may be stdClass
10251 '__MYCOMPANY_ADDRESS__' => $mysoc->address,
10252 '__MYCOMPANY_VATNUMBER__' => $mysoc->tva_intra,
10253 '__MYCOMPANY_ZIP__' => $mysoc->zip,
10254 '__MYCOMPANY_TOWN__' => $mysoc->town,
10255 '__MYCOMPANY_STATE__' => $mysoc->state,
10256 '__MYCOMPANY_COUNTRY__' => $mysoc->country,
10257 '__MYCOMPANY_COUNTRY_ID__' => $mysoc->country_id,
10258 '__MYCOMPANY_COUNTRY_CODE__' => $mysoc->country_code,
10259 '__MYCOMPANY_CURRENCY_CODE__' => $conf->currency
10260 ));
10261 }
10262
10263 if (($onlykey || is_object($object)) && (empty($exclude) || !in_array('object', $exclude)) && (empty($include) || in_array('object', $include))) {
10264 if ($onlykey) {
10265 $substitutionarray['__ID__'] = '__ID__';
10266 $substitutionarray['__REF__'] = '__REF__';
10267 $substitutionarray['__NEWREF__'] = '__NEWREF__';
10268 $substitutionarray['__LABEL__'] = '__LABEL__';
10269 $substitutionarray['__REF_CLIENT__'] = '__REF_CLIENT__';
10270 $substitutionarray['__REF_SUPPLIER__'] = '__REF_SUPPLIER__';
10271 $substitutionarray['__NOTE_PUBLIC__'] = '__NOTE_PUBLIC__';
10272 $substitutionarray['__NOTE_PRIVATE__'] = '__NOTE_PRIVATE__';
10273 $substitutionarray['__EXTRAFIELD_XXX__'] = '__EXTRAFIELD_XXX__';
10274
10275 if (isModEnabled("societe")) { // Most objects are concerned
10276 $substitutionarray['__THIRDPARTY_ID__'] = '__THIRDPARTY_ID__';
10277 $substitutionarray['__THIRDPARTY_NAME__'] = '__THIRDPARTY_NAME__';
10278 $substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = '__THIRDPARTY_NAME_ALIAS__';
10279 $substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = '__THIRDPARTY_CODE_CLIENT__';
10280 $substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = '__THIRDPARTY_CODE_FOURNISSEUR__';
10281 $substitutionarray['__THIRDPARTY_EMAIL__'] = '__THIRDPARTY_EMAIL__';
10282 //$substitutionarray['__THIRDPARTY_EMAIL_URLENCODED__'] = '__THIRDPARTY_EMAIL_URLENCODED__'; // We hide this one
10283 $substitutionarray['__THIRDPARTY_URL__'] = '__THIRDPARTY_URL__';
10284 //$substitutionarray['__THIRDPARTY_URL_URLENCODED__'] = '__THIRDPARTY_URL_URLENCODED__'; // We hide this one
10285 $substitutionarray['__THIRDPARTY_PHONE__'] = '__THIRDPARTY_PHONE__';
10286 $substitutionarray['__THIRDPARTY_FAX__'] = '__THIRDPARTY_FAX__';
10287 $substitutionarray['__THIRDPARTY_ADDRESS__'] = '__THIRDPARTY_ADDRESS__';
10288 $substitutionarray['__THIRDPARTY_ZIP__'] = '__THIRDPARTY_ZIP__';
10289 $substitutionarray['__THIRDPARTY_TOWN__'] = '__THIRDPARTY_TOWN__';
10290 $substitutionarray['__THIRDPARTY_STATE__'] = '__THIRDPARTY_STATE__';
10291 $substitutionarray['__THIRDPARTY_IDPROF1__'] = '__THIRDPARTY_IDPROF1__';
10292 $substitutionarray['__THIRDPARTY_IDPROF2__'] = '__THIRDPARTY_IDPROF2__';
10293 $substitutionarray['__THIRDPARTY_IDPROF3__'] = '__THIRDPARTY_IDPROF3__';
10294 $substitutionarray['__THIRDPARTY_IDPROF4__'] = '__THIRDPARTY_IDPROF4__';
10295 $substitutionarray['__THIRDPARTY_IDPROF5__'] = '__THIRDPARTY_IDPROF5__';
10296 $substitutionarray['__THIRDPARTY_IDPROF6__'] = '__THIRDPARTY_IDPROF6__';
10297 $substitutionarray['__THIRDPARTY_IDPROF7__'] = '__THIRDPARTY_IDPROF7__';
10298 $substitutionarray['__THIRDPARTY_IDPROF8__'] = '__THIRDPARTY_IDPROF8__';
10299 $substitutionarray['__THIRDPARTY_IDPROF9__'] = '__THIRDPARTY_IDPROF9__';
10300 $substitutionarray['__THIRDPARTY_IDPROF10__'] = '__THIRDPARTY_IDPROF10__';
10301 $substitutionarray['__THIRDPARTY_TVAINTRA__'] = '__THIRDPARTY_TVAINTRA__';
10302 $substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = '__THIRDPARTY_NOTE_PUBLIC__';
10303 $substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = '__THIRDPARTY_NOTE_PRIVATE__';
10304 }
10305 if (isModEnabled('member') && (!is_object($object) || $object->element == 'adherent') && (empty($exclude) || !in_array('member', $exclude)) && (empty($include) || in_array('member', $include))) {
10306 $substitutionarray['__MEMBER_ID__'] = '__MEMBER_ID__';
10307 $substitutionarray['__MEMBER_TITLE__'] = '__MEMBER_TITLE__';
10308 $substitutionarray['__MEMBER_FIRSTNAME__'] = '__MEMBER_FIRSTNAME__';
10309 $substitutionarray['__MEMBER_LASTNAME__'] = '__MEMBER_LASTNAME__';
10310 $substitutionarray['__MEMBER_USER_LOGIN_INFORMATION__'] = 'Login and pass of the external user account';
10311 /*$substitutionarray['__MEMBER_NOTE_PUBLIC__'] = '__MEMBER_NOTE_PUBLIC__';
10312 $substitutionarray['__MEMBER_NOTE_PRIVATE__'] = '__MEMBER_NOTE_PRIVATE__';*/
10313 }
10314 // add substitution variables for ticket
10315 if (isModEnabled('ticket') && (!is_object($object) || $object->element == 'ticket') && (empty($exclude) || !in_array('ticket', $exclude)) && (empty($include) || in_array('ticket', $include))) {
10316 $substitutionarray['__TICKET_TRACKID__'] = '__TICKET_TRACKID__';
10317 $substitutionarray['__TICKET_SUBJECT__'] = '__TICKET_SUBJECT__';
10318 $substitutionarray['__TICKET_TYPE__'] = '__TICKET_TYPE__';
10319 $substitutionarray['__TICKET_SEVERITY__'] = '__TICKET_SEVERITY__';
10320 $substitutionarray['__TICKET_CATEGORY__'] = '__TICKET_CATEGORY__';
10321 $substitutionarray['__TICKET_ANALYTIC_CODE__'] = '__TICKET_ANALYTIC_CODE__';
10322 $substitutionarray['__TICKET_MESSAGE__'] = '__TICKET_MESSAGE__';
10323 $substitutionarray['__TICKET_PROGRESSION__'] = '__TICKET_PROGRESSION__';
10324 $substitutionarray['__TICKET_USER_ASSIGN__'] = '__TICKET_USER_ASSIGN__';
10325 }
10326 if (isModEnabled('recruitment') && (!is_object($object) || $object->element == 'recruitmentcandidature') && (empty($exclude) || !in_array('recruitment', $exclude)) && (empty($include) || in_array('recruitment', $include))) {
10327 $substitutionarray['__CANDIDATE_FULLNAME__'] = '__CANDIDATE_FULLNAME__';
10328 $substitutionarray['__CANDIDATE_FIRSTNAME__'] = '__CANDIDATE_FIRSTNAME__';
10329 $substitutionarray['__CANDIDATE_LASTNAME__'] = '__CANDIDATE_LASTNAME__';
10330 }
10331 if (isModEnabled('holiday') && (!is_object($object) || $object->element == 'holiday') && (empty($exclude) || !in_array('holiday', $exclude)) && (empty($include) || in_array('holiday', $include))) {
10332 $substitutionarray['__HOLIDAY_ARRAY_PER_EMPLOYEE_FOR_PERIOD__'] = '__HOLIDAY_ARRAY_PER_EMPLOYEE_FOR_PERIOD__';
10333 }
10334 if (isModEnabled('project') && (empty($exclude) || !in_array('project', $exclude)) && (empty($include) || in_array('project', $include))) { // Most objects
10335 $substitutionarray['__PROJECT_ID__'] = '__PROJECT_ID__';
10336 $substitutionarray['__PROJECT_REF__'] = '__PROJECT_REF__';
10337 $substitutionarray['__PROJECT_NAME__'] = '__PROJECT_NAME__';
10338 /*$substitutionarray['__PROJECT_NOTE_PUBLIC__'] = '__PROJECT_NOTE_PUBLIC__';
10339 $substitutionarray['__PROJECT_NOTE_PRIVATE__'] = '__PROJECT_NOTE_PRIVATE__';*/
10340 }
10341 if (isModEnabled('contract') && (!is_object($object) || $object->element == 'contract') && (empty($exclude) || !in_array('contract', $exclude)) && (empty($include) || in_array('contract', $include))) {
10342 $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = 'Highest date planned for a service start';
10343 $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = 'Highest date and hour planned for service start';
10344 $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = 'Lowest data for planned expiration of service';
10345 $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = 'Lowest date and hour for planned expiration of service';
10346 }
10347 if (isModEnabled("propal") && (!is_object($object) || $object->element == 'propal') && (empty($exclude) || !in_array('propal', $exclude)) && (empty($include) || in_array('propal', $include))) {
10348 $substitutionarray['__ONLINE_SIGN_URL__'] = 'ToOfferALinkForOnlineSignature';
10349 }
10350 if (isModEnabled("intervention") && (!is_object($object) || $object->element == 'fichinter') && (empty($exclude) || !in_array('intervention', $exclude)) && (empty($include) || in_array('intervention', $include))) {
10351 $substitutionarray['__ONLINE_SIGN_FICHINTER_URL__'] = 'ToOfferALinkForOnlineSignature';
10352 }
10353 $substitutionarray['__ONLINE_PAYMENT_URL__'] = 'UrlToPayOnlineIfApplicable';
10354 $substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__'] = 'TextAndUrlToPayOnlineIfApplicable';
10355 $substitutionarray['__SECUREKEYPAYMENT__'] = 'Security key (if key is not unique per record)';
10356 $substitutionarray['__SECUREKEYPAYMENT_MEMBER__'] = 'Security key for payment on a member subscription (one key per member)';
10357 $substitutionarray['__SECUREKEYPAYMENT_ORDER__'] = 'Security key for payment on an order';
10358 $substitutionarray['__SECUREKEYPAYMENT_INVOICE__'] = 'Security key for payment on an invoice';
10359 $substitutionarray['__SECUREKEYPAYMENT_CONTRACTLINE__'] = 'Security key for payment on a service of a contract';
10360
10361 $substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = 'Direct download url of a proposal';
10362 $substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = 'Direct download url of an order';
10363 $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = 'Direct download url of an invoice';
10364 $substitutionarray['__DIRECTDOWNLOAD_URL_CONTRACT__'] = 'Direct download url of a contract';
10365 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_PROPOSAL__'] = 'Direct download url of a supplier proposal';
10366 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_ORDER__'] = 'Direct download url of a supplier order';
10367 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_INVOICE__'] = 'Direct download url of a supplier invoice';
10368
10369 if (isModEnabled("shipping") && (!is_object($object) || $object->element == 'shipping')) {
10370 $substitutionarray['__SHIPPINGTRACKNUM__'] = 'Shipping tracking number';
10371 $substitutionarray['__SHIPPINGTRACKNUMURL__'] = 'Shipping tracking url';
10372 $substitutionarray['__SHIPPINGMETHOD__'] = 'Shipping method';
10373 }
10374 if (isModEnabled("reception") && (!is_object($object) || $object->element == 'reception')) {
10375 $substitutionarray['__RECEPTIONTRACKNUM__'] = 'Shipping tracking number of shipment';
10376 $substitutionarray['__RECEPTIONTRACKNUMURL__'] = 'Shipping tracking url';
10377 }
10378 } else {
10379 '@phan-var-force Adherent|Delivery $object';
10381 $substitutionarray['__ID__'] = $object->id;
10382 $substitutionarray['__REF__'] = $object->ref;
10383 $substitutionarray['__NEWREF__'] = $object->newref;
10384 $substitutionarray['__LABEL__'] = (isset($object->label) ? $object->label : (isset($object->title) ? $object->title : null));
10385 $substitutionarray['__REF_CLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : null));
10386 $substitutionarray['__REF_SUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
10387 $substitutionarray['__NOTE_PUBLIC__'] = (isset($object->note_public) ? $object->note_public : null);
10388 $substitutionarray['__NOTE_PRIVATE__'] = (isset($object->note_private) ? $object->note_private : null);
10389
10390 $substitutionarray['__DATE_CREATION__'] = (isset($object->date_creation) ? dol_print_date($object->date_creation, 'day', false, $outputlangs) : '');
10391 $substitutionarray['__DATE_MODIFICATION__'] = (isset($object->date_modification) ? dol_print_date($object->date_modification, 'day', false, $outputlangs) : '');
10392 $substitutionarray['__DATE_VALIDATION__'] = (isset($object->date_validation) ? dol_print_date($object->date_validation, 'day', false, $outputlangs) : '');
10393
10394 // handle date_delivery: in customer order/supplier order, the property name is delivery_date, in shipment/reception it is date_delivery
10395 $date_delivery = null;
10396 if (property_exists($object, 'date_delivery')) {
10397 $date_delivery = $object->date_delivery;
10398 } elseif (property_exists($object, 'delivery_date')) {
10399 $date_delivery = $object->delivery_date;
10400 }
10401 $substitutionarray['__DATE_DELIVERY__'] = (isset($date_delivery) ? dol_print_date($date_delivery, 'day', false, $outputlangs) : '');
10402 $substitutionarray['__DATE_DELIVERY_DAY__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%d") : '');
10403 $substitutionarray['__DATE_DELIVERY_DAY_TEXT__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%A") : '');
10404 $substitutionarray['__DATE_DELIVERY_MON__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%m") : '');
10405 $substitutionarray['__DATE_DELIVERY_MON_TEXT__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%b") : '');
10406 $substitutionarray['__DATE_DELIVERY_YEAR__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%Y") : '');
10407 $substitutionarray['__DATE_DELIVERY_HH__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%H") : '');
10408 $substitutionarray['__DATE_DELIVERY_MM__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%M") : '');
10409 $substitutionarray['__DATE_DELIVERY_SS__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%S") : '');
10410
10411 // For backward compatibility (deprecated)
10412 $substitutionarray['__REFCLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : null));
10413 $substitutionarray['__REFSUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
10414
10415 $substitutionarray['__SUPPLIER_ORDER_DATE_DELIVERY__'] = (isset($date_delivery) ? dol_print_date($date_delivery, 'day', false, $outputlangs) : '');
10416 $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 : '')) : '');
10417 $substitutionarray['__EXPIRATION_DATE__'] = (isset($object->fin_validite) ? dol_print_date($object->fin_validite, 'daytext') : '');
10418
10419 if (is_object($object) && ($object->element == 'adherent' || $object->element == 'member') && $object->id > 0) {
10420 '@phan-var-force Adherent $object';
10422 $birthday = (empty($object->birth) ? '' : dol_print_date($object->birth, 'day'));
10423
10424 $substitutionarray['__MEMBER_ID__'] = (isset($object->id) ? $object->id : '');
10425 if (method_exists($object, 'getCivilityLabel')) {
10426 $substitutionarray['__MEMBER_TITLE__'] = $object->getCivilityLabel();
10427 }
10428 $substitutionarray['__MEMBER_FIRSTNAME__'] = (isset($object->firstname) ? $object->firstname : '');
10429 $substitutionarray['__MEMBER_LASTNAME__'] = (isset($object->lastname) ? $object->lastname : '');
10430 $substitutionarray['__MEMBER_USER_LOGIN_INFORMATION__'] = '';
10431 if (method_exists($object, 'getFullName')) {
10432 $substitutionarray['__MEMBER_FULLNAME__'] = $object->getFullName($outputlangs);
10433 }
10434 $substitutionarray['__MEMBER_COMPANY__'] = (isset($object->societe) ? $object->societe : '');
10435 $substitutionarray['__MEMBER_ADDRESS__'] = (isset($object->address) ? $object->address : '');
10436 $substitutionarray['__MEMBER_ZIP__'] = (isset($object->zip) ? $object->zip : '');
10437 $substitutionarray['__MEMBER_TOWN__'] = (isset($object->town) ? $object->town : '');
10438 $substitutionarray['__MEMBER_STATE__'] = (isset($object->state) ? $object->state : '');
10439 $substitutionarray['__MEMBER_COUNTRY__'] = (isset($object->country) ? $object->country : '');
10440 $substitutionarray['__MEMBER_EMAIL__'] = (isset($object->email) ? $object->email : '');
10441 $substitutionarray['__MEMBER_BIRTH__'] = (isset($birthday) ? $birthday : '');
10442 $substitutionarray['__MEMBER_PHOTO__'] = (isset($object->photo) ? $object->photo : '');
10443 $substitutionarray['__MEMBER_LOGIN__'] = (isset($object->login) ? $object->login : '');
10444 $substitutionarray['__MEMBER_PASSWORD__'] = (isset($object->pass) ? $object->pass : '');
10445 $substitutionarray['__MEMBER_PHONE__'] = (isset($object->phone) ? dol_print_phone($object->phone) : '');
10446 $substitutionarray['__MEMBER_PHONEPRO__'] = (isset($object->phone_perso) ? dol_print_phone($object->phone_perso) : '');
10447 $substitutionarray['__MEMBER_PHONEMOBILE__'] = (isset($object->phone_mobile) ? dol_print_phone($object->phone_mobile) : '');
10448 $substitutionarray['__MEMBER_TYPE__'] = (isset($object->type) ? $object->type : '');
10449 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE__'] = dol_print_date($object->first_subscription_date, 'day');
10450
10451 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_RFC__'] = dol_print_date($object->first_subscription_date, 'dayrfc');
10452 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_START__'] = (isset($object->first_subscription_date_start) ? dol_print_date($object->first_subscription_date_start, 'day') : '');
10453 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_START_RFC__'] = (isset($object->first_subscription_date_start) ? dol_print_date($object->first_subscription_date_start, 'dayrfc') : '');
10454 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_END__'] = (isset($object->first_subscription_date_end) ? dol_print_date($object->first_subscription_date_end, 'day') : '');
10455 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_END_RFC__'] = (isset($object->first_subscription_date_end) ? dol_print_date($object->first_subscription_date_end, 'dayrfc') : '');
10456 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE__'] = dol_print_date($object->last_subscription_date, 'day');
10457 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_RFC__'] = dol_print_date($object->last_subscription_date, 'dayrfc');
10458 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_START__'] = dol_print_date($object->last_subscription_date_start, 'day');
10459 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_START_RFC__'] = dol_print_date($object->last_subscription_date_start, 'dayrfc');
10460 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_END__'] = dol_print_date($object->last_subscription_date_end, 'day');
10461 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_END_RFC__'] = dol_print_date($object->last_subscription_date_end, 'dayrfc');
10462 }
10463
10464 if (is_object($object) && $object->element == 'societe') {
10466 '@phan-var-force Societe $object';
10467 $substitutionarray['__THIRDPARTY_ID__'] = $object->id ?? '';
10468 $substitutionarray['__THIRDPARTY_NAME__'] = $object->name ?? '';
10469 $substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = $object->name_alias ?? '';
10470 $substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = $object->code_client ?? '';
10471 $substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = $object->code_fournisseur ?? '';
10472 $substitutionarray['__THIRDPARTY_EMAIL__'] = $object->email ?? '';
10473 $substitutionarray['__THIRDPARTY_EMAIL_URLENCODED__'] = urlencode($object->email ?? '');
10474 $substitutionarray['__THIRDPARTY_URL__'] = $object->url ?? '';
10475 $substitutionarray['__THIRDPARTY_URL_URLENCODED__'] = urlencode($object->url ?? '');
10476 $substitutionarray['__THIRDPARTY_PHONE__'] = dol_print_phone($object->phone ?? '');
10477 $substitutionarray['__THIRDPARTY_FAX__'] = dol_print_phone($object->fax ?? '');
10478 $substitutionarray['__THIRDPARTY_ADDRESS__'] = $object->address ?? '';
10479 $substitutionarray['__THIRDPARTY_ZIP__'] = $object->zip ?? '';
10480 $substitutionarray['__THIRDPARTY_TOWN__'] = $object->town ?? '';
10481 $substitutionarray['__THIRDPARTY_STATE__'] = $object->state ?? '';
10482 $substitutionarray['__THIRDPARTY_COUNTRY_ID__'] = ($object->country_id > 0 ?: '');
10483 $substitutionarray['__THIRDPARTY_COUNTRY_CODE__'] = $object->country_code ?? '';
10484 $substitutionarray['__THIRDPARTY_IDPROF1__'] = $object->idprof1 ?? '';
10485 $substitutionarray['__THIRDPARTY_IDPROF2__'] = $object->idprof2 ?? '';
10486 $substitutionarray['__THIRDPARTY_IDPROF3__'] = $object->idprof3 ?? '';
10487 $substitutionarray['__THIRDPARTY_IDPROF4__'] = $object->idprof4 ?? '';
10488 $substitutionarray['__THIRDPARTY_IDPROF5__'] = $object->idprof5 ?? '';
10489 $substitutionarray['__THIRDPARTY_IDPROF6__'] = $object->idprof6 ?? '';
10490 $substitutionarray['__THIRDPARTY_TVAINTRA__'] = $object->tva_intra ?? '';
10491 $substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = dol_htmlentitiesbr($object->note_public ?? '');
10492 $substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = dol_htmlentitiesbr($object->note_private ?? '');
10493 } elseif (is_object($object) && is_object($object->thirdparty)) {
10494 $substitutionarray['__THIRDPARTY_ID__'] = $object->thirdparty->id ?? '';
10495 $substitutionarray['__THIRDPARTY_NAME__'] = $object->thirdparty->name ?? '';
10496 $substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = $object->thirdparty->name_alias ?? '';
10497 $substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = $object->thirdparty->code_client ?? '';
10498 $substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = $object->thirdparty->code_fournisseur ?? '';
10499 $substitutionarray['__THIRDPARTY_EMAIL__'] = $object->thirdparty->email ?? '';
10500 $substitutionarray['__THIRDPARTY_EMAIL_URLENCODED__'] = urlencode($object->thirdparty->email ?? '');
10501 $substitutionarray['__THIRDPARTY_PHONE__'] = dol_print_phone($object->thirdparty->phone ?? '');
10502 $substitutionarray['__THIRDPARTY_FAX__'] = dol_print_phone($object->thirdparty->fax ?? '');
10503 $substitutionarray['__THIRDPARTY_ADDRESS__'] = $object->thirdparty->address ?? '';
10504 $substitutionarray['__THIRDPARTY_ZIP__'] = $object->thirdparty->zip ?? '';
10505 $substitutionarray['__THIRDPARTY_TOWN__'] = $object->thirdparty->town ?? '';
10506 $substitutionarray['__THIRDPARTY_STATE__'] = $object->thirdparty->state ?? '';
10507 $substitutionarray['__THIRDPARTY_COUNTRY_ID__'] = ($object->thirdparty->country_id > 0 ?: '');
10508 $substitutionarray['__THIRDPARTY_COUNTRY_CODE__'] = $object->thirdparty->country_code ?? '';
10509 $substitutionarray['__THIRDPARTY_IDPROF1__'] = $object->thirdparty->idprof1 ?? '';
10510 $substitutionarray['__THIRDPARTY_IDPROF2__'] = $object->thirdparty->idprof2 ?? '';
10511 $substitutionarray['__THIRDPARTY_IDPROF3__'] = $object->thirdparty->idprof3 ?? '';
10512 $substitutionarray['__THIRDPARTY_IDPROF4__'] = $object->thirdparty->idprof4 ?? '';
10513 $substitutionarray['__THIRDPARTY_IDPROF5__'] = $object->thirdparty->idprof5 ?? '';
10514 $substitutionarray['__THIRDPARTY_IDPROF6__'] = $object->thirdparty->idprof6 ?? '';
10515 $substitutionarray['__THIRDPARTY_TVAINTRA__'] = $object->thirdparty->tva_intra ?? '';
10516 $substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = dol_htmlentitiesbr($object->thirdparty->note_public ?? '');
10517 $substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = dol_htmlentitiesbr($object->thirdparty->note_private ?? '');
10518 }
10519
10520 if (is_object($object) && $object->element == 'recruitmentcandidature') {
10521 '@phan-var-force RecruitmentCandidature $object';
10523 $substitutionarray['__CANDIDATE_FULLNAME__'] = $object->getFullName($outputlangs);
10524 $substitutionarray['__CANDIDATE_FIRSTNAME__'] = isset($object->firstname) ? $object->firstname : '';
10525 $substitutionarray['__CANDIDATE_LASTNAME__'] = isset($object->lastname) ? $object->lastname : '';
10526 }
10527 if (is_object($object) && $object->element == 'conferenceorboothattendee') {
10528 '@phan-var-force ConferenceOrBoothAttendee $object';
10530 $substitutionarray['__ATTENDEE_FULLNAME__'] = $object->getFullName($outputlangs);
10531 $substitutionarray['__ATTENDEE_FIRSTNAME__'] = isset($object->firstname) ? $object->firstname : '';
10532 $substitutionarray['__ATTENDEE_LASTNAME__'] = isset($object->lastname) ? $object->lastname : '';
10533 }
10534
10535 if (is_object($object) && $object->element == 'project') {
10536 '@phan-var-force Project $object';
10538 $substitutionarray['__PROJECT_ID__'] = $object->id;
10539 $substitutionarray['__PROJECT_REF__'] = $object->ref;
10540 $substitutionarray['__PROJECT_NAME__'] = $object->title;
10541 } elseif (is_object($object)) {
10542 $project = null;
10543 if (!empty($object->project)) {
10544 $project = $object->project;
10545 }
10546 if (!is_null($project) && is_object($project)) {
10547 $substitutionarray['__PROJECT_ID__'] = $project->id;
10548 $substitutionarray['__PROJECT_REF__'] = $project->ref;
10549 $substitutionarray['__PROJECT_NAME__'] = $project->title;
10550 } else {
10551 // can substitute variables for project : uses lazy load in "make_substitutions" method
10552 $project_id = 0;
10553 if (!empty($object->fk_project) && $object->fk_project > 0) {
10554 $project_id = $object->fk_project;
10555 } elseif (!empty($object->fk_projet) && $object->fk_projet > 0) {
10556 $project_id = $object->fk_project;
10557 }
10558 if ($project_id > 0) {
10559 // path:class:method:id
10560 $substitutionarray['__PROJECT_ID__@lazyload'] = '/projet/class/project.class.php:Project:fetchAndSetSubstitution:' . $project_id;
10561 $substitutionarray['__PROJECT_REF__@lazyload'] = '/projet/class/project.class.php:Project:fetchAndSetSubstitution:' . $project_id;
10562 $substitutionarray['__PROJECT_NAME__@lazyload'] = '/projet/class/project.class.php:Project:fetchAndSetSubstitution:' . $project_id;
10563 }
10564 }
10565 }
10566
10567 if (is_object($object) && $object->element == 'facture') {
10568 '@phan-var-force Facture $object';
10570 $substitutionarray['__INVOICE_SITUATION_NUMBER__'] = isset($object->situation_counter) ? $object->situation_counter : '';
10571 }
10572 if (is_object($object) && $object->element == 'shipping') {
10573 '@phan-var-force Expedition $object';
10575 $substitutionarray['__SHIPPINGTRACKNUM__'] = $object->tracking_number;
10576 $substitutionarray['__SHIPPINGTRACKNUMURL__'] = $object->tracking_url;
10577 $substitutionarray['__SHIPPINGMETHOD__'] = $object->shipping_method;
10578 }
10579 if (is_object($object) && $object->element == 'reception') {
10580 '@phan-var-force Reception $object';
10582 $substitutionarray['__RECEPTIONTRACKNUM__'] = $object->tracking_number;
10583 $substitutionarray['__RECEPTIONTRACKNUMURL__'] = $object->tracking_url;
10584 }
10585
10586 if (is_object($object) && $object->element == 'contrat' && $object->id > 0 && is_array($object->lines)) {
10587 '@phan-var-force Contrat $object';
10589 $dateplannedstart = '';
10590 $datenextexpiration = '';
10591 foreach ($object->lines as $line) {
10592 if ($line->date_start > $dateplannedstart) {
10593 $dateplannedstart = $line->date_start;
10594 }
10595 if ($line->statut == 4 && $line->date_end && (!$datenextexpiration || $line->date_end < $datenextexpiration)) {
10596 $datenextexpiration = $line->date_end;
10597 }
10598 }
10599 $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = dol_print_date($dateplannedstart, 'day');
10600 $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE_RFC__'] = dol_print_date($dateplannedstart, 'dayrfc');
10601 $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = dol_print_date($dateplannedstart, 'standard');
10602
10603 $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = dol_print_date($datenextexpiration, 'day');
10604 $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE_RFC__'] = dol_print_date($datenextexpiration, 'dayrfc');
10605 $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = dol_print_date($datenextexpiration, 'standard');
10606 }
10607 // add substitution variables for ticket
10608 if (is_object($object) && $object->element == 'ticket') {
10609 '@phan-var-force Ticket $object';
10611 $substitutionarray['__TICKET_TRACKID__'] = $object->track_id;
10612 $substitutionarray['__TICKET_SUBJECT__'] = $object->subject;
10613 $substitutionarray['__TICKET_TYPE__'] = $object->type_code;
10614 $substitutionarray['__TICKET_SEVERITY__'] = $object->severity_code;
10615 $substitutionarray['__TICKET_CATEGORY__'] = $object->category_code; // For backward compatibility
10616 $substitutionarray['__TICKET_ANALYTIC_CODE__'] = $object->category_code;
10617 $substitutionarray['__TICKET_MESSAGE__'] = $object->message;
10618 $substitutionarray['__TICKET_PROGRESSION__'] = $object->progress;
10619 $userstat = new User($db);
10620 if ($object->fk_user_assign > 0) {
10621 $userstat->fetch($object->fk_user_assign);
10622 $substitutionarray['__TICKET_USER_ASSIGN__'] = dolGetFirstLastname($userstat->firstname, $userstat->lastname);
10623 }
10624
10625 if ($object->fk_user_create > 0) {
10626 $userstat->fetch($object->fk_user_create);
10627 $substitutionarray['__USER_CREATE__'] = dolGetFirstLastname($userstat->firstname, $userstat->lastname);
10628 }
10629 }
10630
10631 // Create dynamic tags for __EXTRAFIELD_FIELD__
10632 if ($object->table_element && $object->id > 0) {
10633 if (!is_object($extrafields)) {
10634 $extrafields = new ExtraFields($db);
10635 }
10636 $extrafields->fetch_name_optionals_label($object->table_element, true);
10637
10638 if ($object->fetch_optionals() > 0) { // @FIXME: Remove this, the fetch should have been done already, by the caller of getCommonSubstitutionArray()
10639 if (is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label']) > 0) {
10640 foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $label) {
10641 if ($extrafields->attributes[$object->table_element]['type'][$key] == 'date') {
10642 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = dol_print_date($object->array_options['options_' . $key], 'day');
10643 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_LOCALE__'] = dol_print_date($object->array_options['options_' . $key], 'day', 'tzserver', $outputlangs);
10644 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_RFC__'] = dol_print_date($object->array_options['options_' . $key], 'dayrfc');
10645 } elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'datetime') {
10646 $datetime = $object->array_options['options_' . $key];
10647 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhour') : '');
10648 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_LOCALE__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhour', 'tzserver', $outputlangs) : '');
10649 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_DAY_LOCALE__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'day', 'tzserver', $outputlangs) : '');
10650 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_RFC__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhourrfc') : '');
10651 } elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'phone') {
10652 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = dol_print_phone($object->array_options['options_' . $key]);
10653 } elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'price') {
10654 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = $object->array_options['options_' . $key];
10655 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_FORMATED__'] = price($object->array_options['options_' . $key]); // For compatibility
10656 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_FORMATTED__'] = price($object->array_options['options_' . $key]);
10657 } elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'select') {
10658 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = !empty($object->array_options['options_' . $key]) ? $object->array_options['options_' . $key] : '';
10659 $val = $extrafields->attributes[$object->table_element]['param'][$key]['options'][$object->array_options['options_'.$key]] ?? $object->array_options['options_'.$key];
10660 $substitutionarray['__EXTRAFIELD_'.strtoupper($key).'_LABEL__'] = $val;
10661 } elseif ($extrafields->attributes[$object->table_element]['type'][$key] != 'separator') {
10662 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = !empty($object->array_options['options_' . $key]) ? $object->array_options['options_' . $key] : '';
10663 }
10664 }
10665 }
10666 }
10667 }
10668
10669 // Complete substitution array with the url to make online payment
10670 if (empty($substitutionarray['__REF__'])) {
10671 $paymenturl = '';
10672 } else {
10673 // Set the online payment url link into __ONLINE_PAYMENT_URL__ key
10674 require_once DOL_DOCUMENT_ROOT . '/core/lib/payments.lib.php';
10675 $outputlangs->loadLangs(array('paypal', 'other'));
10676
10677 $amounttouse = 0;
10678 $typeforonlinepayment = 'free';
10679 if (is_object($object) && $object->element == 'commande') {
10680 $typeforonlinepayment = 'order';
10681 }
10682 if (is_object($object) && $object->element == 'facture') {
10683 $typeforonlinepayment = 'invoice';
10684 }
10685 if (is_object($object) && $object->element == 'member') {
10686 $typeforonlinepayment = 'member';
10687 if (!empty($object->last_subscription_amount)) {
10688 $amounttouse = $object->last_subscription_amount;
10689 }
10690 }
10691 if (is_object($object) && $object->element == 'contrat') {
10692 $typeforonlinepayment = 'contract';
10693 }
10694 if (is_object($object) && $object->element == 'fichinter') {
10695 $typeforonlinepayment = 'ficheinter';
10696 }
10697
10698 $url = getOnlinePaymentUrl(0, $typeforonlinepayment, $substitutionarray['__REF__'], (float) $amounttouse);
10699 $paymenturl = $url;
10700 }
10701
10702 if ($object->id > 0) {
10703 $substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__'] = ($paymenturl ? str_replace('\n', "\n", $outputlangs->trans("PredefinedMailContentLink", $paymenturl)) : '');
10704 $substitutionarray['__ONLINE_PAYMENT_URL__'] = $paymenturl;
10705
10706 // Show structured communication
10707 if (getDolGlobalString('INVOICE_PAYMENT_ENABLE_STRUCTURED_COMMUNICATION') && $object->element == 'facture') {
10708 include_once DOL_DOCUMENT_ROOT . '/core/lib/functions_be.lib.php';
10709 $substitutionarray['__PAYMENT_STRUCTURED_COMMUNICATION__'] = dolBECalculateStructuredCommunication((string) $object->ref, $object->type);
10710 }
10711
10712 if (getDolGlobalString('PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'propal') {
10713 $substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = $object->getLastMainDocLink($object->element);
10714 } else {
10715 $substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = '';
10716 }
10717 if (getDolGlobalString('ORDER_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'commande') {
10718 $substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = $object->getLastMainDocLink($object->element);
10719 } else {
10720 $substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = '';
10721 }
10722 if (getDolGlobalString('INVOICE_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'facture') {
10723 $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = $object->getLastMainDocLink($object->element);
10724 } else {
10725 $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = '';
10726 }
10727 if (getDolGlobalString('CONTRACT_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'contrat') {
10728 $substitutionarray['__DIRECTDOWNLOAD_URL_CONTRACT__'] = $object->getLastMainDocLink($object->element);
10729 } else {
10730 $substitutionarray['__DIRECTDOWNLOAD_URL_CONTRACT__'] = '';
10731 }
10732 if (getDolGlobalString('FICHINTER_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'fichinter') {
10733 $substitutionarray['__DIRECTDOWNLOAD_URL_FICHINTER__'] = $object->getLastMainDocLink($object->element);
10734 } else {
10735 $substitutionarray['__DIRECTDOWNLOAD_URL_FICHINTER__'] = '';
10736 }
10737 if (getDolGlobalString('SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'supplier_proposal') {
10738 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_PROPOSAL__'] = $object->getLastMainDocLink($object->element);
10739 } else {
10740 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_PROPOSAL__'] = '';
10741 }
10742 if (getDolGlobalString('SUPPLIER_ORDER_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'order_supplier') {
10743 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_ORDER__'] = $object->getLastMainDocLink($object->element);
10744 } else {
10745 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_ORDER__'] = '';
10746 }
10747 if (getDolGlobalString('SUPPLIER_INVOICE_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'invoice_supplier') {
10748 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_INVOICE__'] = $object->getLastMainDocLink($object->element);
10749 } else {
10750 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_INVOICE__'] = '';
10751 }
10752
10753 if (is_object($object) && $object->element == 'propal') {
10754 '@phan-var-force Propal $object';
10756 $substitutionarray['__URL_PROPOSAL__'] = DOL_MAIN_URL_ROOT . "/comm/propal/card.php?id=" . $object->id;
10757 require_once DOL_DOCUMENT_ROOT . '/core/lib/signature.lib.php';
10758 $substitutionarray['__ONLINE_SIGN_URL__'] = getOnlineSignatureUrl(0, 'proposal', (string) $object->ref, 1, $object);
10759 }
10760 if (is_object($object) && $object->element == 'commande') {
10761 '@phan-var-force Commande $object';
10763 $substitutionarray['__URL_ORDER__'] = DOL_MAIN_URL_ROOT . "/commande/card.php?id=" . $object->id;
10764 }
10765 if (is_object($object) && $object->element == 'facture') {
10766 '@phan-var-force Facture $object';
10768 $substitutionarray['__URL_INVOICE__'] = DOL_MAIN_URL_ROOT . "/compta/facture/card.php?id=" . $object->id;
10769 }
10770 if (is_object($object) && $object->element == 'contrat') {
10771 '@phan-var-force Contrat $object';
10773 $substitutionarray['__URL_CONTRACT__'] = DOL_MAIN_URL_ROOT . "/contrat/card.php?id=" . $object->id;
10774 require_once DOL_DOCUMENT_ROOT . '/core/lib/signature.lib.php';
10775 $substitutionarray['__ONLINE_SIGN_URL__'] = getOnlineSignatureUrl(0, 'contract', (string) $object->ref, 1, $object);
10776 }
10777 if (is_object($object) && $object->element == 'fichinter') {
10778 '@phan-var-force Fichinter $object';
10780 $substitutionarray['__URL_FICHINTER__'] = DOL_MAIN_URL_ROOT . "/fichinter/card.php?id=" . $object->id;
10781 require_once DOL_DOCUMENT_ROOT . '/core/lib/signature.lib.php';
10782 $substitutionarray['__ONLINE_SIGN_FICHINTER_URL__'] = getOnlineSignatureUrl(0, 'fichinter', (string) $object->ref, 1, $object);
10783 }
10784 if (is_object($object) && $object->element == 'supplier_proposal') {
10785 '@phan-var-force SupplierProposal $object';
10787 $substitutionarray['__URL_SUPPLIER_PROPOSAL__'] = DOL_MAIN_URL_ROOT . "/supplier_proposal/card.php?id=" . $object->id;
10788 }
10789 if (is_object($object) && $object->element == 'invoice_supplier') {
10790 '@phan-var-force FactureFournisseur $object';
10792 $substitutionarray['__URL_SUPPLIER_INVOICE__'] = DOL_MAIN_URL_ROOT . "/fourn/facture/card.php?id=" . $object->id;
10793 }
10794 if (is_object($object) && $object->element == 'payment_supplier') {
10795 '@phan-var-force PaiementFourn $object';
10797 //print_r($object);
10798 $liste_factures = [];
10799 $total = 0;
10800
10801 // @FIXME We must not have any repeated SQL access into this function.
10802 $sql = 'SELECT f.ref,f.multicurrency_code as f_mccode, pf.*
10803 FROM '.MAIN_DB_PREFIX.'paiementfourn_facturefourn as pf
10804 JOIN '.MAIN_DB_PREFIX.'facture_fourn as f ON pf.fk_facturefourn = f.rowid
10805 WHERE pf.fk_paiementfourn = '.((int) $object->id);
10806
10807 $resql = $db->query($sql);
10808 if ($resql) {
10809 while ($objp = $db->fetch_object($resql)) {
10810 $liste_factures[] = ' - '.$outputlangs->trans('Invoice').' '. $objp->ref.' '.$outputlangs->trans('AmountPayed').' '.price($objp->multicurrency_amount, 0, $outputlangs, 0, -1, -1, $objp->multicurrency_code);
10811 }
10812 }
10813 $substitutionarray['__SUPPLIER_PAYMENT_INVOICES_LIST__'] = implode("\n", $liste_factures);
10814 ;
10815 $substitutionarray['__SUPPLIER_PAYMENT_INVOICES_TOTAL__'] = price($object->multicurrency_amount, 0, $outputlangs, 0, -1, -1, $object->multicurrency_code ? $object->multicurrency_code : $conf->currency);
10816 }
10817 if (is_object($object) && $object->element == 'shipping') {
10818 '@phan-var-force Expedition $object';
10820 $substitutionarray['__URL_SHIPMENT__'] = DOL_MAIN_URL_ROOT . "/expedition/card.php?id=" . $object->id;
10821 if (getDolGlobalInt('EXPEDITION_ALLOW_ONLINESIGN')) {
10822 require_once DOL_DOCUMENT_ROOT . '/core/lib/signature.lib.php';
10823 $substitutionarray['__ONLINE_SIGN_URL__'] = getOnlineSignatureUrl(0, 'expedition', $object->ref, 1, $object);
10824 }
10825 }
10826 }
10827
10828 if (is_object($object) && $object->element == 'action') {
10829 '@phan-var-force ActionComm $object';
10831 $substitutionarray['__EVENT_LABEL__'] = $object->label;
10832 $substitutionarray['__EVENT_DESCRIPTION__'] = $object->note;
10833 $substitutionarray['__EVENT_TYPE__'] = $outputlangs->trans("Action" . $object->type_code);
10834 $substitutionarray['__EVENT_DATE__'] = dol_print_date($object->datep, 'day', 'auto', $outputlangs);
10835 $substitutionarray['__EVENT_TIME__'] = dol_print_date($object->datep, 'hour', 'auto', $outputlangs);
10836 $substitutionarray['__EVENT_DATE_TZUSER__'] = dol_print_date($object->datep, 'day', 'tzuserrel', $outputlangs);
10837 $substitutionarray['__EVENT_TIME_TZUSER__'] = dol_print_date($object->datep, 'hour', 'tzuserrel', $outputlangs);
10838 }
10839 }
10840 }
10841
10842 if ((empty($exclude) || !in_array('objectamount', $exclude)) && (empty($include) || in_array('objectamount', $include))) {
10843 '@phan-var-force Facture|FactureRec $object';
10845 include_once DOL_DOCUMENT_ROOT . '/core/lib/functionsnumtoword.lib.php';
10846
10847 $substitutionarray['__DATE_YMD__'] = is_object($object) ? (isset($object->date) ? dol_print_date($object->date, 'day', false, $outputlangs) : null) : '';
10848 $substitutionarray['__DATE_DUE_YMD__'] = is_object($object) ? (isset($object->date_lim_reglement) ? dol_print_date($object->date_lim_reglement, 'day', false, $outputlangs) : null) : '';
10849 $substitutionarray['__DATE_YMD_TEXT__'] = is_object($object) ? (isset($object->date) ? dol_print_date($object->date, 'daytext', false, $outputlangs) : null) : '';
10850 $substitutionarray['__DATE_DUE_YMD_TEXT__'] = is_object($object) ? (isset($object->date_lim_reglement) ? dol_print_date($object->date_lim_reglement, 'daytext', false, $outputlangs) : null) : '';
10851
10852 $already_payed_all = 0;
10853 if (is_object($object) && ($object instanceof Facture)) {
10854 $already_payed_all = $object->totalpaid + $object->totaldeposits + $object->totalcreditnotes;
10855 }
10856
10857 $substitutionarray['__SIMPLE_ORDER_TABLE__'] = is_object($object) && !empty($object->lines) ? showSimpleOrderTable($outputlangs, $object) : "";
10858 $substitutionarray['__AMOUNT_EXCL_TAX__'] = is_object($object) ? $object->total_ht : '';
10859 $substitutionarray['__AMOUNT_EXCL_TAX_TEXT__'] = is_object($object) ? dol_convertToWord($object->total_ht, $outputlangs, '', true) : '';
10860 $substitutionarray['__AMOUNT_EXCL_TAX_TEXTCURRENCY__'] = is_object($object) ? dol_convertToWord($object->total_ht, $outputlangs, $conf->currency, true) : '';
10861
10862 $substitutionarray['__AMOUNT__'] = is_object($object) ? $object->total_ttc : '';
10863 $substitutionarray['__AMOUNT_TEXT__'] = is_object($object) ? dol_convertToWord($object->total_ttc, $outputlangs, '', true) : '';
10864 $substitutionarray['__AMOUNT_TEXTCURRENCY__'] = is_object($object) ? dol_convertToWord($object->total_ttc, $outputlangs, $conf->currency, true) : '';
10865
10866 $substitutionarray['__DEPOSIT_PERCENT__'] = is_object($object) ? $object->deposit_percent : '';
10867 $substitutionarray['__DEPOSIT_AMOUNT__'] = is_object($object) ? price2num($object->total_ttc * ($object->deposit_percent / 100), 'MT') : '';
10868
10869 $substitutionarray['__AMOUNT_REMAIN__'] = is_object($object) ? price2num($object->total_ttc - $already_payed_all, 'MT') : '';
10870
10871 $substitutionarray['__AMOUNT_VAT__'] = is_object($object) ? (isset($object->total_vat) ? $object->total_vat : $object->total_tva) : '';
10872 $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)) : '';
10873 $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)) : '';
10874
10875 $mysocuselocaltax1 = false;
10876 $mysocuselocaltax2 = false;
10877 if ($mysoc instanceof Societe && !empty($mysoc->country_code)) {
10878 $tmparray = $mysoc->useLocalTax(-1);
10879 $mysocuselocaltax1 = $tmparray[1];
10880 $mysocuselocaltax2 = $tmparray[2];
10881 }
10882
10883 // Local taxes
10884 if ($onlykey != 2 || $mysocuselocaltax1) {
10885 $substitutionarray['__AMOUNT_TAX2__'] = is_object($object) ? $object->total_localtax1 : '';
10886 }
10887 if ($onlykey != 2 || $mysocuselocaltax2) {
10888 $substitutionarray['__AMOUNT_TAX3__'] = is_object($object) ? $object->total_localtax2 : '';
10889 }
10890
10891 // Amount keys formatted in a currency
10892 $substitutionarray['__AMOUNT_EXCL_TAX_FORMATTED__'] = is_object($object) ? ($object->total_ht ? price($object->total_ht, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
10893 $substitutionarray['__AMOUNT_FORMATTED__'] = is_object($object) ? ($object->total_ttc ? price($object->total_ttc, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
10894 $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) : '';
10895 $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)) : '';
10896 if ($onlykey != 2 || $mysocuselocaltax1) {
10897 $substitutionarray['__AMOUNT_TAX2_FORMATTED__'] = is_object($object) ? ($object->total_localtax1 ? price($object->total_localtax1, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
10898 }
10899 if ($onlykey != 2 || $mysocuselocaltax2) {
10900 $substitutionarray['__AMOUNT_TAX3_FORMATTED__'] = is_object($object) ? ($object->total_localtax2 ? price($object->total_localtax2, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
10901 }
10902 // Amount keys formatted in a currency (with the typo error for backward compatibility)
10903 if ($onlykey != 2) {
10904 $substitutionarray['__AMOUNT_EXCL_TAX_FORMATED__'] = $substitutionarray['__AMOUNT_EXCL_TAX_FORMATTED__'];
10905 $substitutionarray['__AMOUNT_FORMATED__'] = $substitutionarray['__AMOUNT_FORMATTED__'];
10906 $substitutionarray['__AMOUNT_REMAIN_FORMATED__'] = $substitutionarray['__AMOUNT_REMAIN_FORMATTED__'];
10907 $substitutionarray['__AMOUNT_VAT_FORMATED__'] = $substitutionarray['__AMOUNT_VAT_FORMATTED__'];
10908 if ($mysocuselocaltax1) {
10909 $substitutionarray['__AMOUNT_TAX2_FORMATED__'] = $substitutionarray['__AMOUNT_TAX2_FORMATTED__'];
10910 }
10911 if ($mysoc->useLocalTax2) {
10912 $substitutionarray['__AMOUNT_TAX3_FORMATED__'] = $substitutionarray['__AMOUNT_TAX3_FORMATTED__'];
10913 }
10914 }
10915
10916 $substitutionarray['__AMOUNT_MULTICURRENCY__'] = (is_object($object) && isset($object->multicurrency_total_ttc)) ? $object->multicurrency_total_ttc : '';
10917 $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) : '';
10918 $substitutionarray['__AMOUNT_MULTICURRENCY_TEXT__'] = (is_object($object) && isset($object->multicurrency_total_ttc)) ? dol_convertToWord($object->multicurrency_total_ttc, $outputlangs, '', true) : '';
10919 $substitutionarray['__AMOUNT_MULTICURRENCY_TEXTCURRENCY__'] = (is_object($object) && isset($object->multicurrency_total_ttc)) ? dol_convertToWord($object->multicurrency_total_ttc, $outputlangs, $object->multicurrency_code, true) : '';
10920 $substitutionarray['__MULTICURRENCY_CODE__'] = (is_object($object) && isset($object->multicurrency_code)) ? $object->multicurrency_code : '';
10921 // TODO Add other keys for foreign multicurrency
10922
10923 // For backward compatibility
10924 if ($onlykey != 2) {
10925 $substitutionarray['__TOTAL_TTC__'] = is_object($object) ? $object->total_ttc : '';
10926 $substitutionarray['__TOTAL_HT__'] = is_object($object) ? $object->total_ht : '';
10927 $substitutionarray['__TOTAL_VAT__'] = is_object($object) ? (isset($object->total_vat) ? $object->total_vat : $object->total_tva) : '';
10928 }
10929 }
10930
10931
10932 if ((empty($exclude) || !in_array('date', $exclude)) && (empty($include) || in_array('date', $include))) {
10933 include_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php';
10934
10935 $now = dol_now();
10936
10937 $tmp = dol_getdate($now, true);
10938 $tmp2 = dol_get_prev_day($tmp['mday'], $tmp['mon'], $tmp['year']);
10939 $tmp3 = dol_get_prev_month($tmp['mon'], $tmp['year']);
10940 $tmp4 = dol_get_next_day($tmp['mday'], $tmp['mon'], $tmp['year']);
10941 $tmp5 = dol_get_next_month($tmp['mon'], $tmp['year']);
10942
10943 $daytext = $outputlangs->trans('Day' . $tmp['wday']);
10944
10945 $substitutionarray = array_merge($substitutionarray, array(
10946 '__NOW_TMS__' => (string) $now, // Must be the string that represent the int
10947 '__NOW_TMS_YMD__' => dol_print_date($now, 'day', 'auto', $outputlangs),
10948 '__DAY__' => (string) $tmp['mday'],
10949 '__DAY_TEXT__' => $daytext, // Monday
10950 '__DAY_TEXT_SHORT__' => dol_trunc($daytext, 3, 'right', 'UTF-8', 1), // Mon
10951 '__DAY_TEXT_MIN__' => dol_trunc($daytext, 1, 'right', 'UTF-8', 1), // M
10952 '__MONTH__' => (string) $tmp['mon'],
10953 '__MONTH_TEXT__' => $outputlangs->transnoentitiesnoconv('Month' . sprintf("%02d", $tmp['mon'])),
10954 '__MONTH_TEXT_SHORT__' => $outputlangs->transnoentitiesnoconv('MonthShort' . sprintf("%02d", $tmp['mon'])),
10955 '__MONTH_TEXT_MIN__' => $outputlangs->transnoentitiesnoconv('MonthVeryShort' . sprintf("%02d", $tmp['mon'])),
10956 '__YEAR__' => (string) $tmp['year'],
10957 '__YEAR_PREVIOUS_MONTH__' => (string) $tmp3['year'],
10958 '__YEAR_NEXT_MONTH__' => (string) $tmp5['year'],
10959 '__PREVIOUS_DAY__' => (string) $tmp2['day'],
10960 '__PREVIOUS_MONTH__' => (string) $tmp3['month'],
10961 '__PREVIOUS_MONTH_TEXT__' => $outputlangs->transnoentitiesnoconv('Month' . sprintf("%02d", $tmp3['month'])),
10962 '__PREVIOUS_MONTH_TEXT_SHORT__' => $outputlangs->transnoentitiesnoconv('MonthShort' . sprintf("%02d", $tmp3['month'])),
10963 '__PREVIOUS_MONTH_TEXT_MIN__' => $outputlangs->transnoentitiesnoconv('MonthVeryShort' . sprintf("%02d", $tmp3['month'])),
10964 '__PREVIOUS_YEAR__' => (string) ($tmp['year'] - 1),
10965 '__NEXT_DAY__' => (string) $tmp4['day'],
10966 '__NEXT_MONTH__' => (string) $tmp5['month'],
10967 '__NEXT_MONTH_TEXT__' => $outputlangs->transnoentitiesnoconv('Month' . sprintf("%02d", $tmp5['month'])),
10968 '__NEXT_MONTH_TEXT_SHORT__' => $outputlangs->transnoentitiesnoconv('MonthShort' . sprintf("%02d", $tmp5['month'])),
10969 '__NEXT_MONTH_TEXT_MIN__' => $outputlangs->transnoentitiesnoconv('MonthVeryShort' . sprintf("%02d", $tmp5['month'])),
10970 '__NEXT_YEAR__' => (string) ($tmp['year'] + 1),
10971 ));
10972 }
10973
10974 if (isModEnabled('multicompany')) {
10975 $substitutionarray = array_merge($substitutionarray, array('__ENTITY_ID__' => $conf->entity));
10976 }
10977 if ((empty($exclude) || !in_array('system', $exclude)) && (empty($include) || in_array('user', $include))) {
10978 $substitutionarray['__DOL_MAIN_URL_ROOT__'] = DOL_MAIN_URL_ROOT;
10979 $substitutionarray['__(AnyTranslationKey)__'] = $outputlangs->transnoentitiesnoconv('TranslationOfKey');
10980 $substitutionarray['__(AnyTranslationKey|langfile)__'] = $outputlangs->transnoentitiesnoconv('TranslationOfKey') . ' (load also language file before)';
10981 $substitutionarray['__[AnyConstantKey]__'] = $outputlangs->transnoentitiesnoconv('ValueOfConstantKey');
10982 }
10983
10984 // Note: The lazyload variables are replaced only during the call by make_substitutions, and only if necessary
10985
10986 return $substitutionarray;
10987}
10988
11005function make_substitutions($text, $substitutionarray, $outputlangs = null, $converttextinhtmlifnecessary = 0)
11006{
11007 global $db, $langs;
11008
11009 if (!is_array($substitutionarray)) {
11010 return 'ErrorBadParameterSubstitutionArrayWhenCalling_make_substitutions';
11011 }
11012
11013 if (empty($outputlangs)) {
11014 $outputlangs = $langs;
11015 }
11016
11017 // Is initial text HTML or simple text ?
11018 $msgishtml = 0;
11019 if (dol_textishtml($text, 1)) {
11020 $msgishtml = 1;
11021 }
11022
11023 // Make substitution for language keys: __(AnyTranslationKey)__ or __(AnyTranslationKey|langfile)__
11024 if (is_object($outputlangs)) {
11025 $reg = array();
11026 while (preg_match('/__\‍(([^\‍)]+)\‍)__/', $text, $reg)) {
11027 // If key is __(TranslationKey|langfile)__, then force load of langfile.lang
11028 $tmp = explode('|', $reg[1]);
11029 if (!empty($tmp[1])) {
11030 $outputlangs->load($tmp[1]);
11031 }
11032
11033 $value = $outputlangs->transnoentitiesnoconv($reg[1]);
11034
11035 if (empty($converttextinhtmlifnecessary)) {
11036 // convert $newval into HTML is necessary
11037 $text = preg_replace('/__\‍(' . preg_quote($reg[1], '/') . '\‍)__/', $msgishtml ? dol_htmlentitiesbr($value) : $value, $text);
11038 } else {
11039 if (! $msgishtml) {
11040 $valueishtml = dol_textishtml($value, 1);
11041 //var_dump("valueishtml=".$valueishtml);
11042
11043 if ($valueishtml) {
11044 $text = dol_htmlentitiesbr($text);
11045 $msgishtml = 1;
11046 }
11047 } else {
11048 $value = dol_nl2br((string) $value);
11049 }
11050
11051 $text = preg_replace('/__\‍(' . preg_quote($reg[1], '/') . '\‍)__/', $value, $text);
11052 }
11053 }
11054 }
11055
11056 // Make substitution for constant keys.
11057 // Must be after the substitution of translation, so if the text of translation contains a string __[xxx]__, it is also converted.
11058 $reg = array();
11059 while (preg_match('/__\[([^\]]+)\]__/', $text, $reg)) {
11060 $originalkeyfound = $reg[1];
11061 $keyfound = preg_replace('/\|urlencode$/', '', $originalkeyfound);
11062
11063 if (isASecretKey($keyfound)) {
11064 $value = '*****forbidden*****';
11065 } else {
11066 $value = getDolGlobalString($keyfound);
11067 // Execute some functions on value of substitution key
11068 if (preg_match('/\|urlencode$/', $originalkeyfound)) {
11069 $value = urlencode($value);
11070 }
11071 }
11072
11073 if (empty($converttextinhtmlifnecessary)) {
11074 // convert $newval into HTML is necessary
11075 $text = preg_replace('/__\[' . preg_quote($originalkeyfound, '/') . '\]__/', $msgishtml ? dol_htmlentitiesbr($value) : $value, $text);
11076 } else {
11077 if (! $msgishtml) {
11078 $valueishtml = dol_textishtml($value, 1);
11079
11080 if ($valueishtml) {
11081 $text = dol_htmlentitiesbr($text);
11082 $msgishtml = 1;
11083 }
11084 } else {
11085 $value = dol_nl2br((string) $value);
11086 }
11087
11088 $text = preg_replace('/__\[' . preg_quote($originalkeyfound, '/') . '\]__/', $value, $text);
11089 }
11090 }
11091
11092 // Make substitution for array $substitutionarray
11093 foreach ($substitutionarray as $key => $value) {
11094 if (!isset($value)) {
11095 continue; // If value is null, it same than not having substitution key at all into array, we do not replace.
11096 }
11097
11098 if (getDolGlobalString('MAIN_MAIL_DO_NOT_USE_SIGN') && ($key == '__USER_SIGNATURE__' || $key == '__SENDEREMAIL_SIGNATURE__')) {
11099 $value = ''; // Protection
11100 }
11101
11102 if (empty($converttextinhtmlifnecessary)) {
11103 $text = str_replace((string) $key, (string) $value, $text); // Cast to string is needed when value is 123.5 for example
11104 } else {
11105 if (! $msgishtml) {
11106 $valueishtml = dol_textishtml($value, 1);
11107
11108 if ($valueishtml) {
11109 $text = dol_htmlentitiesbr($text);
11110 $msgishtml = 1;
11111 }
11112 } else {
11113 $value = dol_nl2br((string) $value);
11114 }
11115 $text = str_replace((string) $key, (string) $value, $text); // Cast to string is needed 123.5 for example
11116 }
11117 }
11118
11119 /*
11120 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
11121 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.
11122 If no, we don't need to make replacement, so we do nothing.
11123 If yes, we can make the substitution:
11124
11125 include_once $path;
11126 $tmpobj = new $class($db);
11127 $valuetouseforsubstitution = $tmpobj->$method($id, '__XXX__');
11128 And make the replacement of "__XXX__@lazyload" with $valuetouseforsubstitution
11129 */
11130 $memory_object_list = array();
11131 foreach ($substitutionarray as $key => $value) {
11132 $lazy_load_arr = array();
11133 if (preg_match('/(__[A-Z\_]+__)@lazyload$/', $key, $lazy_load_arr)) {
11134 if (isset($lazy_load_arr[1]) && !empty($lazy_load_arr[1])) {
11135 $key_to_substitute = $lazy_load_arr[1];
11136 if (preg_match('/' . preg_quote($key_to_substitute, '/') . '/', $text)) {
11137 $param_arr = explode(':', (string) $value);
11138 // path:class:method:id
11139 if (count($param_arr) >= 4) {
11140 $path = $param_arr[0];
11141 $class = $param_arr[1];
11142 $method = $param_arr[2];
11143 $id = (int) $param_arr[3];
11144 $keyinarrayresult = empty($param_arr[4]) ? '' : $param_arr[4];
11145
11146 // load class file and init object list in memory
11147 if (!isset($memory_object_list[$class])) {
11148 if (dol_is_file(DOL_DOCUMENT_ROOT . $path)) {
11149 require_once DOL_DOCUMENT_ROOT . $path;
11150 if (class_exists($class)) {
11151 $memory_object_list[$class] = array(
11152 'list' => array(),
11153 );
11154 }
11155 }
11156 }
11157
11158 // fetch object and set substitution
11159 if (isset($memory_object_list[$class]) && isset($memory_object_list[$class]['list'])) {
11160 if (method_exists($class, $method)) {
11161 if (!isset($memory_object_list[$class]['list'][$id])) {
11162 $tmpobj = new $class($db);
11163 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
11164 $tmpvaluetouseforsubstitution = $tmpobj->$method($id, $key_to_substitute);
11165 $memory_object_list[$class]['list'][$id] = $tmpobj;
11166 } else {
11167 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
11168 $tmpobj = $memory_object_list[$class]['list'][$id];
11169 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
11170 $tmpvaluetouseforsubstitution = $tmpobj->$method($id, $key_to_substitute, true);
11171 }
11172
11173 if ($keyinarrayresult) {
11174 $valuetouseforsubstitution = (string) $tmpvaluetouseforsubstitution[$keyinarrayresult]; // Cast to string in case value is 123.5 for example
11175 } else {
11176 $valuetouseforsubstitution = (string) $tmpvaluetouseforsubstitution; // Cast to string in case value is 123.5 for example
11177 }
11178 $text = str_replace((string) $key_to_substitute, $valuetouseforsubstitution, $text);
11179 }
11180 }
11181 }
11182 }
11183 }
11184 }
11185 }
11186
11187 return $text;
11188}
11189
11202function complete_substitutions_array(&$substitutionarray, $outputlangs, $object = null, $parameters = null, $callfunc = "completesubstitutionarray")
11203{
11204 global $conf, $user;
11205
11206 require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
11207
11208 // Note: substitution key for each extrafields, using key __EXTRA_XXX__ is already available into the getCommonSubstitutionArray used to build the substitution array.
11209
11210 // Check if there is external substitution to do, requested by plugins
11211 $dirsubstitutions = array_merge(array(), (array) $conf->modules_parts['substitutions']);
11212
11213 foreach ($dirsubstitutions as $reldir) {
11214 $dir = dol_buildpath($reldir, 0);
11215
11216 // Check if directory exists
11217 if (!dol_is_dir($dir)) {
11218 continue;
11219 }
11220
11221 $substitfiles = dol_dir_list($dir, 'files', 0, 'functions_');
11222 foreach ($substitfiles as $substitfile) {
11223 $reg = array();
11224 if (preg_match('/functions_(.*)\.lib\.php/i', $substitfile['name'], $reg)) {
11225 $module = $reg[1];
11226
11227 dol_syslog("Library " . $substitfile['name'] . " found into " . $dir);
11228 // Include the user's functions file
11229 require_once $dir . $substitfile['name'];
11230 // Call the user's function, and only if it is defined
11231 $function_name = $module . "_" . $callfunc;
11232 if (function_exists($function_name)) {
11233 $function_name($substitutionarray, $outputlangs, $object, $parameters);
11234 }
11235 }
11236 }
11237 }
11238 if (getDolGlobalString('ODT_ENABLE_ALL_TAGS_IN_SUBSTITUTIONS')) {
11239 // to list all tags in odt template
11240 $tags = '';
11241 foreach ($substitutionarray as $key => $value) {
11242 $tags .= '{' . $key . '} => ' . $value . "\n";
11243 }
11244 $substitutionarray = array_merge($substitutionarray, array('__ALL_TAGS__' => $tags));
11245 }
11246}
11247
11257function print_date_range($date_start, $date_end, $format = '', $outputlangs = null)
11258{
11259 print get_date_range($date_start, $date_end, $format, $outputlangs);
11260}
11261
11272function get_date_range($date_start, $date_end, $format = '', $outputlangs = null, $withparenthesis = 1)
11273{
11274 global $langs;
11275
11276 $out = '';
11277
11278 if (!is_object($outputlangs)) {
11279 $outputlangs = $langs;
11280 }
11281
11282 if ($date_start && $date_end) {
11283 $out .= ($withparenthesis ? ($withparenthesis == 1 ? ' ' : '').'(' : '') . $outputlangs->transnoentitiesnoconv('DateFromTo', dol_print_date($date_start, $format, false, $outputlangs), dol_print_date($date_end, $format, false, $outputlangs)) . ($withparenthesis ? ')' : '');
11284 }
11285 if ($date_start && !$date_end) {
11286 $out .= ($withparenthesis ? ($withparenthesis == 1 ? ' ' : '').'(' : '') . $outputlangs->transnoentitiesnoconv('DateFrom', dol_print_date($date_start, $format, false, $outputlangs)) . ($withparenthesis ? ')' : '');
11287 }
11288 if (!$date_start && $date_end) {
11289 $out .= ($withparenthesis ? ($withparenthesis == 1 ? ' ' : '').'(' : '') . $outputlangs->transnoentitiesnoconv('DateUntil', dol_print_date($date_end, $format, false, $outputlangs)) . ($withparenthesis ? ')' : '');
11290 }
11291
11292 return $out;
11293}
11294
11303function dolGetFirstLastname($firstname, $lastname, $nameorder = -1)
11304{
11305 $ret = '';
11306 // If order not defined, we use the setup
11307 if ($nameorder < 0) {
11308 $nameorder = (!getDolGlobalString('MAIN_FIRSTNAME_NAME_POSITION') ? 1 : 0);
11309 }
11310 if ($nameorder == 1) {
11311 $ret .= $firstname;
11312 if ($firstname && $lastname) {
11313 $ret .= ' ';
11314 }
11315 $ret .= $lastname;
11316 } elseif ($nameorder == 2 || $nameorder == 3) {
11317 $ret .= $firstname;
11318 if (empty($ret) && $nameorder == 3) {
11319 $ret .= $lastname;
11320 }
11321 } else { // 0, 4 or 5
11322 $ret .= $lastname;
11323 if (empty($ret) && $nameorder == 5) {
11324 $ret .= $firstname;
11325 }
11326 if ($nameorder == 0) {
11327 if ($firstname && $lastname) {
11328 $ret .= ' ';
11329 }
11330 $ret .= $firstname;
11331 }
11332 }
11333 return $ret;
11334}
11335
11336
11349function setEventMessage($mesgs, $style = 'mesgs', $noduplicate = 0, $attop = 0)
11350{
11351 //dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING); This is not deprecated, it is used by setEventMessages function
11352 if (!is_array($mesgs)) {
11353 $mesgs = trim((string) $mesgs);
11354 // If mesgs is a not an empty string
11355 if ($mesgs) {
11356 if (!empty($noduplicate) && isset($_SESSION['dol_events'][$style]) && in_array($mesgs, $_SESSION['dol_events'][$style])) {
11357 return;
11358 }
11359 if ($attop) {
11360 array_unshift($_SESSION['dol_events'][$style], $mesgs);
11361 } else {
11362 $_SESSION['dol_events'][$style][] = $mesgs;
11363 }
11364 }
11365 } else {
11366 // If mesgs is an array
11367 foreach ($mesgs as $mesg) {
11368 $mesg = trim((string) $mesg);
11369 if ($mesg) {
11370 if (!empty($noduplicate) && isset($_SESSION['dol_events'][$style]) && in_array($mesg, $_SESSION['dol_events'][$style])) {
11371 return;
11372 }
11373 if ($attop) {
11374 array_unshift($_SESSION['dol_events'][$style], $mesgs);
11375 } else {
11376 $_SESSION['dol_events'][$style][] = $mesg;
11377 }
11378 }
11379 }
11380 }
11381}
11382
11396function setEventMessages($mesg, $mesgs, $style = 'mesgs', $messagekey = '', $noduplicate = 0, $attop = 0)
11397{
11398 if (empty($mesg) && empty($mesgs)) {
11399 dol_syslog("Try to add a message in stack, but value to add is empty message" . getCallerInfoString(), LOG_WARNING);
11400 } else {
11401 if ($messagekey) {
11402 // Complete message with a js link to set a cookie "DOLHIDEMESSAGE".$messagekey;
11403 // TODO
11404 $mesg .= '';
11405 }
11406 if (empty($messagekey) || empty($_COOKIE["DOLUSER_HIDEMESSAGE" . $messagekey])) {
11407 if (!in_array((string) $style, array('mesgs', 'warnings', 'errors'))) {
11408 dol_print_error(null, 'Bad parameter style=' . $style . ' for setEventMessages');
11409 }
11410 if (empty($mesgs)) {
11411 setEventMessage((string) $mesg, $style, $noduplicate, $attop);
11412 } else {
11413 if (!empty($mesg) && !in_array($mesg, $mesgs)) {
11414 setEventMessage($mesg, $style, $noduplicate, $attop); // Add message string if not already into array
11415 }
11416 setEventMessage($mesgs, $style, $noduplicate, $attop);
11417 }
11418 }
11419 }
11420}
11421
11431function dol_htmloutput_events($disabledoutputofmessages = 0)
11432{
11433 // Show mesgs
11434 if (isset($_SESSION['dol_events']['mesgs'])) {
11435 if (empty($disabledoutputofmessages)) {
11436 dol_htmloutput_mesg('', $_SESSION['dol_events']['mesgs']);
11437 }
11438 unset($_SESSION['dol_events']['mesgs']);
11439 }
11440 // Show errors
11441 if (isset($_SESSION['dol_events']['errors'])) {
11442 if (empty($disabledoutputofmessages)) {
11443 dol_htmloutput_mesg('', $_SESSION['dol_events']['errors'], 'error');
11444 }
11445 unset($_SESSION['dol_events']['errors']);
11446 }
11447
11448 // Show warnings
11449 if (isset($_SESSION['dol_events']['warnings'])) {
11450 if (empty($disabledoutputofmessages)) {
11451 dol_htmloutput_mesg('', $_SESSION['dol_events']['warnings'], 'warning');
11452 }
11453 unset($_SESSION['dol_events']['warnings']);
11454 }
11455}
11456
11471function get_htmloutput_mesg($mesgstring = '', $mesgarray = [], $style = 'ok', $keepembedded = 0)
11472{
11473 global $conf, $langs;
11474
11475 $ret = 0;
11476 $return = '';
11477 $out = '';
11478 $divstart = $divend = '';
11479
11480 // If inline message with no format, we add it.
11481 if ((empty($conf->use_javascript_ajax) || getDolGlobalString('MAIN_DISABLE_JQUERY_JNOTIFY') || $keepembedded) && !preg_match('/<div class=".*">/i', $out)) {
11482 $divstart = '<div class="' . $style . ' clearboth">';
11483 $divend = '</div>';
11484 }
11485
11486 if ((is_array($mesgarray) && count($mesgarray)) || $mesgstring) {
11487 $langs->load("errors");
11488 $out .= $divstart;
11489 if (is_array($mesgarray) && count($mesgarray)) {
11490 foreach ($mesgarray as $message) {
11491 $ret++;
11492 $out .= $langs->trans($message);
11493 if ($ret < count($mesgarray)) {
11494 $out .= "<br>\n";
11495 }
11496 }
11497 }
11498 if ($mesgstring) {
11499 $ret++;
11500 $out .= $langs->trans($mesgstring);
11501 }
11502 $out .= $divend;
11503 }
11504
11505 if ($out) {
11506 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_DISABLE_JQUERY_JNOTIFY') && empty($keepembedded)) {
11507 $return = '<script nonce="' . getNonce() . '">
11508 $(document).ready(function() {
11509 /* jnotify(message, preset of message type, keepmessage) */
11510 $.jnotify("' . dol_escape_js($out) . '", "' . ($style == "ok" ? 3000 : $style) . '", ' . ($style == "ok" ? "false" : "true") . ',{ remove: function (){} } );
11511 });
11512 </script>';
11513 } else {
11514 $return = $out;
11515 }
11516 }
11517
11518 return $return;
11519}
11520
11532function get_htmloutput_errors($mesgstring = '', $mesgarray = array(), $keepembedded = 0)
11533{
11534 return get_htmloutput_mesg($mesgstring, $mesgarray, 'error', $keepembedded);
11535}
11536
11550function dol_htmloutput_mesg($mesgstring = '', $mesgarray = array(), $style = 'ok', $keepembedded = 0)
11551{
11552 if (empty($mesgstring) && (!is_array($mesgarray) || count($mesgarray) == 0)) {
11553 return;
11554 }
11555
11556 $iserror = 0;
11557 $iswarning = 0;
11558 if (is_array($mesgarray)) {
11559 foreach ($mesgarray as $val) {
11560 if ($val && preg_match('/class="error"/i', $val)) {
11561 $iserror++;
11562 break;
11563 }
11564 if ($val && preg_match('/class="warning"/i', $val)) {
11565 $iswarning++;
11566 break;
11567 }
11568 }
11569 } elseif ($mesgstring && preg_match('/class="error"/i', $mesgstring)) {
11570 $iserror++;
11571 } elseif ($mesgstring && preg_match('/class="warning"/i', $mesgstring)) {
11572 $iswarning++;
11573 }
11574 if ($style == 'error' || $style == 'errors') {
11575 $iserror++;
11576 }
11577 if ($style == 'warning' || $style == 'warnings') {
11578 $iswarning++;
11579 }
11580
11581 if ($iserror || $iswarning) {
11582 // Remove div from texts
11583 $mesgstring = preg_replace('/<\/div><div class="(error|warning)">/', '<br>', $mesgstring);
11584 $mesgstring = preg_replace('/<div class="(error|warning)">/', '', $mesgstring);
11585 $mesgstring = preg_replace('/<\/div>/', '', $mesgstring);
11586 // Remove div from texts array
11587 if (is_array($mesgarray)) {
11588 $newmesgarray = array();
11589 foreach ($mesgarray as $val) {
11590 if (is_string($val)) {
11591 $tmpmesgstring = preg_replace('/<\/div><div class="(error|warning)">/', '<br>', $val);
11592 $tmpmesgstring = preg_replace('/<div class="(error|warning)">/', '', $tmpmesgstring);
11593 $tmpmesgstring = preg_replace('/<\/div>/', '', $tmpmesgstring);
11594 $newmesgarray[] = $tmpmesgstring;
11595 } else {
11596 dol_syslog("Error call of dol_htmloutput_mesg with an array with a value that is not a string", LOG_WARNING);
11597 }
11598 }
11599 $mesgarray = $newmesgarray;
11600 }
11601 print get_htmloutput_mesg($mesgstring, $mesgarray, ($iserror ? 'error' : 'warning'), $keepembedded);
11602 } else {
11603 print get_htmloutput_mesg($mesgstring, $mesgarray, 'ok', $keepembedded);
11604 }
11605}
11606
11618function dol_htmloutput_errors($mesgstring = '', $mesgarray = array(), $keepembedded = 0)
11619{
11620 dol_htmloutput_mesg($mesgstring, $mesgarray, 'error', $keepembedded);
11621}
11622
11644function dol_sort_array(&$array, $index, $order = 'asc', $natsort = 0, $case_sensitive = 0, $keepindex = 0)
11645{
11646 // Clean parameters
11647 $order = strtolower($order);
11648
11649 if (is_array($array)) {
11650 $sizearray = count($array);
11651 if ($sizearray > 0) {
11652 // Build a temp array with sorting key as value
11653 $temp = array();
11654 foreach (array_keys($array) as $key) {
11655 $tmpmultikey = explode(',', $index);
11656 $newindex = $tmpmultikey[0];
11657 if (is_object($array[$key])) {
11658 $temp[$key] = empty($array[$key]->$newindex) ? 0 : $array[$key]->$newindex;
11659 // Add other keys
11660 if (!empty($tmpmultikey[1])) {
11661 $newindex = $tmpmultikey[1];
11662 $temp[$key] .= '__' . (empty($array[$key]->$newindex) ? 0 : $array[$key]->$newindex);
11663 }
11664 } else {
11665 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable,PhanTypeArraySuspicious,PhanTypeMismatchDimFetch
11666 $temp[$key] = empty($array[$key][$newindex]) ? 0 : $array[$key][$newindex];
11667 // Add other keys
11668 if (!empty($tmpmultikey[1])) {
11669 $newindex = $tmpmultikey[1];
11670 // @phan-suppress-next-line PhanTypeArraySuspicious,PhanTypeMismatchDimFetch
11671 $temp[$key] .= '__' . (empty($array[$key][$newindex]) ? 0 : $array[$key][$newindex]);
11672 }
11673 }
11674 if ($natsort == -1) {
11675 $temp[$key] = '___' . $temp[$key]; // We add a string at begin of value to force an alpha order when using asort.
11676 }
11677 }
11678 if (empty($natsort) || $natsort == -1) {
11679 if ($order == 'asc') {
11680 asort($temp);
11681 } else {
11682 arsort($temp);
11683 }
11684 } else {
11685 if ($case_sensitive) {
11686 natsort($temp);
11687 } else {
11688 natcasesort($temp); // natecasesort is not sensible to case
11689 }
11690 if ($order != 'asc') {
11691 $temp = array_reverse($temp, true);
11692 }
11693 }
11694
11695 $sorted = array();
11696
11697 foreach (array_keys($temp) as $key) {
11698 (is_numeric($key) && empty($keepindex)) ? $sorted[] = $array[$key] : $sorted[$key] = $array[$key];
11699 }
11700
11701 return $sorted;
11702 }
11703 }
11704 return $array;
11705}
11706
11707
11715function utf8_check($str)
11716{
11717 $str = (string) $str; // Sometimes string is an int.
11718
11719 // We must use here a binary strlen function (so not dol_strlen)
11720 $strLength = strlen($str);
11721 for ($i = 0; $i < $strLength; $i++) {
11722 if (ord($str[$i]) < 0x80) {
11723 continue; // 0bbbbbbb
11724 } elseif ((ord($str[$i]) & 0xE0) == 0xC0) {
11725 $n = 1; // 110bbbbb
11726 } elseif ((ord($str[$i]) & 0xF0) == 0xE0) {
11727 $n = 2; // 1110bbbb
11728 } elseif ((ord($str[$i]) & 0xF8) == 0xF0) {
11729 $n = 3; // 11110bbb
11730 } elseif ((ord($str[$i]) & 0xFC) == 0xF8) {
11731 $n = 4; // 111110bb
11732 } elseif ((ord($str[$i]) & 0xFE) == 0xFC) {
11733 $n = 5; // 1111110b
11734 } else {
11735 return false; // Does not match any model
11736 }
11737 for ($j = 0; $j < $n; $j++) { // n bytes matching 10bbbbbb follow ?
11738 if ((++$i == strlen($str)) || ((ord($str[$i]) & 0xC0) != 0x80)) {
11739 return false;
11740 }
11741 }
11742 }
11743 return true;
11744}
11745
11753function utf8_valid($str)
11754{
11755 /* 2 other methods to test if string is utf8
11756 $validUTF8 = mb_check_encoding($messagetext, 'UTF-8');
11757 $validUTF8b = ! (false === mb_detect_encoding($messagetext, 'UTF-8', true));
11758 */
11759 return preg_match('//u', $str) ? true : false;
11760}
11761
11762
11769function ascii_check($str)
11770{
11771 if (function_exists('mb_check_encoding')) {
11772 //if (mb_detect_encoding($str, 'ASCII', true) return false;
11773 if (!mb_check_encoding($str, 'ASCII')) {
11774 return false;
11775 }
11776 } else {
11777 if (preg_match('/[^\x00-\x7f]/', $str)) {
11778 return false; // Contains a byte > 7f
11779 }
11780 }
11781
11782 return true;
11783}
11784
11785
11793function dol_osencode($str)
11794{
11795 $tmp = ini_get("unicode.filesystem_encoding");
11796 if (empty($tmp) && !empty($_SERVER["WINDIR"])) {
11797 $tmp = 'iso-8859-1'; // By default for windows
11798 }
11799 if (empty($tmp)) {
11800 $tmp = 'utf-8'; // By default for other
11801 }
11802 if (getDolGlobalString('MAIN_FILESYSTEM_ENCODING')) {
11803 $tmp = getDolGlobalString('MAIN_FILESYSTEM_ENCODING');
11804 }
11805
11806 if ($tmp == 'iso-8859-1') {
11807 return mb_convert_encoding($str, 'ISO-8859-1', 'UTF-8');
11808 }
11809 return $str;
11810}
11811
11812
11828function dol_getIdFromCode($db, $key, $tablename, $fieldkey = 'code', $fieldid = 'id', $entityfilter = 0, $filters = '', $useCache = true)
11829{
11830 global $conf;
11831
11832 // If key empty
11833 if ($key == '') {
11834 return 0;
11835 }
11836
11837 // Check in cache
11838 if ($useCache && isset($conf->cache['codeid'][$tablename][$key][$fieldid])) { // Can be defined to 0 or ''
11839 return $conf->cache['codeid'][$tablename][$key][$fieldid]; // Found in cache
11840 }
11841
11842 dol_syslog('dol_getIdFromCode (value for field ' . $fieldid . ' from key ' . $key . ' not found into cache)', LOG_DEBUG);
11843
11844 $sql = "SELECT " . $db->sanitize($fieldid) . " as valuetoget";
11845 $sql .= " FROM " . MAIN_DB_PREFIX . $db->sanitize($tablename);
11846 if ($fieldkey == 'id' || $fieldkey == 'rowid') {
11847 $sql .= " WHERE " . $db->sanitize($fieldkey) . " = " . ((int) $key);
11848 } else {
11849 $sql .= " WHERE " . $db->sanitize($fieldkey) . " = '" . $db->escape($key) . "'";
11850 }
11851 if (!empty($entityfilter)) {
11852 $sql .= " AND entity IN (" . getEntity($tablename) . ")";
11853 }
11854 if ($filters) {
11855 $sql .= $filters;
11856 }
11857
11858 $resql = $db->query($sql);
11859 if ($resql) {
11860 $obj = $db->fetch_object($resql);
11861 $valuetoget = '';
11862 if ($obj) {
11863 $valuetoget = $obj->valuetoget;
11864 $conf->cache['codeid'][$tablename][$key][$fieldid] = $valuetoget;
11865 } else {
11866 $conf->cache['codeid'][$tablename][$key][$fieldid] = '';
11867 }
11868 $db->free($resql);
11869
11870 return $valuetoget;
11871 } else {
11872 return -1;
11873 }
11874}
11875
11885function isStringVarMatching($var, $regextext, $matchrule = 1)
11886{
11887 // Tolerate callers (custom modules, older code) that already pass a full regex with delimiters
11888 // like '/^(aaa|bbb)/' instead of the bare body. Without this, the function would build
11889 // '/^/^(aaa|bbb)//' which trips preg_match() with 'Unknown modifier ^'.
11890 $regextext = preg_replace('#^/\^?#', '', (string) $regextext);
11891 $regextext = preg_replace('#\$?/[imsxuADSUXJ]*$#', '', $regextext);
11892
11893 if ($matchrule == 1) {
11894 if ($var == 'mainmenu') {
11895 global $mainmenu;
11896 return (preg_match('/^' . $regextext . '/', $mainmenu));
11897 } elseif ($var == 'leftmenu') {
11898 global $leftmenu;
11899 return (preg_match('/^' . $regextext . '/', $leftmenu));
11900 } else {
11901 return 'This variable is not accessible with dol_eval';
11902 }
11903 } else {
11904 return 'This value '.$matchrule.' for param $matchrule is not yet implemented';
11905 }
11906}
11907
11908
11918function verifCond($strToEvaluate, $onlysimplestring = '1')
11919{
11920 //print $strToEvaluate."<br>\n";
11921 $rights = true;
11922 if (isset($strToEvaluate) && $strToEvaluate !== '') {
11923 //var_dump($strToEvaluate);
11924 //$rep = dol_eval($strToEvaluate, 1, 0, '1'); // to show the error
11925 $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
11926
11927 // On string syntax error, dol_eval may return a string that start with 'Bad call of ...' or 'Bad string syntax to evaluate...' !!!
11928 //var_dump($strToEvaluate, $rep);
11929 $rights = (bool) $rep && (!is_string($rep) || strpos($rep, 'Bad call of') === false || strpos($rep, 'Bad string syntax to evaluate') === false);
11930 //var_dump($rights);
11931 }
11932 return $rights;
11933}
11934
11949function dol_eval($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestring = '1')
11950{
11951 if ($returnvalue != 1) {
11952 dol_syslog("Use of dol_eval with parameter returnvalue = 0 is now forbidden. Please fix this", LOG_ERR);
11953 }
11954
11955 if (getDolGlobalString("MAIN_USE_DOL_EVAL_NEW")) {
11956 return dol_eval_new($s);
11957 } else {
11958 return dol_eval_standard($s, $hideerrors, $onlysimplestring);
11959 }
11960}
11961
11972function dol_eval_new($s)
11973{
11974 // Only this global variables can be read by eval function and returned to caller
11975 global $conf, // Read of const is done with getDolGlobalString() but we need $conf->currency for example
11976 $db, $langs, $user, $website, $websitepage,
11977 $action, $mainmenu, $leftmenu,
11978 $mysoc,
11979 $objectoffield, // To allow the use of $objectoffield in computed fields
11980
11981 // Old variables used
11982 $object;
11983
11984 if (getDolGlobalString('MAIN_ALLOW_OLD_VAR_OBJ_IN_DOL_EVAL')) {
11985 global $obj; // To get $obj used into list when dol_eval() is used for computed fields and $obj is not yet $object
11986 }
11987
11988 // PHP < 7.4.0
11989 defined('T_COALESCE_EQUAL') || define('T_COALESCE_EQUAL', PHP_INT_MAX);
11990 defined('T_FN') || define('T_FN', PHP_INT_MAX);
11991
11992 // PHP < 8.0.0
11993 defined('T_ATTRIBUTE') || define('T_ATTRIBUTE', PHP_INT_MAX);
11994 defined('T_MATCH') || define('T_MATCH', PHP_INT_MAX);
11995 defined('T_NAME_FULLY_QUALIFIED') || define('T_NAME_FULLY_QUALIFIED', PHP_INT_MAX);
11996 defined('T_NAME_QUALIFIED') || define('T_NAME_QUALIFIED', PHP_INT_MAX);
11997 defined('T_NAME_RELATIVE') || define('T_NAME_RELATIVE', PHP_INT_MAX);
11998
11999 // PHP < 8.1.0
12000 defined('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG') || define('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG', PHP_INT_MAX);
12001 defined('T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG') || define('T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG', PHP_INT_MAX);
12002 defined('T_ENUM') || define('T_ENUM', PHP_INT_MAX);
12003 defined('T_READONLY') || define('T_READONLY', PHP_INT_MAX);
12004
12005 // PHP < 8.4.0
12006 defined('T_PRIVATE_SET') || define('T_PRIVATE_SET', PHP_INT_MAX);
12007 defined('T_PROTECTED_SET') || define('T_PROTECTED_SET', PHP_INT_MAX);
12008 defined('T_PUBLIC_SET') || define('T_PUBLIC_SET', PHP_INT_MAX);
12009
12010 $prohibited_token_ids = [
12011 /*
12012 * Prohibited int tokens
12013 */
12014
12015 // T_AND_EQUAL', 'T_ARRAY', 'T_ARRAY_CAST', 'T_AS',
12016 'T_ABSTRACT',
12017 'T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG',
12018 'T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG',
12019 'T_ATTRIBUTE',
12020 // 'T_BOOLEAN_AND', 'T_BOOLEAN_OR', 'T_BOOL_CAST', 'T_BREAK',
12021 'T_BAD_CHARACTER',
12022 // 'T_CASE', 'T_CLASS_C', 'T_CLONE', 'T_COALESCE', 'T_COALESCE_EQUAL', 'T_COMMENT', 'T_CONCAT_EQUAL',
12023 // 'T_CONSTANT_ENCAPSED_STRING', 'T_CONTINUE', 'T_CURLY_OPEN',
12024 'T_CALLABLE',
12025 'T_CATCH',
12026 'T_CLASS',
12027 'T_CLOSE_TAG',
12028 'T_CONST',
12029 // 'T_DEC', 'T_DEFAULT', 'T_DIV_EQUAL', 'T_DNUMBER', 'T_DO', 'T_DOC_COMMENT',
12030 // 'T_DOLLAR_OPEN_CURLY_BRACES', 'T_DOUBLE_ARROW', 'T_DOUBLE_CAST', 'T_DOUBLE_COLON',
12031 'T_DECLARE',
12032 'T_DIR',
12033 // 'T_ELLIPSIS', 'T_ELSE', 'T_ELSEIF', 'T_EMPTY', 'T_ENCAPSED_AND_WHITESPACE', 'T_ENDFOR',
12034 // 'T_ENDFOREACH', 'T_ENDIF', 'T_ENDSWITCH', 'T_ENDWHILE', 'T_END_HEREDOC',
12035 'T_ECHO',
12036 'T_ENDDECLARE',
12037 'T_ENUM',
12038 'T_EVAL',
12039 'T_EXIT',
12040 'T_EXTENDS',
12041 // 'T_FOR', 'T_FOREACH',
12042 'T_FILE',
12043 'T_FINAL',
12044 'T_FINALLY',
12045 'T_FN',
12046 'T_FUNCTION',
12047 'T_FUNC_C',
12048 'T_GLOBAL',
12049 'T_GOTO',
12050 'T_HALT_COMPILER',
12051 // 'T_IF', 'T_INC', 'T_INLINE_HTML', 'T_INSTANCEOF', 'T_INT_CAST', 'T_ISSET', 'T_IS_EQUAL', 'T_IS_GREATER_OR_EQUAL',
12052 // 'T_IS_IDENTICAL', 'T_IS_NOT_EQUAL', 'T_IS_NOT_IDENTICAL', 'T_IS_SMALLER_OR_EQUAL',
12053 'T_IMPLEMENTS',
12054 'T_INCLUDE',
12055 'T_INCLUDE_ONCE',
12056 'T_INSTEADOF',
12057 'T_INTERFACE',
12058 // 'T_LIST', 'T_LNUMBER', 'T_LOGICAL_AND', 'T_LOGICAL_OR', 'T_LOGICAL_XOR',
12059 'T_LINE',
12060 // 'T_MINUS_EQUAL', 'T_MOD_EQUAL', 'T_MUL_EQUAL',
12061 'T_METHOD_C',
12062 // 'T_NEW',
12063 // 'T_NS_SEPARATOR', 'T_NUM_STRING',
12064 'T_NAMESPACE',
12065 // 'T_NAME_FULLY_QUALIFIED', 'T_NAME_QUALIFIED', 'T_NAME_RELATIVE', 'T_NS_C',
12066 // 'T_OBJECT_CAST', 'T_OBJECT_OPERATOR', 'T_OR_EQUAL',
12067 'T_OPEN_TAG',
12068 'T_OPEN_TAG_WITH_ECHO',
12069 // 'T_PAAMAYIM_NEKUDOTAYIM', 'T_PLUS_EQUAL', 'T_POW', 'T_POW_EQUAL',
12070 'T_PRINT',
12071 'T_PRIVATE',
12072 'T_PROTECTED',
12073 'T_PUBLIC',
12074 // 'T_PROPERTY_C',
12075 'T_READONLY',
12076 'T_REQUIRE',
12077 'T_REQUIRE_ONCE',
12078 'T_RETURN',
12079 // 'T_SL', 'T_SL_EQUAL', 'T_SPACESHIP', 'T_SR', 'T_SR_EQUAL', 'T_START_HEREDOC', 'T_STATIC',
12080 // 'T_STRING', 'T_STRING_CAST', 'T_STRING_VARNAME', 'T_SWITCH',
12081 'T_STATIC',
12082 'T_THROW',
12083 'T_TRAIT',
12084 'T_TRAIT_C',
12085 'T_TRY',
12086 'T_UNSET',
12087 'T_UNSET_CAST',
12088 'T_USE',
12089 // 'T_VARIABLE',
12090 'T_VAR',
12091 // 'T_WHILE', 'T_WHITESPACE',
12092 // 'T_XOR_EQUAL',
12093 // 'T_YIELD', 'T_YIELD_FROM',
12094
12095 /*
12096 * Prohibited string tokens
12097 */
12098 ';',
12099 '`',
12100 ];
12101
12102 $prohibited_variables = [
12103 '$_COOKIE',
12104 '$_ENV',
12105 '$_FILES',
12106 '$GLOBALS',
12107 '$_GET',
12108 '$_POST',
12109 '$_REQUEST',
12110 '$_SERVER',
12111 '$_SESSION',
12112 ];
12113
12114 $prohibited_functions = [
12115 // 'base64_decode', 'rawurldecode', 'urldecode', 'str_rot13', 'hex2bin', // I haven't managed to inject anything with these functions yet, can someone confirm?
12116 // 'get_defined_functions', 'get_defined_vars', 'get_defined_constants', 'get_declared_classes', // Should we really block the admin from viewing these lists?
12117 'override_function',
12118 'session_id',
12119 'session_create_id',
12120 'session_regenerate_id',
12121 'call_user_func',
12122 'call_user_func_array', // PREVENT calling forbidden functions
12123 'exec',
12124 'passthru',
12125 'shell_exec',
12126 'system',
12127 'proc_open',
12128 'popen',
12129 'dol_eval',
12130 'dol_eval_new',
12131 'dol_eval_standard',
12132 'dol_contctdesc',
12133 'executeCLI',
12134 'verifCond',
12135 'GETPOST', // Native Dolibarr functions
12136 'create_function',
12137 'assert',
12138 'mb_ereg_replace',
12139 'mb_eregi_replace', // function with eval capabilities
12140 'dol_compress_dir',
12141 'dol_decode',
12142 'dol_delete_file',
12143 'dol_delete_dir',
12144 'dol_delete_dir_recursive',
12145 'dol_copy',
12146 'archiveOrBackupFile', // more dolibarr functions
12147 'fopen',
12148 'file_put_contents',
12149 'fputs',
12150 'fputscsv',
12151 'fwrite',
12152 'fpassthru',
12153 'mkdir',
12154 'rmdir',
12155 'symlink',
12156 'touch',
12157 'unlink',
12158 'umask', // PHP functions related to file operations
12159 'invoke',
12160 'invokeArgs', // Method of ReflectionFunction to execute a function
12161 'filter_input',
12162 'filter_input_array',
12163 'GETPOST', // PREVENT CODE INJECTION
12164 ];
12165
12166 $prohibited_token_arrangements = [
12167 // Variable functions « $a( », « "$a"( », « 'FN_NAME'( », ('FN_NAME')()
12168 ' T_VARIABLE ( ',
12169 ' " ( ',
12170 ' \' ( ',
12171 ' T_CONSTANT_ENCAPSED_STRING ( ',
12172 ' ) ( ',
12173 ];
12174
12175 $tokens = token_get_all("<?php return {$s};", TOKEN_PARSE);
12176
12177 $tokens_arrangement = ' ';
12178
12179 for ($i = 2, $c = count($tokens) - 1; $i < $c; ++$i) { // ignore <?php return and ;
12180 if (is_array($tokens[$i])) {
12181 $token_id = $tokens[$i][0];
12182 $token_value = $tokens[$i][1];
12183 $token_name = token_name($tokens[$i][0]);
12184 } else {
12185 $token_id = $tokens[$i];
12186 $token_value = $tokens[$i];
12187 $token_name = $tokens[$i];
12188 }
12189
12190 // Ignore whitespaces
12191 if (T_WHITESPACE === $token_id) {
12192 continue;
12193 }
12194
12195 // Keep history to check arrangements
12196 $tokens_arrangement .= "{$token_name} ";
12197
12198 // Prohibited Variables
12199 if (
12200 T_VARIABLE === $token_id
12201 && in_array($token_value, $prohibited_variables, true)
12202 ) {
12203 return "« {$token_value} » is prohibited in « {$s} »";
12204 }
12205
12206 // Prohibited Functions
12207 if (
12208 T_STRING === $token_id
12209 && in_array($token_value, $prohibited_functions, true)
12210 ) {
12211 return "« {$token_value} » is prohibited in « {$s} »";
12212 }
12213 }
12214
12215 // Prohibited Token IDs
12216 $maxi = count($prohibited_token_ids);
12217 for ($i = 0; $i < $maxi; ++$i) {
12218 if (false !== strpos($tokens_arrangement, " {$prohibited_token_ids[$i]} ")) {
12219 return "« {$prohibited_token_ids[$i]} » is prohibited in « {$s} »";
12220 }
12221 }
12222
12223 // Prohibited token arrangements
12224 $maxi = count($prohibited_token_arrangements);
12225 for ($i = 0; $i < $maxi; ++$i) {
12226 if (false !== strpos($tokens_arrangement, $prohibited_token_arrangements[$i])) {
12227 return "« {$prohibited_token_arrangements[$i]} » is prohibited in « {$s} »";
12228 }
12229 }
12230
12231 // Return result
12232 try {
12233 return @eval("return {$s};") ?? '';
12234 } catch (Throwable $ex) {
12235 return "Exception during evaluation: " . $s . " - " . $ex->getMessage();
12236 }
12237}
12238
12253function dol_eval_standard($s, $hideerrors = 1, $onlysimplestring = '1')
12254{
12255 // Only this global variables can be read by eval function and returned to caller
12256 // The less we have, the better it is.
12257
12258 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()
12259 global $db, $langs, $user, $website, $websitepage;
12260 global $action, $mainmenu, $leftmenu;
12261 global $mysoc;
12262 global $objectoffield; // To allow the use of $objectoffield in computed fields
12263 global $object;
12264
12265 // Old variables (deprecated since v23)
12266 if (getDolGlobalString('MAIN_ALLOW_OLD_VAR_OBJ_IN_DOL_EVAL')) {
12267 global $obj; // To get $obj used into list when dol_eval() is used for computed fields and $obj is not yet $objectoffield
12268 }
12269
12270 $isObBufferActive = false; // When true, the ObBuffer must be cleaned in the exception handler
12271 if ($onlysimplestring == '0') { // '0' is deprecated, we process it as the more secured '1'
12272 $onlysimplestring = '1';
12273 }
12274 if (!in_array($onlysimplestring, array('1', '2'))) {
12275 return "Bad call of dol_eval. Parameter onlysimplestring must be '1' or '2'.";
12276 }
12277 if (!is_scalar($s)) {
12278 return "Bad call of dol_eval. First parameter must be a string, found ".var_export($s, true);
12279 }
12280
12281 try {
12282 global $dolibarr_main_restrict_eval_methods;
12283
12284 // Set $dolibarr_main_restrict_eval_methods_array
12285 if (!isset($dolibarr_main_restrict_eval_methods)) {
12286 $dolibarr_main_restrict_eval_methods = 'getDolGlobalString, getDolGlobalInt, getDolCurrency, getDolEntity, getDolDBType, fetchNoCompute, hasRight, isAdmin, isExternalUser, isModEnabled, isStringVarMatching, abs, min, max, round, dol_now, preg_match';
12287 }
12288 //print '$dolibarr_main_restrict_eval_methods = '.$dolibarr_main_restrict_eval_methods."\n";
12289 $dolibarr_main_restrict_eval_methods_array = explode(',', str_replace(" ", "", $dolibarr_main_restrict_eval_methods));
12290
12291 // Test on dangerous char (used for RCE), we allow only characters to make PHP variable testing
12292 // We must accept with 1: '1 && getDolGlobalInt("doesnotexist1") && getDolGlobalString("MAIN_FEATURES_LEVEL")'
12293 // We must accept with 1: '$user->hasRight("cabinetmed", "read") && !$objectoffield->canvas == "patient@cabinetmed"'
12294 // 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"
12295
12296 // Check if there is dynamic call (first we check chars are all into a whitelist chars)
12297 $specialcharsallowed = '^$_+-.*>&|=!?():"\',/@';
12298 if ($onlysimplestring == '2') {
12299 $specialcharsallowed .= '<[]'; // Later we check that < has space before and after
12300 }
12301 global $dolibarr_main_allow_unsecured_special_chars_in_dol_eval;
12302 if (!empty($dolibarr_main_allow_unsecured_special_chars_in_dol_eval)) {
12303 $specialcharsallowed .= (string) $dolibarr_main_allow_unsecured_special_chars_in_dol_eval;
12304 }
12305 if (preg_match('/[^a-z0-9\s' . preg_quote($specialcharsallowed, '/') . ']/i', $s)) {
12306 return 'Bad string syntax to evaluate (found chars that are not chars for a simple one line clean eval string): ' . $s;
12307 }
12308
12309 // Check if we found a | without a space before and after
12310 /* Disabled to allow preg_match('/(AAA|BBB)/')
12311 $tmps = str_replace(' || ', '__XXX__', $s);
12312 if (strpos($tmps, '|') !== false) {
12313 return 'Bad string syntax to evaluate (The char | can be used only when duplicated || with a space before and after): ' . $s;
12314 }
12315 */
12316
12317 // Check if there is PHP comments (can be used to obfuscate code)
12318 if (strpos($s, '/*') !== false || strpos($s, '//') !== false) {
12319 return 'Bad string syntax to evaluate (The comment string /* and // are not allowed): ' . $s;
12320 }
12321
12322 // Check if we found a ? without a space before and after
12323 $tmps = str_replace(' ? ', '__XXX__', $s);
12324 if (strpos($tmps, '?') !== false) {
12325 return 'Bad string syntax to evaluate (The char ? can be used only with a space before and after): ' . $s;
12326 }
12327
12328 // Check if there is a < or <= without spaces after
12329 if (preg_match('/<=?[^\s]/', $s)) {
12330 return 'Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found a < or <= without space after): ' . $s;
12331 }
12332
12333 // Check if there is dynamic call (first we use black list patterns)
12334 if (preg_match('/\$[\w]*\s*\‍(/', $s)) {
12335 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;
12336 }
12337
12338 if (empty($dolibarr_main_restrict_eval_methods)) {
12339 // If $dolibarr_main_restrict_eval_methods was set to '', we must check if we try dynamic call
12340
12341 // First we remove white list pattern of using parenthesis then testing if one open parenthesis exists
12342 $savescheck = '';
12343 $scheck = $s;
12344 while ($scheck && $savescheck != $scheck) {
12345 $savescheck = $scheck;
12346 $scheck = preg_replace('/->[a-zA-Z0-9_]+\‍(/', '->__METHOD__', $scheck); // accept parenthesis in '...->method(...'
12347 $scheck = preg_replace('/::[a-zA-Z0-9_]+\‍(/', '->__METHOD__', $scheck); // accept parenthesis in '...::method(...'
12348 $scheck = preg_replace('/^\‍(+/', '__PARENTHESIS__ ', $scheck); // accept parenthesis in '(...'. Must replace with "__PARENTHESIS__ with a space after "to allow following substitutions
12349 $scheck = preg_replace('/\&\&\s+\‍(/', '__ANDPARENTHESIS__ ', $scheck); // accept parenthesis in '&& ('. Must replace with "__PARENTHESIS__ with a space after" to allow following substitutions
12350 $scheck = preg_replace('/\|\|\s+\‍(/', '__ORPARENTHESIS__ ', $scheck); // accept parenthesis in '|| ('. Must replace with "__PARENTHESIS__ with a space after" to allow following substitutions
12351 $scheck = preg_replace('/^!?[a-zA-Z0-9_]+\‍(/', '__FUNCTION__', $scheck); // accept parenthesis in 'function(' and '!function('
12352 $scheck = preg_replace('/\s!?[a-zA-Z0-9_]+\‍(/', '__FUNCTION__', $scheck); // accept parenthesis in '... function(' and '... !function('
12353 $scheck = preg_replace('/^!\‍(/', '__NOTANDPARENTHESIS__', $scheck); // accept parenthesis in '!('
12354 $scheck = preg_replace('/\s!\‍(/', ' __NOTANDPARENTHESIS__', $scheck); // accept parenthesis in '... !('
12355 $scheck = preg_replace('/(\^|\')\‍(/', '__REGEXSTART__', $scheck); // To allow preg_match('/^(aaa|bbb)/'... or isStringVarMatching('leftmenu', '(aaa|bbb)')
12356 }
12357 //print 'scheck='.$scheck." : ".strpos($scheck, '(')."<br>\n";
12358
12359 // Now test if it remains 1 open parenthesis.
12360 if (strpos($scheck, '(') !== false) {
12361 return 'Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found call of a function or method without using the direct name of the function): ' . $s;
12362 }
12363 }
12364
12365 if (strpos($s, '`') !== false) {
12366 return 'Bad string syntax to evaluate (backtick char is forbidden): ' . $s;
12367 }
12368
12369 // Disallow also concat operator
12370 if (!getDolGlobalString('MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL')) {
12371 if (preg_match('/[^0-9]+\.[^0-9]+/', $s)) { // We refuse . if not between 2 numbers
12372 return 'Bad string syntax to evaluate (dot char is forbidden if not strictly between 2 numbers): ' . $s;
12373 }
12374 }
12375
12376 // We exclude string using a $ character that are not an expected global or temporary vars, so that are not:
12377 // $db, $langs, $leftmenu, $topmenu, $user, $langs, $objectoffield, $var....
12378 $savescheck = '';
12379 $scheck = $s;
12380 while ($scheck && $savescheck != $scheck) {
12381 $savescheck = $scheck;
12382 $scheck = preg_replace('/\$conf->[a-z\_]+->enabled/', '__VARCONFENABLED__', $scheck); // Remove this once $user->module->enabled has been replaced everywhere with isModEnabled.
12383 $scheck = preg_replace('/\$user->id/', '__VARUSERID__', $scheck);
12384 $scheck = preg_replace('/\$user->hasRight/', '__VARUSERHASRIGHT__', $scheck);
12385 $scheck = preg_replace('/\$user->rights/', '__VARUSERHASRIGHT__', $scheck); // Remove this once $user->rights->xxx is replaced everywhere with $user->hasRight()
12386 $scheck = preg_replace('/\$user->isAdmin/', '__VARUSERHASRIGHT__', $scheck);
12387 $scheck = preg_replace('/\$user->admin/', '__VARUSERISADMIN__', $scheck); // Remove this once $user->admin is replaced everywhere with $user->isAdmin()
12388 $scheck = preg_replace('/\$user->isExternalUser/', '__VARUSERSOCID__', $scheck);
12389 $scheck = preg_replace('/\$user->socid/', '__VARUSERSOCID__', $scheck); // Remove this once $user->admin is replaced everywhere with $user->isExternalUser()
12390 $scheck = preg_replace('/\‍(\$db\‍)/', '__VARDB__', $scheck);
12391 $scheck = preg_replace('/\$langs/', '__VARLANGSTRANS__', $scheck);
12392 $scheck = preg_replace('/\$mysoc/', '__VARMYSOC__', $scheck);
12393 $scheck = preg_replace('/\$action/', '__VARACTION__', $scheck);
12394 $scheck = preg_replace('/\$mainmenu/', '__VARMAINMENU__', $scheck); // Remove this once all tests on $mainmenu has been replaced with isStringVarMatching
12395 $scheck = preg_replace('/\$leftmenu/', '__VARLEFTMENU__', $scheck); // Remove this once all tests on $mainmenu has been replaced with isStringVarMatching
12396 $scheck = preg_replace('/\$websitepage/', '__VARWEBSITEPAGE__', $scheck);
12397 $scheck = preg_replace('/\$website/', '__VARWEBSITE__', $scheck);
12398 $scheck = preg_replace('/\$objectoffield/', '__VAROBJECTOFFIELD__', $scheck);
12399 $scheck = preg_replace('/\$object/', '__VAROBJECT__', $scheck);
12400 $scheck = preg_replace('/\$var/', '__VARVAR__', $scheck);
12401
12402 // deprecated (now we use $objecf->canvas or $objectoffield->canvas)
12403 $scheck = preg_replace('/\$soc->canvas/', '__VARSOCCANVAS__', $scheck);
12404 $scheck = preg_replace('/\$obj->canvas/', '__VAROBJCANVAS__', $scheck);
12405
12406 // Now test if it remains one '$'
12407 if (strpos($scheck, '$') !== false) {
12408 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);
12409 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;
12410 }
12411 }
12412
12413 // We block use of php exec or php file functions
12414 $forbiddenphpstrings = array('_ENV', '_SESSION', '_COOKIE', '_GET', '_GLOBAL', '_POST', '_REQUEST', 'ReflectionFunction', 'SplFileObject', 'SplTempFileObject');
12415
12416 if (empty($dolibarr_main_restrict_eval_methods)) { // If forced to ''
12417 // 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)
12418 // 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
12419 // like we can do with array_map and its callable parameter: dol_eval('json_encode(array_map(implode("",["ex","ec"]), ["id"]))', 1, 1, '0')
12420 $forbiddenphpfunctions = array();
12421 $forbiddenphpmethods = array();
12422
12423 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("override_function", "session_id", "session_create_id", "session_regenerate_id"));
12424 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("get_defined_functions", "get_defined_vars", "get_defined_constants", "get_declared_classes"));
12425 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("function", "call_user_func", "call_user_func_array"));
12426
12427 $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"));
12428 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("usort", "uasort", "uksort", "preg_replace_callback", "preg_replace_callback_array", "header_register_callback"));
12429 $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"));
12430 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("spl_autoload_register", "spl_autoload_unregister", "iterator_apply", "session_set_save_handler"));
12431 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("forward_static_call", "forward_static_call_array", "register_postsend_function"));
12432
12433 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("ob_start"));
12434
12435 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("require", "include", "require_once", "include_once"));
12436 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("exec", "passthru", "shell_exec", "system", "proc_open", "popen"));
12437 $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"));
12438 $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", ));
12439 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("putenv", "dl", "apache_child_terminate", "apache_setenv"));
12440 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("posix_kill", "posix_setuid", "posix_setgid"));
12441 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_eval", "dol_eval_new", "dol_eval_standard", "executeCLI", "verifCond", "GETPOST", "dolEncrypt", "dolDecrypt")); // native dolibarr functions
12442 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("eval", "create_function", "assert", "mb_ereg_replace")); // function with eval capabilities
12443 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("readline_completion_function", "readline_callback_handler_install"));
12444 $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
12445 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("chdir", "dir", "fopen", "file", "file_exists", "file_get_contents", "file_put_contents", "fget", "fgetc", "fgetcsv", "fputs", "fputscsv", "fpassthru", "fscanf", "fseek", "fwrite", "is_file", "is_dir", "is_link", "mkdir", "opendir", "rmdir", "scandir", "symlink", "touch", "unlink", "umask"));
12446 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("require", "include"));
12447 if (!getDolGlobalString('MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL')) { // We disallow all function that allow to obfuscate the real name of a function
12448 // @phpcs:ignore
12449 $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
12450 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_concat", "dol_concatdesc")); // native dolibarr functions
12451 }
12452 // Remove from blacklist the function that are into the whitelist
12453 /*foreach ($forbiddenphpfunctions as $key => $forbiddenphpfunction) {
12454 if (in_array($forbiddenphpfunction, $dolibarr_main_restrict_eval_methods_array)) {
12455 unset($forbiddenphpfunctions[$key]);
12456 }
12457 }*/
12458
12459 $forbiddenphpmethods = array_merge($forbiddenphpmethods, array('invoke', 'invokeArgs')); // Methods of ReflectionFunction to execute a function
12460 // Remove from blacklist the function that are into the whitelist
12461 /*foreach ($forbiddenphpmethods as $key => $forbiddenphpmethod) {
12462 if (in_array($forbiddenphpmethod, $dolibarr_main_restrict_eval_methods_array)) {
12463 unset($forbiddenphpmethods[$key]);
12464 }
12465 }*/
12466
12467 $forbiddenphpregex = 'global\s*\$';
12468 $forbiddenphpregex .= '|';
12469 $forbiddenphpregex .= '\b(' . implode('|', $forbiddenphpfunctions) . ')\b';
12470
12471 $forbiddenphpmethodsregex = '->(' . implode('|', $forbiddenphpmethods) . ')';
12472
12473 // Now scan all forbidden patterns
12474 do {
12475 $oldstringtoclean = $s;
12476 $s = str_ireplace($forbiddenphpstrings, '__forbiddenstring__', $s);
12477 $s = preg_replace('/' . $forbiddenphpregex . '/i', '__forbiddenstring__', $s);
12478 $s = preg_replace('/' . $forbiddenphpmethodsregex . '/i', '__forbiddenstring__', $s);
12479 //$s = preg_replace('/\$[a-zA-Z0-9_\->\$]+\‍(/i', '', $s); // Remove $function( call and $mycall->mymethod(
12480 } while ($oldstringtoclean != $s);
12481
12482 if (strpos($s, '__forbiddenstring__') !== false) {
12483 dol_syslog('Bad string syntax to evaluate: ' . $s, LOG_WARNING);
12484 return 'Bad string syntax to evaluate: ' . $s;
12485 }
12486 }
12487
12488 if (!empty($dolibarr_main_restrict_eval_methods)) {
12489 // Accept only white-listed allowed function and classes
12490 // TODO Get all pattern '/([\s\w]+)\‍(/', then check that $reg[1] is a defined class or a function into a given list
12491 $pattern = '/([\s\w\'\]\"]+)\‍(/';
12492
12493 $matches = array();
12494 preg_match_all($pattern, $s, $matches);
12495
12496 if (count($matches)) {
12497 foreach ($matches[1] as $m) {
12498 $m = trim($m);
12499 if (empty($m)) {
12500 continue;
12501 }
12502 $reg = array();
12503 if (!preg_match('/new ([A-Z][\w]+)/i', $m, $reg)) {
12504 if (!in_array($m, $dolibarr_main_restrict_eval_methods_array)) {
12505 if ($m != "'" && $m != '"') {
12506 dol_syslog('Bad string syntax to evaluate: ' . $s, LOG_WARNING);
12507 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;
12508 }
12509 }
12510 } else {
12511 if (!class_exists($reg[1])) {
12512 dol_syslog('Bad string syntax to evaluate: Class "'.$reg[1].'" does not exist. ' . $s, LOG_WARNING);
12513 return 'Bad string syntax to evaluate. Class "'.$reg[1].'" does not exist. ' . $s;
12514 }
12515 $parents = class_parents($reg[1]); // Get list of parent classes of class we want to check
12516 if (!in_array('CommonObject', $parents)) { // Only classes that inherit CommonObject are ok. This forbid dangerous classes like ReflectionFunction, SplFileObject, ...
12517 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);
12518 return 'Bad string syntax to evaluate. Class "'.$reg[1].'" is not allowed because only classes extended CommonObject can be used in dynamic evaluation. ' . $s;
12519 }
12520 }
12521 }
12522 }
12523
12524 $forbiddenphpregex = 'global\s*\$';
12525 $forbiddenphpregex .= '|'; // or
12526 $forbiddenphpregex .= '}\s*\[';
12527 $forbiddenphpregex .= '|'; // or
12528 $forbiddenphpregex .= '\‍)\s*\‍(';
12529
12530 // Now scan all forbidden patterns
12531 do {
12532 $oldstringtoclean = $s;
12533 $s = str_ireplace($forbiddenphpstrings, '__forbiddenstring__', $s);
12534 $s = preg_replace('/' . $forbiddenphpregex . '/i', '__forbiddenstring__', $s);
12535 //$s = preg_replace('/' . $forbiddenphpmethodsregex . '/i', '__forbiddenstring__', $s);
12536 //$s = preg_replace('/\$[a-zA-Z0-9_\->\$]+\‍(/i', '', $s); // Remove $function( call and $mycall->mymethod(
12537 } while ($oldstringtoclean != $s);
12538
12539 if (strpos($s, '__forbiddenstring__') !== false) {
12540 dol_syslog('Bad string syntax to evaluate: ' . $s, LOG_WARNING);
12541 return 'Bad string syntax to evaluate: ' . $s;
12542 }
12543 }
12544
12545 //print $s."<br>\n";
12546 ob_start(); // An evaluation has no reason to output data
12547 $isObBufferActive = true;
12548 $tmps = $hideerrors ? @eval('return ' . $s . ';') : eval('return ' . $s . ';');
12549 $tmpo = ob_get_clean(); // This close the buffer
12550 $isObBufferActive = false;
12551 if ($tmpo) {
12552 print 'Bad string syntax to evaluate. Some data were output when it should not when evaluating: ' . $s;
12553 }
12554 return $tmps;
12555 } catch (Exception $e) {
12556 if ($isObBufferActive) {
12557 // Clean up buffer which was left behind due to exception.
12558 $tmpo = ob_get_clean(); // This close the buffer
12559 $isObBufferActive = false;
12560 }
12561 $error = 'dol_eval try/catch error for string: ' . $s . ' - Error: ';
12562 $error .= $e->getMessage();
12563 dol_syslog($error, LOG_WARNING);
12564 return 'Exception during evaluation: ' . $s;
12565 } catch (Error $e) {
12566 if ($isObBufferActive) {
12567 // Clean up buffer which was left behind due to exception.
12568 $tmpo = ob_get_clean(); // This close the buffer
12569 $isObBufferActive = false;
12570 }
12571 $error = 'dol_eval try/catch error for string: ' . $s . ' - Error: ';
12572 $error .= $e->getMessage();
12573 dol_syslog($error, LOG_WARNING);
12574 return 'Exception during evaluation: ' . $s;
12575 }
12576}
12577
12585function dol_validElement($element)
12586{
12587 return (trim($element) != '');
12588}
12589
12598function picto_from_langcode($codelang, $moreatt = '', $notitlealt = 0)
12599{
12600 if (empty($codelang)) {
12601 return '';
12602 }
12603
12604 if ($codelang == 'auto') {
12605 return '<span class="fa fa-language"></span>';
12606 }
12607
12608 $langtocountryflag = array(
12609 'ar_AR' => '',
12610 'ca_ES' => 'catalonia',
12611 'da_DA' => 'dk',
12612 'fr_CA' => 'mq',
12613 'sv_SV' => 'se',
12614 'sw_SW' => 'unknown',
12615 'AQ' => 'unknown',
12616 'CW' => 'unknown',
12617 'IM' => 'unknown',
12618 'JE' => 'unknown',
12619 'MF' => 'unknown',
12620 'BL' => 'unknown',
12621 'SX' => 'unknown'
12622 );
12623
12624 if (isset($langtocountryflag[$codelang])) {
12625 $flagImage = $langtocountryflag[$codelang];
12626 } else {
12627 $tmparray = explode('_', $codelang);
12628 $flagImage = empty($tmparray[1]) ? $tmparray[0] : $tmparray[1];
12629 }
12630
12631 $morecss = '';
12632 $reg = array();
12633 if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
12634 $morecss = $reg[1];
12635 $moreatt = "";
12636 }
12637
12638 // return img_picto_common($codelang, 'flags/'.strtolower($flagImage).'.png', $moreatt, 0, $notitlealt);
12639 return '<span class="flag-sprite ' . strtolower($flagImage) . ($morecss ? ' ' . $morecss : '') . '"' . ($moreatt ? ' ' . $moreatt : '') . (!$notitlealt ? ' title="' . $codelang . '"' : '') . '></span>';
12640}
12641
12649function getLanguageCodeFromCountryCode($countrycode)
12650{
12651 global $mysoc;
12652
12653 if (empty($countrycode)) {
12654 return null;
12655 }
12656
12657 if (strtoupper($countrycode) == 'MQ') {
12658 return 'fr_CA';
12659 }
12660 if (strtoupper($countrycode) == 'SE') {
12661 return 'sv_SE'; // se_SE is Sami/Sweden, and we want in priority sv_SE for SE country
12662 }
12663 if (strtoupper($countrycode) == 'CH') {
12664 if ($mysoc->country_code == 'FR') {
12665 return 'fr_CH';
12666 }
12667 if ($mysoc->country_code == 'DE') {
12668 return 'de_CH';
12669 }
12670 if ($mysoc->country_code == 'IT') {
12671 return 'it_CH';
12672 }
12673 }
12674
12675 // Locale list taken from:
12676 // http://stackoverflow.com/questions/3191664/
12677 // list-of-all-locales-and-their-short-codes
12678 $locales = array(
12679 'af-ZA',
12680 'am-ET',
12681 'ar-AE',
12682 'ar-BH',
12683 'ar-DZ',
12684 'ar-EG',
12685 'ar-IQ',
12686 'ar-JO',
12687 'ar-KW',
12688 'ar-LB',
12689 'ar-LY',
12690 'ar-MA',
12691 'ar-OM',
12692 'ar-QA',
12693 'ar-SA',
12694 'ar-SY',
12695 'ar-TN',
12696 'ar-YE',
12697 //'as-IN', // Moved after en-IN
12698 'ba-RU',
12699 'be-BY',
12700 'bg-BG',
12701 'bn-BD',
12702 //'bn-IN', // Moved after en-IN
12703 'bo-CN',
12704 'br-FR',
12705 'ca-ES',
12706 'co-FR',
12707 'cs-CZ',
12708 'cy-GB',
12709 'da-DK',
12710 'de-AT',
12711 'de-CH',
12712 'de-DE',
12713 'de-LI',
12714 'de-LU',
12715 'dv-MV',
12716 'el-GR',
12717 'en-AU',
12718 'en-BZ',
12719 'en-CA',
12720 'en-GB',
12721 'en-IE',
12722 'en-IN',
12723 'as-IN', // as-IN must be after en-IN (en in priority if country is IN)
12724 'bn-IN', // bn-IN must be after en-IN (en in priority if country is IN)
12725 'en-JM',
12726 'en-MY',
12727 'en-NZ',
12728 'en-PH',
12729 'en-SG',
12730 'en-TT',
12731 'en-US',
12732 'en-ZA',
12733 'en-ZW',
12734 'es-AR',
12735 'es-BO',
12736 'es-CL',
12737 'es-CO',
12738 'es-CR',
12739 'es-DO',
12740 'es-EC',
12741 'es-ES',
12742 'es-GT',
12743 'es-HN',
12744 'es-MX',
12745 'es-NI',
12746 'es-PA',
12747 'es-PE',
12748 'es-PR',
12749 'es-PY',
12750 'es-SV',
12751 'es-US',
12752 'es-UY',
12753 'es-VE',
12754 'et-EE',
12755 'eu-ES',
12756 'fa-IR',
12757 'fi-FI',
12758 'fo-FO',
12759 'fr-BE',
12760 'fr-CA',
12761 'fr-CH',
12762 'fr-FR',
12763 'fr-LU',
12764 'fr-MC',
12765 'fy-NL',
12766 'ga-IE',
12767 'gd-GB',
12768 'gl-ES',
12769 'gu-IN',
12770 'he-IL',
12771 'hi-IN',
12772 'hr-BA',
12773 'hr-HR',
12774 'hu-HU',
12775 'hy-AM',
12776 'id-ID',
12777 'ig-NG',
12778 'ii-CN',
12779 'is-IS',
12780 'it-CH',
12781 'it-IT',
12782 'ja-JP',
12783 'ka-GE',
12784 'kk-KZ',
12785 'kl-GL',
12786 'km-KH',
12787 'kn-IN',
12788 'ko-KR',
12789 'ky-KG',
12790 'lb-LU',
12791 'lo-LA',
12792 'lt-LT',
12793 'lv-LV',
12794 'mi-NZ',
12795 'mk-MK',
12796 'ml-IN',
12797 'mn-MN',
12798 'mr-IN',
12799 'ms-BN',
12800 'ms-MY',
12801 'mt-MT',
12802 'nb-NO',
12803 'ne-NP',
12804 'nl-BE',
12805 'nl-NL',
12806 'nn-NO',
12807 'oc-FR',
12808 'or-IN',
12809 'pa-IN',
12810 'pl-PL',
12811 'ps-AF',
12812 'pt-BR',
12813 'pt-PT',
12814 'rm-CH',
12815 'ro-MD',
12816 'ro-RO',
12817 'ru-RU',
12818 'rw-RW',
12819 'sa-IN',
12820 'se-FI',
12821 'se-NO',
12822 'se-SE',
12823 'si-LK',
12824 'sk-SK',
12825 'sl-SI',
12826 'sq-AL',
12827 'sv-FI',
12828 'sv-SE',
12829 'sw-KE',
12830 'ta-IN',
12831 'te-IN',
12832 'th-TH',
12833 'tk-TM',
12834 'tn-ZA',
12835 'tr-TR',
12836 'tt-RU',
12837 'ug-CN',
12838 'uk-UA',
12839 'ur-PK',
12840 'vi-VN',
12841 'wo-SN',
12842 'xh-ZA',
12843 'yo-NG',
12844 'zh-CN',
12845 'zh-HK',
12846 'zh-MO',
12847 'zh-SG',
12848 'zh-TW',
12849 'zu-ZA',
12850 );
12851
12852 $buildprimarykeytotest = strtolower($countrycode) . '-' . strtoupper($countrycode);
12853 if (in_array($buildprimarykeytotest, $locales)) {
12854 return strtolower($countrycode) . '_' . strtoupper($countrycode);
12855 }
12856
12857 if (function_exists('locale_get_primary_language') && function_exists('locale_get_region')) { // Need extension php-intl
12858 foreach ($locales as $locale) {
12859 $locale_language = locale_get_primary_language($locale);
12860 $locale_region = locale_get_region($locale);
12861 if (strtoupper($countrycode) == $locale_region) {
12862 //var_dump($locale.' - '.$locale_language.' - '.$locale_region);
12863 return strtolower($locale_language) . '_' . strtoupper($locale_region);
12864 }
12865 }
12866 } else {
12867 dol_syslog("Warning Extension php-intl is not available", LOG_WARNING);
12868 }
12869
12870 return null;
12871}
12872
12903function complete_head_from_modules($conf, $langs, $object, &$head, &$h, $type, $mode = 'add', $filterorigmodule = '')
12904{
12905 global $hookmanager, $db;
12906
12907 if (isset($conf->modules_parts['tabs'][$type]) && is_array($conf->modules_parts['tabs'][$type])) {
12908 foreach ($conf->modules_parts['tabs'][$type] as $value) {
12909 $values = explode(':', $value);
12910
12911 $reg = array();
12912 if ($mode == 'add' && !preg_match('/^\-/', $values[1])) {
12913 if (count($values) !== 6) {
12914 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);
12915 continue;
12916 }
12917
12918 // new declaration with permissions:
12919 // $value='objecttype:+tabname1:Title1:langfile@mymodule:$user->rights->mymodule->read:/mymodule/mynewtab1.php?id=__ID__'
12920 // $value='objecttype:+tabname1:Title1,class,pathfile,method:langfile@mymodule:$user->rights->mymodule->read:/mymodule/mynewtab1.php?id=__ID__'
12921 if ($values[0] != $type) {
12922 continue;
12923 }
12924
12925 $newtab = array();
12926 $postab = $h;
12927 // detect if position set in $values[1] ie : +(2)mytab@mymodule (first tab is 0, second is one, ...)
12928 $str = $values[1];
12929 $posstart = strpos($str, '(');
12930 if ($posstart > 0) {
12931 $posend = strpos($str, ')');
12932 if ($posstart > 0) {
12933 $res1 = substr($str, $posstart + 1, $posend - $posstart - 1);
12934 if (is_numeric($res1)) {
12935 $postab = (int) $res1;
12936 $values[1] = '+' . substr($str, $posend + 1);
12937 }
12938 }
12939 }
12940
12941 global $objectoffield; // So we can use $objectoffield int verifCond
12942 $objectoffield = $object;
12943
12944 if (!verifCond($values[4], '2')) {
12945 continue;
12946 }
12947
12948 if ($values[3]) {
12949 if ($filterorigmodule) { // If a filter of module origin has been requested
12950 if (strpos($values[3], '@')) { // This is an external module
12951 if ($filterorigmodule != 'external') {
12952 continue;
12953 }
12954 } else { // This looks a core module
12955 if ($filterorigmodule != 'core') {
12956 continue;
12957 }
12958 }
12959 }
12960 $langs->load($values[3]);
12961 }
12962
12963 if (preg_match('/SUBSTITUTION_([^_]+)/i', $values[2], $reg)) {
12964 // If label is "SUBSTITUION_..."
12965 $substitutionarray = array();
12966 complete_substitutions_array($substitutionarray, $langs, $object, array('needforkey' => $values[2]));
12967 $label = make_substitutions($reg[1], $substitutionarray);
12968 } else {
12969 // If label is "Label,Class,File,Method", we call the method to show content inside the badge
12970 $labeltemp = explode(',', $values[2]);
12971 $label = $langs->trans($labeltemp[0]);
12972
12973 if (!empty($labeltemp[1]) && is_object($object) && !empty($object->id)) {
12974 dol_include_once($labeltemp[2]);
12975 $classtoload = $labeltemp[1];
12976 if (class_exists($classtoload)) {
12977 $obj = new $classtoload($db);
12978 $function = $labeltemp[3];
12979 if ($obj && $function && method_exists($obj, $function)) {
12980 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
12981 $nbrec = $obj->$function($object->id, $obj);
12982 if (!empty($nbrec)) {
12983 $label .= '<span class="badge marginleftonlyshort">' . $nbrec . '</span>';
12984 }
12985 }
12986 }
12987 }
12988 }
12989 $url = preg_replace('/__ID__/i', ((is_object($object) && !empty($object->id)) ? $object->id : ''), $values[5]);
12990 $link = parse_url($url);
12991 $query = [];
12992 if (isset($link['query'])) {
12993 parse_str($link['query'], $query);
12994 }
12995 $newtab[0] = dolBuildUrl(dol_buildpath($link['path'], 1), $query);
12996 $newtab[1] = $label;
12997 $newtab[2] = str_replace('+', '', $values[1]);
12998 $h++;
12999
13000 // set tab at its position
13001 $head = array_merge(array_slice($head, 0, $postab), array($newtab), array_slice($head, $postab));
13002 } elseif ($mode == 'remove' && preg_match('/^\-/', $values[1])) {
13003 if ($values[0] != $type) {
13004 continue;
13005 }
13006 $tabname = str_replace('-', '', $values[1]);
13007 foreach ($head as $key => $val) {
13008 $condition = (!empty($values[3]) ? verifCond($values[3], '2') : 1);
13009 //var_dump($key.' - '.$tabname.' - '.$head[$key][2].' - '.$values[3].' - '.$condition);
13010 if ($head[$key][2] == $tabname && $condition) {
13011 unset($head[$key]);
13012 break;
13013 }
13014 }
13015 }
13016 }
13017 }
13018
13019 // No need to make a return $head. Var is modified as a reference
13020 if (!empty($hookmanager)) {
13021 $parameters = array('object' => $object, 'mode' => $mode, 'head' => &$head, 'filterorigmodule' => $filterorigmodule, 'type' => $type);
13022 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
13023 $reshook = $hookmanager->executeHooks('completeTabsHead', $parameters, $object);
13024 if ($reshook > 0) { // Hook ask to replace completely the array
13025 $head = $hookmanager->resArray;
13026 } else { // Hook
13027 $head = array_merge($head, $hookmanager->resArray);
13028 }
13029 $h = count($head);
13030 }
13031}
13032
13044function printCommonFooter($zone = 'private')
13045{
13046 global $conf, $hookmanager, $user, $langs;
13047 global $action;
13048 global $micro_start_time;
13049
13050 if ($zone == 'private') {
13051 print "\n" . '<!-- Common footer for private page -->' . "\n";
13052 } else {
13053 print "\n" . '<!-- Common footer for public page -->' . "\n";
13054 }
13055
13056 // A div to store page_y POST parameter so we can read it using javascript
13057 print "\n<!-- A div to store page_y POST parameter -->\n";
13058 print '<div id="page_y" style="display: none;">' . (GETPOST('page_y') ? GETPOST('page_y') : '') . '</div>' . "\n";
13059
13060 $parameters = array('zone' => $zone);
13061 $tmpobject = null;
13062 // @phan-suppress-next-line PhanPluginConstantVariableNull
13063 $reshook = $hookmanager->executeHooks('printCommonFooter', $parameters, $tmpobject, $action); // Note that $action and $object may have been modified by some hooks
13064 if (empty($reshook)) {
13065 if (getDolGlobalString('MAIN_HTML_FOOTER')) {
13066 print getDolGlobalString('MAIN_HTML_FOOTER') . "\n";
13067 }
13068
13069 print "\n";
13070 if (!empty($conf->use_javascript_ajax)) {
13071 print "\n<!-- A script section to add menuhider handler on backoffice, manage focus and mandatory fields, tuning info, ... -->\n";
13072 print '<script>' . "\n";
13073 print 'jQuery(document).ready(function() {' . "\n";
13074
13075 if ($zone == 'private' && empty($conf->dol_use_jmobile)) {
13076 print "\n";
13077 print '/* JS CODE TO ENABLE to manage handler to switch left menu page (menuhider) */' . "\n";
13078 print 'jQuery("li.menuhider").click(function(event) {';
13079 print ' if (!$( "body" ).hasClass( "sidebar-collapse" )){ event.preventDefault(); }' . "\n";
13080 print ' console.log("We click on .menuhider");' . "\n";
13081 print ' $("body").toggleClass("sidebar-collapse")' . "\n";
13082 print '});' . "\n";
13083 }
13084
13085 // Management of focus and mandatory for fields
13086 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"])))) {
13087 print '/* JS CODE TO ENABLE to manage focus and mandatory form fields */' . "\n";
13088 $relativepathstring = $_SERVER["PHP_SELF"];
13089 // Clean $relativepathstring
13090 if (constant('DOL_URL_ROOT')) {
13091 $relativepathstring = preg_replace('/^' . preg_quote(constant('DOL_URL_ROOT'), '/') . '/', '', $relativepathstring);
13092 }
13093 $relativepathstring = preg_replace('/^\//', '', $relativepathstring);
13094 $relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
13095 //$tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
13096
13097 if (!empty($user->default_values[$relativepathstring]['focus'])) {
13098 foreach ($user->default_values[$relativepathstring]['focus'] as $defkey => $defval) {
13099 $qualified = 0;
13100 if ($defkey != '_noquery_') {
13101 $tmpqueryarraytohave = explode('&', $defkey);
13102 $foundintru = 0;
13103 foreach ($tmpqueryarraytohave as $tmpquerytohave) {
13104 $tmpquerytohaveparam = explode('=', $tmpquerytohave);
13105 //print "console.log('".$tmpquerytohaveparam[0]." ".$tmpquerytohaveparam[1]." ".GETPOST($tmpquerytohaveparam[0])."');";
13106 if (!GETPOSTISSET($tmpquerytohaveparam[0]) || ($tmpquerytohaveparam[1] != GETPOST($tmpquerytohaveparam[0]))) {
13107 $foundintru = 1;
13108 }
13109 }
13110 if (!$foundintru) {
13111 $qualified = 1;
13112 }
13113 //var_dump($defkey.'-'.$qualified);
13114 } else {
13115 $qualified = 1;
13116 }
13117
13118 if ($qualified) {
13119 print 'console.log("set the focus by executing jQuery(...).focus();")' . "\n";
13120 foreach ($defval as $paramkey => $paramval) {
13121 // Set focus on field
13122 print 'jQuery("input[name=\'' . $paramkey . '\']").focus();' . "\n";
13123 print 'jQuery("textarea[name=\'' . $paramkey . '\']").focus();' . "\n"; // TODO KO with ckeditor
13124 print 'jQuery("select[name=\'' . $paramkey . '\']").focus();' . "\n"; // Not really useful, but we keep it in case of.
13125 }
13126 }
13127 }
13128 }
13129 if (!empty($user->default_values[$relativepathstring]['mandatory'])) {
13130 foreach ($user->default_values[$relativepathstring]['mandatory'] as $defkey => $defval) {
13131 $qualified = 0;
13132 if ($defkey != '_noquery_') {
13133 $tmpqueryarraytohave = explode('&', $defkey);
13134 $foundintru = 0;
13135 foreach ($tmpqueryarraytohave as $tmpquerytohave) {
13136 $tmpquerytohaveparam = explode('=', $tmpquerytohave);
13137 //print "console.log('".$tmpquerytohaveparam[0]." ".$tmpquerytohaveparam[1]." ".GETPOST($tmpquerytohaveparam[0])."');";
13138 if (!GETPOSTISSET($tmpquerytohaveparam[0]) || ($tmpquerytohaveparam[1] != GETPOST($tmpquerytohaveparam[0]))) {
13139 $foundintru = 1;
13140 }
13141 }
13142 if (!$foundintru) {
13143 $qualified = 1;
13144 }
13145 //var_dump($defkey.'-'.$qualified);
13146 } else {
13147 $qualified = 1;
13148 }
13149
13150 if ($qualified) {
13151 print 'console.log("set the js code to manage fields that are set as mandatory");' . "\n";
13152
13153 foreach ($defval as $paramkey => $paramval) {
13154 // Solution 1: Add handler on submit to check if mandatory fields are empty
13155 print 'var form = $(\'[name="'.dol_escape_js($paramkey).'"]\').closest("form");'."\n";
13156 print "form.on('submit', function(event) {
13157 var submitter = \$(this).find(':submit:focus').get(0);
13158 var buttonName = submitter ? \$(submitter).attr('name') : 'save';
13159
13160 if (buttonName == 'cancel') {
13161 console.log('We click on cancel button so we accept submit with no need to check mandatory fields');
13162 return true;
13163 }
13164
13165 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');
13166
13167 var tmpvalue = jQuery('[name=\"".dol_escape_js($paramkey)."\"]').val();
13168 let tmptypefield = jQuery('[name=\"".dol_escape_js($paramkey)."\"]').prop('nodeName').toLowerCase(); // Get the tag name (div, section, footer...)
13169
13170 if (tmptypefield == 'textarea') {
13171 // We must instead check the content of ckeditor
13172 var tmpeditor = CKEDITOR.instances['" . dol_escape_js($paramkey) . "'];
13173 if (tmpeditor) {
13174 tmpvalue = tmpeditor.getData();
13175 console.log('For textarea tmpvalue is '+tmpvalue);
13176 }
13177 }
13178
13179 let tmpvalueisempty = false;
13180 if (tmpvalue === null || tmpvalue === undefined || tmpvalue === '' || tmpvalue === -1) {
13181 tmpvalueisempty = true;
13182 }
13183 if (tmpvalue === '0' && (tmptypefield == 'select' || tmptypefield == 'input')) {
13184 tmpvalueisempty = true;
13185 }
13186 if (tmpvalueisempty && buttonName !== 'cancel') {
13187 console.log('field has type '+tmptypefield+' and is empty, we cancel the submit');
13188 event.preventDefault(); // Stop submission of form to allow custom code to decide.
13189 event.stopPropagation(); // Stop other handlers.
13190
13191 alert('".dol_escape_js($langs->transnoentitiesnoconv("ErrorFieldRequired", $paramkey).' ('.$langs->transnoentitiesnoconv("CustomMandatoryFieldRule").')')."');
13192
13193 return false;
13194 }
13195 console.log('field has type '+tmptypefield+' and is defined to '+tmpvalue);
13196 return true;
13197 });
13198 \n";
13199
13200 // Solution 2: Add property 'required' on input
13201 // so browser will check value and try to focus on it when submitting the form.
13202 //print 'setTimeout(function() {'; // If we want to wait that ckeditor beuatifier has finished its job.
13203 //print 'jQuery("input[name=\''.$paramkey.'\']").prop(\'required\',true);'."\n";
13204 //print 'jQuery("textarea[id=\''.$paramkey.'\']").prop(\'required\',true);'."\n";
13205 //print 'jQuery("select[name=\''.$paramkey.'\']").prop(\'required\',true);'."\n";*/
13206 //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";
13207 //print 'jQuery("select[name=\''.$paramkey.'\'] option[value=\'-1\']").prop(\'value\', \'\');'."\n";
13208 //print 'jQuery("select[name=\''.$paramkey.'\'] option[value=\'0\']").prop(\'value\', \'\');'."\n";
13209 // Add 'field required' class on closest td for all input elements : input, textarea and select
13210 //print '}, 500);'; // 500 milliseconds delay
13211
13212 // Now set the class "fieldrequired"
13213 print 'jQuery(\':input[name="' . dol_escape_js($paramkey) . '"]\').closest("tr").find("td:first").addClass("fieldrequired");' . "\n";
13214 }
13215
13216 // If we submit using the cancel button, we remove the required attributes
13217 print 'jQuery("input[name=\'cancel\']").click(function() {
13218 console.log("We click on cancel button so removed all required attribute");
13219 jQuery("input, textarea, select").each(function(){this.removeAttribute(\'required\');});
13220 });' . "\n";
13221 }
13222 }
13223 }
13224 }
13225
13226 print '});' . "\n";
13227
13228 // End of tuning
13229 if (!empty($_SERVER['MAIN_SHOW_TUNING_INFO']) || getDolGlobalString('MAIN_SHOW_TUNING_INFO')) {
13230 print "\n";
13231 print "/* JS CODE TO ENABLE to add memory info */\n";
13232 print 'window.console && console.log("';
13233 if (getDolGlobalString('MEMCACHED_SERVER')) {
13234 print 'MEMCACHED_SERVER=' . getDolGlobalString('MEMCACHED_SERVER') . ' - ';
13235 }
13236 print 'MAIN_OPTIMIZE_SPEED=' . getDolGlobalString('MAIN_OPTIMIZE_SPEED', 'off');
13237 if (!empty($micro_start_time)) { // Works only if MAIN_SHOW_TUNING_INFO is defined at $_SERVER level. Not in global variable.
13238 $micro_end_time = microtime(true);
13239 print ' - Build time: ' . ceil(1000 * ($micro_end_time - $micro_start_time)) . ' ms';
13240 }
13241
13242 if (function_exists("memory_get_usage")) {
13243 print ' - Mem: ' . memory_get_usage(); // Do not use true here, it seems it takes the peak amount
13244 }
13245 if (function_exists("memory_get_peak_usage")) {
13246 print ' - Real mem peak: ' . memory_get_peak_usage(true);
13247 }
13248 if (function_exists("zend_loader_file_encoded")) {
13249 print ' - Zend encoded file: ' . (zend_loader_file_encoded() ? 'yes' : 'no');
13250 }
13251 print '");' . "\n";
13252 }
13253
13254 print "\n" . '</script>' . "\n";
13255
13256 // Google Analytics
13257 // TODO Remove this, can be replaced with the hook printCommonFooter
13258 if (isModEnabled('google') && getDolGlobalString('MAIN_GOOGLE_AN_ID')) {
13259 $tmptagarray = explode(',', getDolGlobalString('MAIN_GOOGLE_AN_ID'));
13260 foreach ($tmptagarray as $tmptag) {
13261 print "\n";
13262 print "<!-- JS CODE TO ENABLE for google analtics tag -->\n";
13263 print '
13264 <!-- Global site tag (gtag.js) - Google Analytics -->
13265 <script nonce="' . getNonce() . '" async src="https://www.googletagmanager.com/gtag/js?id=' . trim($tmptag) . '"></script>
13266 <script>
13267 window.dataLayer = window.dataLayer || [];
13268 function gtag(){dataLayer.push(arguments);}
13269 gtag(\'js\', new Date());
13270
13271 gtag(\'config\', \'' . trim($tmptag) . '\');
13272 </script>';
13273 print "\n";
13274 }
13275 }
13276 }
13277
13278 // Add Xdebug coverage of code
13279 if (defined('XDEBUGCOVERAGE')) {
13280 print_r(xdebug_get_code_coverage());
13281 }
13282
13283 // Output string from hooks
13284 if (!empty($hookmanager->resPrint)) {
13285 print $hookmanager->resPrint;
13286 }
13287
13288 // Add DebugBar data
13289 if ($user->hasRight('debugbar', 'read')) {
13290 global $debugbar;
13291 if ($debugbar instanceof DebugBar\DebugBar) {
13292 if (isset($debugbar['time'])) {
13293 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
13294 $debugbar['time']->stopMeasure('pageaftermaster');
13295 }
13296 print '<!-- Output debugbar data -->' . "\n";
13297 $renderer = $debugbar->getJavascriptRenderer();
13298 print $renderer->render();
13299 }
13300 } elseif (count($conf->logbuffer)) { // If there is some logs in buffer to show
13301 print "\n";
13302 print "<!-- Start of log output\n";
13303 //print '<div class="hidden">'."\n";
13304 foreach ($conf->logbuffer as $logline) {
13305 print $logline . "<br>\n";
13306 }
13307 //print '</div>'."\n";
13308 print "End of log output -->\n";
13309 }
13310 }
13311}
13312
13322function dolExplodeIntoArray($string, $delimiter = ';', $kv = '=')
13323{
13324 if (is_null($string)) {
13325 return array();
13326 }
13327
13328 if (preg_match('/^\[.*\]$/sm', $delimiter) || preg_match('/^\‍(.*\‍)$/sm', $delimiter)) {
13329 // This is a regex string
13330 $newdelimiter = $delimiter;
13331 } else {
13332 // This is a simple string
13333 // @phan-suppress-next-line PhanPluginSuspiciousParamPositionInternal
13334 $newdelimiter = preg_quote($delimiter, '/');
13335 }
13336
13337 if ($a = preg_split('/' . $newdelimiter . '/', $string)) {
13338 $ka = array();
13339 foreach ($a as $s) { // each part
13340 if ($s) {
13341 if ($pos = strpos($s, $kv)) { // key/value delimiter
13342 $ka[trim(substr($s, 0, $pos))] = trim(substr($s, $pos + strlen($kv)));
13343 } else { // key delimiter not found
13344 $ka[] = trim($s);
13345 }
13346 }
13347 }
13348 return $ka;
13349 }
13350
13351 return array();
13352}
13353
13361function dolExplodeKeepIfQuotes($input)
13362{
13363 // Use regexp to capture words and section in quotes
13364 $matches = array();
13365 preg_match_all('/"([^"]*)"|\'([^\']*)\'|(\S+)/', $input, $matches);
13366
13367 // Merge result and delete empty values
13368
13369 $result = array_map(
13376 static function ($a, $b, $c) {
13377 if ($a !== '') {
13378 return $a;
13379 }
13380 if ($b !== '') {
13381 return $b;
13382 }
13383 if ($c !== '') {
13384 return $c;
13385 }
13386 return '';
13387 },
13388 $matches[1],
13389 $matches[2],
13390 $matches[3]
13391 );
13392 return array_values(array_filter(
13393 $result,
13400 static function ($val) {
13401 return $val !== '';
13402 }
13403 ));
13404}
13405
13406
13413function dol_set_focus($selector)
13414{
13415 print "\n" . '<!-- Set focus onto a specific field -->' . "\n";
13416 print '<script nonce="' . getNonce() . '">jQuery(document).ready(function() { console.log("Force focus by dol_set_focus"); jQuery("' . dol_escape_js($selector) . '").focus(); });</script>' . "\n";
13417}
13418
13419
13427function dol_getmypid()
13428{
13429 if (!function_exists('getmypid')) {
13430 return mt_rand(99900000, 99965535);
13431 } else {
13432 return getmypid(); // May be a number on 64 bits (depending on OS)
13433 }
13434}
13435
13458function natural_search($fields, $value, $mode = 0, $nofirstand = 0, $sqltoadd = '')
13459{
13460 global $db, $langs;
13461
13462 $value = trim($value);
13463
13464 if ($mode == 0) {
13465 $value = preg_replace('/\*/', '%', $value); // Replace * with %
13466 }
13467 if ($mode == 1) {
13468 $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
13469 }
13470
13471 $value = preg_replace('/\s*\|\s*/', '|', $value);
13472
13473 // Split criteria on ' ' but not if we are inside quotes.
13474 // For mode 3, the split is done later on the , only and not on the ' '.
13475 if ($mode != -3 && $mode != 3) {
13476 $crits = dolExplodeKeepIfQuotes($value);
13477 } else {
13478 $crits = array($value);
13479 }
13480
13481 $res = '';
13482 if (!is_array($fields)) {
13483 $fields = array($fields);
13484 }
13485 $i1 = 0; // count the nb of "and" criteria added (all fields / criteria)
13486 foreach ($crits as $crit) { // Loop on each AND criteria
13487 $crit = trim($crit);
13488 $i2 = 0; // count the nb of valid criteria added for this this first criteria
13489 $newres = '';
13490
13491 foreach ($fields as $field) {
13492 if ($mode == 1) {
13493 $tmpcrits = explode('|', $crit);
13494 $i3 = 0; // count the nb of valid criteria added for this current field
13495 foreach ($tmpcrits as $tmpcrit) {
13496 if ($tmpcrit !== '0' && empty($tmpcrit)) {
13497 continue;
13498 }
13499 $tmpcrit = trim($tmpcrit);
13500
13501 $newres .= (($i2 > 0 || $i3 > 0) ? ' OR ' : '');
13502
13503 $operator = '=';
13504 $newcrit = preg_replace('/([!<>=]+)/', '', $tmpcrit);
13505
13506 $reg = array();
13507 preg_match('/([!<>=]+)/', $tmpcrit, $reg);
13508 if (!empty($reg[1])) {
13509 $operator = $reg[1];
13510 }
13511 if ($newcrit != '') {
13512 $numnewcrit = price2num($newcrit);
13513 if (is_numeric($numnewcrit)) {
13514 $newres .= $db->sanitize($field) . ' ' . $operator . ' ' . ((float) $numnewcrit); // should be a numeric
13515 } else {
13516 $newres .= '1 = 2'; // force false, we received a corrupted data
13517 }
13518 $i3++; // a criteria was added to string
13519 }
13520 }
13521 $i2++; // a criteria for 1 more field was added to string
13522 } elseif ($mode == 2 || $mode == -2) {
13523 $crit = preg_replace('/[^\-0-9,]/', '', $crit); // ID are always integer
13524 $newres .= ($i2 > 0 ? ' OR ' : '') . $db->sanitize($field) . " " . ($mode == -2 ? 'NOT ' : '');
13525 $newres .= $crit ? "IN (" . $db->sanitize($db->escape($crit)) . ")" : "IN (0)";
13526 if ($mode == -2) {
13527 $newres .= ' OR ' . $db->sanitize($field) . ' IS NULL';
13528 }
13529 $i2++; // a criteria for 1 more field was added to string
13530 } elseif ($mode == 3 || $mode == -3) {
13531 $tmparray = explode(',', $crit);
13532 if (count($tmparray)) {
13533 $listofcodes = '';
13534 $listofcodesnot = '';
13535 foreach ($tmparray as $val) {
13536 $val = trim($val);
13537 if ($val !== '') {
13538 if (preg_match('/^!/', $val)) {
13539 $listofcodesnot .= ($listofcodesnot ? ',' : '');
13540 $listofcodesnot .= "'" . $db->escape(preg_replace('/^!=?/', '', $val)) . "'";
13541 } else {
13542 $listofcodes .= ($listofcodes ? ',' : '');
13543 $listofcodes .= "'" . $db->escape($val) . "'";
13544 }
13545 }
13546 }
13547 $newres .= ($i2 > 0 ? ' OR ' : '');
13548 if ($listofcodes && $listofcodesnot) {
13549 $newres .= '(';
13550 }
13551 if ($listofcodes) {
13552 $newres .= $db->sanitize($field) . " " . ($mode == -3 ? 'NOT IN' : 'IN') . " (" . $db->sanitize($listofcodes, 1, 0, 1) . ")";
13553 }
13554 if ($listofcodes && $listofcodesnot) {
13555 $newres .= ' AND ';
13556 }
13557 if ($listofcodesnot) {
13558 $newres .= $db->sanitize($field) . " " . ($mode == -3 ? 'IN ' : 'NOT IN') . " (" . $db->sanitize($listofcodesnot, 1, 0, 1) . ")";
13559 }
13560 if ($listofcodes && $listofcodesnot) {
13561 $newres .= ')';
13562 }
13563 $i2++; // a criteria for 1 more field was added to string
13564 }
13565 if ($mode == -3) {
13566 $newres .= ' OR ' . $db->sanitize($field) . ' IS NULL';
13567 }
13568 } elseif ($mode == 4) {
13569 $tmparray = explode(',', $crit);
13570 if (count($tmparray)) {
13571 $listofcodes = '';
13572 foreach ($tmparray as $val) {
13573 $val = trim($val);
13574 if ($val) {
13575 $newres .= ($i2 > 0 ? " OR (" : "(") . $db->sanitize($field) . " LIKE '" . $db->escape($val) . ",%'";
13576 $newres .= ' OR ' . $db->sanitize($field) . " = '" . $db->escape($val) . "'";
13577 $newres .= ' OR ' . $db->sanitize($field) . " LIKE '%," . $db->escape($val) . "'";
13578 $newres .= ' OR ' . $db->sanitize($field) . " LIKE '%," . $db->escape($val) . ",%'";
13579 $newres .= ')';
13580 $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)
13581 }
13582 }
13583 }
13584 } else { // $mode=0
13585 $tmpcrits = explode('|', $crit);
13586 $i3 = 0; // count the nb of valid criteria added for the current couple criteria/field
13587 foreach ($tmpcrits as $tmpcrit) { // loop on each OR criteria
13588 if ($tmpcrit !== '0' && empty($tmpcrit)) {
13589 continue;
13590 }
13591 $tmpcrit = trim($tmpcrit);
13592
13593 if ($tmpcrit == '^$' || strpos($crit, '!') === 0) { // If we search empty, we must combined different OR fields with AND
13594 $newres .= (($i2 > 0 || $i3 > 0) ? ' AND ' : '');
13595 } else {
13596 $newres .= (($i2 > 0 || $i3 > 0) ? ' OR ' : '');
13597 }
13598
13599 $isSellist = false;
13600 $table = $label = $key = null;
13601
13602 if (strpos($field, 'ef.') === 0) {
13603 $extrafieldName = substr($field, 3);
13604 $extrafields = new ExtraFields($db);
13605 $extrafields->fetch_name_optionals_label('product');
13606
13607 if (isset($extrafields->attributes['product']['type'][$extrafieldName]) && $extrafields->attributes['product']['type'][$extrafieldName] === 'sellist') {
13608 $isSellist = true;
13609 $paramArray = $extrafields->attributes['product']['param'][$extrafieldName]['options'] ?? [];
13610 $param = array_key_first($paramArray);
13611 list($table, $label, $key) = explode(':', $param);
13612 }
13613 }
13614
13615 if (preg_match('/\.(id|rowid)$/', $field)) { // Special case for rowid that is sometimes a ref so used as a search field
13616 $newres .= $db->sanitize($field) . " = " . (is_numeric($tmpcrit) ? ((float) $tmpcrit) : '0');
13617 } else {
13618 $tmpcrit2 = $tmpcrit;
13619 $tmpbefore = '%';
13620 $tmpafter = '%';
13621 $tmps = '';
13622
13623 if ($isSellist) {
13624 $newres .= $field . " IN (SELECT t." . $key . " FROM " . $db->prefix() . $table . " AS t WHERE t." . $label . " LIKE '%" . $db->escape($tmpcrit2) . "%')";
13625 } else {
13626 if (preg_match('/^!/', $tmpcrit)) {
13627 $tmps .= $db->sanitize($field) . " NOT LIKE "; // ! as exclude character
13628 $tmpcrit2 = preg_replace('/^!/', '', $tmpcrit2);
13629 } else {
13630 $tmps .= $db->sanitize($field) . " LIKE ";
13631 }
13632 $tmps .= "'";
13633
13634 if (preg_match('/^[\^\$]/', $tmpcrit)) {
13635 $tmpbefore = '';
13636 $tmpcrit2 = preg_replace('/^[\^\$]/', '', $tmpcrit2);
13637 }
13638 if (preg_match('/[\^\$]$/', $tmpcrit)) {
13639 $tmpafter = '';
13640 $tmpcrit2 = preg_replace('/[\^\$]$/', '', $tmpcrit2);
13641 }
13642
13643 if ($tmpcrit2 == '' || preg_match('/^!/', $tmpcrit)) {
13644 $tmps = "(" . $tmps;
13645 }
13646 $newres .= $tmps;
13647 $newres .= $tmpbefore;
13648 $newres .= $db->escape($tmpcrit2);
13649 $newres .= $tmpafter;
13650 $newres .= "'";
13651 if ($tmpcrit2 == '' || preg_match('/^!/', $tmpcrit)) {
13652 $newres .= " OR " . $field . " IS NULL)";
13653 }
13654 }
13655 }
13656
13657 $i3++;
13658 }
13659
13660 $i2++; // a criteria for 1 more field was added to string
13661 }
13662 }
13663
13664 if ($sqltoadd) {
13665 $newres .= ($newres ? '' : ' OR ').str_replace('__KEYTOSEARCH__', $crit, $sqltoadd);
13666 }
13667
13668 if ($newres) {
13669 $res = $res . ($res ? ' AND ' : '') . ($i2 > 1 ? '(' : '') . $newres . ($i2 > 1 ? ')' : '');
13670 }
13671 $i1++;
13672 }
13673 $res = ($nofirstand ? "" : " AND ") . "(" . $res . ")";
13674
13675 return $res;
13676}
13677
13685function showSimpleOrderTable($outputlangs, $object)
13686{
13687
13688 global $conf;
13689
13690 $discountIsAvailable = false;
13691 $orderPositionHasNoPrice = false;
13692
13693 if(!property_exists($object->lines[0], "remise_percent") ||
13694 !property_exists($object->lines[0], "fk_unit") ||
13695 !property_exists($object->lines[0], "multicurrency_total_ttc") ||
13696 !property_exists($object->lines[0], "description") ||
13697 !property_exists($object->lines[0], "qty")) {
13698 return"";
13699 }
13700
13701 foreach($object->lines as $order_position) {
13702
13703 if(!property_exists($order_position, "price")){
13704 $orderPositionHasNoPrice = true;
13705 break;
13706 }
13707
13708 if(!empty($order_position->remise_percent)){
13709 $discountIsAvailable = true;
13710 break;
13711 }
13712 };
13713
13714 if($orderPositionHasNoPrice){
13715 return "";
13716 }
13717
13718 $discountHeader = $discountIsAvailable ? `<th style="width:120px">{$outputlangs->trans("Discount")}</th>` : "";
13719
13720 $table = '<table border="0" cellpadding="1" cellspacing="1">';
13721
13722 $table .= <<<TABLEHEADER
13723 <thead>
13724 <tr>
13725 <th style="width:50px; text-align:left">#</th>
13726 <th style="text-align:left">{$outputlangs->trans("Description")}</th>
13727 <th style="width:120px; text-align:right;">{$outputlangs->trans("Price")}</th>
13728 <th style="width:100px; text-align:right;">{$outputlangs->trans("Quantity")}</th>
13729 <th style="width:120px; text-align:right;">{$outputlangs->trans("Unit")}</th>
13730 {$discountHeader}
13731 <th style="width:120px; text-align:right;">{$outputlangs->trans("Sum")}</th>
13732 </tr>
13733 </thead>
13734 <tbody>
13735 TABLEHEADER;
13736
13737 foreach($object->lines as $index => $order_position) {
13738
13739 $position = $index + 1;
13740 $price = price($order_position->price, 0, $outputlangs, 0, -1, -1, $conf->currency);
13741 $unit = measuringUnitString($order_position->fk_unit,'','',1);
13742 $total = price($order_position->multicurrency_total_ttc, 0, $outputlangs, 0, -1, -1, $conf->currency);
13743 $discount = $discountIsAvailable ? `<td style="text-align:center">{$order_position->remise_percent}%</td>` : "";
13744
13745
13746 $table .= <<<ORDERPOSITION
13747 <tr>
13748 <td>$position</td>
13749 <td>$order_position->description</td>
13750 <td style="text-align:right">$price</td>
13751 <td style="text-align:right">$order_position->qty</td>
13752 <td style="text-align:right">$unit</td>
13753 {$discount}
13754 <td style="text-align:right">$total</td>
13755
13756 </tr>
13757 ORDERPOSITION;
13758
13759 }
13760 $table .= '</tbody></table>';
13761 return $table;
13762}
13763
13770function showDirectDownloadLink($object)
13771{
13772 global $langs;
13773
13774 $out = '';
13775 $url = $object->getLastMainDocLink($object->element);
13776
13777 $out .= img_picto($langs->trans("PublicDownloadLinkDesc"), 'globe') . ' <span class="opacitymedium">' . $langs->trans("DirectDownloadLink") . '</span><br>';
13778 if ($url) {
13779 $out .= '<div class="urllink"><input type="text" id="directdownloadlink" class="quatrevingtpercent" value="' . $url . '"></div>';
13780 $out .= ajax_autoselect("directdownloadlink", '');
13781 } else {
13782 $out .= '<div class="urllink">' . $langs->trans("FileNotShared") . '</div>';
13783 }
13784
13785 return $out;
13786}
13787
13796function getImageFileNameForSize($file, $extName, $extImgTarget = '')
13797{
13798 $dirName = dirname($file);
13799 if ($dirName == '.') {
13800 $dirName = '';
13801 }
13802
13803 if (!in_array($extName, array('', '_small', '_mini'))) {
13804 return 'Bad parameter extName';
13805 }
13806
13807 $fileName = preg_replace('/(\.gif|\.jpeg|\.jpg|\.png|\.bmp|\.webp|\.avif)$/i', '', $file); // We remove image extension, whatever is its case
13808 $fileName = basename($fileName);
13809
13810 if (empty($extImgTarget)) {
13811 $extImgTarget = (preg_match('/\.jpg$/i', $file) ? '.jpg' : '');
13812 }
13813 if (empty($extImgTarget)) {
13814 $extImgTarget = (preg_match('/\.jpeg$/i', $file) ? '.jpeg' : '');
13815 }
13816 if (empty($extImgTarget)) {
13817 $extImgTarget = (preg_match('/\.gif$/i', $file) ? '.gif' : '');
13818 }
13819 if (empty($extImgTarget)) {
13820 $extImgTarget = (preg_match('/\.png$/i', $file) ? '.png' : '');
13821 }
13822 if (empty($extImgTarget)) {
13823 $extImgTarget = (preg_match('/\.bmp$/i', $file) ? '.bmp' : '');
13824 }
13825 if (empty($extImgTarget)) {
13826 $extImgTarget = (preg_match('/\.webp$/i', $file) ? '.webp' : '');
13827 }
13828 if (empty($extImgTarget)) {
13829 $extImgTarget = (preg_match('/\.avif$/i', $file) ? '.avif' : '');
13830 }
13831
13832 if (!$extImgTarget) {
13833 return $file;
13834 }
13835
13836 $subdir = '';
13837 if ($extName) {
13838 $subdir = 'thumbs/';
13839 }
13840
13841 return ($dirName ? $dirName . '/' : '') . $subdir . $fileName . $extName . $extImgTarget; // New filename for thumb
13842}
13843
13844
13854function getAdvancedPreviewUrl($modulepart, $relativepath, $alldata = 0, $param = '')
13855{
13856 global $conf, $langs;
13857
13858 if (empty($conf->use_javascript_ajax)) {
13859 return '';
13860 }
13861
13862 $isAllowedForPreview = dolIsAllowedForPreview($relativepath);
13863
13864 if ($alldata == 1) {
13865 if ($isAllowedForPreview) {
13866 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));
13867 } else {
13868 return array();
13869 }
13870 }
13871
13872 // old behavior, return a string
13873 if ($isAllowedForPreview) {
13874 $tmpurl = DOL_URL_ROOT . '/document.php?modulepart=' . urlencode($modulepart) . '&attachment=0&file=' . urlencode($relativepath) . ($param ? '&' . $param : '');
13875 $title = $langs->transnoentities("Preview");
13876 //$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().
13877 //$tmpurl = 'file='.urlencode("'-alert(document.domain)-'_small.jpg"); // An example of tmpurl that should be blocked by the dol_escape_uri()
13878
13879 // 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,
13880 // 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.
13881 // Using the dol_escape_uri guarantee that we encode for URI so decode retrieve original expected value.
13882 return 'javascript:' . dol_escape_uri('document_preview(\'' . dol_escape_js($tmpurl) . '\', \'' . dol_escape_js(dol_mimetype($relativepath)) . '\', \'' . dol_escape_js($title) . '\')');
13883 } else {
13884 return '';
13885 }
13886}
13887
13894function getLabelSpecialCode($idcode)
13895{
13896 global $langs;
13897
13898 $arrayspecialines = array(1 => 'Transport', 2 => 'EcoTax', 3 => 'Option');
13899 if ($idcode > 10) {
13900 return 'Module ID ' . $idcode;
13901 }
13902 if (!empty($arrayspecialines[$idcode])) {
13903 return $langs->trans($arrayspecialines[$idcode]);
13904 }
13905 return '';
13906}
13907
13917function ajax_autoselect($htmlname, $addlink = '', $textonlink = 'Link')
13918{
13919 global $langs;
13920 $out = '<script nonce="' . getNonce() . '">
13921 jQuery(document).ready(function () {
13922 jQuery("' . ((strpos($htmlname, '.') === 0 ? '' : '#') . $htmlname) . '").click(function() { jQuery(this).select(); } );
13923 });
13924 </script>';
13925 if ($addlink) {
13926 if ($textonlink === 'image') {
13927 $out .= ' <a href="' . $addlink . '" target="_blank" rel="noopener noreferrer">' . img_picto('', 'globe') . '</a>';
13928 } else {
13929 $out .= ' <a href="' . $addlink . '" target="_blank" rel="noopener noreferrer">' . $langs->trans("Link") . '</a>';
13930 }
13931 }
13932 return $out;
13933}
13934
13942function dolIsAllowedForPreview($file)
13943{
13944 // Check .noexe extension in filename
13945 if (preg_match('/\.noexe$/i', $file)) {
13946 return 0;
13947 }
13948
13949 // Check mime types
13950 $mime_preview = array('avif', 'bmp', 'jpeg', 'png', 'gif', 'tiff', 'pdf', 'plain', 'css', 'webp', 'webm', 'mp4');
13951 if (getDolGlobalString('MAIN_ALLOW_SVG_FILES_AS_IMAGES')) {
13952 $mime_preview[] = 'svg+xml';
13953 }
13954 //$mime_preview[]='vnd.oasis.opendocument.presentation';
13955 //$mime_preview[]='archive';
13956 $num_mime = array_search(dol_mimetype($file, '', 1), $mime_preview);
13957 if ($num_mime !== false) {
13958 return 1;
13959 }
13960
13961 // By default, not allowed for preview
13962 return 0;
13963}
13964
13965
13975function dol_mimetype($file, $default = 'application/octet-stream', $mode = 0)
13976{
13977 $mime = $default;
13978 $imgmime = 'other.png';
13979 $famime = 'file-o';
13980 $srclang = '';
13981
13982 $tmpfile = preg_replace('/\.noexe$/', '', $file);
13983
13984 // Plain text files
13985 if (preg_match('/\.txt$/i', $tmpfile)) {
13986 $mime = 'text/plain';
13987 $imgmime = 'text.png';
13988 $famime = 'file-alt';
13989 } elseif (preg_match('/\.rtx$/i', $tmpfile)) {
13990 $mime = 'text/richtext';
13991 $imgmime = 'text.png';
13992 $famime = 'file-alt';
13993 } elseif (preg_match('/\.csv$/i', $tmpfile)) {
13994 $mime = 'text/csv';
13995 $imgmime = 'text.png';
13996 $famime = 'file-csv';
13997 } elseif (preg_match('/\.tsv$/i', $tmpfile)) {
13998 $mime = 'text/tab-separated-values';
13999 $imgmime = 'text.png';
14000 $famime = 'file-alt';
14001 } elseif (preg_match('/\.(cf|conf|log)$/i', $tmpfile)) {
14002 $mime = 'text/plain';
14003 $imgmime = 'text.png';
14004 $famime = 'file-alt';
14005 } elseif (preg_match('/\.ini$/i', $tmpfile)) {
14006 $mime = 'text/plain';
14007 $imgmime = 'text.png';
14008 $srclang = 'ini';
14009 $famime = 'file-alt';
14010 } elseif (preg_match('/\.md$/i', $tmpfile)) {
14011 $mime = 'text/plain';
14012 $imgmime = 'text.png';
14013 $srclang = 'md';
14014 $famime = 'file-alt';
14015 } elseif (preg_match('/\.css$/i', $tmpfile)) {
14016 $mime = 'text/css';
14017 $imgmime = 'css.png';
14018 $srclang = 'css';
14019 $famime = 'file-alt';
14020 } elseif (preg_match('/\.lang$/i', $tmpfile)) {
14021 $mime = 'text/plain';
14022 $imgmime = 'text.png';
14023 $srclang = 'lang';
14024 $famime = 'file-alt';
14025 } elseif (preg_match('/\.(crt|cer|key|pub)$/i', $tmpfile)) { // Certificate files
14026 $mime = 'text/plain';
14027 $imgmime = 'text.png';
14028 $famime = 'file-alt';
14029 } elseif (preg_match('/\.(html|htm|shtml)$/i', $tmpfile)) { // XML based (HTML/XML/XAML)
14030 $mime = 'text/html';
14031 $imgmime = 'html.png';
14032 $srclang = 'html';
14033 $famime = 'file-alt';
14034 } elseif (preg_match('/\.(xml|xhtml)$/i', $tmpfile)) {
14035 $mime = 'text/xml';
14036 $imgmime = 'other.png';
14037 $srclang = 'xml';
14038 $famime = 'file-alt';
14039 } elseif (preg_match('/\.xaml$/i', $tmpfile)) {
14040 $mime = 'text/xml';
14041 $imgmime = 'other.png';
14042 $srclang = 'xaml';
14043 $famime = 'file-alt';
14044 } elseif (preg_match('/\.bas$/i', $tmpfile)) { // Languages
14045 $mime = 'text/plain';
14046 $imgmime = 'text.png';
14047 $srclang = 'bas';
14048 $famime = 'file-code';
14049 } elseif (preg_match('/\.(c)$/i', $tmpfile)) {
14050 $mime = 'text/plain';
14051 $imgmime = 'text.png';
14052 $srclang = 'c';
14053 $famime = 'file-code';
14054 } elseif (preg_match('/\.(cpp)$/i', $tmpfile)) {
14055 $mime = 'text/plain';
14056 $imgmime = 'text.png';
14057 $srclang = 'cpp';
14058 $famime = 'file-code';
14059 } elseif (preg_match('/\.cs$/i', $tmpfile)) {
14060 $mime = 'text/plain';
14061 $imgmime = 'text.png';
14062 $srclang = 'cs';
14063 $famime = 'file-code';
14064 } elseif (preg_match('/\.(h)$/i', $tmpfile)) {
14065 $mime = 'text/plain';
14066 $imgmime = 'text.png';
14067 $srclang = 'h';
14068 $famime = 'file-code';
14069 } elseif (preg_match('/\.(java|jsp)$/i', $tmpfile)) {
14070 $mime = 'text/plain';
14071 $imgmime = 'text.png';
14072 $srclang = 'java';
14073 $famime = 'file-code';
14074 } elseif (preg_match('/\.php([0-9]{1})?$/i', $tmpfile)) {
14075 $mime = 'text/plain';
14076 $imgmime = 'php.png';
14077 $srclang = 'php';
14078 $famime = 'file-code';
14079 } elseif (preg_match('/\.phtml$/i', $tmpfile)) {
14080 $mime = 'text/plain';
14081 $imgmime = 'php.png';
14082 $srclang = 'php';
14083 $famime = 'file-code';
14084 } elseif (preg_match('/\.(pl|pm)$/i', $tmpfile)) {
14085 $mime = 'text/plain';
14086 $imgmime = 'pl.png';
14087 $srclang = 'perl';
14088 $famime = 'file-code';
14089 } elseif (preg_match('/\.sql$/i', $tmpfile)) {
14090 $mime = 'text/plain';
14091 $imgmime = 'text.png';
14092 $srclang = 'sql';
14093 $famime = 'file-code';
14094 } elseif (preg_match('/\.js$/i', $tmpfile)) {
14095 $mime = 'text/x-javascript';
14096 $imgmime = 'jscript.png';
14097 $srclang = 'js';
14098 $famime = 'file-code';
14099 } elseif (preg_match('/\.odp$/i', $tmpfile)) { // Open office
14100 $mime = 'application/vnd.oasis.opendocument.presentation';
14101 $imgmime = 'ooffice.png';
14102 $famime = 'file-powerpoint';
14103 } elseif (preg_match('/\.ods$/i', $tmpfile)) {
14104 $mime = 'application/vnd.oasis.opendocument.spreadsheet';
14105 $imgmime = 'ooffice.png';
14106 $famime = 'file-excel';
14107 } elseif (preg_match('/\.odt$/i', $tmpfile)) {
14108 $mime = 'application/vnd.oasis.opendocument.text';
14109 $imgmime = 'ooffice.png';
14110 $famime = 'file-word';
14111 } elseif (preg_match('/\.mdb$/i', $tmpfile)) { // MS Office
14112 $mime = 'application/msaccess';
14113 $imgmime = 'mdb.png';
14114 $famime = 'file';
14115 } elseif (preg_match('/\.doc[xm]?$/i', $tmpfile)) {
14116 $mime = 'application/msword';
14117 $imgmime = 'doc.png';
14118 $famime = 'file-word';
14119 } elseif (preg_match('/\.dot[xm]?$/i', $tmpfile)) {
14120 $mime = 'application/msword';
14121 $imgmime = 'doc.png';
14122 $famime = 'file-word';
14123 } elseif (preg_match('/\.xlt(x)?$/i', $tmpfile)) {
14124 $mime = 'application/vnd.ms-excel';
14125 $imgmime = 'xls.png';
14126 $famime = 'file-excel';
14127 } elseif (preg_match('/\.xla(m)?$/i', $tmpfile)) {
14128 $mime = 'application/vnd.ms-excel';
14129 $imgmime = 'xls.png';
14130 $famime = 'file-excel';
14131 } elseif (preg_match('/\.xls$/i', $tmpfile)) {
14132 $mime = 'application/vnd.ms-excel';
14133 $imgmime = 'xls.png';
14134 $famime = 'file-excel';
14135 } elseif (preg_match('/\.xls[bmx]$/i', $tmpfile)) {
14136 $mime = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
14137 $imgmime = 'xls.png';
14138 $famime = 'file-excel';
14139 } elseif (preg_match('/\.pps[mx]?$/i', $tmpfile)) {
14140 $mime = 'application/vnd.ms-powerpoint';
14141 $imgmime = 'ppt.png';
14142 $famime = 'file-powerpoint';
14143 } elseif (preg_match('/\.ppt[mx]?$/i', $tmpfile)) {
14144 $mime = 'application/x-mspowerpoint';
14145 $imgmime = 'ppt.png';
14146 $famime = 'file-powerpoint';
14147 } elseif (preg_match('/\.pdf$/i', $tmpfile)) { // Other
14148 $mime = 'application/pdf';
14149 $imgmime = 'pdf.png';
14150 $famime = 'file-pdf';
14151 } elseif (preg_match('/\.bat$/i', $tmpfile)) { // Scripts
14152 $mime = 'text/x-bat';
14153 $imgmime = 'script.png';
14154 $srclang = 'dos';
14155 $famime = 'file-code';
14156 } elseif (preg_match('/\.sh$/i', $tmpfile)) {
14157 $mime = 'text/x-sh';
14158 $imgmime = 'script.png';
14159 $srclang = 'bash';
14160 $famime = 'file-code';
14161 } elseif (preg_match('/\.ksh$/i', $tmpfile)) {
14162 $mime = 'text/x-ksh';
14163 $imgmime = 'script.png';
14164 $srclang = 'bash';
14165 $famime = 'file-code';
14166 } elseif (preg_match('/\.bash$/i', $tmpfile)) {
14167 $mime = 'text/x-bash';
14168 $imgmime = 'script.png';
14169 $srclang = 'bash';
14170 $famime = 'file-code';
14171 } elseif (preg_match('/\.ico$/i', $tmpfile)) { // Images
14172 $mime = 'image/x-icon';
14173 $imgmime = 'image.png';
14174 $famime = 'file-image';
14175 } elseif (preg_match('/\.(jpg|jpeg)$/i', $tmpfile)) {
14176 $mime = 'image/jpeg';
14177 $imgmime = 'image.png';
14178 $famime = 'file-image';
14179 } elseif (preg_match('/\.png$/i', $tmpfile)) {
14180 $mime = 'image/png';
14181 $imgmime = 'image.png';
14182 $famime = 'file-image';
14183 } elseif (preg_match('/\.gif$/i', $tmpfile)) {
14184 $mime = 'image/gif';
14185 $imgmime = 'image.png';
14186 $famime = 'file-image';
14187 } elseif (preg_match('/\.bmp$/i', $tmpfile)) {
14188 $mime = 'image/bmp';
14189 $imgmime = 'image.png';
14190 $famime = 'file-image';
14191 } elseif (preg_match('/\.(tif|tiff)$/i', $tmpfile)) {
14192 $mime = 'image/tiff';
14193 $imgmime = 'image.png';
14194 $famime = 'file-image';
14195 } elseif (preg_match('/\.svg$/i', $tmpfile)) {
14196 $mime = 'image/svg+xml';
14197 $imgmime = 'image.png';
14198 $famime = 'file-image';
14199 } elseif (preg_match('/\.webp$/i', $tmpfile)) {
14200 $mime = 'image/webp';
14201 $imgmime = 'image.png';
14202 $famime = 'file-image';
14203 } elseif (preg_match('/\.vcs$/i', $tmpfile)) { // Calendar
14204 $mime = 'text/calendar';
14205 $imgmime = 'other.png';
14206 $famime = 'file-alt';
14207 } elseif (preg_match('/\.ics$/i', $tmpfile)) {
14208 $mime = 'text/calendar';
14209 $imgmime = 'other.png';
14210 $famime = 'file-alt';
14211 } elseif (preg_match('/\.torrent$/i', $tmpfile)) { // Other
14212 $mime = 'application/x-bittorrent';
14213 $imgmime = 'other.png';
14214 $famime = 'file-o';
14215 } elseif (preg_match('/\.(mp3|ogg|au|wav|wma|mid)$/i', $tmpfile)) { // Audio
14216 $mime = 'audio';
14217 $imgmime = 'audio.png';
14218 $famime = 'file-audio';
14219 } elseif (preg_match('/\.mp4$/i', $tmpfile)) { // Video
14220 $mime = 'video/mp4';
14221 $imgmime = 'video.png';
14222 $famime = 'file-video';
14223 } elseif (preg_match('/\.ogv$/i', $tmpfile)) {
14224 $mime = 'video/ogg';
14225 $imgmime = 'video.png';
14226 $famime = 'file-video';
14227 } elseif (preg_match('/\.webm$/i', $tmpfile)) {
14228 $mime = 'video/webm';
14229 $imgmime = 'video.png';
14230 $famime = 'file-video';
14231 } elseif (preg_match('/\.avif$/i', $tmpfile)) {
14232 $mime = 'image/avif';
14233 $imgmime = 'image.png';
14234 $famime = 'file-image';
14235 } elseif (preg_match('/\.avi$/i', $tmpfile)) {
14236 $mime = 'video/x-msvideo';
14237 $imgmime = 'video.png';
14238 $famime = 'file-video';
14239 } elseif (preg_match('/\.divx$/i', $tmpfile)) {
14240 $mime = 'video/divx';
14241 $imgmime = 'video.png';
14242 $famime = 'file-video';
14243 } elseif (preg_match('/\.xvid$/i', $tmpfile)) {
14244 $mime = 'video/xvid';
14245 $imgmime = 'video.png';
14246 $famime = 'file-video';
14247 } elseif (preg_match('/\.(wmv|mpg|mpeg)$/i', $tmpfile)) {
14248 $mime = 'video';
14249 $imgmime = 'video.png';
14250 $famime = 'file-video';
14251 } elseif (preg_match('/\.(zip|rar|gz|tgz|xz|z|cab|bz2|7z|tar|lzh|zst)$/i', $tmpfile)) { // Archive
14252 // application/xxx where zzz is zip, ...
14253 $mime = 'archive';
14254 $imgmime = 'archive.png';
14255 $famime = 'file-archive';
14256 } elseif (preg_match('/\.(exe|com)$/i', $tmpfile)) { // Exe
14257 $mime = 'application/octet-stream';
14258 $imgmime = 'other.png';
14259 $famime = 'file-o';
14260 } elseif (preg_match('/\.(dll|lib|o|so|a)$/i', $tmpfile)) { // Lib
14261 $mime = 'library';
14262 $imgmime = 'library.png';
14263 $famime = 'file-o';
14264 } elseif (preg_match('/\.err$/i', $tmpfile)) { // phpcs:ignore
14265 $mime = 'error';
14266 $imgmime = 'error.png';
14267 $famime = 'file-alt';
14268 }
14269
14270 if ($famime == 'file-o') {
14271 // file-o seems to not work in fontawesome 5
14272 $famime = 'file';
14273 }
14274
14275 // Return mimetype string
14276 switch ((int) $mode) {
14277 case 1:
14278 $tmp = explode('/', $mime);
14279 return (!empty($tmp[1]) ? $tmp[1] : $tmp[0]);
14280 case 2:
14281 return $imgmime;
14282 case 3:
14283 return $srclang;
14284 case 4:
14285 return $famime;
14286 }
14287 return $mime;
14288}
14289
14301function getDictionaryValue($tablename, $field, $id, $checkentity = false, $rowidfield = 'rowid')
14302{
14303 global $conf, $db;
14304
14305 $tablename = preg_replace('/^' . preg_quote(MAIN_DB_PREFIX, '/') . '/', '', $tablename); // Clean name of table for backward compatibility.
14306
14307 $dictvalues = (isset($conf->cache['dictvalues_' . $tablename]) ? $conf->cache['dictvalues_' . $tablename] : null);
14308
14309 if (is_null($dictvalues)) {
14310 $dictvalues = array();
14311
14312 $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
14313 if ($checkentity) {
14314 $sql .= ' AND entity IN (0,' . getEntity($tablename) . ')';
14315 }
14316
14317 $resql = $db->query($sql);
14318 if ($resql) {
14319 while ($obj = $db->fetch_object($resql)) {
14320 $dictvalues[$obj->$rowidfield] = $obj; // $obj is stdClass
14321 }
14322 } else {
14323 dol_print_error($db);
14324 }
14325
14326 $conf->cache['dictvalues_' . $tablename] = $dictvalues;
14327 }
14328
14329 if (!empty($dictvalues[$id])) {
14330 // Found
14331 $tmp = $dictvalues[$id];
14332 return (property_exists($tmp, $field) ? $tmp->$field : '');
14333 } else {
14334 // Not found
14335 return '';
14336 }
14337}
14338
14345function colorIsLight($stringcolor)
14346{
14347 $stringcolor = str_replace('#', '', $stringcolor);
14348 $res = -1;
14349 if (!empty($stringcolor)) {
14350 $res = 0;
14351 $tmp = explode(',', $stringcolor);
14352 if (count($tmp) > 1) { // This is a comma RGB ('255','255','255')
14353 $r = $tmp[0];
14354 $g = $tmp[1];
14355 $b = $tmp[2];
14356 } else {
14357 $hexr = $stringcolor[0] . $stringcolor[1];
14358 $hexg = $stringcolor[2] . $stringcolor[3];
14359 $hexb = $stringcolor[4] . $stringcolor[5];
14360 $r = hexdec($hexr);
14361 $g = hexdec($hexg);
14362 $b = hexdec($hexb);
14363 }
14364 $bright = (max($r, $g, $b) + min($r, $g, $b)) / 510.0; // HSL algorithm
14365 if ($bright > 0.6) {
14366 $res = 1;
14367 }
14368 }
14369 return $res;
14370}
14371
14380function isVisibleToUserType($type_user, &$menuentry, &$listofmodulesforexternal)
14381{
14382 //print 'type_user='.$type_user.' module='.$menuentry['module'].' enabled='.$menuentry['enabled'].' perms='.$menuentry['perms'];
14383 //print 'ok='.in_array($menuentry['module'], $listofmodulesforexternal);
14384 if (empty($menuentry['enabled'])) {
14385 return 0; // Entry disabled by condition
14386 }
14387 if ($type_user && array_key_exists('module', $menuentry) && $menuentry['module']) {
14388 $tmploops = explode('|', $menuentry['module']);
14389 $found = 0;
14390 foreach ($tmploops as $tmploop) {
14391 if (in_array($tmploop, $listofmodulesforexternal)) {
14392 $found++;
14393 break;
14394 }
14395 }
14396 if (!$found) {
14397 return 0; // Entry is for menus all excluded to external users
14398 }
14399 }
14400 if (!$menuentry['perms'] && $type_user) {
14401 return 0; // No permissions and user is external
14402 }
14403 if (!$menuentry['perms'] && getDolGlobalString('MAIN_MENU_HIDE_UNAUTHORIZED')) {
14404 return 0; // No permissions and option to hide when not allowed, even for internal user, is on
14405 }
14406 if (!$menuentry['perms']) {
14407 return 2; // No permissions and user is external
14408 }
14409 return 1;
14410}
14411
14419function roundUpToNextMultiple($n, $x = 5)
14420{
14421 $result = (ceil($n) % $x === 0) ? ceil($n) : (round(($n + $x / 2) / $x) * $x);
14422 return (int) $result;
14423}
14424
14436function dolGetBadge($label, $html = '', $type = 'primary', $mode = '', $url = '', $params = array())
14437{
14438 $csstouse = 'badge';
14439 $csstouse .= (!empty($mode) ? ' badge-' . $mode : '');
14440 $csstouse .= (!empty($type) ? ' badge-' . $type : '');
14441 $csstouse .= (empty($params['css']) ? '' : ' ' . $params['css']);
14442
14443 $attr = array(
14444 'class' => $csstouse
14445 );
14446
14447 if (empty($html)) {
14448 $html = $label;
14449 }
14450
14451 if (!empty($url)) {
14452 $attr['href'] = $url;
14453 }
14454
14455 if ($mode === 'dot') {
14456 $attr['class'] .= ' classfortooltip';
14457 $attr['title'] = $html;
14458 $attr['aria-label'] = $label;
14459 $html = '';
14460 }
14461
14462 // Override attr
14463 if (!empty($params['attr']) && is_array($params['attr'])) {
14464 foreach ($params['attr'] as $key => $value) {
14465 if ($key == 'class') {
14466 $attr['class'] .= ' ' . $value;
14467 } elseif ($key == 'classOverride') {
14468 $attr['class'] = $value;
14469 } else {
14470 $attr[$key] = $value;
14471 }
14472 }
14473 }
14474
14475 // TODO: add hook
14476
14477 // escape all attribute
14478 $attr = array_map('dolPrintHTMLForAttribute', $attr);
14479
14480 $TCompiledAttr = array();
14481 foreach ($attr as $key => $value) {
14482 $TCompiledAttr[] = $key . '="' . $value . '"';
14483 }
14484
14485 $compiledAttributes = !empty($TCompiledAttr) ? implode(' ', $TCompiledAttr) : '';
14486
14487 $tag = !empty($url) ? 'a' : 'span';
14488
14489 return '<' . $tag . ' ' . $compiledAttributes . '>' . $html . '</' . $tag . '>';
14490}
14491
14492
14505function dolGetStatus($statusLabel = '', $statusLabelShort = '', $html = '', $statusType = 'status0', $displayMode = 0, $url = '', $params = array())
14506{
14507 global $conf;
14508
14509 $return = '';
14510 $dolGetBadgeParams = array();
14511
14512 if (!empty($params['badgeParams'])) {
14513 $dolGetBadgeParams = $params['badgeParams'];
14514 }
14515
14516 // TODO : add a hook
14517 if ($displayMode == 0) {
14518 $return = !empty($html) ? $html : (empty($conf->dol_optimize_smallscreen) ? $statusLabel : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort));
14519 } elseif ($displayMode == 1) {
14520 $return = !empty($html) ? $html : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort);
14521 } elseif (getDolGlobalString('MAIN_STATUS_USES_IMAGES')) {
14522 // Use status with images (for backward compatibility)
14523 $return = '';
14524 $htmlLabel = (in_array($displayMode, array(1, 2, 5)) ? '<span class="hideonsmartphone">' : '') . (!empty($html) ? $html : $statusLabel) . (in_array($displayMode, array(1, 2, 5)) ? '</span>' : '');
14525 $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>' : '');
14526
14527 // For small screen, we always use the short label instead of long label.
14528 if (!empty($conf->dol_optimize_smallscreen)) {
14529 if ($displayMode == 0) {
14530 $displayMode = 1;
14531 } elseif ($displayMode == 4) {
14532 $displayMode = 2;
14533 } elseif ($displayMode == 6) {
14534 $displayMode = 5;
14535 }
14536 }
14537
14538 // For backward compatibility. Image's filename are still in French, so we use this array to convert
14539 $statusImg = array(
14540 'status0' => 'statut0',
14541 'status1' => 'statut1',
14542 'status2' => 'statut2',
14543 'status3' => 'statut3',
14544 'status4' => 'statut4',
14545 'status5' => 'statut5',
14546 'status6' => 'statut6',
14547 'status7' => 'statut7',
14548 'status8' => 'statut8',
14549 'status9' => 'statut9'
14550 );
14551
14552 if (!empty($statusImg[$statusType])) {
14553 $htmlImg = img_picto($statusLabel, $statusImg[$statusType]);
14554 } else {
14555 $htmlImg = img_picto($statusLabel, $statusType);
14556 }
14557
14558 if ($displayMode === 2) {
14559 $return = $htmlImg . ' ' . $htmlLabelShort;
14560 } elseif ($displayMode === 3) {
14561 $return = $htmlImg;
14562 } elseif ($displayMode === 4) {
14563 $return = $htmlImg . ' ' . $htmlLabel;
14564 } elseif ($displayMode === 5) {
14565 $return = $htmlLabelShort . ' ' . $htmlImg;
14566 } else { // $displayMode >= 6
14567 $return = $htmlLabel . ' ' . $htmlImg;
14568 }
14569 } elseif (!getDolGlobalString('MAIN_STATUS_USES_IMAGES') && !empty($displayMode)) {
14570 // Use new badge
14571 $statusLabelShort = (empty($statusLabelShort) ? $statusLabel : $statusLabelShort);
14572
14573 $dolGetBadgeParams['attr']['class'] = 'badge-status';
14574 if (empty($dolGetBadgeParams['attr']['title'])) {
14575 $dolGetBadgeParams['attr']['title'] = empty($params['tooltip']) ? $statusLabel : ($params['tooltip'] != 'no' ? $params['tooltip'] : '');
14576 } else { // If a title was forced from $params['badgeParams']['attr']['title'], we set the class to get it as a tooltip.
14577 $dolGetBadgeParams['attr']['class'] .= ' classfortooltip';
14578 // And if we use tooltip, we can output title in HTML @phan-suppress-next-line PhanTypeInvalidDimOffset
14579 $dolGetBadgeParams['attr']['title'] = dol_htmlentitiesbr((string) $dolGetBadgeParams['attr']['title'], 1);
14580 }
14581
14582 if ($displayMode == 3) {
14583 $return = dolGetBadge((empty($conf->dol_optimize_smallscreen) ? $statusLabel : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort)), '', $statusType, 'dot', $url, $dolGetBadgeParams);
14584 } elseif ($displayMode === 5) {
14585 $return = dolGetBadge($statusLabelShort, $html, $statusType, '', $url, $dolGetBadgeParams);
14586 } else {
14587 $return = dolGetBadge(((empty($conf->dol_optimize_smallscreen) && $displayMode != 2) ? $statusLabel : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort)), $html, $statusType, '', $url, $dolGetBadgeParams);
14588 }
14589 }
14590
14591 return $return;
14592}
14593
14594
14630function dolGetButtonAction($label, $text = '', $actionType = 'default', $url = '', $id = '', $userRight = 1, $params = array())
14631{
14632 global $hookmanager, $action, $object, $langs;
14633
14634 // If $url is an array, we must build a dropdown button or recursively iterate over each value
14635 if (is_array($url)) {
14636 // Loop on $url array to remove entries of disabled modules
14637 foreach ($url as $key => $subbutton) {
14638 if (isset($subbutton['enabled']) && empty($subbutton['enabled'])) {
14639 unset($url[$key]);
14640 }
14641 }
14642
14643 $out = '';
14644
14645 if (array_key_exists('areDropdownButtons', $params) && $params["areDropdownButtons"] === false) { // @phan-suppress-current-line PhanTypeInvalidDimOffset
14646 foreach ($url as $button) {
14647 if (!empty($button['lang'])) {
14648 $langs->load($button['lang']);
14649 }
14650 $label = $langs->trans($button['label']);
14651 $text = $button['text'] ?? '';
14652 $actionType = $button['actionType'] ?? '';
14653 $tmpUrl = DOL_URL_ROOT . $button['url'] . (empty($params['backtopage']) ? '' : '&amp;backtopage=' . urlencode($params['backtopage']));
14654 $id = $button['id'] ?? '';
14655 $userRight = $button['perm'] ?? 1;
14656 $button['params'] = $button['params'] ?? []; // @phan-suppress-current-line PhanPluginDuplicateExpressionAssignmentOperation
14657
14658 $out .= dolGetButtonAction($label, $text, $actionType, $tmpUrl, $id, $userRight, $button['params']);
14659 }
14660 return $out;
14661 }
14662
14663 if (count($url) > 1) {
14664 $out .= '<div class="dropdown inline-block dropdown-holder">';
14665 $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>';
14666 $out .= '<div class="dropdown-content">';
14667 foreach ($url as $subbutton) {
14668 if (!empty($subbutton['lang'])) {
14669 $langs->load($subbutton['lang']);
14670 }
14671
14672 if (!empty($subbutton['urlraw'])) {
14673 $tmpurl = $subbutton['urlraw']; // Use raw url, no url completion, use only what developer send
14674 } else {
14675 $tmpurl = !empty($subbutton['urlroot']) ? $subbutton['urlroot'] : $subbutton['url'];
14676 $tmpurl = dolCompletUrlForDropdownButton($tmpurl, $params, empty($subbutton['urlroot']));
14677 }
14678
14679 $subbuttonparam = array();
14680 if (!empty($subbutton['attr'])) {
14681 $subbuttonparam['attr'] = $subbutton['attr'];
14682 }
14683 $subbuttonparam['isDropDown'] = (empty($params['isDropDown']) ? ($subbutton['isDropDown'] ?? false) : $params['isDropDown']);
14684
14685 $out .= dolGetButtonAction('', $langs->trans($subbutton['label']), 'default', $tmpurl, $subbutton['id'] ?? '', $subbutton['perm'], $subbuttonparam);
14686 }
14687 $out .= "</div>";
14688 $out .= "</div>";
14689 } else {
14690 foreach ($url as $subbutton) { // Should loop on 1 record only
14691 if (!empty($subbutton['lang'])) {
14692 $langs->load($subbutton['lang']);
14693 }
14694
14695 if (!empty($subbutton['urlraw'])) {
14696 $tmpurl = $subbutton['urlraw']; // Use raw url, no url completion, use only what developer send
14697 } else {
14698 $tmpurl = !empty($subbutton['urlroot']) ? $subbutton['urlroot'] : $subbutton['url'];
14699 $tmpurl = dolCompletUrlForDropdownButton($tmpurl, $params, empty($subbutton['urlroot']));
14700 }
14701
14702 $label = $langs->trans($subbutton['label']);
14703 $text = $subbutton['text'] ?? '';
14704 if (empty($text)) {
14705 $text = $label;
14706 $label = '';
14707 }
14708
14709 $out .= dolGetButtonAction($label, $text, 'default', $tmpurl, '', $subbutton['perm'], $params);
14710 }
14711 }
14712
14713 return $out;
14714 }
14715
14716 // Here, $url is a simple link
14717 if (!empty($params['isDropdown']) || !empty($params['isDropDown'])) { // Use the dropdown-item style (not for action button)
14718 $class = "dropdown-item";
14719 } else {
14720 $class = 'butAction';
14721 if ($actionType == 'edit') {
14722 $class = 'butAction butActionEdit';
14723 } elseif ($actionType == 'email') {
14724 $class = 'butAction butActionEmail';
14725 } elseif ($actionType == 'clone') {
14726 $class = 'butAction butActionClone';
14727 } elseif ($actionType == 'cancel') {
14728 $class = 'butAction butActionDelete';
14729 } elseif ($actionType == 'danger' || $actionType == 'delete') {
14730 $class = 'butAction butActionDelete';
14731 if (!empty($url) && strpos($url, 'token=') === false) {
14732 $url .= '&token=' . newToken();
14733 }
14734 }
14735 }
14736 $attr = array(
14737 'class' => $class,
14738 'href' => empty($url) ? '' : $url,
14739 'title' => $label
14740 );
14741
14742 if (empty($text)) {
14743 $text = $label;
14744 $attr['title'] = ''; // if html not set, using label on title is redundant
14745 } else {
14746 $attr['title'] = $label;
14747 $attr['aria-label'] = $label;
14748 }
14749
14750 if (empty($userRight) || $userRight < 0) {
14751 $attr['class'] = 'butActionRefused';
14752 $attr['href'] = '';
14753 $attr['title'] = (($label && $text && $label != $text) ? $label : '');
14754 $attr['title'] = ($attr['title'] ? $attr['title'] . (empty($userRight) ? '<br>' : '') : '');
14755 $attr['title'] .= ((empty($userRight) && empty($label)) ? $langs->trans('NotEnoughPermissions') : '');
14756 }
14757
14758 if (!empty($id)) {
14759 $attr['id'] = $id;
14760 }
14761
14762 // Override attr
14763 if (!empty($params['attr']) && is_array($params['attr'])) {
14764 foreach ($params['attr'] as $key => $value) {
14765 if ($key == 'class') {
14766 $attr['class'] .= ' ' . $value;
14767 } elseif ($key == 'classOverride') {
14768 $attr['class'] = $value;
14769 } else {
14770 $attr[$key] = $value;
14771 }
14772 }
14773 }
14774
14775 // automatic add tooltip when title is detected
14776 if (!empty($attr['title']) && !empty($attr['class']) && strpos($attr['class'], 'classfortooltip') === false) {
14777 $attr['class'] .= ' classfortooltip';
14778 }
14779
14780 // Js Confirm button
14781 if ($userRight && !empty($params['confirm'])) {
14782 if (!is_array($params['confirm'])) {
14783 $params['confirm'] = array();
14784 }
14785
14786 if (empty($params['confirm']['url'])) {
14787 $params['confirm']['url'] = $url . (strpos($url, '?') > 0 ? '&' : '?') . 'confirm=yes';
14788 }
14789
14790 // for js disabled compatibility set $url as call to confirm action and $params['confirm']['url'] to confirmed action
14791 $attr['data-confirm-url'] = $params['confirm']['url'];
14792 $attr['data-confirm-title'] = !empty($params['confirm']['title']) ? $params['confirm']['title'] : $langs->trans('ConfirmBtnCommonTitle', $label);
14793 $attr['data-confirm-content'] = !empty($params['confirm']['content']) ? $params['confirm']['content'] : $langs->trans('ConfirmBtnCommonContent', $label);
14794 $attr['data-confirm-content'] = preg_replace("/\r|\n/", "", $attr['data-confirm-content']);
14795 $attr['data-confirm-action-btn-label'] = !empty($params['confirm']['action-btn-label']) ? $params['confirm']['action-btn-label'] : $langs->trans('Confirm');
14796 $attr['data-confirm-cancel-btn-label'] = !empty($params['confirm']['cancel-btn-label']) ? $params['confirm']['cancel-btn-label'] : $langs->trans('CloseDialog');
14797 $attr['data-confirm-modal'] = !empty($params['confirm']['modal']) ? $params['confirm']['modal'] : true;
14798
14799 $attr['class'] .= ' butActionConfirm';
14800 }
14801
14802 if (isset($attr['href']) && empty($attr['href'])) {
14803 unset($attr['href']);
14804 }
14805
14806 // TODO replace $TCompiledAttr generation by commonHtmlAttributeBuilder given below
14807 $TCompiledAttr = array();
14808 foreach ($attr as $key => $value) {
14809 if (!empty($params['use_unsecured_unescapedattr']) && is_array($params['use_unsecured_unescapedattr']) && in_array($key, $params['use_unsecured_unescapedattr'])) {
14810 // Not recommended
14811 $value = dol_htmlentities($value, ENT_QUOTES | ENT_SUBSTITUTE);
14812 } elseif ($key == 'href') {
14813 $value = dolPrintHTMLForAttributeUrl($value);
14814 } else {
14815 $value = dolPrintHTMLForAttribute($value);
14816 }
14817
14818 $TCompiledAttr[] = $key . '="' . $value . '"'; // $value has been escaped by the dolPrintHTMLForAttribute... just before
14819 }
14820 // TODO replace $TCompiledAttr generation by uncomment line below and remove old code
14821 // $TCompiledAttr = commonHtmlAttributeBuilder($attr,$params['use_unsecured_unescapedattr'] ?? []);
14822 $compiledAttributes = empty($TCompiledAttr) ? '' : implode(' ', $TCompiledAttr);
14823
14824 $tag = !empty($attr['href']) ? 'a' : 'span';
14825
14826 $parameters = array(
14827 'TCompiledAttr' => $TCompiledAttr, // array
14828 'compiledAttributes' => $compiledAttributes, // string
14829 'attr' => $attr,
14830 'tag' => $tag,
14831 'label' => $label,
14832 'html' => $text,
14833 'actionType' => $actionType,
14834 'url' => $url,
14835 'id' => $id,
14836 'userRight' => $userRight,
14837 'params' => $params
14838 );
14839
14840 $reshook = $hookmanager->executeHooks('dolGetButtonAction', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
14841 if ($reshook < 0) {
14842 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
14843 }
14844
14845 if (empty($reshook)) {
14846 if (dol_textishtml($text)) { // If content already HTML encoded
14847 return '<' . $tag . ' ' . $compiledAttributes . '><span class="textbutton">' . $text . '</span></' . $tag . '>';
14848 } else {
14849 return '<' . $tag . ' ' . $compiledAttributes . '><span class="textbutton">' . dol_escape_htmltag($text) . '</span></' . $tag . '>';
14850 }
14851 } else {
14852 return $hookmanager->resPrint;
14853 }
14854}
14855
14891function commonHtmlAttributeBuilder($attr, array $unescapedAttr = [])
14892{
14893 $TCompiledAttr = array();
14894 if (empty($attr)) {
14895 return [];
14896 }
14897
14898 foreach ($attr as $key => $value) {
14899 // special boolean attributes case
14900 if (in_array($key, getListOfHtmlBooleanAttributes())) {
14901 if ($value) {
14902 $TCompiledAttr[$key] = $key;
14903 }
14904 continue;
14905 }
14906
14907 if (!empty($unescapedAttr) && in_array($key, $unescapedAttr)) {
14908 // Not recommended
14909 $value = dol_htmlentities((string) $value, ENT_QUOTES | ENT_SUBSTITUTE);
14910 } elseif ($key == 'href') {
14911 $value = dolPrintHTMLForAttributeUrl((string) $value);
14912 } else {
14913 $value = dolPrintHTMLForAttribute((string) $value);
14914 }
14915
14916 $TCompiledAttr[$key] = $key . '="' . $value . '"'; // $value has been escaped by the dolPrintHTMLForAttribute... just before
14917 }
14918
14919 return $TCompiledAttr;
14920}
14921
14936function getListOfHtmlBooleanAttributes(): array
14937{
14938 return [
14939 // Input / Form
14940 'checked',
14941 'disabled',
14942 'readonly',
14943 'required',
14944 'autofocus',
14945 'multiple',
14946
14947 // Option
14948 'selected',
14949
14950 // Form / General
14951 'novalidate',
14952 'formnovalidate',
14953
14954 // Media
14955 'autoplay',
14956 'controls',
14957 'loop',
14958 'muted',
14959 'playsinline',
14960
14961 // Other elements
14962 'hidden',
14963 'open',
14964 'ismap',
14965 'reversed',
14966 'allowfullscreen',
14967 'itemscope',
14968 'nomodule',
14969 'defer',
14970 'async',
14971 'default',
14972 'inert',
14973 ];
14974}
14975
14976
14985function dolCompletUrlForDropdownButton(string $url, array $params, bool $addDolUrlRoot = true)
14986{
14987 if (empty($url)) {
14988 return '';
14989 }
14990
14991 $parsedUrl = parse_url($url);
14992 if ((isset($parsedUrl['scheme']) && in_array($parsedUrl['scheme'], ['javascript', 'mailto', 'tel'])) || strpos($url, '#') === 0) {
14993 return $url;
14994 }
14995
14996 if (!empty($parsedUrl['query'])) {
14997 // Use parse_str() function to parse the string passed via URL
14998 parse_str($parsedUrl['query'], $urlQuery);
14999 if (!isset($urlQuery['backtopage']) && isset($params['backtopage'])) {
15000 $url .= '&amp;backtopage=' . urlencode($params['backtopage']);
15001 }
15002 }
15003
15004 if (!isset($parsedUrl['scheme']) && $addDolUrlRoot) {
15005 $url = DOL_URL_ROOT . $url;
15006 }
15007
15008 return $url;
15009}
15010
15011
15018function dolGetButtonTitleSeparator($moreClass = "")
15019{
15020 return '<span class="button-title-separator ' . $moreClass . '" ></span>';
15021}
15022
15029function getFieldErrorIcon($fieldValidationErrorMsg)
15030{
15031 $out = '';
15032 if (!empty($fieldValidationErrorMsg)) {
15033 $out .= '<span class="field-error-icon classfortooltip" title="' . dol_escape_htmltag($fieldValidationErrorMsg, 1) . '" role="alert" >'; // role alert is used for accessibility
15034 $out .= '<span class="fa fa-exclamation-circle" aria-hidden="true" ></span>'; // For accessibility icon is separated and aria-hidden
15035 $out .= '</span>';
15036 }
15037
15038 return $out;
15039}
15040
15053function dolGetButtonTitle($label, $helpText = '', $iconClass = 'fa fa-file', $url = '', $id = '', $status = 1, $params = array())
15054{
15055 global $langs, $user;
15056
15057 // Actually this conf is used in css too for external module compatibility and smooth transition to this function
15058 if (getDolGlobalString('MAIN_BUTTON_HIDE_UNAUTHORIZED') && (!$user->admin) && $status <= 0) {
15059 return '';
15060 }
15061 // Fix old picto fa-th-list to use fa-grid-vertical instead
15062 if ($iconClass == 'fa fa-th-list imgforviewmode') {
15063 $iconClass = ' fa fa-grip-horizontal imgforviewmode';
15064 }
15065
15066 $class = 'btnTitle';
15067 if (in_array($iconClass, array('fa fa-plus-circle', 'fa fa-plus-circle size15x', 'fa fa-comment-dots', 'fa fa-paper-plane'))) {
15068 $class .= ' btnTitlePlus';
15069 }
15070 $useclassfortooltip = 1;
15071
15072 if (!empty($params['morecss'])) {
15073 $class .= ' ' . $params['morecss'];
15074 }
15075
15076 $attr = array(
15077 'class' => $class,
15078 'href' => empty($url) ? '' : $url
15079 );
15080
15081 if (!empty($helpText)) {
15082 $attr['title'] = $helpText;
15083 } elseif ($label) { // empty($attr['title']) &&
15084 $attr['title'] = $label;
15085 $useclassfortooltip = 0;
15086 }
15087
15088 if ($status == 2) {
15089 $attr['class'] .= ' btnTitleSelected';
15090 } elseif ($status <= 0) {
15091 $attr['class'] .= ' refused';
15092
15093 $attr['href'] = '';
15094
15095 if ($status == -1) { // disable
15096 $attr['title'] = $langs->transnoentitiesnoconv("FeatureDisabled");
15097 } elseif ($status == 0) { // Not enough permissions
15098 $attr['title'] = $langs->transnoentitiesnoconv("NotEnoughPermissions");
15099 }
15100 }
15101
15102 if (!empty($attr['title']) && $useclassfortooltip) {
15103 $attr['class'] .= ' classfortooltip';
15104 }
15105
15106 if (!empty($id)) {
15107 $attr['id'] = $id;
15108 }
15109
15110 // Override attr
15111 if (!empty($params['attr']) && is_array($params['attr'])) {
15112 foreach ($params['attr'] as $key => $value) {
15113 if ($key == 'class') {
15114 $attr['class'] .= ' ' . $value;
15115 } elseif ($key == 'classOverride') {
15116 $attr['class'] = $value;
15117 } else {
15118 $attr[$key] = $value;
15119 }
15120 }
15121 }
15122
15123 if (isset($attr['href']) && empty($attr['href'])) {
15124 unset($attr['href']);
15125 }
15126
15127 // TODO : add a hook
15128
15129 // Generate attributes with escapement
15130 $TCompiledAttr = array();
15131 foreach ($attr as $key => $value) {
15132 $TCompiledAttr[] = $key . '="' . dol_escape_htmltag($value) . '"'; // Do not use dolPrintHTMLForAttribute() here, we must accept "javascript:string"
15133 }
15134
15135 $compiledAttributes = (empty($TCompiledAttr) ? '' : implode(' ', $TCompiledAttr));
15136
15137 $tag = (empty($attr['href']) ? 'span' : 'a');
15138
15139 $button = '<' . $tag . ' ' . $compiledAttributes . '>';
15140 $button .= '<span class="' . $iconClass . ' valignmiddle btnTitle-icon"></span>';
15141 if (!empty($params['forcenohideoftext'])) {
15142 $button .= '<span class="valignmiddle text-plus-circle btnTitle-label' . (empty($params['forcenohideoftext']) ? ' hideonsmartphone' : '') . '">' . $label . '</span>';
15143 }
15144 $button .= '</' . $tag . '>';
15145
15146 return $button;
15147}
15148
15158function getElementProperties($elementType)
15159{
15160 global $conf, $db, $hookmanager;
15161
15162 $regs = array();
15163
15164 //$element_type='facture';
15165
15166 $classfile = $classname = $classpath = $subdir = $dir_output = $dir_temp = $parent_element = '';
15167
15168 // Parse element/subelement
15169 $module = $elementType;
15170 $element = $elementType;
15171 $subelement = $elementType;
15172 $table_element = $elementType;
15173
15174 // If we ask a resource form external module (instead of default path)
15175 if (preg_match('/^([^@]+)@([^@]+)$/i', $elementType, $regs)) { // 'myobject@mymodule'
15176 $element = $subelement = $regs[1];
15177 $module = $regs[2];
15178 }
15179
15180 // If we ask a resource for a string with an element and a subelement
15181 // Example 'project_task'
15182 if (preg_match('/^([^_]+)_([^_]+)/i', $element, $regs)) { // 'myobject_mysubobject' with myobject=mymodule
15183 $module = $element = $regs[1];
15184 $subelement = $regs[2];
15185 }
15186
15187 // Object lines will use parent classpath and module ref
15188 if (substr($elementType, -3) == 'det') {
15189 $module = preg_replace('/det$/', '', $element);
15190 $subelement = preg_replace('/det$/', '', $subelement);
15191 $classpath = $module . '/class';
15192 $classfile = $module;
15193 $classname = preg_replace('/det$/', 'Line', $element);
15194 if (in_array($module, array('expedition', 'propale', 'facture', 'contrat', 'fichinter', 'supplier_order', 'commandefournisseur'))) {
15195 $classname = preg_replace('/det$/', 'Ligne', $element);
15196 }
15197 }
15198 // For compatibility and to work with non standard path
15199 if ($elementType == "action" || $elementType == "actioncomm") {
15200 $classpath = 'comm/action/class';
15201 $subelement = 'Actioncomm';
15202 $module = 'agenda';
15203 $table_element = 'actioncomm';
15204 } elseif ($elementType == 'cronjob') {
15205 $classpath = 'cron/class';
15206 $module = 'cron';
15207 $table_element = 'cron';
15208 } elseif ($elementType == 'adherent_type') {
15209 $classpath = 'adherents/class';
15210 $classfile = 'adherent_type';
15211 $module = 'adherent';
15212 $subelement = 'adherent_type';
15213 $classname = 'AdherentType';
15214 $table_element = 'adherent_type';
15215 } elseif ($elementType == 'bank_account') {
15216 $classpath = 'compta/bank/class';
15217 $module = 'bank'; // We need $conf->bank->dir_output and not $conf->banque->dir_output
15218 $classfile = 'account';
15219 $classname = 'Account';
15220 } elseif ($elementType == 'bank_line') {
15221 $classpath = 'compta/bank/class';
15222 $module = 'bank'; // We need $conf->bank->dir_output and not $conf->banque->dir_output
15223 $classfile = 'account';
15224 $classname = 'AccountLine';
15225 } elseif ($elementType == 'category') {
15226 $classpath = 'categories/class';
15227 $module = 'categorie';
15228 $subelement = 'categorie';
15229 $table_element = 'categorie';
15230 } elseif ($elementType == 'contact') {
15231 $classpath = 'contact/class';
15232 $classfile = 'contact';
15233 $module = 'societe';
15234 $subelement = 'contact';
15235 $table_element = 'socpeople';
15236 } elseif ($elementType == 'inventory') {
15237 $module = 'product';
15238 $classpath = 'product/inventory/class';
15239 } elseif ($elementType == 'inventoryline') {
15240 $module = 'product';
15241 $classpath = 'product/inventory/class';
15242 $table_element = 'inventorydet';
15243 $parent_element = 'inventory';
15244 } elseif ($elementType == 'stock' || $elementType == 'entrepot' || $elementType == 'warehouse') {
15245 $module = 'stock';
15246 $classpath = 'product/stock/class';
15247 $classfile = 'entrepot';
15248 $classname = 'Entrepot';
15249 $table_element = 'entrepot';
15250 } elseif ($elementType == 'project') {
15251 $classpath = 'projet/class';
15252 $module = 'projet';
15253 $table_element = 'projet';
15254 } elseif ($elementType == 'project_task') {
15255 $classpath = 'projet/class';
15256 $module = 'projet';
15257 $subelement = 'task';
15258 $table_element = 'projet_task';
15259 } elseif ($elementType == 'mo') {
15260 $classpath = 'mrp/class';
15261 $module = 'mrp';
15262 $classfile = 'mo';
15263 $classname = 'Mo';
15264 $table_element = 'mrp_mo';
15265 } elseif ($elementType == 'facture' || $elementType == 'invoice') {
15266 $classpath = 'compta/facture/class';
15267 $module = 'facture';
15268 $subelement = 'facture';
15269 $table_element = 'facture';
15270 } elseif ($elementType == 'facturedet') {
15271 $classpath = 'compta/facture/class';
15272 $classfile = 'facture';
15273 $classname = 'FactureLigne';
15274 $module = 'facture';
15275 $table_element = 'facturedet';
15276 $parent_element = 'facture';
15277 } elseif ($elementType == 'facturerec' || $elementType == 'facture_rec') {
15278 $classpath = 'compta/facture/class';
15279 $classfile = 'facture-rec';
15280 $module = 'facture';
15281 $classname = 'FactureRec';
15282 } elseif ($elementType == 'commande' || $elementType == 'order') {
15283 $classpath = 'commande/class';
15284 $module = 'commande';
15285 $subelement = 'commande';
15286 $table_element = 'commande';
15287 } elseif ($elementType == 'commandedet') {
15288 $classpath = 'commande/class';
15289 $classfile = 'commande';
15290 $classname = 'OrderLine';
15291 $module = 'commande';
15292 $table_element = 'commandedet';
15293 $parent_element = 'commande';
15294 } elseif ($elementType == 'propal') {
15295 $classpath = 'comm/propal/class';
15296 $table_element = 'propal';
15297 } elseif ($elementType == 'propaldet') {
15298 $classpath = 'comm/propal/class';
15299 $classfile = 'propal';
15300 $subelement = 'propaleligne';
15301 $module = 'propal';
15302 $table_element = 'propaldet';
15303 $parent_element = 'propal';
15304 } elseif ($elementType == 'shipping') {
15305 $classpath = 'expedition/class';
15306 $classfile = 'expedition';
15307 $classname = 'Expedition';
15308 $module = 'expedition';
15309 $table_element = 'expedition';
15310 } elseif ($elementType == 'expeditiondet' || $elementType == 'shippingdet') {
15311 $classpath = 'expedition/class';
15312 $classfile = 'expedition';
15313 $classname = 'ExpeditionLigne';
15314 $module = 'expedition';
15315 $table_element = 'expeditiondet';
15316 $parent_element = 'expedition';
15317 } elseif ($elementType == 'delivery_note') {
15318 $classpath = 'delivery/class';
15319 $subelement = 'delivery';
15320 $module = 'expedition';
15321 } elseif ($elementType == 'delivery') {
15322 $classpath = 'delivery/class';
15323 $subelement = 'delivery';
15324 $module = 'expedition';
15325 } elseif ($elementType == 'deliverydet') {
15326 // @todo
15327 } elseif ($elementType == 'supplier_proposal') {
15328 $classpath = 'supplier_proposal/class';
15329 $module = 'supplier_proposal';
15330 $element = 'supplierproposal';
15331 $classfile = 'supplier_proposal';
15332 $subelement = 'supplierproposal';
15333 } elseif ($elementType == 'supplier_proposaldet') {
15334 $classpath = 'supplier_proposal/class';
15335 $module = 'supplier_proposal';
15336 $classfile = 'supplier_proposal';
15337 $classname = 'SupplierProposalLine';
15338 $table_element = 'supplier_proposaldet';
15339 $parent_element = 'supplier_proposal';
15340 } elseif ($elementType == 'contract') {
15341 $classpath = 'contrat/class';
15342 $module = 'contrat';
15343 $subelement = 'contrat';
15344 $table_element = 'contract';
15345 } elseif ($elementType == 'contratdet') {
15346 $classpath = 'contrat/class';
15347 $module = 'contrat';
15348 $table_element = 'contratdet';
15349 $parent_element = 'contrat';
15350 } elseif ($elementType == 'mailing') {
15351 $classpath = 'comm/mailing/class';
15352 $module = 'mailing';
15353 $classfile = 'mailing';
15354 $classname = 'Mailing';
15355 $subelement = '';
15356 } elseif ($elementType == 'member' || $elementType == 'adherent') {
15357 $classpath = 'adherents/class';
15358 $module = 'adherent';
15359 $subelement = 'adherent';
15360 $table_element = 'adherent';
15361 } elseif ($elementType == 'subscription') {
15362 $classpath = 'adherents/class';
15363 $classfile = 'subscription';
15364 $module = 'adherent';
15365 $subelement = 'subscription';
15366 $classname = 'Subscription';
15367 $table_element = 'subscription';
15368 } elseif ($elementType == 'usergroup') {
15369 $classpath = 'user/class';
15370 $module = 'user';
15371 } elseif ($elementType == 'mo' || $elementType == 'mrp') {
15372 $classpath = 'mrp/class';
15373 $classfile = 'mo';
15374 $classname = 'Mo';
15375 $module = 'mrp';
15376 $subelement = '';
15377 $table_element = 'mrp_mo';
15378 } elseif ($elementType == 'mrp_production') {
15379 $classpath = 'mrp/class';
15380 $classfile = 'mo';
15381 $classname = 'MoLine';
15382 $module = 'mrp';
15383 $subelement = '';
15384 $table_element = 'mrp_production';
15385 $parent_element = 'mo';
15386 } elseif ($elementType == 'cabinetmed_cons') {
15387 $classpath = 'cabinetmed/class';
15388 $module = 'cabinetmed';
15389 $subelement = 'cabinetmedcons';
15390 $table_element = 'cabinetmedcons';
15391 } elseif ($elementType == 'fichinter') {
15392 $classpath = 'fichinter/class';
15393 $module = 'ficheinter';
15394 $subelement = 'fichinter';
15395 $table_element = 'fichinter';
15396 } elseif ($elementType == 'dolresource' || $elementType == 'resource') {
15397 $classpath = 'resource/class';
15398 $module = 'resource';
15399 $subelement = 'dolresource';
15400 $table_element = 'resource';
15401 } elseif ($elementType == 'opensurvey_sondage') {
15402 $classpath = 'opensurvey/class';
15403 $module = 'opensurvey';
15404 $subelement = 'opensurveysondage';
15405 } elseif ($elementType == 'order_supplier' || $elementType == 'supplier_order' || $elementType == 'commande_fournisseur' || $elementType == 'commandefournisseur') {
15406 $classpath = 'fourn/class';
15407 $module = 'fournisseur';
15408 $classfile = 'fournisseur.commande';
15409 $element = 'order_supplier';
15410 $subelement = '';
15411 $classname = 'CommandeFournisseur';
15412 $table_element = 'commande_fournisseur';
15413 } elseif ($elementType == 'commande_fournisseurdet') {
15414 $classpath = 'fourn/class';
15415 $module = 'fournisseur';
15416 $classfile = 'fournisseur.commande';
15417 $element = 'commande_fournisseurdet';
15418 $subelement = '';
15419 $classname = 'CommandeFournisseurLigne';
15420 $table_element = 'commande_fournisseurdet';
15421 $parent_element = 'commande_fournisseur';
15422 } elseif ($elementType == 'invoice_supplier' || $elementType == 'supplier_invoice' || $elementType == 'facture_fourn') {
15423 $classpath = 'fourn/class';
15424 $module = 'fournisseur';
15425 $classfile = 'fournisseur.facture';
15426 $element = 'invoice_supplier';
15427 $subelement = '';
15428 $classname = 'FactureFournisseur';
15429 $table_element = 'facture_fourn';
15430 } elseif ($elementType == 'facture_fourn_det') {
15431 $classpath = 'fourn/class';
15432 $module = 'fournisseur';
15433 $classfile = 'fournisseur.facture';
15434 $element = 'facture_fourn_det';
15435 $subelement = '';
15436 $classname = 'SupplierInvoiceLine';
15437 $table_element = 'facture_fourn_det';
15438 $parent_element = 'invoice_supplier';
15439 } elseif ($elementType == "service") {
15440 $classpath = 'product/class';
15441 $subelement = 'product';
15442 $table_element = 'product';
15443 } elseif ($elementType == 'salary') {
15444 $classpath = 'salaries/class';
15445 $module = 'salaries';
15446 } elseif ($elementType == 'payment_salary') {
15447 $classpath = 'salaries/class';
15448 $classfile = 'paymentsalary';
15449 $classname = 'PaymentSalary';
15450 $module = 'salaries';
15451 } elseif ($elementType == 'productlot') {
15452 $module = 'productbatch';
15453 $classpath = 'product/stock/class';
15454 $classfile = 'productlot';
15455 $classname = 'Productlot';
15456 $element = 'productlot';
15457 $subelement = '';
15458 $table_element = 'product_lot';
15459 } elseif ($elementType == 'societeaccount') {
15460 $classpath = 'societe/class';
15461 $classfile = 'societeaccount';
15462 $classname = 'SocieteAccount';
15463 $module = 'societe';
15464 } elseif ($elementType == 'websitepage' || $elementType == 'website_page') {
15465 $classpath = 'website/class';
15466 $classfile = 'websitepage';
15467 $classname = 'Websitepage';
15468 $module = 'website';
15469 $subelement = 'websitepage';
15470 $table_element = 'website_page';
15471 } elseif ($elementType == 'fiscalyear') {
15472 $classpath = 'core/class';
15473 $module = 'accounting';
15474 $subelement = 'fiscalyear';
15475 } elseif ($elementType == 'chargesociales') {
15476 $classpath = 'compta/sociales/class';
15477 $module = 'tax';
15478 $table_element = 'chargesociales';
15479 } elseif ($elementType == 'tva') {
15480 $classpath = 'compta/tva/class';
15481 $module = 'tax';
15482 $subdir = '/vat';
15483 $table_element = 'tva';
15484 } elseif ($elementType == 'emailsenderprofile') {
15485 $module = '';
15486 $classpath = 'core/class';
15487 $classfile = 'emailsenderprofile';
15488 $classname = 'EmailSenderProfile';
15489 $table_element = 'c_email_senderprofile';
15490 $subelement = '';
15491 } elseif ($elementType == 'conferenceorboothattendee') {
15492 $classpath = 'eventorganization/class';
15493 $classfile = 'conferenceorboothattendee';
15494 $classname = 'ConferenceOrBoothAttendee';
15495 $module = 'eventorganization';
15496 } elseif ($elementType == 'conferenceorbooth') {
15497 $classpath = 'eventorganization/class';
15498 $classfile = 'conferenceorbooth';
15499 $classname = 'ConferenceOrBooth';
15500 $module = 'eventorganization';
15501 } elseif ($elementType == 'ccountry') {
15502 $module = '';
15503 $classpath = 'core/class';
15504 $classfile = 'ccountry';
15505 $classname = 'Ccountry';
15506 $table_element = 'c_country';
15507 $subelement = '';
15508 } elseif ($elementType == 'ecmfiles') {
15509 $module = 'ecm';
15510 $classpath = 'ecm/class';
15511 $classfile = 'ecmfiles';
15512 $classname = 'Ecmfiles';
15513 $table_element = 'ecmfiles';
15514 $subelement = '';
15515 } elseif ($elementType == 'knowledgerecord' || $elementType == 'knowledgemanagement') {
15516 $module = 'knowledgemanagement';
15517 $classpath = 'knowledgemanagement/class';
15518 $classfile = 'knowledgerecord';
15519 $classname = 'KnowledgeRecord';
15520 $table_element = 'knowledgemanagement_knowledgerecord';
15521 $subelement = '';
15522 } elseif ($elementType == 'customer') {
15523 $module = 'societe';
15524 $classpath = 'societe/class';
15525 $classfile = 'client';
15526 $classname = 'Client';
15527 $table_element = 'societe';
15528 $subelement = '';
15529 } elseif ($elementType == 'fournisseur' || $elementType == 'supplier') {
15530 $module = 'societe';
15531 $classpath = 'fourn/class';
15532 $classfile = 'fournisseur';
15533 $classname = 'Fournisseur';
15534 $table_element = 'societe';
15535 $subelement = '';
15536 } elseif ($elementType == 'recruitmentcandidature') {
15537 $module = 'recruitment';
15538 $classfile = 'recruitmentcandidature';
15539 $classpath = 'recruitment/class';
15540 $classname = 'RecruitmentCandidature';
15541 $subelement = 'recruitmentcandidature';
15542 $subdir = '/recruitmentcandidature';
15543 } elseif ($elementType == 'recruitmentjobposition') {
15544 $module = 'recruitment';
15545 $classfile = 'recruitmentjobposition';
15546 $classpath = 'recruitment/class';
15547 $classname = 'RecruitmentJobPosition';
15548 $subelement = 'recruitmentjobposition';
15549 $subdir = '/recruitmentjobposition';
15550 }
15551
15552
15553 if (empty($classfile)) {
15554 $classfile = strtolower($subelement);
15555 }
15556 if (empty($classname)) {
15557 $classname = ucfirst($subelement);
15558 }
15559 if (empty($classpath)) {
15560 $classpath = $module . '/class';
15561 }
15562
15563 //print 'getElementProperties subdir='.$subdir;
15564
15565 // Set dir_output
15566 if ($module && isset($conf->$module)) { // The generic case
15567 if (!empty($conf->$module->multidir_output[$conf->entity])) {
15568 $dir_output = $conf->$module->multidir_output[$conf->entity];
15569 } elseif (!empty($conf->$module->output[$conf->entity])) {
15570 $dir_output = $conf->$module->output[$conf->entity];
15571 } elseif (!empty($conf->$module->dir_output)) {
15572 $dir_output = $conf->$module->dir_output;
15573 }
15574 if (!empty($conf->$module->multidir_temp[$conf->entity])) {
15575 $dir_temp = $conf->$module->multidir_temp[$conf->entity];
15576 } elseif (!empty($conf->$module->temp[$conf->entity])) {
15577 $dir_temp = $conf->$module->temp[$conf->entity];
15578 } elseif (!empty($conf->$module->dir_temp)) {
15579 $dir_temp = $conf->$module->dir_temp;
15580 }
15581 }
15582
15583 // Overwrite value for special cases
15584 if ($element == 'order_supplier' && isModEnabled('fournisseur')) {
15585 $dir_output = $conf->fournisseur->commande->dir_output;
15586 $dir_temp = $conf->fournisseur->commande->dir_temp;
15587 } elseif ($element == 'invoice_supplier' && isModEnabled('fournisseur')) {
15588 $dir_output = $conf->fournisseur->facture->dir_output;
15589 $dir_temp = $conf->fournisseur->facture->dir_temp;
15590 }
15591 $dir_output .= $subdir;
15592 $dir_temp .= $subdir;
15593
15594 $elementProperties = array(
15595 'module' => $module,
15596 'element' => $element,
15597 'table_element' => $table_element,
15598 'subelement' => $subelement,
15599 'classpath' => $classpath,
15600 'classfile' => $classfile,
15601 'classname' => $classname,
15602 'dir_output' => $dir_output,
15603 'dir_temp' => $dir_temp,
15604 'parent_element' => $parent_element,
15605 );
15606
15607
15608 // Add hook
15609 if (!is_object($hookmanager)) {
15610 include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
15611 $hookmanager = new HookManager($db);
15612 }
15613 $hookmanager->initHooks(array('elementproperties'));
15614
15615
15616 // Hook params
15617 $parameters = array(
15618 'elementType' => $elementType,
15619 'elementProperties' => $elementProperties
15620 );
15621
15622 $reshook = $hookmanager->executeHooks('getElementProperties', $parameters);
15623
15624 if ($reshook) {
15625 $elementProperties = $hookmanager->resArray;
15626 } elseif (!empty($hookmanager->resArray) && is_array($hookmanager->resArray)) { // resArray is always an array but for sécurity against misconfigured external modules
15627 $elementProperties = array_replace($elementProperties, $hookmanager->resArray);
15628 }
15629
15630 // context of elementproperties doesn't need to exist out of this function so delete it to avoid elementproperties context is equal to all
15631 if (($key = array_search('elementproperties', $hookmanager->contextarray)) !== false) {
15632 unset($hookmanager->contextarray[$key]);
15633 }
15634
15635 return $elementProperties;
15636}
15637
15650function fetchObjectByElement($element_id, $element_type, $element_ref = '', $useCache = 0, $maxCacheByType = 10)
15651{
15652 global $db, $conf;
15653
15654 $ret = 0;
15655
15656 $element_prop = getElementProperties($element_type);
15657
15658 if ($element_prop['module'] == 'product' || $element_prop['module'] == 'service') {
15659 // For example, for an extrafield 'product' (shared for both product and service) that is a link to an object,
15660 // this is called with $element_type = 'product' when we need element properties of a service, we must return a product. If we create the
15661 // extrafield for a service, it is not supported and not found when editing the product/service card. So we must keep 'product' for extrafields
15662 // of service and we will return properties of a product.
15663 $ismodenabled = (isModEnabled('product') || isModEnabled('service'));
15664 } elseif ($element_prop['module'] == 'societeaccount') {
15665 $ismodenabled = isModEnabled('website') || isModEnabled('webportal');
15666 } else {
15667 $ismodenabled = isModEnabled($element_prop['module']);
15668 }
15669 //var_dump('element_type='.$element_type);
15670 //var_dump($element_prop);
15671 //var_dump($element_prop['module'].' '.$ismodenabled);
15672 if (is_array($element_prop) && (empty($element_prop['module']) || $ismodenabled)) {
15673 if ($useCache === 1 && $element_id > 0
15674 && !empty($conf->cache['fetchObjectByElement'][$element_type])
15675 && !empty($conf->cache['fetchObjectByElement'][$element_type][$element_id])
15676 && is_object($conf->cache['fetchObjectByElement'][$element_type][$element_id])
15677 ) {
15678 return $conf->cache['fetchObjectByElement'][$element_type][$element_id];
15679 }
15680
15681 dol_include_once('/' . $element_prop['classpath'] . '/' . $element_prop['classfile'] . '.class.php');
15682
15683 if (class_exists($element_prop['classname'])) {
15684 $className = $element_prop['classname'];
15685 $objecttmp = new $className($db);
15686 '@phan-var-force CommonObject $objecttmp';
15689 if ($element_id > 0 || !empty($element_ref)) {
15690 $ret = $objecttmp->fetch($element_id, $element_ref);
15691 if ($ret >= 0) {
15692 if (empty($objecttmp->module)) {
15693 $objecttmp->module = $element_prop['module'];
15694 }
15695
15696 if ($useCache > 0) {
15697 if (!isset($conf->cache['fetchObjectByElement'][$element_type])) {
15698 $conf->cache['fetchObjectByElement'][$element_type] = [];
15699 }
15700
15701 // Manage cache limit
15702 if (! empty($conf->cache['fetchObjectByElement'][$element_type]) && is_array($conf->cache['fetchObjectByElement'][$element_type]) && count($conf->cache['fetchObjectByElement'][$element_type]) >= $maxCacheByType) {
15703 array_shift($conf->cache['fetchObjectByElement'][$element_type]);
15704 }
15705
15706 $conf->cache['fetchObjectByElement'][$element_type][$element_id] = $objecttmp;
15707 }
15708
15709 return $objecttmp;
15710 }
15711 } else {
15712 return $objecttmp; // returned an object without fetch
15713 }
15714 } else {
15715 dol_syslog($element_prop['classname'] . ' doesn\'t exists in /' . $element_prop['classpath'] . '/' . $element_prop['classfile'] . '.class.php');
15716 return -1;
15717 }
15718 }
15719
15720 return $ret;
15721}
15722
15728function getExecutableContent()
15729{
15730 $arrayofregexextension = array(
15731 'htm',
15732 'html',
15733 'shtml',
15734 'js',
15735 'phar',
15736 'php',
15737 'php3',
15738 'php4',
15739 'php5',
15740 'phtml',
15741 'pht',
15742 'pl',
15743 'py',
15744 'cgi',
15745 'ksh',
15746 'sh',
15747 'shtml',
15748 'bash',
15749 'bat',
15750 'cmd',
15751 'wpk',
15752 'exe',
15753 'dmg',
15754 'appimage'
15755 );
15756
15757 return $arrayofregexextension;
15758}
15759
15766function isAFileWithExecutableContent($filename)
15767{
15768 $arrayofregexextension = getExecutableContent();
15769
15770 foreach ($arrayofregexextension as $fileextension) {
15771 if (preg_match('/\.' . preg_quote($fileextension, '/') . '$/i', $filename)) {
15772 return true;
15773 }
15774 }
15775
15776 return false;
15777}
15778
15786function newToken()
15787{
15788 return empty($_SESSION['newtoken']) ? '' : $_SESSION['newtoken'];
15789}
15790
15798function currentToken()
15799{
15800 return isset($_SESSION['token']) ? $_SESSION['token'] : '';
15801}
15802
15808function getNonce()
15809{
15810 global $conf;
15811
15812 if (empty($conf->cache['nonce'])) {
15813 include_once DOL_DOCUMENT_ROOT . '/core/lib/security.lib.php';
15814 $conf->cache['nonce'] = dolGetRandomBytes(8);
15815 }
15816
15817 return $conf->cache['nonce'];
15818}
15819
15820
15834function startSimpleTable($header, $link = "", $arguments = "", $emptyColumns = 0, $number = -1, $pictofulllist = '')
15835{
15836 global $langs;
15837
15838 print '<div class="div-table-responsive-no-min">';
15839 print '<table class="noborder centpercent">';
15840 print '<tr class="liste_titre">';
15841
15842 print ($emptyColumns < 1) ? '<th>' : '<th colspan="' . ($emptyColumns + 1) . '">';
15843
15844 print '<span class="valignmiddle">' . $langs->trans($header) . '</span>';
15845
15846 if (!empty($link)) {
15847 if (!empty($arguments)) {
15848 print '<a href="' . DOL_URL_ROOT . '/' . $link . '?' . $arguments . '">';
15849 } else {
15850 print '<a href="' . DOL_URL_ROOT . '/' . $link . '">';
15851 }
15852 }
15853
15854 if ($number > -1) {
15855 print '<span class="badge marginleftonlyshort">' . $number . '</span>';
15856 } elseif (!empty($link)) {
15857 print '<span class="badge marginleftonlyshort">...</span>';
15858 }
15859
15860 if (!empty($link)) {
15861 print '</a>';
15862 }
15863
15864 print '</th>';
15865
15866 if ($number < 0 && !empty($link)) {
15867 print '<th class="right">';
15868 print '</th>';
15869 }
15870
15871 print '</tr>';
15872}
15873
15882function finishSimpleTable($addLineBreak = false)
15883{
15884 print '</table>';
15885 print '</div>';
15886
15887 if ($addLineBreak) {
15888 print '<br>';
15889 }
15890}
15891
15903function addSummaryTableLine($tableColumnCount, $num, $nbofloop = 0, $total = 0, $noneWord = "None", $extraRightColumn = false)
15904{
15905 global $langs;
15906
15907 if ($num === 0) {
15908 print '<tr class="oddeven">';
15909 print '<td colspan="' . $tableColumnCount . '"><span class="opacitymedium">' . $langs->trans($noneWord) . '</span></td>';
15910 print '</tr>';
15911 return;
15912 }
15913
15914 if ($nbofloop === 0) {
15915 // don't show a summary line
15916 return;
15917 }
15918
15919 /* Case already handled above, commented to satisfy phpstan.
15920 if ($num === 0) {
15921 $colspan = $tableColumnCount;
15922 } else
15923 */
15924 if ($num > $nbofloop) {
15925 $colspan = $tableColumnCount;
15926 } else {
15927 $colspan = $tableColumnCount - 1;
15928 }
15929
15930 if ($extraRightColumn) {
15931 $colspan--;
15932 }
15933
15934 print '<tr class="liste_total">';
15935
15936 if ($nbofloop > 0 && $num > $nbofloop) {
15937 print '<td colspan="' . $colspan . '" class="right">' . $langs->trans("XMoreLines", ($num - $nbofloop)) . '</td>';
15938 } else {
15939 print '<td colspan="' . $colspan . '" class="right"> ' . $langs->trans("Total") . '</td>';
15940 print '<td class="right centpercent">' . price($total) . '</td>';
15941 }
15942
15943 if ($extraRightColumn) {
15944 print '<td></td>';
15945 }
15946
15947 print '</tr>';
15948}
15949
15958function readfileLowMemory($fullpath_original_file_osencoded, $method = -1)
15959{
15960 if ($method == -1) {
15961 $method = 0;
15962 if (getDolGlobalString('MAIN_FORCE_READFILE_WITH_FREAD')) {
15963 $method = 1;
15964 }
15965 if (getDolGlobalString('MAIN_FORCE_READFILE_WITH_STREAM_COPY')) {
15966 $method = 2;
15967 }
15968 }
15969
15970 // Be sure we don't have output buffering enabled to have readfile working correctly
15971 $level = ob_get_level();
15972 ob_start();
15973 while (ob_get_level() > $level) {
15974 ob_end_clean();
15975 }
15976
15977 // Solution 0
15978 if ($method == 0) {
15979 readfile($fullpath_original_file_osencoded);
15980 } elseif ($method == 1) {
15981 // Solution 1
15982 $handle = fopen($fullpath_original_file_osencoded, "rb");
15983 while (!feof($handle)) {
15984 print fread($handle, 8192);
15985 }
15986 fclose($handle);
15987 } elseif ($method == 2) {
15988 // Solution 2
15989 $handle1 = fopen($fullpath_original_file_osencoded, "rb");
15990 $handle2 = fopen("php://output", "wb");
15991 stream_copy_to_stream($handle1, $handle2);
15992 fclose($handle1);
15993 fclose($handle2);
15994 }
15995}
15996
16006function showValueWithClipboardCPButton($valuetocopy, $showonlyonhover = 1, $texttoshow = '')
16007{
16008 global $langs;
16009
16010 $tag = 'span'; // Using div (like any style of type 'block') does not work when using the js copy code.
16011
16012 $result = '<span class="clipboardCP' . ($showonlyonhover ? ' clipboardCPShowOnHover valignmiddle' : '') . '">';
16013 if ($texttoshow === 'none') {
16014 $result .= '<' . $tag . ' class="clipboardCPValue hidewithsize">' . dol_escape_htmltag($valuetocopy, 1, 1) . '</' . $tag . '>';
16015 $result .= '<span class="clipboardCPValueToPrint"></span>';
16016 } elseif ($texttoshow) {
16017 $result .= '<' . $tag . ' class="clipboardCPValue hidewithsize">' . dol_escape_htmltag($valuetocopy, 1, 1) . '</' . $tag . '>';
16018 $result .= '<span class="clipboardCPValueToPrint">' . dol_escape_htmltag($texttoshow, 1, 1) . '</span>';
16019 } else {
16020 $result .= '<' . $tag . ' class="clipboardCPValue">' . dol_escape_htmltag($valuetocopy, 1, 1) . '</' . $tag . '>';
16021 }
16022 $result .= '<span class="clipboardCPButton far fa-clipboard opacitymedium paddingleft pictomodule" title="' . dolPrintHTML($langs->trans("ClickToCopyToClipboard")) . '"></span>';
16023 $result .= img_picto('', 'tick', 'class="clipboardCPTick hidden paddingleft pictomodule"');
16024 $result .= '<span class="clipboardCPText"></span>';
16025 $result .= '</span>';
16026
16027 return $result;
16028}
16029
16030
16038function jsonOrUnserialize($stringtodecode, $assoc = true)
16039{
16040 $result = json_decode($stringtodecode, $assoc);
16041 if ($result === null) {
16042 $result = unserialize($stringtodecode, ['allowed_classes' => false]); // For backward compatibility. Is no more used in recent versions.
16043 }
16044
16045 return $result;
16046}
16047
16048
16065function forgeSQLFromUniversalSearchCriteria($filter, &$errorstr = '', $noand = 0, $nopar = 0, $noerror = 0)
16066{
16067 global $db, $user;
16068
16069 if (is_null($filter) || !is_string($filter) || $filter === '') {
16070 return '';
16071 }
16072 if (!preg_match('/^\‍(.*\‍)$/', $filter)) { // If $filter does not start and end with ()
16073 $filter = '(' . $filter . ')';
16074 }
16075
16076 $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'
16077 $firstandlastparenthesis = 0;
16078
16079 if (!dolCheckFilters($filter, $errorstr, $firstandlastparenthesis)) {
16080 if ($noerror) {
16081 return '1 = 2';
16082 } else {
16083 return 'Filter syntax error - ' . $errorstr; // Bad balance of parenthesis, we return an error message or force a SQL not found
16084 }
16085 }
16086
16087 // Test the filter syntax
16088 $t = preg_replace_callback('/' . $regexstring . '/i', 'dolForgeDummyCriteriaCallback', $filter);
16089 $t = str_ireplace(array('and', 'or', ' '), '', $t); // Remove the only strings allowed between each () criteria
16090 // If the string result contains something else than '()', the syntax was wrong
16091
16092 if (preg_match('/[^\‍(\‍)]/', $t)) {
16093 $tmperrorstr = 'Bad syntax of the search string';
16094 $errorstr = 'Bad syntax of the search string: ' . $filter;
16095 if ($noerror) {
16096 return '1 = 2';
16097 } else {
16098 dol_syslog("forgeSQLFromUniversalSearchCriteria Filter error - " . $errorstr, LOG_WARNING);
16099 return 'Filter error - ' . $tmperrorstr; // Bad syntax of the search string, we return an error message or force a SQL not found
16100 }
16101 }
16102
16103 $ret = ($noand ? "" : " AND ") . ($nopar ? "" : '(') . preg_replace_callback('/' . $regexstring . '/i', 'dolForgeSQLCriteriaCallback', $filter) . ($nopar ? "" : ')');
16104
16105 if (is_object($db)) {
16106 $ret = str_replace('__NOW__', "'" . $db->idate(dol_now()) . "'", $ret);
16107 }
16108 if (is_object($user)) {
16109 $ret = str_replace('__USER_ID__', (string) $user->id, $ret);
16110 }
16111
16112 return $ret;
16113}
16114
16122function dolForgeExplodeAnd($sqlfilters)
16123{
16124 $arrayofandtags = array();
16125 $nbofchars = dol_strlen($sqlfilters);
16126
16127 $error = '';
16128 $parenthesislevel = 0;
16129 $result = dolCheckFilters($sqlfilters, $error, $parenthesislevel);
16130 if (!$result) {
16131 return array();
16132 }
16133 if ($parenthesislevel >= 1) {
16134 $sqlfilters = preg_replace('/^\‍(/', '', preg_replace('/\‍)$/', '', $sqlfilters));
16135 }
16136
16137 $i = 0;
16138 $s = '';
16139 $countparenthesis = 0;
16140 while ($i < $nbofchars) {
16141 $char = dol_substr($sqlfilters, $i, 1);
16142
16143 if ($char == '(') {
16144 $countparenthesis++;
16145 } elseif ($char == ')') {
16146 $countparenthesis--;
16147 }
16148
16149 if ($countparenthesis == 0) {
16150 $char2 = dol_substr($sqlfilters, $i + 1, 1);
16151 $char3 = dol_substr($sqlfilters, $i + 2, 1);
16152 if ($char == 'A' && $char2 == 'N' && $char3 == 'D') {
16153 // We found a AND
16154 $s = trim($s);
16155 if (!preg_match('/^\‍(.*\‍)$/', $s)) {
16156 $s = '(' . $s . ')';
16157 }
16158 $arrayofandtags[] = $s;
16159 $s = '';
16160 $i += 2;
16161 } else {
16162 $s .= $char;
16163 }
16164 } else {
16165 $s .= $char;
16166 }
16167 $i++;
16168 }
16169 if ($s) {
16170 $s = trim($s);
16171 if (!preg_match('/^\‍(.*\‍)$/', $s)) {
16172 $s = '(' . $s . ')';
16173 }
16174 $arrayofandtags[] = $s;
16175 }
16176
16177 return $arrayofandtags;
16178}
16179
16189function dolCheckFilters($sqlfilters, &$error = '', &$parenthesislevel = 0)
16190{
16191 //$regexstring='\‍(([^:\'\‍(\‍)]+:[^:\'\‍(\‍)]+:[^:\‍(\‍)]+)\‍)';
16192 //$tmp=preg_replace_all('/'.$regexstring.'/', '', $sqlfilters);
16193 $tmp = $sqlfilters;
16194
16195 $nb = dol_strlen($tmp);
16196 $counter = 0;
16197 $parenthesislevel = 0;
16198
16199 $error = '';
16200
16201 $i = 0;
16202 while ($i < $nb) {
16203 $char = dol_substr($tmp, $i, 1);
16204
16205 if ($char == '(') {
16206 if ($i == $parenthesislevel && $parenthesislevel == $counter) {
16207 // We open a parenthesis and it is the first char
16208 $parenthesislevel++;
16209 }
16210 $counter++;
16211 } elseif ($char == ')') {
16212 $nbcharremaining = ($nb - $i - 1);
16213 if ($nbcharremaining >= $counter) {
16214 $parenthesislevel = min($parenthesislevel, $counter - 1);
16215 }
16216 if ($parenthesislevel > $counter && $nbcharremaining >= $counter) {
16217 $parenthesislevel = $counter;
16218 }
16219 $counter--;
16220 }
16221
16222 if ($counter < 0) {
16223 $error = "Wrong balance of parenthesis in sqlfilters=" . $sqlfilters;
16224 $parenthesislevel = 0;
16225 dol_syslog($error, LOG_WARNING);
16226 return false;
16227 }
16228
16229 $i++;
16230 }
16231
16232 if ($counter > 0) {
16233 $error = "Wrong balance of parenthesis in sqlfilters=" . $sqlfilters;
16234 $parenthesislevel = 0;
16235 dol_syslog($error, LOG_WARNING);
16236 return false;
16237 }
16238
16239 return true;
16240}
16241
16249function dolForgeDummyCriteriaCallback($matches)
16250{
16251 //dol_syslog("Convert matches ".$matches[1]);
16252 if (empty($matches[1])) {
16253 return '';
16254 }
16255 $tmp = explode(':', $matches[1]);
16256 if (count($tmp) < 3) {
16257 return '';
16258 }
16259
16260 return '()'; // An empty criteria
16261}
16262
16272function dolForgeSQLCriteriaCallback($matches)
16273{
16274 global $db;
16275
16276 //dol_syslog("Convert matches ".$matches[1]);
16277 if (empty($matches[1])) {
16278 return '';
16279 }
16280 $tmp = explode(':', $matches[1], 3);
16281 if (count($tmp) < 3) {
16282 return '';
16283 }
16284
16285 $operand = preg_replace('/[^a-z0-9\._]/i', '', trim($tmp[0]));
16286
16287 $operator = strtoupper(preg_replace('/[^a-z<>!=]/i', '', trim($tmp[1])));
16288
16289 $realOperator = [
16290 'NOTLIKE' => 'NOT LIKE',
16291 'ISNOT' => 'IS NOT',
16292 'NOTIN' => 'NOT IN',
16293 '!=' => '<>',
16294 ];
16295
16296 if (array_key_exists($operator, $realOperator)) {
16297 $operator = $realOperator[$operator];
16298 }
16299
16300 $tmpescaped = $tmp[2];
16301
16302 //print "Case: ".$operator." ".$operand." ".$tmpescaped."\n";
16303
16304 $regbis = array();
16305
16306 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)
16307 global $dolibarr_allow_unsecured_select_in_extrafields_filter;
16308
16309 //if (!preg_match('/^\‍(.*\‍)$/', $tmpescaped)) {
16310 $tmpescaped2 = '(';
16311 // Explode and sanitize each element in list
16312 $tmpelemarray = explode(',', $tmpescaped);
16313 foreach ($tmpelemarray as $tmpkey => $tmpelem) {
16314 $reg = array();
16315 $tmpelem = trim($tmpelem);
16316 if (preg_match('/^\'(.*)\'$/', $tmpelem, $reg)) {
16317 $tmpelemarray[$tmpkey] = "'" . $db->escape($db->sanitize($reg[1], 2, 1, 1, 1)) . "'";
16318 } elseif (ctype_digit((string) $tmpelem)) { // if only 0-9 chars, no .
16319 $tmpelemarray[$tmpkey] = (int) $tmpelem;
16320 } elseif (is_numeric((string) $tmpelem)) { // it can be a float with a .
16321 $tmpelemarray[$tmpkey] = (float) $tmpelem;
16322 } elseif (!empty($dolibarr_allow_unsecured_select_in_extrafields_filter)) {
16323 $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)
16324 } else {
16325 $tmpelemarray[$tmpkey] = preg_replace('/[^a-z0-9_]/i', '', $tmpelem); // it can be a name of field or a substitution variable like '__NOW__'
16326 }
16327 }
16328 $tmpescaped2 .= implode(',', $tmpelemarray);
16329 $tmpescaped2 .= ')';
16330
16331 $tmpescaped = $tmpescaped2;
16332 } elseif ($operator == 'LIKE' || $operator == 'NOT LIKE') {
16333 if (preg_match('/^\'([^\']*)\'$/', $tmpescaped, $regbis)) {
16334 $tmpescaped = $regbis[1];
16335 }
16336 //$tmpescaped = "'".$db->escape($db->escapeforlike($regbis[1]))."'";
16337 $tmpescaped = "'" . $db->escape($tmpescaped) . "'"; // We do not escape the _ and % so the LIKE will work as expected
16338 } elseif (preg_match('/^\'(.*)\'$/', $tmpescaped, $regbis)) {
16339 // TODO Retrieve type of field for $operand field name.
16340 // So we can complete format. For example we could complete a year with month and day.
16341 $tmpescaped = "'" . $db->escape($regbis[1]) . "'";
16342 } else {
16343 if (strtoupper($tmpescaped) == 'NULL') {
16344 $tmpescaped = 'NULL';
16345 } elseif (ctype_digit((string) $tmpescaped)) { // if only 0-9 chars, no .
16346 $tmpescaped = (int) $tmpescaped;
16347 } elseif (is_numeric((string) $tmpescaped)) { // it can be a float with a .
16348 $tmpescaped = (float) $tmpescaped;
16349 } else {
16350 $tmpescaped = preg_replace('/[^a-z0-9_]/i', '', $tmpescaped); // it can be a name of field or a substitution variable like '__NOW__'
16351 }
16352 }
16353
16354 return '(' . $db->escape($operand) . ' ' . strtoupper($operator) . ' ' . $tmpescaped . ')';
16355}
16356
16357
16367function getTimelineIcon($actionstatic, &$histo, $key)
16368{
16369 dol_syslog('getTimelineIcon::begin', LOG_DEBUG);
16370 global $langs;
16371
16372 $out = '<!-- timeline icon -->' . "\n";
16373 $iconClass = 'fa fa-comments';
16374 $img_picto = '';
16375 $colorClass = '';
16376 $pictoTitle = '';
16377
16378 if ($histo[$key]['percent'] == -1) {
16379 $colorClass = 'timeline-icon-not-applicble';
16380 $pictoTitle = $langs->trans('StatusNotApplicable');
16381 } elseif ($histo[$key]['percent'] == 0) {
16382 $colorClass = 'timeline-icon-todo';
16383 $pictoTitle = $langs->trans('StatusActionToDo') . ' (0%)';
16384 } elseif ($histo[$key]['percent'] > 0 && $histo[$key]['percent'] < 100) {
16385 $colorClass = 'timeline-icon-in-progress';
16386 $pictoTitle = $langs->trans('StatusActionInProcess') . ' (' . $histo[$key]['percent'] . '%)';
16387 } elseif ($histo[$key]['percent'] >= 100) {
16388 $colorClass = 'timeline-icon-done';
16389 $pictoTitle = $langs->trans('StatusActionDone') . ' (100%)';
16390 }
16391
16392 if ($actionstatic->code == 'AC_TICKET_CREATE') {
16393 $iconClass = 'fa fa-ticket';
16394 } elseif ($actionstatic->code == 'AC_TICKET_MODIFY') {
16395 $iconClass = 'fa fa-pencilxxx';
16396 } elseif (preg_match('/^TICKET_MSG/', $actionstatic->code)) {
16397 $iconClass = 'fa fa-comments';
16398 } elseif (preg_match('/^TICKET_MSG_PRIVATE/', $actionstatic->code)) {
16399 $iconClass = 'fa fa-mask';
16400 } elseif (getDolGlobalString('AGENDA_USE_EVENT_TYPE')) {
16401 if ($actionstatic->type_picto) {
16402 $img_picto = img_picto('', $actionstatic->type_picto);
16403 } else {
16404 if ($actionstatic->type_code == 'AC_RDV') {
16405 $iconClass = 'fa fa-handshake';
16406 } elseif ($actionstatic->type_code == 'AC_TEL') {
16407 $iconClass = 'fa fa-phone';
16408 } elseif ($actionstatic->type_code == 'AC_FAX') {
16409 $iconClass = 'fa fa-fax';
16410 } elseif ($actionstatic->type_code == 'AC_EMAIL') {
16411 $iconClass = 'fa fa-envelope';
16412 } elseif ($actionstatic->type_code == 'AC_INT') {
16413 $iconClass = 'fa fa-shipping-fast';
16414 } elseif ($actionstatic->type_code == 'AC_OTH_AUTO') {
16415 $iconClass = 'fa fa-robot';
16416 } elseif (!preg_match('/_AUTO/', $actionstatic->type_code)) {
16417 $iconClass = 'fa fa-robot';
16418 }
16419 }
16420 }
16421
16422 $out .= '<i class="' . $iconClass . ' ' . $colorClass . '" title="' . $pictoTitle . '">' . $img_picto . '</i>' . "\n";
16423 return $out;
16424}
16425
16432function getActionCommEcmList($object)
16433{
16434 global $db;
16435
16436 $documents = array();
16437
16438 $sql = 'SELECT ecm.rowid as id, ecm.src_object_type, ecm.src_object_id, ecm.filepath, ecm.filename, ecm.agenda_id';
16439 $sql .= ' FROM ' . MAIN_DB_PREFIX . 'ecm_files ecm';
16440 $sql .= " WHERE ecm.filepath = 'agenda/" . ((int) $object->id) . "'";
16441 //$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
16442 $sql .= ' OR ecm.agenda_id = ' . (int) $object->id;
16443 $sql .= ' ORDER BY ecm.position ASC';
16444
16445 $resql = $db->query($sql);
16446 if ($resql) {
16447 if ($db->num_rows($resql)) {
16448 while ($obj = $db->fetch_object($resql)) {
16449 $documents[$obj->id] = $obj;
16450 }
16451 }
16452 }
16453
16454 return $documents;
16455}
16456
16457
16475function show_actions_messaging($conf, $langs, $db, $filterobj, $objcon = null, $noprint = 0, $actioncode = '', $donetodo = 'done', $filters = array(), $sortfield = 'a.datep,a.id', $sortorder = 'DESC')
16476{
16477 dol_syslog('show_actions_messaging::begin', LOG_DEBUG);
16478 global $user, $conf;
16479 global $form;
16480
16481 global $param, $massactionbutton;
16482
16483 require_once DOL_DOCUMENT_ROOT . '/comm/action/class/actioncomm.class.php';
16484
16485 // Check parameters
16486 if (!is_object($filterobj) && !is_object($objcon)) {
16487 dol_print_error(null, 'BadParameter');
16488 }
16489
16490 $histo = array();
16491 '@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';
16492
16493 $numaction = 0;
16494 $now = dol_now();
16495
16496 $sortfield_list = explode(',', $sortfield);
16497 $sortfield_label_list = array('a.id' => 'id', 'a.datep' => 'dp', 'a.percent' => 'percent');
16498 $sortfield_new_list = array();
16499 foreach ($sortfield_list as $sortfield_value) {
16500 $sortfield_new_list[] = $sortfield_label_list[trim($sortfield_value)];
16501 }
16502 $sortfield_new = implode(',', $sortfield_new_list);
16503
16504 $sql = null;
16505 $sql2 = null;
16506
16507 if (isModEnabled('agenda')) {
16508 // Search histo on actioncomm
16509 if (is_object($objcon) && $objcon->id > 0) {
16510 $sql = "SELECT DISTINCT a.id, a.label as label,";
16511 } else {
16512 $sql = "SELECT a.id, a.label as label,";
16513 }
16514 $sql .= " a.datep as dp,";
16515 $sql .= " a.note as message,";
16516 $sql .= " a.datep2 as dp2,";
16517 $sql .= " a.percent as percent, 'action' as type,";
16518 $sql .= " a.fk_element, a.elementtype,";
16519 $sql .= " a.fk_contact, a.fulldayevent,";
16520 $sql .= " a.email_from as msg_from,";
16521 $sql .= " c.code as acode, c.libelle as alabel, c.picto as apicto,";
16522 $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";
16523 if (is_object($filterobj) && get_class($filterobj) == 'Societe') {
16524 $sql .= ", sp.lastname, sp.firstname";
16525 } elseif (is_object($filterobj) && get_class($filterobj) == 'Adherent') {
16526 $sql .= ", m.lastname, m.firstname";
16527 } elseif (is_object($filterobj) && in_array(get_class($filterobj), array('Commande', 'CommandeFournisseur', 'Product', 'Ticket', 'BOM', 'Contrat', 'Facture', 'FactureFournisseur', 'Propal', 'Expedition'))) {
16528 $sql .= ", o.ref";
16529 } else {
16530 if (is_object($filterobj) && !empty($filterobj->table_element) && !empty($filterobj->element) && !empty($filterobj->id) && array_key_exists('ref', $filterobj->fields)) {
16531 $sql .= ", o.ref";
16532 }
16533 }
16534 $sql .= " FROM " . MAIN_DB_PREFIX . "actioncomm as a";
16535 $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "user as u on u.rowid = a.fk_user_action";
16536 $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "c_actioncomm as c ON a.fk_action = c.id";
16537
16538 $force_filter_contact = $filterobj instanceof User;
16539
16540 if (is_object($objcon) && $objcon->id > 0) {
16541 $force_filter_contact = true;
16542 $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "actioncomm_resources as r ON a.id = r.fk_actioncomm";
16543 $sql .= " AND r.element_type = '" . $db->escape($objcon->table_element) . "' AND r.fk_element = " . ((int) $objcon->id);
16544 }
16545
16546 if ((is_object($filterobj) && get_class($filterobj) == 'Societe') || (is_object($filterobj) && get_class($filterobj) == 'Contact')) {
16547 $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "socpeople as sp ON a.fk_contact = sp.rowid";
16548 } elseif (is_object($filterobj) && get_class($filterobj) == 'Dolresource') {
16549 $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "element_resources as er";
16550 $sql .= " ON er.resource_type = 'dolresource'";
16551 $sql .= " AND er.element_id = a.id";
16552 $sql .= " AND er.resource_id = " . ((int) $filterobj->id);
16553 } elseif (is_object($filterobj) && get_class($filterobj) == 'Adherent') {
16554 $sql .= ", " . MAIN_DB_PREFIX . "adherent as m";
16555 } elseif (is_object($filterobj) && get_class($filterobj) == 'CommandeFournisseur') {
16556 $sql .= ", " . MAIN_DB_PREFIX . "commande_fournisseur as o";
16557 } elseif (is_object($filterobj) && get_class($filterobj) == 'Product') {
16558 $sql .= ", " . MAIN_DB_PREFIX . "product as o";
16559 } elseif (is_object($filterobj) && get_class($filterobj) == 'Ticket') {
16560 $sql .= ", " . MAIN_DB_PREFIX . "ticket as o";
16561 } elseif (is_object($filterobj) && get_class($filterobj) == 'BOM') {
16562 $sql .= ", " . MAIN_DB_PREFIX . "bom_bom as o";
16563 } elseif (is_object($filterobj) && get_class($filterobj) == 'Contrat') {
16564 $sql .= ", " . MAIN_DB_PREFIX . "contrat as o";
16565 } elseif (is_object($filterobj) && get_class($filterobj) == 'Facture') {
16566 $sql .= ", " . MAIN_DB_PREFIX . "facture as o";
16567 } elseif (is_object($filterobj) && get_class($filterobj) == 'FactureFournisseur') {
16568 $sql .= ", " . MAIN_DB_PREFIX . "facture_fourn as o";
16569 } elseif (is_object($filterobj) && get_class($filterobj) == 'Commande') {
16570 $sql .= ", " . MAIN_DB_PREFIX . "commande as o";
16571 } elseif (is_object($filterobj) && get_class($filterobj) == 'Expedition') {
16572 $sql .= ", " . MAIN_DB_PREFIX . "expedition as o";
16573 } elseif (is_object($filterobj) && get_class($filterobj) == 'Propal') {
16574 $sql .= ", " . MAIN_DB_PREFIX . "propal as o";
16575 } else {
16576 if (is_object($filterobj) && !empty($filterobj->table_element) && !empty($filterobj->element) && !empty($filterobj->id) && array_key_exists('ref', $filterobj->fields)) {
16577 $sql .= ", " . MAIN_DB_PREFIX . $filterobj->table_element . " as o";
16578 }
16579 }
16580 $sql .= " WHERE a.entity IN (" . getEntity('agenda') . ")";
16581 if (!$force_filter_contact) {
16582 if (is_object($filterobj) && in_array(get_class($filterobj), array('Societe', 'Client', 'Fournisseur')) && $filterobj->id) {
16583 $sql .= " AND a.fk_soc = " . ((int) $filterobj->id);
16584 } elseif (is_object($filterobj) && get_class($filterobj) == 'Project' && $filterobj->id) {
16585 $sql .= " AND a.fk_project = o.rowid AND a.fk_project = " . ((int) $filterobj->id);
16586 } elseif (is_object($filterobj) && get_class($filterobj) == 'Adherent') {
16587 $sql .= " AND a.fk_element = m.rowid AND a.elementtype = 'member'";
16588 if ($filterobj->id) {
16589 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16590 }
16591 } elseif (is_object($filterobj) && get_class($filterobj) == 'Commande') {
16592 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'order'";
16593 if ($filterobj->id) {
16594 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16595 }
16596 } elseif (is_object($filterobj) && get_class($filterobj) == 'Expedition') {
16597 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'shipping'";
16598 if ($filterobj->id) {
16599 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16600 }
16601 } elseif (is_object($filterobj) && get_class($filterobj) == 'Propal') {
16602 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'propal'";
16603 if ($filterobj->id) {
16604 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16605 }
16606 } elseif (is_object($filterobj) && get_class($filterobj) == 'CommandeFournisseur') {
16607 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'order_supplier'";
16608 if ($filterobj->id) {
16609 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16610 }
16611 } elseif (is_object($filterobj) && get_class($filterobj) == 'Product') {
16612 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'product'";
16613 if ($filterobj->id) {
16614 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16615 }
16616 } elseif (is_object($filterobj) && get_class($filterobj) == 'Ticket') {
16617 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'ticket'";
16618 if ($filterobj->id) {
16619 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16620 }
16621 } elseif (is_object($filterobj) && get_class($filterobj) == 'BOM') {
16622 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'bom'";
16623 if ($filterobj->id) {
16624 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16625 }
16626 } elseif (is_object($filterobj) && get_class($filterobj) == 'Contrat') {
16627 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'contract'";
16628 if ($filterobj->id) {
16629 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16630 }
16631 } elseif (is_object($filterobj) && get_class($filterobj) == 'Contact' && $filterobj->id) {
16632 $sql .= " AND a.fk_contact = sp.rowid";
16633 if ($filterobj->id) {
16634 $sql .= " AND a.fk_contact = " . ((int) $filterobj->id);
16635 }
16636 } elseif (is_object($filterobj) && get_class($filterobj) == 'Facture') {
16637 $sql .= " AND a.fk_element = o.rowid";
16638 if ($filterobj->id) {
16639 $sql .= " AND a.fk_element = " . ((int) $filterobj->id) . " AND a.elementtype = 'invoice'";
16640 }
16641 } elseif (is_object($filterobj) && get_class($filterobj) == 'FactureFournisseur') {
16642 $sql .= " AND a.fk_element = o.rowid";
16643 if ($filterobj->id) {
16644 $sql .= " AND a.fk_element = " . ((int) $filterobj->id) . " AND a.elementtype = 'invoice_supplier'";
16645 }
16646 } else {
16647 if (is_object($filterobj) && !empty($filterobj->element) && !empty($filterobj->id) && array_key_exists('ref', $filterobj->fields)) {
16648 $sql .= " AND a.fk_element = o.rowid";
16649 $sql .= " AND a.elementtype = '" . $db->escape($filterobj->element) . "'";
16650 if ($filterobj->id) {
16651 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16652 }
16653 }
16654 }
16655 } else {
16656 $sql .= " AND u.rowid = " . ((int) $filterobj->id);
16657 }
16658
16659 // Condition on actioncode
16660 if (!empty($actioncode) && $actioncode != '-1') {
16661 if (!getDolGlobalString('AGENDA_USE_EVENT_TYPE')) {
16662 if ($actioncode == 'AC_NON_AUTO') {
16663 $sql .= " AND c.type != 'systemauto'";
16664 } elseif ($actioncode == 'AC_ALL_AUTO') {
16665 $sql .= " AND c.type = 'systemauto'";
16666 } else {
16667 if ($actioncode == 'AC_OTH') {
16668 $sql .= " AND c.type != 'systemauto'";
16669 } elseif ($actioncode == 'AC_OTH_AUTO') {
16670 $sql .= " AND c.type = 'systemauto'";
16671 }
16672 }
16673 } else {
16674 if ($actioncode == 'AC_NON_AUTO') {
16675 $sql .= " AND c.type != 'systemauto'";
16676 } elseif ($actioncode == 'AC_ALL_AUTO') {
16677 $sql .= " AND c.type = 'systemauto'";
16678 } else {
16679 $sql .= " AND c.code = '" . $db->escape($actioncode) . "'";
16680 }
16681 }
16682 }
16683 if ($donetodo == 'todo') {
16684 $sql .= " AND ((a.percent >= 0 AND a.percent < 100) OR (a.percent = -1 AND a.datep > '" . $db->idate($now) . "'))";
16685 } elseif ($donetodo == 'done') {
16686 $sql .= " AND (a.percent = 100 OR (a.percent = -1 AND a.datep <= '" . $db->idate($now) . "'))";
16687 }
16688 if (is_array($filters) && $filters['search_agenda_label']) {
16689 $sql .= natural_search('a.label', $filters['search_agenda_label']);
16690 }
16691 }
16692
16693 // Add also event from emailings. TODO This should be replaced by an automatic event ? May be it's too much for very large emailing.
16694 if (
16695 isModEnabled('mailing') && !empty($objcon->email)
16696 && (empty($actioncode) || $actioncode == 'AC_OTH_AUTO' || $actioncode == 'AC_EMAILING')
16697 ) {
16698 $langs->load("mails");
16699
16700 $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";
16701 $sql2 .= ", null as fk_element, '' as elementtype, null as contact_id";
16702 $sql2 .= ", 'AC_EMAILING' as acode, '' as alabel, '' as apicto";
16703 $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
16704 if (is_object($filterobj) && get_class($filterobj) == 'Societe') {
16705 $sql2 .= ", '' as lastname, '' as firstname";
16706 } elseif (is_object($filterobj) && get_class($filterobj) == 'Adherent') {
16707 $sql2 .= ", '' as lastname, '' as firstname";
16708 } elseif (is_object($filterobj) && get_class($filterobj) == 'CommandeFournisseur') {
16709 $sql2 .= ", '' as ref";
16710 } elseif (is_object($filterobj) && get_class($filterobj) == 'Product') {
16711 $sql2 .= ", '' as ref";
16712 } elseif (is_object($filterobj) && get_class($filterobj) == 'Ticket') {
16713 $sql2 .= ", '' as ref";
16714 }
16715 $sql2 .= " FROM " . MAIN_DB_PREFIX . "mailing as m, " . MAIN_DB_PREFIX . "mailing_cibles as mc, " . MAIN_DB_PREFIX . "user as u";
16716 $sql2 .= " WHERE mc.email = '" . $db->escape($objcon->email) . "'"; // Search is done on email.
16717 $sql2 .= " AND mc.statut = 1";
16718 $sql2 .= " AND u.rowid = m.fk_user_valid";
16719 $sql2 .= " AND mc.fk_mailing=m.rowid";
16720 }
16721
16722 $num = 0;
16723 $MAXWITHOUTPAGINATION = getDolGlobalInt('AGENDA_MAX_EVENTS_ON_PAGE_WITHOUT_PAGINATION', 100);
16724
16725 if ($sql || $sql2) { // May not be defined if module Agenda is not enabled and mailing module disabled too
16726 if (!empty($sql) && !empty($sql2)) {
16727 $sql = $sql . " UNION " . $sql2;
16728 } elseif (empty($sql) && !empty($sql2)) {
16729 $sql = $sql2;
16730 }
16731
16732 //TODO Add navigation with this limits...
16733 $offset = 0;
16734 $limit = $MAXWITHOUTPAGINATION;
16735
16736 // Complete request and execute it with limit
16737 $sql .= $db->order($sortfield_new, $sortorder);
16738 if ($limit) {
16739 $sql .= $db->plimit($limit + 1, $offset);
16740 }
16741
16742 dol_syslog("function.lib::show_actions_messaging", LOG_DEBUG);
16743
16744 $resql = $db->query($sql);
16745 if ($resql) {
16746 $i = 0;
16747 $num = $db->num_rows($resql);
16748
16749 $imaxinloop = ($limit ? min($num, $limit) : $num);
16750 while ($i < $imaxinloop) {
16751 $obj = $db->fetch_object($resql);
16752
16753 if ($obj->type == 'action') {
16754 $contactaction = new ActionComm($db);
16755 $contactaction->id = $obj->id;
16756 $result = $contactaction->fetchResources();
16757 if ($result < 0) {
16759 setEventMessage("actions.lib::show_actions_messaging Error fetch resource", 'errors');
16760 }
16761
16762 //if ($donetodo == 'todo') $sql.= " AND ((a.percent >= 0 AND a.percent < 100) OR (a.percent = -1 AND a.datep > '".$db->idate($now)."'))";
16763 //elseif ($donetodo == 'done') $sql.= " AND (a.percent = 100 OR (a.percent = -1 AND a.datep <= '".$db->idate($now)."'))";
16764 $tododone = '';
16765 if (($obj->percent >= 0 and $obj->percent < 100) || ($obj->percent == -1 && $obj->dp > $now)) {
16766 $tododone = 'todo';
16767 }
16768
16769 $histo[$numaction] = array(
16770 'type' => $obj->type,
16771 'tododone' => $tododone,
16772 'id' => $obj->id,
16773 'datestart' => $db->jdate($obj->dp),
16774 'dateend' => $db->jdate($obj->dp2),
16775 'fulldayevent' => (int) $obj->fulldayevent,
16776 'note' => $obj->label,
16777 'message' => $obj->message,
16778 'percent' => $obj->percent,
16779
16780 'userid' => $obj->user_id,
16781 'login' => $obj->user_login,
16782 'userfirstname' => $obj->user_firstname,
16783 'userlastname' => $obj->user_lastname,
16784 'userphoto' => $obj->user_photo,
16785 'msg_from' => $obj->msg_from,
16786
16787 'contact_id' => $obj->fk_contact,
16788 'socpeopleassigned' => $contactaction->socpeopleassigned,
16789 'lastname' => (empty($obj->lastname) ? '' : $obj->lastname),
16790 'firstname' => (empty($obj->firstname) ? '' : $obj->firstname),
16791 'fk_element' => $obj->fk_element,
16792 'elementtype' => $obj->elementtype,
16793 // Type of event
16794 'acode' => $obj->acode,
16795 'alabel' => $obj->alabel,
16796 'libelle' => $obj->alabel, // deprecated
16797 'apicto' => $obj->apicto
16798 );
16799 } else {
16800 $histo[$numaction] = array(
16801 'type' => $obj->type,
16802 'tododone' => 'done',
16803 'id' => $obj->id,
16804 'datestart' => $db->jdate($obj->dp),
16805 'dateend' => $db->jdate($obj->dp2),
16806 'fulldayevent' => (int) $obj->fulldayevent,
16807 'note' => $obj->label,
16808 'message' => $obj->message,
16809 'percent' => $obj->percent,
16810 'acode' => $obj->acode,
16811
16812 'userid' => $obj->user_id,
16813 'login' => $obj->user_login,
16814 'userfirstname' => $obj->user_firstname,
16815 'userlastname' => $obj->user_lastname,
16816 'userphoto' => $obj->user_photo
16817 );
16818 }
16819
16820 $numaction++;
16821 $i++;
16822 }
16823 } else {
16825 }
16826 }
16827
16828 // Set $out to show events
16829 $out = '';
16830
16831 if (!isModEnabled('agenda')) {
16832 $langs->loadLangs(array("admin", "errors"));
16833 $out = info_admin($langs->trans("WarningModuleXDisabledSoYouMayMissEventHere", $langs->transnoentitiesnoconv("Module2400Name")), 0, 0, 'warning');
16834 }
16835
16836 if (isModEnabled('agenda') || (isModEnabled('mailing') && !empty($objcon->email))) {
16837 $delay_warning = getDolGlobalInt('MAIN_DELAY_ACTIONS_TODO') * 24 * 60 * 60;
16838
16839 require_once DOL_DOCUMENT_ROOT . '/comm/action/class/actioncomm.class.php';
16840 include_once DOL_DOCUMENT_ROOT . '/core/lib/functions2.lib.php';
16841 require_once DOL_DOCUMENT_ROOT . '/core/class/html.formactions.class.php';
16842 require_once DOL_DOCUMENT_ROOT . '/contact/class/contact.class.php';
16843
16844 $formactions = new FormActions($db);
16845
16846 $actionstatic = new ActionComm($db);
16847 $userstatic = new User($db);
16848 $contactstatic = new Contact($db);
16849 $userGetNomUrlCache = array();
16850 $contactGetNomUrlCache = array();
16851
16852 $out .= '<div class="filters-container" >';
16853 $out .= '<form name="listactionsfilter" class="listactionsfilter" action="' . $_SERVER["PHP_SELF"] . '" method="POST">';
16854 $out .= '<input type="hidden" name="token" value="' . newToken() . '">';
16855
16856 if (
16857 $objcon && get_class($objcon) == 'Contact' &&
16858 (is_null($filterobj) || get_class($filterobj) == 'Societe')
16859 ) {
16860 $out .= '<input type="hidden" name="id" value="' . $objcon->id . '" />';
16861 } else {
16862 $out .= '<input type="hidden" name="id" value="' . $filterobj->id . '" />';
16863 }
16864 if (($filterobj && get_class($filterobj) == 'Societe')) {
16865 $out .= '<input type="hidden" name="socid" value="' . $filterobj->id . '" />';
16866 } else {
16867 $out .= '<input type="hidden" name="userid" value="' . $filterobj->id . '" />';
16868 }
16869
16870 $out .= "\n";
16871
16872 $out .= '<div class="div-table-responsive-no-min">';
16873 $out .= '<table class="noborder borderbottom centpercent">';
16874
16875 $out .= '<tr class="liste_titre">';
16876
16877 // Action column
16878 if ($conf->main_checkbox_left_column) {
16879 $out .= '<th class="liste_titre width50 middle">';
16880 $searchpicto = $form->showFilterAndCheckAddButtons($massactionbutton ? 1 : 0, 'checkforselect', 1);
16881 $out .= $searchpicto;
16882 $out .= '</th>';
16883 }
16884
16885 // Date
16886 $out .= getTitleFieldOfList('Date', 0, $_SERVER["PHP_SELF"], 'a.datep', '', $param, '', $sortfield, $sortorder, 'nowraponall nopaddingleftimp ') . "\n";
16887
16888 $out .= '<th class="liste_titre hideonsmartphone"><strong class="hideonsmartphone">' . $langs->trans("Search") . ' : </strong></th>';
16889 if ($donetodo) {
16890 $out .= '<th class="liste_titre"></th>';
16891 }
16892 // Type of event
16893 $out .= '<th class="liste_titre">';
16894 $out .= '<span class="fas fa-square inline-block fawidth30 hideonsmartphone" style="color: #ddd;" title="' . $langs->trans("ActionType") . '"></span>';
16895 $out .= $formactions->select_type_actions($actioncode, "actioncode", '', getDolGlobalString('AGENDA_USE_EVENT_TYPE') ? -1 : 1, 0, 0, 1, 'selecttype minwidth100', $langs->trans("Type"));
16896 $out .= '</th>';
16897 // Label
16898 $out .= '<th class="liste_titre maxwidth100onsmartphone">';
16899 $out .= '<input type="text" class="maxwidth100onsmartphone" name="search_agenda_label" value="' . $filters['search_agenda_label'] . '" placeholder="' . $langs->trans("Label") . '">';
16900 $out .= '</th>';
16901
16902 // Action column
16903 if (!$conf->main_checkbox_left_column) {
16904 $out .= '<th class="liste_titre width50 middle">';
16905 $searchpicto = $form->showFilterAndCheckAddButtons($massactionbutton ? 1 : 0, 'checkforselect', 1);
16906 $out .= $searchpicto;
16907 $out .= '</th>';
16908 }
16909
16910 $out .= '</tr>';
16911
16912 $out .= '</table>';
16913
16914 $out .= '</form>';
16915 $out .= '</div>';
16916
16917 $out .= "\n";
16918
16919 $out .= '<ul class="timeline">';
16920
16921 if ($donetodo) {
16922 $tmp = '';
16923 if ($filterobj instanceof Societe) {
16924 $tmp .= '<a href="' . DOL_URL_ROOT . '/comm/action/list.php?mode=show_list&socid=' . $filterobj->id . '&status=done">';
16925 }
16926 if ($filterobj instanceof User) {
16927 $tmp .= '<a href="' . DOL_URL_ROOT . '/comm/action/list.php?mode=show_list&socid=' . $filterobj->id . '&status=done">';
16928 }
16929 $tmp .= ($donetodo != 'done' ? $langs->trans("ActionsToDoShort") : '');
16930 $tmp .= ($donetodo != 'done' && $donetodo != 'todo' ? ' / ' : '');
16931 $tmp .= ($donetodo != 'todo' ? $langs->trans("ActionsDoneShort") : '');
16932 //$out.=$langs->trans("ActionsToDoShort").' / '.$langs->trans("ActionsDoneShort");
16933 if ($filterobj instanceof Societe) {
16934 $tmp .= '</a>';
16935 }
16936 if ($filterobj instanceof User) {
16937 $tmp .= '</a>';
16938 }
16939 $out .= getTitleFieldOfList($tmp);
16940 }
16941
16942 require_once DOL_DOCUMENT_ROOT . '/comm/action/class/cactioncomm.class.php';
16943 $caction = new CActionComm($db);
16944 $arraylist = $caction->liste_array(1, 'code', '', (!getDolGlobalString('AGENDA_USE_EVENT_TYPE') ? 1 : 0), '', 1);
16945
16946 $actualCycleDate = false;
16947
16948 // Loop on each event to show it
16949 foreach ($histo as $key => $value) {
16950 $actionstatic->fetch($histo[$key]['id']); // TODO Do we need this, we already have a lot of data of line into $histo
16951
16952 $actionstatic->type_picto = $histo[$key]['apicto'];
16953 $actionstatic->type_code = $histo[$key]['acode'];
16954
16955 $labeltype = $actionstatic->type_code;
16956 if (!getDolGlobalString('AGENDA_USE_EVENT_TYPE') && empty($arraylist[$labeltype])) {
16957 $labeltype = 'AC_OTH';
16958 }
16959 if (!empty($actionstatic->code) && preg_match('/^TICKET_MSG/', $actionstatic->code)) {
16960 $labeltype = $langs->trans("Message");
16961 } else {
16962 if (!empty($arraylist[$labeltype])) {
16963 $labeltype = $arraylist[$labeltype];
16964 }
16965 if ($actionstatic->type_code == 'AC_OTH_AUTO' && ($actionstatic->type_code != $actionstatic->code) && $labeltype && !empty($arraylist[$actionstatic->code])) {
16966 $labeltype .= ' - ' . $arraylist[$actionstatic->code]; // Use code in priority on type_code
16967 }
16968 }
16969
16970 $url = DOL_URL_ROOT . '/comm/action/card.php?id=' . $histo[$key]['id'];
16971
16972 $tmpa = dol_getdate($histo[$key]['datestart'], false);
16973
16974 if (isset($tmpa['year']) && isset($tmpa['yday']) && $actualCycleDate !== $tmpa['year'] . '-' . $tmpa['yday']) {
16975 $actualCycleDate = $tmpa['year'] . '-' . $tmpa['yday'];
16976 $out .= '<!-- timeline time label -->';
16977 $out .= '<li class="time-label">';
16978 $out .= '<span class="timeline-badge-date">';
16979 $out .= dol_print_date($histo[$key]['datestart'], 'daytext', 'tzuserrel', $langs);
16980 $out .= '</span>';
16981 $out .= '</li>';
16982 $out .= '<!-- /.timeline-label -->';
16983 }
16984
16985
16986 $out .= '<!-- timeline item -->' . "\n";
16987 $out .= '<li class="timeline-code-' . (!empty($actionstatic->code) ? strtolower($actionstatic->code) : "none") . '">';
16988
16989 //$timelineicon = getTimelineIcon($actionstatic, $histo, $key);
16990 $typeicon = $actionstatic->getTypePicto('pictofixedwidth timeline-icon-not-applicble', $labeltype);
16991 //$out .= $timelineicon;
16992 //var_dump($timelineicon);
16993 $out .= $typeicon;
16994
16995 $out .= '<div class="timeline-item">' . "\n";
16996
16997 $out .= '<span class="time timeline-header-action2">';
16998
16999 if (isset($histo[$key]['type']) && $histo[$key]['type'] == 'mailing') {
17000 $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") . ' ';
17001 $out .= $histo[$key]['id'];
17002 $out .= '</a> ';
17003 } else {
17004 $out .= $actionstatic->getNomUrl(1, -1, 'valignmiddle') . ' ';
17005 }
17006
17007 if (
17008 $user->hasRight('agenda', 'allactions', 'create') ||
17009 (($actionstatic->authorid == $user->id || $actionstatic->userownerid == $user->id) && $user->hasRight('agenda', 'myactions', 'create'))
17010 ) {
17011 $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) . '">';
17012 //$out .= '<i class="fa fa-pencil" title="'.$langs->trans("Modify").'" ></i>';
17013 $out .= img_picto($langs->trans("Modify"), 'edit', 'class="edita"');
17014 $out .= '</a>';
17015 }
17016
17017 $out .= '</span>';
17018
17019 // Date
17020 $out .= '<span class="time"><i class="fa fa-clock valignmiddle"></i> ';
17021 $out .= '<span class="valignmiddle marginrightonly">';
17022 $out .= dol_print_date($histo[$key]['datestart'], 'day', 'tzuserrel');
17023 //$out .= '</span>';
17024 //$out .= '<span class="valignmiddle">'.
17025 $out .= ' '.dol_print_date($histo[$key]['datestart'], 'hour', 'tzuserrel', null, false, 'opacitymedium');
17026 //$out .= '</span>';
17027 if ($histo[$key]['dateend'] && $histo[$key]['dateend'] != $histo[$key]['datestart']) {
17028 $tmpa = dol_getdate($histo[$key]['datestart'], true);
17029 $tmpb = dol_getdate($histo[$key]['dateend'], true);
17030 if ($tmpa['mday'] == $tmpb['mday'] && $tmpa['mon'] == $tmpb['mon'] && $tmpa['year'] == $tmpb['year']) {
17031 $out .= ' - ' . dol_print_date($histo[$key]['dateend'], 'hour', 'tzuserrel', null, false, 1);
17032 } else {
17033 $out .= ' - ' . dol_print_date($histo[$key]['dateend'], 'day', 'tzuserrel');
17034 //$out .= '<span class="valignmiddle marginrightonly">';
17035 $out .= ' '.dol_print_date($histo[$key]['dateend'], 'hour', 'tzuserrel', null, false, 'opacitymedium');
17036 //$out .= '</span>';
17037 }
17038 }
17039 $late = 0;
17040 if ($histo[$key]['percent'] == 0 && $histo[$key]['datestart'] && $histo[$key]['datestart'] < ($now - $delay_warning)) {
17041 $late = 1;
17042 }
17043 if ($histo[$key]['percent'] == 0 && !$histo[$key]['datestart'] && $histo[$key]['dateend'] && $histo[$key]['datestart'] < ($now - $delay_warning)) {
17044 $late = 1;
17045 }
17046 if ($histo[$key]['percent'] > 0 && $histo[$key]['percent'] < 100 && $histo[$key]['dateend'] && $histo[$key]['dateend'] < ($now - $delay_warning)) {
17047 $late = 1;
17048 }
17049 if ($histo[$key]['percent'] > 0 && $histo[$key]['percent'] < 100 && !$histo[$key]['dateend'] && $histo[$key]['datestart'] && $histo[$key]['datestart'] < ($now - $delay_warning)) {
17050 $late = 1;
17051 }
17052 if ($late) {
17053 $out .= img_warning($langs->trans("Late")) . ' ';
17054 }
17055 $out .= "</span></span>\n";
17056
17057 $out .= '<span class="time">';
17058 $out .= $actionstatic->getLibStatut(2);
17059 $out .= '</span>';
17060
17061 // Ref
17062 $out .= '<h3 class="timeline-header">';
17063
17064 // Author of event
17065 $out .= '<div class="messaging-author inline-block tdoverflowmax150 valignmiddle marginrightonly">';
17066 if ($histo[$key]['userid'] > 0) {
17067 if (!isset($userGetNomUrlCache[$histo[$key]['userid']])) { // is in cache ?
17068 $userstatic->fetch($histo[$key]['userid']);
17069 $userGetNomUrlCache[$histo[$key]['userid']] = $userstatic->getNomUrl(-1, '', 0, 0, 16, 0, 'firstelselast', '');
17070 }
17071 $out .= $userGetNomUrlCache[$histo[$key]['userid']];
17072 } elseif (!empty($histo[$key]['msg_from']) && $actionstatic->code == 'TICKET_MSG') {
17073 if (!isset($contactGetNomUrlCache[$histo[$key]['msg_from']])) {
17074 if ($contactstatic->fetch(0, null, '', $histo[$key]['msg_from']) > 0) {
17075 $contactGetNomUrlCache[$histo[$key]['msg_from']] = $contactstatic->getNomUrl(-1, '', 16);
17076 } else {
17077 $contactGetNomUrlCache[$histo[$key]['msg_from']] = $histo[$key]['msg_from'];
17078 }
17079 }
17080 $out .= $contactGetNomUrlCache[$histo[$key]['msg_from']];
17081 } else {
17082 $out .= '<img class="photomemberphoto userphoto" alt="" src="/public/theme/common/user_anonymous.png">'.$langs->trans("Anonymous");
17083 }
17084 $out .= '</div>';
17085
17086 // Title
17087 $out .= ' <div class="messaging-title inline-block">';
17088 //$out .= $actionstatic->getTypePicto(); // The type of event is already into the timeline on left.
17089 if (empty($conf->dol_optimize_smallscreen) && $actionstatic->type_code != 'AC_OTH_AUTO') {
17090 $out .= $labeltype . ' - ';
17091 }
17092
17093 $libelle = '';
17094
17095 if (!empty($actionstatic->code) && preg_match('/^TICKET_MSG_PRIVATE/', $actionstatic->code)) {
17096 $out .= $langs->trans('TicketNewMessage').' - <em>'.img_picto($langs->trans('Private'), 'lock', 'class="valignmiddle"').' '.$langs->trans('Private').'</em>';
17097 $summary = preg_replace('/\[[^\]]*\]\s*/', '', $actionstatic->label);
17098 //if ($summary != $object->title) {
17099 $out .= ' - '.dolPrintHTML($summary);
17100 //}
17101 } elseif (!empty($actionstatic->code) && preg_match('/^TICKET_MSG/', $actionstatic->code)) {
17102 $out .= $langs->trans('TicketNewMessage');
17103 } elseif (isset($histo[$key]['type'])) {
17104 if ($histo[$key]['type'] == 'action') {
17105 $transcode = $langs->transnoentitiesnoconv("Action" . $histo[$key]['acode']);
17106 $libelle = ($transcode != "Action" . $histo[$key]['acode'] ? $transcode : $histo[$key]['alabel']);
17107 $libelle = $histo[$key]['note'];
17108 $actionstatic->id = $histo[$key]['id'];
17109 if ($libelle != $labeltype) {
17110 $out .= dol_escape_htmltag(dol_trunc($libelle, 120));
17111 }
17112 } elseif ($histo[$key]['type'] == 'mailing') {
17113 $out .= '<a href="' . DOL_URL_ROOT . '/comm/mailing/card.php?id=' . $histo[$key]['id'] . '">' . img_object($langs->trans("ShowEMailing"), "email") . ' ';
17114 $transcode = $langs->transnoentitiesnoconv("Action" . $histo[$key]['acode']);
17115 $libelle = ($transcode != "Action" . $histo[$key]['acode'] ? $transcode : 'Send mass mailing');
17116 $out .= dol_escape_htmltag(dol_trunc($libelle, 120));
17117 } else {
17118 $libelle .= $histo[$key]['note'];
17119 $out .= dol_escape_htmltag(dol_trunc($libelle, 120));
17120 }
17121 }
17122 $out = preg_replace('/ - $/', '', $out); // Remove ending ' - '
17123
17124 if (isset($histo[$key]['elementtype']) && !empty($histo[$key]['fk_element'])) {
17125 if (isset($conf->cache['elementlinkcache'][$histo[$key]['elementtype']]) && isset($conf->cache['elementlinkcache'][$histo[$key]['elementtype']][$histo[$key]['fk_element']])) {
17126 $link = $conf->cache['elementlinkcache'][$histo[$key]['elementtype']][$histo[$key]['fk_element']];
17127 } else {
17128 if (!isset($conf->cache['elementlinkcache'][$histo[$key]['elementtype']])) {
17129 $conf->cache['elementlinkcache'][$histo[$key]['elementtype']] = array();
17130 }
17131 $link = dolGetElementUrl($histo[$key]['fk_element'], $histo[$key]['elementtype'], 1);
17132 $conf->cache['elementlinkcache'][$histo[$key]['elementtype']][$histo[$key]['fk_element']] = $link;
17133 }
17134
17135 // 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)
17136 $showlink = 1;
17137 if (is_object($filterobj) && get_class($filterobj) == 'Ticket') {
17138 if ($histo[$key]['elementtype'] == 'ticket') {
17139 $showlink = 0;
17140 }
17141 }
17142
17143 if ($link && $showlink) {
17144 $out .= ' - ' . $link;
17145 }
17146 }
17147
17148 $out .= '</div>';
17149
17150 $out .= '</h3>';
17151
17152 // Message
17153 if ($actionstatic->code == 'AC_TICKET_CREATE') {
17154 $newmess = $filterobj->message;
17155 } else {
17156 $newmess = $histo[$key]['message'];
17157 }
17158 if (
17159 !empty($newmess && $newmess != $libelle)
17160 && $actionstatic->code != 'AC_TICKET_MODIFY'
17161 ) {
17162 $out .= '<div class="timeline-body wordbreak small">';
17163 $truncateLines = getDolGlobalInt('MAIN_TRUNCATE_TIMELINE_MESSAGE', 3);
17164 $truncatedText = dolGetFirstLineOfText($newmess, $truncateLines);
17165 if ($truncateLines > 0 && strlen($newmess) > strlen($truncatedText)) {
17166 $out .= '<div class="readmore-block --closed" >';
17167 $out .= ' <div class="readmore-block__excerpt">';
17168 $out .= dolPrintHTML($truncatedText, 0, array('pre', 'code'));
17169 $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>';
17170 $out .= ' </div>';
17171 $out .= ' <div class="readmore-block__full-text" >';
17172
17173 $out .= dolPrintHTML($newmess, 0, array('pre', 'code'));
17174
17175 $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>';
17176 $out .= ' </div>';
17177 $out .= '</div>';
17178 } else {
17179 $out .= dolPrintHTML($newmess, 0, array('pre', 'code'));
17180 }
17181 $out .= '</div>';
17182 }
17183
17184 // Timeline footer
17185 $footer = '';
17186
17187 // Contact for this action
17188 if (isset($histo[$key]['socpeopleassigned']) && is_array($histo[$key]['socpeopleassigned']) && count($histo[$key]['socpeopleassigned']) > 0) {
17189 $contactList = '';
17190 foreach ($histo[$key]['socpeopleassigned'] as $cid => $Tab) {
17191 if (empty($conf->cache['contact'][$cid])) {
17192 $contact = new Contact($db);
17193 $contact->fetch($cid);
17194 $conf->cache['contact'][$cid] = $contact;
17195 } else {
17196 $contact = $conf->cache['contact'][$cid];
17197 }
17198
17199 if ($contact) {
17200 $contactList .= !empty($contactList) ? ', ' : '';
17201 $contactList .= $contact->getNomUrl(1);
17202 if (isset($histo[$key]['acode']) && $histo[$key]['acode'] == 'AC_TEL') {
17203 if (!empty($contact->phone_pro)) {
17204 $contactList .= '(' . dol_print_phone($contact->phone_pro) . ')';
17205 }
17206 }
17207 }
17208 }
17209
17210 $footer .= $langs->trans('ActionOnContact') . ' : ' . $contactList;
17211 } elseif (empty($objcon->id) && isset($histo[$key]['contact_id']) && $histo[$key]['contact_id'] > 0) {
17212 if (empty($conf->cache['contact'][$histo[$key]['contact_id']])) {
17213 $contact = new Contact($db);
17214 $result = $contact->fetch($histo[$key]['contact_id']);
17215 $conf->cache['contact'][$histo[$key]['contact_id']] = $contact;
17216 } else {
17217 $contact = $conf->cache['contact'][$histo[$key]['contact_id']];
17218 $result = ($contact instanceof Contact) ? $contact->id : 0;
17219 }
17220
17221 if ($result > 0) {
17222 $footer .= $contact->getNomUrl(1);
17223 if (isset($histo[$key]['acode']) && $histo[$key]['acode'] == 'AC_TEL') {
17224 if (!empty($contact->phone_pro)) {
17225 $footer .= '(' . dol_print_phone($contact->phone_pro) . ')';
17226 }
17227 }
17228 }
17229 }
17230
17231 $documents = getActionCommEcmList($actionstatic);
17232 if (!empty($documents)) {
17233 $footer .= '<div class="timeline-documents-container">';
17234 foreach ($documents as $doc) {
17235 $footer .= '<span id="document_' . $doc->id . '" class="timeline-documents" ';
17236 $footer .= ' data-id="' . $doc->id . '" ';
17237 $footer .= ' data-path="' . $doc->filepath . '"';
17238 $footer .= ' data-filename="' . dol_escape_htmltag($doc->filename) . '" ';
17239 $footer .= '>';
17240
17241 $filePath = DOL_DATA_ROOT . '/' . $doc->filepath . '/' . $doc->filename;
17242 $mime = dol_mimetype($filePath);
17243 if (empty($doc->agenda_id)) {
17244 $dir_ref = $actionstatic->id;
17245 $modulepart = 'actions';
17246 } else {
17247 $split_dir = explode('/', $doc->filepath);
17248 $modulepart = array_shift($split_dir);
17249 $dir_ref = implode('/', $split_dir);
17250 }
17251
17252 $file = $dir_ref . '/' . $doc->filename;
17253 $thumb = $dir_ref . '/thumbs/' . substr($doc->filename, 0, strrpos($doc->filename, '.')) . '_mini' . substr($doc->filename, strrpos($doc->filename, '.'));
17254 $doclink = dol_buildpath('document.php', 1) . '?modulepart=' . $modulepart . '&attachment=0&file=' . urlencode($file) . '&entity=' . $conf->entity;
17255 $viewlink = dol_buildpath('viewimage.php', 1) . '?modulepart=' . $modulepart . '&file=' . urlencode($thumb) . '&entity=' . $conf->entity;
17256
17257
17258
17259 $mimeAttr = ' mime="' . $mime . '" ';
17260 $class = '';
17261 if (in_array($mime, array('image/png', 'image/jpeg', 'application/pdf'))) {
17262 $class .= ' documentpreview';
17263 }
17264
17265 $footer .= '<a href="' . $doclink . '" class="btn-link ' . $class . '" target="_blank" rel="noopener noreferrer" ' . $mimeAttr . ' >';
17266 $footer .= img_mime($filePath) . ' ' . $doc->filename;
17267 $footer .= '</a>';
17268
17269 $footer .= '</span>';
17270 }
17271 $footer .= '</div>';
17272 }
17273
17274 if (!empty($footer)) {
17275 $out .= '<div class="timeline-footer">' . $footer . '</div>';
17276 }
17277
17278 $out .= '</div>' . "\n"; // end timeline-item
17279
17280 $out .= '</li>';
17281 $out .= '<!-- END timeline item -->';
17282 }
17283
17284 $out .= "</ul>\n";
17285
17286 // Code to manage the click on button data-read-more-action to show full description of an event
17287 $out .= '<script>
17288 jQuery(document).ready(function () {
17289 $(document).on("click", "[data-read-more-action]", function(e){
17290 console.log("We click on data-read-more-action");
17291 let readMoreBloc = $(this).closest(".readmore-block");
17292 if(readMoreBloc.length > 0){
17293 e.preventDefault();
17294 if($(this).attr("data-read-more-action") == "close"){
17295 readMoreBloc.addClass("--closed").removeClass("--open");
17296 $("html, body").animate({
17297 scrollTop: readMoreBloc.offset().top - 200
17298 }, 100);
17299 }else{
17300 readMoreBloc.addClass("--open").removeClass("--closed");
17301 }
17302 }
17303 });
17304 });
17305 </script>';
17306
17307
17308 if (empty($histo)) {
17309 $out .= '<span class="opacitymedium">' . $langs->trans("NoRecordFound") . '</span>';
17310 }
17311
17312 if ($num > $MAXWITHOUTPAGINATION) {
17313 $langs->load("errors");
17314 $out .= '<center><span class="opacitymedium">...' . $langs->trans("WarningTooManyDataPleaseUseMoreFilters", $MAXWITHOUTPAGINATION) . '...</span></center>';
17315 }
17316 }
17317
17318 if ($noprint) {
17319 return $out;
17320 } else {
17321 print $out;
17322 return null;
17323 }
17324}
17325
17337function buildParamDate($prefix, $timestamp = null, $hourTime = '', $gm = 'auto')
17338{
17339 if ($timestamp === null) {
17340 $timestamp = GETPOSTDATE($prefix, $hourTime, $gm);
17341 }
17342 $TParam = array(
17343 $prefix . 'day' => intval(dol_print_date($timestamp, '%d')),
17344 $prefix . 'month' => intval(dol_print_date($timestamp, '%m')),
17345 $prefix . 'year' => intval(dol_print_date($timestamp, '%Y')),
17346 );
17347 if ($hourTime === 'getpost' || ($timestamp !== null && dol_print_date($timestamp, '%H:%M:%S') !== '00:00:00')) {
17348 $TParam = array_merge($TParam, array(
17349 $prefix . 'hour' => intval(dol_print_date($timestamp, '%H')),
17350 $prefix . 'min' => intval(dol_print_date($timestamp, '%M')),
17351 $prefix . 'sec' => intval(dol_print_date($timestamp, '%S'))
17352 ));
17353 }
17354
17355 return '&' . http_build_query($TParam);
17356}
17357
17376function recordNotFound($message = '', $printheader = 1, $printfooter = 1, $showonlymessage = 0, $params = null)
17377{
17378 global $conf, $db, $langs, $hookmanager;
17379 global $action, $object;
17380
17381 if (!is_object($langs)) {
17382 include_once DOL_DOCUMENT_ROOT . '/core/class/translate.class.php';
17383 $langs = new Translate('', $conf);
17384 $langs->setDefaultLang();
17385 }
17386
17387 $langs->load("errors");
17388
17389 if ($printheader) {
17390 if (function_exists("llxHeader")) {
17391 llxHeader('');
17392 } elseif (function_exists("llxHeaderVierge")) {
17393 llxHeaderVierge('');
17394 }
17395 }
17396
17397 print '<div class="error">';
17398 if (empty($message)) {
17399 print $langs->trans("ErrorRecordNotFound");
17400 } else {
17401 print $langs->trans($message);
17402 }
17403 print '</div>';
17404 print '<br>';
17405
17406 if (empty($showonlymessage)) {
17407 if (empty($hookmanager)) {
17408 include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
17409 $hookmanager = new HookManager($db);
17410 // Initialize a technical object to manage hooks of page. Note that conf->hooks_modules contains an array of hook context
17411 $hookmanager->initHooks(array('main'));
17412 }
17413
17414 $parameters = array('message' => $message, 'params' => $params);
17415 $reshook = $hookmanager->executeHooks('getErrorRecordNotFound', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
17416 print $hookmanager->resPrint;
17417 }
17418
17419 if ($printfooter && function_exists("llxFooter")) {
17420 llxFooter();
17421 if (is_object($db)) {
17422 $db->close();
17423 }
17424 }
17425 exit(0);
17426}
17427
17456function array_merge_recursive_distinct(array $array1, array $array2): array
17457{
17458 $merged = $array1;
17459
17460 foreach ($array2 as $key => $value) {
17461 if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
17462 $merged[$key] = array_merge_recursive_distinct($merged[$key], $value);
17463 } else {
17464 $merged[$key] = $value;
17465 }
17466 }
17467
17468 return $merged;
17469}
17470
17477function getObjectSocId($obj)
17478{
17479 if (!empty($obj->socid)) {
17480 return (int) $obj->socid;
17481 } elseif (!empty($obj->soc_id)) {
17482 return (int) $obj->soc_id;
17483 } elseif (!empty($obj->societe_id)) {
17484 return (int) $obj->societe_id;
17485 }
17486 return null;
17487}
17488
17494function getListLimitFromScreenHeight()
17495{
17496 $default = 10;
17497 if (!empty($_SESSION['dol_screenheight']) && $_SESSION['dol_screenheight'] < 700) {
17498 $default = 8;
17499 } elseif (!empty($_SESSION['dol_screenheight']) && $_SESSION['dol_screenheight'] < 950) {
17500 $default = 10;
17501 } elseif (!empty($_SESSION['dol_screenheight']) && $_SESSION['dol_screenheight'] > 1130) {
17502 $default = 15;
17503 }
17504
17505 return $default;
17506}
$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:799
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:523
dol_get_next_day($day, $month, $year)
Return next day.
Definition date.lib.php:508
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:492
dol_get_next_month($month, $year)
Return next month.
Definition date.lib.php:542
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.
verifCond($strToEvaluate, $onlysimplestring='1')
Verify if condition in string is ok or not.
getDolGlobalLoginBadCharUnauthorized()
Return the list of unauthorized characters in user logins.
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...
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.
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.
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.
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.
info_admin($text, $infoonimgalt=0, $nodiv=0, $admin='1', $morecss='hideonsmartphone', $textfordropdown='', $picto='', $textonpictotooltip='')
Show information in HTML for admin users or standard users.
img_delete($titlealt='default', $other='class="pictodelete"', $morecss='')
Show delete logo.
GETPOSTINT($paramname, $method=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
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.
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.
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.
img_view($titlealt='default', $float=0, $other='class="valignmiddle"')
Show logo view card.
dol_get_object_properties($obj, $properties=[])
Get 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.
dol_string_unaccent($str)
Clean a string from all accent characters to be used as ref, login or by dol_sanitizeFileName.
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...
dol_clone_in_array($srcobject, $startlevel=0)
Create a clone of instance of object into a full array, using recursive call.
dol_print_email($email, $contactid=0, $socid=0, $addlink=0, $max=0, $showinvalid=1, $withpicto=0, $morecss='paddingrightonly')
Show EMail link formatted for HTML output.
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)
showSimpleOrderTable($outputlangs, $object)
Returns simple order table template as string.
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.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
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)
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.
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.
dol_htmloutput_mesg($mesgstring='', $mesgarray=array(), $style='ok', $keepembedded=0)
Print formatted messages to output (Used to show messages on html output).
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.
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...
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.
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.
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.
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).
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