dolibarr 24.0.2
functions.lib.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2000-2007 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2003 Jean-Louis Bergamo <jlb@j1b.org>
4 * Copyright (C) 2004-2025 Laurent Destailleur <eldy@users.sourceforge.net>
5 * Copyright (C) 2004 Sebastien Di Cintio <sdicintio@ressource-toi.org>
6 * Copyright (C) 2004 Benoit Mortier <benoit.mortier@opensides.be>
7 * Copyright (C) 2004 Christophe Combelles <ccomb@free.fr>
8 * Copyright (C) 2005-2019 Regis Houssin <regis.houssin@inodbox.com>
9 * Copyright (C) 2008 Raphael Bertrand (Resultic) <raphael.bertrand@resultic.fr>
10 * Copyright (C) 2010-2018 Juanjo Menent <jmenent@2byte.es>
11 * Copyright (C) 2013 Cédric Salvador <csalvador@gpcsolutions.fr>
12 * Copyright (C) 2013-2026 Alexandre Spangaro <alexandre@inovea-conseil.com>
13 * Copyright (C) 2014 Cédric GROSS <c.gross@kreiz-it.fr>
14 * Copyright (C) 2014-2015 Marcos García <marcosgdf@gmail.com>
15 * Copyright (C) 2015 Jean-François Ferry <jfefe@aternatik.fr>
16 * Copyright (C) 2018-2026 Frédéric France <frederic.france@free.fr>
17 * Copyright (C) 2019-2023 Thibault Foucart <support@ptibogxiv.net>
18 * Copyright (C) 2020 Open-Dsi <support@open-dsi.fr>
19 * Copyright (C) 2021 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
20 * Copyright (C) 2022 Anthony Berton <anthony.berton@bb2a.fr>
21 * Copyright (C) 2022 Ferran Marcet <fmarcet@2byte.es>
22 * Copyright (C) 2022-2026 Charlene Benke <charlene@patas-monkey.com>
23 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
24 * Copyright (C) 2023-2024 Joachim Kueter <git-jk@bloxera.com>
25 * Copyright (C) 2024 Lenin Rivas <lenin.rivas777@gmail.com>
26 * Copyright (C) 2024 Josep Lluís Amador Teruel <joseplluis@lliuretic.cat>
27 * Copyright (C) 2024 Benoît PASCAL <contact@p-ben.com>
28 * Copyright (C) 2025 Vincent Maury <vmaury@timgroup.fr>
29 * Copyright (C) 2026 Benjamin Falière <benjamin@faliere.com>
30 * Copyright (C) 2026 Pierre Ardoin <developpeur@lesmetiersdubatiment.fr>
31 *
32 * This program is free software; you can redistribute it and/or modify
33 * it under the terms of the GNU General Public License as published by
34 * the Free Software Foundation; either version 3 of the License, or
35 * (at your option) any later version.
36 *
37 * This program is distributed in the hope that it will be useful,
38 * but WITHOUT ANY WARRANTY; without even the implied warranty of
39 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
40 * GNU General Public License for more details.
41 *
42 * You should have received a copy of the GNU General Public License
43 * along with this program. If not, see <https://www.gnu.org/licenses/>.
44 * or see https://www.gnu.org/
45 */
46
53//include_once DOL_DOCUMENT_ROOT.'/core/lib/json.lib.php';
54
55// Function for better PHP x compatibility
56if (!function_exists('utf8_encode')) {
64 function utf8_encode($elements)
65 {
66 return mb_convert_encoding($elements, 'UTF-8', 'ISO-8859-1');
67 }
68}
69
70if (!function_exists('utf8_decode')) {
78 function utf8_decode($elements)
79 {
80 return mb_convert_encoding($elements, 'ISO-8859-1', 'UTF-8');
81 }
82}
83if (!function_exists('str_starts_with')) {
92 function str_starts_with($haystack, $needle)
93 {
94 return (string) $needle !== '' && strncmp($haystack, $needle, strlen($needle)) === 0;
95 }
96}
97if (!function_exists('str_ends_with')) {
106 function str_ends_with($haystack, $needle)
107 {
108 return $needle !== '' && substr($haystack, -strlen($needle)) === (string) $needle;
109 }
110}
111if (!function_exists('str_contains')) {
120 function str_contains($haystack, $needle)
121 {
122 return $needle !== '' && mb_strpos($haystack, $needle) !== false;
123 }
124}
125
126
134function formatLogObject($data)
135{
136 if (getDolGlobalInt("MAIN_LOG_ON_ONE_LINE")) {
137 return json_encode($data);
138 }
139
140 return var_export($data, true);
141}
142
143
155function getMultidirOutput($object, $module = '', $forobject = 0, $mode = 'output')
156{
157 global $conf;
158
159 $subdirectory = '';
160 if (!is_object($object) && empty($module)) {
161 return null;
162 }
163 if (empty($module) && !empty($object->element)) {
164 $module = $object->element;
165 }
166
167 // Special case for backward compatibility
168 switch ($module) {
169 case 'fichinter':
170 $module = 'ficheinter';
171 break;
172 case 'invoice_supplier':
173 $module = 'supplier_invoice';
174 break;
175 case 'order_supplier':
176 $module = 'supplier_order';
177 break;
178 case 'recruitmentjobposition':
179 $module = 'recruitment';
180 $subdirectory = '/recruitmentjobposition';
181 break;
182 case 'recruitmentcandidature':
183 $module = 'recruitment';
184 $subdirectory = '/recruitmentcandidature';
185 break;
186 case 'knowledgerecord':
187 $module = 'knowledgemanagement';
188 $subdirectory = '/knowledgerecord';
189 break;
190 case 'commande_fournisseur':
191 $module = 'fournisseur';
192 $subdirectory = '/commande';
193 break;
194 case 'expedition':
195 case 'shipment':
196 case 'shipping':
197 $module = 'expedition';
198 $subdirectory = '/sending';
199 break;
200 case 'company':
201 $module = 'societe';
202 break;
203 case 'service':
204 case 'produit':
205 $module = 'product';
206 break;
207 case 'project_task':
208 $module = 'projet';
209
210 // Fetch the project to build the correct path
211 $object->fetchProject();
212
213 $subdirectory = '/'.$object->project->ref;
214 break;
215 case 'action':
216 case 'actioncomm':
217 case 'event':
218 $module = 'agenda';
219 break;
220 default:
221 break;
222 }
223
224 // Get the relative path of directory
225 if ($mode == 'output' || $mode == 'outputrel' || $mode == 'version') {
226 if (isset($conf->$module) && property_exists($conf->$module, 'multidir_output')) {
227 $s = '';
228 if ($mode != 'outputrel') {
229 // An entity with no declared directory returned an undefined index, so an empty path that
230 // made the caller read or write a relative path under the web root. Answer the error instead.
231 $entity = (int) (empty($object->entity) ? $conf->entity : $object->entity);
232 if (!isset($conf->$module->multidir_output[$entity])) {
233 return 'error-diroutput-not-defined-for-this-entity-and-object='.$module;
234 }
235 $s = $conf->$module->multidir_output[$entity] . $subdirectory;
236 }
237 if ($forobject && $object->id > 0) {
238 $s .= ($mode != 'outputrel' ? '/' : '') . get_exdir(0, 0, 0, 0, $object);
239 }
240 return dol_sanitizePathName($s);
241 } elseif (isset($conf->$module) && property_exists($conf->$module, 'dir_output')) {
242 $s = '';
243 if ($mode != 'outputrel') {
244 $s = $conf->$module->dir_output . $subdirectory;
245 }
246 if ($forobject && $object->id > 0) {
247 $s .= ($mode != 'outputrel' ? '/' : '') . get_exdir(0, 0, 0, 0, $object);
248 }
249 return dol_sanitizePathName($s);
250 } else {
251 return 'error-diroutput-not-defined-for-this-entity-and-object='.$module;
252 }
253 } elseif ($mode == 'temp') {
254 if (isset($conf->$module) && property_exists($conf->$module, 'multidir_temp')) {
255 // Same guard as the 'output' mode above, see the comment there
256 $entity = (int) (empty($object->entity) ? $conf->entity : $object->entity);
257 if (!isset($conf->$module->multidir_temp[$entity])) {
258 return 'error-dirtemp-not-defined-for-this-entity-and-object='.$module;
259 }
260 return dol_sanitizePathName($conf->$module->multidir_temp[$entity]);
261 } elseif (isset($conf->$module) && property_exists($conf->$module, 'dir_temp')) {
262 return dol_sanitizePathName($conf->$module->dir_temp);
263 } else {
264 return 'error-dirtemp-not-defined-for-this-entity-and-object='.$module;
265 }
266 } else {
267 return 'error-bad-value-for-mode';
268 }
269}
270
280function getMultidirTemp($object, $module = '', $forobject = 0)
281{
282 return getMultidirOutput($object, $module, $forobject, 'temp');
283}
284
294function getMultidirVersion($object, $module = '', $forobject = 0)
295{
296 return getMultidirOutput($object, $module, $forobject, 'version');
297}
298
299
308function getDolGlobalString($key, $default = '')
309{
310 global $conf;
311 return (string) (isset($conf->global->$key) ? $conf->global->$key : $default);
312}
313
320{
321 global $dolibarr_login_badcharunauthorized;
322
323 if (isset($dolibarr_login_badcharunauthorized)) {
324 if ($dolibarr_login_badcharunauthorized === 'MAIN_LOGIN_BADCHARUNAUTHORIZED') {
325 return getDolGlobalString('MAIN_LOGIN_BADCHARUNAUTHORIZED', ',@<>"\'');
326 }
327
328 return (string) $dolibarr_login_badcharunauthorized;
329 }
330
331 return ',@<>"\'';
332}
333
343function getDolGlobalInt($key, $default = 0)
344{
345 global $conf;
346 return (int) (isset($conf->global->$key) ? $conf->global->$key : $default);
347}
348
358function getDolGlobalFloat($key, $default = 0)
359{
360 global $conf;
361 return (float) (isset($conf->global->$key) ? $conf->global->$key : $default);
362}
363
372function getDolGlobalBool($key, $default = false)
373{
374 global $conf;
375 return (bool) ($conf->global->$key ?? $default);
376}
377
384{
385 global $conf;
386 return (string) $conf->currency;
387}
388
395{
396 global $conf;
397 return (string) $conf->dol_optimize_smallscreen;
398}
399
405function getDolEntity()
406{
407 global $conf;
408 return (int) $conf->entity;
409}
410
416function getDolDBType()
417{
418 global $conf;
419 return $conf->db->type;
420}
421
429{
430 return str_replace('_', '', basename(dirname($s)).basename($s, '.php'));
431}
432
442function getDolUserString($key, $default = '', $tmpuser = null)
443{
444 if (empty($tmpuser)) {
445 global $user;
446 $tmpuser = $user;
447 }
448
449 return (string) (isset($tmpuser->conf->$key) ? $tmpuser->conf->$key : $default);
450}
451
460function getDolUserInt($key, $default = 0, $tmpuser = null)
461{
462 if (empty($tmpuser)) {
463 global $user;
464 $tmpuser = $user;
465 }
466
467 return (int) (isset($tmpuser->conf->$key) ? $tmpuser->conf->$key : $default);
468}
469
470
479define(
480 'MODULE_MAPPING',
481 array(
482 // Map deprecated names to new names
483 'adherent' => 'member', // Has new directory
484 'member_type' => 'adherent_type', // No directory, but file called adherent_type
485 'banque' => 'bank', // Has new directory
486 'contrat' => 'contract', // Has new directory
487 'entrepot' => 'stock', // Has new directory
488 'projet' => 'project', // Has new directory
489 'categorie' => 'category', // Has old directory
490 'commande' => 'order', // Has old directory
491 'expedition' => 'shipping', // Has old directory
492 'facture' => 'invoice', // Has old directory
493 'fichinter' => 'intervention', // Has old directory
494 'ficheinter' => 'intervention', // Backup for 'fichinter'
495 'propale' => 'propal', // Has old directory
496 'societe' => 'thirdparty', // Has old directory
497 'socpeople' => 'contact', // Has old directory
498 'fournisseur' => 'supplier', // Has old directory
499
500 'actioncomm' => 'agenda', // NO module directory (public dir agenda)
501 'product_price' => 'productprice', // NO directory
502 'product_fournisseur_price' => 'productsupplierprice', // NO directory
503 )
504);
505
512function isModEnabled($module)
513{
514 global $conf;
515
516 // Fix old names (map to new names)
517 $arrayconv = MODULE_MAPPING;
518 $arrayconvbis = array_flip(MODULE_MAPPING);
519
520 if (!getDolGlobalString('MAIN_USE_NEW_SUPPLIERMOD')) {
521 // Special cases: both use the same module.
522 $arrayconv['supplier_order'] = 'fournisseur';
523 $arrayconv['supplier_invoice'] = 'fournisseur';
524 }
525
526 $module_alt = $module;
527 if (!empty($arrayconv[$module])) {
528 $module_alt = $arrayconv[$module];
529 }
530 $module_bis = $module;
531 if (!empty($arrayconvbis[$module])) {
532 $module_bis = $arrayconvbis[$module];
533 }
534
535 return !empty($conf->modules[$module]) || !empty($conf->modules[$module_alt]) || !empty($conf->modules[$module_bis]);
536}
537
548function getWarningDelay($module, $parmlevel1, $parmlevel2 = '')
549{
550 global $conf;
551
552 // For compatibility with bad naming on module
553 $moduletomoduletouse = array(
554 'invoice' => 'facture',
555 );
556 $moduleParmsMapping = array(
557 'product' => 'produit',
558 );
559
560 if (!empty($moduletomoduletouse[$module])) {
561 $module = $moduletomoduletouse[$module];
562 }
563
564 $warningDelayPath = $parmlevel1;
565 if (!empty($moduleParmsMapping[$warningDelayPath])) {
566 $warningDelayPath = $moduleParmsMapping[$warningDelayPath];
567 }
568
569 if ($parmlevel2) {
570 if (!empty($conf->$module) && !empty($conf->$module->$warningDelayPath) && !empty($conf->$module->$warningDelayPath->$parmlevel2) && !empty($conf->$module->$warningDelayPath->$parmlevel2->warning_delay)) {
571 return (int) $conf->$module->$warningDelayPath->$parmlevel2->warning_delay;
572 }
573 } else {
574 if (!empty($conf->$module) && !empty($conf->$module->$warningDelayPath) && !empty($conf->$module->$warningDelayPath->warning_delay)) {
575 return (int) $conf->$module->$warningDelayPath->warning_delay;
576 }
577 }
578
579 return 0;
580}
581
588function isDolTms($timestamp)
589{
590 if ($timestamp === '') {
591 dol_syslog('Using empty string for a timestamp is deprecated, prefer use of null when calling page ' . $_SERVER["PHP_SELF"] . getCallerInfoString(), LOG_DEBUG);
592 return false;
593 }
594 if (is_null($timestamp) || !is_numeric($timestamp)) {
595 return false;
596 }
597
598 return true;
599}
600
612function getDoliDBInstance($type, $host, $user, $pass, $name, $port)
613{
614 require_once DOL_DOCUMENT_ROOT . "/core/db/" . $type . '.class.php';
615
616 $class = 'DoliDB' . ucfirst($type);
617 $db = new $class($type, $host, $user, $pass, $name, $port);
618 return $db;
619}
620
638function getEntity($element, $shared = 1, $currentobject = null)
639{
640 global $conf, $mc, $hookmanager, $object, $action, $db;
641
642 if (!is_object($hookmanager)) {
643 include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
644 $hookmanager = new HookManager($db);
645 }
646
647 // fix different element names (France to English)
648 switch ($element) {
649 case 'projet':
650 $element = 'project';
651 break;
652 case 'contrat':
653 $element = 'contract';
654 break; // "/contrat/class/contrat.class.php"
655 case 'order_supplier':
656 $element = 'supplier_order';
657 break; // "/fourn/class/fournisseur.commande.class.php"
658 case 'invoice_supplier':
659 $element = 'supplier_invoice';
660 break; // "/fourn/class/fournisseur.facture.class.php"
661 }
662
663 if (is_object($mc)) {
664 $out = $mc->getEntity($element, $shared, $currentobject);
665 } else {
666 $out = '';
667 $addzero = array('user', 'usergroup', 'cronjob', 'c_email_templates', 'email_template', 'default_values', 'overwrite_trans');
668 if (getDolGlobalString('HOLIDAY_ALLOW_ZERO_IN_DIC')) { // this constant break the dictionary admin without Multicompany
669 $addzero[] = 'c_holiday_types';
670 }
671 if (in_array($element, $addzero)) {
672 $out .= '0,';
673 }
674 $out .= ((int) $conf->entity);
675 }
676
677 // Manipulate entities to query on the fly
678 $parameters = array(
679 'element' => $element,
680 'shared' => $shared,
681 'object' => $object,
682 'currentobject' => $currentobject,
683 'out' => $out
684 );
685 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
686 $reshook = $hookmanager->executeHooks('hookGetEntity', $parameters, $currentobject, $action); // Note that $action and $object may have been modified by some hooks
687
688 if (is_numeric($reshook)) {
689 if ($reshook == 0 && !empty($hookmanager->resPrint)) {
690 $out .= ',' . $hookmanager->resPrint; // add
691 } elseif ($reshook == 1) {
692 $out = $hookmanager->resPrint; // replace
693 }
694 }
695
696 return $out;
697}
698
705function setEntity($currentobject)
706{
707 global $conf, $mc;
708
709 if (is_object($mc) && method_exists($mc, 'setEntity')) {
710 return $mc->setEntity($currentobject);
711 } else {
712 return ((is_object($currentobject) && $currentobject->id > 0 && ((int) $currentobject->entity) > 0) ? (int) $currentobject->entity : $conf->entity);
713 }
714}
715
722function isASecretKey($keyname)
723{
724 return preg_match('/(_pass|password|_pw|_key|securekey|serverkey|secret\d?|p12key|exportkey|_PW_[a-z]+|token)$/i', $keyname);
725}
726
727
734function num2Alpha($n)
735{
736 $r = '';
737 for ($r = ""; $n >= 0; $n = intval($n / 26) - 1) {
738 $r = chr($n % 26 + 0x41) . $r;
739 }
740 return $r;
741}
742
743
760function getBrowserInfo($user_agent)
761{
762 include_once DOL_DOCUMENT_ROOT . '/includes/mobiledetect/mobiledetectlib/Mobile_Detect.php';
763
764 $name = 'unknown';
765 $version = '';
766 $os = 'unknown';
767 $phone = '';
768
769 $user_agent = substr($user_agent, 0, 512); // Avoid to process too large user agent
770
771 // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal Bad definition of Mobile_Detect function
772 $detectmobile = new Mobile_Detect(null, $user_agent);
773 $tablet = $detectmobile->isTablet();
774
775 if ($detectmobile->isMobile()) {
776 $phone = 'unknown';
777
778 // If phone/smartphone, we set phone os name.
779 if ($detectmobile->is('AndroidOS')) {
780 $os = $phone = 'android';
781 } elseif ($detectmobile->is('BlackBerryOS')) {
782 $os = $phone = 'blackberry';
783 } elseif ($detectmobile->is('iOS')) {
784 $os = 'ios';
785 $phone = 'iphone';
786 } elseif ($detectmobile->is('PalmOS')) {
787 $os = $phone = 'palm';
788 } elseif ($detectmobile->is('SymbianOS')) {
789 $os = 'symbian';
790 } elseif ($detectmobile->is('webOS')) {
791 $os = 'webos';
792 } elseif ($detectmobile->is('MaemoOS')) {
793 $os = 'maemo';
794 } elseif ($detectmobile->is('WindowsMobileOS') || $detectmobile->is('WindowsPhoneOS')) {
795 $os = 'windows';
796 }
797 }
798
799 // OS
800 if (preg_match('/linux/i', $user_agent)) {
801 $os = 'linux';
802 } elseif (preg_match('/macintosh/i', $user_agent)) {
803 $os = 'macintosh';
804 } elseif (preg_match('/windows/i', $user_agent)) {
805 $os = 'windows';
806 }
807
808 // Name
809 $reg = array();
810 if (preg_match('/firefox(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
811 $name = 'firefox';
812 $version = empty($reg[2]) ? '' : $reg[2];
813 } elseif (preg_match('/edge(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
814 $name = 'edge';
815 $version = empty($reg[2]) ? '' : $reg[2];
816 } elseif (preg_match('/chrome(\/|\s)([\d\.]+)/i', $user_agent, $reg)) {
817 $name = 'chrome';
818 $version = empty($reg[2]) ? '' : $reg[2];
819 } elseif (preg_match('/chrome/i', $user_agent, $reg)) {
820 // we can have 'chrome (Mozilla...) chrome x.y' in one string
821 $name = 'chrome';
822 } elseif (preg_match('/iceweasel/i', $user_agent)) {
823 $name = 'iceweasel';
824 } elseif (preg_match('/epiphany/i', $user_agent)) {
825 $name = 'epiphany';
826 } elseif (preg_match('/safari(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
827 $name = 'safari';
828 $version = empty($reg[2]) ? '' : $reg[2];
829 } elseif (preg_match('/opera(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
830 // Safari is often present in string for mobile but its not.
831 $name = 'opera';
832 $version = empty($reg[2]) ? '' : $reg[2];
833 } elseif (preg_match('/(MSIE\s([0-9]+\.[0-9]))|.*(Trident\/[0-9]+.[0-9];.*rv:([0-9]+\.[0-9]+))/i', $user_agent, $reg)) {
834 $name = 'ie';
835 $version = end($reg);
836 } elseif (preg_match('/(Windows NT\s([0-9]+\.[0-9])).*(Trident\/[0-9]+.[0-9];.*rv:([0-9]+\.[0-9]+))/i', $user_agent, $reg)) {
837 // MS products at end
838 $name = 'ie';
839 $version = end($reg);
840 } elseif (preg_match('/l[iy]n(x|ks)(\‍(|\/|\s)*([\d\.]+)/i', $user_agent, $reg)) {
841 // MS products at end
842 $name = 'textbrowser';
843 $version = empty($reg[3]) ? '' : $reg[3];
844 } elseif (preg_match('/w3m\/([\d\.]+)/i', $user_agent, $reg)) {
845 // MS products at end
846 $name = 'textbrowser';
847 $version = empty($reg[1]) ? '' : $reg[1];
848 }
849
850 if ($tablet) {
851 $layout = 'tablet';
852 } elseif ($phone) {
853 $layout = 'phone';
854 } else {
855 $layout = 'classic';
856 }
857
858 return array(
859 'browsername' => $name,
860 'browserversion' => $version,
861 'browseros' => $os,
862 'browserua' => $user_agent,
863 'layout' => $layout, // tablet, phone, classic
864 'phone' => $phone, // deprecated
865 'tablet' => $tablet // deprecated
866 );
867}
868
874function dol_shutdown()
875{
876 global $db;
877 $disconnectdone = false;
878 $depth = 0;
879 if (is_object($db) && !empty($db->connected)) {
880 $depth = $db->transaction_opened;
881 $disconnectdone = $db->close();
882 }
883 dol_syslog("--- End access to " . (empty($_SERVER["PHP_SELF"]) ? 'unknown' : $_SERVER["PHP_SELF"]) . (($disconnectdone && $depth) ? ' (Warn: db disconnection forced, transaction depth was ' . $depth . ')' : ''), (($disconnectdone && $depth) ? LOG_WARNING : LOG_INFO));
884}
885
895function GETPOSTISSET($paramname)
896{
897 $isset = false;
898
899 $relativepathstring = $_SERVER["PHP_SELF"];
900 // Clean $relativepathstring
901 if (constant('DOL_URL_ROOT')) {
902 $relativepathstring = preg_replace('/^' . preg_quote(constant('DOL_URL_ROOT'), '/') . '/', '', $relativepathstring);
903 }
904 $relativepathstring = ltrim($relativepathstring, '/');
905 $relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
906
907 // Code for search criteria persistence.
908 // Retrieve values if restore_lastsearch_values
909 if (!empty($_GET['restore_lastsearch_values'])) { // Use $_GET here and not GETPOST
910 if (!empty($_SESSION['lastsearch_values_' . $relativepathstring])) { // If there is saved values
911 $tmp = json_decode($_SESSION['lastsearch_values_' . $relativepathstring], true);
912 if (is_array($tmp)) {
913 foreach ($tmp as $key => $val) {
914 if ($key == $paramname) { // We are on the requested parameter
915 $isset = true;
916 break;
917 }
918 }
919 }
920 }
921 // If there is saved contextpage, limit, page or mode
922 if ($paramname == 'contextpage' && !empty($_SESSION['lastsearch_contextpage_' . $relativepathstring])) {
923 $isset = true;
924 } elseif ($paramname == 'limit' && !empty($_SESSION['lastsearch_limit_' . $relativepathstring])) {
925 $isset = true;
926 } elseif ($paramname == 'page' && !empty($_SESSION['lastsearch_page_' . $relativepathstring])) {
927 $isset = true;
928 } elseif ($paramname == 'mode' && !empty($_SESSION['lastsearch_mode_' . $relativepathstring])) {
929 $isset = true;
930 }
931 } else {
932 $isset = (isset($_POST[$paramname]) || isset($_GET[$paramname])); // We must keep $_POST and $_GET here
933 }
934
935 return $isset;
936}
937
946function GETPOSTISARRAY($paramname, $method = 0)
947{
948 // for $method test need return the same $val as GETPOST
949 if (empty($method)) {
950 $val = isset($_GET[$paramname]) ? $_GET[$paramname] : (isset($_POST[$paramname]) ? $_POST[$paramname] : '');
951 } elseif ($method == 1) {
952 $val = isset($_GET[$paramname]) ? $_GET[$paramname] : '';
953 } elseif ($method == 2) {
954 $val = isset($_POST[$paramname]) ? $_POST[$paramname] : '';
955 } elseif ($method == 3) {
956 $val = isset($_POST[$paramname]) ? $_POST[$paramname] : (isset($_GET[$paramname]) ? $_GET[$paramname] : '');
957 } else {
958 $val = 'BadFirstParameterForGETPOST';
959 }
960
961 return is_array($val);
962}
963
964
975function GETPOSTINT($paramname, $method = 0, $nodefault = 0)
976{
977 return (int) GETPOST($paramname, 'int', $method, null, null, 0, $nodefault);
978}
979
993function GETPOSTFLOAT($paramname, $rounding = '', $option = 2)
994{
995 // price2num() can be used to round to an expected accuracy and/or to sanitize any valid user input (such as "1 234.5", "1 234,5", "1'234,5", "1·234,5", "1,234.5", etc.)
996 return (float) price2num(GETPOST($paramname), $rounding, $option);
997}
998
1014function GETPOSTDATE($prefix, $hourTime = '', $gm = 'auto', $saverestore = '')
1015{
1016 $m = array();
1017 if ($hourTime === 'getpost' || $hourTime === 'getpostend') {
1018 $hour = (GETPOSTISSET($prefix . 'hour') && GETPOSTINT($prefix . 'hour') >= 0) ? GETPOSTINT($prefix . 'hour') : ($hourTime === 'getpostend' ? 23 : 0);
1019 $minute = (GETPOSTISSET($prefix . 'min') && GETPOSTINT($prefix . 'min') >= 0) ? GETPOSTINT($prefix . 'min') : ($hourTime === 'getpostend' ? 59 : 0);
1020 $second = (GETPOSTISSET($prefix . 'sec') && GETPOSTINT($prefix . 'sec') >= 0) ? GETPOSTINT($prefix . 'sec') : ($hourTime === 'getpostend' ? 59 : 0);
1021 } elseif (preg_match('/^(\d\d):(\d\d):(\d\d)$/', $hourTime, $m)) {
1022 $hour = intval($m[1]);
1023 $minute = intval($m[2]);
1024 $second = intval($m[3]);
1025 } elseif ($hourTime === 'end') {
1026 $hour = 23;
1027 $minute = 59;
1028 $second = 59;
1029 } else {
1030 $hour = $minute = $second = 0;
1031 }
1032
1033 if (
1034 $saverestore
1035 && !GETPOSTISSET($prefix . 'day')
1036 && !GETPOSTISSET($prefix . 'month')
1037 && !GETPOSTISSET($prefix . 'year')
1038 && isset($_SESSION['DOLDATE_' . $saverestore . '_day'])
1039 && isset($_SESSION['DOLDATE_' . $saverestore . '_month'])
1040 && isset($_SESSION['DOLDATE_' . $saverestore . '_year'])
1041 ) {
1042 $day = $_SESSION['DOLDATE_' . $saverestore . '_day'];
1043 $month = $_SESSION['DOLDATE_' . $saverestore . '_month'];
1044 $year = $_SESSION['DOLDATE_' . $saverestore . '_year'];
1045 } else {
1046 $month = GETPOSTINT($prefix . 'month');
1047 $day = GETPOSTINT($prefix . 'day');
1048 $year = GETPOSTINT($prefix . 'year');
1049 }
1050
1051 // normalize out of range values
1052 $hour = (int) min($hour, 23);
1053 $minute = (int) min($minute, 59);
1054 $second = (int) min($second, 59);
1055
1056 if ($saverestore) {
1057 $_SESSION['DOLDATE_' . $saverestore . '_day'] = $day;
1058 $_SESSION['DOLDATE_' . $saverestore . '_month'] = $month;
1059 $_SESSION['DOLDATE_' . $saverestore . '_year'] = $year;
1060 }
1061
1062 //print "$hour, $minute, $second, $month, $day, $year, $gm<br>";
1063 return dol_mktime($hour, $minute, $second, $month, $day, $year, $gm);
1064}
1065
1107function GETPOST($paramname, $check = 'alphanohtml', $method = 0, $filter = null, $options = null, $noreplace = 0, $nodefault = 0)
1108{
1109 global $langs, $mysoc, $user, $conf;
1110
1111 if (empty($paramname)) { // Explicit test for null for phan.
1112 return 'BadFirstParameterForGETPOST';
1113 }
1114 if (empty($check)) {
1115 dol_syslog("Deprecated use of GETPOST, called with 1st param = " . $paramname . " and a 2nd param that is '', when calling page " . $_SERVER["PHP_SELF"], LOG_WARNING);
1116 // Enable this line to know who call the GETPOST with '' $check parameter.
1117 //var_dump(getCallerInfoString());
1118 }
1119 if (in_array($paramname, array('sortfield', 'sortorder'))) { // Force the $check to a more appropriated value
1120 $check = 'aZ09comma';
1121 }
1122
1123 if (empty($method)) {
1124 $out = isset($_GET[$paramname]) ? $_GET[$paramname] : (isset($_POST[$paramname]) ? $_POST[$paramname] : '');
1125 } elseif ($method == 1) {
1126 $out = isset($_GET[$paramname]) ? $_GET[$paramname] : '';
1127 } elseif ($method == 2) {
1128 $out = isset($_POST[$paramname]) ? $_POST[$paramname] : '';
1129 } elseif ($method == 3) {
1130 $out = isset($_POST[$paramname]) ? $_POST[$paramname] : (isset($_GET[$paramname]) ? $_GET[$paramname] : '');
1131 } else {
1132 return 'BadThirdParameterForGETPOST';
1133 }
1134
1135 $relativepathstring = ''; // For static analysis - looks possibly undefined if not set.
1136
1137 if (empty($method) || $method == 3 || $method == 4) {
1138 $relativepathstring = (empty($_SERVER["PHP_SELF"]) ? '' : $_SERVER["PHP_SELF"]);
1139 // Clean $relativepathstring
1140 if (constant('DOL_URL_ROOT')) {
1141 $relativepathstring = preg_replace('/^' . preg_quote(constant('DOL_URL_ROOT'), '/') . '/', '', $relativepathstring);
1142 }
1143 $relativepathstring = ltrim($relativepathstring, '/');
1144 $relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
1145
1146 // Code for search criteria persistence.
1147 // Retrieve saved values if restore_lastsearch_values is set
1148 if (!empty($_GET['restore_lastsearch_values'])) { // Use $_GET here and not GETPOST
1149 if (!empty($_SESSION['lastsearch_values_' . $relativepathstring])) { // If there is saved values
1150 $tmp = json_decode($_SESSION['lastsearch_values_' . $relativepathstring], true);
1151 if (is_array($tmp)) {
1152 foreach ($tmp as $key => $val) {
1153 if ($key == $paramname) { // We are on the requested parameter
1154 $out = $val;
1155 break;
1156 }
1157 }
1158 }
1159 }
1160 // If there is saved contextpage, page or limit
1161 if ($paramname == 'contextpage' && !empty($_SESSION['lastsearch_contextpage_' . $relativepathstring])) {
1162 $out = $_SESSION['lastsearch_contextpage_' . $relativepathstring];
1163 } elseif ($paramname == 'limit' && !empty($_SESSION['lastsearch_limit_' . $relativepathstring])) {
1164 $out = $_SESSION['lastsearch_limit_' . $relativepathstring];
1165 } elseif ($paramname == 'page' && !empty($_SESSION['lastsearch_page_' . $relativepathstring])) {
1166 $out = $_SESSION['lastsearch_page_' . $relativepathstring];
1167 } elseif ($paramname == 'mode' && !empty($_SESSION['lastsearch_mode_' . $relativepathstring])) {
1168 $out = $_SESSION['lastsearch_mode_' . $relativepathstring];
1169 }
1170 } elseif (!isset($_GET['sortfield'])) {
1171 // Else, retrieve default values if we are not doing a sort
1172 // If we did a click on a field to sort, we do no apply default values. Same if option MAIN_ENABLE_DEFAULT_VALUES is not set
1173 if (!empty($_GET['action']) && $_GET['action'] == 'create' && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) {
1174 // Search default value from $object->field
1175 global $object;
1176 '@phan-var-force CommonObject $object'; // Suppose it's a CommonObject for analysis, but other objects have the $fields field as well
1177 if (is_object($object) && isset($object->fields[$paramname]['default'])) {
1178 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
1179 $out = $object->fields[$paramname]['default'];
1180 }
1181 }
1182 if (getDolGlobalString('MAIN_ENABLE_DEFAULT_VALUES')) {
1183 if (!empty($_GET['action']) && (preg_match('/^create/', $_GET['action']) || preg_match('/^presend/', $_GET['action'])) && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) {
1184 // Now search in setup to overwrite default values
1185 if (!empty($user->default_values)) { // $user->default_values defined from menu 'Setup - Default values'
1186 if (isset($user->default_values[$relativepathstring]['createform'])) {
1187 foreach ($user->default_values[$relativepathstring]['createform'] as $defkey => $defval) {
1188 $qualified = 0;
1189 if ($defkey != '_noquery_') {
1190 $tmpqueryarraytohave = explode('&', $defkey);
1191 $tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
1192 $foundintru = 0;
1193 foreach ($tmpqueryarraytohave as $tmpquerytohave) {
1194 if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) {
1195 $foundintru = 1;
1196 }
1197 }
1198 if (!$foundintru) {
1199 $qualified = 1;
1200 }
1201 } else {
1202 $qualified = 1;
1203 }
1204
1205 if ($qualified) {
1206 if (isset($user->default_values[$relativepathstring]['createform'][$defkey][$paramname])) {
1207 $out = $user->default_values[$relativepathstring]['createform'][$defkey][$paramname];
1208 break;
1209 }
1210 }
1211 }
1212 }
1213 }
1214 } elseif (!empty($paramname) && !isset($_GET[$paramname]) && !isset($_POST[$paramname]) && empty($nodefault)) {
1215 // Management of default search_filters and sort order
1216 if (!empty($user->default_values)) {
1217 // $user->default_values defined from menu 'Setup - Default values'
1218 //var_dump($user->default_values[$relativepathstring]);
1219 if ($paramname == 'sortfield' || $paramname == 'sortorder') {
1220 // Sorted on which fields ? ASC or DESC ?
1221 if (isset($user->default_values[$relativepathstring]['sortorder'])) {
1222 // Even if paramname is sortfield, data are stored into ['sortorder...']
1223 foreach ($user->default_values[$relativepathstring]['sortorder'] as $defkey => $defval) {
1224 $qualified = 0;
1225 if ($defkey != '_noquery_') {
1226 $tmpqueryarraytohave = explode('&', $defkey);
1227 $tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
1228 $foundintru = 0;
1229 foreach ($tmpqueryarraytohave as $tmpquerytohave) {
1230 if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) {
1231 $foundintru = 1;
1232 }
1233 }
1234 if (!$foundintru) {
1235 $qualified = 1;
1236 }
1237 } else {
1238 $qualified = 1;
1239 }
1240
1241 if ($qualified) {
1242 $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
1243 foreach ($user->default_values[$relativepathstring]['sortorder'][$defkey] as $key => $val) {
1244 if ($out) {
1245 $out .= ', ';
1246 }
1247 if ($paramname == 'sortfield') {
1248 $out .= dol_string_nospecial($key, '', $forbidden_chars_to_replace);
1249 }
1250 if ($paramname == 'sortorder') {
1251 $out .= dol_string_nospecial($val, '', $forbidden_chars_to_replace);
1252 }
1253 }
1254 //break; // No break for sortfield and sortorder so we can cumulate fields (is it really useful ?)
1255 }
1256 }
1257 }
1258 } elseif (isset($user->default_values[$relativepathstring]['filters'])) {
1259 foreach ($user->default_values[$relativepathstring]['filters'] as $defkey => $defval) { // $defkey is a querystring like 'a=b&c=d', $defval is key of user
1260 if (!empty($_GET['disabledefaultvalues'])) { // If set of default values has been disabled by a request parameter
1261 continue;
1262 }
1263 $qualified = 0;
1264 if ($defkey != '_noquery_') {
1265 $tmpqueryarraytohave = explode('&', $defkey);
1266 $tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
1267 $foundintru = 0;
1268 foreach ($tmpqueryarraytohave as $tmpquerytohave) {
1269 if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) {
1270 $foundintru = 1;
1271 }
1272 }
1273 if (!$foundintru) {
1274 $qualified = 1;
1275 }
1276 } else {
1277 $qualified = 1;
1278 }
1279
1280 if ($qualified && isset($user->default_values[$relativepathstring]['filters'][$defkey][$paramname])) {
1281 // We must keep $_POST and $_GET here
1282 if (isset($_POST['search_all']) || isset($_GET['search_all'])) {
1283 // We made a search from quick search menu, do we still use default filter ?
1284 if (!getDolGlobalString('MAIN_DISABLE_DEFAULT_FILTER_FOR_QUICK_SEARCH')) {
1285 $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
1286 $out = dol_string_nospecial($user->default_values[$relativepathstring]['filters'][$defkey][$paramname], '', $forbidden_chars_to_replace);
1287 }
1288 } else {
1289 $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
1290 $out = dol_string_nospecial($user->default_values[$relativepathstring]['filters'][$defkey][$paramname], '', $forbidden_chars_to_replace);
1291 }
1292 break;
1293 }
1294 }
1295 }
1296 }
1297 }
1298 }
1299 }
1300 }
1301
1302 // Replace substitution variables for GETPOST (used to get final url with variable parameters or final default value, when using variable parameters __XXX__ in the GET URL)
1303 // Example of variables: __DAY__, __MONTH__, __YEAR__, __MYCOMPANY_COUNTRY_ID__, __USER_ID__, ...
1304 // We do this only if var is a GET. If it is a POST, may be we want to post the text with vars as the setup text.
1305 '@phan-var-force string $paramname';
1306 if (!is_array($out) && empty($_POST[$paramname]) && empty($noreplace)) {
1307 if (preg_match('/__([A-Z0-9]+(?:_[A-Z0-9]+){0,3})__/i', $out)) { // If there is at least one substitution key, we try to replace all known substitution keys
1308 $substitutionarray = getCommonSubstitutionArray($langs, 0, null, $user, array('mycompany', 'date', 'system', 'user'));
1309 complete_substitutions_array($substitutionarray, $langs, $user);
1310
1311 $out = make_substitutions($out, $substitutionarray, $langs);
1312 }
1313 }
1314
1315 // Check type of variable and make sanitization according to this
1316 if (preg_match('/^array/', $check)) { // If 'array' or 'array:restricthtml' or 'array:aZ09' or 'array:int'
1317 $tmpcheck = 'alphanohtml';
1318 if ($out === null || $out === '') {
1319 $out = array();
1320 } elseif (!is_array($out)) {
1321 $out = explode(',', $out);
1322 } else {
1323 $tmparray = explode(':', $check);
1324 if (!empty($tmparray[1])) {
1325 $tmpcheck = $tmparray[1];
1326 }
1327 }
1328 foreach ($out as $outkey => $outval) {
1329 $out[$outkey] = sanitizeVal($outval, $tmpcheck, $filter, $options);
1330 }
1331 } else {
1332 // If field name is 'search_xxx' then we force the add of space after each < and > (when following char is numeric) because it means
1333 // we use the < or > to make a search on a numeric value to do higher or lower so we can add a space to break html tags
1334 if (strpos($paramname, 'search_') === 0) {
1335 $out = preg_replace('/([<>])([-+]?\d)/', '\1 \2', $out);
1336 }
1337
1338 // @phan-suppress-next-line UnknownSanitizeType
1339 $out = sanitizeVal($out, $check, $filter, $options);
1340 }
1341
1342 // Sanitizing for special parameters.
1343 // Note: There is no reason to allow the backtopage/backtopageforcancel/backtopagejs, backtolist or backtourl parameter to contains an external URL. Only relative URLs are allowed.
1344 // @TODO Merge backtopage with backtourl
1345 // @TODO Rename backtolist into backtopagelist
1346 // @TODO Merge urlfrom into backtourl
1347 if (preg_match('/^backto/i', $paramname) || preg_match('/^urlfrom/i', $paramname)) {
1348 $out = str_replace('\\', '/', $out); // Can be before the loop because only 1 char is replaced. No risk to get it after other replacements.
1349 $out = str_replace(array(':', ';', '@', "\t", ' '), '', $out); // Can be before the loop because only 1 char is replaced. No risk to retrieve it after other replacements.
1350 do {
1351 $oldstringtoclean = $out;
1352 $out = str_ireplace(array('javascript', 'vbscript', '&colon', '&#'), '', $out);
1353 $out = preg_replace(array('/^[^\?]*%/'), '', $out); // We remove any % chars before the ?. Example in url: '/product/stock/card.php?action=create&backtopage=%2Fdolibarr_dev%2Fhtdocs%2Fpro%25duct%2Fcard.php%3Fid%3Dabc'
1354 $out = preg_replace(array('/^[a-z]*\/\s*\/+/i'), '', $out); // We remove schema*// to remove external URL
1355 } while ($oldstringtoclean != $out);
1356 }
1357
1358 // Code for search criteria persistence.
1359 // Save data into session if key start with 'search_'
1360 if (empty($method) || $method == 3 || $method == 4) {
1361 if (preg_match('/^search_/', $paramname) || in_array($paramname, array('sortorder', 'sortfield'))) {
1362 //var_dump($paramname.' - '.$out.' '.$user->default_values[$relativepathstring]['filters'][$paramname]);
1363
1364 // We save search key only if $out not empty that means:
1365 // - posted value not empty, or
1366 // - if posted value is empty and a default value exists that is not empty (it means we did a filter to an empty value when default was not).
1367
1368 if ($out != '' && isset($user)) { // $out = '0' or 'abc', it is a search criteria to keep
1369 $user->lastsearch_values_tmp[$relativepathstring][$paramname] = $out;
1370 }
1371 }
1372 }
1373
1374 if ($paramname == 'hashp' && $out == 'shared') {
1375 $out = ''; // We refuse to have hashp=shared as a parameter
1376 }
1377
1378 return $out;
1379}
1380
1390function sanitizeVal($out = '', $check = 'alphanohtml', $filter = null, $options = null)
1391{
1392 // TODO : use class "Validate" to perform tests (and add missing tests) if needed for factorize
1393 // Check is done after replacement
1394 if ($out === null) {
1395 $out = '';
1396 }
1397 switch ($check) {
1398 case 'none':
1399 case 'password':
1400 break;
1401 case 'int': // Check param is a numeric value (integer but also float or hexadecimal)
1402 if (!is_numeric($out)) {
1403 $out = '';
1404 }
1405 break;
1406 case 'intcomma':
1407 if (is_array($out)) {
1408 $out = implode(',', $out);
1409 }
1410 if (preg_match('/[^0-9,-]+/i', $out)) {
1411 $out = '';
1412 }
1413 break;
1414 case 'san_alpha':
1415 dol_syslog("Use of parameter value 'san_alpha' in GETPOST is deprecated. Use 'alphanohtml', 'aZ09comma', ...", LOG_WARNING);
1416 $out = filter_var($out, FILTER_SANITIZE_STRING);
1417 break;
1418 case 'email':
1419 $out = filter_var($out, FILTER_SANITIZE_EMAIL);
1420 break;
1421 case 'url':
1422 //$out = filter_var($out, FILTER_SANITIZE_URL); // Not reliable, replaced with FILTER_VALIDATE_URL
1423 $out = preg_replace('/[^:\/\[\]a-z0-9@\$\'\*\~\.\-_,;\?\!=%&+#]+/i', '', $out);
1424 // TODO Allow ( ) but only into password of https://login:password@domain...
1425 break;
1426 case 'aZ':
1427 if (!is_array($out)) {
1428 $out = trim($out);
1429 if (preg_match('/[^a-z]+/i', $out)) {
1430 $out = '';
1431 }
1432 }
1433 break;
1434 case 'aZ09':
1435 if (!is_array($out)) {
1436 $out = trim($out);
1437 if (preg_match('/[^a-z0-9_\-\.]+/i', $out)) {
1438 $out = '';
1439 }
1440 }
1441 break;
1442 case 'aZ09arobase': // great to sanitize $objecttype parameter
1443 if (!is_array($out)) {
1444 $out = trim($out);
1445 if (preg_match('/[^a-z0-9_\-\.@]+/i', $out)) {
1446 $out = '';
1447 }
1448 }
1449 break;
1450 case 'aZ09comma': // great to sanitize $sortfield or $sortorder params that can be 't.abc,t.def_gh'
1451 if (!is_array($out)) {
1452 $out = trim($out);
1453 if (preg_match('/[^a-z0-9_\-\.,]+/i', $out)) {
1454 $out = '';
1455 }
1456 }
1457 break;
1458 case 'alpha': // No html and no ../ and "
1459 case 'alphanohtml': // Recommended for most scalar parameters and search parameters. Not valid for json string.
1460 if (!is_array($out)) {
1461 $out = trim($out);
1462 do {
1463 $oldstringtoclean = $out;
1464 // Remove html tags
1465 $out = dol_string_nohtmltag($out, 0);
1466 // Refuse octal syntax \999, hexa syntax \x999 and unicode syntax \u{999} by replacing the \ into / (so if it is a \ for a windows path, it is still ok).
1467 $out = preg_replace('/\\\‍([0-9xu])/', '/\1', $out);
1468 // Remove also other dangerous string sequences
1469 // '../' or '..\' is dangerous because it allows dir transversals
1470 // '&#38', '&#0000038', '&#x26'... is a the char '&' alone but there is no reason to accept such way to encode input char
1471 // '"' = '&#34' = '&#0000034' = '&#x22' is dangerous because param in url can close the href= or src= and add javascript functions.
1472 // '&#47', '&#0000047', '&#x2F' is the char '/' but there is no reason to accept such way to encode this input char
1473 // '&#92' = '&#0000092' = '&#x5C' is the char '\' but there is no reason to accept such way to encode this input char
1474 $out = str_ireplace(array('../', '..\\', '&#38', '&#0000038', '&#x26', '&quot', '"', '&#34', '&#0000034', '&#x22', '&#47', '&#0000047', '&#x2F', '&#92', '&#0000092', '&#x5C'), '', $out);
1475 } while ($oldstringtoclean != $out);
1476 // keep lines feed
1477 }
1478 break;
1479 case 'alphawithlgt': // No " and no ../ but we keep balanced < > tags with no special chars inside. Can be used for email string like "Name <email@domain.com>". Less secured than 'alphanohtml'
1480 if (!is_array($out)) {
1481 $out = trim($out);
1482 do {
1483 $oldstringtoclean = $out;
1484 // Decode html entities
1485 $out = dol_html_entity_decode($out, ENT_COMPAT | ENT_HTML5, 'UTF-8');
1486 // Refuse octal syntax \999, hexa syntax \x999 and unicode syntax \u{999} by replacing the \ into / (so if it is a \ for a windows path, it is still ok).
1487 $out = preg_replace('/\\\‍([0-9xu])/', '/\1', $out);
1488 // Remove also other dangerous string sequences
1489 // '../' or '..\' is dangerous because it allows dir transversals
1490 // '&#38', '&#0000038', '&#x26'... is a the char '&' alone but there is no reason to accept such way to encode input char
1491 // '"' = '&#34' = '&#0000034' = '&#x22' is dangerous because param in url can close the href= or src= and add javascript functions.
1492 // '&#47', '&#0000047', '&#x2F' is the char '/' but there is no reason to accept such way to encode this input char
1493 // '&#92' = '&#0000092' = '&#x5C' is the char '\' but there is no reason to accept such way to encode this input char
1494 $out = str_ireplace(array('../', '..\\', '&#38', '&#0000038', '&#x26', '&quot', '"', '&#34', '&#0000034', '&#x22', '&#47', '&#0000047', '&#x2F', '&#92', '&#0000092', '&#x5C'), '', $out);
1495 } while ($oldstringtoclean != $out);
1496 }
1497 break;
1498 case 'nohtml': // No html. Valid for JSON strings.
1499 $out = dol_string_nohtmltag($out, 0);
1500 break;
1501 case 'restricthtmlnolink':
1502 case 'restricthtml': // Recommended for most html textarea
1503 case 'restricthtmlallowclass':
1504 case 'restricthtmlallowiframe':
1505 case 'restricthtmlallowlinkscript': // Allow link and script tag for head section.
1506 case 'restricthtmlallowunvalid':
1507 $out = dol_htmlwithnojs($out, 1, $check);
1508 break;
1509 case 'custom':
1510 if (!empty($out)) {
1511 if (empty($filter)) {
1512 return 'BadParameterForGETPOST - Param 3 of sanitizeVal()';
1513 }
1514 if (is_null($options)) {
1515 $options = 0;
1516 }
1517 $out = filter_var($out, $filter, $options);
1518 }
1519 break;
1520 default:
1521 dol_syslog("Error, you call sanitizeVal() with a bad value for the check type. Data will be sanitized with alphanohtml.", LOG_ERR);
1522 $out = GETPOST($out, 'alphanohtml');
1523 break;
1524 }
1525
1526 return $out;
1527}
1528
1537function dolSetCookie(string $cookiename, string $cookievalue, int $expire = -1)
1538{
1539 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/securitycore.lib.php';
1540
1541 global $dolibarr_main_force_https;
1542
1543 if ($expire == -1) {
1544 $expire = (time() + (86400 * 354)); // keep cookie 1 year.
1545 }
1546
1547 if (PHP_VERSION_ID < 70300) {
1548 setcookie($cookiename, empty($cookievalue) ? '' : $cookievalue, empty($cookievalue) ? 0 : $expire, '/', '', !(empty($dolibarr_main_force_https) && isHTTPS() === false), true); // add tag httponly
1549 } else {
1550 // Only available for php >= 7.3
1551 $cookieparams = array(
1552 'expires' => empty($cookievalue) ? 0 : $expire,
1553 'path' => '/',
1554 //'domain' => '.mywebsite.com', // the dot at the beginning allows compatibility with subdomains
1555 'secure' => !(empty($dolibarr_main_force_https) && isHTTPS() === false),
1556 'httponly' => true,
1557 'samesite' => 'Lax' // None || Lax || Strict
1558 );
1559 setcookie($cookiename, empty($cookievalue) ? '' : $cookievalue, $cookieparams);
1560 }
1561 if (empty($cookievalue)) {
1562 unset($_COOKIE[$cookiename]);
1563 }
1564}
1565
1566if (!function_exists('dol_getprefix')) {
1577 function dol_getprefix($mode = '')
1578 {
1579 // If prefix is for email (we need to have $conf already loaded for this case)
1580 if ($mode == 'email') {
1581 global $conf;
1582
1583 if (getDolGlobalString('MAIL_PREFIX_FOR_EMAIL_ID')) { // If MAIL_PREFIX_FOR_EMAIL_ID is set
1584 if (getDolGlobalString('MAIL_PREFIX_FOR_EMAIL_ID') != 'SERVER_NAME') {
1585 return getDolGlobalString('MAIL_PREFIX_FOR_EMAIL_ID');
1586 } elseif (isset($_SERVER["SERVER_NAME"])) { // If MAIL_PREFIX_FOR_EMAIL_ID is set to 'SERVER_NAME'
1587 return $_SERVER["SERVER_NAME"];
1588 }
1589 }
1590
1591 // The recommended value if MAIL_PREFIX_FOR_EMAIL_ID is not defined (may be not defined for old versions)
1592 if (!empty($conf->file->instance_unique_id)) {
1593 return sha1('dolibarr' . $conf->file->instance_unique_id);
1594 }
1595
1596 // For backward compatibility when instance_unique_id is not set
1597 return sha1(DOL_DOCUMENT_ROOT . DOL_URL_ROOT);
1598 }
1599
1600 // If prefix is for session (no need to have $conf loaded)
1601 global $dolibarr_main_instance_unique_id, $dolibarr_main_cookie_cryptkey; // This is loaded by filefunc.inc.php
1602 $tmp_instance_unique_id = empty($dolibarr_main_instance_unique_id) ? (empty($dolibarr_main_cookie_cryptkey) ? '' : $dolibarr_main_cookie_cryptkey) : $dolibarr_main_instance_unique_id; // Unique id of instance
1603
1604 // The recommended value (may be not defined for old versions)
1605 if (!empty($tmp_instance_unique_id)) {
1606 return sha1('dolibarr' . $tmp_instance_unique_id);
1607 }
1608
1609 // For backward compatibility when instance_unique_id is not set
1610 if (isset($_SERVER["SERVER_NAME"]) && isset($_SERVER["DOCUMENT_ROOT"])) {
1611 return sha1($_SERVER["SERVER_NAME"] . $_SERVER["DOCUMENT_ROOT"] . DOL_DOCUMENT_ROOT . DOL_URL_ROOT);
1612 } else {
1613 return sha1(DOL_DOCUMENT_ROOT . DOL_URL_ROOT);
1614 }
1615 }
1616}
1617
1628function dol_include_once($relpath, $classname = '')
1629{
1630 global $conf, $langs, $user, $mysoc; // Do not remove this. They must be defined for files we make "include". Other globals var must be retrieved with $GLOBALS['var']
1631
1632 if (strpos($relpath, '..') !== false) {
1633 // Found a not valid path
1634 dol_syslog('functions::dol_include_once Tried to load a file with a path including a forbidden sequence ".." : ' . $relpath, LOG_WARNING);
1635 return false;
1636 }
1637 if (!preg_match('/\.php$/', $relpath)) {
1638 // Found a not valid path
1639 dol_syslog('functions::dol_include_once Tried to load a file that is not a PHP file : ' . $relpath, LOG_WARNING);
1640 return false;
1641 }
1642
1643 $fullpath = dol_buildpath($relpath);
1644
1645 if (!file_exists($fullpath)) {
1646 dol_syslog('functions::dol_include_once Tried to load unexisting file: ' . $relpath, LOG_WARNING);
1647 return false;
1648 }
1649 if (!empty($classname) && !class_exists($classname)) {
1650 return include $fullpath;
1651 } else {
1652 return include_once $fullpath;
1653 }
1654}
1655
1656
1670function dol_buildpath($path, $type = 0, $returnemptyifnotfound = 0)
1671{
1672 global $conf;
1673
1674 $path = preg_replace('/^\//', '', $path);
1675
1676 if (empty($type)) { // For a filesystem path
1677 $res = DOL_DOCUMENT_ROOT . '/' . $path; // Standard default path
1678 if (is_array($conf->file->dol_document_root)) {
1679 foreach ($conf->file->dol_document_root as $key => $dirroot) { // ex: array("main"=>"/home/main/htdocs", "alt0"=>"/home/dirmod/htdocs", ...)
1680 if ($key == 'main') {
1681 continue;
1682 }
1683 // if (@file_exists($dirroot.'/'.$path)) {
1684 if (@file_exists($dirroot . '/' . $path)) { // avoid [php:warn]
1685 if ($key != 'main' && preg_match('/^core\//', $path)) { // When searching into an alternative custom path, we don't want path like 'core/...' because path should be 'modulename/core/...'
1686 continue;
1687 }
1688 $res = $dirroot . '/' . $path;
1689 return $res;
1690 }
1691 }
1692 }
1693 if ($returnemptyifnotfound) {
1694 // Not found into alternate dir
1695 if ($returnemptyifnotfound == 1 || !file_exists($res)) {
1696 return '';
1697 }
1698 }
1699 } else {
1700 // For an url path
1701 // We try to get local path of file on filesystem from url
1702 // Note that trying to know if a file on disk exist by forging path on disk from url
1703 // works only for some web server and some setup. This is bugged when
1704 // using proxy, rewriting, virtual path, etc...
1705 $res = '';
1706 if ($type == 1) {
1707 $res = DOL_URL_ROOT . '/' . $path; // Standard value
1708 }
1709 if ($type == 2) {
1710 $res = DOL_MAIN_URL_ROOT . '/' . $path; // Standard value
1711 }
1712 if ($type == 3) {
1713 $res = DOL_URL_ROOT . '/' . $path;
1714 }
1715
1716 foreach ($conf->file->dol_document_root as $key => $dirroot) { // ex: array(["main"]=>"/home/main/htdocs", ["alt0"]=>"/home/dirmod/htdocs", ...)
1717 if ($key == 'main') {
1718 if ($type == 3) {
1719 /*global $dolibarr_main_url_root;*/
1720
1721 // Define $urlwithroot
1722 $urlwithouturlroot = preg_replace('/' . preg_quote(DOL_URL_ROOT, '/') . '$/i', '', trim($conf->file->dol_main_url_root));
1723 $urlwithroot = $urlwithouturlroot . DOL_URL_ROOT; // This is to use external domain name found into config file
1724 //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current
1725
1726 $res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : $urlwithroot) . '/' . $path; // Test on start with http is for old conf syntax
1727 }
1728 continue;
1729 }
1730 $regs = array();
1731 preg_match('/^([^\?]+(\.css\.php|\.css|\.js\.php|\.js|\.png|\.jpg|\.php)?)/i', $path, $regs); // Take part before '?'
1732 if (!empty($regs[1])) {
1733 //print $key.'-'.$dirroot.'/'.$path.'-'.$conf->file->dol_url_root[$type].'<br>'."\n";
1734 //if (file_exists($dirroot.'/'.$regs[1])) {
1735 if (@file_exists($dirroot . '/' . $regs[1])) { // avoid [php:warn]
1736 if ($type == 1) {
1737 $res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : DOL_URL_ROOT) . $conf->file->dol_url_root[$key] . '/' . $path;
1738 } elseif ($type == 2) {
1739 $res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : DOL_MAIN_URL_ROOT) . $conf->file->dol_url_root[$key] . '/' . $path;
1740 } elseif ($type == 3) {
1741 /*global $dolibarr_main_url_root;*/
1742
1743 // Define $urlwithroot
1744 $urlwithouturlroot = preg_replace('/' . preg_quote(DOL_URL_ROOT, '/') . '$/i', '', trim($conf->file->dol_main_url_root));
1745 $urlwithroot = $urlwithouturlroot . DOL_URL_ROOT; // This is to use external domain name found into config file
1746 //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current
1747
1748 $res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : $urlwithroot) . $conf->file->dol_url_root[$key] . '/' . $path; // Test on start with http is for old conf syntax
1749 }
1750 break;
1751 }
1752 }
1753 }
1754 }
1755
1756 return $res;
1757}
1758
1768function dolBuildUrl($url, $params = [], $addtoken = false, $anchor = '')
1769{
1770 global $db, $hookmanager;
1771
1772 if (!is_object($hookmanager)) {
1773 include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
1774 $hookmanager = new HookManager($db);
1775 }
1776 if ((!isset($params['mainmenu']) || empty($params['mainmenu'])) && GETPOSTISSET('mainmenu')) {
1777 $params = array_merge($params, ['mainmenu' => (GETPOST('mainmenu', 'restricthtml'))]);
1778 }
1779 if ((!isset($params['leftmenu'])/* || empty($params['leftmenu']) */) && GETPOSTISSET('leftmenu')) { // do not fill leftmenu if we have leftmenu=
1780 $params = array_merge($params, ['leftmenu' => (GETPOST('leftmenu', 'restricthtml'))]);
1781 }
1782 $parameters = [
1783 'path' => &$url,
1784 'params' => &$params,
1785 'addtoken' => &$addtoken,
1786 ];
1787 $hookmanager->executeHooks('buildurl', $parameters);
1788 if ($addtoken) {
1789 $params = array_merge($params, ['token' => newToken()]);
1790 }
1791 if ($params) {
1792 $url .= '?' . http_build_query($params);
1793 }
1794 if ($anchor) {
1795 $url .= '#' . preg_replace('/[^a-z]/i', '', $anchor);
1796 }
1797
1798 return $url;
1799}
1800
1811function dol_get_object_properties($obj, $properties = [])
1812{
1813 // Get real properties using get_object_vars() if $properties is empty
1814 if (empty($properties)) {
1815 return get_object_vars($obj);
1816 }
1817
1818 $existingProperties = [];
1819 $realProperties = get_object_vars($obj);
1820
1821 // Get the real or magic property values
1822 foreach ($properties as $property) {
1823 if (array_key_exists($property, $realProperties)) {
1824 // Real property, add the value
1825 $existingProperties[$property] = $obj->{$property};
1826 } elseif (property_exists($obj, $property)) {
1827 // Magic property
1828 $existingProperties[$property] = $obj->{$property};
1829 }
1830 }
1831
1832 return $existingProperties;
1833}
1834
1835
1853function dol_clone($srcobject, $native = 2)
1854{
1855 if ($native == 0) {
1856 // deprecated method, use the method with native = 2 instead
1857 dol_syslog("Warning, call to dol_clone() with the deprecated parameter native=0, use 2 instead", LOG_WARNING);
1858
1859 $tmpsavdb = null;
1860 if (isset($srcobject->db) && isset($srcobject->db->db) && is_object($srcobject->db->db) && get_class($srcobject->db->db) == 'PgSql\Connection') {
1861 $tmpsavdb = $srcobject->db;
1862 unset($srcobject->db); // Such property can not be serialized with pgsl (when object->db->db = 'PgSql\Connection')
1863 }
1864
1865 $myclone = unserialize(serialize($srcobject)); // serialize then unserialize is a hack to be sure to have a new object for all fields
1866
1867 if (!empty($tmpsavdb)) {
1868 $srcobject->db = $tmpsavdb;
1869 }
1870 } elseif ($native == 2) {
1871 // recommended method to have a full secured isolated cloned object
1872 $myclone = new stdClass();
1873 $tmparray = get_object_vars($srcobject); // return only public properties
1874
1875 if (is_array($tmparray)) {
1876 foreach ($tmparray as $propertykey => $propertyval) {
1877 if (is_scalar($propertyval) || is_array($propertyval)) {
1878 $myclone->$propertykey = $propertyval;
1879 }
1880 }
1881 }
1882 } else {
1883 $myclone = clone $srcobject; // PHP clone is a shallow copy only, not a real clone, so properties of references will keep the reference (referring to the same target/variable)
1884 }
1885
1886 return $myclone;
1887}
1888
1889
1898function dol_clone_in_array($srcobject, $startlevel = 0)
1899{
1900 if (is_object($srcobject)) {
1901 $srcobject = get_object_vars($srcobject); // exclude private/protected properties
1902 }
1903
1904 if (is_array($srcobject)) {
1905 $result = [];
1906 foreach ($srcobject as $key => $value) {
1907 if (in_array($key, array('db', 'fields', 'error', 'errorhidden', 'errors', 'oldcopy', 'linkedObjects', 'linked_objects'))) {
1908 continue;
1909 }
1910 $result[$key] = dol_clone_in_array($value, $startlevel + 1);
1911 }
1912 return $result;
1913 }
1914
1915 return $srcobject;
1916}
1917
1918
1928function dol_size($size, $type = '')
1929{
1930 global $conf;
1931 if (empty($conf->dol_optimize_smallscreen)) {
1932 return $size;
1933 }
1934 if ($type == 'width' && $size > 250) {
1935 return 250;
1936 } else {
1937 return 10;
1938 }
1939}
1940
1941
1955function dol_sanitizeFileName($str, $newstr = '_', $unaccent = 1, $includequotes = 0, $allowdash = 0)
1956{
1957 $str = (string) $str;
1958
1959 // List of special chars for filenames in windows are defined on page https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
1960 // Char '>' '<' '|' '$' and ';' are special chars for shells.
1961 // Char '/' and '\' are file delimiters.
1962 // Chars '--' can be used into filename to inject special parameters like --use-compress-program to make command with file as parameter making remote execution of command
1963 $filesystem_forbidden_chars = array('<', '>', '/', '\\', '?', '*', '|', '"', ':', '°', '$', ';', '`');
1964 if ($includequotes) {
1965 $filesystem_forbidden_chars[] = "'";
1966 }
1967 $tmp = dol_string_nospecial($unaccent ? dol_string_unaccent($str) : $str, $newstr, $filesystem_forbidden_chars);
1968 $tmp = preg_replace('/\-\-+/', '_', $tmp);
1969 if (empty($allowdash)) {
1970 $tmp = preg_replace('/\s+\-([^\s])/', ' _$1', $tmp);
1971 $tmp = preg_replace('/\s+\-$/', '', $tmp);
1972 }
1973 $tmp = str_replace('..', '', $tmp);
1974 $tmp = str_replace('~', $newstr, $tmp);
1975 $tmp = preg_replace('/\s{2,}/', ' ', $tmp);
1976
1977 return $tmp;
1978}
1979
1980
1993function dol_sanitizePathName($str, $newstr = '_', $unaccent = 0, $allowdash = 0)
1994{
1995 // List of special chars for filenames in windows are defined on page https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
1996 // Char '>' '<' '|' '$' ';' and '`' are special chars for shells.
1997 // Char '?' and '*' are for wild card chars.
1998 // Char '"' is dangerous.
1999 // Char '°' is just not expected.
2000 // Chars '-' and '--' can be used into filename to inject special parameters like --use-compress-program to make command with file as parameter making remote execution of command
2001 // Chars '--' and '~' can be used for path transversal
2002 $filesystem_forbidden_chars = array('<', '>', '?', '*', '|', '"', '°', '$', ';', '`');
2003
2004 $tmp = $str;
2005 if ($unaccent) {
2006 $tmp = dol_string_unaccent($tmp);
2007 }
2008 $tmp = dol_string_nospecial($tmp, $newstr, $filesystem_forbidden_chars);
2009 $tmp = preg_replace('/\-\-+/', $newstr, $tmp);
2010 if (empty($allowdash)) {
2011 $tmp = preg_replace('/\s+\-([^\s])/', ' '.$newstr.'$1', $tmp);
2012 $tmp = preg_replace('/\s+\-$/', '', $tmp);
2013 }
2014 $tmp = str_replace('..', $newstr, $tmp);
2015 $tmp = str_replace('~', $newstr, $tmp);
2016 $tmp = preg_replace('/\s{2,}/', ' ', $tmp);
2017
2018 return $tmp;
2019}
2020
2028function dol_sanitizeUrl($stringtoclean, $type = 1)
2029{
2030 // We clean string because some hacks try to obfuscate evil strings by inserting non printable chars. Example: 'java(ascci09)scr(ascii00)ipt' is processed like 'javascript' (whatever is place of evil ascii char)
2031 // We should use dol_string_nounprintableascii but function may not be yet loaded/available
2032 $stringtoclean = preg_replace('/[\x00-\x1F\x7F]/u', '', $stringtoclean); // /u operator makes UTF8 valid characters being ignored so are not included into the replace
2033 // We clean html comments because some hacks try to obfuscate evil strings by inserting HTML comments. Example: on<!-- -->error=alert(1)
2034 $stringtoclean = preg_replace('/<!--[^>]*-->/', '', $stringtoclean);
2035
2036 $stringtoclean = str_replace('\\', '/', $stringtoclean);
2037 if ($type == 1) {
2038 // removing : should disable links to external url like http:aaa)
2039 // removing ';' should disable "named" html entities encode into an url (we should not have this into an url)
2040 $stringtoclean = str_replace(array(':', ';', '@'), '', $stringtoclean);
2041 }
2042
2043 do {
2044 $oldstringtoclean = $stringtoclean;
2045 // removing '&colon' should disable links to external url like http:aaa)
2046 // removing '&#' should disable "numeric" html entities encode into an url (we should not have this into an url)
2047 $stringtoclean = str_ireplace(array('javascript', 'vbscript', '&colon', '&#'), '', $stringtoclean);
2048 } while ($oldstringtoclean != $stringtoclean);
2049
2050 if ($type == 1) {
2051 // removing '//' should disable links to external url like //aaa or http//)
2052 $stringtoclean = preg_replace(array('/^[a-z]*\/\/+/i'), '', $stringtoclean);
2053 }
2054
2055 return $stringtoclean;
2056}
2057
2064function dol_sanitizeEmail($stringtoclean)
2065{
2066 do {
2067 $oldstringtoclean = $stringtoclean;
2068 $stringtoclean = str_ireplace(array('"', ':', '[', ']', "\n", "\r", '\\', '\/'), '', $stringtoclean);
2069 } while ($oldstringtoclean != $stringtoclean);
2070
2071 return $stringtoclean;
2072}
2073
2082function dol_sanitizeKeyCode($str)
2083{
2084 return preg_replace('/[^\w]+/', '', $str);
2085}
2086
2087
2096function dol_string_unaccent($str)
2097{
2098 if (is_null($str)) {
2099 return '';
2100 }
2101
2102 if (utf8_check($str)) {
2103 if (extension_loaded('intl') && getDolGlobalString('MAIN_UNACCENT_USE_TRANSLITERATOR')) {
2104 $transliterator = Transliterator::createFromRules(':: Any-Latin; :: Latin-ASCII; :: NFD; :: [:Nonspacing Mark:] Remove; :: NFC;', Transliterator::FORWARD);
2105 return $transliterator->transliterate($str);
2106 }
2107 // See http://www.utf8-chartable.de/
2108 $string = rawurlencode($str);
2109 $replacements = array(
2110 '%C3%80' => 'A', '%C3%81' => 'A', '%C3%82' => 'A', '%C3%83' => 'A', '%C3%84' => 'A', '%C3%85' => 'A',
2111 '%C3%87' => 'C',
2112 '%C3%88' => 'E', '%C3%89' => 'E', '%C3%8A' => 'E', '%C3%8B' => 'E',
2113 '%C3%8C' => 'I', '%C3%8D' => 'I', '%C3%8E' => 'I', '%C3%8F' => 'I',
2114 '%C3%91' => 'N',
2115 '%C3%92' => 'O', '%C3%93' => 'O', '%C3%94' => 'O', '%C3%95' => 'O', '%C3%96' => 'O', '%C5%90' => 'O',
2116 '%C5%A0' => 'S',
2117 '%C3%99' => 'U', '%C3%9A' => 'U', '%C3%9B' => 'U', '%C3%9C' => 'U', '%C5%B0' => 'U',
2118 '%C3%9D' => 'Y', '%C5%B8' => 'y',
2119 '%C3%A0' => 'a', '%C3%A1' => 'a', '%C3%A2' => 'a', '%C3%A3' => 'a', '%C3%A4' => 'a', '%C3%A5' => 'a',
2120 '%C3%A7' => 'c',
2121 '%C3%A8' => 'e', '%C3%A9' => 'e', '%C3%AA' => 'e', '%C3%AB' => 'e',
2122 '%C3%AC' => 'i', '%C3%AD' => 'i', '%C3%AE' => 'i', '%C3%AF' => 'i',
2123 '%C3%B1' => 'n',
2124 '%C3%B2' => 'o', '%C3%B3' => 'o', '%C3%B4' => 'o', '%C3%B5' => 'o', '%C3%B6' => 'o', '%C5%91' => 'o',
2125 '%C5%A1' => 's',
2126 '%C3%B9' => 'u', '%C3%BA' => 'u', '%C3%BB' => 'u', '%C3%BC' => 'u', '%C5%B1' => 'u',
2127 '%C3%BD' => 'y', '%C3%BF' => 'y',
2128 '%CC%80' => '',
2129 '%CC%81' => '',
2130 '%CC%82' => '',
2131 '%CC%83' => '',
2132 '%CC%84' => '',
2133 '%CC%85' => '',
2134 '%CC%86' => '',
2135 '%CC%87' => '',
2136 '%CC%88' => '',
2137 '%CC%89' => '',
2138 '%CC%8A' => '',
2139 '%CC%8B' => '',
2140 '%CC%8C' => '',
2141 '%CC%8D' => '',
2142 '%CC%8E' => '',
2143 '%CC%8F' => '',
2144 '%CC%90' => '',
2145 '%CC%91' => '',
2146 '%CC%A7' => ''
2147 );
2148 $string = strtr($string, $replacements);
2149 return rawurldecode($string);
2150 } else {
2151 // See http://www.ascii-code.com/
2152 $string = strtr(
2153 $str,
2154 "\xC0\xC1\xC2\xC3\xC4\xC5\xC7
2155 \xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1
2156 \xD2\xD3\xD4\xD5\xD8\xD9\xDA\xDB\xDD
2157 \xE0\xE1\xE2\xE3\xE4\xE5\xE7\xE8\xE9\xEA\xEB
2158 \xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF8
2159 \xF9\xFA\xFB\xFC\xFD\xFF",
2160 "AAAAAAC
2161 EEEEIIIIDN
2162 OOOOOUUUY
2163 aaaaaaceeee
2164 iiiidnooooo
2165 uuuuyy"
2166 );
2167 $string = strtr($string, array("\xC4" => "Ae", "\xC6" => "AE", "\xD6" => "Oe", "\xDC" => "Ue", "\xDE" => "TH", "\xDF" => "ss", "\xE4" => "ae", "\xE6" => "ae", "\xF6" => "oe", "\xFC" => "ue", "\xFE" => "th"));
2168 return $string;
2169 }
2170}
2171
2185function dol_string_nospecial($str, $newstr = '_', $badcharstoreplace = '', $badcharstoremove = '', $keepspaces = 0)
2186{
2187 $forbidden_chars_to_replace = array("'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ",", ";", "=", '°', '$', ';'); // more complete than dol_sanitizeFileName
2188 if (empty($keepspaces)) {
2189 $forbidden_chars_to_replace[] = " ";
2190 }
2191 $forbidden_chars_to_remove = array();
2192 //$forbidden_chars_to_remove=array("(",")");
2193
2194 if (is_array($badcharstoreplace)) {
2195 $forbidden_chars_to_replace = $badcharstoreplace;
2196 }
2197 if (is_array($badcharstoremove)) {
2198 $forbidden_chars_to_remove = $badcharstoremove;
2199 }
2200
2201 // @phan-suppress-next-line PhanPluginSuspiciousParamOrderInternal
2202 return str_replace($forbidden_chars_to_replace, $newstr, str_replace($forbidden_chars_to_remove, "", $str));
2203}
2204
2205
2219function dol_string_nounprintableascii($str, $removetabcrlf = 1)
2220{
2221 if ($removetabcrlf) {
2222 return preg_replace('/[\x00-\x1F\x7F]/u', '', $str); // /u operator makes UTF8 valid characters being ignored so are not included into the replace
2223 } else {
2224 return preg_replace('/[\x00-\x08\x11-\x12\x14-\x1F\x7F]/u', '', $str); // /u operator should make UTF8 valid characters being ignored so are not included into the replace
2225 }
2226}
2227
2234function dolSlugify($stringtoslugify)
2235{
2236 $slug = dol_string_unaccent($stringtoslugify);
2237
2238 // Convert special characters to their ASCII equivalents
2239 if (function_exists('iconv')) {
2240 $slug = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $slug);
2241 }
2242
2243 // Convert to lowercase
2244 $slug = strtolower($slug);
2245
2246 // Replace non-alphanumeric characters with hyphens
2247 $slug = preg_replace('/[^a-z0-9]+/', '-', $slug);
2248
2249 // Remove leading and trailing hyphens
2250 $slug = trim($slug, '-');
2251
2252 return $slug;
2253}
2254
2263function dol_escape_js($stringtoescape, $mode = 0, $noescapebackslashn = 0)
2264{
2265 if (is_null($stringtoescape)) {
2266 return '';
2267 }
2268
2269 // escape quotes and backslashes, newlines, etc.
2270 $substitjs = array("&#039;" => "\\'", "\r" => '\\r');
2271 //$substitjs['</']='<\/'; // We removed this. Should be useless.
2272 if (empty($noescapebackslashn)) {
2273 $substitjs["\n"] = '\\n';
2274 $substitjs['\\'] = '\\\\';
2275 }
2276 if (empty($mode)) {
2277 $substitjs["'"] = "\\'";
2278 $substitjs['"'] = "\\'";
2279 } elseif ($mode == 1) {
2280 $substitjs["'"] = "\\'";
2281 } elseif ($mode == 2) {
2282 $substitjs['"'] = '\\"';
2283 } elseif ($mode == 3) {
2284 $substitjs["'"] = "\\'";
2285 $substitjs['"'] = "\\\"";
2286 }
2287 return strtr((string) $stringtoescape, $substitjs);
2288}
2289
2299function dol_escape_uri($stringtoescape)
2300{
2301 return rawurlencode($stringtoescape);
2302}
2303
2310function dol_escape_json($stringtoescape)
2311{
2312 return str_replace('"', '\"', $stringtoescape);
2313}
2314
2322function dol_escape_php($stringtoescape, $stringforquotes = 2)
2323{
2324 if (is_null($stringtoescape)) {
2325 return '';
2326 }
2327
2328 if ($stringforquotes == 2) {
2329 return str_replace('"', "'", $stringtoescape);
2330 } elseif ($stringforquotes == 1) {
2331 // We remove the \ char.
2332 // If we allow the \ char, we can have $stringtoescape =
2333 // abc\';phpcodedanger; so the escapement will become
2334 // abc\\';phpcodedanger; and injecting this into
2335 // $a='...' will give $ac='abc\\';phpcodedanger;
2336 $stringtoescape = str_replace('\\', '', $stringtoescape);
2337 return str_replace("'", "\'", str_replace('"', "'", $stringtoescape));
2338 }
2339
2340 return 'Bad parameter for stringforquotes in dol_escape_php';
2341}
2342
2349function dol_escape_all($stringtoescape)
2350{
2351 return preg_replace('/[^a-z0-9_]/i', '', $stringtoescape);
2352}
2353
2360function dol_escape_xml($stringtoescape)
2361{
2362 return $stringtoescape;
2363}
2364
2374function dolPrintLabel($s, $escapeonlyhtmltags = 0)
2375{
2376 return dol_escape_htmltag(dol_string_nohtmltag($s, 1, 'UTF-8', 0, 0), 0, 0, '', $escapeonlyhtmltags, 1);
2377}
2378
2387function dolPrintText($s)
2388{
2389 return dol_escape_htmltag(dol_string_nohtmltag($s, 2, 'UTF-8', 0, 0), 0, 1, '', 0, 1);
2390}
2391
2403function dolPrintHTML($s, $allowiframe = 0, $moreallowedtags = array())
2404{
2405 // If text is already HTML, we want to escape only dangerous chars else we want to escape all content.
2406 //$isAlreadyHTML = dol_textishtml($s);
2407
2408 // dol_htmlentitiesbr encode all chars except "'" if string is not already HTML, but
2409 // encode only special char like accented chars but not &, <, >, ", ' if already HTML.
2410 $stringWithEntitesForSpecialChar = dol_htmlentitiesbr((string) $s);
2411
2412 $allowedtags = 'common';
2413 if (!empty($moreallowedtags)) {
2414 $allowedtags .= ','.implode(',', $moreallowedtags);
2415 }
2416 return dol_escape_htmltag(dol_htmlwithnojs(dol_string_onlythesehtmltags($stringWithEntitesForSpecialChar, 1, 1, 1, $allowiframe, $allowedtags)), 1, 1, $allowedtags, 0, 1);
2417}
2418
2429function dolPrintHTMLForAttribute($s, $escapeonlyhtmltags = 0, $allowothertags = array())
2430{
2431 $allowedtags = array('br', 'b', 'font', 'hr', 'span');
2432 if (!empty($allowothertags) && is_array($allowothertags)) {
2433 $allowedtags = array_merge($allowedtags, $allowothertags);
2434 }
2435 // The dol_htmlentitiesbr will convert simple text into html, including switching accent into HTML entities
2436 // The dol_escape_htmltag will escape html tags.
2437 if ($escapeonlyhtmltags) {
2438 return dol_escape_htmltag(dol_string_onlythesehtmltags($s, 1, 0, 0, 0, $allowedtags), 1, -1, '', 1, 1);
2439 } else {
2440 return dol_escape_htmltag(dol_string_onlythesehtmltags(dol_htmlentitiesbr($s), 1, 0, 0, 0, $allowedtags), 1, -1, '', 0, 1);
2441 }
2442}
2443
2452function dolPrintHTMLForAttributeUrl($s)
2453{
2454 // The dol_htmlentitiesbr has been removed compared to dolPrintHTMLForAttribute because we know content is a HTML URL string (even if we have no way to detect it automatically)
2455 // The dol_escape_htmltag will escape html chars.
2456 $escapeonlyhtmltags = 1;
2457 return dol_escape_htmltag(dol_string_onlythesehtmltags($s, 1, 1, 1, 0, array()), 0, 0, '', $escapeonlyhtmltags, 1);
2458}
2459
2469function dolPrintHTMLForTextArea($s, $allowiframe = 0)
2470{
2471 return dol_escape_htmltag(dol_htmlwithnojs(dol_string_onlythesehtmltags(dol_htmlentitiesbr($s), 1, 1, 1, $allowiframe)), 1, 1, '', 0, 1);
2472}
2473
2480function dolPrintPassword($s)
2481{
2482 return htmlspecialchars($s, ENT_HTML5, 'UTF-8');
2483}
2484
2485
2502function dol_escape_htmltag($stringtoescape, $keepb = 0, $keepn = 0, $noescapetags = '', $escapeonlyhtmltags = 0, $cleanalsojavascript = 0)
2503{
2504 $reg = array();
2505 if (preg_match('/^common([a-z,]*)/', $noescapetags, $reg)) {
2506 $noescapetags = 'html,body,a,b,em,hr,i,u,ul,ol,li,br,div,img,font,p,span,strong,table,tr,td,th,tbody,h1,h2,h3,h4,h5,h6,h7,h8,h9';
2507 // Add also html5 tags
2508 $noescapetags .= ',header,footer,nav,section,menu,menuitem';
2509 if (!empty($reg[1])) {
2510 $noescapetags .= $reg[1];
2511 }
2512 }
2513 if ($cleanalsojavascript) {
2514 $stringtoescape = dol_string_onlythesehtmltags($stringtoescape, 0, 0, $cleanalsojavascript, 0, array(), 0);
2515 }
2516
2517 // escape quotes and backslashes, newlines, etc.
2518 if ($escapeonlyhtmltags) {
2519 $tmp = htmlspecialchars_decode((string) $stringtoescape, ENT_COMPAT);
2520 } else {
2521 // We make a manipulation by calling the html_entity_decode() to convert content into NON HTML UTF8 string.
2522 // Because content can be or not already HTML.
2523 // For example, this decode &egrave; into its UTF-8 char so string is UTF8 (but numbers entities like &#39; is not decoded).
2524 // In a future, we should not need this
2525
2526 $tmp = (string) $stringtoescape;
2527
2528 // We protect the 6 special entities that we don't want to decode.
2529 $tmp = str_ireplace('&lt', '__DONOTDECODELT', $tmp);
2530 $tmp = str_ireplace('&gt', '__DONOTDECODEGT', $tmp);
2531 $tmp = str_ireplace('&amp', '__DONOTDECODEAMP', $tmp);
2532 $tmp = str_ireplace('&quot', '__DONOTDECODEQUOT', $tmp);
2533 $tmp = str_ireplace('&apos', '__DONOTDECODEAPOS', $tmp);
2534 $tmp = str_ireplace('&#39', '__DONOTDECODE39', $tmp);
2535
2536 $tmp = html_entity_decode((string) $tmp, ENT_COMPAT, 'UTF-8'); // Convert entities into UTF8
2537
2538 // We restore the 6 special entities that we don't want to have been decoded by previous command
2539 $tmp = str_ireplace('__DONOTDECODELT', '&lt', $tmp);
2540 $tmp = str_ireplace('__DONOTDECODEGT', '&gt', $tmp);
2541 $tmp = str_ireplace('__DONOTDECODEAMP', '&amp', $tmp);
2542 $tmp = str_ireplace('__DONOTDECODEQUOT', '&quot', $tmp);
2543 $tmp = str_ireplace('__DONOTDECODEAPOS', '&apos', $tmp);
2544 $tmp = str_ireplace('__DONOTDECODE39', '&#39', $tmp);
2545
2546 $tmp = str_ireplace('&#39;', '__SIMPLEQUOTE__', $tmp); // HTML 4
2547 }
2548 if (!$keepb) {
2549 $tmp = strtr($tmp, array("<b>" => '', '</b>' => '', '<strong>' => '', '</strong>' => ''));
2550 }
2551 if (!$keepn) {
2552 $tmp = strtr($tmp, array("\r" => '\\r', "\n" => '\\n'));
2553 } elseif ($keepn == -1) {
2554 $tmp = strtr($tmp, array("\r" => '', "\n" => ''));
2555 }
2556
2557 if ($escapeonlyhtmltags) {
2558 $tmp = htmlspecialchars($tmp, ENT_COMPAT, 'UTF-8');
2559 return $tmp;
2560 } else {
2561 // Now we protect all the tags we want to keep
2562 $tmparrayoftags = array();
2563 if ($noescapetags) {
2564 $tmparrayoftags = explode(',', $noescapetags);
2565 }
2566
2567 if (count($tmparrayoftags)) {
2568 // Now we will protect tags (defined into $tmparrayoftags) that we want to keep untouched
2569
2570 $reg = array();
2571 // Remove reserved keywords. They are forbidden in a source string
2572 $tmp = str_ireplace(array('__DOUBLEQUOTE', '__BEGINTAGTOREPLACE', '__ENDTAGTOREPLACE', '__BEGINENDTAGTOREPLACE'), '', $tmp);
2573
2574 foreach ($tmparrayoftags as $tagtoreplace) {
2575 // For case of tag without attributes '<abc>', '</abc>', '<abc />', we protect them to avoid transformation by htmlentities() later
2576 $tmp = preg_replace('/<' . preg_quote($tagtoreplace, '/') . '>/', '__BEGINTAGTOREPLACE' . $tagtoreplace . '__', $tmp);
2577 $tmp = str_ireplace('</' . $tagtoreplace . '>', '__ENDTAGTOREPLACE' . $tagtoreplace . '__', $tmp);
2578 $tmp = preg_replace('/<' . preg_quote($tagtoreplace, '/') . ' \/>/', '__BEGINENDTAGTOREPLACE' . $tagtoreplace . '__', $tmp);
2579
2580 // For case of tag with attributes.
2581 // All the occurrences are protected in a single pass: the replacement string contains no '<', so it
2582 // can never build a new tag to protect (a loop replacing one distinct attribute string per round was
2583 // rescanning the whole content for each of them, so the cost was quadratic on large contents).
2584 $tmp = preg_replace_callback(
2585 '/<'.preg_quote($tagtoreplace, '/').'(\s+)([^>]+)>/',
2590 static function ($reg) use ($tagtoreplace) {
2591 // We want to protect the attribute part ... in '<xxx ...>' to avoid transformation by htmlentities() later
2592 $tmpattributes = str_ireplace(array('[', ']'), '_', $reg[2]); // We must never have [ ] inside the attribute string
2593 $tmpattributes = str_ireplace('"', '__DOUBLEQUOTE__', $tmpattributes);
2594 $tmpattributes = preg_replace('/[^a-z0-9_%,\/\?\;\s=&\.\-@:\.#\+]/i', '', $tmpattributes);
2595 //$tmpattributes = preg_replace("/float:\s*(left|right)/", "", $tmpattributes); // Disabled: we must not remove content
2596 return '__BEGINTAGTOREPLACE'.$tagtoreplace.'['.$tmpattributes.']__';
2597 },
2598 $tmp
2599 ) ?? $tmp;
2600 }
2601
2602 $tmp = str_ireplace('&amp', '__ANDNOSEMICOLON__', $tmp);
2603 $tmp = str_ireplace('&quot', '__DOUBLEQUOTENOSEMICOLON__', $tmp);
2604 $tmp = str_ireplace('&lt', '__LESSTHAN__', $tmp);
2605 $tmp = str_ireplace('&gt', '__GREATERTHAN__', $tmp);
2606 }
2607
2608 // Warning: htmlentities encode all special chars that remains (except "'" with ENT_COMPAT).
2609 $result = htmlentities($tmp, ENT_COMPAT, 'UTF-8');
2610
2611 //print $result;
2612
2613 if (count($tmparrayoftags)) {
2614 // Restore protected tags
2615 foreach ($tmparrayoftags as $tagtoreplace) {
2616 $result = str_ireplace('__BEGINTAGTOREPLACE' . $tagtoreplace . '__', '<' . $tagtoreplace . '>', $result);
2617 $result = preg_replace('/__BEGINTAGTOREPLACE' . $tagtoreplace . '\[([^\]]*)\]__/', '<' . $tagtoreplace . ' \1>', $result);
2618 $result = str_ireplace('__ENDTAGTOREPLACE' . $tagtoreplace . '__', '</' . $tagtoreplace . '>', $result);
2619 $result = str_ireplace('__BEGINENDTAGTOREPLACE' . $tagtoreplace . '__', '<' . $tagtoreplace . ' />', $result);
2620 $result = preg_replace('/__BEGINENDTAGTOREPLACE' . $tagtoreplace . '\[([^\]]*)\]__/', '<' . $tagtoreplace . ' \1 />', $result);
2621 }
2622
2623 $result = str_ireplace('__DOUBLEQUOTE__', '"', $result);
2624
2625 $result = str_ireplace('__ANDNOSEMICOLON__', '&amp', $result);
2626 $result = str_ireplace('__DOUBLEQUOTENOSEMICOLON__', '&quot', $result);
2627 $result = str_ireplace('__LESSTHAN__', '&lt', $result);
2628 $result = str_ireplace('__GREATERTHAN__', '&gt', $result);
2629 }
2630
2631 $result = str_ireplace('__SIMPLEQUOTE__', '&#39;', $result);
2632
2633 //$result="\n\n\n".var_export($tmp, true)."\n\n\n".var_export($result, true);
2634
2635 return $result;
2636 }
2637}
2638
2646function dol_strtolower($string, $encoding = "UTF-8")
2647{
2648 if (function_exists('mb_strtolower')) {
2649 return mb_strtolower($string, $encoding);
2650 } else {
2651 return strtolower($string);
2652 }
2653}
2654
2663function dol_strtoupper($string, $encoding = "UTF-8")
2664{
2665 if (function_exists('mb_strtoupper')) {
2666 return mb_strtoupper($string, $encoding);
2667 } else {
2668 return strtoupper($string);
2669 }
2670}
2671
2680function dol_ucfirst($string, $encoding = "UTF-8")
2681{
2682 if (function_exists('mb_substr')) {
2683 return mb_strtoupper(mb_substr($string, 0, 1, $encoding), $encoding) . mb_substr($string, 1, null, $encoding);
2684 } else {
2685 return ucfirst($string);
2686 }
2687}
2688
2697function dol_ucwords($string, $encoding = "UTF-8")
2698{
2699 if (function_exists('mb_convert_case')) {
2700 return mb_convert_case($string, MB_CASE_TITLE, $encoding);
2701 } else {
2702 return ucwords($string);
2703 }
2704}
2705
2706
2712function getCallerInfoString()
2713{
2714 $backtrace = debug_backtrace();
2715 $msg = "";
2716 if (count($backtrace) >= 1) {
2717 $pos = 1;
2718 if (count($backtrace) == 1) {
2719 $pos = 0;
2720 }
2721 $trace = $backtrace[$pos];
2722 if (isset($trace['file'], $trace['line'])) {
2723 $msg = " From {$trace['file']}:{$trace['line']}.";
2724 }
2725 }
2726 return $msg;
2727}
2728
2751function dol_syslog($message, $level = LOG_INFO, $ident = 0, $suffixinfilename = '', $restricttologhandler = '', $logcontext = null)
2752{
2753 global $conf, $user, $debugbar;
2754
2755 // If syslog module enabled
2756 if (!isModEnabled('syslog')) {
2757 return;
2758 }
2759
2760 // Check if we are into execution of code of a website
2761 if (defined('USEEXTERNALSERVER') && !defined('USEDOLIBARRSERVER') && !defined('USEDOLIBARREDITOR')) {
2762 global $website, $websitekey;
2763 if (is_object($website) && !empty($website->ref)) {
2764 $suffixinfilename .= '_website_' . $website->ref;
2765 } elseif (!empty($websitekey)) {
2766 $suffixinfilename .= '_website_' . $websitekey;
2767 }
2768 }
2769
2770 // Check if we have a forced suffix
2771 if (defined('USESUFFIXINLOG')) {
2772 $suffixinfilename .= constant('USESUFFIXINLOG');
2773 }
2774
2775 if ($ident < 0) {
2776 foreach ($conf->loghandlers as $loghandlerinstance) {
2777 $loghandlerinstance->setIdent($ident);
2778 }
2779 }
2780
2781 if (!empty($message)) {
2782 // Test log level
2783 // @phan-suppress-next-line PhanPluginDuplicateArrayKey
2784 $logLevels = array(LOG_EMERG => 'EMERG', LOG_ALERT => 'ALERT', LOG_CRIT => 'CRITICAL', LOG_ERR => 'ERR', LOG_WARNING => 'WARN', LOG_NOTICE => 'NOTICE', LOG_INFO => 'INFO', LOG_DEBUG => 'DEBUG');
2785
2786 if (!array_key_exists($level, $logLevels)) {
2787 dol_syslog('Error Bad Log Level ' . $level, LOG_ERR);
2788 $level = LOG_ERR;
2789 }
2790 if ($level > getDolGlobalInt('SYSLOG_LEVEL')) {
2791 return;
2792 }
2793
2794 if (!getDolGlobalString('MAIN_SHOW_PASSWORD_INTO_LOG')) {
2795 $message = preg_replace('/password=\'[^\']*\'/', 'password=\'hidden\'', $message); // protection to avoid to have value of password in log
2796 }
2797
2798 // If adding log inside HTML page is required
2799 if ((!empty($_REQUEST['logtohtml']) && getDolGlobalString('MAIN_ENABLE_LOG_TO_HTML'))
2800 || (is_object($user) && $user->hasRight('debugbar', 'read') && is_object($debugbar))
2801 ) {
2802 $ospid = sprintf("%7s", dol_trunc((string) getmypid(), 7, 'right', 'UTF-8', 1));
2803 $osuser = " " . sprintf("%6s", dol_trunc(function_exists('posix_getuid') ? posix_getuid() : '', 6, 'right', 'UTF-8', 1));
2804
2805 $conf->logbuffer[] = dol_print_date(time(), "%Y-%m-%d %H:%M:%S") . " " . sprintf("%-7s", $logLevels[$level]) . " " . $ospid . " " . $osuser . " " . $message;
2806 }
2807
2808 //TODO: Remove this. MAIN_ENABLE_LOG_INLINE_HTML should be deprecated and use a log handler dedicated to HTML output
2809 // If html log tag enabled and url parameter log defined, we show output log on HTML comments
2810 if (getDolGlobalString('MAIN_ENABLE_LOG_INLINE_HTML') && GETPOSTINT("log")) {
2811 print "\n\n<!-- Log start\n";
2812 print dol_escape_htmltag($message) . "\n";
2813 print "Log end -->\n";
2814 }
2815
2816 $data = array(
2817 'message' => $message,
2818 'script' => (isset($_SERVER['PHP_SELF']) ? basename($_SERVER['PHP_SELF'], '.php') : ''),
2819 'level' => $level,
2820 'user' => ((is_object($user) && $user->id) ? $user->login : ''),
2821 'ip' => '',
2822 'osuser' => function_exists('posix_getuid') ? (string) posix_getuid() : '',
2823 'ospid' => (string) getmypid() // on linux, max value is defined into cat /proc/sys/kernel/pid_max
2824 );
2825
2826 // For log, we want the reliable IP first.
2827 $remoteip = getUserRemoteIP(1); // Get ip when page run on a web server
2828 if (!empty($remoteip)) {
2829 $data['ip'] = $remoteip;
2830 // This is when server run behind a reverse proxy
2831 // A HTTP_X_FORWARDED_FOR as format "ip real of user, ip of proxy1, ip of proxy2, ..."
2832 // $data['ip'] is last
2833 if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
2834 $tmpips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
2835 $data['ip'] = '';
2836 $foundremoteip = 0;
2837 $j = 0;
2838 foreach ($tmpips as $tmpip) {
2839 $tmpip = trim($tmpip);
2840 if (strtolower($tmpip) == strtolower($remoteip)) {
2841 $foundremoteip = 1;
2842 }
2843 if (empty($data['ip'])) {
2844 $data['ip'] = $tmpip;
2845 } else {
2846 $j++;
2847 $data['ip'] .= (($j == 1) ? ' [via ' : ',') . $tmpip;
2848 }
2849 }
2850 if (!$foundremoteip) {
2851 $j++;
2852 $data['ip'] .= (($j == 1) ? ' [via ' : ',') . $remoteip;
2853 }
2854 $data['ip'] .= (($j > 0) ? ']' : '');
2855 } elseif (!empty($_SERVER['HTTP_CLIENT_IP'])) {
2856 $tmpips = explode(',', $_SERVER['HTTP_CLIENT_IP']);
2857 $data['ip'] = '';
2858 $foundremoteip = 0;
2859 $j = 0;
2860 foreach ($tmpips as $tmpip) {
2861 $tmpip = trim($tmpip);
2862 if (strtolower($tmpip) == strtolower($remoteip)) {
2863 $foundremoteip = 1;
2864 }
2865 if (empty($data['ip'])) {
2866 $data['ip'] = $tmpip;
2867 } else {
2868 $j++;
2869 $data['ip'] .= (($j == 1) ? ' [via ' : ',') . $tmpip;
2870 }
2871 }
2872 if (!$foundremoteip) {
2873 $j++;
2874 $data['ip'] .= (($j == 1) ? ' [via ' : ',') . $remoteip;
2875 }
2876 $data['ip'] .= (($j > 0) ? ']' : '');
2877 }
2878 } elseif (!empty($_SERVER['SERVER_ADDR'])) {
2879 // This is when PHP session is ran inside a web server but not inside a client request (example: init code of apache)
2880 $data['ip'] = (string) $_SERVER['SERVER_ADDR'];
2881 } elseif (!empty($_SERVER['COMPUTERNAME'])) {
2882 // This is when PHP session is ran outside a web server, like from Windows command line (Not always defined, but useful if OS defines it).
2883 $data['ip'] = (string) $_SERVER['COMPUTERNAME'];
2884 } else {
2885 $data['ip'] = '???';
2886 }
2887
2888 if (!empty($_SERVER['USERNAME'])) {
2889 // This is when PHP session is ran outside a web server, like from Linux command line (Not always defined, but useful if OS defines it).
2890 $data['osuser'] = (string) $_SERVER['USERNAME'];
2891 } elseif (!empty($_SERVER['LOGNAME'])) {
2892 // This is when PHP session is ran outside a web server, like from Linux command line (Not always defined, but useful if OS defines it).
2893 $data['osuser'] = (string) $_SERVER['LOGNAME'];
2894 }
2895
2896 // Loop on each log handler and send output
2897 foreach ($conf->loghandlers as $loghandlerinstance) {
2898 if ($restricttologhandler && $loghandlerinstance->code != $restricttologhandler) {
2899 continue;
2900 }
2901 $loghandlerinstance->export($data, $suffixinfilename);
2902 }
2903 unset($data);
2904 }
2905
2906 if ($ident > 0) {
2907 foreach ($conf->loghandlers as $loghandlerinstance) {
2908 $loghandlerinstance->setIdent($ident);
2909 }
2910 }
2911}
2912
2924function dolButtonToOpenExportDialog($name, $label, $buttonstring, $exportSiteName, $overwriteGitUrl, $website)
2925{
2926 global $langs, $db;
2927
2928 $form = new Form($db);
2929
2930 $templatenameforexport = $website->name_template; // Example 'website_template-corporate'
2931 if (empty($templatenameforexport)) {
2932 $templatenameforexport = 'website_' . $website->ref;
2933 }
2934
2935 $out = '';
2936 $out .= '<input type="button" class="cursorpointer button bordertransp" id="open-dialog-' . $name . '" value="' . dol_escape_htmltag($buttonstring) . '"/>';
2937
2938 // for generate popup
2939 $out .= '<script nonce="' . getNonce() . '" type="text/javascript">';
2940 $out .= 'jQuery(document).ready(function () {';
2941 $out .= ' jQuery("#open-dialog-' . $name . '").click(function () {';
2942 $out .= ' var dialogHtml = \'';
2943
2944 $dialogcontent = ' <div id="custom-dialog-' . $name . '">';
2945 $dialogcontent .= ' <div style="margin-top: 20px;">';
2946 $dialogcontent .= ' <label for="export-site-' . $name . '"><strong>' . $langs->trans("ExportSiteLabel") . '...</label><br>';
2947 $dialogcontent .= ' <button class="button smallpaddingimp" id="export-site-' . $name . '">' . dol_escape_htmltag($langs->trans("DownloadZip")) . '</button>';
2948 $dialogcontent .= ' </div>';
2949 $dialogcontent .= ' <br>';
2950 $dialogcontent .= ' <div style="margin-top: 20px;">';
2951 $dialogcontent .= ' <strong>' . $langs->trans("ExportSiteGitLabel") . ' ' . $form->textwithpicto('', $langs->trans("SourceFiles"), 1, 'help', '', 0, 3, '') . '</strong><br>';
2952 $dialogcontent .= ' <form action="' . dol_escape_htmltag($overwriteGitUrl) . '" method="POST">';
2953 $dialogcontent .= ' <input type="hidden" name="action" value="overwritesite">';
2954 $dialogcontent .= ' <input type="hidden" name="token" value="' . newToken() . '">';
2955 $dialogcontent .= ' <input type="text" autofocus name="export_path" id="export-path-' . $name . '" placeholder="' . $langs->trans('ExportPath') . '" style="width:400px " value="' . dol_escape_htmltag($templatenameforexport) . '"/><br>';
2956 $dialogcontent .= ' <button type="submit" class="button smallpaddingimp" id="overwrite-git-' . $name . '">' . dol_escape_htmltag($langs->trans("ExportIntoGIT")) . '</button>';
2957 $dialogcontent .= ' </form>';
2958 $dialogcontent .= ' </div>';
2959 $dialogcontent .= ' </div>';
2960
2961 $out .= dol_escape_js($dialogcontent);
2962
2963 $out .= '\';';
2964
2965
2966 // Add the content of the dialog to the body of the page
2967 $out .= ' var $dialog = jQuery("#custom-dialog-' . $name . '");';
2968 $out .= ' if ($dialog.length > 0) {
2969 $dialog.remove();
2970 }
2971 jQuery("body").append(dialogHtml);';
2972
2973 // Configuration of popup
2974 $out .= ' jQuery("#custom-dialog-' . $name . '").dialog({';
2975 $out .= ' autoOpen: false,';
2976 $out .= ' modal: true,';
2977 $out .= ' height: 290,';
2978 $out .= ' width: "40%",';
2979 $out .= ' title: "' . dol_escape_js($label) . '",';
2980 $out .= ' });';
2981
2982 // Simulate a click on the original "submit" input to export the site.
2983 $out .= ' jQuery("#export-site-' . $name . '").click(function () {';
2984 $out .= ' console.log("Clic on exportsite.");';
2985 $out .= ' var target = jQuery("input[name=\'' . dol_escape_js($exportSiteName) . '\']");';
2986 $out .= ' console.log("element founded:", target.length > 0);';
2987 $out .= ' if (target.length > 0) { target.click(); }';
2988 $out .= ' jQuery("#custom-dialog-' . $name . '").dialog("close");';
2989 $out .= ' });';
2990
2991 // open popup
2992 $out .= ' jQuery("#custom-dialog-' . $name . '").dialog("open");';
2993 $out .= ' return false;';
2994 $out .= ' });';
2995 $out .= '});';
2996 $out .= '</script>';
2997
2998 return $out;
2999}
3000
3001
3018function dolButtonToOpenUrlInDialogPopup($name, $label, $buttonstring, $url, $disabled = '', $morecss = 'classlink button bordertransp', $jsonopen = '', $jsonclose = '', $accesskey = '')
3019{
3020 global $conf;
3021
3022 if (strpos($url, '?') > 0) {
3023 $url .= '&dol_hide_topmenu=1&dol_hide_leftmenu=1&dol_openinpopup=' . urlencode($name);
3024 } else {
3025 $url .= '?dol_hide_topmenu=1&dol_hide_leftmenu=1&dol_openinpopup=' . urlencode($name);
3026 }
3027
3028 if (preg_match('/^https/i', $url)) {
3029 $urltoopen = $url;
3030 } else {
3031 $urltoopen = DOL_URL_ROOT . $url;
3032 }
3033
3034 $out = '';
3035
3036 //print '<input type="submit" class="button bordertransp"'.$disabled.' value="'.dol_escape_htmltag($langs->trans("MediaFiles")).'" name="file_manager">';
3037 $out .= '<!-- a link for button to open url into a dialog popup -->';
3038 $out .= '<a ' . ($accesskey ? ' accesskey="' . $accesskey . '"' : '') . ' class="cursorpointer reposition button_' . $name . ($morecss ? ' ' . $morecss : '') . '"' . $disabled . ' title="' . dol_escape_htmltag($label) . '"';
3039 if (empty($conf->use_javascript_ajax)) {
3040 $out .= ' href="' . $urltoopen . '" target="_blank"';
3041 } elseif ($jsonopen) {
3042 $out .= ' href="#" onclick="' . $jsonopen . '"';
3043 } else {
3044 $out .= ' href="#"';
3045 }
3046 $out .= '>' . $buttonstring . '</a>';
3047
3048 if (!empty($conf->use_javascript_ajax)) {
3049 // Add code to open url using the popup.
3050 $out .= '<!-- code to open popup and variables to retrieve returned variables -->';
3051 $out .= '<div id="idfordialog' . $name . '" class="hidden">' . (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2 ? 'div for dialog' : '') . '</div>';
3052
3053 $out .= '<!-- Add js code to open dialog popup on dialog -->';
3054 $out .= '<script nonce="' . getNonce() . '" type="text/javascript">
3055 jQuery(document).ready(function () {
3056 jQuery(".button_' . $name . '").click(function () {
3057 console.log(\'Open popup with jQuery(...).dialog() on URL ' . dol_escape_js($urltoopen) . '\');
3058 var $tmpdialog = $(\'#idfordialog' . $name . '\');
3059 $tmpdialog.html(\'<iframe class="iframedialog" id="iframedialog' . $name . '" style="border: 0px;" src="' . $urltoopen . '" width="100%" height="98%"></iframe>\');
3060 $tmpdialog.dialog({
3061 autoOpen: false,
3062 modal: true,
3063 height: (window.innerHeight - 150),
3064 width: \'80%\',
3065 title: \'' . dol_escape_js($label) . '\',
3066 open: function (event, ui) {
3067 console.log("open popup name=' . $name . '");
3068 },
3069 close: function (event, ui) {
3070 console.log("Popup is closed, run jsonclose = ' . $jsonclose . '");
3071 ' . (empty($jsonclose) || preg_match('/^TODO/', $jsonclose) ? '' : $jsonclose . ';') . '
3072 }
3073 });
3074
3075 $tmpdialog.dialog(\'open\');
3076 return false;
3077 });
3078 });
3079 </script>';
3080 }
3081 return $out;
3082}
3083
3100function dol_fiche_head($links = array(), $active = '0', $title = '', $notab = 0, $picto = '', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '', $limittoshow = 0, $moretabssuffix = '')
3101{
3102 print dol_get_fiche_head($links, $active, $title, $notab, $picto, $pictoisfullpath, $morehtmlright, $morecss, $limittoshow, $moretabssuffix);
3103}
3104
3122function dol_get_fiche_head($links = array(), $active = '', $title = '', $notab = 0, $picto = '', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '', $limittoshow = 0, $moretabssuffix = '', $dragdropfile = 0, $morecssdiv = '')
3123{
3124 global $conf, $langs, $hookmanager;
3125
3126 // Show title
3127 $showtitle = 1;
3128 if (!empty($conf->dol_optimize_smallscreen)) {
3129 $showtitle = 0;
3130 }
3131
3132 $out = "\n" . '<!-- dol_fiche_head - dol_get_fiche_head -->';
3133
3134 if ((!empty($title) && $showtitle) || $morehtmlright || !empty($links)) {
3135 $out .= '<div class="tabs' . ($picto ? '' : ' nopaddingleft') . '" data-role="controlgroup" data-type="horizontal">' . "\n";
3136 }
3137
3138 // Show right part
3139 if ($morehtmlright) {
3140 $out .= '<div class="inline-block floatright tabsElem">' . $morehtmlright . '</div>'; // Output right area first so when space is missing, text is in front of tabs and not under.
3141 }
3142
3143 // Show tabs
3144
3145 // Define max of key (max may be higher than sizeof because of hole due to module disabling some tabs).
3146 $maxkey = -1;
3147 if (is_array($links) && !empty($links)) {
3148 $keys = array_keys($links);
3149 if (count($keys)) {
3150 $maxkey = max($keys);
3151 }
3152 }
3153
3154 // if =0 we don't use the feature
3155 if (empty($limittoshow)) {
3156 $limittoshow = getDolGlobalInt('MAIN_MAXTABS_IN_CARD', 99);
3157 }
3158 if (!empty($conf->dol_optimize_smallscreen)) { // If on smartphone, we limit to 1 tab to show
3159 $limittoshow = 1;
3160 }
3161
3162 $displaytab = 0;
3163 $nbintab = 0;
3164 $popuptab = 0;
3165 $outmore = '';
3166 for ($i = 0; $i <= $maxkey; $i++) {
3167 if ((is_numeric($active) && $i == $active) || (!empty($links[$i][2]) && !is_numeric($active) && $active == $links[$i][2])) {
3168 // If active tab is already present
3169 if ($i >= $limittoshow) {
3170 $limittoshow--;
3171 }
3172 }
3173 }
3174
3175 for ($i = 0; $i <= $maxkey; $i++) {
3176 if ((is_numeric($active) && $i == $active) || (!empty($links[$i][2]) && !is_numeric($active) && $active == $links[$i][2])) {
3177 $isactive = true;
3178 } else {
3179 $isactive = false;
3180 }
3181
3182 if ($i < $limittoshow || $isactive) {
3183 // Output entry with a visible tab
3184 $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])) . ' -->';
3185
3186 if (isset($links[$i][2]) && $links[$i][2] == 'image') {
3187 if (!empty($links[$i][0])) {
3188 $out .= '<a class="tabimage' . ($morecss ? ' ' . $morecss : '') . '" href="' . $links[$i][0] . '">' . $links[$i][1] . '</a>' . "\n";
3189 } else {
3190 $out .= '<span class="tabspan">' . $links[$i][1] . '</span>' . "\n";
3191 }
3192 } elseif (!empty($links[$i][1])) {
3193 //print "x $i $active ".$links[$i][2]." z";
3194 $out .= '<div class="tab tab' . ($isactive ? 'active' : 'unactive') . '" style="margin: 0 !important">';
3195
3196 if (!empty($links[$i][0])) {
3197 $titletoshow = preg_replace('/<.*$/', '', $links[$i][1]);
3198 $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) . '">';
3199 }
3200
3201 if ($displaytab == 0 && $picto) {
3202 $out .= img_picto($title, $picto, '', $pictoisfullpath, 0, 0, '', 'imgTabTitle paddingright marginrightonlyshort');
3203 }
3204
3205 $out .= $links[$i][1];
3206 if (!empty($links[$i][0])) {
3207 $out .= '</a>' . "\n";
3208 }
3209 $out .= empty($links[$i][4]) ? '' : $links[$i][4];
3210 $out .= '</div>';
3211 }
3212
3213 $out .= '</div>';
3214 } else {
3215 // Add entry into the combo popup with the other tabs
3216 if (!$popuptab) {
3217 $popuptab = 1;
3218 $outmore .= '<div class="popuptabset wordwrap">'; // The css used to hide/show popup
3219 }
3220 $outmore_content = '';
3221
3222 if (isset($links[$i][2]) && $links[$i][2] == 'image') {
3223 if (!empty($links[$i][0])) {
3224 $outmore_content .= '<a class="tabimage' . ($morecss ? ' ' . $morecss : '') . '" href="' . $links[$i][0] . '">' . $links[$i][1] . '</a>' . "\n";
3225 } else {
3226 $outmore_content .= '<span class="tabspan">' . $links[$i][1] . '</span>' . "\n";
3227 }
3228 } elseif (!empty($links[$i][1])) {
3229 $outmore_content .= '<a' . (!empty($links[$i][2]) ? ' id="' . $links[$i][2] . '"' : '') . ' class="wordwrap inline-block' . ($morecss ? ' ' . $morecss : '') . '" href="' . $links[$i][0] . '">';
3230 $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.
3231 $outmore_content .= '</a>' . "\n";
3232 }
3233 if ($outmore_content !== '') {
3234 $outmore .= '<div class="popuptab wordwrap" style="display:inherit;">' . $outmore_content . '</div>';
3235 }
3236
3237 $nbintab++;
3238 }
3239
3240 $displaytab = $i + 1;
3241 }
3242 if ($popuptab) {
3243 $outmore .= '</div>';
3244 }
3245
3246 if ($popuptab) { // If there is some tabs not shown
3247 $left = ($langs->trans("DIRECTION") == 'rtl' ? 'right' : 'left');
3248 $right = ($langs->trans("DIRECTION") == 'rtl' ? 'left' : 'right');
3249 $widthofpopup = 240;
3250
3251 $tabsname = $moretabssuffix;
3252 if (empty($tabsname)) {
3253 $tabsname = str_replace("@", "", $picto);
3254 }
3255 $out .= '<div id="moretabs' . $tabsname . '" class="inline-block tabsElem valignmiddle">';
3256 if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2) {
3257 $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".
3258 }
3259 $out .= '<div id="moretabsList' . $tabsname . '" style="width: ' . $widthofpopup . 'px; position: absolute; ' . $left . ': -999em; text-align: ' . $left . '; margin:0px; padding:2px; z-index:10;">';
3260 $out .= $outmore;
3261 $out .= '</div>';
3262 $out .= '<div></div>';
3263 $out .= "</div>\n";
3264
3265 $out .= '<script nonce="' . getNonce() . '">';
3266 $out .= "$('#moretabs" . $tabsname . "').mouseenter( function() {
3267 var x = this.offsetLeft, y = this.offsetTop;
3268 console.log('mouseenter " . $left . " x='+x+' y='+y+' window.innerWidth='+window.innerWidth);
3269 if ((window.innerWidth - x) < " . ($widthofpopup + 10) . ") {
3270 $('#moretabsList" . $tabsname . "').css('" . $right . "','8px');
3271 }
3272 $('#moretabsList" . $tabsname . "').css('" . $left . "','auto');
3273 });
3274 ";
3275 $out .= "$('#moretabs" . $tabsname . "').mouseleave( function() { console.log('mouseleave " . $left . "'); $('#moretabsList" . $tabsname . "').css('" . $left . "','-999em');});";
3276 $out .= "</script>";
3277 }
3278
3279 if ((!empty($title) && $showtitle) || $morehtmlright || !empty($links)) {
3280 $out .= "</div>\n";
3281 }
3282
3283 if (!$notab || $notab == -1 || $notab == -2 || $notab == -3 || $notab == -4) {
3284 $out .= "\n" . '<div id="dragDropAreaTabBar" class="tabBar' . ($notab == -1 ? '' : ($notab == -2 ? ' tabBarNoTop' : ((($notab == -3 || $notab == -4) ? ' noborderbottom' : '') . ($notab == -4 ? '' : ' tabBarWithBottom'))));
3285 $out .= ($morecssdiv ? ' ' . $morecssdiv : '');
3286 $out .= '">' . "\n";
3287 }
3288 if (!empty($dragdropfile)) {
3289 // Add the js code to manage the drag and drop
3290 include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
3291 $out .= dragAndDropFileUpload("dragDropAreaTabBar");
3292 }
3293 $parameters = array('tabname' => $active, 'out' => $out);
3294 $reshook = $hookmanager->executeHooks('printTabsHead', $parameters); // This hook usage is called just before output the head of tabs. Take also a look at "completeTabsHead"
3295 if ($reshook > 0) {
3296 $out = $hookmanager->resPrint;
3297 }
3298
3299 return $out;
3300}
3301
3309function dol_fiche_end($notab = 0)
3310{
3311 print dol_get_fiche_end($notab);
3312}
3313
3320function dol_get_fiche_end($notab = 0)
3321{
3322 if (!$notab || $notab == -1) {
3323 return "\n</div>\n";
3324 } else {
3325 return '';
3326 }
3327}
3328
3348function dol_banner_tab($object, $paramid, $morehtml = '', $shownav = 1, $fieldid = 'rowid', $fieldref = 'ref', $morehtmlref = '', $moreparam = '', $nodbprefix = 0, $morehtmlleft = '', $morehtmlstatus = '', $onlybanner = 0, $morehtmlright = '')
3349{
3350 global $conf, $form, $user, $langs, $hookmanager, $action;
3351
3352 $error = 0;
3353
3354 $maxvisiblephotos = 1;
3355 $showimage = 1;
3356 $entity = (empty($object->entity) ? $conf->entity : $object->entity);
3357 // @phan-suppress-next-line PhanUndeclaredMethod
3358 $showbarcode = !isModEnabled('barcode') ? 0 : (empty($object->barcode) ? 0 : 1);
3359 if (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && !$user->hasRight('barcode', 'lire_advance')) {
3360 $showbarcode = 0;
3361 }
3362 $modulepart = 'unknown';
3363
3364 if (in_array($object->element, ['societe', 'contact', 'product', 'ticket', 'bom'])) {
3365 $modulepart = $object->element;
3366 } elseif ($object->element == 'member') {
3367 $modulepart = 'memberphoto';
3368 } elseif ($object->element == 'user') {
3369 $modulepart = 'userphoto';
3370 }
3371
3372 if (class_exists("Imagick")) {
3373 if ($object->element == 'expensereport' || $object->element == 'propal' || $object->element == 'commande' || $object->element == 'facture' || $object->element == 'supplier_proposal') {
3374 $modulepart = $object->element;
3375 } elseif ($object->element == 'fichinter' || $object->element == 'intervention') {
3376 $modulepart = 'ficheinter';
3377 } elseif ($object->element == 'contrat' || $object->element == 'contract') {
3378 $modulepart = 'contract';
3379 } elseif ($object->element == 'order_supplier') {
3380 $modulepart = 'supplier_order';
3381 } elseif ($object->element == 'invoice_supplier') {
3382 $modulepart = 'supplier_invoice';
3383 }
3384 }
3385
3386 if ($object->element == 'product') {
3388 '@phan-var-force Product $object';
3389 $width = 80;
3390 $cssclass = 'photowithmargin photoref';
3391 $showimage = $object->is_photo_available($conf->product->multidir_output[$entity]);
3392 $maxvisiblephotos = getDolGlobalInt('PRODUCT_MAX_VISIBLE_PHOTO', 5);
3393 if ($conf->browser->layout == 'phone') {
3394 $maxvisiblephotos = 1;
3395 }
3396 $useLinkPathPhoto = getDolGlobalInt('PRODUCT_USE_LINK_PATH_FOR_PHOTO');
3397 if ($showimage || $useLinkPathPhoto) {
3398 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">' . $object->show_photos('product', $conf->product->multidir_output[$entity], 1, $maxvisiblephotos, 0, 0, 0, 0, $width, 0, '') . '</div>';
3399 } else {
3400 if (getDolGlobalString('PRODUCT_NODISPLAYIFNOPHOTO')) {
3401 $nophoto = '';
3402 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
3403 } else { // Show no photo link
3404 $nophoto = '/public/theme/common/nophoto.png';
3405 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><img class="photo' . $modulepart . ($cssclass ? ' ' . $cssclass : '') . '" title="' . dol_escape_htmltag($langs->trans("UploadAnImageToSeeAPhotoHere", $langs->transnoentitiesnoconv("Documents"))) . '" alt="No photo"' . ($width ? ' style="width: ' . $width . 'px"' : '') . ' src="' . DOL_URL_ROOT . $nophoto . '"></div>';
3406 }
3407 }
3408 } elseif ($object->element == 'category') {
3410 '@phan-var-force Categorie $object';
3411 $width = 80;
3412 $cssclass = 'photowithmargin photoref';
3413 $showimage = $object->isAnyPhotoAvailable($conf->categorie->multidir_output[$entity]);
3414 $maxvisiblephotos = getDolGlobalInt('CATEGORY_MAX_VISIBLE_PHOTO', 5);
3415 if ($conf->browser->layout == 'phone') {
3416 $maxvisiblephotos = 1;
3417 }
3418 if ($showimage) {
3419 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">' . $object->show_photos('category', $conf->categorie->multidir_output[$entity], 'small', $maxvisiblephotos, 0, 0, 0, 0, $width, 0, '') . '</div>';
3420 } else {
3421 if (getDolGlobalString('CATEGORY_NODISPLAYIFNOPHOTO')) {
3422 $nophoto = '';
3423 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
3424 } else { // Show no photo link
3425 $nophoto = '/public/theme/common/nophoto.png';
3426 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><img class="photo' . $modulepart . ($cssclass ? ' ' . $cssclass : '') . '" title="' . dol_escape_htmltag($langs->trans("UploadAnImageToSeeAPhotoHere", $langs->transnoentitiesnoconv("Documents"))) . '" alt="No photo"' . ($width ? ' style="width: ' . $width . 'px"' : '') . ' src="' . DOL_URL_ROOT . $nophoto . '"></div>';
3427 }
3428 }
3429 } elseif ($object->element == 'bom') {
3431 '@phan-var-force Bom $object';
3432 $width = 80;
3433 $cssclass = 'photowithmargin photoref';
3434 $showimage = $object->is_photo_available($conf->bom->multidir_output[$entity]);
3435 $maxvisiblephotos = getDolGlobalInt('BOM_MAX_VISIBLE_PHOTO', 5);
3436 if ($conf->browser->layout == 'phone') {
3437 $maxvisiblephotos = 1;
3438 }
3439 if ($showimage) {
3440 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">' . $object->show_photos('bom', $conf->bom->multidir_output[$entity], 'small', $maxvisiblephotos, 0, 0, 0, 0, $width, 0, '') . '</div>';
3441 } else {
3442 if (getDolGlobalString('BOM_NODISPLAYIFNOPHOTO')) {
3443 $nophoto = '';
3444 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
3445 } else { // Show no photo link
3446 $nophoto = '/public/theme/common/nophoto.png';
3447 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><img class="photo' . $modulepart . ($cssclass ? ' ' . $cssclass : '') . '" title="' . dol_escape_htmltag($langs->trans("UploadAnImageToSeeAPhotoHere", $langs->transnoentitiesnoconv("Documents"))) . '" alt="No photo"' . ($width ? ' style="width: ' . $width . 'px"' : '') . ' src="' . DOL_URL_ROOT . $nophoto . '"></div>';
3448 }
3449 }
3450 } elseif ($object->element == 'ticket') {
3451 $width = 80;
3452 $cssclass = 'photoref';
3454 '@phan-var-force Ticket $object';
3455 $showimage = $object->is_photo_available($conf->ticket->multidir_output[$entity] . '/' . $object->ref);
3456 $maxvisiblephotos = getDolGlobalInt('TICKET_MAX_VISIBLE_PHOTO', 2);
3457 if ($conf->browser->layout == 'phone') {
3458 $maxvisiblephotos = 1;
3459 }
3460
3461 if ($showimage) {
3462 $showphoto = $object->show_photos('ticket', $conf->ticket->multidir_output[$entity], 'small', $maxvisiblephotos, 0, 0, 0, $width, 0);
3463 if ($object->nbphoto > 0) {
3464 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">' . $showphoto . '</div>';
3465 } else {
3466 $showimage = 0;
3467 }
3468 }
3469 if (!$showimage) {
3470 if (getDolGlobalString('TICKET_NODISPLAYIFNOPHOTO')) {
3471 $nophoto = '';
3472 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
3473 } else { // Show no photo link
3474 $nophoto = img_picto('No photo', 'object_ticket');
3475 $morehtmlleft .= '<!-- No photo to show -->';
3476 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref">';
3477 $morehtmlleft .= $nophoto;
3478 $morehtmlleft .= '</div></div>';
3479 }
3480 }
3481 } else {
3482 // $modulepart may have been set previously if Imagick class exists (see before).
3483 if ($modulepart != 'unknown' || method_exists($object, 'getDataToShowPhoto')) {
3484 $phototoshow = '';
3485 // Check if a preview file is available
3486 if (in_array($modulepart, array('propal', 'commande', 'facture', 'ficheinter', 'contract', 'supplier_order', 'supplier_proposal', 'supplier_invoice', 'expensereport')) && class_exists("Imagick")) {
3487 $objectref = dol_sanitizeFileName($object->ref);
3488 $dir_output = (empty($conf->$modulepart->multidir_output[$entity]) ? $conf->$modulepart->dir_output : $conf->$modulepart->multidir_output[$entity]) . "/";
3489 if (in_array($modulepart, array('invoice_supplier', 'supplier_invoice'))) {
3490 $subdir = get_exdir($object->id, 2, 0, 1, $object, $modulepart);
3491 $subdir .= ((!empty($subdir) && !preg_match('/\/$/', $subdir)) ? '/' : '') . $objectref; // the objectref dir is not included into get_exdir when used with level=2, so we add it at end
3492 } else {
3493 $subdir = get_exdir($object->id, 0, 0, 1, $object, $modulepart);
3494 }
3495 if (empty($subdir)) {
3496 $subdir = 'errorgettingsubdirofobject'; // Protection to avoid to return empty path
3497 }
3498
3499 $filepath = $dir_output . $subdir . "/";
3500
3501 $filepdf = $filepath . $objectref . ".pdf";
3502 $relativepath = $subdir . '/' . $objectref . '.pdf';
3503
3504 // Define path to preview pdf file (preview precompiled "file.ext" are "file.ext_preview.png")
3505 $fileimage = $filepdf . '_preview.png';
3506 $relativepathimage = $relativepath . '_preview.png';
3507
3508 $pdfexists = file_exists($filepdf);
3509
3510 // If PDF file exists
3511 if ($pdfexists) {
3512 // Conversion du PDF en image png si fichier png non existent
3513 if (!file_exists($fileimage) || (filemtime($fileimage) < filemtime($filepdf))) {
3514 if (!getDolGlobalString('MAIN_DISABLE_PDF_THUMBS')) { // If you experience trouble with pdf thumb generation and imagick, you can disable here.
3515 include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
3516 $ret = dol_convert_file($filepdf, 'png', $fileimage, '0'); // Convert first page of PDF into a file _preview.png
3517 if ($ret < 0) {
3518 $error++;
3519 }
3520 }
3521 }
3522 }
3523
3524 if ($pdfexists && !$error) {
3525 $heightforphotref = 80;
3526 if (!empty($conf->dol_optimize_smallscreen)) {
3527 $heightforphotref = 60;
3528 }
3529 // If the preview file is found
3530 if (file_exists($fileimage)) {
3531 $phototoshow = '<div class="photoref">';
3532 $phototoshow .= '<img height="' . $heightforphotref . '" class="photo photowithborder" src="' . DOL_URL_ROOT . '/viewimage.php?modulepart=apercu' . $modulepart . '&amp;file=' . urlencode($relativepathimage) . '">';
3533 $phototoshow .= '</div>';
3534 }
3535 }
3536 } elseif (!$phototoshow) { // example if modulepart = 'societe' or 'photo' or 'memberphoto'
3537 $phototoshow .= $form->showphoto($modulepart, $object, 0, 0, 0, 'photowithmargin photoref', 'small', 1, 0);
3538 }
3539
3540 if ($phototoshow) {
3541 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">';
3542 $morehtmlleft .= $phototoshow;
3543 $morehtmlleft .= '</div>';
3544 }
3545 }
3546
3547 if (empty($phototoshow)) { // Show No photo link (picto of object)
3548 if ($object->element == 'action') {
3549 $width = 80;
3550 $cssclass = 'photorefcenter';
3551 $nophoto = img_picto('No photo', 'title_agenda');
3552 } else {
3553 $width = 14;
3554 $cssclass = 'photorefcenter';
3555 $picto = $object->picto; // @phan-suppress-current-line PhanUndeclaredProperty
3556 $prefix = 'object_';
3557 if ($object->element == 'project' && !$object->public) { // @phan-suppress-current-line PhanUndeclaredProperty
3558 $picto = 'project'; // instead of projectpub
3559 }
3560 if (strpos($picto, 'fontawesome_') !== false) {
3561 $prefix = '';
3562 }
3563 $nophoto = img_picto('No photo', $prefix . $picto);
3564 }
3565 $morehtmlleft .= '<!-- No photo to show -->';
3566 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref">';
3567 $morehtmlleft .= $nophoto;
3568 $morehtmlleft .= '</div></div>';
3569 }
3570 }
3571
3572 if (getDolGlobalString('MAIN_SHOW_TECHNICAL_ID') && (getDolGlobalString('MAIN_SHOW_TECHNICAL_ID') == '1' || preg_match('/' . preg_quote($object->element, '/') . '/i', getDolGlobalString('MAIN_SHOW_TECHNICAL_ID'))) && !empty($object->id)) {
3573 $morehtmlref .= '<div style="clear: both;"></div>';
3574 $morehtmlref .= '<div class="smallimp refidno opacitymedium banner-object-technical-id">';
3575 $morehtmlref .= $langs->trans("TechnicalID") . ': ' . ((int) $object->id);
3576 $morehtmlref .= '</div>';
3577 }
3578
3579
3580 // Show barcode
3581 if ($showbarcode) {
3582 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">' . $form->showbarcode($object, 100, 'photoref valignmiddle') . '</div>';
3583 }
3584
3585 if ($object->element == 'societe') {
3587 if (!empty($conf->use_javascript_ajax) && $user->hasRight('societe', 'creer') && getDolGlobalString('MAIN_DIRECT_STATUS_UPDATE')) {
3588 $morehtmlstatus .= ajax_object_onoff($object, 'status', 'status', 'InActivity', 'ActivityCeased');
3589 } else {
3590 $morehtmlstatus .= $object->getLibStatut(6);
3591 }
3592 } elseif ($object->element == 'product') {
3594 //$morehtmlstatus.=$langs->trans("Status").' ('.$langs->trans("Sell").') ';
3595 if (!empty($conf->use_javascript_ajax) && $user->hasRight('produit', 'creer') && getDolGlobalString('MAIN_DIRECT_STATUS_UPDATE')) {
3596 $morehtmlstatus .= ajax_object_onoff($object, 'status', 'status', 'ProductStatusOnSell', 'ProductStatusNotOnSell');
3597 } else {
3598 $morehtmlstatus .= '<span class="statusrefsell">' . $object->getLibStatut(6, 0) . '</span>';
3599 }
3600 $morehtmlstatus .= ' &nbsp; ';
3601 //$morehtmlstatus.=$langs->trans("Status").' ('.$langs->trans("Buy").') ';
3602 if (!empty($conf->use_javascript_ajax) && $user->hasRight('produit', 'creer') && getDolGlobalString('MAIN_DIRECT_STATUS_UPDATE')) {
3603 $morehtmlstatus .= ajax_object_onoff($object, 'status_buy', 'status_buy', 'ProductStatusOnBuy', 'ProductStatusNotOnBuy');
3604 } else {
3605 $morehtmlstatus .= '<span class="statusrefbuy">' . $object->getLibStatut(6, 1) . '</span>';
3606 }
3607 } elseif (in_array($object->element, array('salary'))) {
3609 '@phan-var-force Salary $object';
3610 $tmptxt = $object->getLibStatut(6, $object->alreadypaid);
3611 if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) {
3612 $tmptxt = $object->getLibStatut(5, $object->alreadypaid);
3613 }
3614 $morehtmlstatus .= $tmptxt;
3615 } elseif (in_array($object->element, array('facture', 'invoice', 'invoice_supplier'))) {
3617 '@phan-var-force Facture|FactureFournisseur|CommonInvoice $object';
3618 if (!isset($object->alreadypaid)) {
3619 $object->totalpaid = $object->getSommePaiement(0);
3620 $object->totalcreditnotes = $object->getSumCreditNotesUsed(0);
3621 $object->totaldeposits = $object->getSumDepositsUsed(0);
3622 $object->alreadypaid = $object->totalpaid + $object->totalcreditnotes + $object->totaldeposits;
3623 }
3624 $tmptxt = $object->getLibStatut(6, (float) $object->alreadypaid);
3625 if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) {
3626 $tmptxt = $object->getLibStatut(5, (float) $object->alreadypaid);
3627 }
3628 $morehtmlstatus .= $tmptxt;
3629 } elseif (in_array($object->element, array('chargesociales', 'loan', 'tva'))) { // TODO Move this to use ->alreadypaid like for invoices
3631 '@phan-var-force ChargeSociales|Loan|Tva $object';
3632 $tmptxt = $object->getLibStatut(6, $object->totalpaid);
3633 if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) {
3634 $tmptxt = $object->getLibStatut(5, $object->totalpaid);
3635 }
3636 $morehtmlstatus .= $tmptxt;
3637 } elseif ($object->element == 'contrat' || $object->element == 'contract') {
3639 if ($object->status == 0) {
3640 $morehtmlstatus .= $object->getLibStatut(5);
3641 } else {
3642 $morehtmlstatus .= $object->getLibStatut(4);
3643 }
3644 } elseif ($object->element == 'facturerec') {
3646 '@phan-var-force FactureRec $object';
3647 if ($object->frequency == 0) {
3648 $morehtmlstatus .= $object->getLibStatut(2);
3649 } else {
3650 $morehtmlstatus .= $object->getLibStatut(5);
3651 }
3652 } elseif ($object->element == 'project_task') {
3654 $tmptxt = $object->getLibStatut(4);
3655 $morehtmlstatus .= $tmptxt;
3656 } elseif (method_exists($object, 'getLibStatut')) { // Generic case for status
3657 $tmptxt = $object->getLibStatut(6);
3658 if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) {
3659 $tmptxt = $object->getLibStatut(5);
3660 }
3661 $morehtmlstatus .= $tmptxt;
3662 }
3663
3664 // Say if object was dispatched/transferred "into accountancy"
3665 if (isModEnabled('accounting') && in_array($object->element, array('bank', 'paiementcharge', 'facture', 'invoice', 'invoice_supplier', 'expensereport', 'payment_various'))) {
3666 // Note: For 'chargesociales', 'salaries'... this is the payments that are dispatched (so element = 'bank')
3667 if (method_exists($object, 'getVentilExportCompta')) {
3668 $accounted = $object->getVentilExportCompta(1);
3669 $langs->load("accountancy");
3670 $morehtmlstatus .= '</div><div class="statusref statusrefbis"><span class="opacitymedium">' . ($accounted > 0 ? '<a href="' . DOL_URL_ROOT . '/accountancy/bookkeeping/list.php?search_mvt_num=' . ((int) $accounted) . '">' . $langs->trans("Accounted") . '</a>' : $langs->trans("NotYetAccounted")) . '</span>';
3671 }
3672 }
3673
3674 // Add alias for thirdparty
3675 if (!empty($object->name_alias)) {
3677 '@phan-var-force Societe $object';
3678 $morehtmlref .= '<div class="refidno opacitymedium banner-object-name-alias">' . dol_escape_htmltag($object->name_alias) . '</div>';
3679 }
3680
3681 // Add label
3682 if (in_array($object->element, array('product', 'bank_account', 'project_task'))) {
3684 if (!empty($object->label)) {
3685 $morehtmlref .= '<div class="refidno banner-object-label">' . $object->label . '</div>';
3686 }
3687 }
3688 // Show address and email
3689 if (method_exists($object, 'getBannerAddress') && !in_array($object->element, array('product', 'bookmark', 'ecm_directories', 'ecm_files'))) {
3690 $moreaddress = $object->getBannerAddress('refaddress', $object); // address, email, url, social networks
3691 if ($moreaddress) {
3692 $morehtmlref .= '<div class="refidno refaddress">';
3693 $morehtmlref .= $moreaddress;
3694 $morehtmlref .= '</div>';
3695 }
3696 }
3697
3698 $parameters = array('morehtmlref' => &$morehtmlref, 'moreparam' => &$moreparam, 'morehtmlleft' => &$morehtmlleft, 'morehtmlstatus' => &$morehtmlstatus, 'morehtmlright' => &$morehtmlright);
3699 $reshook = $hookmanager->executeHooks('formDolBanner', $parameters, $object, $action);
3700 if ($reshook < 0) {
3701 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
3702 } elseif (empty($reshook)) {
3703 $morehtmlref .= $hookmanager->resPrint;
3704 } elseif ($reshook > 0) {
3705 $morehtmlref = $hookmanager->resPrint;
3706 }
3707
3708 // $morehtml is the right part (link "Back to list")
3709 // $morehtmlref is the part after the ref
3710 // $morehtmlleft is the picto or photo of banner
3711 // $morehtmlstatus is part under the status
3712 // $morehtmlright is part of htmlright
3713
3714 print '<div class="' . ($onlybanner ? 'arearefnobottom ' : 'arearef ') . 'heightref valignmiddle centpercent object-banner-tab-container" data-module-part="'.dolPrintHTMLForAttribute($modulepart).'">';
3715 print $form->showrefnav($object, $paramid, $morehtml, $shownav, $fieldid, $fieldref, $morehtmlref, $moreparam, $nodbprefix, $morehtmlleft, $morehtmlstatus, $morehtmlright);
3716 print '</div>';
3717 print '<div class="underrefbanner clearboth"></div>';
3718}
3719
3729function fieldLabel($langkey, $fieldkey, $fieldrequired = 0)
3730{
3731 global $langs;
3732 $ret = '';
3733 if ($fieldrequired) {
3734 $ret .= '<span class="fieldrequired">';
3735 }
3736 $ret .= '<label for="' . $fieldkey . '">';
3737 $ret .= $langs->trans($langkey);
3738 $ret .= '</label>';
3739 if ($fieldrequired) {
3740 $ret .= '</span>';
3741 }
3742 return $ret;
3743}
3744
3758function dol_format_address($object, $withcountry = 0, $sep = "\n", $outputlangs = null, $mode = 0, $extralangcode = '')
3759{
3760 global $langs, $hookmanager;
3761
3762 $ret = '';
3763 $countriesusingstate = array('AU', 'CA', 'US', 'IN', 'GB', 'ES', 'UK', 'TR', 'CN'); // See also MAIN_FORCE_STATE_INTO_ADDRESS
3764
3765 // See format of addresses on https://en.wikipedia.org/wiki/Address
3766 // Address
3767 if (empty($mode)) {
3768 $ret .= (($extralangcode && !empty($object->array_languages['address'][$extralangcode])) ? $object->array_languages['address'][$extralangcode] : (empty($object->address) ? '' : preg_replace('/(\r\n|\r|\n)+/', $sep, $object->address)));
3769 }
3770 // Zip/Town/State
3771 if (isset($object->country_code) && in_array($object->country_code, array('AU', 'CA', 'US', 'CN')) || getDolGlobalString('MAIN_FORCE_STATE_INTO_ADDRESS')) {
3772 // US: title firstname name \n address lines \n town, state, zip \n country
3773 $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
3774 $ret .= (($ret && $town) ? $sep : '') . $town;
3775
3776 if (!empty($object->state)) {
3777 $ret .= ($ret ? ($town ? ", " : $sep) : '') . $object->state;
3778 }
3779 if (!empty($object->zip)) {
3780 $ret .= ($ret ? (($town || $object->state) ? ", " : $sep) : '') . $object->zip;
3781 }
3782 } elseif (isset($object->country_code) && in_array($object->country_code, array('GB', 'UK'))) {
3783 // UK: title firstname name \n address lines \n town state \n zip \n country
3784 $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
3785 $ret .= ($ret ? $sep : '') . $town;
3786 if (!empty($object->state)) {
3787 $ret .= ($ret ? ", " : '') . $object->state;
3788 }
3789 if (!empty($object->zip)) {
3790 $ret .= ($ret ? $sep : '') . $object->zip;
3791 }
3792 } elseif (isset($object->country_code) && in_array($object->country_code, array('ES', 'TR'))) {
3793 // ES: title firstname name \n address lines \n zip town \n state \n country
3794 $ret .= ($ret ? $sep : '') . $object->zip;
3795 $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
3796 $ret .= ($town ? (($object->zip ? ' ' : '') . $town) : '');
3797 if (!empty($object->state)) {
3798 $ret .= $sep . $object->state;
3799 }
3800 } elseif (isset($object->country_code) && in_array($object->country_code, array('JP'))) {
3801 // JP: In romaji, title firstname name\n address lines \n [state,] town zip \n country
3802 // See https://www.sljfaq.org/afaq/addresses.html
3803 $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
3804 $ret .= ($ret ? $sep : '') . ($object->state ? $object->state . ', ' : '') . $town . ($object->zip ? ' ' : '') . $object->zip;
3805 } elseif (isset($object->country_code) && in_array($object->country_code, array('IT'))) {
3806 // IT: title firstname name\n address lines \n zip town state_code \n country
3807 $ret .= ($ret ? $sep : '') . $object->zip;
3808 $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
3809 $ret .= ($town ? (($object->zip ? ' ' : '') . $town) : '');
3810 $ret .= (empty($object->state_code) ? '' : (' ' . $object->state_code));
3811 } else {
3812 // Other: title firstname name \n address lines \n zip town[, state] \n country
3813 $town = (($extralangcode && !empty($object->array_languages['address'][$extralangcode])) ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
3814 $ret .= !empty($object->zip) ? (($ret ? $sep : '') . $object->zip) : '';
3815 $ret .= ($town ? (($object->zip ? ' ' : ($ret ? $sep : '')) . $town) : '');
3816 if (!empty($object->state) && in_array($object->country_code, $countriesusingstate)) {
3817 $ret .= ($ret ? ", " : '') . $object->state;
3818 }
3819 }
3820
3821 if (!is_object($outputlangs)) {
3822 $outputlangs = $langs;
3823 }
3824 if ($withcountry) {
3825 $langs->load("dict");
3826 $ret .= (empty($object->country_code) ? '' : ($ret ? $sep : '') . $outputlangs->convToOutputCharset($outputlangs->transnoentitiesnoconv("Country" . $object->country_code)));
3827 }
3828 if ($hookmanager) {
3829 $parameters = array('withcountry' => $withcountry, 'sep' => $sep, 'outputlangs' => $outputlangs, 'mode' => $mode, 'extralangcode' => $extralangcode);
3830 $reshook = $hookmanager->executeHooks('formatAddress', $parameters, $object);
3831 if ($reshook > 0) {
3832 $ret = '';
3833 }
3834 $ret .= $hookmanager->resPrint;
3835 }
3836
3837 return $ret;
3838}
3839
3840
3841
3851function dol_strftime($fmt, $ts = false, $is_gmt = false)
3852{
3853 if ((abs($ts) <= 0x7FFFFFFF)) { // check if number in 32-bit signed range
3854 return dol_print_date($ts, $fmt, $is_gmt);
3855 } else {
3856 return 'Error date outside supported range';
3857 }
3858}
3859
3882function dol_print_date($time, $format = '', $tzoutput = 'auto', $outputlangs = null, $encodetooutput = false, $decorate = 0)
3883{
3884 global $conf, $langs;
3885
3886 // If date undefined or "", we return ""
3887 if (dol_strlen((string) $time) == 0) {
3888 return ''; // $time=0 allowed (it means 01/01/1970 00:00:00)
3889 }
3890
3891 if ($tzoutput === 'auto') {
3892 $tzoutput = (empty($conf) ? 'tzserver' : (isset($conf->tzuserinputkey) ? $conf->tzuserinputkey : 'tzserver'));
3893 }
3894
3895 // Clean parameters
3896 $to_gmt = false; // false if we want date in server timezone, true if we want to add offset
3897 $offsettz = $offsetdst = 0;
3898 if ($tzoutput) {
3899 $to_gmt = true; // For backward compatibility
3900 if (is_string($tzoutput)) {
3901 if ($tzoutput == 'tzserver') {
3902 $to_gmt = false;
3903 $offsettzstring = @date_default_timezone_get(); // Example 'Europe/Berlin' or 'Indian/Reunion'
3904 // @phan-suppress-next-line PhanPluginRedundantAssignment
3905 $offsettz = 0; // Timezone offset with server timezone (because to_gmt is false), so 0
3906 // @phan-suppress-next-line PhanPluginRedundantAssignment
3907 $offsetdst = 0; // Dst offset with server timezone (because to_gmt is false), so 0
3908 } elseif ($tzoutput == 'tzuser' || $tzoutput == 'tzuserrel') {
3909 $to_gmt = true;
3910 // if no session (by example in cron) may use MAIN_DOLIBARR_USER_TIMEZONE instead UTC
3911 $offsettzstring = (empty($_SESSION['dol_tz_string']) ? getDolGlobalString('MAIN_DOLIBARR_USER_TIMEZONE', 'UTC') : $_SESSION['dol_tz_string']); // Example 'Europe/Berlin' or 'Indian/Reunion'
3912
3913 if (class_exists('DateTimeZone')) {
3914 try {
3915 $user_date_tz = new DateTimeZone($offsettzstring);
3916 } catch (Exception $e) {
3917 // Bad value for $offsettzstring
3918 dol_syslog("DateInvalidTimeZoneException for timezone string '".$offsettzstring."'. Falling back to UTC.", LOG_ERR);
3919 $user_date_tz = new DateTimeZone('UTC'); // Force valid timezone as UTC
3920 }
3921 $user_dt = new DateTime();
3922 $user_dt->setTimezone($user_date_tz);
3923 $user_dt->setTimestamp($tzoutput == 'tzuser' ? dol_now() : (int) $time);
3924 $offsettz = $user_dt->getOffset(); // should include dst ?
3925 } else { // with old method (The 'tzuser' was processed like the 'tzuserrel')
3926 $offsettz = (empty($_SESSION['dol_tz']) ? 0 : $_SESSION['dol_tz']) * 60 * 60; // Will not be used anymore
3927 $offsetdst = (empty($_SESSION['dol_dst']) ? 0 : $_SESSION['dol_dst']) * 60 * 60; // Will not be used anymore
3928 }
3929 }
3930 }
3931 }
3932 if (!is_object($outputlangs)) {
3933 $outputlangs = $langs;
3934 }
3935 if (!$format) {
3936 $format = 'daytextshort';
3937 }
3938
3939 // Do we have to reduce the length of date (year on 2 chars) to save space.
3940 // Note: dayinputnoreduce is same than day but no reduction of year length will be done
3941 $reduceformat = (!empty($conf->dol_optimize_smallscreen) && in_array($format, array('day', 'dayhour', 'dayhoursec'))) ? 1 : 0; // Test on original $format param.
3942 $format = preg_replace('/inputnoreduce/', '', $format); // so format 'dayinputnoreduce' is processed like day
3943 $formatwithoutreduce = preg_replace('/reduceformat/', '', $format);
3944 if ($formatwithoutreduce != $format) {
3945 $format = $formatwithoutreduce;
3946 $reduceformat = 1;
3947 } // so format 'dayreduceformat' is processed like day
3948
3949 // Change predefined format into computer format. If found translation in lang file we use it, otherwise we use default.
3950 // TODO Add format daysmallyear and dayhoursmallyear
3951 if ($format == 'day') {
3952 $format = ($outputlangs->trans("FormatDateShort") != "FormatDateShort" ? $outputlangs->trans("FormatDateShort") : $conf->format_date_short);
3953 } elseif ($format == 'hour') {
3954 $format = ($outputlangs->trans("FormatHourShort") != "FormatHourShort" ? $outputlangs->trans("FormatHourShort") : $conf->format_hour_short);
3955 } elseif ($format == 'hoursec') {
3956 $s1 = $outputlangs->trans("FormatDateShort");
3957 $s2 = $outputlangs->trans("FormatDateHourSecShort");
3958 $s3 = trim(preg_replace('/'.preg_quote($s1, '/').'/', '', $s2)); // Try to guess the format for FormatHourSecShort using FormatDateShort and FormatDateHourSecShort
3959 $format = $s3;
3960 //$format = ($outputlangs->trans("FormatHourSecShort") != "FormatHourSecShort" ? $outputlangs->trans("FormatHourSecShort") : ($s3 ? $s3 : $conf->format_hour_sec_short));
3961 } elseif ($format == 'hourduration') {
3962 $format = ($outputlangs->trans("FormatHourShortDuration") != "FormatHourShortDuration" ? $outputlangs->trans("FormatHourShortDuration") : $conf->format_hour_short_duration);
3963 } elseif ($format == 'daytext') {
3964 $format = ($outputlangs->trans("FormatDateText") != "FormatDateText" ? $outputlangs->trans("FormatDateText") : $conf->format_date_text);
3965 } elseif ($format == 'daytextshort') {
3966 $format = ($outputlangs->trans("FormatDateTextShort") != "FormatDateTextShort" ? $outputlangs->trans("FormatDateTextShort") : $conf->format_date_text_short);
3967 } elseif ($format == 'dayhour') {
3968 $format = ($outputlangs->trans("FormatDateHourShort") != "FormatDateHourShort" ? $outputlangs->trans("FormatDateHourShort") : $conf->format_date_hour_short);
3969 } elseif ($format == 'dayhoursec') {
3970 $format = ($outputlangs->trans("FormatDateHourSecShort") != "FormatDateHourSecShort" ? $outputlangs->trans("FormatDateHourSecShort") : $conf->format_date_hour_sec_short);
3971 } elseif ($format == 'dayhourtext') {
3972 $format = ($outputlangs->trans("FormatDateHourText") != "FormatDateHourText" ? $outputlangs->trans("FormatDateHourText") : $conf->format_date_hour_text);
3973 } elseif ($format == 'dayhourtextshort') {
3974 $format = ($outputlangs->trans("FormatDateHourTextShort") != "FormatDateHourTextShort" ? $outputlangs->trans("FormatDateHourTextShort") : $conf->format_date_hour_text_short);
3975 } elseif ($format == 'dayhourlog') {
3976 // Format not sensitive to language
3977 $format = '%Y%m%d%H%M%S';
3978 } elseif ($format == 'dayhourlogsmall') {
3979 // Format not sensitive to language
3980 $format = '%y%m%d%H%M';
3981 } elseif ($format == 'dayhourldap') {
3982 $format = '%Y%m%d%H%M%SZ';
3983 } elseif ($format == 'dayhourxcard') {
3984 $format = '%Y%m%dT%H%M%SZ';
3985 } elseif ($format == 'dayxcard') {
3986 $format = '%Y%m%d';
3987 } elseif ($format == 'dayrfc') {
3988 $format = '%Y-%m-%d'; // DATE_RFC3339
3989 } elseif ($format == 'dayhourrfc') {
3990 $format = '%Y-%m-%dT%H:%M:%SZ'; // DATETIME RFC3339
3991 } elseif ($format == 'standard') {
3992 $format = '%Y-%m-%d %H:%M:%S';
3993 }
3994
3995 if ($reduceformat) {
3996 $format = str_replace('%Y', '%y', $format);
3997 $format = str_replace('yyyy', 'yy', $format);
3998 }
3999
4000 // Clean format
4001 if (preg_match('/%b/i', $format)) { // There is some text to translate
4002 // We inhibit translation to text made by strftime functions. We will use trans instead later.
4003 $format = str_replace('%b', '__b__', $format);
4004 $format = str_replace('%B', '__B__', $format);
4005 }
4006 if (preg_match('/%a/i', $format)) { // There is some text to translate
4007 // We inhibit translation to text made by strftime functions. We will use trans instead later.
4008 $format = str_replace('%a', '__a__', $format);
4009 $format = str_replace('%A', '__A__', $format);
4010 }
4011
4012 // Analyze date
4013 $reg = array();
4014 if (preg_match('/^([0-9][0-9][0-9][0-9])([0-9][0-9])([0-9][0-9])([0-9][0-9])([0-9][0-9])([0-9][0-9])$/i', (string) $time, $reg)) { // Deprecated. Ex: 1970-01-01, 1970-01-01 01:00:00, 19700101010000
4015 dol_print_error(null, "Functions.lib::dol_print_date function called with a bad value" . getCallerInfoString());
4016 return '';
4017 } elseif (preg_match('/^([0-9]+)\-([0-9]+)\-([0-9]+) ?([0-9]+)?:?([0-9]+)?:?([0-9]+)?/i', (string) $time, $reg)) { // Still available to solve problems in extrafields of type date
4018 // This part of code should not be used anymore.
4019 dol_syslog("Functions.lib::dol_print_date function called with a bad value" . getCallerInfoString(), LOG_WARNING);
4020 // Date has format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'
4021 $syear = (!empty($reg[1]) ? $reg[1] : '');
4022 $smonth = (!empty($reg[2]) ? $reg[2] : '');
4023 $sday = (!empty($reg[3]) ? $reg[3] : '');
4024 $shour = (!empty($reg[4]) ? $reg[4] : '');
4025 $smin = (!empty($reg[5]) ? $reg[5] : '');
4026 $ssec = (!empty($reg[6]) ? $reg[6] : '');
4027
4028 $time = dol_mktime((int) $shour, (int) $smin, (int) $ssec, (int) $smonth, (int) $sday, (int) $syear, true);
4029
4030 if ($to_gmt) {
4031 $tzo = new DateTimeZone('UTC'); // when to_gmt is true, base for offsettz and offsetdst (so timetouse) is UTC
4032 } else {
4033 $tzo = new DateTimeZone(date_default_timezone_get()); // when to_gmt is false, base for offsettz and offsetdst (so timetouse) is PHP server
4034 }
4035 $dtts = new DateTime();
4036 $dtts->setTimestamp($time);
4037 $dtts->setTimezone($tzo);
4038 $newformat = str_replace(
4039 array('%Y', '%y', '%m', '%d', '%H', '%I', '%M', '%S', '%p', 'T', 'Z', '__a__', '__A__', '__b__', '__B__'),
4040 array('Y', 'y', 'm', 'd', 'H', 'h', 'i', 's', 'A', '__£__', '__$__', '__{__', '__}__', '__[__', '__]__'),
4041 $format
4042 );
4043 $ret = $dtts->format($newformat);
4044 $ret = str_replace(
4045 array('__£__', '__$__', '__{__', '__}__', '__[__', '__]__'),
4046 array('T', 'Z', '__a__', '__A__', '__b__', '__B__'),
4047 $ret
4048 );
4049 } else {
4050 // Date is a timestamps
4051 if ($time < 100000000000) { // Protection against bad date values
4052 $dtts = new DateTime();
4053 //var_dump($tzoutput.' '.$offsettzstring.' '.$offsettz.$offsetdst.' x '.$to_gmt);
4054 if ($to_gmt) { // to_gmt means "not in php server timezone" (if tzoutput = 'gmt', offsets should be 0 but if = 'tzuser...', offsets may be defined
4055 $timetouse = (int) $time + $offsettz + $offsetdst; // TODO We could be able to disable use of offsettz and offsetdst to use only offsettzstring.
4056
4057 $tzo = new DateTimeZone('UTC'); // when to_gmt is true, base for offsettz and offsetdst (so timetouse) is UTC
4058 $dtts->setTimezone($tzo); // important: must be before the setTimestamp
4059 $dtts->setTimestamp($timetouse);
4060 } else {
4061 $timetouse = (int) $time + $offsettz + $offsetdst; // TODO We could be able to disable use of offsettz and offsetdst to use only offsettzstring.
4062
4063 $tzo = new DateTimeZone(date_default_timezone_get()); // when to_gmt is false, base for offsettz and offsetdst (so timetouse) is PHP server
4064 $dtts->setTimestamp($timetouse); // TODO May be we can invert setTimestamp and setTimezone
4065 $dtts->setTimezone($tzo);
4066 }
4067
4068 $newformat = str_replace(
4069 array('%Y', '%y', '%m', '%d', '%H', '%I', '%M', '%S', '%p', '%w', 'T', 'Z', '__a__', '__A__', '__b__', '__B__'),
4070 array('Y', 'y', 'm', 'd', 'H', 'h', 'i', 's', 'A', 'w', '__£__', '__$__', '__{__', '__}__', '__[__', '__]__'),
4071 $format
4072 );
4073
4074 $ret = $dtts->format($newformat);
4075 //var_dump($timetouse, $offsettz, $offsetdst, $tzo, $newformat, $ret);
4076 $ret = str_replace(
4077 array('__£__', '__$__', '__{__', '__}__', '__[__', '__]__'),
4078 array('T', 'Z', '__a__', '__A__', '__b__', '__B__'),
4079 $ret
4080 );
4081 } else {
4082 $ret = 'Bad value ' . $time . ' for date';
4083 }
4084 }
4085
4086 if (preg_match('/__b__/i', $format)) {
4087 $timetouse = $time + $offsettz + $offsetdst; // TODO We could be able to disable use of offsettz and offsetdst to use only offsettzstring.
4088
4089 if ($to_gmt) {
4090 $tzo = new DateTimeZone('UTC'); // when to_gmt is true, base for offsettz and offsetdst (so timetouse) is UTC
4091 } else {
4092 $tzo = new DateTimeZone(date_default_timezone_get()); // when to_gmt is false, base for offsettz and offsetdst (so timetouse) is PHP server
4093 }
4094 $dtts = new DateTime();
4095 $dtts->setTimestamp($timetouse);
4096 $dtts->setTimezone($tzo);
4097 $month = (int) $dtts->format("m");
4098 $month = sprintf("%02d", $month); // $month may be return with format '06' on some installation and '6' on other, so we force it to '06'.
4099 if ($encodetooutput) {
4100 $monthtext = $outputlangs->transnoentities('Month' . $month);
4101 $monthtextshort = $outputlangs->transnoentities('MonthShort' . $month);
4102 } else {
4103 $monthtext = $outputlangs->transnoentitiesnoconv('Month' . $month);
4104 $monthtextshort = $outputlangs->transnoentitiesnoconv('MonthShort' . $month);
4105 }
4106 //print 'monthtext='.$monthtext.' monthtextshort='.$monthtextshort;
4107 $ret = str_replace('__b__', $monthtextshort, $ret);
4108 $ret = str_replace('__B__', $monthtext, $ret);
4109 //print 'x'.$outputlangs->charset_output.'-'.$ret.'x';
4110 //return $ret;
4111 }
4112 if (preg_match('/__a__/i', $format)) {
4113 //print "time=$time offsettz=$offsettz offsetdst=$offsetdst offsettzstring=$offsettzstring";
4114 $timetouse = $time + $offsettz + $offsetdst; // TODO Replace this with function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
4115
4116 if ($to_gmt) {
4117 $tzo = new DateTimeZone('UTC');
4118 } else {
4119 $tzo = new DateTimeZone(date_default_timezone_get());
4120 }
4121 $dtts = new DateTime();
4122 $dtts->setTimestamp($timetouse);
4123 $dtts->setTimezone($tzo);
4124 $w = $dtts->format("w");
4125 $dayweek = $outputlangs->transnoentitiesnoconv('Day' . $w);
4126
4127 $ret = str_replace('__A__', $dayweek, $ret);
4128 $ret = str_replace('__a__', dol_substr($dayweek, 0, 3), $ret);
4129 }
4130
4131 if ($decorate) {
4132 $ret = preg_replace('/(\d\d:\d\d [AP]M)$/', '<span class="'.($decorate === 1 ? 'opacitymedium' : $decorate).'">\1</span>', $ret);
4133 $ret = preg_replace('/(\d\d:\d\d)$/', '<span class="'.($decorate === 1 ? 'opacitymedium' : $decorate).'">\1</span>', $ret);
4134 }
4135
4136 return $ret;
4137}
4138
4139
4160function dol_getdate($timestamp, $fast = false, $forcetimezone = '')
4161{
4162 if ($timestamp === '') {
4163 return array();
4164 }
4165
4166 $datetimeobj = new DateTime();
4167 $datetimeobj->setTimestamp($timestamp); // Use local PHP server timezone
4168 if ($forcetimezone && $forcetimezone != 'tzserver') {
4169 $datetimeobj->setTimezone(new DateTimeZone($forcetimezone == 'gmt' ? 'UTC' : $forcetimezone)); // (add timezone relative to the date entered)
4170 }
4171 $arrayinfo = array(
4172 'year' => ((int) date_format($datetimeobj, 'Y')),
4173 'mon' => ((int) date_format($datetimeobj, 'm')),
4174 'mday' => ((int) date_format($datetimeobj, 'd')),
4175 'wday' => ((int) date_format($datetimeobj, 'w')),
4176 'yday' => ((int) date_format($datetimeobj, 'z')),
4177 'hours' => ((int) date_format($datetimeobj, 'H')),
4178 'minutes' => ((int) date_format($datetimeobj, 'i')),
4179 'seconds' => ((int) date_format($datetimeobj, 's')),
4180 '0' => $timestamp
4181 );
4182
4183 return $arrayinfo;
4184}
4185
4207function dol_mktime($hour, $minute, $second, $month, $day, $year, $gm = 'auto', $check = 1)
4208{
4209 global $conf;
4210 //print "- ".$hour.",".$minute.",".$second.",".$month.",".$day.",".$year.",".$_SERVER["WINDIR"]." -";
4211
4212 if ($gm === 'auto') {
4213 $gm = (empty($conf) ? 'tzserver' : $conf->tzuserinputkey);
4214 }
4215 //print 'gm:'.$gm.' gm === auto:'.($gm === 'auto').'<br>';exit;
4216
4217 // Clean parameters
4218 if ($hour == -1 || empty($hour)) {
4219 $hour = 0;
4220 }
4221 if ($minute == -1 || empty($minute)) {
4222 $minute = 0;
4223 }
4224 if ($second == -1 || empty($second)) {
4225 $second = 0;
4226 }
4227
4228 // Check parameters
4229 if ($check) {
4230 if (!$month || !$day) {
4231 return '';
4232 }
4233 if ($day > 31) {
4234 return '';
4235 }
4236 if ($month > 12) {
4237 return '';
4238 }
4239 if ($hour < 0 || $hour > 24) {
4240 return '';
4241 }
4242 if ($minute < 0 || $minute > 60) {
4243 return '';
4244 }
4245 if ($second < 0 || $second > 60) {
4246 return '';
4247 }
4248 }
4249
4250 if (empty($gm) || ($gm === 'server' || $gm === 'tzserver')) {
4251 $default_timezone = @date_default_timezone_get(); // Example 'Europe/Berlin'
4252 $localtz = new DateTimeZone($default_timezone);
4253 } elseif ($gm === 'user' || $gm === 'tzuser' || $gm === 'tzuserrel') {
4254 // We use dol_tz_string first because it is more reliable.
4255 $default_timezone = (empty($_SESSION["dol_tz_string"]) ? @date_default_timezone_get() : $_SESSION["dol_tz_string"]); // Example 'Europe/Berlin'
4256 try {
4257 $localtz = new DateTimeZone($default_timezone);
4258 } catch (Exception $e) {
4259 dol_syslog("Warning dol_tz_string contains an invalid value " . json_encode($_SESSION["dol_tz_string"] ?? null), LOG_WARNING);
4260 $default_timezone = @date_default_timezone_get();
4261 }
4262 } elseif (strrpos($gm, "tz,") !== false) {
4263 $timezone = (string) str_replace("tz,", "", $gm); // Example 'tz,Europe/Berlin'
4264 try {
4265 $localtz = new DateTimeZone($timezone);
4266 } catch (Exception $e) {
4267 dol_syslog("Warning passed timezone contains an invalid value " . $timezone, LOG_WARNING);
4268 }
4269 }
4270
4271 if (empty($localtz)) {
4272 $localtz = new DateTimeZone('UTC');
4273 }
4274 $dt = new DateTime('now', $localtz);
4275 $dt->setDate((int) $year, (int) $month, (int) $day);
4276 $dt->setTime((int) $hour, (int) $minute, (int) $second);
4277 $date = $dt->getTimestamp(); // should include daylight saving time
4278
4279 return $date;
4280}
4281
4282
4293function dol_now($mode = 'gmt')
4294{
4295 $ret = 0;
4296
4297 if ($mode === 'auto') {
4298 $mode = 'gmt';
4299 }
4300
4301 if ($mode == 'gmt') {
4302 $ret = time(); // Time for now at greenwich.
4303 } elseif ($mode == 'tzserver') { // Time for now with PHP server timezone added
4304 require_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php';
4305 $tzsecond = getServerTimeZoneInt('now'); // Contains tz+dayling saving time
4306 $ret = (int) (dol_now('gmt') + ($tzsecond * 3600));
4307 // } elseif ($mode == 'tzref') {// Time for now with parent company timezone is added
4308 // require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
4309 // $tzsecond=getParentCompanyTimeZoneInt(); // Contains tz+dayling saving time
4310 // $ret=dol_now('gmt')+($tzsecond*3600);
4311 } elseif ($mode == 'tzuser' || $mode == 'tzuserrel') {
4312 // Time for now with user timezone added
4313 // print 'time: '.time();
4314 $offsettz = (empty($_SESSION['dol_tz']) ? 0 : $_SESSION['dol_tz']) * 60 * 60;
4315 $offsetdst = (empty($_SESSION['dol_dst']) ? 0 : $_SESSION['dol_dst']) * 60 * 60;
4316 $ret = (int) (dol_now('gmt') + ($offsettz + $offsetdst));
4317 }
4318
4319 return $ret;
4320}
4321
4322
4331function dol_print_size($size, $shortvalue = 0, $shortunit = 0)
4332{
4333 global $conf, $langs;
4334 $level = 1024;
4335
4336 if (!empty($conf->dol_optimize_smallscreen)) {
4337 $shortunit = 1;
4338 }
4339
4340 // Set value text
4341 if (empty($shortvalue) || $size < ($level * 10)) {
4342 $ret = $size;
4343 $textunitshort = $langs->trans("b");
4344 $textunitlong = $langs->trans("Bytes");
4345 } else {
4346 $ret = round($size / $level, 0);
4347 $textunitshort = $langs->trans("Kb");
4348 $textunitlong = $langs->trans("KiloBytes");
4349 }
4350 // Use long or short text unit
4351 if (empty($shortunit)) {
4352 $ret .= ' ' . $textunitlong;
4353 } else {
4354 $ret .= ' ' . $textunitshort;
4355 }
4356
4357 return $ret;
4358}
4359
4370function dol_print_url($url, $target = '_blank', $max = 32, $withpicto = 0, $morecss = '')
4371{
4372 global $langs;
4373
4374 if (empty($url)) {
4375 return '';
4376 }
4377
4378 $linkstart = '<a href="';
4379 if (!preg_match('/^http/i', $url)) {
4380 $linkstart .= 'http://';
4381 }
4382 $linkstart .= $url;
4383 $linkstart .= '"';
4384 if ($target) {
4385 $linkstart .= ' target="' . $target . '"';
4386 }
4387 $linkstart .= ' title="' . $langs->trans("URL") . ': ' . $url . '"';
4388 $linkstart .= '>';
4389
4390 $link = '';
4391 if (!preg_match('/^http/i', $url)) {
4392 $link .= 'http://';
4393 }
4394 $link .= dol_trunc($url, $max);
4395
4396 $linkend = '</a>';
4397
4398 if ($morecss == 'float') { // deprecated
4399 return '<div class="nospan' . ($morecss ? ' ' . $morecss : '') . '" style="margin-right: 10px">' . ($withpicto ? img_picto($langs->trans("Url"), 'globe', 'class="paddingrightonly"') : '') . $link . '</div>';
4400 } else {
4401 return $linkstart . '<span class="nospan' . ($morecss ? ' ' . $morecss : '') . '" style="margin-right: 10px">' . ($withpicto ? img_picto('', 'globe', 'class="paddingrightonly"') : '') . $link . '</span>' . $linkend;
4402 }
4403}
4404
4418function dol_print_email($email, $contactid = 0, $socid = 0, $addlink = 0, $max = 0, $showinvalid = 2, $withpicto = 0, $morecss = 'paddingrightonly')
4419{
4420 global $user, $langs, $hookmanager;
4421
4422 //global $conf; $conf->global->AGENDA_ADDACTIONFOREMAIL = 1;
4423 //$showinvalid = 1; $email = 'rrrrr';
4424
4425 $newemail = dol_escape_htmltag($email);
4426
4427 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER') && $withpicto) {
4428 $withpicto = 0;
4429 }
4430
4431 if (empty($email)) {
4432 return '&nbsp;';
4433 }
4434
4435 if ($addlink == 1) {
4436 $newemail = '<a class="' . ($morecss ? $morecss : '') . '" style="text-overflow: ellipsis;" href="';
4437 if (!preg_match('/^mailto:/i', $email)) {
4438 $newemail .= 'mailto:';
4439 }
4440 $newemail .= $email;
4441 $newemail .= '" target="_blank">';
4442
4443 $newemail .= ($withpicto ? img_picto($langs->trans("EMail") . ' : ' . $email, (is_numeric($withpicto) ? 'email' : $withpicto), 'class="paddingrightonly"') : '');
4444
4445 if ($max > 0) {
4446 $newemail .= dol_trunc($email, $max);
4447 } else {
4448 $newemail .= $email;
4449 }
4450 $newemail .= '</a>';
4451
4452 if ($showinvalid) {
4453 include_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
4454 include_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
4455 $emailonly = CMailFile::getValidAddress($email, 2);
4456 if (!isValidEmail($emailonly)) {
4457 $langs->load("errors");
4458 $newemail .= img_warning($langs->transnoentitiesnoconv("ErrorBadEMail", $emailonly), '', 'paddingrightonly');
4459 } elseif ($showinvalid == 2 && !isValidMailDomain($emailonly)) {
4460 $langs->load("errors");
4461 $newemail .= img_warning($langs->transnoentitiesnoconv("ErrorBadMXDomain", $emailonly), '', 'paddingrightonly');
4462 }
4463 }
4464
4465 if (($contactid || $socid) && isModEnabled('agenda') && $user->hasRight("agenda", "myactions", "create")) {
4466 $type = 'AC_EMAIL';
4467 $linktoaddaction = '';
4468 if (getDolGlobalString('AGENDA_ADDACTIONFOREMAIL')) {
4469 $linktoaddaction = '<a href="' . DOL_URL_ROOT . '/comm/action/card.php?action=create&backtopage=1&actioncode=' . urlencode($type) . '&contactid=' . ((int) $contactid) . '&socid=' . ((int) $socid) . '">' . img_object($langs->trans("AddAction"), "calendar") . '</a>';
4470 }
4471 if ($linktoaddaction) {
4472 $newemail = '<div>' . $newemail . ' ' . $linktoaddaction . '</div>';
4473 }
4474 }
4475 } elseif ($addlink === 'thirdparty') {
4476 $tmpnewemail = '<a class="' . ($morecss ? $morecss : '') . '" style="text-overflow: ellipsis;" href="' . DOL_URL_ROOT . '/societe/card.php?socid=' . $socid . '&action=presend&mode=init#formmailbeforetitle">';
4477 $tmpnewemail .= ($withpicto ? img_picto($langs->trans("EMail") . ' : ' . $email, (is_numeric($withpicto) ? 'email' : $withpicto), 'class="paddingrightonly"') : '');
4478 if ($withpicto == 1) {
4479 $tmpnewemail .= $newemail;
4480 }
4481 $tmpnewemail .= '</a>';
4482
4483 $newemail = $tmpnewemail;
4484 } else {
4485 $newemail = ($withpicto ? img_picto($langs->trans("EMail") . ' : ' . $email, (is_numeric($withpicto) ? 'email' : $withpicto), 'class="paddingrightonly"') : '') . $newemail;
4486
4487 if ($showinvalid) {
4488 include_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
4489 include_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
4490 $emailonly = CMailFile::getValidAddress($email, 2);
4491 if (!isValidEmail($emailonly)) {
4492 $langs->load("errors");
4493 $newemail .= img_warning($langs->transnoentitiesnoconv("ErrorBadEMail", $email));
4494 } elseif ($showinvalid == 2 && !isValidMailDomain($emailonly)) {
4495 $langs->load("errors");
4496 $newemail .= img_warning($langs->transnoentitiesnoconv("ErrorBadMXDomain", $emailonly));
4497 }
4498 }
4499 }
4500
4501 //$rep = '<div class="nospan" style="margin-right: 10px">';
4502 //$rep = ($withpicto ? img_picto($langs->trans("EMail").' : '.$email, (is_numeric($withpicto) ? 'email' : $withpicto), 'class="paddingrightonly"') : '').$newemail;
4503 //$rep .= '</div>';
4504 $rep = $newemail;
4505 if (getDolGlobalString('MAIN_MAIL_COPY_ON_CLICK')) {
4506 $rep .= showValueWithClipboardCPButton($newemail, 0, 'none');
4507 }
4508
4509 if ($hookmanager) {
4510 $parameters = array('cid' => $contactid, 'socid' => $socid, 'addlink' => $addlink, 'picto' => $withpicto);
4511
4512 $reshook = $hookmanager->executeHooks('printEmail', $parameters, $email);
4513 if ($reshook > 0) {
4514 $rep = '';
4515 }
4516 $rep .= $hookmanager->resPrint;
4517 }
4518
4519 return $rep;
4520}
4521
4522
4538function dolOutputDates($datep, $datef = null, $fullday = 0, $addseconds = 0, $pictotoadd = '', $tzoutput = 'tzuserrel', $reduceformat = 0)
4539{
4540 $tmpa = dol_getdate($datep);
4541 if (empty($datef)) {
4542 $tmpb = $tmpa;
4543 } else {
4544 $tmpb = dol_getdate($datef);
4545 }
4546
4547 $s = '';
4548
4549 if ($tmpa['mday'] == $tmpb['mday'] && $tmpa['mon'] == $tmpb['mon'] && $tmpa['year'] == $tmpb['year']) {
4550 // The same day
4551 $s .= '<div class="center inline-block">';
4552 if ($tmpa['hours'] != $tmpb['hours'] || $tmpa['minutes'] != $tmpb['minutes']) {
4553 // Not the same hour
4554 $s .= dol_print_date($datep, 'day'.($reduceformat ? 'reduceformat' : ''), $tzoutput);
4555 $s .= $pictotoadd;
4556 if (empty($fullday)) {
4557 $s .= '<br><span class="small opacitymedium">';
4558 $s .= dol_print_date($datep, 'hour'.($addseconds ? 'sec' : '').'reduceformat', $tzoutput);
4559 $s .= '-'.dol_print_date($datef, 'hour'.($addseconds ? 'sec' : '').'reduceformat', $tzoutput);
4560 $s .= '</span>';
4561 }
4562 } else {
4563 // The same hour
4564 $s .= dol_print_date($datep, 'day'.($reduceformat ? 'reduceformat' : ''), 'tzuserrel');
4565 $s .= $pictotoadd;
4566 if (empty($fullday)) {
4567 $s .= '<br><span class="small opacitymedium">';
4568 $s .= dol_print_date($datep, 'hour'.($addseconds ? 'sec' : '').'reduceformat', $tzoutput);
4569 $s .= '</span>';
4570 }
4571 }
4572 $s .= '</div>';
4573 } else {
4574 // Not the same day
4575 $s .= '<div class="center inline-block dateborderright">';
4576 $s .= dol_print_date($datep, 'day'.($reduceformat ? 'reduceformat' : ''), $tzoutput);
4577 if (empty($fullday)) {
4578 $s .= '<br><span class="small opacitymedium">';
4579 $s .= dol_print_date($datep, 'hour'.($addseconds ? 'sec' : '').'reduceformat', $tzoutput);
4580 $s .= '</span>';
4581 }
4582 $s .= '</div>';
4583 $s .= '<div class="center inline-block dateborderleft">';
4584 $s .= dol_print_date($datef, 'day'.($reduceformat ? 'reduceformat' : ''), 'tzuserrel');
4585 $s .= $pictotoadd;
4586 if (empty($fullday)) {
4587 $s .= '<br><span class="small opacitymedium">';
4588 $s .= dol_print_date($datef, 'hour'.($addseconds ? 'sec' : '').'reduceformat', $tzoutput);
4589 $s .= '</span>';
4590 }
4591 $s .= '</div>';
4592 }
4593
4594 return $s;
4595}
4596
4597
4603function getArrayOfSocialNetworks()
4604{
4605 global $db;
4606
4607 $socialnetworks = array();
4608 // Enable caching of array
4609 require_once DOL_DOCUMENT_ROOT . '/core/lib/memory.lib.php';
4610 $cachekey = dol_sanitizeKeyCode(str_replace(',', '_', 'socialnetworks_'.getEntity('c_socialnetworks')));
4611 $dataretrieved = dol_getcache($cachekey);
4612
4613 if (!is_null($dataretrieved)) {
4614 $socialnetworks = $dataretrieved;
4615 } else {
4616 $sql = "SELECT rowid, code, label, url, icon, active FROM " . MAIN_DB_PREFIX . "c_socialnetworks";
4617 $sql .= " WHERE entity IN (" . getEntity('c_socialnetworks').")";
4618
4619 $resql = $db->query($sql);
4620 if ($resql) {
4621 while ($obj = $db->fetch_object($resql)) {
4622 $socialnetworks[$obj->code] = array(
4623 'rowid' => $obj->rowid,
4624 'label' => $obj->label,
4625 'url' => $obj->url,
4626 'icon' => $obj->icon,
4627 'active' => $obj->active,
4628 );
4629 }
4630 }
4631 dol_setcache($cachekey, $socialnetworks); // If setting cache fails, this is not a problem, so we do not test result.
4632 }
4633
4634 return (is_array($socialnetworks) ? $socialnetworks : array());
4635}
4636
4647function dol_print_socialnetworks($value, $contactid, $socid, $type, $dictsocialnetworks = array())
4648{
4649 global $hookmanager, $langs, $user;
4650
4651 $htmllink = $value;
4652
4653 if (empty($value)) {
4654 return '&nbsp;';
4655 }
4656
4657 if (!empty($type)) {
4658 $htmllink = '<div class="divsocialnetwork inline-block valignmiddle">';
4659 // Use dictionary definition for picto $dictsocialnetworks[$type]['icon']
4660 $htmllink .= '<span class="fab pictofixedwidth ' . ($dictsocialnetworks[$type]['icon'] ? $dictsocialnetworks[$type]['icon'] : 'fa-link') . '"></span>';
4661 if ($type == 'skype') {
4662 $htmllink .= dol_escape_htmltag($value);
4663 $htmllink .= '&nbsp; <a href="skype:';
4664 $htmllink .= dol_string_nospecial($value, '_', '', array('@'));
4665 $htmllink .= '?call" alt="' . $langs->trans("Call") . '&nbsp;' . $value . '" title="' . dol_escape_htmltag($langs->trans("Call") . ' ' . $value) . '">';
4666 $htmllink .= '<img src="' . DOL_URL_ROOT . '/theme/common/skype_callbutton.png" border="0">';
4667 $htmllink .= '</a><a href="skype:';
4668 $htmllink .= dol_string_nospecial($value, '_', '', array('@'));
4669 $htmllink .= '?chat" alt="' . $langs->trans("Chat") . '&nbsp;' . $value . '" title="' . dol_escape_htmltag($langs->trans("Chat") . ' ' . $value) . '">';
4670 $htmllink .= '<img class="paddingleft" src="' . DOL_URL_ROOT . '/theme/common/skype_chatbutton.png" border="0">';
4671 $htmllink .= '</a>';
4672 if (($contactid || $socid) && isModEnabled('agenda') && $user->hasRight('agenda', 'myactions', 'create')) {
4673 $addlink = 'AC_SKYPE';
4674 $link = '';
4675 if (getDolGlobalString('AGENDA_ADDACTIONFORSKYPE')) {
4676 $link = '<a href="' . DOL_URL_ROOT . '/comm/action/card.php?action=create&backtopage=1&actioncode=' . $addlink . '&contactid=' . $contactid . '&socid=' . $socid . '">' . img_object($langs->trans("AddAction"), "calendar") . '</a>';
4677 }
4678 $htmllink .= ($link ? ' ' . $link : '');
4679 }
4680 } else {
4681 if (!empty($dictsocialnetworks[$type]['url'])) {
4682 $tmpvirginurl = preg_replace('/\/?{socialid}/', '', $dictsocialnetworks[$type]['url']);
4683 if ($tmpvirginurl) {
4684 $value = preg_replace('/^www\.' . preg_quote($tmpvirginurl, '/') . '\/?/', '', $value);
4685 $value = preg_replace('/^' . preg_quote($tmpvirginurl, '/') . '\/?/', '', $value);
4686
4687 $tmpvirginurl3 = preg_replace('/^https:\/\//i', 'https://www.', $tmpvirginurl);
4688 if ($tmpvirginurl3) {
4689 $value = preg_replace('/^www\.' . preg_quote($tmpvirginurl3, '/') . '\/?/', '', $value);
4690 $value = preg_replace('/^' . preg_quote($tmpvirginurl3, '/') . '\/?/', '', $value);
4691 }
4692
4693 $tmpvirginurl2 = preg_replace('/^https?:\/\//i', '', $tmpvirginurl);
4694 if ($tmpvirginurl2) {
4695 $value = preg_replace('/^www\.' . preg_quote($tmpvirginurl2, '/') . '\/?/', '', $value);
4696 $value = preg_replace('/^' . preg_quote($tmpvirginurl2, '/') . '\/?/', '', $value);
4697 }
4698 }
4699 if (preg_match('/^https?:\/\//i', $value)) {
4700 $link = $value;
4701 } else {
4702 $link = str_replace('{socialid}', $value, $dictsocialnetworks[$type]['url']);
4703 }
4704 $valuetoshow = $value;
4705 $valuetoshow = preg_replace('/https:\/\/www\.(twitter|x|linkedin)\.com\/?/', '', $valuetoshow);
4706 if (preg_match('/^https?:\/\//i', $link)) {
4707 $htmllink .= '<a href="' . dol_sanitizeUrl($link, 0) . '" target="_blank" rel="noopener noreferrer">' . dol_escape_htmltag($valuetoshow) . '</a>';
4708 } else {
4709 $htmllink .= '<a href="' . dol_sanitizeUrl($link, 1) . '" target="_blank" rel="noopener noreferrer">' . dol_escape_htmltag($valuetoshow) . '</a>';
4710 }
4711 } else {
4712 $htmllink .= dol_escape_htmltag($value);
4713 }
4714 }
4715 $htmllink .= '</div>';
4716 } else {
4717 $langs->load("errors");
4718 $htmllink .= img_warning($langs->trans("ErrorBadSocialNetworkValue", $value));
4719 }
4720
4721 if ($hookmanager) {
4722 $parameters = array(
4723 'value' => $value,
4724 'cid' => $contactid,
4725 'socid' => $socid,
4726 'type' => $type,
4727 'dictsocialnetworks' => $dictsocialnetworks,
4728 );
4729
4730 $reshook = $hookmanager->executeHooks('printSocialNetworks', $parameters);
4731 if ($reshook > 0) {
4732 $htmllink = '';
4733 }
4734 $htmllink .= $hookmanager->resPrint;
4735 }
4736
4737 return $htmllink;
4738}
4739
4749function dol_print_profids($profID, $profIDtype, $countrycode = '', $addcpButton = 1)
4750{
4751 global $mysoc;
4752
4753 if (empty($profID) || empty($profIDtype)) {
4754 return '';
4755 }
4756 if (empty($countrycode)) {
4757 $countrycode = $mysoc->country_code;
4758 }
4759 $newProfID = $profID;
4760 $id = substr($profIDtype, -1);
4761 $ret = '';
4762 if (strtoupper($countrycode) == 'FR') {
4763 // France
4764 // (see https://www.economie.gouv.fr/entreprises/numeros-identification-entreprise)
4765
4766 if ($id == 1 && dol_strlen($newProfID) == 9) {
4767 // SIREN (ex: 123 123 123)
4768 $newProfID = substr($newProfID, 0, 3) . ' ' . substr($newProfID, 3, 3) . ' ' . substr($newProfID, 6, 3);
4769 }
4770 if ($id == 2 && dol_strlen($newProfID) == 14) {
4771 // SIRET (ex: 123 123 123 12345)
4772 $newProfID = substr($newProfID, 0, 3) . ' ' . substr($newProfID, 3, 3) . ' ' . substr($newProfID, 6, 3) . ' ' . substr($newProfID, 9, 5);
4773 }
4774 if ($id == 3 && dol_strlen($newProfID) == 5) {
4775 // NAF/APE (ex: 69.20Z)
4776 $newProfID = substr($newProfID, 0, 2) . '.' . substr($newProfID, 2, 3);
4777 }
4778 if ($profIDtype === 'VAT' && dol_strlen($newProfID) == 13) {
4779 // TVA intracommunautaire (ex: FR12 123 123 123)
4780 $newProfID = substr($newProfID, 0, 4) . ' ' . substr($newProfID, 4, 3) . ' ' . substr($newProfID, 7, 3) . ' ' . substr($newProfID, 10, 3);
4781 }
4782 }
4783 if (!empty($addcpButton)) {
4784 $ret = showValueWithClipboardCPButton(dol_escape_htmltag($profID), ($addcpButton == 1 ? 1 : 0), $newProfID);
4785 } else {
4786 $ret = $newProfID;
4787 }
4788 return $ret;
4789}
4790
4806function dol_print_phone($phone, $countrycode = '', $contactid = 0, $socid = 0, $addlink = '', $separ = "&nbsp;", $withpicto = '', $titlealt = '', $adddivfloat = 0, $morecss = 'paddingright')
4807{
4808 global $conf, $user, $langs, $mysoc, $hookmanager;
4809
4810 // Clean phone parameter
4811 $phone = is_null($phone) ? '' : preg_replace("/[\s.-]/", "", trim($phone));
4812 if (empty($phone)) {
4813 return '';
4814 }
4815 if (getDolGlobalString('MAIN_PHONE_SEPAR')) {
4816 $separ = getDolGlobalString('MAIN_PHONE_SEPAR');
4817 }
4818 if (empty($countrycode) && is_object($mysoc)) {
4819 $countrycode = $mysoc->country_code;
4820 }
4821
4822 // Short format for small screens
4823 if (!empty($conf->dol_optimize_smallscreen) && $separ != 'hidenum') {
4824 $separ = '';
4825 }
4826
4827 $newphone = $phone;
4828 $newphonewa = $phone;
4829 if (strtoupper($countrycode) == "FR") {
4830 // France
4831 if (dol_strlen($phone) == 10) {
4832 $newphone = substr($newphone, 0, 2) . $separ . substr($newphone, 2, 2) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2);
4833 } elseif (dol_strlen($phone) == 7) {
4834 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 2);
4835 } elseif (dol_strlen($phone) == 9) {
4836 $newphone = substr($newphone, 0, 2) . $separ . substr($newphone, 2, 3) . $separ . substr($newphone, 5, 2) . $separ . substr($newphone, 7, 2);
4837 } elseif (dol_strlen($phone) == 11) {
4838 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 2) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2);
4839 } elseif (dol_strlen($phone) == 12) {
4840 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 1) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
4841 } elseif (dol_strlen($phone) == 13) {
4842 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 3) . $separ . substr($newphone, 11, 2);
4843 }
4844 } elseif (strtoupper($countrycode) == "CA") {
4845 if (dol_strlen($phone) == 10) {
4846 $newphone = ($separ != '' ? '(' : '') . substr($newphone, 0, 3) . ($separ != '' ? ')' : '') . $separ . substr($newphone, 3, 3) . ($separ != '' ? '-' : '') . substr($newphone, 6, 4);
4847 }
4848 } elseif (strtoupper($countrycode) == "PT") { //Portugal
4849 if (dol_strlen($phone) == 13) { //ex: +351_ABC_DEF_GHI
4850 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 3);
4851 }
4852 } elseif (strtoupper($countrycode) == "SR") { //Suriname
4853 if (dol_strlen($phone) == 10) { //ex: +597_ABC_DEF
4854 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3);
4855 } elseif (dol_strlen($phone) == 11) { //ex: +597_ABC_DEFG
4856 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 4);
4857 }
4858 } elseif (strtoupper($countrycode) == "DE") { //Deutschland
4859 if (dol_strlen($phone) == 14) { //ex: +49_ABCD_EFGH_IJK
4860 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 4) . $separ . substr($newphone, 7, 4) . $separ . substr($newphone, 11, 3);
4861 } elseif (dol_strlen($phone) == 13) { //ex: +49_ABC_DEFG_HIJ
4862 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 4) . $separ . substr($newphone, 10, 3);
4863 }
4864 } elseif (strtoupper($countrycode) == "ES") { //Spain
4865 if (dol_strlen($phone) == 12) { //ex: +34_ABC_DEF_GHI
4866 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
4867 }
4868 } elseif (strtoupper($countrycode) == "BF") { // Burkina Faso
4869 if (dol_strlen($phone) == 12) { //ex : +22 A BC_DE_FG_HI
4870 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 1) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
4871 }
4872 } elseif (strtoupper($countrycode) == "RO") { // Roumanie
4873 if (dol_strlen($phone) == 12) { //ex : +40 AB_CDE_FG_HI
4874 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
4875 }
4876 } elseif (strtoupper($countrycode) == "TR") { //Turquie
4877 if (dol_strlen($phone) == 13) { //ex : +90 ABC_DEF_GHIJ
4878 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 4);
4879 }
4880 } elseif (strtoupper($countrycode) == "US") { //Etat-Unis
4881 if (dol_strlen($phone) == 12) { //ex: +1 ABC_DEF_GHIJ
4882 $newphone = substr($newphone, 0, 2) . $separ . substr($newphone, 2, 3) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 4);
4883 }
4884 } elseif (strtoupper($countrycode) == "MX") { //Mexique
4885 if (dol_strlen($phone) == 12) { //ex: +52 ABCD_EFG_HI
4886 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 4) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 2);
4887 } elseif (dol_strlen($phone) == 11) { //ex: +52 AB_CD_EF_GH
4888 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 2) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2);
4889 } elseif (dol_strlen($phone) == 13) { //ex: +52 ABC_DEF_GHIJ
4890 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 4);
4891 }
4892 } elseif (strtoupper($countrycode) == "ML") { //Mali
4893 if (dol_strlen($phone) == 12) { //ex: +223 AB_CD_EF_GH
4894 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
4895 }
4896 } elseif (strtoupper($countrycode) == "TH") { //Thailand
4897 if (dol_strlen($phone) == 11) { //ex: +66_ABC_DE_FGH
4898 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 3);
4899 } elseif (dol_strlen($phone) == 12) { //ex: +66_A_BCD_EF_GHI
4900 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 1) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 3);
4901 }
4902 } elseif (strtoupper($countrycode) == "MU") {
4903 //Maurice
4904 if (dol_strlen($phone) == 11) { //ex: +230_ABC_DE_FG
4905 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2);
4906 } elseif (dol_strlen($phone) == 12) { //ex: +230_ABCD_EF_GH
4907 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 4) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
4908 }
4909 } elseif (strtoupper($countrycode) == "ZA") { //Afrique du sud
4910 if (dol_strlen($phone) == 12) { //ex: +27_AB_CDE_FG_HI
4911 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
4912 }
4913 } elseif (strtoupper($countrycode) == "SY") { //Syrie
4914 if (dol_strlen($phone) == 12) { //ex: +963_AB_CD_EF_GH
4915 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
4916 } elseif (dol_strlen($phone) == 13) { //ex: +963_AB_CD_EF_GHI
4917 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 3);
4918 }
4919 } elseif (strtoupper($countrycode) == "AE") { //Emirats Arabes Unis
4920 if (dol_strlen($phone) == 12) { //ex: +971_ABC_DEF_GH
4921 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 2);
4922 } elseif (dol_strlen($phone) == 13) { //ex: +971_ABC_DEF_GHI
4923 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 3);
4924 } elseif (dol_strlen($phone) == 14) { //ex: +971_ABC_DEF_GHIK
4925 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 4);
4926 }
4927 } elseif (strtoupper($countrycode) == "DZ") { //Algeria
4928 if (dol_strlen($phone) == 13) { //ex: +213_ABC_DEF_GHI
4929 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 3);
4930 }
4931 } elseif (strtoupper($countrycode) == "BE") { //Belgique
4932 if (dol_strlen($phone) == 11) { //ex: +32_ABC_DE_FGH
4933 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 3);
4934 } elseif (dol_strlen($phone) == 12) { //ex: +32_ABC_DEF_GHI
4935 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
4936 }
4937 } elseif (strtoupper($countrycode) == "PF") { //French Polynesia
4938 if (dol_strlen($phone) == 12) { //ex: +689_AB_CD_EF_GH
4939 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
4940 }
4941 } elseif (strtoupper($countrycode) == "CO") { //Colombie
4942 if (dol_strlen($phone) == 13) { //ex: +57_ABC_DEF_GH_IJ
4943 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 2) . $separ . substr($newphone, 11, 2);
4944 }
4945 } elseif (strtoupper($countrycode) == "JO") { //Jordanie
4946 if (dol_strlen($phone) == 12) { //ex: +962_A_BCD_EF_GH
4947 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 1) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
4948 }
4949 } elseif (strtoupper($countrycode) == "JM") { //Jamaica
4950 if (dol_strlen($newphone) == 12) { //ex: +1867_ABC_DEFG
4951 $newphone = substr($newphone, 0, 5) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 4);
4952 }
4953 } elseif (strtoupper($countrycode) == "MG") { //Madagascar
4954 if (dol_strlen($phone) == 13) { //ex: +261_AB_CD_EFG_HI
4955 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 3) . $separ . substr($newphone, 11, 2);
4956 }
4957 } elseif (strtoupper($countrycode) == "GB") { //Royaume uni
4958 if (dol_strlen($phone) == 13) { //ex: +44_ABCD_EFG_HIJ
4959 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 4) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 3);
4960 }
4961 } elseif (strtoupper($countrycode) == "CH") { //Suisse
4962 if (dol_strlen($phone) == 12) { //ex: +41_AB_CDE_FG_HI
4963 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
4964 } elseif (dol_strlen($phone) == 15) { // +41_AB_CDE_FGH_IJKL
4965 $newphone = $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 3) . $separ . substr($newphone, 11, 4);
4966 }
4967 } elseif (strtoupper($countrycode) == "TN") { //Tunisie
4968 if (dol_strlen($phone) == 12) { //ex: +216_AB_CDE_FGH
4969 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
4970 }
4971 } elseif (strtoupper($countrycode) == "GF") { //Guyane francaise
4972 if (dol_strlen($phone) == 13) { //ex: +594_ABC_DE_FG_HI (ABC=594 de nouveau)
4973 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2) . $separ . substr($newphone, 11, 2);
4974 }
4975 } elseif (strtoupper($countrycode) == "GP") { //Guadeloupe
4976 if (dol_strlen($phone) == 13) { //ex: +590_ABC_DE_FG_HI (ABC=590 de nouveau)
4977 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2) . $separ . substr($newphone, 11, 2);
4978 }
4979 } elseif (strtoupper($countrycode) == "MQ") { //Martinique
4980 if (dol_strlen($phone) == 13) { //ex: +596_ABC_DE_FG_HI (ABC=596 de nouveau)
4981 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2) . $separ . substr($newphone, 11, 2);
4982 }
4983 } elseif (strtoupper($countrycode) == "IT") { //Italie
4984 if (dol_strlen($phone) == 12) { //ex: +39_ABC_DEF_GHI
4985 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
4986 } elseif (dol_strlen($phone) == 13) { //ex: +39_ABC_DEF_GH_IJ
4987 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 2) . $separ . substr($newphone, 11, 2);
4988 }
4989 } elseif (strtoupper($countrycode) == "AU") {
4990 //Australie
4991 if (dol_strlen($phone) == 12) {
4992 //ex: +61_A_BCDE_FGHI
4993 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 1) . $separ . substr($newphone, 4, 4) . $separ . substr($newphone, 8, 4);
4994 }
4995 } elseif (strtoupper($countrycode) == "LU") {
4996 // Luxembourg
4997 if (dol_strlen($phone) == 10) { // fix 6 digits +352_AA_BB_CC
4998 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2);
4999 } elseif (dol_strlen($phone) == 11) { // fix 7 digits +352_AA_BB_CC_D
5000 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 1);
5001 } elseif (dol_strlen($phone) == 12) { // fix 8 digits +352_AA_BB_CC_DD
5002 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
5003 } elseif (dol_strlen($phone) == 13) { // mobile +352_AAA_BB_CC_DD
5004 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2) . $separ . substr($newphone, 11, 2);
5005 }
5006 } elseif (strtoupper($countrycode) == "PE") {
5007 // Peru
5008 if (dol_strlen($phone) == 7) { // fix 7 numbers without code AAA_BBBB
5009 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 4);
5010 } elseif (dol_strlen($phone) == 9) { // mobile add code and fix 9 numbers +51_AAA_BBB_CCC
5011 $newphonewa = '+51' . $newphone;
5012 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3);
5013 } elseif (dol_strlen($phone) == 11) { // fix 11 numbers +511_AAA_BBBB
5014 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 4);
5015 } elseif (dol_strlen($phone) == 12) { // mobile +51_AAA_BBB_CCC
5016 $newphonewa = $newphone;
5017 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
5018 }
5019 } elseif (strtoupper($countrycode) == "IN") { //India
5020 if (dol_strlen($phone) == 13) {
5021 if ($withpicto == 'phone') { //ex: +91_AB_CDEF_GHIJ
5022 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 4) . $separ . substr($newphone, 9, 4);
5023 } else { //ex: +91_ABCDE_FGHIJ
5024 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 5) . $separ . substr($newphone, 8, 5);
5025 }
5026 }
5027 }
5028
5029 $newphoneastart = $newphoneaend = '';
5030 if (!empty($addlink)) { // Link on phone number (+ link to add action if conf->global->AGENDA_ADDACTIONFORPHONE set)
5031 if ($addlink == 'tel' || $conf->browser->layout == 'phone' || (isModEnabled('clicktodial') && getDolGlobalString('CLICKTODIAL_USE_TEL_LINK_ON_PHONE_NUMBERS'))) { // If phone or option for, we use link of phone
5032 $newphoneastart = '<a href="tel:' . urlencode($phone) . '">';
5033 $newphoneaend .= '</a>';
5034 } elseif (isModEnabled('clicktodial') && $addlink == 'AC_TEL') { // If click to dial, we use click to dial url
5035 if (empty($user->clicktodial_loaded)) {
5036 $user->fetch_clicktodial();
5037 }
5038
5039 // Define urlmask
5040 $urlmask = getDolGlobalString('CLICKTODIAL_URL', 'ErrorClickToDialModuleNotConfigured');
5041 if (!empty($user->clicktodial_url)) {
5042 $urlmask = $user->clicktodial_url;
5043 }
5044
5045 $clicktodial_poste = (!empty($user->clicktodial_poste) ? urlencode($user->clicktodial_poste) : '');
5046 $clicktodial_login = (!empty($user->clicktodial_login) ? urlencode($user->clicktodial_login) : '');
5047 $clicktodial_password = (!empty($user->clicktodial_password) ? urlencode($user->clicktodial_password) : '');
5048 // This line is for backward compatibility @phan-suppress-next-line PhanPluginPrintfVariableFormatString
5049 $url = sprintf($urlmask, urlencode($phone), $clicktodial_poste, $clicktodial_login, $clicktodial_password);
5050 // Those lines are for substitution
5051 $substitarray = array(
5052 '__PHONEFROM__' => $clicktodial_poste,
5053 '__PHONETO__' => urlencode($phone),
5054 '__LOGIN__' => $clicktodial_login,
5055 '__PASS__' => $clicktodial_password
5056 );
5057 $url = make_substitutions($url, $substitarray);
5058 if (!getDolGlobalString('CLICKTODIAL_DO_NOT_USE_AJAX_CALL')) {
5059 // Default and recommended: New method using ajax without submitting a page making a javascript history.go(-1) back
5060 $newphoneastart = '<a href="' . $url . '" class="cssforclicktodial">'; // Call of ajax is handled by the lib_foot.js.php on class 'cssforclicktodial'
5061 $newphoneaend = '</a>';
5062 } else {
5063 // Old method
5064 $newphoneastart = '<a href="' . $url . '"';
5065 if (getDolGlobalString('CLICKTODIAL_FORCENEWTARGET')) {
5066 $newphoneastart .= ' target="_blank" rel="noopener noreferrer"';
5067 }
5068 $newphoneastart .= '>';
5069 $newphoneaend .= '</a>';
5070 }
5071 }
5072
5073 //if (($contactid || $socid) && isModEnabled('agenda') && $user->hasRight('agenda', 'myactions', 'create'))
5074 if (isModEnabled('agenda') && $user->hasRight("agenda", "myactions", "create")) {
5075 $type = 'AC_TEL';
5076 $addlinktoagenda = '';
5077 if ($addlink == 'AC_FAX') {
5078 $type = 'AC_FAX';
5079 }
5080 if (getDolGlobalString('AGENDA_ADDACTIONFORPHONE')) {
5081 $addlinktoagenda = '<a href="' . DOL_URL_ROOT . '/comm/action/card.php?action=create&backtopage=' . urlencode($_SERVER['REQUEST_URI']) . '&actioncode=' . $type . ($contactid ? '&contactid=' . $contactid : '') . ($socid ? '&socid=' . $socid : '') . '">' . img_object($langs->trans("AddAction"), "calendar") . '</a>';
5082 }
5083 if ($addlinktoagenda) {
5084 $newphone = '<span>' . $newphone . ' ' . $addlinktoagenda . '</span>';
5085 }
5086 }
5087 }
5088
5089 if (getDolGlobalString('CONTACT_PHONEMOBILE_SHOW_LINK_TO_WHATSAPP') && $withpicto == 'mobile') {
5090 // Link to Whatsapp
5091 $newphone .= ' <a href="https://wa.me/' . $newphonewa . '" target="_blank"'; // Use api to whatasapp contacts
5092 $newphone .= '><span class="paddingright fab fa-whatsapp" style="color:#25D366;" title="WhatsApp"></span></a>';
5093 }
5094
5095 if (empty($titlealt) && isset($langs)) {
5096 $titlealt = ($withpicto == 'fax' ? $langs->trans("Fax") : $langs->trans("Phone"));
5097 }
5098 $rep = '';
5099
5100 if ($hookmanager) {
5101 $parameters = array('countrycode' => $countrycode, 'cid' => $contactid, 'socid' => $socid, 'titlealt' => $titlealt, 'picto' => $withpicto);
5102 $reshook = $hookmanager->executeHooks('printPhone', $parameters, $phone);
5103 $rep .= $hookmanager->resPrint;
5104 }
5105 if (empty($reshook)) {
5106 $picto = '';
5107 if ($withpicto) {
5108 if ($withpicto == 'fax') {
5109 $picto = 'phoning_fax';
5110 } elseif ($withpicto == 'phone') {
5111 $picto = 'phone';
5112 } elseif ($withpicto == 'mobile') {
5113 $picto = 'phoning_mobile';
5114 } else {
5115 $picto = '';
5116 }
5117 }
5118 if ($adddivfloat == 1) {
5119 $rep .= '<div class="nospan float' . ($morecss ? ' ' . $morecss : '') . '">';
5120 } elseif (empty($adddivfloat)) {
5121 $rep .= '<span' . ($morecss ? ' class="' . $morecss . '"' : '') . '>';
5122 }
5123
5124 $rep .= $newphoneastart;
5125 $rep .= ($withpicto ? img_picto($titlealt, $picto) : '');
5126 if ($separ != 'hidenum') {
5127 $rep .= ($withpicto ? ' ' : '') . $newphone;
5128 }
5129 $rep .= $newphoneaend;
5130
5131 if ($adddivfloat == 1) {
5132 $rep .= '</div>';
5133 } elseif (empty($adddivfloat)) {
5134 $rep .= '</span>';
5135 }
5136 }
5137
5138 return $rep;
5139}
5140
5149function dol_print_ip($ip, $mode = 0, $showname = 0)
5150{
5151 global $conf;
5152
5153 $ret = '';
5154 if (!isset($conf->cache['resolveips'])) {
5155 $conf->cache['resolveips'] = array();
5156 }
5157
5158 if ($mode != 2) {
5159 $countrycode = dolGetCountryCodeFromIp($ip);
5160 if ($countrycode) { // If success, countrycode is us, fr, ...
5161 if (file_exists(DOL_DOCUMENT_ROOT . '/theme/common/flags/' . $countrycode . '.png')) {
5162 $ret .= picto_from_langcode($countrycode);
5163 } else {
5164 $ret .= '(' . $countrycode . ')';
5165 }
5166 $ret .= '&nbsp;';
5167 } else {
5168 // Nothing
5169 }
5170 }
5171
5172 if (in_array($mode, [0, 2])) {
5173 $domain = '';
5174 if ($showname) {
5175 if (!array_key_exists($ip, $conf->cache['resolveips'])) {
5176 $domain = gethostbyaddr($ip);
5177 $conf->cache['resolveips'][$ip] = $domain; // false or domain
5178 } else {
5179 $domain = $conf->cache['resolveips'][$ip];
5180 }
5181 }
5182 if ($domain) {
5183 $ret .= $domain;
5184 } else {
5185 $ret .= $ip;
5186 }
5187 }
5188
5189 return $ret;
5190}
5191
5204function getUserRemoteIP($trusted = 0)
5205{
5206 if ($trusted) { // Return only IP we can rely on (not spoofable by the client)
5207 $ip = (empty($_SERVER['REMOTE_ADDR']) ? '' : $_SERVER['REMOTE_ADDR']); // value may be the IP of a proxy
5208 // Note that if apache module remoteip has been enabled, REMOTE_ADDR can contain the real client (the value from cloudFlare HTTP_CF_CONNECTING_IP for example)
5209 // This can happen if the proxy were added in the list of trusted proxy.
5210 return $ip;
5211 }
5212
5213 // Try to guess the real IP of client (but this may not be reliable)
5214 if (empty($_SERVER['HTTP_X_FORWARDED_FOR']) || preg_match('/[^0-9\.\:,\[\]\s]/', $_SERVER['HTTP_X_FORWARDED_FOR'])) {
5215 if (empty($_SERVER['HTTP_CLIENT_IP']) || preg_match('/[^0-9\.\:,\[\]\s]/', $_SERVER['HTTP_CLIENT_IP'])) {
5216 if (empty($_SERVER["HTTP_CF_CONNECTING_IP"])) {
5217 $ip = (empty($_SERVER['REMOTE_ADDR']) ? '' : $_SERVER['REMOTE_ADDR']); // value may be the IP of the proxy and not the client
5218 } else {
5219 $ip = $_SERVER["HTTP_CF_CONNECTING_IP"]; // value here may have been forged by client
5220 }
5221 } else {
5222 $ip = preg_replace('/,.*$/', '', $_SERVER['HTTP_CLIENT_IP']); // value is clean here but may have been forged by proxy
5223 }
5224 } else {
5225 $ip = preg_replace('/,.*$/', '', $_SERVER['HTTP_X_FORWARDED_FOR']); // value is clean here but may have been forged by proxy
5226 }
5227 return $ip;
5228}
5229
5236function dolGetCountryCodeFromIp($ip)
5237{
5238 $countrycode = '';
5239
5240 if (isModEnabled('geoipmaxmind')) {
5241 if (getDolGlobalString('GEOIP_VERSION') == 'php') {
5242 $datafile = getDolGlobalString('GEOIPMAXMIND_COUNTRY_DATAFILE');
5243 } else {
5244 $diroffile = getMultidirOutput(null, 'geoipmaxmind');
5245 $datafile = $diroffile . '/' . getDolGlobalString('GEOIPMAXMIND_COUNTRY_DATAFILE_EMBEDDED');
5246 }
5247 //$ip='24.24.24.24';
5248 //$datafile='/usr/share/GeoIP/GeoIP.dat'; Note that this must be downloaded datafile (not same than datafile provided with ubuntu packages)
5249 if ($datafile) {
5250 try {
5251 include_once DOL_DOCUMENT_ROOT . '/core/class/dolgeoip.class.php';
5252 $geoip = new DolGeoIP('country', $datafile);
5253 //print 'ip='.$ip.' databaseType='.$geoip->gi->databaseType." GEOIP_CITY_EDITION_REV1=".GEOIP_CITY_EDITION_REV1."\n";
5254 $countrycode = $geoip->getCountryCodeFromIP($ip);
5255 } catch (Exception $e) {
5256 //print 'Error with GeoIP database: '.$e->getMessage();
5257 }
5258 }
5259 }
5260
5261 return $countrycode;
5262}
5263
5264
5271function dol_user_country()
5272{
5273 global $conf, $langs, $user;
5274
5275 //$ret=$user->xxx;
5276 $ret = '';
5277 if (isModEnabled('geoipmaxmind')) {
5278 $ip = getUserRemoteIP();
5279 $datafile = getDolGlobalString('GEOIPMAXMIND_COUNTRY_DATAFILE');
5280 //$ip='24.24.24.24';
5281 //$datafile='E:\Mes Sites\Web\Admin1\awstats\maxmind\GeoIP.dat';
5282 include_once DOL_DOCUMENT_ROOT . '/core/class/dolgeoip.class.php';
5283 $geoip = new DolGeoIP('country', $datafile);
5284 $countrycode = $geoip->getCountryCodeFromIP($ip);
5285 $ret = $countrycode;
5286 }
5287 return $ret;
5288}
5289
5302function dol_print_address($address, $htmlid, $element, $id, $noprint = 0, $charfornl = '')
5303{
5304 global $hookmanager;
5305
5306 $out = '';
5307
5308 if ($address) {
5309 if ($hookmanager) {
5310 $parameters = array('element' => $element, 'id' => $id);
5311 $reshook = $hookmanager->executeHooks('printAddress', $parameters, $address);
5312 $out .= $hookmanager->resPrint;
5313 }
5314 if (empty($reshook)) {
5315 if (empty($charfornl)) {
5316 $out .= nl2br((string) $address);
5317 } else {
5318 $out .= preg_replace('/[\r\n]+/', $charfornl, (string) $address);
5319 }
5320
5321 // TODO Remove this block, we can add this using the hook now
5322 $showgmap = $showomap = 0;
5323 if (($element == 'thirdparty' || $element == 'societe') && isModEnabled('google') && getDolGlobalString('GOOGLE_ENABLE_GMAPS')) {
5324 $showgmap = 1;
5325 }
5326 if ($element == 'contact' && isModEnabled('google') && getDolGlobalString('GOOGLE_ENABLE_GMAPS_CONTACTS')) {
5327 $showgmap = 1;
5328 }
5329 if ($element == 'member' && isModEnabled('google') && getDolGlobalString('GOOGLE_ENABLE_GMAPS_MEMBERS')) {
5330 $showgmap = 1;
5331 }
5332 if ($element == 'user' && isModEnabled('google') && getDolGlobalString('GOOGLE_ENABLE_GMAPS_USERS')) {
5333 $showgmap = 1;
5334 }
5335 if (($element == 'thirdparty' || $element == 'societe') && isModEnabled('openstreetmap') && getDolGlobalString('OPENSTREETMAP_ENABLE_MAPS')) {
5336 $showomap = 1;
5337 }
5338 if ($element == 'contact' && isModEnabled('openstreetmap') && getDolGlobalString('OPENSTREETMAP_ENABLE_MAPS_CONTACTS')) {
5339 $showomap = 1;
5340 }
5341 if ($element == 'member' && isModEnabled('openstreetmap') && getDolGlobalString('OPENSTREETMAP_ENABLE_MAPS_MEMBERS')) {
5342 $showomap = 1;
5343 }
5344 if ($element == 'user' && isModEnabled('openstreetmap') && getDolGlobalString('OPENSTREETMAP_ENABLE_MAPS_USERS')) {
5345 $showomap = 1;
5346 }
5347 if ($showgmap) {
5348 $url = dol_buildpath('/google/gmaps.php?mode=' . $element . '&id=' . $id, 1);
5349 $out .= ' <a href="' . $url . '" target="_gmaps"><img id="' . $htmlid . '" class="valigntextbottom" src="' . DOL_URL_ROOT . '/theme/common/gmap.png"></a>';
5350 }
5351 if ($showomap) {
5352 $url = dol_buildpath('/openstreetmap/maps.php?mode=' . $element . '&id=' . $id, 1);
5353 $out .= ' <a href="' . $url . '" target="_gmaps"><img id="' . $htmlid . '_openstreetmap" class="valigntextbottom" src="' . DOL_URL_ROOT . '/theme/common/gmap.png"></a>';
5354 }
5355 }
5356 }
5357 if ($noprint) {
5358 return $out;
5359 } else {
5360 print $out;
5361 return null;
5362 }
5363}
5364
5365
5375function isValidEmail($address, $acceptsupervisorkey = 0, $acceptuserkey = 0)
5376{
5377 if ($acceptsupervisorkey && $address == '__SUPERVISOREMAIL__') {
5378 return true;
5379 }
5380 if ($acceptuserkey && $address == '__USER_EMAIL__') {
5381 return true;
5382 }
5383 if (filter_var($address, FILTER_VALIDATE_EMAIL)) {
5384 return true;
5385 }
5386
5387 return false;
5388}
5389
5399function isValidMXRecord($domain)
5400{
5401 if (function_exists('idn_to_ascii') && function_exists('checkdnsrr')) {
5402 if (!checkdnsrr(idn_to_ascii($domain), 'MX')) {
5403 return 0;
5404 }
5405 if (function_exists('getmxrr')) {
5406 $mxhosts = array();
5407 $weight = array();
5408 getmxrr(idn_to_ascii($domain), $mxhosts, $weight);
5409 if (count($mxhosts) > 1) {
5410 return 1;
5411 }
5412 if (count($mxhosts) == 1 && !in_array((string) $mxhosts[0], array('', '.'))) {
5413 return 1;
5414 }
5415
5416 return 0;
5417 }
5418 }
5419
5420 // function idn_to_ascii or checkdnsrr or getmxrr does not exists
5421 return -1;
5422}
5423
5431function isValidPhone($phone)
5432{
5433 return true;
5434}
5435
5436
5446function dolGetFirstLetters($s, $nbofchar = 1)
5447{
5448 $ret = '';
5449 $tmparray = explode(' ', $s);
5450 foreach ($tmparray as $tmps) {
5451 $ret .= dol_substr($tmps, 0, $nbofchar);
5452 }
5453
5454 return $ret;
5455}
5456
5457
5465function dol_strlen($string, $stringencoding = 'UTF-8')
5466{
5467 if (is_null($string)) {
5468 return 0;
5469 }
5470
5471 if (function_exists('mb_strlen')) {
5472 return mb_strlen($string, $stringencoding);
5473 } else {
5474 return strlen($string);
5475 }
5476}
5477
5488function dol_substr($string, $start, $length = null, $stringencoding = '', $trunconbytes = 0)
5489{
5490 global $langs;
5491
5492 if (empty($stringencoding)) {
5493 $stringencoding = (empty($langs) ? 'UTF-8' : $langs->charset_output);
5494 }
5495
5496 $ret = '';
5497 if (empty($trunconbytes)) {
5498 if (function_exists('mb_substr')) {
5499 $ret = mb_substr($string, $start, $length, $stringencoding);
5500 } else {
5501 $ret = substr($string, $start, $length);
5502 }
5503 } else {
5504 if (function_exists('mb_strcut')) {
5505 $ret = mb_strcut($string, $start, $length, $stringencoding);
5506 } else {
5507 $ret = substr($string, $start, $length);
5508 }
5509 }
5510 return $ret;
5511}
5512
5513
5527function dol_trunc($string, $size = 40, $trunc = 'right', $stringencoding = 'UTF-8', $nodot = 0, $display = 0)
5528{
5529 global $conf;
5530
5531 if (empty($size) || getDolGlobalString('MAIN_DISABLE_TRUNC')) {
5532 return $string;
5533 }
5534
5535 if (empty($stringencoding)) {
5536 $stringencoding = 'UTF-8';
5537 }
5538 // reduce for small screen
5539 if (!empty($conf->dol_optimize_smallscreen) && $conf->dol_optimize_smallscreen == 1 && $display == 1) {
5540 $size = round($size / 3);
5541 }
5542
5543 // We go always here
5544 if ($trunc == 'right') {
5545 $newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
5546 if (dol_strlen($newstring, $stringencoding) > ($size + ($nodot ? 0 : 1))) {
5547 // If nodot is 0 and size is 1 chars more, we don't trunc and don't add '...'
5548 return dol_substr($newstring, 0, $size, $stringencoding) . ($nodot ? '' : '…');
5549 } else {
5550 //return 'u'.$size.'-'.$newstring.'-'.dol_strlen($newstring,$stringencoding).'-'.$string;
5551 return $string;
5552 }
5553 } elseif ($trunc == 'middle') {
5554 $newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
5555 if (dol_strlen($newstring, $stringencoding) > 2 && dol_strlen($newstring, $stringencoding) > ($size + 1)) {
5556 $size1 = (int) round($size / 2);
5557 $size2 = (int) round($size / 2);
5558 return dol_substr($newstring, 0, $size1, $stringencoding) . '…' . dol_substr($newstring, dol_strlen($newstring, $stringencoding) - $size2, $size2, $stringencoding);
5559 } else {
5560 return $string;
5561 }
5562 } elseif ($trunc == 'left') {
5563 $newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
5564 if (dol_strlen($newstring, $stringencoding) > ($size + ($nodot ? 0 : 1))) {
5565 // If nodot is 0 and size is 1 chars more, we don't trunc and don't add '...'
5566 return '…' . dol_substr($newstring, dol_strlen($newstring, $stringencoding) - $size, $size, $stringencoding);
5567 } else {
5568 return $string;
5569 }
5570 } elseif ($trunc == 'wrap') {
5571 $newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
5572 if (dol_strlen($newstring, $stringencoding) > ($size + 1)) {
5573 return dol_substr($newstring, 0, $size, $stringencoding) . "\n" . dol_trunc(dol_substr($newstring, $size, dol_strlen($newstring, $stringencoding) - $size, $stringencoding), $size, $trunc);
5574 } else {
5575 return $string;
5576 }
5577 } else {
5578 return 'BadParam3CallingDolTrunc';
5579 }
5580}
5581
5589function getPictoForType($key, $morecss = '')
5590{
5591 // Set array with type -> picto
5592 $type2picto = array(
5593 'varchar' => 'font',
5594 'text' => 'font',
5595 'html' => 'code',
5596 'int' => 'sort-numeric-down',
5597 'double' => 'sort-numeric-down',
5598 'price' => 'currency',
5599 'pricecy' => 'multicurrency',
5600 'password' => 'key',
5601 'boolean' => 'check-square',
5602 'date' => 'calendar',
5603 'datetime' => 'calendar',
5604 'duration' => 'hourglass',
5605 'phone' => 'phone',
5606 'mail' => 'email',
5607 'url' => 'url',
5608 'ip' => 'country',
5609 'select' => 'list',
5610 'sellist' => 'list',
5611 'stars' => 'fontawesome_star_fas',
5612 'radio' => 'check-circle',
5613 'checkbox' => 'list',
5614 'chkbxlst' => 'list',
5615 'link' => 'link',
5616 'icon' => "question",
5617 'point' => "country",
5618 'multipts' => 'country',
5619 'linestrg' => "country",
5620 'polygon' => "country",
5621 'separate' => 'minus'
5622 );
5623
5624 if (!empty($type2picto[$key])) {
5625 return img_picto('', $type2picto[$key], 'class="pictofixedwidth' . ($morecss ? ' ' . $morecss : '') . '"');
5626 }
5627
5628 return img_picto('', 'generic', 'class="pictofixedwidth' . ($morecss ? ' ' . $morecss : '') . '"');
5629}
5630
5631
5655function img_picto($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0, $srconly = 0, $notitle = 0, $alt = '', $morecss = '', $marginleftonlyshort = 2, $allowothertags = array())
5656{
5657 global $conf;
5658
5659 // We forge fullpathpicto for image to $path/img/$picto. By default, we take DOL_URL_ROOT/theme/$conf->theme/img/$picto
5660 $url = DOL_URL_ROOT;
5661 $theme = isset($conf->theme) ? $conf->theme : null;
5662 $path = 'theme/' . $theme;
5663 if (empty($picto)) {
5664 $picto = 'generic';
5665 }
5666
5667 if ($titlealt) {
5668 $titlealt = str_replace(array('\n', "\n"), ' - ', $titlealt);
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
6868function info_admin($text, $infoonimgalt = 0, $nodiv = 0, $admin = '1', $morecss = 'hideonsmartphone', $textfordropdown = '', $picto = '', $textonpictotooltip = '', $cssfordropdown = 'info_admin')
6869{
6870 global $conf, $langs;
6871
6872 if ($infoonimgalt) {
6873 $result = img_picto($text, 'info', 'class="' . ($morecss ? ' ' . $morecss : '') . '"');
6874 } else {
6875 if (empty($conf->use_javascript_ajax)) {
6876 $textfordropdown = '';
6877 }
6878
6879 $class = (empty($admin) ? 'undefined' : ((string) $admin == '1' ? 'info' : $admin));
6880 $fa = 'info-circle';
6881 if ($picto == 'warning') {
6882 $fa = 'exclamation-triangle';
6883 }
6884 $result = ($nodiv ? '' : '<div class="wordbreak ' . $class . ($cssfordropdown ? ' ' . $cssfordropdown : '') . ($morecss ? ' ' . $morecss : '') . ($textfordropdown ? ' hidden' : '') . '">');
6885 $result .= img_picto(((string) $admin ? $langs->trans('InfoAdmin') : $langs->trans('Note')).($textonpictotooltip ? ' : '.$textonpictotooltip : ''), $fa);
6886 $result .= ' ';
6887 $result .= dol_escape_htmltag($text, 1, 0, 'div,span,b,br,a');
6888 $result .= ($nodiv ? '' : '</div>');
6889
6890 if ($textfordropdown) {
6891 $tmpresult = '<span class="' . $class . ' '. $cssfordropdown.'text opacitymedium cursorpointer">' . $langs->trans($textfordropdown) . ' ' . img_picto($langs->trans($textfordropdown), '1downarrow') . '</span>';
6892 $tmpresult .= '<script nonce="' . getNonce() . '" type="text/javascript">
6893 jQuery(document).ready(function() {
6894 jQuery(".' . $cssfordropdown . 'text").click(function() {
6895 console.log("toggle text of .'.$cssfordropdown.'");
6896 jQuery(".' . $cssfordropdown . '").toggle().removeClass("hidden");
6897 });
6898 });
6899 </script>';
6900
6901 $result = $tmpresult . $result;
6902 }
6903 }
6904
6905 return $result;
6906}
6907
6908
6920function dol_print_error($db = null, $error = '', $errors = null)
6921{
6922 global $conf, $langs, $user, $argv;
6923 global $dolibarr_main_prod;
6924
6925 $out = '';
6926 $syslog = '';
6927
6928 // If error occurs before the $lang object was loaded
6929 if (!$langs) {
6930 require_once DOL_DOCUMENT_ROOT . '/core/class/translate.class.php';
6931 $langs = new Translate('', $conf);
6932 $langs->load("main");
6933 }
6934
6935 // Load translation files required by the error messages
6936 $langs->loadLangs(array('main', 'errors'));
6937
6938 if ($_SERVER['DOCUMENT_ROOT']) { // Mode web
6939 $out .= $langs->trans("DolibarrHasDetectedError") . ".<br>\n";
6940 if (getDolGlobalInt('MAIN_FEATURES_LEVEL') > 0) {
6941 $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";
6942 }
6943 $out .= $langs->trans("InformationToHelpDiagnose") . ":<br>\n";
6944
6945 $out .= "<b>" . $langs->trans("Date") . ":</b> " . dol_print_date(time(), 'dayhourlog') . "<br>\n";
6946 $out .= "<b>" . $langs->trans("Dolibarr") . ":</b> " . DOL_VERSION . " - https://www.dolibarr.org<br>\n";
6947 if (isset($conf->global->MAIN_FEATURES_LEVEL)) {
6948 $out .= "<b>" . $langs->trans("LevelOfFeature") . ":</b> " . getDolGlobalInt('MAIN_FEATURES_LEVEL') . "<br>\n";
6949 }
6950 if ($user instanceof User) {
6951 $out .= "<b>" . $langs->trans("Login") . ":</b> " . $user->login . "<br>\n";
6952 }
6953 if (function_exists("phpversion")) {
6954 $out .= "<b>" . $langs->trans("PHP") . ":</b> " . phpversion() . "<br>\n";
6955 }
6956 $out .= "<b>" . $langs->trans("Server") . ":</b> " . (isset($_SERVER["SERVER_SOFTWARE"]) ? dol_htmlentities($_SERVER["SERVER_SOFTWARE"], ENT_COMPAT) : '') . "<br>\n";
6957 if (function_exists("php_uname")) {
6958 $out .= "<b>" . $langs->trans("OS") . ":</b> " . php_uname() . "<br>\n";
6959 }
6960 $out .= "<b>" . $langs->trans("UserAgent") . ":</b> " . (isset($_SERVER["HTTP_USER_AGENT"]) ? dol_htmlentities($_SERVER["HTTP_USER_AGENT"], ENT_COMPAT) : '') . "<br>\n";
6961 $out .= "<br>\n";
6962 $out .= "<b>" . $langs->trans("RequestedUrl") . ":</b> " . (isset($_SERVER["REQUEST_URI"]) ? dol_htmlentities($_SERVER["REQUEST_URI"], ENT_COMPAT) : '') . "<br>\n";
6963 $out .= "<b>" . $langs->trans("Referer") . ":</b> " . (isset($_SERVER["HTTP_REFERER"]) ? dol_htmlentities($_SERVER["HTTP_REFERER"], ENT_COMPAT) : '') . "<br>\n";
6964 $out .= "<b>" . $langs->trans("MenuManager") . ":</b> " . (isset($conf->standard_menu) ? dol_htmlentities($conf->standard_menu, ENT_COMPAT) : '') . "<br>\n";
6965 $out .= "<br>\n";
6966 $syslog .= "url=" . (isset($_SERVER["REQUEST_URI"]) ? dol_escape_htmltag($_SERVER["REQUEST_URI"]) : '');
6967 $syslog .= ", query_string=" . (isset($_SERVER["QUERY_STRING"]) ? dol_escape_htmltag($_SERVER["QUERY_STRING"]) : '');
6968 } else { // Mode CLI
6969 $out .= '> ' . $langs->transnoentities("ErrorInternalErrorDetected") . ":\n" . $argv[0] . "\n";
6970 $syslog .= "pid=" . dol_getmypid();
6971 }
6972
6973 if (!empty($conf->modules)) {
6974 $out .= "<b>" . $langs->trans("Modules") . ":</b> " . implode(', ', $conf->modules) . "<br>\n";
6975 }
6976
6977 if (is_object($db)) {
6978 if ($_SERVER['DOCUMENT_ROOT']) { // Mode web
6979 $out .= "<b>" . $langs->trans("DatabaseTypeManager") . ":</b> " . $db->type . "<br>\n";
6980 $lastqueryerror = $db->lastqueryerror();
6981 if (!utf8_check($lastqueryerror)) {
6982 $lastqueryerror = "SQL error string is not a valid UTF8 string. We can't show it.";
6983 }
6984 $out .= "<b>" . $langs->trans("RequestLastAccessInError") . ":</b> " . ($lastqueryerror ? dol_escape_htmltag($lastqueryerror) : $langs->trans("ErrorNoRequestInError")) . "<br>\n";
6985 $out .= "<b>" . $langs->trans("ReturnCodeLastAccessInError") . ":</b> " . ($db->lasterrno() ? dol_escape_htmltag($db->lasterrno()) : $langs->trans("ErrorNoRequestInError")) . "<br>\n";
6986 $out .= "<b>" . $langs->trans("InformationLastAccessInError") . ":</b> " . ($db->lasterror() ? dol_escape_htmltag($db->lasterror()) : $langs->trans("ErrorNoRequestInError")) . "<br>\n";
6987 $out .= "<br>\n";
6988 } else { // Mode CLI
6989 // No dol_escape_htmltag for output, we are in CLI mode
6990 $out .= '> ' . $langs->transnoentities("DatabaseTypeManager") . ":\n" . $db->type . "\n";
6991 $out .= '> ' . $langs->transnoentities("RequestLastAccessInError") . ":\n" . ($db->lastqueryerror() ? $db->lastqueryerror() : $langs->transnoentities("ErrorNoRequestInError")) . "\n";
6992 $out .= '> ' . $langs->transnoentities("ReturnCodeLastAccessInError") . ":\n" . ($db->lasterrno() ? $db->lasterrno() : $langs->transnoentities("ErrorNoRequestInError")) . "\n";
6993 $out .= '> ' . $langs->transnoentities("InformationLastAccessInError") . ":\n" . ($db->lasterror() ? $db->lasterror() : $langs->transnoentities("ErrorNoRequestInError")) . "\n";
6994 }
6995 $syslog .= ", sql=" . $db->lastquery();
6996 $syslog .= ", db_error=" . $db->lasterror();
6997 }
6998
6999 if ($error || $errors) {
7000 // Merge all into $errors array
7001 if (is_array($error) && is_array($errors)) {
7002 $errors = array_merge($error, $errors);
7003 } elseif (is_array($error)) { // deprecated, use second parameters
7004 $errors = $error;
7005 } elseif (is_array($errors) && !empty($error)) {
7006 $errors = array_merge(array($error), $errors);
7007 } elseif (!empty($error)) {
7008 $errors = array_merge(array($error), array($errors));
7009 }
7010
7011 $langs->load("errors");
7012
7013 foreach ($errors as $msg) {
7014 if (empty($msg)) {
7015 continue;
7016 }
7017 if ($_SERVER['DOCUMENT_ROOT']) { // Mode web
7018 $out .= "<b>" . $langs->trans("Message") . ":</b> " . dol_escape_htmltag($msg) . "<br>\n";
7019 } else { // Mode CLI
7020 $out .= '> ' . $langs->transnoentities("Message") . ":\n" . $msg . "\n";
7021 }
7022 $syslog .= ", msg=" . $msg;
7023 }
7024 }
7025 if (empty($dolibarr_main_prod) && $_SERVER['DOCUMENT_ROOT'] && function_exists('xdebug_print_function_stack') && function_exists('xdebug_call_file')) {
7026 xdebug_print_function_stack();
7027 $out .= '<b>XDebug information:</b>' . "<br>\n";
7028 $out .= 'File: ' . xdebug_call_file() . "<br>\n";
7029 $out .= 'Line: ' . xdebug_call_line() . "<br>\n";
7030 $out .= 'Function: ' . xdebug_call_function() . "<br>\n";
7031 $out .= "<br>\n";
7032 }
7033
7034 // Return a http header with error code if possible
7035 if (!headers_sent()) {
7036 if (function_exists('top_httphead')) { // In CLI context, the method does not exists
7037 top_httphead();
7038 }
7039 //http_response_code(500); // If we use 500, message is not output with some command line tools
7040 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
7041 }
7042
7043 if (empty($dolibarr_main_prod)) {
7044 print $out;
7045 } else {
7046 if (empty($langs->defaultlang)) {
7047 $langs->setDefaultLang();
7048 }
7049 $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.
7050 // This should not happen, except if there is a bug somewhere. Enabled and check log in such case.
7051 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";
7052 print $langs->trans("DolibarrHasDetectedError") . '. ';
7053 print $langs->trans("YouCanSetOptionDolibarrMainProdToZero");
7054 if (!defined("MAIN_CORE_ERROR")) {
7055 define("MAIN_CORE_ERROR", 1);
7056 }
7057 }
7058
7059 dol_syslog("Error " . $syslog, LOG_ERR);
7060}
7061
7072function dol_print_error_email($prefixcode, $errormessage = '', $errormessages = array(), $morecss = 'error', $email = '')
7073{
7074 global $langs;
7075
7076 if (empty($email)) {
7077 $email = getDolGlobalString('MAIN_INFO_SOCIETE_MAIL');
7078 }
7079
7080 $langs->load("errors");
7081 $now = dol_now();
7082
7083 print '<br><div class="center login_main_message"><div class="' . $morecss . '">';
7084 print $langs->trans("ErrorContactEMail", $email, $prefixcode . '-' . dol_print_date($now, '%Y%m%d%H%M%S'));
7085 if ($errormessage) {
7086 print '<br><br>' . $errormessage;
7087 }
7088 if (is_array($errormessages) && count($errormessages)) {
7089 foreach ($errormessages as $mesgtoshow) {
7090 print '<br><br>' . $mesgtoshow;
7091 }
7092 }
7093 print '</div></div>';
7094}
7095
7112function print_liste_field_titre($name, $file = "", $field = "", $begin = "", $param = "", $moreattrib = "", $sortfield = "", $sortorder = "", $prefix = "", $tooltip = "", $forcenowrapcolumntitle = 0)
7113{
7114 print getTitleFieldOfList($name, 0, $file, $field, $begin, $param, $moreattrib, $sortfield, $sortorder, $prefix, 0, $tooltip, $forcenowrapcolumntitle);
7115}
7116
7135function getTitleFieldOfList($name, $thead = 0, $file = "", $field = "", $begin = "", $moreparam = "", $moreattrib = "", $sortfield = "", $sortorder = "", $prefix = "", $disablesortlink = 0, $tooltip = '', $forcenowrapcolumntitle = 0)
7136{
7137 global $langs, $form;
7138 //print "$name, $file, $field, $begin, $options, $moreattrib, $sortfield, $sortorder<br>\n";
7139
7140 if ($moreattrib == 'class="right"') {
7141 $prefix .= 'right '; // For backward compatibility
7142 }
7143
7144 $tooltip = (string) $tooltip; // In case $tooltip is null
7145
7146 $sortorder = strtoupper((string) $sortorder);
7147 $out = '';
7148 $sortimg = '';
7149
7150 $tag = 'th';
7151 if ($thead == 2) {
7152 $tag = 'div';
7153 }
7154
7155 $tmpsortfield = explode(',', (string) $sortfield);
7156 $sortfield1 = trim($tmpsortfield[0]); // If $sortfield is 'd.datep,d.id', it becomes 'd.datep'
7157 $tmpfield = explode(',', $field);
7158 $field1 = trim($tmpfield[0]); // If $field is 'd.datep,d.id', it becomes 'd.datep'
7159
7160 if (strpos((string) $tooltip, ':') !== false) {
7161 $tmptooltip = explode(':', (string) $tooltip);
7162 } else {
7163 $tmptooltip = array($tooltip);
7164 }
7165
7166 $wrapcolumntitle = (empty($forcenowrapcolumntitle) || (!empty($tmptooltip[2]) && $tmptooltip[2] == '-1'));
7167
7168 if (!getDolGlobalString('MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE') && $wrapcolumntitle) {
7169 $prefix = 'wrapcolumntitle ' . $prefix;
7170 }
7171
7172 //var_dump('field='.$field.' field1='.$field1.' sortfield='.$sortfield.' sortfield1='.$sortfield1);
7173 // If field is used as sort criteria we use a specific css class liste_titre_sel
7174 // Example if (sortfield,field)=("nom","xxx.nom") or (sortfield,field)=("nom","nom")
7175 $liste_titre = 'liste_titre';
7176 if ($field1 && ($sortfield1 == $field1 || $sortfield1 == preg_replace("/^[^\.]+\./", "", $field1))) {
7177 $liste_titre = 'liste_titre_sel';
7178 }
7179
7180 $tagstart = '<' . $tag . ' class="' . $prefix . $liste_titre . '" ' . $moreattrib;
7181 //$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)).'"' : '');
7182 $tagstart .= ($name && !getDolGlobalString('MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE') && $wrapcolumntitle && !dol_textishtml($name)) ? ' title="' . dolPrintHTMLForAttribute($langs->trans($name)) . '"' : '';
7183 $tagstart .= '>';
7184
7185 if (empty($thead) && $field && empty($disablesortlink)) { // If this is a sort field
7186 $options = preg_replace('/sortfield=([a-zA-Z0-9,\s\.]+)/i', '', (is_scalar($moreparam) ? $moreparam : ''));
7187 $options = preg_replace('/sortorder=([a-zA-Z0-9,\s\.]+)/i', '', $options);
7188 $options = preg_replace('/&+/i', '&', $options);
7189 if (!preg_match('/^&/', $options)) {
7190 $options = '&' . $options;
7191 }
7192
7193 $sortordertouseinlink = '';
7194 if ($field1 != $sortfield1) { // We are on another field than current sorted field
7195 if (preg_match('/^DESC/i', $sortorder)) {
7196 $sortordertouseinlink .= str_repeat('desc,', count(explode(',', $field)));
7197 } else { // We reverse the var $sortordertouseinlink
7198 $sortordertouseinlink .= str_repeat('asc,', count(explode(',', $field)));
7199 }
7200 } else { // We are on field that is the first current sorting criteria
7201 if (preg_match('/^ASC/i', $sortorder)) { // We reverse the var $sortordertouseinlink
7202 $sortordertouseinlink .= str_repeat('desc,', count(explode(',', $field)));
7203 } else {
7204 $sortordertouseinlink .= str_repeat('asc,', count(explode(',', $field)));
7205 }
7206 }
7207 $sortordertouseinlink = preg_replace('/,$/', '', $sortordertouseinlink);
7208 $out .= '<a class="reposition" href="' . dolBuildUrl($file, ['sortfield' => $field, 'sortorder' => $sortordertouseinlink, 'begin' => $begin]) . $options . '"';
7209 //$out .= (getDolGlobalString('MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE') ? '' : ' title="'.dol_escape_htmltag($langs->trans($name)).'"');
7210 $out .= '>';
7211 }
7212 if ($tooltip && $tmptooltip[0]) {
7213 // You can also use 'TranslationString:[keyfortooltiponclick]:[tooltipdirection]' for a tooltip on click or to change tooltip position.
7214 $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]));
7215 } else {
7216 $out .= $langs->trans((string) $name);
7217 }
7218
7219 if (empty($thead) && $field && empty($disablesortlink)) { // If this is a sort field
7220 $out .= '</a>';
7221 }
7222
7223 if (empty($thead) && $field) { // If this is a sort field
7224 $options = preg_replace('/sortfield=([a-zA-Z0-9,\s\.]+)/i', '', (is_scalar($moreparam) ? $moreparam : ''));
7225 $options = preg_replace('/sortorder=([a-zA-Z0-9,\s\.]+)/i', '', $options);
7226 $options = preg_replace('/&+/i', '&', $options);
7227 if (!preg_match('/^&/', $options)) {
7228 $options = '&' . $options;
7229 }
7230
7231 if (!$sortorder || ($field1 != $sortfield1)) {
7232 // Nothing
7233 } else {
7234 if (preg_match('/^DESC/', $sortorder)) {
7235 $sortimg .= '<span class="nowrap">' . img_up("Z-A", 0, 'paddingright') . '</span>';
7236 }
7237 if (preg_match('/^ASC/', $sortorder)) {
7238 $sortimg .= '<span class="nowrap">' . img_down("A-Z", 0, 'paddingright') . '</span>';
7239 }
7240 }
7241 }
7242
7243 $tagend = '</' . $tag . '>';
7244
7245 $out = $tagstart . $sortimg . $out . $tagend;
7246
7247 return $out;
7248}
7249
7258function print_titre($title)
7259{
7260 dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING);
7261
7262 print '<div class="titre">' . $title . '</div>';
7263}
7264
7276function print_fiche_titre($title, $mesg = '', $picto = 'generic', $pictoisfullpath = 0, $id = '')
7277{
7278 print load_fiche_titre($title, $mesg, $picto, $pictoisfullpath, $id);
7279}
7280
7295function load_fiche_titre($title, $morehtmlright = '', $picto = 'generic', $pictoisfullpath = 0, $id = '', $morecssontable = '', $morehtmlcenter = '', $morecssonpicto = 'widthpictotitle')
7296{
7297 $return = '';
7298
7299 if ($picto == 'setup') {
7300 $picto = 'generic';
7301 }
7302
7303 $return .= "\n";
7304 $return .= '<table ' . ($id ? 'id="' . $id . '" ' : '') . 'class="centpercent notopnoleftnoright table-fiche-title' . ($morecssontable ? ' ' . $morecssontable : '') . '">'; // margin bottom must be same than into print_barre_list
7305 $return .= '<tr class="toptitle">';
7306 if ($picto) {
7307 $return .= '<td class="nobordernopadding widthpictotitle valignmiddle col-picto">' . img_picto('', $picto, 'class="valignmiddle pictotitle'.($morecssonpicto ? ' '.$morecssonpicto : '').'"', $pictoisfullpath) . '</td>';
7308 }
7309 $return .= '<td class="nobordernopadding valignmiddle col-title">';
7310 $return .= '<div class="titre inline-block">';
7311 $return .= '<span class="inline-block valignmiddle print-barre-liste">' . $title . '</span>'; // $title is already HTML sanitized content
7312 $return .= '</div>';
7313 $return .= '</td>';
7314 if (dol_strlen($morehtmlcenter)) {
7315 $return .= '<td class="nobordernopadding center valignmiddle col-center">' . $morehtmlcenter . '</td>';
7316 }
7317 if (dol_strlen($morehtmlright)) {
7318 $return .= '<td class="nobordernopadding titre_right wordbreakimp right valignmiddle col-right">' . $morehtmlright . '</td>';
7319 }
7320 $return .= '</tr></table>' . "\n";
7321
7322 return $return;
7323}
7324
7348function 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 = '')
7349{
7350 global $conf, $langs;
7351
7352 $savlimit = $limit;
7353 $savtotalnboflines = $totalnboflines;
7354 if (is_numeric($totalnboflines)) {
7355 $totalnboflines = abs($totalnboflines);
7356 }
7357
7358 // Detect if there is a subtitle
7359 $subtitle = '';
7360 $tmparray = preg_split('/<br>/i', $title, 2);
7361 if (!empty($tmparray[1])) {
7362 $title = $tmparray[0];
7363 $subtitle = $tmparray[1];
7364 }
7365
7366 $page = (int) $page;
7367
7368 if ($picto == 'setup') {
7369 $picto = 'title_setup';
7370 }
7371 if (($conf->browser->name == 'ie') && $picto == 'generic') {
7372 $picto = 'title.gif';
7373 }
7374 if ($limit < 0) {
7375 $limit = $conf->liste_limit;
7376 }
7377
7378 if ($savlimit != 0 && (($num > $limit) || ($num == -1) || ($limit == 0))) {
7379 $nextpage = 1;
7380 } else {
7381 $nextpage = 0;
7382 }
7383 //print 'totalnboflines='.$totalnboflines.'-savlimit='.$savlimit.'-limit='.$limit.'-num='.$num.'-nextpage='.$nextpage.'-selectlimitsuffix='.$selectlimitsuffix.'-hidenavigation='.$hidenavigation;
7384
7385 print "\n";
7386 print "<!-- Begin print_barre_liste -->\n";
7387 print '<table class="centpercent notopnoleftnoright table-fiche-title' . ($morecss ? ' ' . $morecss : '') . '">';
7388 print '<tr class="toptitle">'; // margin bottom must be same than into load_fiche_tire
7389
7390 // Left
7391
7392 if ($picto && $title) {
7393 print '<td class="nobordernopadding widthpictotitle valignmiddle col-picto">';
7394 print img_picto('', $picto, 'class="valignmiddle pictotitle widthpictotitle"', $pictoisfullpath);
7395 print '</td>';
7396 }
7397
7398 print '<td class="nobordernopadding valignmiddle col-title">';
7399 print '<div class="titre inline-block nowrap">';
7400 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()
7401 if (!empty($title) && $savtotalnboflines >= 0 && (string) $savtotalnboflines != '') {
7402 if (is_numeric($totalnboflines) && (int) $totalnboflines > 0) {
7403 print '<span class="opacitymedium colorblack marginleftonly totalnboflines valignmiddle" title="' . $langs->trans("NbRecordQualified") . '">(' . $totalnboflines . ')</span>';
7404 } else {
7405 print '<span class="opacitymedium colorblack marginleftonly totalnboflines valignmiddle">(' . $totalnboflines . ')</span>';
7406 }
7407 }
7408 print '</div>';
7409 if (!empty($subtitle)) {
7410 print '<br><div class="subtitle inline-block hideonsmartphone">' . $subtitle . '</div>';
7411 }
7412 print '</td>';
7413
7414 // Center
7415 if ($morehtmlcenter && empty($conf->dol_optimize_smallscreen)) {
7416 print '<td class="nobordernopadding center valignmiddle col-center">' . $morehtmlcenter . '</td>';
7417 }
7418
7419 // Right
7420 print '<td class="nobordernopadding valignmiddle right col-right">';
7421 print '<input type="hidden" name="pageplusoneold" value="' . ((int) $page + 1) . '">';
7422 $query = [];
7423 parse_str($options, $query);
7424 if ($sortfield) {
7425 $query += ['sortfield' => $sortfield];
7426 }
7427 if ($sortorder) {
7428 $query += ['sortorder' => $sortorder];
7429 }
7430
7431 $options = '&' . http_build_query($query);
7432 if ($page) {
7433 $query = array_merge($query, ['page' => $page]);
7434 }
7435 // Show navigation bar
7436 $pagelist = '';
7437 if ($savlimit != 0 && ($page > 0 || $num > $limit)) {
7438 if ($totalnboflines) { // If we know total nb of lines
7439 // Define nb of extra page links before and after selected page + ... + first or last
7440 $maxnbofpage = (empty($conf->dol_optimize_smallscreen) ? 4 : 0);
7441
7442 if ($limit > 0) {
7443 $nbpages = ceil($totalnboflines / $limit);
7444 } else {
7445 $nbpages = 1;
7446 }
7447 $cpt = ($page - $maxnbofpage);
7448 if ($cpt < 0) {
7449 $cpt = 0;
7450 }
7451
7452 if ($cpt >= 1) {
7453 if (empty($pagenavastextinput)) {
7454 $query['page'] = 0;
7455 $pagelist .= '<li class="pagination"><a class="reposition" href="' . dolBuildUrl($file, $query) . '">1</a></li>';
7456 if ($cpt > 2) {
7457 $pagelist .= '<li class="pagination"><span class="inactive">...</span></li>';
7458 } elseif ($cpt == 2) {
7459 $query['page'] = 0;
7460 $pagelist .= '<li class="pagination"><a class="reposition" href="' . dolBuildUrl($file, $query) . '">2</a></li>';
7461 }
7462 }
7463 }
7464
7465 do {
7466 if ($pagenavastextinput) {
7467 if ($cpt == $page) {
7468 $pagelist .= '<li class="pagination pageplusone valignmiddle"><input type="text" class="' . ($totalnboflines > 100 ? 'width40' : 'width25') . ' center pageplusone heightofcombo" name="pageplusone" value="' . ($page + 1) . '"></li>';
7469 $pagelist .= '/';
7470 }
7471 } else {
7472 if ($cpt == $page) {
7473 $pagelist .= '<li class="pagination"><span class="active">' . ($page + 1) . '</span></li>';
7474 } else {
7475 $query['page'] = $cpt;
7476 $pagelist .= '<li class="pagination"><a class="reposition" href="' . dolBuildUrl($file, $query) . '">' . ($cpt + 1) . '</a></li>';
7477 }
7478 }
7479 $cpt++;
7480 } while ($cpt < $nbpages && $cpt <= ($page + $maxnbofpage));
7481
7482 if (empty($pagenavastextinput)) {
7483 if ($cpt < $nbpages) {
7484 if ($cpt < $nbpages - 2) {
7485 $pagelist .= '<li class="pagination"><span class="inactive">...</span></li>';
7486 } elseif ($cpt == $nbpages - 2) {
7487 $query['page'] = ($nbpages - 2);
7488 $pagelist .= '<li class="pagination"><a class="reposition" href="' . dolBuildUrl($file, $query) . '">' . ($nbpages - 1) . '</a></li>';
7489 }
7490 $query['page'] = ($nbpages - 1);
7491 $pagelist .= '<li class="pagination"><a class="reposition" href="' . dolBuildUrl($file, $query) . '">' . $nbpages . '</a></li>';
7492 }
7493 } else {
7494 $query['page'] = ($nbpages - 1);
7495 $pagelist .= '<li class="pagination paginationlastpage"><a class="reposition" href="' . dolBuildUrl($file, $query) . '">' . $nbpages . '</a></li>';
7496 }
7497 } else {
7498 $pagelist .= '<li class="pagination"><span class="active">' . ($page + 1) . "</li>";
7499 }
7500 }
7501
7502 if ($savlimit || $morehtmlright || $morehtmlrightbeforearrow) {
7503 // Show the combolist to select number of record per page and the navigation arrows.
7504 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
7505 }
7506
7507 // js to autoselect page field on focus
7508 if ($pagenavastextinput) {
7509 print ajax_autoselect('.pageplusone');
7510 }
7511
7512 print '</td>';
7513 print '</tr>';
7514
7515 print "</table>\n";
7516
7517 // Center
7518 if ($morehtmlcenter && !empty($conf->dol_optimize_smallscreen)) {
7519 print '<div class="nobordernopadding marginbottomonly center valignmiddle col-center centpercent">' . $morehtmlcenter . '</div>';
7520 }
7521
7522 print "<!-- End title -->\n\n";
7523}
7524
7541function print_fleche_navigation($page, $file, $options = '', $nextpage = 0, $betweenarrows = '', $afterarrows = '', $limit = -1, $totalnboflines = 0, $selectlimitsuffix = '', $beforearrows = '', $hidenavigation = 0)
7542{
7543 global $conf, $langs;
7544
7545 print '<div class="pagination"><ul>';
7546 if ($beforearrows) {
7547 print '<li class="paginationbeforearrows">';
7548 print $beforearrows;
7549 print '</li>';
7550 }
7551
7552 if (empty($hidenavigation)) {
7553 if ((int) $limit > 0 && (empty($selectlimitsuffix) || !is_numeric($selectlimitsuffix))) {
7554 $pagesizechoices = '10:10,15:15,20:20,25:25,50:50,100:100,250:250,500:500,1000:1000';
7555 $pagesizechoices .= ',5000:5000';
7556 //$pagesizechoices .= ',10000:10000'; // Memory trouble on most browsers
7557 //$pagesizechoices .= ',20000:20000'; // Memory trouble on most browsers
7558 //$pagesizechoices .= ',0:'.$langs->trans("All"); // Not yet supported
7559 //$pagesizechoices .= ',2:2';
7560 if (getDolGlobalString('MAIN_PAGESIZE_CHOICES')) {
7561 $pagesizechoices = getDolGlobalString('MAIN_PAGESIZE_CHOICES');
7562 }
7563
7564 if (getDolGlobalString('MAIN_USE_HTML5_LIMIT_SELECTOR')) {
7565 print '<li class="pagination">';
7566 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 . '">';
7567 print '<datalist id="limitlist">';
7568 } else {
7569 print '<li class="paginationcombolimit valignmiddle">';
7570 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")) . '">';
7571 }
7572 $tmpchoice = explode(',', $pagesizechoices);
7573 $tmpkey = $limit . ':' . $limit;
7574 if (!in_array($tmpkey, $tmpchoice)) {
7575 $tmpchoice[$tmpkey] = $tmpkey;
7576 }
7577 $tmpkey = $conf->liste_limit . ':' . $conf->liste_limit;
7578 if (!in_array($tmpkey, $tmpchoice)) {
7579 $tmpchoice[$tmpkey] = $tmpkey;
7580 }
7581 asort($tmpchoice, SORT_NUMERIC);
7582 foreach ($tmpchoice as $val) {
7583 $selected = '';
7584 $tmp = explode(':', $val);
7585 $key = $tmp[0];
7586 $val = $tmp[1];
7587 if ($key != '' && $val != '') {
7588 if ((int) $key == (int) $limit) {
7589 $selected = ' selected="selected"';
7590 }
7591 print '<option name="' . $key . '"' . $selected . '>' . dol_escape_htmltag($val) . '</option>' . "\n";
7592 }
7593 }
7594 if (getDolGlobalString('MAIN_USE_HTML5_LIMIT_SELECTOR')) {
7595 print '</datalist>';
7596 } else {
7597 print '</select>';
7598 print ajax_combobox("limit" . (is_numeric($selectlimitsuffix) ? '' : $selectlimitsuffix), array(), 0, 0, 'resolve', '-1', 'limit');
7599 //print ajax_combobox("limit");
7600 }
7601
7602 if ($conf->use_javascript_ajax) {
7603 print '<!-- JS CODE TO ENABLE select limit to launch submit of page -->
7604 <script>
7605 jQuery(document).ready(function () {
7606 jQuery(".selectlimit").change(function() {
7607 console.log("We change limit so we submit the form");
7608 $(this).parents(\'form:first\').submit();
7609 });
7610 });
7611 </script>
7612 ';
7613 }
7614 print '</li>';
7615 }
7616 if ($page > 0) {
7617 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>';
7618 }
7619 if ($betweenarrows) {
7620 print '<!--<div class="betweenarrows nowraponall inline-block">-->';
7621 print $betweenarrows;
7622 print '<!--</div>-->';
7623 }
7624 if ($nextpage > 0) {
7625 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>';
7626 }
7627 if ($afterarrows) {
7628 print '<li class="paginationafterarrows">';
7629 print $afterarrows;
7630 print '</li>';
7631 }
7632 }
7633 print '</ul></div>' . "\n";
7634}
7635
7636
7648function vatrate($rate, $addpercent = false, $info_bits = 0, $usestarfornpr = 0, $html = 0)
7649{
7650 $morelabel = '';
7651
7652 if (preg_match('/%/', $rate)) {
7653 $rate = str_replace('%', '', $rate);
7654 $addpercent = true;
7655 }
7656 $reg = array();
7657 if (preg_match('/\‍((.*)\‍)/', $rate, $reg)) {
7658 $morelabel = ' (' . $reg[1] . ')';
7659 $rate = preg_replace('/\s*' . preg_quote($morelabel, '/') . '/', '', $rate);
7660 $morelabel = ' ' . ($html ? '<span class="opacitymedium small">' : '') . '(' . $reg[1] . ')' . ($html ? '</span>' : '');
7661 }
7662 if (preg_match('/\*/', $rate)) {
7663 $rate = str_replace('*', '', $rate);
7664 $info_bits |= 1;
7665 }
7666
7667 // If rate is '9/9/9' we don't change it. If rate is '9.000' we apply price()
7668 if (!preg_match('/\//', $rate)) {
7669 $ret = price($rate, 0, '', 0, 0) . ($addpercent ? '%' : '');
7670 } else {
7671 // TODO Split on / and output with a price2num to have clean numbers without ton of 000.
7672 $ret = $rate . ($addpercent ? '%' : '');
7673 }
7674 if (($info_bits & 1) && $usestarfornpr >= 0) {
7675 $ret .= ' *';
7676 }
7677 $ret .= $morelabel;
7678 return $ret;
7679}
7680
7681
7696function price($amount, $form = 0, $outlangs = '', $trunc = 1, $rounding = -1, $forcerounding = -1, $currency_code = '')
7697{
7698 global $langs, $conf;
7699
7700 // Clean parameters
7701 if (empty($amount)) {
7702 $amount = 0; // To have a numeric value if amount not defined or = ''
7703 }
7704 $amount = (is_numeric($amount) ? $amount : 0); // Check if amount is numeric, for example, an error occurred when amount value = o (letter) instead 0 (number)
7705 if ($rounding == -1) {
7706 $rounding = min(getDolGlobalString('MAIN_MAX_DECIMALS_UNIT'), getDolGlobalString('MAIN_MAX_DECIMALS_TOT'));
7707 }
7708 $nbdecimal = $rounding;
7709
7710 if ($outlangs === 'none') {
7711 // Use international separators
7712 $dec = '.';
7713 $thousand = '';
7714 } else {
7715 // Output separators by default (french)
7716 $dec = ',';
7717 $thousand = ' ';
7718
7719 // If $outlangs not forced, we use use language
7720 if (!($outlangs instanceof Translate)) {
7721 $outlangs = $langs;
7722 }
7723
7724 if ($outlangs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal") {
7725 $dec = $outlangs->transnoentitiesnoconv("SeparatorDecimal");
7726 }
7727 if ($outlangs->transnoentitiesnoconv("SeparatorThousand") != "SeparatorThousand") {
7728 $thousand = $outlangs->transnoentitiesnoconv("SeparatorThousand");
7729 }
7730 if ($thousand == 'None') {
7731 $thousand = '';
7732 } elseif ($thousand == 'Space') {
7733 $thousand = ' ';
7734 }
7735 }
7736 //print "outlangs=".$outlangs->defaultlang." amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
7737
7738 //print "amount=".$amount."-";
7739 $amount = str_replace(',', '.', $amount); // should be useless
7740 //print $amount."-";
7741 $data = explode('.', $amount);
7742 $decpart = isset($data[1]) ? $data[1] : '';
7743 $decpart = preg_replace('/0+$/i', '', $decpart); // Remove 0 at end of decimal part
7744 //print "decpart=".$decpart."<br>";
7745 $end = '';
7746
7747 // We increase nbdecimal if there is more decimal than asked (to not loose information)
7748 if (dol_strlen($decpart) > $nbdecimal) {
7749 $nbdecimal = dol_strlen($decpart);
7750 }
7751
7752 // If nbdecimal is higher than max to show
7753 $nbdecimalmaxshown = (int) str_replace('...', '', getDolGlobalString('MAIN_MAX_DECIMALS_SHOWN'));
7754 if ($trunc && $nbdecimal > $nbdecimalmaxshown) {
7755 $nbdecimal = $nbdecimalmaxshown;
7756 if (preg_match('/\.\.\./i', getDolGlobalString('MAIN_MAX_DECIMALS_SHOWN'))) {
7757 // If output is truncated, we show ...
7758 $end = '...';
7759 }
7760 }
7761
7762 // If force rounding
7763 if ((string) $forcerounding != '-1' && (string) $forcerounding != '') {
7764 if ($forcerounding === 'MU') {
7765 $nbdecimal = getDolGlobalInt('MAIN_MAX_DECIMALS_UNIT');
7766 } elseif ($forcerounding === 'MT') {
7767 $nbdecimal = getDolGlobalInt('MAIN_MAX_DECIMALS_TOT');
7768 } elseif ($forcerounding >= 0) {
7769 $nbdecimal = (int) $forcerounding;
7770 }
7771 }
7772
7773 // Format number
7774 $output = number_format((float) $amount, $nbdecimal, $dec, $thousand);
7775 // Add symbol of currency if requested
7776 $cursymbolbefore = $cursymbolafter = '';
7777 if ($currency_code && is_object($outlangs)) {
7778 if ($currency_code == 'auto') {
7779 $currency_code = $conf->currency;
7780 }
7781
7782 $listofcurrenciesbefore = array('AUD', 'CAD', 'CNY', 'COP', 'CLP', 'GBP', 'HKD', 'MXN', 'PEN', 'USD', 'CRC', 'ZAR');
7783 $listoflanguagesbefore = array('nl_NL');
7784 if (in_array($currency_code, $listofcurrenciesbefore) || in_array($outlangs->defaultlang, $listoflanguagesbefore)) {
7785 $cursymbolbefore .= $outlangs->getCurrencySymbol($currency_code);
7786 } else {
7787 $tmpcur = $outlangs->getCurrencySymbol($currency_code);
7788 $cursymbolafter .= ($tmpcur == $currency_code ? ' ' . $tmpcur : $tmpcur);
7789 }
7790 }
7791 if ($form) {
7792 $output = preg_replace('/\s/', '&nbsp;', $output);
7793 $output = $cursymbolbefore . $output . $end . ($cursymbolafter ? ' <span class="small">'.$cursymbolafter.'</span>' : '');
7794 $output = preg_replace('/\'/', '&#039;', $output);
7795 } else {
7796 $output = $cursymbolbefore . $output . $end . ($cursymbolafter ? ' '.$cursymbolafter : '');
7797 }
7798
7799 return $output;
7800}
7801
7827function price2num($amount, $rounding = '', $option = 0)
7828{
7829 global $langs;
7830
7831 // Clean parameters
7832 if (is_null($amount)) {
7833 $amount = '';
7834 }
7835
7836 // Round PHP function does not allow number like '1,234.56' nor '1.234,56' nor '1 234,56'
7837 // Numbers must be '1234.56'
7838 // Decimal delimiter for PHP and database SQL requests must be '.'
7839 $dec = ',';
7840 $thousand = ' ';
7841 if (is_null($langs)) { // $langs is not defined, we use english values.
7842 $dec = '.';
7843 $thousand = ',';
7844 } else {
7845 if ($langs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal") {
7846 $dec = $langs->transnoentitiesnoconv("SeparatorDecimal");
7847 }
7848 if ($langs->transnoentitiesnoconv("SeparatorThousand") != "SeparatorThousand") {
7849 $thousand = $langs->transnoentitiesnoconv("SeparatorThousand");
7850 }
7851 }
7852 if ($thousand == 'None') {
7853 $thousand = '';
7854 } elseif ($thousand == 'Space') {
7855 $thousand = ' ';
7856 }
7857 //print "amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
7858
7859 // Convert value to universal number format (no thousand separator, '.' as decimal separator)
7860 if ($option != 1) { // If not a PHP number or unknown, we change or clean format
7861 //print "\n".'PP'.$amount.' - '.$dec.' - '.$thousand.' - '.intval($amount).'<br>';
7862 if (!is_numeric($amount)) {
7863 $amount = preg_replace('/[a-zA-Z\/\\\*\‍(\‍)<>\_]/', '', $amount);
7864 }
7865
7866 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
7867 $amount = str_replace($thousand, '', $amount);
7868 }
7869
7870 // Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
7871 // to format defined by LC_NUMERIC after a calculation and we want source format to be like defined by Dolibarr setup.
7872 // So if number was already a good number, it is converted into local Dolibarr setup.
7873 if (is_numeric($amount)) {
7874 // We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
7875 $temps = sprintf("%10.10F", $amount - intval($amount)); // temps=0.0000000000 or 0.0000200000 or 9999.1000000000
7876 $temps = preg_replace('/([\.1-9])0+$/', '\\1', $temps); // temps=0. or 0.00002 or 9999.1
7877 $nbofdec = max(0, dol_strlen($temps) - 2); // -2 to remove "0."
7878 $amount = number_format($amount, $nbofdec, $dec, $thousand);
7879 }
7880 //print "QQ".$amount."<br>\n";
7881
7882 // Now make replaceents (the main goal of function)
7883
7884 if ($thousand != ',' && $thousand != '.') {
7885 // Accept the two types of decimal points french users (i.e., using ' ' for thousands)
7886
7887 // REGEX: Find the integral and decimal parts.
7888 //
7889 // We require that the decimal point only appears once in $amount.
7890 // The regex `/^(?<int>[^,]*,|[^.]*\.)(?<dec>[^.,]*)$/u` can be broken down as follows:
7891 // - `(?<int>[^,]*,|[^.]*\.)` is any accepted sequence up to the last potential decimal point '.' or ',' and named `int`.
7892 // It covers two cases:
7893 // - `[^,]*,`: Any sequence of characters that is not ',' with ',' accepted as the decimal point (from start of string because of earlier `^`);
7894 // - `[^.]*\.`: Any sequence of characters that is not a '.' with '.' accepted as the decimal point (from start of string.
7895 // - `(?<dec>[^.,]*)`: The sequence after the character accepted as the decimal point, not including it.
7896 $matches = array();
7897 if (preg_match('/^(?<int>[^,]*,|[^.]*\.)(?<dec>[^.,]*)$/u', $amount, $matches)) {
7898 $intPart = $matches['int'];
7899 $decPart = $matches['dec'];
7900
7901 // Remove all commas and dots from intPart
7902 $intPart = str_replace(['.', ','], '', $intPart);
7903
7904 // Combine intPart and decPart with a dot
7905 $amount = $intPart . $dec . $decPart;
7906 }
7907 }
7908
7909 $amount = str_replace(' ', '', $amount); // To avoid spaces
7910 $amount = str_replace($thousand, '', $amount); // Replace of thousand before replace of dec to avoid pb if thousand is .
7911 $amount = str_replace($dec, '.', $amount);
7912
7913 $amount = preg_replace('/[^0-9\-\.]/', '', $amount); // Clean non numeric chars (so it clean some UTF8 spaces for example.
7914 }
7915 //print ' XX'.$amount.' '.$rounding;
7916
7917 // Now, $amount is a real PHP float number. We make a rounding if required.
7918 if ($rounding) {
7919 $nbofdectoround = '';
7920 if ($rounding == 'MU') {
7921 $nbofdectoround = getDolGlobalInt('MAIN_MAX_DECIMALS_UNIT'); // usually 5
7922 } elseif ($rounding == 'MT') {
7923 $nbofdectoround = getDolGlobalInt('MAIN_MAX_DECIMALS_TOT'); // usually 2 or 3
7924 } elseif ($rounding == 'MS') {
7925 $nbofdectoround = getDolGlobalInt('MAIN_MAX_DECIMALS_STOCK', 5);
7926 } elseif ($rounding == 'CU') {
7927 $nbofdectoround = getDolGlobalInt('MAIN_MAX_DECIMALS_CURRENCY_UNIT', getDolGlobalInt('MAIN_MAX_DECIMALS_UNIT')); // TODO Use param of currency
7928 } elseif ($rounding == 'CT') {
7929 $nbofdectoround = getDolGlobalInt('MAIN_MAX_DECIMALS_CURRENCY_TOT', getDolGlobalInt('MAIN_MAX_DECIMALS_TOT')); // TODO Use param of currency
7930 } elseif (is_numeric($rounding)) {
7931 $nbofdectoround = (int) $rounding;
7932 }
7933
7934 //print " RR".$amount.' - '.$nbofdectoround.'<br>';
7935 if (dol_strlen($nbofdectoround)) {
7936 $amount = round(is_string($amount) ? (float) $amount : $amount, $nbofdectoround); // $nbofdectoround can be 0.
7937 } else {
7938 return 'ErrorBadParameterProvidedToFunction';
7939 }
7940 //print ' SS'.$amount.' - '.$nbofdec.' - '.$dec.' - '.$thousand.' - '.$nbofdectoround.'<br>';
7941
7942 // Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
7943 // to format defined by LC_NUMERIC after a calculation and we want source format to be defined by Dolibarr setup.
7944 if (is_numeric($amount)) {
7945 // We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
7946 $temps = sprintf("%10.10F", $amount - intval($amount)); // temps=0.0000000000 or 0.0000200000 or 9999.1000000000
7947 $temps = preg_replace('/([\.1-9])0+$/', '\\1', $temps); // temps=0. or 0.00002 or 9999.1
7948 $nbofdec = max(0, dol_strlen($temps) - 2); // -2 to remove "0."
7949 $amount = number_format($amount, min($nbofdec, $nbofdectoround), $dec, $thousand); // Convert amount to format with dolibarr dec and thousand
7950 }
7951 //print "TT".$amount.'<br>';
7952
7953 // Always make replace because each math function (like round) replace
7954 // with local values and we want a number that has a SQL string format x.y
7955 if ($thousand != ',' && $thousand != '.') {
7956 $amount = str_replace(',', '.', $amount); // To accept 2 notations for french users
7957 }
7958
7959 $amount = str_replace(' ', '', $amount); // To avoid spaces
7960 $amount = str_replace($thousand, '', $amount); // Replace of thousand before replace of dec to avoid pb if thousand is .
7961 $amount = str_replace($dec, '.', $amount);
7962
7963 $amount = preg_replace('/[^0-9\-\.]/', '', $amount); // Clean non numeric chars (so it clean some UTF8 spaces for example.
7964 }
7965
7966 return $amount;
7967}
7968
7981function showDimensionInBestUnit($dimension, $unit, $type, $outputlangs, $round = -1, $forceunitoutput = 'no', $use_short_label = 0)
7982{
7983 require_once DOL_DOCUMENT_ROOT . '/core/lib/product.lib.php';
7984
7985 if (($forceunitoutput == 'no' && $dimension < 1 / 10000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == -6)) {
7986 $dimension *= 1000000;
7987 $unit -= 6;
7988 } elseif (($forceunitoutput == 'no' && $dimension < 1 / 10 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == -3)) {
7989 $dimension *= 1000;
7990 $unit -= 3;
7991 } elseif (($forceunitoutput == 'no' && $dimension > 100000000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == 6)) {
7992 $dimension /= 1000000;
7993 $unit += 6;
7994 } elseif (($forceunitoutput == 'no' && $dimension > 100000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == 3)) {
7995 $dimension /= 1000;
7996 $unit += 3;
7997 }
7998 // Special case when we want output unit into pound or ounce
7999 /* TODO
8000 if ($unit < 90 && $type == 'weight' && is_numeric($forceunitoutput) && (($forceunitoutput == 98) || ($forceunitoutput == 99))
8001 {
8002 $dimension = // convert dimension from standard unit into ounce or pound
8003 $unit = $forceunitoutput;
8004 }
8005 if ($unit > 90 && $type == 'weight' && is_numeric($forceunitoutput) && $forceunitoutput < 90)
8006 {
8007 $dimension = // convert dimension from standard unit into ounce or pound
8008 $unit = $forceunitoutput;
8009 }*/
8010
8011 $ret = price($dimension, 0, $outputlangs, 0, 0, $round);
8012 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
8013 $ret .= ' ' . measuringUnitString(0, $type, $unit, $use_short_label, $outputlangs);
8014
8015 return $ret;
8016}
8017
8018
8031function get_localtax($vatrate, $local, $thirdparty_buyer = null, $thirdparty_seller = null, $vatnpr = 0)
8032{
8033 global $db, $conf, $mysoc;
8034
8035 if (empty($thirdparty_seller) || !is_object($thirdparty_seller)) {
8036 $thirdparty_seller = $mysoc;
8037 }
8038
8039 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);
8040
8041 $vatratecleaned = $vatrate;
8042 $reg = array();
8043 if (preg_match('/^(.*)\s*\‍((.*)\‍)$/', (string) $vatrate, $reg)) { // If vat is "xx (yy)"
8044 $vatratecleaned = trim($reg[1]);
8045 $vatratecode = $reg[2];
8046 }
8047
8048 /*if ($thirdparty_buyer->country_code != $thirdparty_seller->country_code)
8049 {
8050 return 0;
8051 }*/
8052
8053 // Some test to guess with no need to make database access
8054 if ($mysoc->country_code == 'ES') { // For spain localtaxes 1 and 2, tax is qualified if buyer use local tax
8055 if ($local == 1) {
8056 if (!$mysoc->localtax1_assuj || (string) $vatratecleaned == "0") {
8057 return 0;
8058 }
8059 if ($thirdparty_seller->id == $mysoc->id) {
8060 if (!$thirdparty_buyer->localtax1_assuj) {
8061 return 0;
8062 }
8063 } else {
8064 if (!$thirdparty_seller->localtax1_assuj) {
8065 return 0;
8066 }
8067 }
8068 }
8069
8070 if ($local == 2) {
8071 //if (! $mysoc->localtax2_assuj || (string) $vatratecleaned == "0") return 0;
8072 if (!$mysoc->localtax2_assuj) {
8073 return 0; // If main vat is 0, IRPF may be different than 0.
8074 }
8075 if ($thirdparty_seller->id == $mysoc->id) {
8076 if (!$thirdparty_buyer->localtax2_assuj) {
8077 return 0;
8078 }
8079 } else {
8080 if (!$thirdparty_seller->localtax2_assuj) {
8081 return 0;
8082 }
8083 }
8084 }
8085 } else {
8086 if ($local == 1 && !$thirdparty_seller->localtax1_assuj) {
8087 return 0;
8088 }
8089 if ($local == 2 && !$thirdparty_seller->localtax2_assuj) {
8090 return 0;
8091 }
8092 }
8093
8094 // For some country MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY is forced to on.
8095 if (in_array($mysoc->country_code, array('ES'))) {
8096 $conf->global->MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY = 1;
8097 }
8098
8099 // Search local taxes
8100 if (getDolGlobalString('MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY')) {
8101 if ($local == 1) {
8102 if ($thirdparty_seller != $mysoc) {
8103 if (!isOnlyOneLocalTax($local)) { // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
8104 return $thirdparty_seller->localtax1_value;
8105 }
8106 } else { // i am the seller
8107 if (!isOnlyOneLocalTax($local)) { // TODO If seller is me, why not always returning this, even if there is only one locatax vat.
8108 return getDolGlobalString('MAIN_INFO_VALUE_LOCALTAX1');
8109 }
8110 }
8111 }
8112 if ($local == 2) {
8113 if ($thirdparty_seller != $mysoc) {
8114 if (!isOnlyOneLocalTax($local)) { // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
8115 // TODO We should also return value defined on thirdparty only if defined
8116 return $thirdparty_seller->localtax2_value;
8117 }
8118 } else { // i am the seller
8119 if (in_array($mysoc->country_code, array('ES'))) {
8120 return $thirdparty_buyer->localtax2_value;
8121 } else {
8122 return getDolGlobalString('MAIN_INFO_VALUE_LOCALTAX2');
8123 }
8124 }
8125 }
8126 }
8127
8128 // By default, search value of local tax on line of common tax
8129 $sql = "SELECT t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
8130 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t, " . MAIN_DB_PREFIX . "c_country as c";
8131 $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '" . $db->escape($thirdparty_seller->country_code) . "'";
8132 $sql .= " AND t.taux = " . ((float) $vatratecleaned) . " AND t.active = 1";
8133 $sql .= " AND t.entity IN (" . getEntity('c_tva') . ")";
8134 if (!empty($vatratecode)) {
8135 $sql .= " AND t.code ='" . $db->escape($vatratecode) . "'"; // If we have the code, we use it in priority
8136 } else {
8137 $sql .= " AND t.recuperableonly = '" . $db->escape((string) $vatnpr) . "'";
8138 }
8139
8140 $resql = $db->query($sql);
8141
8142 if ($resql) {
8143 $obj = $db->fetch_object($resql);
8144 if ($obj) {
8145 if ($local == 1) {
8146 return $obj->localtax1;
8147 } elseif ($local == 2) {
8148 return $obj->localtax2;
8149 }
8150 }
8151 }
8152
8153 return 0;
8154}
8155
8156
8165function isOnlyOneLocalTax($local)
8166{
8167 $tax = get_localtax_by_third($local);
8168
8169 $valors = explode(":", $tax);
8170
8171 if (count($valors) > 1) {
8172 return false;
8173 } else {
8174 return true;
8175 }
8176}
8177
8184function get_localtax_by_third($local)
8185{
8186 global $db, $mysoc;
8187
8188 $sql = " SELECT t.localtax" . ((int) $local) . " as localtax";
8189 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t INNER JOIN " . MAIN_DB_PREFIX . "c_country as c ON c.rowid = t.fk_pays";
8190 $sql .= " WHERE c.code = '" . $db->escape($mysoc->country_code) . "' AND t.active = 1 AND t.entity IN (" . getEntity('c_tva') . ") AND t.taux = (";
8191 $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";
8192 $sql .= " WHERE c.code = '" . $db->escape($mysoc->country_code) . "' AND t.entity IN (" . getEntity('c_tva') . ") AND tt.active = 1)";
8193 $sql .= " AND t.localtax" . ((int) $local) . "_type <> '0'";
8194 $sql .= " ORDER BY t.rowid DESC";
8195
8196 $resql = $db->query($sql);
8197 if ($resql) {
8198 $obj = $db->fetch_object($resql);
8199 if ($obj) {
8200 return $obj->localtax;
8201 } else {
8202 return '0';
8203 }
8204 }
8205
8206 return 'Error';
8207}
8208
8209
8221function getTaxesFromId($vatrate, $buyer = null, $seller = null, $firstparamisid = 1)
8222{
8223 global $db;
8224
8225 dol_syslog("getTaxesFromId vat id or rate = " . $vatrate);
8226
8227 // Search local taxes
8228 $sql = "SELECT t.rowid, t.code, t.taux as rate, t.recuperableonly as npr, t.accountancy_code_sell, t.accountancy_code_buy,";
8229 $sql .= " t.localtax1, t.localtax1_type, t.localtax2, t.localtax2_type";
8230 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t";
8231 if ($firstparamisid) {
8232 $sql .= " WHERE t.rowid = " . (int) $vatrate;
8233 } else {
8234 $vatratecleaned = $vatrate;
8235 $vatratecode = '';
8236 $reg = array();
8237 if (preg_match('/^(.*)\s*\‍((.*)\‍)$/', $vatrate, $reg)) { // If vat is "xx (yy)"
8238 $vatratecleaned = $reg[1];
8239 $vatratecode = $reg[2];
8240 }
8241
8242 $sql .= ", " . MAIN_DB_PREFIX . "c_country as c";
8243 /*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 ??
8244 else $sql.= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($seller->country_code)."'";*/
8245 $sql .= " WHERE t.fk_pays = c.rowid";
8246 if (getDolGlobalString('SERVICE_ARE_ECOMMERCE_200238EC')) {
8247 $sql .= " AND c.code = '" . $db->escape($buyer->country_code) . "'";
8248 } else {
8249 $sql .= " AND c.code = '" . $db->escape($seller->country_code) . "'";
8250 }
8251 $sql .= " AND t.taux = " . ((float) $vatratecleaned) . " AND t.active = 1";
8252 $sql .= " AND t.entity IN (" . getEntity('c_tva') . ")";
8253 if ($vatratecode) {
8254 $sql .= " AND t.code = '" . $db->escape($vatratecode) . "'";
8255 }
8256 }
8257
8258 $resql = $db->query($sql);
8259 if ($resql) {
8260 $obj = $db->fetch_object($resql);
8261 if ($obj) {
8262 return array(
8263 'rowid' => $obj->rowid,
8264 'code' => $obj->code,
8265 'rate' => $obj->rate,
8266 'localtax1' => $obj->localtax1,
8267 'localtax1_type' => $obj->localtax1_type,
8268 'localtax2' => $obj->localtax2,
8269 'localtax2_type' => $obj->localtax2_type,
8270 'npr' => $obj->npr,
8271 'accountancy_code_sell' => $obj->accountancy_code_sell,
8272 'accountancy_code_buy' => $obj->accountancy_code_buy
8273 );
8274 } else {
8275 return array();
8276 }
8277 } else {
8279 }
8280
8281 return array();
8282}
8283
8300function getLocalTaxesFromRate($vatrate, $local, $buyer, $seller, $firstparamisid = 0)
8301{
8302 global $db, $mysoc;
8303
8304 dol_syslog("getLocalTaxesFromRate vatrate=" . $vatrate . " local=" . $local);
8305
8306 // Search local taxes
8307 $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";
8308 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t";
8309 if ($firstparamisid) {
8310 $sql .= " WHERE t.rowid = " . (int) $vatrate;
8311 } else {
8312 $vatratecleaned = $vatrate;
8313 $vatratecode = '';
8314 $reg = array();
8315 if (preg_match('/^(.*)\s*\‍((.*)\‍)$/', $vatrate, $reg)) { // If vat is "x.x (yy)"
8316 $vatratecleaned = $reg[1];
8317 $vatratecode = $reg[2];
8318 }
8319
8320 $sql .= ", " . MAIN_DB_PREFIX . "c_country as c";
8321 if (!empty($mysoc) && $mysoc->country_code == 'ES') {
8322 $countrycodetouse = ((empty($buyer) || empty($buyer->country_code)) ? $mysoc->country_code : $buyer->country_code);
8323 $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '" . $db->escape($countrycodetouse) . "'"; // local tax in spain use the buyer country ??
8324 } else {
8325 $countrycodetouse = ((empty($seller) || empty($seller->country_code)) ? $mysoc->country_code : $seller->country_code);
8326 $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '" . $db->escape($countrycodetouse) . "'";
8327 }
8328 $sql .= " AND t.taux = " . ((float) $vatratecleaned) . " AND t.active = 1";
8329 if ($vatratecode) {
8330 $sql .= " AND t.code = '" . $db->escape($vatratecode) . "'";
8331 }
8332 }
8333
8334 $resql = $db->query($sql);
8335 if ($resql) {
8336 $obj = $db->fetch_object($resql);
8337
8338 if ($obj) {
8339 $vateratestring = $obj->rate . ($obj->code ? ' (' . $obj->code . ')' : '');
8340
8341 if ($local == 1) {
8342 return array($obj->localtax1_type, get_localtax($vateratestring, $local, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
8343 } elseif ($local == 2) {
8344 return array($obj->localtax2_type, get_localtax($vateratestring, $local, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
8345 } else {
8346 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);
8347 }
8348 }
8349 }
8350
8351 return array();
8352}
8353
8364function get_product_vat_for_country($idprod, $thirdpartytouseforcountry, $idprodfournprice = 0)
8365{
8366 global $db, $mysoc;
8367
8368 require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
8369
8370 $ret = 0;
8371 $found = 0;
8372
8373 if ($idprod > 0) {
8374 // Load product
8375 $product = new Product($db);
8376 $product->fetch($idprod);
8377
8378 if (($mysoc->country_code == $thirdpartytouseforcountry->country_code)
8379 || (in_array($mysoc->country_code, array('FR', 'MC')) && in_array($thirdpartytouseforcountry->country_code, array('FR', 'MC')))
8380 || (in_array($mysoc->country_code, array('MQ', 'GP')) && in_array($thirdpartytouseforcountry->country_code, array('MQ', 'GP')))
8381 ) {
8382 // If country of thirdparty to consider is ours
8383 if ($idprodfournprice > 0) { // We want vat for product for a "supplier" object
8384 $result = $product->get_buyprice($idprodfournprice, 0, 0, '');
8385 if ($result > 0) {
8386 $ret = $product->vatrate_supplier;
8387 if ($product->default_vat_code_supplier) {
8388 $ret .= ' (' . $product->default_vat_code_supplier . ')';
8389 }
8390 $found = 1;
8391 }
8392 }
8393 if (!$found) {
8394 $ret = $product->tva_tx; // Default sales vat of product
8395 if ($product->default_vat_code) {
8396 $ret .= ' (' . $product->default_vat_code . ')';
8397 }
8398 $found = 1;
8399 }
8400 } else {
8401 // TODO Read default product vat according to product and an other countrycode.
8402 // Vat for couple anothercountrycode/product is data that is not managed and store yet, so we will fallback on next rule.
8403 }
8404 }
8405
8406 if (!$found) {
8407 if (!getDolGlobalString('MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS')) {
8408 // 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).
8409 $sql = "SELECT t.taux as vat_rate, t.code as default_vat_code";
8410 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t, " . MAIN_DB_PREFIX . "c_country as c";
8411 $sql .= " WHERE t.active = 1 AND t.fk_pays = c.rowid AND c.code = '" . $db->escape($thirdpartytouseforcountry->country_code) . "'";
8412 $sql .= " AND t.entity IN (" . getEntity('c_tva') . ")";
8413 $sql .= " ORDER BY t.use_default DESC, t.taux DESC, t.code ASC, t.recuperableonly ASC";
8414 $sql .= $db->plimit(1);
8415
8416 $resql = $db->query($sql);
8417 if ($resql) {
8418 $obj = $db->fetch_object($resql);
8419 if ($obj) {
8420 $ret = $obj->vat_rate;
8421 if ($obj->default_vat_code) {
8422 $ret .= ' (' . $obj->default_vat_code . ')';
8423 }
8424 }
8425 $db->free($resql);
8426 } else {
8428 }
8429 } else {
8430 // Forced value if autodetect fails. MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS can be
8431 // '1.23'
8432 // or '1.23 (CODE)'
8433 $defaulttx = '';
8434 if (getDolGlobalString('MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS') != 'none') {
8435 $defaulttx = getDolGlobalString('MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS');
8436 }
8437 /*if (preg_match('/\‍((.*)\‍)/', $defaulttx, $reg)) {
8438 $defaultcode = $reg[1];
8439 $defaulttx = preg_replace('/\s*\‍(.*\‍)/', '', $defaulttx);
8440 }*/
8441
8442 $ret = $defaulttx;
8443 }
8444 }
8445
8446 dol_syslog("get_product_vat_for_country: ret=" . $ret);
8447
8448 return $ret;
8449}
8450
8460function get_product_localtax_for_country($idprod, $local, $thirdpartytouseforcountry)
8461{
8462 global $db, $mysoc;
8463
8464 if (!class_exists('Product')) {
8465 require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
8466 }
8467
8468 $ret = 0;
8469 $found = 0;
8470
8471 if ($idprod > 0) {
8472 // Load product
8473 $product = new Product($db);
8474 $result = $product->fetch($idprod);
8475
8476 if ($mysoc->country_code == $thirdpartytouseforcountry->country_code) { // If selling country is ours
8477 /* Not defined yet, so we don't use this
8478 if ($local==1) $ret=$product->localtax1_tx;
8479 elseif ($local==2) $ret=$product->localtax2_tx;
8480 $found=1;
8481 */
8482 } else {
8483 // TODO Read default product vat according to product and another countrycode.
8484 // Vat for couple anothercountrycode/product is data that is not managed and store yet, so we will fallback on next rule.
8485 }
8486 }
8487
8488 if (!$found) {
8489 // If vat of product for the country not found or not defined, we return higher vat of country.
8490 $sql = "SELECT taux as vat_rate, localtax1, localtax2";
8491 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t, " . MAIN_DB_PREFIX . "c_country as c";
8492 $sql .= " WHERE t.active=1 AND t.fk_pays = c.rowid AND c.code='" . $db->escape($thirdpartytouseforcountry->country_code) . "'";
8493 $sql .= " AND t.entity IN (" . getEntity('c_tva') . ")";
8494 $sql .= " ORDER BY t.taux DESC, t.recuperableonly ASC";
8495 $sql .= $db->plimit(1);
8496
8497 $resql = $db->query($sql);
8498 if ($resql) {
8499 $obj = $db->fetch_object($resql);
8500 if ($obj) {
8501 if ($local == 1) {
8502 $ret = $obj->localtax1;
8503 } elseif ($local == 2) {
8504 $ret = $obj->localtax2;
8505 }
8506 }
8507 } else {
8509 }
8510 }
8511
8512 dol_syslog("get_product_localtax_for_country: ret=" . $ret);
8513 return $ret;
8514}
8515
8534function get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0)
8535{
8536 global $mysoc, $db, $hookmanager;
8537
8538 require_once DOL_DOCUMENT_ROOT . '/core/lib/company.lib.php';
8539
8540 // Note: possible values for tva_assuj are 0/1 or franchise/reel
8541 $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;
8542
8543 if (empty($thirdparty_seller->country_code)) {
8544 $thirdparty_seller->country_code = $mysoc->country_code;
8545 }
8546 $seller_country_code = $thirdparty_seller->country_code;
8547 $seller_in_cee = isInEEC($thirdparty_seller);
8548
8549 if (empty($thirdparty_buyer->country_code)) {
8550 $thirdparty_buyer->country_code = $mysoc->country_code;
8551 }
8552 $buyer_country_code = $thirdparty_buyer->country_code;
8553 $buyer_in_cee = isInEEC($thirdparty_buyer);
8554
8555 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'));
8556
8557 $vatvalue = 0;
8558 $vatrule = '';
8559
8560 // 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)
8561 // we use the buyer VAT.
8562 if (getDolGlobalString('SERVICE_ARE_ECOMMERCE_200238EC')) {
8563 if ($seller_in_cee && $buyer_in_cee) {
8564 $isacompany = $thirdparty_buyer->isACompany();
8565 if ($isacompany && !getDolGlobalString('MAIN_USE_VAT_ZERO_FOR_COMPANIES_IN_EEC_EVEN_IF_VAT_ID_UNKNOWN')) {
8566 require_once DOL_DOCUMENT_ROOT . '/core/lib/functions2.lib.php';
8567 if (!isValidVATID($thirdparty_buyer)) {
8568 $isacompany = 0;
8569 }
8570 }
8571
8572 if (!$isacompany) {
8573 $vatvalue = get_product_vat_for_country($idprod, $thirdparty_buyer, $idprodfournprice);
8574 $vatrule = 'VATRULE 0';
8575 }
8576 }
8577 }
8578
8579 // If seller does not use VAT, default VAT is 0. End of rule.
8580 if (empty($vatrule) && !$seller_use_vat) {
8581 //print 'VATRULE 1';
8582 // TODO get the VAT Code of exemption asked into setup if country isInEEC (from an array list of possible
8583 // values like VATEX-EU-132-*, VATEX-FR-FRANCHISE, VATEX-EU-AE...
8584 // When we had recorded it, we also added a corresponding entry into table of vat code if it does not exists yet.
8585 // Here we test if entry for the VAT exemption code exists in llx_vat, we can return '0 (VATEX-EU-132-xx)'
8586 // If not, we add it and we return '0 (VATEX-EU-132-xx)'
8587 $vatvalue = 0;
8588 $vatrule = 'VATRULE 1';
8589 }
8590
8591 // 'VATRULE 2' - Force VAT if a buyer department is defined on vat rates dictionary
8592 if (empty($vatrule) && !empty($thirdparty_buyer->state_id)) {
8593 $sql = "SELECT d.rowid, t.taux as vat_default_rate, t.code as vat_default_code ";
8594 $sql .= " FROM " . $db->prefix() . "c_tva as t";
8595 $sql .= " INNER JOIN " . $db->prefix() . "c_departements as d ON t.fk_department_buyer = d.rowid";
8596 $sql .= " WHERE d.rowid = " . ((int) $thirdparty_buyer->state_id);
8597 $sql .= " AND t.active > 0";
8598 $sql .= " AND t.entity IN (".getEntity('c_tva').")";
8599 $sql .= " ORDER BY t.use_default DESC, t.taux DESC, t.code ASC, t.recuperableonly ASC";
8600
8601 $res = $db->query($sql);
8602 if ($res) {
8603 if ($db->num_rows($res)) {
8604 $obj = $db->fetch_object($res);
8605
8606 $vatvalue = $obj->vat_default_rate . ' (' . $obj->vat_default_code . ')';
8607 $vatrule = 'VATRULE 2';
8608 }
8609 $db->free($res);
8610 }
8611 }
8612
8613 // If the (seller country = buyer country) then the default VAT = VAT of the product sold. End of rule.
8614 if (empty($vatrule) && (
8615 ($seller_country_code == $buyer_country_code)
8616 || (in_array($seller_country_code, array('FR', 'MC')) && in_array($buyer_country_code, array('FR', 'MC')))
8617 || (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.
8618 )) { // Warning ->country_code not always defined
8619 //print 'VATRULE 3';
8620 $tmpvat = get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
8621
8622 if ($seller_country_code == 'IN' && getDolGlobalString('MAIN_SALETAX_AUTOSWITCH_I_CS_FOR_INDIA')) {
8623 // Special case for india.
8624 //print 'VATRULE 3b';
8625 $reg = array();
8626 if (preg_match('/C+S-(\d+)/', $tmpvat, $reg) && $thirdparty_seller->state_id != $thirdparty_buyer->state_id) {
8627 // we must revert the C+S into I
8628 $tmpvat = str_replace("C+S", "I", $tmpvat);
8629 } elseif (preg_match('/I-(\d+)/', $tmpvat, $reg) && $thirdparty_seller->state_id == $thirdparty_buyer->state_id) {
8630 // we must revert the I into C+S
8631 $tmpvat = str_replace("I", "C+S", $tmpvat);
8632 }
8633 }
8634
8635 $vatvalue = $tmpvat;
8636 $vatrule = 'VATRULE 3b';
8637 }
8638
8639 // 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.
8640 // 'VATRULE 4' - Not supported
8641
8642 // If (seller and buyer in the European Community) and (buyer = individual) then VAT by default = VAT of the product sold. End of rule
8643 // If (seller and buyer in European Community) and (buyer = company) then VAT by default=0. End of rule
8644 if (empty($vatrule) && ($seller_in_cee && $buyer_in_cee)) {
8645 $isacompany = $thirdparty_buyer->isACompany();
8646 if ($isacompany && !getDolGlobalString('MAIN_USE_VAT_ZERO_FOR_COMPANIES_IN_EEC_EVEN_IF_VAT_ID_UNKNOWN')) {
8647 require_once DOL_DOCUMENT_ROOT . '/core/lib/functions2.lib.php';
8648 if (!isValidVATID($thirdparty_buyer)) {
8649 $isacompany = 0;
8650 }
8651 }
8652
8653 if (!$isacompany) {
8654 //print 'VATRULE 5';
8655 $vatvalue = get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
8656 $vatrule = 'VATRULE 5';
8657 } else {
8658 //print 'VATRULE 6';
8659 // TODO This is the case of VAT exemption 'VATEX-EU-IC'
8660 // If entry for the VAT exemption code exists in llx_vat, we can return '0 (VATEX-EU-IC)'
8661 // If not, we add it and we return '0 (VATEX-EU-IC)'
8662 $vatvalue = 0;
8663 $vatrule = 'VATRULE 6';
8664 }
8665 }
8666
8667 // 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
8668 // 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
8669 if (empty($vatrule) && getDolGlobalString('MAIN_USE_VAT_OF_PRODUCT_FOR_INDIVIDUAL_CUSTOMER_OUT_OF_EEC') && empty($buyer_in_cee)) {
8670 $isacompany = $thirdparty_buyer->isACompany();
8671 if (!$isacompany) {
8672 $vatvalue = get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
8673 $vatrule = 'VATRULE extra';
8674 //print 'VATRULE extra';
8675 }
8676 }
8677
8678 // Otherwise the VAT proposed by default=0. End of rule.
8679 // Rem: This means that at least one of the 2 is outside the European Community and the country differs
8680 //print 'VATRULE 7';
8681 // TODO This is the case of VAT exemption 'VATEX-EU-G'
8682 // If entry for the VAT exemption code exists in llx_vat, we can return '0 (VATEX-xxx)'
8683 // If not, we add it and we return '0 (VATEX-xxx)'
8684
8685 // Allow an external module to bypass the calculation of prices
8686 $parameters = array('vatvalue' => $vatvalue, 'vatrule' => $vatrule);
8687 $tmpobject = null;
8688 $tmpaction = '';
8689 // @phan-suppress-next-line PhanPluginConstantVariableNull
8690 $reshook = $hookmanager->executeHooks('get_default_tva', $parameters, $tmpobject, $tmpaction); // @phan-suppress-current-line PhanPluginConstantVariableNull
8691 if ($reshook > 0 && !empty($hookmanager->resArray['vatvalue'])) {
8692 $vatvalue = $hookmanager->resArray['vatvalue'];
8693 $vatrule = $hookmanager->resArray['vatrule']; // For information
8694 }
8695
8696 return $vatvalue;
8697}
8698
8699
8710function get_default_npr(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0)
8711{
8712 global $db;
8713
8714 if ($idprodfournprice > 0) {
8715 if (!class_exists('ProductFournisseur')) {
8716 require_once DOL_DOCUMENT_ROOT . '/fourn/class/fournisseur.product.class.php';
8717 }
8718 $prodprice = new ProductFournisseur($db);
8719 $prodprice->fetch_product_fournisseur_price($idprodfournprice);
8720 return $prodprice->fourn_tva_npr;
8721 } elseif ($idprod > 0) {
8722 if (!class_exists('Product')) {
8723 require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
8724 }
8725 $prod = new Product($db);
8726 $prod->fetch($idprod);
8727 return $prod->tva_npr;
8728 }
8729
8730 return 0;
8731}
8732
8746function get_default_localtax($thirdparty_seller, $thirdparty_buyer, $local, $idprod = 0)
8747{
8748 global $mysoc;
8749
8750 if (!is_object($thirdparty_seller)) {
8751 return -1;
8752 }
8753 if (!is_object($thirdparty_buyer)) {
8754 return -1;
8755 }
8756
8757 if (empty($thirdparty_seller->country_code)) {
8758 $thirdparty_seller->country_code = $mysoc->country_code;
8759 }
8760 $seller_country_code = $thirdparty_seller->country_code;
8761 //$seller_in_cee = isInEEC($thirdparty_seller);
8762
8763 if (empty($thirdparty_buyer->country_code)) {
8764 $thirdparty_buyer->country_code = $mysoc->country_code;
8765 }
8766 $buyer_country_code = $thirdparty_buyer->country_code;
8767 //$buyer_in_cee = isInEEC($thirdparty_buyer);
8768
8769 if ($local == 1) { // Localtax 1
8770 if ($mysoc->country_code == 'ES') {
8771 if (is_numeric($thirdparty_buyer->localtax1_assuj) && !$thirdparty_buyer->localtax1_assuj) {
8772 return 0;
8773 }
8774 } else {
8775 // Si vendeur non assujeti a Localtax1, localtax1 par default=0
8776 if (is_numeric($thirdparty_seller->localtax1_assuj) && !$thirdparty_seller->localtax1_assuj) {
8777 return 0;
8778 }
8779 if (!is_numeric($thirdparty_seller->localtax1_assuj) && $thirdparty_seller->localtax1_assuj == 'localtax1off') {
8780 return 0;
8781 }
8782 }
8783 } elseif ($local == 2) { //I Localtax 2
8784 // Si vendeur non assujeti a Localtax2, localtax2 par default=0
8785 if (is_numeric($thirdparty_seller->localtax2_assuj) && !$thirdparty_seller->localtax2_assuj) {
8786 return 0;
8787 }
8788 if (!is_numeric($thirdparty_seller->localtax2_assuj) && $thirdparty_seller->localtax2_assuj == 'localtax2off') {
8789 return 0;
8790 }
8791 }
8792
8793 if ($seller_country_code == $buyer_country_code) {
8794 return get_product_localtax_for_country($idprod, $local, $thirdparty_seller);
8795 }
8796
8797 return 0;
8798}
8799
8808function yn($yesno, $format = 1, $color = 0)
8809{
8810 global $langs;
8811
8812 $result = 'unknown';
8813 $classname = '';
8814 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'
8815 $result = $langs->trans('yes');
8816 if ($format == 1 || $format == 3) {
8817 $result = $langs->trans("Yes");
8818 }
8819 if ($format == 2) {
8820 $result = '<input type="checkbox" value="1" checked disabled>';
8821 }
8822 if ($format == 3) {
8823 $result = '<input type="checkbox" value="1" checked disabled> ' . $result;
8824 }
8825 if ($format == 4 || !is_numeric($format)) {
8826 $result = img_picto(is_numeric($format) ? '' : $format, 'check');
8827 }
8828
8829 $classname = 'ok';
8830 } else {
8831 $result = $langs->trans("no");
8832 if ($format == 1 || $format == 3) {
8833 $result = $langs->trans("No");
8834 }
8835 if ($format == 2) {
8836 $result = '<input type="checkbox" value="0" disabled>';
8837 }
8838 if ($format == 3) {
8839 $result = '<input type="checkbox" value="0" disabled> ' . $result;
8840 }
8841 if ($format == 4 || !is_numeric($format)) {
8842 $result = img_picto(is_numeric($format) ? '' : $format, 'uncheck');
8843 }
8844
8845 if ($color == 2) {
8846 $classname = 'ok';
8847 } else {
8848 $classname = 'error';
8849 }
8850 }
8851 if ($color) {
8852 return '<span class="' . $classname . '">' . $result . '</span>';
8853 }
8854 return $result;
8855}
8856
8875function get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart = '')
8876{
8877 if (empty($modulepart) && is_object($object)) {
8878 if (!empty($object->module)) {
8879 $modulepart = $object->module;
8880 } elseif (!empty($object->element)) {
8881 $modulepart = $object->element;
8882 }
8883 }
8884
8885 $path = '';
8886
8887 // Define $arrayforoldpath that is module path using a hierarchy on more than 1 level.
8888 $arrayforoldpath = array('cheque' => 2, 'category' => 2, 'supplier_invoice' => 2, 'invoice_supplier' => 2, 'mailing' => 2, 'supplier_payment' => 2);
8889 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
8890 $arrayforoldpath['product'] = 2;
8891 }
8892
8893 if (empty($level) && array_key_exists($modulepart, $arrayforoldpath)) {
8894 $level = $arrayforoldpath[$modulepart];
8895 }
8896 if (!empty($level) && array_key_exists($modulepart, $arrayforoldpath)) {
8897 // This part should be removed once all code is using "get_exdir" to forge path, with parameter $object and $modulepart provided.
8898 if (empty($num) && is_object($object)) {
8899 $num = ((int) $object->id);
8900 }
8901 if (empty($alpha)) {
8902 $num = preg_replace('/([^0-9])/i', '', $num);
8903 } else {
8904 $num = preg_replace('/^.*\-/i', '', $num);
8905 }
8906 $num = substr("000" . $num, -$level);
8907 if ($level == 1) {
8908 $path = substr($num, 0, 1);
8909 }
8910 if ($level == 2) {
8911 $path = substr($num, 1, 1) . '/' . substr($num, 0, 1);
8912 }
8913 if ($level == 3) {
8914 $path = substr($num, 2, 1) . '/' . substr($num, 1, 1) . '/' . substr($num, 0, 1);
8915 }
8916 } else {
8917 // We will enhance here a common way of forging path for document storage.
8918 // In a future, we may distribute directories on several levels depending on setup and object.
8919 // Here, $object->id, $object->ref and $modulepart are required.
8920 if (in_array($modulepart, array('societe', 'thirdparty')) && $object instanceof Societe) {
8921 // 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
8922 $path = dol_sanitizeFileName((string) $object->id);
8923 } else {
8924 $path = dol_sanitizeFileName(empty($object->ref) ? (string) ((is_object($object) && property_exists($object, 'id')) ? ((int) $object->id) : '') : $object->ref);
8925 }
8926 }
8927
8928 if (empty($withoutslash) && !empty($path)) {
8929 $path .= '/';
8930 }
8931
8932 return $path;
8933}
8934
8943function dol_mkdir($dir, $dataroot = '', $newmask = '')
8944{
8945 dol_syslog("functions.lib::dol_mkdir: dir=" . $dir, LOG_INFO);
8946
8947 $dir = dol_sanitizePathName($dir, '_', 0);
8948
8949 $dir_osencoded = dol_osencode($dir);
8950 if (@is_dir($dir_osencoded)) {
8951 return 0;
8952 }
8953
8954 $nberr = 0;
8955 $nbcreated = 0;
8956
8957 $ccdir = '';
8958 if (!empty($dataroot)) {
8959 // Remove data root from loop
8960 $dir = str_replace($dataroot . '/', '', $dir);
8961 $ccdir = $dataroot . '/';
8962 }
8963
8964 $cdir = explode("/", $dir);
8965 $num = count($cdir);
8966 for ($i = 0; $i < $num; $i++) {
8967 if ($i > 0) {
8968 $ccdir .= '/' . $cdir[$i];
8969 } else {
8970 $ccdir .= $cdir[$i];
8971 }
8972 $regs = array();
8973 if (preg_match("/^.:$/", $ccdir, $regs)) {
8974 continue; // If the Windows path is incomplete, continue with next directory
8975 }
8976
8977 // Attention, is_dir() can fail event if the directory exists
8978 // (i.e. according the open_basedir configuration)
8979 if ($ccdir) {
8980 $ccdir_osencoded = dol_osencode($ccdir);
8981 if (!@is_dir($ccdir_osencoded)) {
8982 dol_syslog("functions.lib::dol_mkdir: Directory '" . $ccdir . "' is not found (does not exists or is outside open_basedir PHP setting).", LOG_DEBUG);
8983
8984 umask(0);
8985 $dirmaskdec = octdec((string) $newmask);
8986 if (empty($newmask)) {
8987 $dirmaskdec = octdec(getDolGlobalString('MAIN_UMASK', '0755'));
8988 }
8989 $dirmaskdec |= octdec('0111'); // Set x bit required for directories
8990 if (!@mkdir($ccdir_osencoded, $dirmaskdec)) {
8991 // If the is_dir has returned a false information, we arrive here
8992 dol_syslog("functions.lib::dol_mkdir: Fails to create directory '" . $ccdir . "' (no permission to write into parent or directory already exists).", LOG_WARNING);
8993 $nberr++;
8994 } else {
8995 dol_syslog("functions.lib::dol_mkdir: Directory '" . $ccdir . "' created", LOG_DEBUG);
8996 $nberr = 0; // At this point in the code, the previous failures can be ignored -> set $nberr to 0
8997 $nbcreated++;
8998 }
8999 } else {
9000 $nberr = 0; // At this point in the code, the previous failures can be ignored -> set $nberr to 0
9001 }
9002 }
9003 }
9004 return ($nberr ? -$nberr : $nbcreated);
9005}
9006
9007
9015function dolChmod($filepath, $newmask = '')
9016{
9017 if (!empty($newmask)) {
9018 @chmod($filepath, octdec($newmask));
9019 } elseif (getDolGlobalString('MAIN_UMASK')) {
9020 @chmod($filepath, octdec(getDolGlobalString('MAIN_UMASK')));
9021 }
9022}
9023
9024
9030function picto_required()
9031{
9032 return '<span class="fieldrequired">*</span>';
9033}
9034
9035
9052function dol_string_nohtmltag($stringtoclean, $removelinefeed = 1, $pagecodeto = 'UTF-8', $strip_tags = 0, $removedoublespaces = 1)
9053{
9054 if (is_null($stringtoclean)) {
9055 return '';
9056 }
9057
9058 if ($removelinefeed == 2) {
9059 $stringtoclean = preg_replace('/<br[^>]*>(\n|\r)+/ims', '<br>', $stringtoclean);
9060 }
9061 $temp = preg_replace('/<br[^>]*>/i', "\n", $stringtoclean);
9062
9063 // 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)
9064 $temp = dol_html_entity_decode($temp, ENT_COMPAT | ENT_HTML5, $pagecodeto);
9065
9066 $temp = str_replace('< ', '__ltspace__', $temp);
9067 $temp = str_replace('<:', '__lttwopoints__', $temp);
9068
9069 if ($strip_tags) {
9070 $temp = strip_tags($temp);
9071 } else {
9072 // 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).
9073 $pattern = "/<[^<>]+>/";
9074 // Example of $temp: <a href="/myurl" title="<u>A title</u>">0000-021</a>
9075 // pass 1 - $temp after pass 1: <a href="/myurl" title="A title">0000-021
9076 // pass 2 - $temp after pass 2: 0000-021
9077 $tempbis = $temp;
9078 do {
9079 $temp = $tempbis;
9080 $tempbis = str_replace('<>', '', $temp); // No reason to have this into a text, except if value is to try bypass the next html cleaning
9081 $tempbis = preg_replace($pattern, '', $tempbis);
9082 //$idowhile++; print $temp.'-'.$tempbis."\n"; if ($idowhile > 100) break;
9083 } while ($tempbis != $temp);
9084
9085 $temp = $tempbis;
9086
9087 // 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).
9088 $temp = preg_replace('/<+([a-z]+)/i', '\1', $temp);
9089 }
9090
9091 $temp = dol_html_entity_decode($temp, ENT_COMPAT, $pagecodeto);
9092
9093 // Remove also carriage returns
9094 if ($removelinefeed == 1) {
9095 $temp = str_replace(array("\r\n", "\r", "\n"), " ", $temp);
9096 }
9097
9098 // And double spaces
9099 if ($removedoublespaces) {
9100 while (strpos($temp, " ") !== false) {
9101 $temp = str_replace(" ", " ", $temp);
9102 }
9103 }
9104
9105 $temp = str_replace('__ltspace__', '< ', $temp);
9106 $temp = str_replace('__lttwopoints__', '<:', $temp);
9107
9108 return trim($temp);
9109}
9110
9130function dol_string_onlythesehtmltags($stringtoclean, $cleanalsosomestyles = 1, $removeclassattribute = 1, $cleanalsojavascript = 0, $allowiframe = 0, $allowed_tags = array(), $allowlink = 0, $allowscript = 0, $allowstyle = 0, $allowphp = 0)
9131{
9132 $sav_allowed_tags = $allowed_tags;
9133
9134 if (empty($allowed_tags) || (is_string($allowed_tags) && preg_match('/^common/', $allowed_tags))) {
9135 $allowed_tags = array(
9136 // HTML 4
9137 "html",
9138 "head",
9139 "body",
9140 "article",
9141 "a",
9142 "abbr",
9143 "b",
9144 "blockquote",
9145 "br",
9146 "cite",
9147 "div",
9148 "dl",
9149 "dd",
9150 "dt",
9151 "em",
9152 "font",
9153 "img",
9154 "ins",
9155 "hr",
9156 "i",
9157 "li",
9158 "ol",
9159 "p",
9160 "q",
9161 "s",
9162 "span",
9163 "strike",
9164 "strong",
9165 "title",
9166 "table",
9167 "tr",
9168 "th",
9169 "td",
9170 "u",
9171 "ul",
9172 "sup",
9173 "sub",
9174 "blockquote",
9175 "pre",
9176 "h1",
9177 "h2",
9178 "h3",
9179 "h4",
9180 "h5",
9181 "h6",
9182
9183 // HTML 5
9184 "footer",
9185 "header",
9186 "menu",
9187 "menuitem",
9188 "nav",
9189 "section"
9190 );
9191 }
9192 $allowed_tags[] = "comment"; // this tags is added to manage comment <!--...--> that are replaced into <comment>...</comment>
9193 if ($allowiframe) {
9194 if (!in_array('iframe', $allowed_tags)) {
9195 $allowed_tags[] = "iframe";
9196 }
9197 }
9198 if ($allowlink) {
9199 if (!in_array('link', $allowed_tags)) {
9200 $allowed_tags[] = "link";
9201 }
9202 if (!in_array('meta', $allowed_tags)) {
9203 $allowed_tags[] = "meta";
9204 }
9205 }
9206 if ($allowscript) {
9207 if (!in_array('script', $allowed_tags)) {
9208 $allowed_tags[] = "script";
9209 }
9210 }
9211 if ($allowstyle) {
9212 if (!in_array('style', $allowed_tags)) {
9213 $allowed_tags[] = "style";
9214 }
9215 }
9216 if (is_string($sav_allowed_tags)) {
9217 $tmptags = explode(',', $sav_allowed_tags);
9218 foreach ($tmptags as $tag) {
9219 if ($tag != 'common') {
9220 $allowed_tags[] = $tag;
9221 }
9222 }
9223 }
9224
9225
9226 $allowed_tags_string = implode("><", $allowed_tags);
9227 $allowed_tags_string = '<' . $allowed_tags_string . '>';
9228
9229 $stringtoclean = str_replace('<!DOCTYPE html>', '__!DOCTYPE_HTML__', $stringtoclean); // Replace DOCTYPE to avoid to have it removed by the strip_tags
9230
9231 $stringtoclean = dol_string_nounprintableascii($stringtoclean, 0);
9232
9233 //$stringtoclean = preg_replace('/<!--[^>]*-->/', '', $stringtoclean);
9234 $stringtoclean = preg_replace('/<!--([^>]*)-->/', '<comment>\1</comment>', $stringtoclean);
9235
9236 if ($allowphp) {
9237 $allowed_tags[] = "commentphp";
9238 $stringtoclean = preg_replace('/^<\?php([^"]+)\?>$/i', '<commentphp>\1__</commentphp>', $stringtoclean); // Note: <?php ... > is allowed only if on the same line
9239 $stringtoclean = preg_replace('/"<\?php([^"]+)\?>"/i', '"<commentphp>\1</commentphp>"', $stringtoclean); // Note: "<?php ... >" is allowed only if on the same line
9240 }
9241
9242 $stringtoclean = preg_replace('/&colon;/i', ':', $stringtoclean);
9243 $stringtoclean = preg_replace('/&#58;|&#0+58|&#x3A/i', '', $stringtoclean); // refused string ':' encoded (no reason to have a : encoded like this) to disable 'javascript:...'
9244
9245 // Remove all HTML tags
9246 $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
9247
9248 if ($cleanalsosomestyles) { // Clean for remaining html tags
9249 //$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
9250 $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
9251 $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
9252 }
9253 if ($removeclassattribute) { // Clean for remaining html tags
9254 $temp = preg_replace('/(<[^>]+)\s+class=((["\']).*?\\3|\\w*)/i', '\\1', $temp);
9255 }
9256
9257 // Remove 'javascript:' that we should not find into a text
9258 // 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)).
9259 if ($cleanalsojavascript) {
9260 $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);
9261 }
9262
9263 $temp = str_replace('__!DOCTYPE_HTML__', '<!DOCTYPE html>', $temp); // Restore the DOCTYPE
9264
9265 if ($allowphp) {
9266 $temp = preg_replace('/<commentphp>(.*)<\/commentphp>/', '<?php\1?>', $temp); // Restore php code
9267 }
9268
9269 $temp = preg_replace('/<comment>([^>]*)<\/comment>/', '<!--\1-->', $temp); // Restore html comments
9270
9271
9272 return $temp;
9273}
9274
9275
9288function dol_string_onlythesehtmlattributes($stringtoclean, $allowed_attributes = null)
9289{
9290 if (is_null($allowed_attributes)) {
9291 $allowed_attributes = array(
9292 // HTML 4
9293 "allow",
9294 "allowfullscreen",
9295 "alt",
9296 "async",
9297 "class",
9298 "contenteditable",
9299 "crossorigin",
9300 "data-html",
9301 "frameborder",
9302 "height",
9303 "href",
9304 "id",
9305 "name",
9306 "property",
9307 "rel",
9308 "src",
9309 "style",
9310 "target",
9311 "title",
9312 "type",
9313 "width",
9314
9315 // HTML5
9316 "footer",
9317 "header",
9318 "menu",
9319 "menuitem",
9320 "nav",
9321 "section"
9322 );
9323 }
9324 // Always add content and http-equiv for meta tags, required to force encoding and keep html content in utf8 by load/saveHTML functions.
9325 if (!in_array("content", $allowed_attributes)) {
9326 $allowed_attributes[] = "content";
9327 }
9328 if (!in_array("http-equiv", $allowed_attributes)) {
9329 $allowed_attributes[] = "http-equiv";
9330 }
9331
9332 if (class_exists('DOMDocument') && !empty($stringtoclean)) {
9333 //$stringtoclean = '<?xml encoding="UTF-8"><html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>'.$stringtoclean.'</body></html>';
9334 $stringtoclean = '<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>' . $stringtoclean . '</body></html>';
9335
9336 // Warning: loadHTML does not support HTML5 on old libxml versions.
9337 $dom = new DOMDocument('', 'UTF-8');
9338 // If $stringtoclean is wrong, it will generates warnings. So we disable warnings and restore them later.
9339 $savwarning = error_reporting();
9340 error_reporting(E_ALL & ~E_WARNING & ~E_NOTICE);
9341 $dom->loadHTML($stringtoclean, LIBXML_ERR_NONE | LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD | LIBXML_NONET | LIBXML_NOWARNING | LIBXML_NOXMLDECL);
9342 error_reporting($savwarning);
9343
9344 if ($dom instanceof DOMDocument) {
9345 for ($els = $dom->getElementsByTagname('*'), $i = $els->length - 1; $i >= 0; $i--) {
9346 $el = $els->item($i);
9347 if (!$el instanceof DOMElement) {
9348 continue;
9349 }
9350 $attrs = $el->attributes;
9351 for ($ii = $attrs->length - 1; $ii >= 0; $ii--) {
9352 //var_dump($attrs->item($ii));
9353 if (!empty($attrs->item($ii)->name)) {
9354 if (! in_array($attrs->item($ii)->name, $allowed_attributes)) {
9355 // Delete attribute if not into allowed_attributes @phan-suppress-next-line PhanUndeclaredMethod
9356 $els->item($i)->removeAttribute($attrs->item($ii)->name);
9357 } elseif (in_array($attrs->item($ii)->name, array('style'))) {
9358 // If attribute is 'style'
9359 $valuetoclean = $attrs->item($ii)->value;
9360
9361 if (isset($valuetoclean)) {
9362 do {
9363 $oldvaluetoclean = $valuetoclean;
9364 $valuetoclean = preg_replace('/\/\*.*\*\//m', '', $valuetoclean); // clean css comments
9365 $valuetoclean = preg_replace('/position\s*:\s*[a-z]+/mi', '', $valuetoclean);
9366 if ($els->item($i)->tagName == 'a') { // more paranoiac cleaning for clickable tags.
9367 $valuetoclean = preg_replace('/display\s*:/mi', '', $valuetoclean);
9368 $valuetoclean = preg_replace('/z-index\s*:/mi', '', $valuetoclean);
9369 $valuetoclean = preg_replace('/\s+(top|left|right|bottom)\s*:/mi', '', $valuetoclean);
9370 }
9371
9372 // We do not allow logout|passwordforgotten.php and action= into the content of a "style" tag
9373 $valuetoclean = preg_replace('/(logout|passwordforgotten)\.php/mi', '', $valuetoclean);
9374 $valuetoclean = preg_replace('/action=/mi', '', $valuetoclean);
9375 } while ($oldvaluetoclean != $valuetoclean);
9376 }
9377
9378 $attrs->item($ii)->value = $valuetoclean;
9379 }
9380 }
9381 }
9382 }
9383 }
9384
9385 $dom->encoding = 'UTF-8';
9386
9387 $return = $dom->saveHTML(); // This may add a LF at end of lines, so we will trim later
9388 //$return = '<html><body>aaaa</p>bb<p>ssdd</p>'."\n<p>aaa</p>aa<p>bb</p>";
9389
9390 //$return = preg_replace('/^'.preg_quote('<?xml encoding="UTF-8">', '/').'/', '', $return);
9391 $return = preg_replace('/^' . preg_quote('<html><head><', '/') . '[^<>]*' . preg_quote('></head><body>', '/') . '/', '', $return);
9392 $return = preg_replace('/' . preg_quote('</body></html>', '/') . '$/', '', trim($return));
9393
9394 return trim($return);
9395 } else {
9396 return $stringtoclean;
9397 }
9398}
9399
9411function dol_string_neverthesehtmltags($stringtoclean, $disallowed_tags = array('textarea'), $cleanalsosomestyles = 0)
9412{
9413 $temp = $stringtoclean;
9414 foreach ($disallowed_tags as $tagtoremove) {
9415 $temp = preg_replace('/<\/?' . $tagtoremove . '>/', '', $temp);
9416 $temp = preg_replace('/<\/?' . $tagtoremove . '\s+[^>]*>/', '', $temp);
9417 }
9418
9419 if ($cleanalsosomestyles) {
9420 $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
9421 }
9422
9423 return $temp;
9424}
9425
9426
9436function dolCloseUnclosedHtmlTags($text)
9437{
9438 if (!is_string($text) || $text === '') {
9439 return $text;
9440 }
9441
9442 // Tags that never carry a closing tag
9443 $selfclosing = array('br', 'hr', 'img', 'input', 'meta', 'link', 'source', 'col', 'area', 'base', 'embed', 'param', 'track', 'wbr');
9444
9445 $opened = array();
9446 if (preg_match_all('/<\s*(\/?)([a-zA-Z][a-zA-Z0-9]*)[^>]*?(\/?)\s*>/', $text, $matches, PREG_SET_ORDER)) {
9447 foreach ($matches as $match) {
9448 $tag = strtolower($match[2]);
9449 if (in_array($tag, $selfclosing) || !empty($match[3])) {
9450 continue;
9451 }
9452 if (empty($match[1])) {
9453 $opened[] = $tag;
9454 } else {
9455 // Close the most recent matching opened tag, ignore a stray closing tag
9456 $idx = array_search($tag, array_reverse($opened, true), true);
9457 if ($idx !== false) {
9458 unset($opened[$idx]);
9459 }
9460 }
9461 }
9462 }
9463
9464 foreach (array_reverse($opened) as $tag) {
9465 $text .= '</'.$tag.'>';
9466 }
9467
9468 return $text;
9469}
9470
9480function dolGetFirstLineOfText($text, $nboflines = 1, $charset = 'UTF-8')
9481{
9482 if ($nboflines == 1) {
9483 if (dol_textishtml($text)) {
9484 $firstline = preg_replace('/<br[^>]*>.*$/s', '', $text); // The s pattern modifier means the . can match newline characters
9485 $firstline = preg_replace('/<div[^>]*>.*$/s', '', $firstline); // The s pattern modifier means the . can match newline characters
9486 } else {
9487 if (isset($text)) {
9488 $firstline = preg_replace('/[\n\r].*/', '', $text);
9489 } else {
9490 $firstline = '';
9491 }
9492 }
9493 return $firstline . (isset($firstline) && isset($text) && (strlen($firstline) != strlen($text)) ? '...' : '');
9494 } else {
9495 $ishtml = 0;
9496 if (dol_textishtml($text)) {
9497 $text = preg_replace('/\n/', '', $text);
9498 $ishtml = 1;
9499 $repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
9500 } else {
9501 $repTable = array("\t" => " ", "\n" => "<br>", "\r" => " ", "\0" => " ", "\x0B" => " ");
9502 }
9503
9504 $text = strtr($text, $repTable);
9505 if ($charset == 'UTF-8') {
9506 $pattern = '/(<br[^>]*>)/Uu';
9507 } else {
9508 // /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
9509 $pattern = '/(<br[^>]*>)/U'; // /U is to have UNGREEDY regex to limit to one html tag.
9510 }
9511 $a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
9512
9513 $firstline = '';
9514 $i = 0;
9515 $countline = 0;
9516 $lastaddediscontent = 1;
9517 while ($countline < $nboflines && isset($a[$i])) {
9518 if (preg_match('/<br[^>]*>/', $a[$i])) {
9519 if (array_key_exists($i + 1, $a) && !empty($a[$i + 1])) {
9520 $firstline .= ($ishtml ? "<br>\n" : "\n");
9521 // Is it a br for a new line of after a printed line ?
9522 if (!$lastaddediscontent) {
9523 $countline++;
9524 }
9525 $lastaddediscontent = 0;
9526 }
9527 } else {
9528 $firstline .= $a[$i];
9529 $lastaddediscontent = 1;
9530 $countline++;
9531 }
9532 $i++;
9533 }
9534
9535 $adddots = (isset($a[$i]) && (!preg_match('/<br[^>]*>/', $a[$i]) || (array_key_exists($i + 1, $a) && !empty($a[$i + 1]))));
9536 //unset($a);
9537 $ret = $firstline . ($adddots ? '...' : '');
9538 //exit;
9539 return $ret;
9540 }
9541}
9542
9543
9555function dol_nl2br($stringtoencode, $nl2brmode = 0, $forxml = false)
9556{
9557 if (is_null($stringtoencode)) {
9558 return '';
9559 }
9560
9561 if (!$nl2brmode) {
9562 return nl2br($stringtoencode, $forxml);
9563 } else {
9564 $ret = preg_replace('/(\r\n|\r|\n)/i', ($forxml ? '<br />' : '<br>'), $stringtoencode);
9565 return $ret;
9566 }
9567}
9568
9578function dol_htmlwithnojs($stringtoencode, $nouseofiframesandbox = 0, $check = 'restricthtml')
9579{
9580 if (empty($nouseofiframesandbox) && getDolGlobalString('MAIN_SECURITY_USE_SANDBOX_FOR_HTMLWITHNOJS')) {
9581 // TODO using sandbox on inline html content is not possible yet with current browsers
9582 //$s = '<iframe class="iframewithsandbox" sandbox><html><body>';
9583 //$s .= $stringtoencode;
9584 //$s .= '</body></html></iframe>';
9585 return $stringtoencode;
9586 } else {
9587 $out = $stringtoencode;
9588
9589 // First clean HTML content
9590 do {
9591 $oldstringtoclean = $out;
9592
9593 $outishtml = 0;
9594 if (dol_textishtml($out)) {
9595 $outishtml = 1;
9596 }
9597
9598 // HTML sanitizer by DOMDocument
9599 if (!empty($out) && getDolGlobalInt('MAIN_RESTRICTHTML_ONLY_VALID_HTML') && $check != 'restricthtmlallowunvalid') {
9600 try {
9601 libxml_use_internal_errors(false); // Avoid to fill memory with xml errors
9602 if (LIBXML_VERSION < 20900) {
9603 // Avoid load of external entities (security problem).
9604 // Required only if LIBXML_VERSION < 20900
9605 // @phan-suppress-next-line PhanDeprecatedFunctionInternal
9606 libxml_disable_entity_loader(true);
9607 }
9608
9609 $dom = new DOMDocument();
9610 // Add a trick '<div class="tricktoremove">' to solve pb with text without parent tag
9611 // like '<h1>Foo</h1><p>bar</p>' that wrongly ends up, without the trick, with '<h1>Foo<p>bar</p></h1>'
9612 // like 'abc' that wrongly ends up, without the trick, with '<p>abc</p>'
9613 // Add also a trick <html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"> to solve utf8 lost.
9614 // I don't know what the xml encoding is the trick for
9615 if ($outishtml) {
9616 //$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>';
9617 $out = '<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body><div class="tricktoremove">' . $out . '</div></body></html>';
9618 //$out = '<html><head><meta charset="utf-8"></head><body><div class="tricktoremove">'.$out.'</div></body></html>';
9619 } else {
9620 // String is not a HTML content but pure text so we accept the & char alone. We protect it from being modified by HTML Tidy or XML sanitizers
9621 $out = preg_replace('/&(?![a-zA-Z0-9#]+;)/', '__AMPINTEXT__', $out);
9622
9623 //$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>';
9624 $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>';
9625 //$out = '<html><head><meta charset="utf-8"></head><body><div class="tricktoremove">'.dol_nl2br($out).'</div></body></html>';
9626 }
9627
9628 // Note: <a href="https://__[aaa]__/aaa.html"> is transformed into <a href="https://__[aaa]__/aaa.html">
9629 // We don't want that, so we protect __[xxx]__ by replacing [ and ] before loadHTML and restore them after saveHTML
9630 $out = preg_replace_callback(
9631 '/__\[([0-9a-zA-Z_]+)\]__/',
9636 function ($m) {
9637 return '__BRACKETSTART' . $m[1] . 'BRACKETEND__';
9638 },
9639 $out
9640 );
9641
9642 $dom->loadHTML($out, LIBXML_HTML_NODEFDTD | LIBXML_ERR_NONE | LIBXML_HTML_NOIMPLIED | LIBXML_NONET | LIBXML_NOWARNING | LIBXML_NOERROR | LIBXML_NOXMLDECL);
9643
9644 $dom->encoding = 'UTF-8';
9645
9646 // Add a layer to remove some styles
9647 if (getDolGlobalInt('MAIN_RESTRICTHTML_ONLY_VALID_HTML') == 2) {
9648 foreach ($dom->getElementsByTagName('*') as $el) {
9649 if (!$el instanceof DOMElement) {
9650 continue;
9651 }
9652
9653 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
9654 if ($el->hasAttribute('style')) {
9655 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
9656 $style = $el->getAttribute('style');
9657
9658 // delete some styles
9659 $style = preg_replace('/z-index\s*:/i', '', $style);
9660 $style = preg_replace('/position\s*:/i', '', $style);
9661 $style = preg_replace('/top\s*:/i', '', $style);
9662 $style = preg_replace('/left\s*:/i', '', $style);
9663 $style = preg_replace('/background\s*:/i', '', $style);
9664 /*
9665 $style = preg_replace('/width\s*:/i', '', $style);
9666 $style = preg_replace('/height\s*:/i', '', $style);
9667 $style = preg_replace('/backdrop-filter\s*:/i', '', $style);
9668 */
9669 if (trim($style) === '') {
9670 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
9671 $el->removeAttribute('style');
9672 } else {
9673 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
9674 $el->setAttribute('style', $style);
9675 }
9676 }
9677 }
9678 }
9679
9680 $out = trim($dom->saveHTML());
9681
9682 // Restore [ and ] that were protected before loadHTML
9683 $out = preg_replace_callback(
9684 '/__BRACKETSTART([0-9a-zA-Z_]+)BRACKETEND__/',
9689 function ($m) {
9690 return '__[' . $m[1] . ']__';
9691 },
9692 $out
9693 );
9694
9695 // Remove the trick added to solve pb with text in utf8 and text without parent tag
9696 //$out = preg_replace('/^'.preg_quote('<?xml encoding="UTF-8">', '/').'/', '', $out);
9697 $out = preg_replace('/^' . preg_quote('<html><head><', '/') . '[^<>]+' . preg_quote('></head><body><div class="tricktoremove">', '/') . '/', '', $out);
9698 $out = preg_replace('/' . preg_quote('</div></body></html>', '/') . '$/', '', trim($out));
9699 //$out = preg_replace('/^<\?xml encoding="UTF-8"><div class="tricktoremove">/', '', $out);
9700 //$out = preg_replace('/<\/div>$/', '', $out);
9701
9702 if (!$outishtml) { // If $out was not HTML content we made before a dol_nl2br so we must do the opposite operation now
9703 $out = str_replace('__AMPINTEXT__', '&', $out); // Restore & char not entity
9704 $out = str_replace('<br>', '', $out);
9705 }
9706 } catch (Exception $e) {
9707 // If error, invalid HTML string with no way to clean it
9708 //print $e->getMessage();
9709 $out = 'InvalidHTMLStringCantBeCleaned ' . $e->getMessage();
9710 }
9711 }
9712
9713 // HTML sanitizer by Tidy
9714 // Tidy can't be used for restricthtmlallowunvalid and restricthtmlallowlinkscript
9715 // Tidy can't be used for non html text content as it is corrupting the new lines fields.
9716 if (!empty($out) && getDolGlobalInt('MAIN_RESTRICTHTML_ONLY_VALID_HTML_TIDY') && !in_array($check, array('restricthtmlallowunvalid', 'restricthtmlallowlinkscript')) && $outishtml) {
9717 // TODO Try to implement a hack for restricthtmlallowlinkscript by renaming tag <link> and <script> ?
9718 try {
9719 //var_dump($out);
9720
9721 // Try cleaning using tidy
9722 if (extension_loaded('tidy') && class_exists("tidy")) {
9723 //print "aaa".$out."\n";
9724
9725 // See options at https://tidy.sourceforge.net/docs/quickref.html
9726 $config = array(
9727 'clean' => false,
9728 // 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;
9729 'quote-marks' => false,
9730 'doctype' => 'strict',
9731 'show-body-only' => true,
9732 "indent-attributes" => false,
9733 "vertical-space" => false,
9734 //'ident' => false, // Not always supported
9735 "wrap" => 0,
9736 'preserve-entities' => true
9737 // HTML5 tags
9738 //'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',
9739 //'new-blocklevel-tags' => 'footer header section menu menuitem'
9740 //'new-empty-tags' => 'command embed keygen source track wbr',
9741 //'new-inline-tags' => 'audio command datalist embed keygen mark menuitem meter output progress source time video wbr',
9742 );
9743
9744 // Tidy
9745 $locale = setlocale(LC_NUMERIC, '0'); // Tidy has a bug and is changing the PHP locale. So we save it to restore it after.
9746
9747 $tidy = new tidy();
9748 $out = $tidy->repairString($out, $config, 'utf8');
9749
9750 setlocale(LC_NUMERIC, $locale); // Restore original local
9751
9752 //print "xxx".$out;exit;
9753 }
9754
9755 //var_dump($out);
9756 } catch (Exception $e) {
9757 // If error, invalid HTML string with no way to clean it
9758 //print $e->getMessage();
9759 $out = 'InvalidHTMLStringCantBeCleaned ' . $e->getMessage();
9760 }
9761 }
9762
9763 // Clear ZERO WIDTH NO-BREAK SPACE, ZERO WIDTH SPACE, ZERO WIDTH JOINER
9764 // TODO $out = preg_replace('/[\x{2000}-\x{200D}\x{FEFF}]/u', ' ', $out);
9765 $out = preg_replace('/[\x{200B}-\x{200D}\x{FEFF}]/u', ' ', $out);
9766
9767 // Clean some html entities that are useless so text is cleaner
9768 $out = preg_replace('/&(tab|newline);/i', ' ', $out);
9769
9770 // Ckeditor uses the numeric entity for apostrophe, so we force it to
9771 // the text entity (all other special chars are encoded using text entities) so we can then exclude all numeric entities.
9772 $out = preg_replace('/&#39;/i', '&apos;', $out);
9773
9774 // 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).
9775 // 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
9776 // using a non conventionnal way to be encoded, to not have them sanitized just after)
9777 if (function_exists('realCharForNumericEntities')) { // May not exist when main.inc.php not loaded, for example in a CLI context
9778 $out = preg_replace_callback(
9779 '/&#(x?[0-9][0-9a-f]+;?)/i',
9784 static function ($m) {
9785 return realCharForNumericEntities($m);
9786 },
9787 $out
9788 );
9789 }
9790
9791 // Now we remove all remaining HTML entities starting with a number. We don't want such entities.
9792 $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'.
9793
9794 // Keep only some html tags and remove also some 'javascript:' strings
9795 if ($check == 'restricthtmlallowlinkscript') {
9796 $out = dol_string_onlythesehtmltags($out, 0, 1, 0, 0, array(), 1, 1, 1, getDolGlobalInt("UNSECURED_restricthtmlallowlinkscript_ALLOW_PHP"));
9797 } elseif ($check == 'restricthtmlallowclass' || $check == 'restricthtmlallowunvalid') {
9798 $out = dol_string_onlythesehtmltags($out, 0, 0, 1);
9799 } elseif ($check == 'restricthtmlallowiframe') {
9800 $out = dol_string_onlythesehtmltags($out, 0, 0, 1, 1);
9801 } else {
9802 $out = dol_string_onlythesehtmltags($out, 0, 1, 1); // styles are allowed to allow rich text editor features of ckeditor managed by the "style=" attribute
9803 }
9804
9805 // Keep only some html attributes and exclude non expected HTML attributes and clean content of some attributes (keep only alt=, title=...).
9806 if (getDolGlobalString('MAIN_RESTRICTHTML_REMOVE_ALSO_BAD_ATTRIBUTES')) {
9808 }
9809
9810 // Restore entity &apos; into &#39; (restricthtml is for html content so we can use html entity) because it is
9811 // compatible with HTML 4 used y CKEditor, and HTML 5 (when &apos; works only with HTML5).
9812 $out = preg_replace('/&apos;/i', "&#39;", $out);
9813
9814 // Now remove js
9815 // 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
9816 $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)>
9817 $out = preg_replace('/on(abort|after|animation|auxclick|before|blur|cancel|canplay|canplaythrough|change|click|close|command|contentvisibility|context|cuechange|copy|cut)[a-z]*\s*=/i', '', $out);
9818 $out = preg_replace('/on(dblclick|drop|durationchange|emptied|end|ended|error|focus(in|out)?|formdata|gotpointercapture|hashchange|input|invalid)[a-z]*\s*=/i', '', $out);
9819 $out = preg_replace('/on(lost|offline|online|message|pagehide|pageshow)[a-z]*\s*=/i', '', $out);
9820 $out = preg_replace('/on(paste|pause|play|playing|progress|ratechange|rejectionn|reset|resize|scroll|search|security|seeked|seeking|show|stalled|start|submit|suspend)[a-z]*\s*=/i', '', $out);
9821 $out = preg_replace('/on(timeupdate|toggle|unhandled|unload|volumechange|waiting|wheel)[a-z]*\s*=/i', '', $out);
9822 // More not into the previous list
9823 $out = preg_replace('/on(repeat|begin|finish|beforeinput)[a-z]*\s*=/i', '', $out);
9824 // Add also a generic removal of any onxxx= attribute
9825 $out = preg_replace('/\son[a-z]+\s*=/i', ' ', $out);
9826 } while ($oldstringtoclean != $out);
9827
9828 // Check the limit of external links that are automatically executed in a Rich text content. We count:
9829 // '<img' to avoid <img src="http...">, we can only accept "<img src="data:..."
9830 // 'url(' to avoid inline style like background: url(http...
9831 // '<link' to avoid <link href="http...">
9832 $reg = array();
9833 $tmpout = preg_replace('/<img src="data:/mi', '<__IMG_SRC_DATA__ src="data:', $out);
9834 preg_match_all('/(<img|url\‍(|<link)/i', $tmpout, $reg);
9835 $nblinks = count($reg[0]);
9836 if ($nblinks > getDolGlobalInt("MAIN_SECURITY_MAX_IMG_IN_HTML_CONTENT", 1000)) {
9837 $out = 'ErrorTooManyLinksIntoHTMLString';
9838 }
9839
9840 if (getDolGlobalInt('MAIN_DISALLOW_URL_INTO_DESCRIPTIONS') == 2 || $check == 'restricthtmlnolink') {
9841 if ($nblinks > 0) {
9842 $out = 'ErrorHTMLLinksNotAllowed';
9843 }
9844 } elseif (getDolGlobalInt('MAIN_DISALLOW_URL_INTO_DESCRIPTIONS') == 1) {
9845 // Refuse any links except it they are to the wrapper document.php or viewimage.php
9846 $nblinks = 0;
9847
9848 // Loop on each url in src= and url(
9849 $pattern = '/src=["\']?(http[^"\']+)|url\‍(["\']?(http[^\‍)]+)/';
9850
9852
9853 $matches = array();
9854 if (preg_match_all($pattern, $out, $matches)) {
9855 // URLs are into $matches[1] or $matches[2]
9856 $urls = array();
9857 foreach ($matches[1] as $tmpval) {
9858 if (!empty($tmpval)) {
9859 $urls[] = $tmpval;
9860 }
9861 }
9862 foreach ($matches[2] as $tmpval) {
9863 if (!empty($tmpval)) {
9864 $urls[] = $tmpval;
9865 }
9866 }
9867
9868 // Show URLs
9869 $firstexturl = '';
9870 $secondexturl = '';
9871 foreach ($urls as $url) {
9872 $urlok = 0;
9873 $parsedurl = parse_url($url);
9874 if (!empty($parsedurl)) {
9875 if (preg_match('/'.preg_quote($dolibarr_main_url_root, '/').'/', $url)
9876 //&& preg_match('/(document|viewimage)\.php$/', $parsedurl['path']) && preg_match('/modulepart=(media|mycompany)/', $parsedurl['query'])
9877 ) {
9878 $urlok = 1;
9879 }
9880 }
9881 if (!$urlok) {
9882 $nblinks++;
9883 if (empty($firstexturl)) {
9884 $firstexturl = $url;
9885 } elseif (empty($secondexturl)) {
9886 $secondexturl = $url;
9887 }
9888 //echo "Found url = ".$url . "\n";
9889 }
9890 }
9891 if ($nblinks > 0) {
9892 $out = 'ErrorHTMLExternalLinksNotAllowed (Example: '.$firstexturl.($secondexturl ? ' '.$secondexturl : '').')';
9893 }
9894 }
9895 }
9896
9897 return $out;
9898 }
9899}
9900
9921function dol_htmlentitiesbr($stringtoencode, $nl2brmode = 0, $pagecodefrom = 'UTF-8', $removelasteolbr = 1)
9922{
9923 if (is_null($stringtoencode)) {
9924 return '';
9925 }
9926
9927 $newstring = $stringtoencode;
9928 if (dol_textishtml($stringtoencode)) { // Check if text is already HTML or not
9929 $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.
9930 if ($removelasteolbr) {
9931 $newstring = preg_replace('/<br>$/i', '', $newstring); // Remove last <br> (remove only last one)
9932 }
9933 $newstring = preg_replace('/[\x{200B}-\x{200D}\x{FEFF}]/u', ' ', $newstring);
9934 $newstring = strtr($newstring, array('&' => '__PROTECTand__', '<' => '__PROTECTlt__', '>' => '__PROTECTgt__', '"' => '__PROTECTdquot__'));
9935 $newstring = dol_htmlentities($newstring, ENT_COMPAT, $pagecodefrom); // Make entity encoding
9936 $newstring = strtr($newstring, array('__PROTECTand__' => '&', '__PROTECTlt__' => '<', '__PROTECTgt__' => '>', '__PROTECTdquot__' => '"'));
9937 } else {
9938 if ($removelasteolbr) {
9939 $newstring = preg_replace('/(\r\n|\r|\n)$/i', '', $newstring); // Remove last \n (may remove several)
9940 }
9941 $newstring = dol_nl2br(dol_htmlentities($newstring, ENT_COMPAT, $pagecodefrom), $nl2brmode);
9942 }
9943 // Other substitutions that htmlentities does not do
9944 //$newstring=str_replace(chr(128),'&euro;',$newstring); // 128 = 0x80. Not in html entity table. // Seems useles with TCPDF. Make bug with UTF8 languages
9945 return $newstring;
9946}
9947
9955function dol_htmlentitiesbr_decode($stringtodecode, $pagecodeto = 'UTF-8')
9956{
9957 $ret = dol_html_entity_decode($stringtodecode, ENT_COMPAT | ENT_HTML5, $pagecodeto);
9958 $ret = preg_replace('/' . "\r\n" . '<br(\s[\sa-zA-Z_="]*)?\/?>/i', "<br>", $ret);
9959 $ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>' . "\r\n" . '/i', "\r\n", $ret);
9960 $ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>' . "\n" . '/i', "\n", $ret);
9961 $ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>/i', "\n", $ret);
9962 return $ret;
9963}
9964
9971function dol_htmlcleanlastbr($stringtodecode)
9972{
9973 $ret = preg_replace('/&nbsp;$/i', "", $stringtodecode); // Because wysiwyg editor may add a &nbsp; at end of last line
9974 $ret = preg_replace('/(<br>|<br(\s[\sa-zA-Z_="]*)?\/?>|' . "\n" . '|' . "\r" . ')+$/i', "", $ret);
9975 return $ret;
9976}
9977
9987function dol_html_entity_decode($a, $b, $c = 'UTF-8', $keepsomeentities = 0)
9988{
9989 $newstring = $a;
9990 if ($keepsomeentities) {
9991 $newstring = strtr($newstring, array('&amp;' => '__andamp__', '&lt;' => '__andlt__', '&gt;' => '__andgt__', '"' => '__dquot__'));
9992 }
9993 $newstring = html_entity_decode((string) $newstring, (int) $b, (string) $c);
9994 if ($keepsomeentities) {
9995 $newstring = strtr($newstring, array('__andamp__' => '&amp;', '__andlt__' => '&lt;', '__andgt__' => '&gt;', '__dquot__' => '"'));
9996 }
9997 return $newstring;
9998}
9999
10011function dol_htmlentities($string, $flags = ENT_QUOTES | ENT_SUBSTITUTE, $encoding = 'UTF-8', $double_encode = false)
10012{
10013 return htmlentities($string, $flags, $encoding, $double_encode);
10014}
10015
10027function dol_string_is_good_iso($s, $clean = 0)
10028{
10029 $len = dol_strlen($s);
10030 $out = '';
10031 $ok = 1;
10032 for ($scursor = 0; $scursor < $len; $scursor++) {
10033 $ordchar = ord($s[$scursor]);
10034 //print $scursor.'-'.$ordchar.'<br>';
10035 if ($ordchar < 32 && $ordchar != 13 && $ordchar != 10) {
10036 $ok = 0;
10037 break;
10038 } elseif ($ordchar > 126 && $ordchar < 160) {
10039 $ok = 0;
10040 break;
10041 } elseif ($clean) {
10042 $out .= $s[$scursor];
10043 }
10044 }
10045 if ($clean) {
10046 return $out;
10047 }
10048 return $ok;
10049}
10050
10059function dol_nboflines($s, $maxchar = 0)
10060{
10061 if ($s == '') {
10062 return 0;
10063 }
10064 $arraystring = explode("\n", $s);
10065 $nb = count($arraystring);
10066
10067 return $nb;
10068}
10069
10070
10080function dol_nboflines_bis($text, $maxlinesize = 0, $charset = 'UTF-8')
10081{
10082 $repTable = array("\t" => " ", "\n" => "<br>", "\r" => " ", "\0" => " ", "\x0B" => " ");
10083 if (dol_textishtml($text)) {
10084 $repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
10085 }
10086
10087 $text = strtr($text, $repTable);
10088 if ($charset == 'UTF-8') {
10089 $pattern = '/(<br[^>]*>)/Uu';
10090 } else {
10091 // /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
10092 $pattern = '/(<br[^>]*>)/U'; // /U is to have UNGREEDY regex to limit to one html tag.
10093 }
10094 $a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
10095
10096 $nblines = (int) floor((count($a) + 1) / 2);
10097 // count possible auto line breaks
10098 if ($maxlinesize) {
10099 foreach ($a as $line) {
10100 if (dol_strlen($line) > $maxlinesize) {
10101 //$line_dec = html_entity_decode(strip_tags($line));
10102 $line_dec = html_entity_decode($line);
10103 if (dol_strlen($line_dec) > $maxlinesize) {
10104 $line_dec = wordwrap($line_dec, $maxlinesize, '\n', true);
10105 $nblines += substr_count($line_dec, '\n');
10106 }
10107 }
10108 }
10109 }
10110
10111 unset($a);
10112 return $nblines;
10113}
10114
10123function dol_textishtml($msg, $option = 0)
10124{
10125 if (is_null($msg)) {
10126 return false;
10127 }
10128
10129 if ($option == 1) {
10130 if (preg_match('/<(html|link|script)/i', $msg)) {
10131 return true;
10132 } elseif (preg_match('/<body/i', $msg)) {
10133 return true;
10134 } elseif (preg_match('/<\/textarea/i', $msg)) {
10135 return true;
10136 } elseif (preg_match('/<(b|em|i|u)(\s+[^>]+)?>/i', $msg)) {
10137 return true;
10138 } elseif (preg_match('/<br/i', $msg)) {
10139 return true;
10140 }
10141 return false;
10142 } else {
10143 // Remove all urls because 'http://aa?param1=abc&amp;param2=def' must not be used inside detection
10144 $msg = preg_replace('/https?:\/\/[^"\'\s]+/i', '', $msg);
10145 if (preg_match('/<(html|link|script|body)/i', $msg)) {
10146 return true;
10147 } elseif (preg_match('/<\/textarea/i', $msg)) {
10148 return true;
10149 } elseif (preg_match('/<(b|em|i|u)(\s+[^>]+)?>/i', $msg)) {
10150 return true;
10151 } elseif (preg_match('/<(br|hr)\/>/i', $msg)) {
10152 return true;
10153 } elseif (preg_match('/<(br|hr|div|font|li|p|span|strong|table)>/i', $msg)) {
10154 return true;
10155 } elseif (preg_match('/<(br|hr|div|font|li|p|span|strong|table)\s+[^<>\/]*\/?>/i', $msg)) {
10156 return true;
10157 } elseif (preg_match('/<img\s+[^<>]*src[^<>]*>/i', $msg)) {
10158 return true; // must accept <img src="http://example.com/aaa.png" />
10159 } elseif (preg_match('/<a\s+[^<>]*href[^<>]*>/i', $msg)) {
10160 return true; // must accept <a href="http://example.com/aaa.png" />
10161 } elseif (preg_match('/<h[0-9]>/i', $msg)) {
10162 return true;
10163 } elseif (preg_match('/&[A-Z0-9]{1,6};/i', $msg)) {
10164 // TODO If content is 'A link https://aaa?param=abc&amp;param2=def', it return true but must be false
10165 return true; // Html entities names (http://www.w3schools.com/tags/ref_entities.asp)
10166 } elseif (preg_match('/&#[0-9]{2,3};/i', $msg)) {
10167 return true; // Html entities numbers (http://www.w3schools.com/tags/ref_entities.asp)
10168 } elseif (preg_match('/&#x[a-f0-9][a-f0-9];/i', $msg)) {
10169 return true; // Html entities numbers in hexa
10170 }
10171
10172 return false;
10173 }
10174}
10175
10190function dol_concatdesc($text1, $text2, $forxml = false, $invert = false)
10191{
10192 if (!empty($invert)) {
10193 $tmp = $text1;
10194 $text1 = $text2;
10195 $text2 = $tmp;
10196 }
10197
10198 $ret = '';
10199 $ret .= (!dol_textishtml($text1) && dol_textishtml($text2)) ? dol_nl2br(dol_escape_htmltag($text1, 0, 1, '', 1), 0, $forxml) : $text1;
10200 $ret .= (!empty($text1) && !empty($text2)) ? ((dol_textishtml($text1) || dol_textishtml($text2)) ? ($forxml ? "<br >\n" : "<br>\n") : "\n") : "";
10201 $ret .= (dol_textishtml($text1) && !dol_textishtml($text2)) ? dol_nl2br(dol_escape_htmltag($text2, 0, 1, '', 1), 0, $forxml) : $text2;
10202 return $ret;
10203}
10204
10213function dol_concat($text1, $text2)
10214{
10215 return $text1.$text2;
10216}
10217
10225function safeArrayMap($callback, array $array)
10226{
10227 if (!is_string($callback)) {
10228 throw new InvalidArgumentException("Les callbacks sont désactivés.");
10229 }
10230 // Check that $callback is a sure function
10231 $allowed_callbacks = ['strtolower', 'strtoupper', 'intval'];
10232 if (!in_array($callback, $allowed_callbacks, true)) {
10233 throw new InvalidArgumentException("Callback function not allowed.");
10234 }
10235 return array_map($callback, $array);
10236}
10237
10238
10252function getCommonSubstitutionArray($outputlangs, $onlykey = 0, $exclude = null, $object = null, $include = null)
10253{
10254 global $db, $conf, $mysoc, $user, $extrafields;
10255
10256 $substitutionarray = array();
10257
10258 if ((empty($exclude) || !in_array('user', $exclude)) && (empty($include) || in_array('user', $include)) && $user instanceof User) {
10259 // Add SIGNATURE into substitutionarray first, so, when we will make the substitution,
10260 // this will include signature content first and then replace var found into content of signature
10261 //var_dump($onlykey);
10262 $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()
10263 $usersignature = $user->signature;
10264 $substitutionarray = array_merge($substitutionarray, array(
10265 '__SENDEREMAIL_SIGNATURE__' => (string) ((!getDolGlobalString('MAIN_MAIL_DO_NOT_USE_SIGN')) ? ($onlykey == 2 ? dol_trunc('SignatureFromTheSelectedSenderProfile', 30) : $emailsendersignature) : ''),
10266 '__USER_SIGNATURE__' => (string) (($usersignature && !getDolGlobalString('MAIN_MAIL_DO_NOT_USE_SIGN')) ? ($onlykey == 2 ? dol_trunc(dol_string_nohtmltag($usersignature), 30) : $usersignature) : '')
10267 ));
10268
10269 if (is_object($user) && ($user instanceof User)) {
10270 $substitutionarray = array_merge($substitutionarray, array(
10271 '__USER_ID__' => (string) $user->id,
10272 '__USER_LOGIN__' => (string) $user->login,
10273 '__USER_EMAIL__' => (string) $user->email,
10274 '__USER_PHONE__' => (string) dol_print_phone($user->office_phone, '', 0, 0, '', " ", '', '', -1),
10275 '__USER_PHONEPRO__' => (string) dol_print_phone($user->user_mobile, '', 0, 0, '', " ", '', '', -1),
10276 '__USER_PHONEMOBILE__' => (string) dol_print_phone($user->personal_mobile, '', 0, 0, '', " ", '', '', -1),
10277 '__USER_FAX__' => (string) $user->office_fax,
10278 '__USER_LASTNAME__' => (string) $user->lastname,
10279 '__USER_FIRSTNAME__' => (string) $user->firstname,
10280 '__USER_FULLNAME__' => (string) $user->getFullName($outputlangs),
10281 '__USER_SUPERVISOR_ID__' => (string) ($user->fk_user ? $user->fk_user : '0'),
10282 '__USER_JOB__' => (string) $user->job,
10283 '__USER_REMOTE_IP__' => (string) getUserRemoteIP(),
10284 '__USER_VCARD_URL__' => (string) $user->getOnlineVirtualCardUrl('', 'external')
10285 ));
10286 }
10287 }
10288 if ((empty($exclude) || !in_array('mycompany', $exclude)) && is_object($mysoc) && (empty($include) || in_array('mycompany', $include))) {
10289 $substitutionarray = array_merge($substitutionarray, array(
10290 '__MYCOMPANY_NAME__' => $mysoc->name,
10291 '__MYCOMPANY_EMAIL__' => $mysoc->email,
10292 '__MYCOMPANY_URL__' => $mysoc->url,
10293 '__MYCOMPANY_PHONE__' => dol_print_phone((string) $mysoc->phone, '', 0, 0, '', " ", '', '', -1),
10294 '__MYCOMPANY_PHONEMOBILE__' => dol_print_phone((string) $mysoc->phone_mobile, '', 0, 0, '', " ", '', '', -1),
10295 '__MYCOMPANY_FAX__' => dol_print_phone((string) $mysoc->fax, '', 0, 0, '', " ", '', '', -1),
10296 '__MYCOMPANY_PROFID1__' => $mysoc->idprof1,
10297 '__MYCOMPANY_PROFID2__' => $mysoc->idprof2,
10298 '__MYCOMPANY_PROFID3__' => $mysoc->idprof3,
10299 '__MYCOMPANY_PROFID4__' => $mysoc->idprof4,
10300 '__MYCOMPANY_PROFID5__' => $mysoc->idprof5,
10301 '__MYCOMPANY_PROFID6__' => $mysoc->idprof6,
10302 '__MYCOMPANY_PROFID7__' => $mysoc->idprof7,
10303 '__MYCOMPANY_PROFID8__' => $mysoc->idprof8,
10304 '__MYCOMPANY_PROFID9__' => $mysoc->idprof9,
10305 '__MYCOMPANY_PROFID10__' => $mysoc->idprof10,
10306 '__MYCOMPANY_CAPITAL__' => $mysoc->capital,
10307 '__MYCOMPANY_FULLADDRESS__' => (method_exists($mysoc, 'getFullAddress') ? $mysoc->getFullAddress(1, ', ') : ''), // $mysoc may be stdClass
10308 '__MYCOMPANY_ADDRESS__' => $mysoc->address,
10309 '__MYCOMPANY_VATNUMBER__' => $mysoc->tva_intra,
10310 '__MYCOMPANY_ZIP__' => $mysoc->zip,
10311 '__MYCOMPANY_TOWN__' => $mysoc->town,
10312 '__MYCOMPANY_STATE__' => $mysoc->state,
10313 '__MYCOMPANY_COUNTRY__' => $mysoc->country,
10314 '__MYCOMPANY_COUNTRY_ID__' => $mysoc->country_id,
10315 '__MYCOMPANY_COUNTRY_CODE__' => $mysoc->country_code,
10316 '__MYCOMPANY_CURRENCY_CODE__' => $conf->currency
10317 ));
10318 }
10319
10320 if (($onlykey || is_object($object)) && (empty($exclude) || !in_array('object', $exclude)) && (empty($include) || in_array('object', $include))) {
10321 if ($onlykey) {
10322 $substitutionarray['__ID__'] = '__ID__';
10323 $substitutionarray['__REF__'] = '__REF__';
10324 $substitutionarray['__NEWREF__'] = '__NEWREF__';
10325 $substitutionarray['__LABEL__'] = '__LABEL__';
10326 $substitutionarray['__REF_CLIENT__'] = '__REF_CLIENT__';
10327 $substitutionarray['__REF_SUPPLIER__'] = '__REF_SUPPLIER__';
10328 $substitutionarray['__NOTE_PUBLIC__'] = '__NOTE_PUBLIC__';
10329 $substitutionarray['__NOTE_PRIVATE__'] = '__NOTE_PRIVATE__';
10330 $substitutionarray['__EXTRAFIELD_XXX__'] = '__EXTRAFIELD_XXX__';
10331
10332 if (isModEnabled("societe")) { // Most objects are concerned
10333 $substitutionarray['__THIRDPARTY_ID__'] = '__THIRDPARTY_ID__';
10334 $substitutionarray['__THIRDPARTY_NAME__'] = '__THIRDPARTY_NAME__';
10335 $substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = '__THIRDPARTY_NAME_ALIAS__';
10336 $substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = '__THIRDPARTY_CODE_CLIENT__';
10337 $substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = '__THIRDPARTY_CODE_FOURNISSEUR__';
10338 $substitutionarray['__THIRDPARTY_EMAIL__'] = '__THIRDPARTY_EMAIL__';
10339 //$substitutionarray['__THIRDPARTY_EMAIL_URLENCODED__'] = '__THIRDPARTY_EMAIL_URLENCODED__'; // We hide this one
10340 $substitutionarray['__THIRDPARTY_URL__'] = '__THIRDPARTY_URL__';
10341 //$substitutionarray['__THIRDPARTY_URL_URLENCODED__'] = '__THIRDPARTY_URL_URLENCODED__'; // We hide this one
10342 $substitutionarray['__THIRDPARTY_PHONE__'] = '__THIRDPARTY_PHONE__';
10343 $substitutionarray['__THIRDPARTY_FAX__'] = '__THIRDPARTY_FAX__';
10344 $substitutionarray['__THIRDPARTY_ADDRESS__'] = '__THIRDPARTY_ADDRESS__';
10345 $substitutionarray['__THIRDPARTY_ZIP__'] = '__THIRDPARTY_ZIP__';
10346 $substitutionarray['__THIRDPARTY_TOWN__'] = '__THIRDPARTY_TOWN__';
10347 $substitutionarray['__THIRDPARTY_STATE__'] = '__THIRDPARTY_STATE__';
10348 $substitutionarray['__THIRDPARTY_IDPROF1__'] = '__THIRDPARTY_IDPROF1__';
10349 $substitutionarray['__THIRDPARTY_IDPROF2__'] = '__THIRDPARTY_IDPROF2__';
10350 $substitutionarray['__THIRDPARTY_IDPROF3__'] = '__THIRDPARTY_IDPROF3__';
10351 $substitutionarray['__THIRDPARTY_IDPROF4__'] = '__THIRDPARTY_IDPROF4__';
10352 $substitutionarray['__THIRDPARTY_IDPROF5__'] = '__THIRDPARTY_IDPROF5__';
10353 $substitutionarray['__THIRDPARTY_IDPROF6__'] = '__THIRDPARTY_IDPROF6__';
10354 $substitutionarray['__THIRDPARTY_IDPROF7__'] = '__THIRDPARTY_IDPROF7__';
10355 $substitutionarray['__THIRDPARTY_IDPROF8__'] = '__THIRDPARTY_IDPROF8__';
10356 $substitutionarray['__THIRDPARTY_IDPROF9__'] = '__THIRDPARTY_IDPROF9__';
10357 $substitutionarray['__THIRDPARTY_IDPROF10__'] = '__THIRDPARTY_IDPROF10__';
10358 $substitutionarray['__THIRDPARTY_TVAINTRA__'] = '__THIRDPARTY_TVAINTRA__';
10359 $substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = '__THIRDPARTY_NOTE_PUBLIC__';
10360 $substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = '__THIRDPARTY_NOTE_PRIVATE__';
10361 }
10362 if (isModEnabled('member') && (!is_object($object) || $object->element == 'adherent') && (empty($exclude) || !in_array('member', $exclude)) && (empty($include) || in_array('member', $include))) {
10363 $substitutionarray['__MEMBER_ID__'] = '__MEMBER_ID__';
10364 $substitutionarray['__MEMBER_TITLE__'] = '__MEMBER_TITLE__';
10365 $substitutionarray['__MEMBER_FIRSTNAME__'] = '__MEMBER_FIRSTNAME__';
10366 $substitutionarray['__MEMBER_LASTNAME__'] = '__MEMBER_LASTNAME__';
10367 $substitutionarray['__MEMBER_USER_LOGIN_INFORMATION__'] = 'Login and pass of the external user account';
10368 /*$substitutionarray['__MEMBER_NOTE_PUBLIC__'] = '__MEMBER_NOTE_PUBLIC__';
10369 $substitutionarray['__MEMBER_NOTE_PRIVATE__'] = '__MEMBER_NOTE_PRIVATE__';*/
10370 }
10371 // add substitution variables for ticket
10372 if (isModEnabled('ticket') && (!is_object($object) || $object->element == 'ticket') && (empty($exclude) || !in_array('ticket', $exclude)) && (empty($include) || in_array('ticket', $include))) {
10373 $substitutionarray['__TICKET_TRACKID__'] = '__TICKET_TRACKID__';
10374 $substitutionarray['__TICKET_SUBJECT__'] = '__TICKET_SUBJECT__';
10375 $substitutionarray['__TICKET_TYPE__'] = '__TICKET_TYPE__';
10376 $substitutionarray['__TICKET_SEVERITY__'] = '__TICKET_SEVERITY__';
10377 $substitutionarray['__TICKET_CATEGORY__'] = '__TICKET_CATEGORY__';
10378 $substitutionarray['__TICKET_ANALYTIC_CODE__'] = '__TICKET_ANALYTIC_CODE__';
10379 $substitutionarray['__TICKET_MESSAGE__'] = '__TICKET_MESSAGE__';
10380 $substitutionarray['__TICKET_PROGRESSION__'] = '__TICKET_PROGRESSION__';
10381 $substitutionarray['__TICKET_USER_ASSIGN__'] = '__TICKET_USER_ASSIGN__';
10382 $substitutionarray['__TICKET_TYPE_LABEL__'] = '__TICKET_TYPE_LABEL__';
10383 $substitutionarray['__TICKET_SEVERITY_LABEL__'] = '__TICKET_SEVERITY_LABEL__';
10384 $substitutionarray['__TICKET_CATEGORY_LABEL__'] = '__TICKET_CATEGORY_LABEL__';
10385 $substitutionarray['__TICKET_PUBLIC_URL__'] = '__TICKET_PUBLIC_URL__';
10386 $substitutionarray['__TICKET_MANAGEMENT_URL__'] = '__TICKET_MANAGEMENT_URL__';
10387 }
10388 if (isModEnabled('recruitment') && (!is_object($object) || $object->element == 'recruitmentcandidature') && (empty($exclude) || !in_array('recruitment', $exclude)) && (empty($include) || in_array('recruitment', $include))) {
10389 $substitutionarray['__CANDIDATE_FULLNAME__'] = '__CANDIDATE_FULLNAME__';
10390 $substitutionarray['__CANDIDATE_FIRSTNAME__'] = '__CANDIDATE_FIRSTNAME__';
10391 $substitutionarray['__CANDIDATE_LASTNAME__'] = '__CANDIDATE_LASTNAME__';
10392 }
10393 if (isModEnabled('holiday') && (!is_object($object) || $object->element == 'holiday') && (empty($exclude) || !in_array('holiday', $exclude)) && (empty($include) || in_array('holiday', $include))) {
10394 $substitutionarray['__HOLIDAY_ARRAY_PER_EMPLOYEE_FOR_PERIOD__'] = '__HOLIDAY_ARRAY_PER_EMPLOYEE_FOR_PERIOD__';
10395 }
10396 if (isModEnabled('project') && (empty($exclude) || !in_array('project', $exclude)) && (empty($include) || in_array('project', $include))) { // Most objects
10397 $substitutionarray['__PROJECT_ID__'] = '__PROJECT_ID__';
10398 $substitutionarray['__PROJECT_REF__'] = '__PROJECT_REF__';
10399 $substitutionarray['__PROJECT_NAME__'] = '__PROJECT_NAME__';
10400 /*$substitutionarray['__PROJECT_NOTE_PUBLIC__'] = '__PROJECT_NOTE_PUBLIC__';
10401 $substitutionarray['__PROJECT_NOTE_PRIVATE__'] = '__PROJECT_NOTE_PRIVATE__';*/
10402 }
10403 if (isModEnabled('contract') && (!is_object($object) || $object->element == 'contract') && (empty($exclude) || !in_array('contract', $exclude)) && (empty($include) || in_array('contract', $include))) {
10404 $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = 'Highest date planned for a service start';
10405 $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = 'Highest date and hour planned for service start';
10406 $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = 'Lowest data for planned expiration of service';
10407 $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = 'Lowest date and hour for planned expiration of service';
10408 }
10409 if (isModEnabled("propal") && (!is_object($object) || $object->element == 'propal') && (empty($exclude) || !in_array('propal', $exclude)) && (empty($include) || in_array('propal', $include))) {
10410 $substitutionarray['__ONLINE_SIGN_URL__'] = 'ToOfferALinkForOnlineSignature';
10411 }
10412 if (isModEnabled("intervention") && (!is_object($object) || $object->element == 'fichinter') && (empty($exclude) || !in_array('intervention', $exclude)) && (empty($include) || in_array('intervention', $include))) {
10413 $substitutionarray['__ONLINE_SIGN_FICHINTER_URL__'] = 'ToOfferALinkForOnlineSignature';
10414 }
10415 $substitutionarray['__ONLINE_PAYMENT_URL__'] = 'UrlToPayOnlineIfApplicable';
10416 $substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__'] = 'TextAndUrlToPayOnlineIfApplicable';
10417 $substitutionarray['__SECUREKEYPAYMENT__'] = 'Security key (if key is not unique per record)';
10418 $substitutionarray['__SECUREKEYPAYMENT_MEMBER__'] = 'Security key for payment on a member subscription (one key per member)';
10419 $substitutionarray['__SECUREKEYPAYMENT_ORDER__'] = 'Security key for payment on an order';
10420 $substitutionarray['__SECUREKEYPAYMENT_INVOICE__'] = 'Security key for payment on an invoice';
10421 $substitutionarray['__SECUREKEYPAYMENT_CONTRACTLINE__'] = 'Security key for payment on a service of a contract';
10422
10423 $substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = 'Direct download url of a proposal';
10424 $substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = 'Direct download url of an order';
10425 $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = 'Direct download url of an invoice';
10426 $substitutionarray['__DIRECTDOWNLOAD_URL_CONTRACT__'] = 'Direct download url of a contract';
10427 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_PROPOSAL__'] = 'Direct download url of a supplier proposal';
10428 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_ORDER__'] = 'Direct download url of a supplier order';
10429 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_INVOICE__'] = 'Direct download url of a supplier invoice';
10430
10431 if (isModEnabled("shipping") && (!is_object($object) || $object->element == 'shipping')) {
10432 $substitutionarray['__SHIPPINGTRACKNUM__'] = 'Shipping tracking number';
10433 $substitutionarray['__SHIPPINGTRACKNUMURL__'] = 'Shipping tracking url';
10434 $substitutionarray['__SHIPPINGMETHOD__'] = 'Shipping method';
10435 }
10436 if (isModEnabled("reception") && (!is_object($object) || $object->element == 'reception')) {
10437 $substitutionarray['__RECEPTIONTRACKNUM__'] = 'Shipping tracking number of shipment';
10438 $substitutionarray['__RECEPTIONTRACKNUMURL__'] = 'Shipping tracking url';
10439 }
10440 } else {
10441 '@phan-var-force Adherent|Delivery $object';
10443 $substitutionarray['__ID__'] = $object->id;
10444 $substitutionarray['__REF__'] = $object->ref;
10445 $substitutionarray['__NEWREF__'] = $object->newref;
10446 $substitutionarray['__LABEL__'] = (isset($object->label) ? $object->label : (isset($object->title) ? $object->title : null));
10447 $substitutionarray['__REF_CLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : null));
10448 $substitutionarray['__REF_SUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
10449 $substitutionarray['__NOTE_PUBLIC__'] = (isset($object->note_public) ? $object->note_public : null);
10450 $substitutionarray['__NOTE_PRIVATE__'] = (isset($object->note_private) ? $object->note_private : null);
10451
10452 $substitutionarray['__DATE_CREATION__'] = (isset($object->date_creation) ? dol_print_date($object->date_creation, 'day', false, $outputlangs) : '');
10453 $substitutionarray['__DATE_MODIFICATION__'] = (isset($object->date_modification) ? dol_print_date($object->date_modification, 'day', false, $outputlangs) : '');
10454 $substitutionarray['__DATE_VALIDATION__'] = (isset($object->date_validation) ? dol_print_date($object->date_validation, 'day', false, $outputlangs) : '');
10455
10456 // handle date_delivery: in customer order/supplier order, the property name is delivery_date, in shipment/reception it is date_delivery
10457 $date_delivery = null;
10458 if (property_exists($object, 'date_delivery')) {
10459 $date_delivery = $object->date_delivery;
10460 } elseif (property_exists($object, 'delivery_date')) {
10461 $date_delivery = $object->delivery_date;
10462 }
10463 $substitutionarray['__DATE_DELIVERY__'] = (isset($date_delivery) ? dol_print_date($date_delivery, 'day', false, $outputlangs) : '');
10464 $substitutionarray['__DATE_DELIVERY_DAY__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%d") : '');
10465 $substitutionarray['__DATE_DELIVERY_DAY_TEXT__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%A") : '');
10466 $substitutionarray['__DATE_DELIVERY_MON__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%m") : '');
10467 $substitutionarray['__DATE_DELIVERY_MON_TEXT__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%b") : '');
10468 $substitutionarray['__DATE_DELIVERY_YEAR__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%Y") : '');
10469 $substitutionarray['__DATE_DELIVERY_HH__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%H") : '');
10470 $substitutionarray['__DATE_DELIVERY_MM__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%M") : '');
10471 $substitutionarray['__DATE_DELIVERY_SS__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%S") : '');
10472
10473 // For backward compatibility (deprecated)
10474 $substitutionarray['__REFCLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : null));
10475 $substitutionarray['__REFSUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
10476
10477 $substitutionarray['__SUPPLIER_ORDER_DATE_DELIVERY__'] = (isset($date_delivery) ? dol_print_date($date_delivery, 'day', false, $outputlangs) : '');
10478 $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 : '')) : '');
10479 $substitutionarray['__EXPIRATION_DATE__'] = (isset($object->fin_validite) ? dol_print_date($object->fin_validite, 'daytext') : '');
10480
10481 if (is_object($object) && ($object->element == 'adherent' || $object->element == 'member') && $object->id > 0) {
10482 '@phan-var-force Adherent $object';
10484 $birthday = (empty($object->birth) ? '' : dol_print_date($object->birth, 'day'));
10485
10486 $substitutionarray['__MEMBER_ID__'] = (isset($object->id) ? $object->id : '');
10487 if (method_exists($object, 'getCivilityLabel')) {
10488 $substitutionarray['__MEMBER_TITLE__'] = $object->getCivilityLabel();
10489 }
10490 $substitutionarray['__MEMBER_FIRSTNAME__'] = (isset($object->firstname) ? $object->firstname : '');
10491 $substitutionarray['__MEMBER_LASTNAME__'] = (isset($object->lastname) ? $object->lastname : '');
10492 $substitutionarray['__MEMBER_USER_LOGIN_INFORMATION__'] = '';
10493 if (method_exists($object, 'getFullName')) {
10494 $substitutionarray['__MEMBER_FULLNAME__'] = $object->getFullName($outputlangs);
10495 }
10496 $substitutionarray['__MEMBER_COMPANY__'] = (isset($object->societe) ? $object->societe : '');
10497 $substitutionarray['__MEMBER_ADDRESS__'] = (isset($object->address) ? $object->address : '');
10498 $substitutionarray['__MEMBER_ZIP__'] = (isset($object->zip) ? $object->zip : '');
10499 $substitutionarray['__MEMBER_TOWN__'] = (isset($object->town) ? $object->town : '');
10500 $substitutionarray['__MEMBER_STATE__'] = (isset($object->state) ? $object->state : '');
10501 $substitutionarray['__MEMBER_COUNTRY__'] = (isset($object->country) ? $object->country : '');
10502 $substitutionarray['__MEMBER_EMAIL__'] = (isset($object->email) ? $object->email : '');
10503 $substitutionarray['__MEMBER_BIRTH__'] = (isset($birthday) ? $birthday : '');
10504 $substitutionarray['__MEMBER_PHOTO__'] = (isset($object->photo) ? $object->photo : '');
10505 $substitutionarray['__MEMBER_LOGIN__'] = (isset($object->login) ? $object->login : '');
10506 $substitutionarray['__MEMBER_PASSWORD__'] = (isset($object->pass) ? $object->pass : '');
10507 $substitutionarray['__MEMBER_PHONE__'] = (isset($object->phone) ? dol_print_phone($object->phone) : '');
10508 $substitutionarray['__MEMBER_PHONEPRO__'] = (isset($object->phone_perso) ? dol_print_phone($object->phone_perso) : '');
10509 $substitutionarray['__MEMBER_PHONEMOBILE__'] = (isset($object->phone_mobile) ? dol_print_phone($object->phone_mobile) : '');
10510 $substitutionarray['__MEMBER_TYPE__'] = (isset($object->type) ? $object->type : '');
10511 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE__'] = dol_print_date($object->first_subscription_date, 'day');
10512
10513 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_RFC__'] = dol_print_date($object->first_subscription_date, 'dayrfc');
10514 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_START__'] = (isset($object->first_subscription_date_start) ? dol_print_date($object->first_subscription_date_start, 'day') : '');
10515 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_START_RFC__'] = (isset($object->first_subscription_date_start) ? dol_print_date($object->first_subscription_date_start, 'dayrfc') : '');
10516 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_END__'] = (isset($object->first_subscription_date_end) ? dol_print_date($object->first_subscription_date_end, 'day') : '');
10517 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_END_RFC__'] = (isset($object->first_subscription_date_end) ? dol_print_date($object->first_subscription_date_end, 'dayrfc') : '');
10518 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE__'] = dol_print_date($object->last_subscription_date, 'day');
10519 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_RFC__'] = dol_print_date($object->last_subscription_date, 'dayrfc');
10520 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_START__'] = dol_print_date($object->last_subscription_date_start, 'day');
10521 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_START_RFC__'] = dol_print_date($object->last_subscription_date_start, 'dayrfc');
10522 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_END__'] = dol_print_date($object->last_subscription_date_end, 'day');
10523 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_END_RFC__'] = dol_print_date($object->last_subscription_date_end, 'dayrfc');
10524 }
10525
10526 if (is_object($object) && $object->element == 'societe') {
10528 '@phan-var-force Societe $object';
10529 $substitutionarray['__THIRDPARTY_ID__'] = $object->id ?? '';
10530 $substitutionarray['__THIRDPARTY_NAME__'] = $object->name ?? '';
10531 $substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = $object->name_alias ?? '';
10532 $substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = $object->code_client ?? '';
10533 $substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = $object->code_fournisseur ?? '';
10534 $substitutionarray['__THIRDPARTY_EMAIL__'] = $object->email ?? '';
10535 $substitutionarray['__THIRDPARTY_EMAIL_URLENCODED__'] = urlencode($object->email ?? '');
10536 $substitutionarray['__THIRDPARTY_URL__'] = $object->url ?? '';
10537 $substitutionarray['__THIRDPARTY_URL_URLENCODED__'] = urlencode($object->url ?? '');
10538 $substitutionarray['__THIRDPARTY_PHONE__'] = dol_print_phone($object->phone ?? '');
10539 $substitutionarray['__THIRDPARTY_FAX__'] = dol_print_phone($object->fax ?? '');
10540 $substitutionarray['__THIRDPARTY_ADDRESS__'] = $object->address ?? '';
10541 $substitutionarray['__THIRDPARTY_ZIP__'] = $object->zip ?? '';
10542 $substitutionarray['__THIRDPARTY_TOWN__'] = $object->town ?? '';
10543 $substitutionarray['__THIRDPARTY_STATE__'] = $object->state ?? '';
10544 $substitutionarray['__THIRDPARTY_COUNTRY_ID__'] = ($object->country_id > 0 ? $object->country_id : '');
10545 $substitutionarray['__THIRDPARTY_COUNTRY_CODE__'] = $object->country_code ?? '';
10546 $substitutionarray['__THIRDPARTY_IDPROF1__'] = $object->idprof1 ?? '';
10547 $substitutionarray['__THIRDPARTY_IDPROF2__'] = $object->idprof2 ?? '';
10548 $substitutionarray['__THIRDPARTY_IDPROF3__'] = $object->idprof3 ?? '';
10549 $substitutionarray['__THIRDPARTY_IDPROF4__'] = $object->idprof4 ?? '';
10550 $substitutionarray['__THIRDPARTY_IDPROF5__'] = $object->idprof5 ?? '';
10551 $substitutionarray['__THIRDPARTY_IDPROF6__'] = $object->idprof6 ?? '';
10552 $substitutionarray['__THIRDPARTY_TVAINTRA__'] = $object->tva_intra ?? '';
10553 $substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = dol_htmlentitiesbr($object->note_public ?? '');
10554 $substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = dol_htmlentitiesbr($object->note_private ?? '');
10555 } elseif (is_object($object) && is_object($object->thirdparty)) {
10556 $substitutionarray['__THIRDPARTY_ID__'] = $object->thirdparty->id ?? '';
10557 $substitutionarray['__THIRDPARTY_NAME__'] = $object->thirdparty->name ?? '';
10558 $substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = $object->thirdparty->name_alias ?? '';
10559 $substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = $object->thirdparty->code_client ?? '';
10560 $substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = $object->thirdparty->code_fournisseur ?? '';
10561 $substitutionarray['__THIRDPARTY_EMAIL__'] = $object->thirdparty->email ?? '';
10562 $substitutionarray['__THIRDPARTY_EMAIL_URLENCODED__'] = urlencode($object->thirdparty->email ?? '');
10563 $substitutionarray['__THIRDPARTY_PHONE__'] = dol_print_phone($object->thirdparty->phone ?? '');
10564 $substitutionarray['__THIRDPARTY_FAX__'] = dol_print_phone($object->thirdparty->fax ?? '');
10565 $substitutionarray['__THIRDPARTY_ADDRESS__'] = $object->thirdparty->address ?? '';
10566 $substitutionarray['__THIRDPARTY_ZIP__'] = $object->thirdparty->zip ?? '';
10567 $substitutionarray['__THIRDPARTY_TOWN__'] = $object->thirdparty->town ?? '';
10568 $substitutionarray['__THIRDPARTY_STATE__'] = $object->thirdparty->state ?? '';
10569 $substitutionarray['__THIRDPARTY_COUNTRY_ID__'] = ($object->thirdparty->country_id > 0 ? $object->thirdparty->country_id : '');
10570 $substitutionarray['__THIRDPARTY_COUNTRY_CODE__'] = $object->thirdparty->country_code ?? '';
10571 $substitutionarray['__THIRDPARTY_IDPROF1__'] = $object->thirdparty->idprof1 ?? '';
10572 $substitutionarray['__THIRDPARTY_IDPROF2__'] = $object->thirdparty->idprof2 ?? '';
10573 $substitutionarray['__THIRDPARTY_IDPROF3__'] = $object->thirdparty->idprof3 ?? '';
10574 $substitutionarray['__THIRDPARTY_IDPROF4__'] = $object->thirdparty->idprof4 ?? '';
10575 $substitutionarray['__THIRDPARTY_IDPROF5__'] = $object->thirdparty->idprof5 ?? '';
10576 $substitutionarray['__THIRDPARTY_IDPROF6__'] = $object->thirdparty->idprof6 ?? '';
10577 $substitutionarray['__THIRDPARTY_TVAINTRA__'] = $object->thirdparty->tva_intra ?? '';
10578 $substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = dol_htmlentitiesbr($object->thirdparty->note_public ?? '');
10579 $substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = dol_htmlentitiesbr($object->thirdparty->note_private ?? '');
10580 }
10581
10582 if (is_object($object) && $object->element == 'recruitmentcandidature') {
10583 '@phan-var-force RecruitmentCandidature $object';
10585 $substitutionarray['__CANDIDATE_FULLNAME__'] = $object->getFullName($outputlangs);
10586 $substitutionarray['__CANDIDATE_FIRSTNAME__'] = isset($object->firstname) ? $object->firstname : '';
10587 $substitutionarray['__CANDIDATE_LASTNAME__'] = isset($object->lastname) ? $object->lastname : '';
10588 }
10589 if (is_object($object) && $object->element == 'conferenceorboothattendee') {
10590 '@phan-var-force ConferenceOrBoothAttendee $object';
10592 $substitutionarray['__ATTENDEE_FULLNAME__'] = $object->getFullName($outputlangs);
10593 $substitutionarray['__ATTENDEE_FIRSTNAME__'] = isset($object->firstname) ? $object->firstname : '';
10594 $substitutionarray['__ATTENDEE_LASTNAME__'] = isset($object->lastname) ? $object->lastname : '';
10595 }
10596
10597 if (is_object($object) && $object->element == 'project') {
10598 '@phan-var-force Project $object';
10600 $substitutionarray['__PROJECT_ID__'] = $object->id;
10601 $substitutionarray['__PROJECT_REF__'] = $object->ref;
10602 $substitutionarray['__PROJECT_NAME__'] = $object->title;
10603 } elseif (is_object($object)) {
10604 $project = null;
10605 if (!empty($object->project)) {
10606 $project = $object->project;
10607 }
10608 if (!is_null($project) && is_object($project)) {
10609 $substitutionarray['__PROJECT_ID__'] = $project->id;
10610 $substitutionarray['__PROJECT_REF__'] = $project->ref;
10611 $substitutionarray['__PROJECT_NAME__'] = $project->title;
10612 } else {
10613 // can substitute variables for project : uses lazy load in "make_substitutions" method
10614 $project_id = 0;
10615 if (!empty($object->fk_project) && $object->fk_project > 0) {
10616 $project_id = $object->fk_project;
10617 } elseif (!empty($object->fk_projet) && $object->fk_projet > 0) {
10618 $project_id = $object->fk_project;
10619 }
10620 if ($project_id > 0) {
10621 // path:class:method:id
10622 $substitutionarray['__PROJECT_ID__@lazyload'] = '/projet/class/project.class.php:Project:fetchAndSetSubstitution:' . $project_id;
10623 $substitutionarray['__PROJECT_REF__@lazyload'] = '/projet/class/project.class.php:Project:fetchAndSetSubstitution:' . $project_id;
10624 $substitutionarray['__PROJECT_NAME__@lazyload'] = '/projet/class/project.class.php:Project:fetchAndSetSubstitution:' . $project_id;
10625 }
10626 }
10627 }
10628
10629 if (is_object($object) && $object->element == 'facture') {
10630 '@phan-var-force Facture $object';
10632 $substitutionarray['__INVOICE_SITUATION_NUMBER__'] = isset($object->situation_counter) ? $object->situation_counter : '';
10633 }
10634 if (is_object($object) && $object->element == 'shipping') {
10635 '@phan-var-force Expedition $object';
10637 $substitutionarray['__SHIPPINGTRACKNUM__'] = $object->tracking_number;
10638 $substitutionarray['__SHIPPINGTRACKNUMURL__'] = $object->tracking_url;
10639 $substitutionarray['__SHIPPINGMETHOD__'] = $object->shipping_method;
10640 }
10641 if (is_object($object) && $object->element == 'reception') {
10642 '@phan-var-force Reception $object';
10644 $substitutionarray['__RECEPTIONTRACKNUM__'] = $object->tracking_number;
10645 $substitutionarray['__RECEPTIONTRACKNUMURL__'] = $object->tracking_url;
10646 }
10647
10648 if (is_object($object) && $object->element == 'contrat' && $object->id > 0 && is_array($object->lines)) {
10649 '@phan-var-force Contrat $object';
10651 $dateplannedstart = '';
10652 $datenextexpiration = '';
10653 foreach ($object->lines as $line) {
10654 if ($line->date_start > $dateplannedstart) {
10655 $dateplannedstart = $line->date_start;
10656 }
10657 if ($line->statut == 4 && $line->date_end && (!$datenextexpiration || $line->date_end < $datenextexpiration)) {
10658 $datenextexpiration = $line->date_end;
10659 }
10660 }
10661 $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = dol_print_date($dateplannedstart, 'day');
10662 $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE_RFC__'] = dol_print_date($dateplannedstart, 'dayrfc');
10663 $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = dol_print_date($dateplannedstart, 'standard');
10664
10665 $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = dol_print_date($datenextexpiration, 'day');
10666 $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE_RFC__'] = dol_print_date($datenextexpiration, 'dayrfc');
10667 $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = dol_print_date($datenextexpiration, 'standard');
10668 }
10669 // add substitution variables for ticket
10670 if (is_object($object) && $object->element == 'ticket') {
10671 '@phan-var-force Ticket $object';
10673 $substitutionarray['__TICKET_TRACKID__'] = $object->track_id;
10674 $substitutionarray['__TICKET_SUBJECT__'] = $object->subject;
10675 $substitutionarray['__TICKET_TYPE__'] = $object->type_code;
10676 $substitutionarray['__TICKET_SEVERITY__'] = $object->severity_code;
10677 $substitutionarray['__TICKET_CATEGORY__'] = $object->category_code; // For backward compatibility
10678 $substitutionarray['__TICKET_ANALYTIC_CODE__'] = $object->category_code;
10679 $substitutionarray['__TICKET_MESSAGE__'] = $object->message;
10680 $substitutionarray['__TICKET_PROGRESSION__'] = $object->progress;
10681 // __TICKET_TYPE__, __TICKET_SEVERITY__ and __TICKET_CATEGORY__ hold the raw codes.
10682 // An email usually needs the human readable labels instead.
10683 $substitutionarray['__TICKET_TYPE_LABEL__'] = empty($object->type_code) ? '' : $outputlangs->getLabelFromKey($db, 'TicketTypeShort'.$object->type_code, 'c_ticket_type', 'code', 'label', $object->type_code);
10684 $substitutionarray['__TICKET_SEVERITY_LABEL__'] = empty($object->severity_code) ? '' : $outputlangs->getLabelFromKey($db, 'TicketSeverityShort'.$object->severity_code, 'c_ticket_severity', 'code', 'label', $object->severity_code);
10685 $substitutionarray['__TICKET_CATEGORY_LABEL__'] = empty($object->category_code) ? '' : $outputlangs->getLabelFromKey($db, 'TicketCategoryShort'.$object->category_code, 'c_ticket_category', 'code', 'label', $object->category_code);
10686 $substitutionarray['__TICKET_PUBLIC_URL__'] = getDolGlobalString('TICKET_URL_PUBLIC_INTERFACE', dol_buildpath('/public/ticket/', 2)).'view.php?track_id='.urlencode((string) $object->track_id);
10687 $substitutionarray['__TICKET_MANAGEMENT_URL__'] = dol_buildpath('/ticket/card.php', 2).'?track_id='.urlencode((string) $object->track_id);
10688 $userstat = new User($db);
10689 if ($object->fk_user_assign > 0) {
10690 $userstat->fetch($object->fk_user_assign);
10691 $substitutionarray['__TICKET_USER_ASSIGN__'] = dolGetFirstLastname($userstat->firstname, $userstat->lastname);
10692 }
10693
10694 if ($object->fk_user_create > 0) {
10695 $userstat->fetch($object->fk_user_create);
10696 $substitutionarray['__USER_CREATE__'] = dolGetFirstLastname($userstat->firstname, $userstat->lastname);
10697 }
10698 }
10699
10700 // Create dynamic tags for __EXTRAFIELD_FIELD__
10701 if ($object->table_element && $object->id > 0) {
10702 if (!is_object($extrafields)) {
10703 $extrafields = new ExtraFields($db);
10704 }
10705 $extrafields->fetch_name_optionals_label($object->table_element, true);
10706
10707 if ($object->fetch_optionals() > 0) { // @FIXME: Remove this, the fetch should have been done already, by the caller of getCommonSubstitutionArray()
10708 if (is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label']) > 0) {
10709 foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $label) {
10710 if ($extrafields->attributes[$object->table_element]['type'][$key] == 'date') {
10711 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = dol_print_date($object->array_options['options_' . $key], 'day');
10712 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_LOCALE__'] = dol_print_date($object->array_options['options_' . $key], 'day', 'tzserver', $outputlangs);
10713 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_RFC__'] = dol_print_date($object->array_options['options_' . $key], 'dayrfc');
10714 } elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'datetime') {
10715 $datetime = $object->array_options['options_' . $key];
10716 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhour') : '');
10717 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_LOCALE__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhour', 'tzserver', $outputlangs) : '');
10718 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_DAY_LOCALE__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'day', 'tzserver', $outputlangs) : '');
10719 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_RFC__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhourrfc') : '');
10720 } elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'phone') {
10721 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = dol_print_phone($object->array_options['options_' . $key]);
10722 } elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'price') {
10723 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = $object->array_options['options_' . $key];
10724 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_FORMATED__'] = price($object->array_options['options_' . $key]); // For compatibility
10725 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_FORMATTED__'] = price($object->array_options['options_' . $key]);
10726 } elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'select') {
10727 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = !empty($object->array_options['options_' . $key]) ? $object->array_options['options_' . $key] : '';
10728 $val = $extrafields->attributes[$object->table_element]['param'][$key]['options'][$object->array_options['options_'.$key]] ?? $object->array_options['options_'.$key];
10729 $substitutionarray['__EXTRAFIELD_'.strtoupper($key).'_LABEL__'] = $val;
10730 } elseif ($extrafields->attributes[$object->table_element]['type'][$key] != 'separator') {
10731 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = !empty($object->array_options['options_' . $key]) ? $object->array_options['options_' . $key] : '';
10732 }
10733 }
10734 }
10735 }
10736 }
10737
10738 // Complete substitution array with the url to make online payment
10739 if (empty($substitutionarray['__REF__'])) {
10740 $paymenturl = '';
10741 } else {
10742 // Set the online payment url link into __ONLINE_PAYMENT_URL__ key
10743 require_once DOL_DOCUMENT_ROOT . '/core/lib/payments.lib.php';
10744 $outputlangs->loadLangs(array('paypal', 'other'));
10745
10746 $amounttouse = 0;
10747 $typeforonlinepayment = 'free';
10748 if (is_object($object) && $object->element == 'commande') {
10749 $typeforonlinepayment = 'order';
10750 }
10751 if (is_object($object) && $object->element == 'facture') {
10752 $typeforonlinepayment = 'invoice';
10753 }
10754 if (is_object($object) && $object->element == 'member') {
10755 $typeforonlinepayment = 'member';
10756 if (!empty($object->last_subscription_amount)) {
10757 $amounttouse = $object->last_subscription_amount;
10758 }
10759 }
10760 if (is_object($object) && $object->element == 'contrat') {
10761 $typeforonlinepayment = 'contract';
10762 }
10763 if (is_object($object) && $object->element == 'fichinter') {
10764 $typeforonlinepayment = 'ficheinter';
10765 }
10766
10767 $url = getOnlinePaymentUrl(0, $typeforonlinepayment, $substitutionarray['__REF__'], (float) $amounttouse);
10768 $paymenturl = $url;
10769 }
10770
10771 if ($object->id > 0) {
10772 $substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__'] = ($paymenturl ? str_replace('\n', "\n", $outputlangs->trans("PredefinedMailContentLink", $paymenturl)) : '');
10773 $substitutionarray['__ONLINE_PAYMENT_URL__'] = $paymenturl;
10774
10775 // Show structured communication
10776 if (getDolGlobalString('INVOICE_PAYMENT_ENABLE_STRUCTURED_COMMUNICATION') && $object->element == 'facture') {
10777 include_once DOL_DOCUMENT_ROOT . '/core/lib/functions_be.lib.php';
10778 $substitutionarray['__PAYMENT_STRUCTURED_COMMUNICATION__'] = dolBECalculateStructuredCommunication((string) $object->ref, $object->type);
10779 }
10780
10781 if (getDolGlobalString('PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'propal') {
10782 $substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = $object->getLastMainDocLink($object->element);
10783 } else {
10784 $substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = '';
10785 }
10786 if (getDolGlobalString('ORDER_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'commande') {
10787 $substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = $object->getLastMainDocLink($object->element);
10788 } else {
10789 $substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = '';
10790 }
10791 if (getDolGlobalString('INVOICE_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'facture') {
10792 $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = $object->getLastMainDocLink($object->element);
10793 } else {
10794 $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = '';
10795 }
10796 if (getDolGlobalString('CONTRACT_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'contrat') {
10797 $substitutionarray['__DIRECTDOWNLOAD_URL_CONTRACT__'] = $object->getLastMainDocLink($object->element);
10798 } else {
10799 $substitutionarray['__DIRECTDOWNLOAD_URL_CONTRACT__'] = '';
10800 }
10801 if (getDolGlobalString('FICHINTER_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'fichinter') {
10802 $substitutionarray['__DIRECTDOWNLOAD_URL_FICHINTER__'] = $object->getLastMainDocLink($object->element);
10803 } else {
10804 $substitutionarray['__DIRECTDOWNLOAD_URL_FICHINTER__'] = '';
10805 }
10806 if (getDolGlobalString('SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'supplier_proposal') {
10807 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_PROPOSAL__'] = $object->getLastMainDocLink($object->element);
10808 } else {
10809 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_PROPOSAL__'] = '';
10810 }
10811 if (getDolGlobalString('SUPPLIER_ORDER_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'order_supplier') {
10812 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_ORDER__'] = $object->getLastMainDocLink($object->element);
10813 } else {
10814 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_ORDER__'] = '';
10815 }
10816 if (getDolGlobalString('SUPPLIER_INVOICE_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'invoice_supplier') {
10817 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_INVOICE__'] = $object->getLastMainDocLink($object->element);
10818 } else {
10819 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_INVOICE__'] = '';
10820 }
10821
10822 if (is_object($object) && $object->element == 'propal') {
10823 '@phan-var-force Propal $object';
10825 $substitutionarray['__URL_PROPOSAL__'] = DOL_MAIN_URL_ROOT . "/comm/propal/card.php?id=" . $object->id;
10826 require_once DOL_DOCUMENT_ROOT . '/core/lib/signature.lib.php';
10827 $substitutionarray['__ONLINE_SIGN_URL__'] = getOnlineSignatureUrl(0, 'proposal', (string) $object->ref, 1, $object);
10828 }
10829 if (is_object($object) && $object->element == 'commande') {
10830 '@phan-var-force Commande $object';
10832 $substitutionarray['__URL_ORDER__'] = DOL_MAIN_URL_ROOT . "/commande/card.php?id=" . $object->id;
10833 }
10834 if (is_object($object) && $object->element == 'facture') {
10835 '@phan-var-force Facture $object';
10837 $substitutionarray['__URL_INVOICE__'] = DOL_MAIN_URL_ROOT . "/compta/facture/card.php?id=" . $object->id;
10838 }
10839 if (is_object($object) && $object->element == 'contrat') {
10840 '@phan-var-force Contrat $object';
10842 $substitutionarray['__URL_CONTRACT__'] = DOL_MAIN_URL_ROOT . "/contrat/card.php?id=" . $object->id;
10843 require_once DOL_DOCUMENT_ROOT . '/core/lib/signature.lib.php';
10844 $substitutionarray['__ONLINE_SIGN_URL__'] = getOnlineSignatureUrl(0, 'contract', (string) $object->ref, 1, $object);
10845 }
10846 if (is_object($object) && $object->element == 'fichinter') {
10847 '@phan-var-force Fichinter $object';
10849 $substitutionarray['__URL_FICHINTER__'] = DOL_MAIN_URL_ROOT . "/fichinter/card.php?id=" . $object->id;
10850 require_once DOL_DOCUMENT_ROOT . '/core/lib/signature.lib.php';
10851 $substitutionarray['__ONLINE_SIGN_FICHINTER_URL__'] = getOnlineSignatureUrl(0, 'fichinter', (string) $object->ref, 1, $object);
10852 }
10853 if (is_object($object) && $object->element == 'supplier_proposal') {
10854 '@phan-var-force SupplierProposal $object';
10856 $substitutionarray['__URL_SUPPLIER_PROPOSAL__'] = DOL_MAIN_URL_ROOT . "/supplier_proposal/card.php?id=" . $object->id;
10857 }
10858 if (is_object($object) && $object->element == 'invoice_supplier') {
10859 '@phan-var-force FactureFournisseur $object';
10861 $substitutionarray['__URL_SUPPLIER_INVOICE__'] = DOL_MAIN_URL_ROOT . "/fourn/facture/card.php?id=" . $object->id;
10862 }
10863 if (is_object($object) && $object->element == 'payment_supplier') {
10864 '@phan-var-force PaiementFourn $object';
10866 //print_r($object);
10867 $liste_factures = [];
10868 $total = 0;
10869
10870 // @FIXME We must not have any repeated SQL access into this function.
10871 $sql = 'SELECT f.ref,f.multicurrency_code as f_mccode, pf.*
10872 FROM '.MAIN_DB_PREFIX.'paiementfourn_facturefourn as pf
10873 JOIN '.MAIN_DB_PREFIX.'facture_fourn as f ON pf.fk_facturefourn = f.rowid
10874 WHERE pf.fk_paiementfourn = '.((int) $object->id);
10875
10876 $resql = $db->query($sql);
10877 if ($resql) {
10878 while ($objp = $db->fetch_object($resql)) {
10879 $liste_factures[] = ' - '.$outputlangs->trans('Invoice').' '. $objp->ref.' '.$outputlangs->trans('AmountPayed').' '.price($objp->multicurrency_amount, 0, $outputlangs, 0, -1, -1, $objp->multicurrency_code);
10880 }
10881 }
10882 $substitutionarray['__SUPPLIER_PAYMENT_INVOICES_LIST__'] = implode("\n", $liste_factures);
10883 ;
10884 $substitutionarray['__SUPPLIER_PAYMENT_INVOICES_TOTAL__'] = price($object->multicurrency_amount, 0, $outputlangs, 0, -1, -1, $object->multicurrency_code ? $object->multicurrency_code : $conf->currency);
10885 }
10886 if (is_object($object) && $object->element == 'shipping') {
10887 '@phan-var-force Expedition $object';
10889 $substitutionarray['__URL_SHIPMENT__'] = DOL_MAIN_URL_ROOT . "/expedition/card.php?id=" . $object->id;
10890 if (getDolGlobalInt('EXPEDITION_ALLOW_ONLINESIGN')) {
10891 require_once DOL_DOCUMENT_ROOT . '/core/lib/signature.lib.php';
10892 $substitutionarray['__ONLINE_SIGN_URL__'] = getOnlineSignatureUrl(0, 'expedition', (string) $object->ref, 1, $object);
10893 }
10894 }
10895 }
10896
10897 if (is_object($object) && $object->element == 'action') {
10898 '@phan-var-force ActionComm $object';
10900 $substitutionarray['__EVENT_LABEL__'] = $object->label;
10901 $substitutionarray['__EVENT_DESCRIPTION__'] = $object->note;
10902 $substitutionarray['__EVENT_TYPE__'] = $outputlangs->trans("Action" . $object->type_code);
10903 $substitutionarray['__EVENT_DATE__'] = dol_print_date($object->datep, 'day', 'auto', $outputlangs);
10904 $substitutionarray['__EVENT_TIME__'] = dol_print_date($object->datep, 'hour', 'auto', $outputlangs);
10905 $substitutionarray['__EVENT_DATE_TZUSER__'] = dol_print_date($object->datep, 'day', 'tzuserrel', $outputlangs);
10906 $substitutionarray['__EVENT_TIME_TZUSER__'] = dol_print_date($object->datep, 'hour', 'tzuserrel', $outputlangs);
10907 }
10908 }
10909 }
10910
10911 if ((empty($exclude) || !in_array('objectamount', $exclude)) && (empty($include) || in_array('objectamount', $include))) {
10912 '@phan-var-force Facture|FactureRec $object';
10914 include_once DOL_DOCUMENT_ROOT . '/core/lib/functionsnumtoword.lib.php';
10915
10916 $substitutionarray['__DATE_YMD__'] = is_object($object) ? (isset($object->date) ? dol_print_date($object->date, 'day', false, $outputlangs) : null) : '';
10917 $substitutionarray['__DATE_DUE_YMD__'] = is_object($object) ? (isset($object->date_lim_reglement) ? dol_print_date($object->date_lim_reglement, 'day', false, $outputlangs) : null) : '';
10918 $substitutionarray['__DATE_YMD_TEXT__'] = is_object($object) ? (isset($object->date) ? dol_print_date($object->date, 'daytext', false, $outputlangs) : null) : '';
10919 $substitutionarray['__DATE_DUE_YMD_TEXT__'] = is_object($object) ? (isset($object->date_lim_reglement) ? dol_print_date($object->date_lim_reglement, 'daytext', false, $outputlangs) : null) : '';
10920
10921 $already_payed_all = 0;
10922 if (is_object($object) && ($object instanceof Facture)) {
10923 $already_payed_all = $object->totalpaid + $object->totaldeposits + $object->totalcreditnotes;
10924 }
10925
10926 $substitutionarray['__SIMPLE_HTML_TABLE__'] = is_object($object) && !empty($object->lines) ? showSimpleHTMLTable($outputlangs, $object) : "";
10927 $substitutionarray['__AMOUNT_EXCL_TAX__'] = is_object($object) ? $object->total_ht : '';
10928 $substitutionarray['__AMOUNT_EXCL_TAX_TEXT__'] = is_object($object) ? dol_convertToWord($object->total_ht, $outputlangs, '', true) : '';
10929 $substitutionarray['__AMOUNT_EXCL_TAX_TEXTCURRENCY__'] = is_object($object) ? dol_convertToWord($object->total_ht, $outputlangs, $conf->currency, true) : '';
10930
10931 $substitutionarray['__AMOUNT__'] = is_object($object) ? $object->total_ttc : '';
10932 $substitutionarray['__AMOUNT_TEXT__'] = is_object($object) ? dol_convertToWord($object->total_ttc, $outputlangs, '', true) : '';
10933 $substitutionarray['__AMOUNT_TEXTCURRENCY__'] = is_object($object) ? dol_convertToWord($object->total_ttc, $outputlangs, $conf->currency, true) : '';
10934
10935 $substitutionarray['__DEPOSIT_PERCENT__'] = is_object($object) ? $object->deposit_percent : '';
10936 $substitutionarray['__DEPOSIT_AMOUNT__'] = is_object($object) ? price2num($object->total_ttc * ($object->deposit_percent / 100), 'MT') : '';
10937
10938 $substitutionarray['__AMOUNT_REMAIN__'] = is_object($object) ? price2num($object->total_ttc - $already_payed_all, 'MT') : '';
10939
10940 $substitutionarray['__AMOUNT_VAT__'] = is_object($object) ? (isset($object->total_vat) ? $object->total_vat : $object->total_tva) : '';
10941 $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)) : '';
10942 $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)) : '';
10943
10944 $mysocuselocaltax1 = false;
10945 $mysocuselocaltax2 = false;
10946 if ($mysoc instanceof Societe && !empty($mysoc->country_code)) {
10947 $tmparray = $mysoc->useLocalTax(-1);
10948 $mysocuselocaltax1 = $tmparray[1];
10949 $mysocuselocaltax2 = $tmparray[2];
10950 }
10951
10952 // Local taxes
10953 if ($onlykey != 2 || $mysocuselocaltax1) {
10954 $substitutionarray['__AMOUNT_TAX2__'] = is_object($object) ? $object->total_localtax1 : '';
10955 }
10956 if ($onlykey != 2 || $mysocuselocaltax2) {
10957 $substitutionarray['__AMOUNT_TAX3__'] = is_object($object) ? $object->total_localtax2 : '';
10958 }
10959
10960 // Amount keys formatted in a currency
10961 $substitutionarray['__AMOUNT_EXCL_TAX_FORMATTED__'] = is_object($object) ? ($object->total_ht ? price($object->total_ht, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
10962 $substitutionarray['__AMOUNT_FORMATTED__'] = is_object($object) ? ($object->total_ttc ? price($object->total_ttc, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
10963 $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) : '';
10964 $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)) : '';
10965 if ($onlykey != 2 || $mysocuselocaltax1) {
10966 $substitutionarray['__AMOUNT_TAX2_FORMATTED__'] = is_object($object) ? ($object->total_localtax1 ? price($object->total_localtax1, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
10967 }
10968 if ($onlykey != 2 || $mysocuselocaltax2) {
10969 $substitutionarray['__AMOUNT_TAX3_FORMATTED__'] = is_object($object) ? ($object->total_localtax2 ? price($object->total_localtax2, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
10970 }
10971 // Amount keys formatted in a currency (with the typo error for backward compatibility)
10972 if ($onlykey != 2) {
10973 $substitutionarray['__AMOUNT_EXCL_TAX_FORMATED__'] = $substitutionarray['__AMOUNT_EXCL_TAX_FORMATTED__'];
10974 $substitutionarray['__AMOUNT_FORMATED__'] = $substitutionarray['__AMOUNT_FORMATTED__'];
10975 $substitutionarray['__AMOUNT_REMAIN_FORMATED__'] = $substitutionarray['__AMOUNT_REMAIN_FORMATTED__'];
10976 $substitutionarray['__AMOUNT_VAT_FORMATED__'] = $substitutionarray['__AMOUNT_VAT_FORMATTED__'];
10977 if ($mysocuselocaltax1) {
10978 $substitutionarray['__AMOUNT_TAX2_FORMATED__'] = $substitutionarray['__AMOUNT_TAX2_FORMATTED__'];
10979 }
10980 if ($mysoc->useLocalTax2) {
10981 $substitutionarray['__AMOUNT_TAX3_FORMATED__'] = $substitutionarray['__AMOUNT_TAX3_FORMATTED__'];
10982 }
10983 }
10984
10985 $substitutionarray['__AMOUNT_MULTICURRENCY__'] = (is_object($object) && isset($object->multicurrency_total_ttc)) ? $object->multicurrency_total_ttc : '';
10986 $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) : '';
10987 $substitutionarray['__AMOUNT_MULTICURRENCY_TEXT__'] = (is_object($object) && isset($object->multicurrency_total_ttc)) ? dol_convertToWord($object->multicurrency_total_ttc, $outputlangs, '', true) : '';
10988 $substitutionarray['__AMOUNT_MULTICURRENCY_TEXTCURRENCY__'] = (is_object($object) && isset($object->multicurrency_total_ttc)) ? dol_convertToWord($object->multicurrency_total_ttc, $outputlangs, $object->multicurrency_code, true) : '';
10989 $substitutionarray['__MULTICURRENCY_CODE__'] = (is_object($object) && isset($object->multicurrency_code)) ? $object->multicurrency_code : '';
10990 // TODO Add other keys for foreign multicurrency
10991
10992 // For backward compatibility
10993 if ($onlykey != 2) {
10994 $substitutionarray['__TOTAL_TTC__'] = is_object($object) ? $object->total_ttc : '';
10995 $substitutionarray['__TOTAL_HT__'] = is_object($object) ? $object->total_ht : '';
10996 $substitutionarray['__TOTAL_VAT__'] = is_object($object) ? (isset($object->total_vat) ? $object->total_vat : $object->total_tva) : '';
10997 }
10998 }
10999
11000
11001 if ((empty($exclude) || !in_array('date', $exclude)) && (empty($include) || in_array('date', $include))) {
11002 include_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php';
11003
11004 $now = dol_now();
11005
11006 $tmp = dol_getdate($now, true);
11007 $tmp2 = dol_get_prev_day($tmp['mday'], $tmp['mon'], $tmp['year']);
11008 $tmp3 = dol_get_prev_month($tmp['mon'], $tmp['year']);
11009 $tmp4 = dol_get_next_day($tmp['mday'], $tmp['mon'], $tmp['year']);
11010 $tmp5 = dol_get_next_month($tmp['mon'], $tmp['year']);
11011
11012 $daytext = $outputlangs->trans('Day' . $tmp['wday']);
11013
11014 $substitutionarray = array_merge($substitutionarray, array(
11015 '__NOW_TMS__' => (string) $now, // Must be the string that represent the int
11016 '__NOW_TMS_YMD__' => dol_print_date($now, 'day', 'auto', $outputlangs),
11017 '__DAY__' => (string) $tmp['mday'],
11018 '__DAY_TEXT__' => $daytext, // Monday
11019 '__DAY_TEXT_SHORT__' => dol_trunc($daytext, 3, 'right', 'UTF-8', 1), // Mon
11020 '__DAY_TEXT_MIN__' => dol_trunc($daytext, 1, 'right', 'UTF-8', 1), // M
11021 '__MONTH__' => (string) $tmp['mon'],
11022 '__MONTH_TEXT__' => $outputlangs->transnoentitiesnoconv('Month' . sprintf("%02d", $tmp['mon'])),
11023 '__MONTH_TEXT_SHORT__' => $outputlangs->transnoentitiesnoconv('MonthShort' . sprintf("%02d", $tmp['mon'])),
11024 '__MONTH_TEXT_MIN__' => $outputlangs->transnoentitiesnoconv('MonthVeryShort' . sprintf("%02d", $tmp['mon'])),
11025 '__YEAR__' => (string) $tmp['year'],
11026 '__YEAR_PREVIOUS_MONTH__' => (string) $tmp3['year'],
11027 '__YEAR_NEXT_MONTH__' => (string) $tmp5['year'],
11028 '__PREVIOUS_DAY__' => (string) $tmp2['day'],
11029 '__PREVIOUS_MONTH__' => (string) $tmp3['month'],
11030 '__PREVIOUS_MONTH_TEXT__' => $outputlangs->transnoentitiesnoconv('Month' . sprintf("%02d", $tmp3['month'])),
11031 '__PREVIOUS_MONTH_TEXT_SHORT__' => $outputlangs->transnoentitiesnoconv('MonthShort' . sprintf("%02d", $tmp3['month'])),
11032 '__PREVIOUS_MONTH_TEXT_MIN__' => $outputlangs->transnoentitiesnoconv('MonthVeryShort' . sprintf("%02d", $tmp3['month'])),
11033 '__PREVIOUS_YEAR__' => (string) ($tmp['year'] - 1),
11034 '__NEXT_DAY__' => (string) $tmp4['day'],
11035 '__NEXT_MONTH__' => (string) $tmp5['month'],
11036 '__NEXT_MONTH_TEXT__' => $outputlangs->transnoentitiesnoconv('Month' . sprintf("%02d", $tmp5['month'])),
11037 '__NEXT_MONTH_TEXT_SHORT__' => $outputlangs->transnoentitiesnoconv('MonthShort' . sprintf("%02d", $tmp5['month'])),
11038 '__NEXT_MONTH_TEXT_MIN__' => $outputlangs->transnoentitiesnoconv('MonthVeryShort' . sprintf("%02d", $tmp5['month'])),
11039 '__NEXT_YEAR__' => (string) ($tmp['year'] + 1),
11040 ));
11041 }
11042
11043 if (isModEnabled('multicompany')) {
11044 $substitutionarray = array_merge($substitutionarray, array('__ENTITY_ID__' => $conf->entity));
11045 }
11046 if ((empty($exclude) || !in_array('system', $exclude)) && (empty($include) || in_array('user', $include))) {
11047 $substitutionarray['__DOL_MAIN_URL_ROOT__'] = DOL_MAIN_URL_ROOT;
11048 $substitutionarray['__(AnyTranslationKey)__'] = $outputlangs->transnoentitiesnoconv('TranslationOfKey');
11049 $substitutionarray['__(AnyTranslationKey|langfile)__'] = $outputlangs->transnoentitiesnoconv('TranslationOfKey') . ' (load also language file before)';
11050 $substitutionarray['__[AnyConstantKey]__'] = $outputlangs->transnoentitiesnoconv('ValueOfConstantKey');
11051 }
11052
11053 // Note: The lazyload variables are replaced only during the call by make_substitutions, and only if necessary
11054
11055 return $substitutionarray;
11056}
11057
11074function make_substitutions($text, $substitutionarray, $outputlangs = null, $converttextinhtmlifnecessary = 0)
11075{
11076 global $db, $langs;
11077
11078 if (!is_array($substitutionarray)) {
11079 return 'ErrorBadParameterSubstitutionArrayWhenCalling_make_substitutions';
11080 }
11081
11082 if (empty($outputlangs)) {
11083 $outputlangs = $langs;
11084 }
11085
11086 // Is initial text HTML or simple text ?
11087 $msgishtml = 0;
11088 if (dol_textishtml($text, 1)) {
11089 $msgishtml = 1;
11090 }
11091
11092 // Make substitution for language keys: __(AnyTranslationKey)__ or __(AnyTranslationKey|langfile)__
11093 if (is_object($outputlangs)) {
11094 $reg = array();
11095 while (preg_match('/__\‍(([^\‍)]+)\‍)__/', $text, $reg)) {
11096 // If key is __(TranslationKey|langfile)__, then force load of langfile.lang
11097 $tmp = explode('|', $reg[1]);
11098 if (!empty($tmp[1])) {
11099 $outputlangs->load($tmp[1]);
11100 }
11101
11102 $value = $outputlangs->transnoentitiesnoconv($reg[1]);
11103
11104 if (empty($converttextinhtmlifnecessary)) {
11105 // convert $newval into HTML is necessary
11106 $text = preg_replace('/__\‍(' . preg_quote($reg[1], '/') . '\‍)__/', $msgishtml ? dol_htmlentitiesbr($value) : $value, $text);
11107 } else {
11108 if (preg_match('/__\‍(' . preg_quote($reg[1], '/') . '\‍)__/', $text)) { // If found, so replacement will be done later
11109 if (! $msgishtml) {
11110 $valueishtml = dol_textishtml($value, 1);
11111 //var_dump("valueishtml=".$valueishtml);
11112
11113 if ($valueishtml) {
11114 $text = dol_htmlentitiesbr($text);
11115 $msgishtml = 1;
11116 }
11117 } else {
11118 $value = dol_nl2br((string) $value);
11119 }
11120 }
11121 $text = preg_replace('/__\‍(' . preg_quote($reg[1], '/') . '\‍)__/', $value, $text);
11122 }
11123 }
11124 }
11125
11126 // Make substitution for constant keys.
11127 // Must be after the substitution of translation, so if the text of translation contains a string __[xxx]__, it is also converted.
11128 $reg = array();
11129 while (preg_match('/__\[([^\]]+)\]__/', $text, $reg)) {
11130 $originalkeyfound = $reg[1];
11131 $keyfound = preg_replace('/\|urlencode$/', '', $originalkeyfound);
11132
11133 if (isASecretKey($keyfound)) {
11134 $value = '*****forbidden*****';
11135 } else {
11136 $value = getDolGlobalString($keyfound);
11137 // Execute some functions on value of substitution key
11138 if (preg_match('/\|urlencode$/', $originalkeyfound)) {
11139 $value = urlencode($value);
11140 }
11141 }
11142
11143 if (empty($converttextinhtmlifnecessary)) {
11144 // convert $newval into HTML is necessary
11145 $text = preg_replace('/__\[' . preg_quote($originalkeyfound, '/') . '\]__/', $msgishtml ? dol_htmlentitiesbr($value) : $value, $text);
11146 } else {
11147 if (preg_match('/__\[' . preg_quote($reg[1], '/') . '\]__/', $text)) { // If found, so replacement will be done later
11148 if (! $msgishtml) {
11149 $valueishtml = dol_textishtml($value, 1);
11150
11151 if ($valueishtml) {
11152 $text = dol_htmlentitiesbr($text);
11153 $msgishtml = 1;
11154 }
11155 } else {
11156 $value = dol_nl2br((string) $value);
11157 }
11158 }
11159 $text = preg_replace('/__\[' . preg_quote($originalkeyfound, '/') . '\]__/', $value, $text);
11160 }
11161 }
11162
11163 // Make substitution for array $substitutionarray
11164 foreach ($substitutionarray as $key => $value) {
11165 if (!isset($value)) {
11166 continue; // If value is null, it same than not having substitution key at all into array, we do not replace.
11167 }
11168
11169 if (getDolGlobalString('MAIN_MAIL_DO_NOT_USE_SIGN') && ($key == '__USER_SIGNATURE__' || $key == '__SENDEREMAIL_SIGNATURE__')) {
11170 $value = ''; // Protection
11171 }
11172 if (empty($converttextinhtmlifnecessary)) {
11173 $text = str_replace((string) $key, (string) $value, $text); // Cast to string is needed when value is 123.5 for example
11174 } else {
11175 if (strpos($text, (string) $key) !== false) { // If found, so replacement will be done later
11176 if (! $msgishtml) {
11177 $valueishtml = dol_textishtml($value, 1);
11178
11179 if ($valueishtml) {
11180 $text = dol_htmlentitiesbr($text);
11181 $msgishtml = 1;
11182 }
11183 } else {
11184 $value = dol_nl2br((string) $value);
11185 }
11186 }
11187 $text = str_replace((string) $key, (string) $value, $text); // Cast to string is needed, for 123.5 for example
11188 }
11189 }
11190
11191 /*
11192 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
11193 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.
11194 If no, we don't need to make replacement, so we do nothing.
11195 If yes, we can make the substitution:
11196
11197 include_once $path;
11198 $tmpobj = new $class($db);
11199 $valuetouseforsubstitution = $tmpobj->$method($id, '__XXX__');
11200 And make the replacement of "__XXX__@lazyload" with $valuetouseforsubstitution
11201 */
11202 $memory_object_list = array();
11203 foreach ($substitutionarray as $key => $value) {
11204 $lazy_load_arr = array();
11205 if (preg_match('/(__[A-Z\_]+__)@lazyload$/', $key, $lazy_load_arr)) {
11206 if (isset($lazy_load_arr[1]) && !empty($lazy_load_arr[1])) {
11207 $key_to_substitute = $lazy_load_arr[1];
11208 if (preg_match('/' . preg_quote($key_to_substitute, '/') . '/', $text)) {
11209 $param_arr = explode(':', (string) $value);
11210 // path:class:method:id
11211 if (count($param_arr) >= 4) {
11212 $path = $param_arr[0];
11213 $class = $param_arr[1];
11214 $method = $param_arr[2];
11215 $id = (int) $param_arr[3];
11216 $keyinarrayresult = empty($param_arr[4]) ? '' : $param_arr[4];
11217
11218 // load class file and init object list in memory
11219 if (!isset($memory_object_list[$class])) {
11220 if (dol_is_file(DOL_DOCUMENT_ROOT . $path)) {
11221 require_once DOL_DOCUMENT_ROOT . $path;
11222 if (class_exists($class)) {
11223 $memory_object_list[$class] = array(
11224 'list' => array(),
11225 );
11226 }
11227 }
11228 }
11229
11230 // fetch object and set substitution
11231 if (isset($memory_object_list[$class]) && isset($memory_object_list[$class]['list'])) {
11232 if (method_exists($class, $method)) {
11233 if (!isset($memory_object_list[$class]['list'][$id])) {
11234 $tmpobj = new $class($db);
11235 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
11236 $tmpvaluetouseforsubstitution = $tmpobj->$method($id, $key_to_substitute);
11237 $memory_object_list[$class]['list'][$id] = $tmpobj;
11238 } else {
11239 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
11240 $tmpobj = $memory_object_list[$class]['list'][$id];
11241 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
11242 $tmpvaluetouseforsubstitution = $tmpobj->$method($id, $key_to_substitute, true);
11243 }
11244
11245 if ($keyinarrayresult) {
11246 $valuetouseforsubstitution = (string) $tmpvaluetouseforsubstitution[$keyinarrayresult]; // Cast to string in case value is 123.5 for example
11247 } else {
11248 $valuetouseforsubstitution = (string) $tmpvaluetouseforsubstitution; // Cast to string in case value is 123.5 for example
11249 }
11250 $text = str_replace((string) $key_to_substitute, $valuetouseforsubstitution, $text);
11251 }
11252 }
11253 }
11254 }
11255 }
11256 }
11257 }
11258
11259 return $text;
11260}
11261
11274function complete_substitutions_array(&$substitutionarray, $outputlangs, $object = null, $parameters = null, $callfunc = "completesubstitutionarray")
11275{
11276 global $conf, $user;
11277
11278 require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
11279
11280 // Note: substitution key for each extrafields, using key __EXTRA_XXX__ is already available into the getCommonSubstitutionArray used to build the substitution array.
11281
11282 // Check if there is external substitution to do, requested by plugins
11283 $dirsubstitutions = array_merge(array(), (array) $conf->modules_parts['substitutions']);
11284
11285 foreach ($dirsubstitutions as $reldir) {
11286 $dir = dol_buildpath($reldir, 0);
11287
11288 // Check if directory exists
11289 if (!dol_is_dir($dir)) {
11290 continue;
11291 }
11292
11293 $substitfiles = dol_dir_list($dir, 'files', 0, 'functions_');
11294 foreach ($substitfiles as $substitfile) {
11295 $reg = array();
11296 if (preg_match('/functions_(.*)\.lib\.php/i', $substitfile['name'], $reg)) {
11297 $module = $reg[1];
11298
11299 dol_syslog("Library " . $substitfile['name'] . " found into " . $dir);
11300 // Include the user's functions file
11301 require_once $dir . $substitfile['name'];
11302 // Call the user's function, and only if it is defined
11303 $function_name = $module . "_" . $callfunc;
11304 if (function_exists($function_name)) {
11305 $function_name($substitutionarray, $outputlangs, $object, $parameters);
11306 }
11307 }
11308 }
11309 }
11310 if (getDolGlobalString('ODT_ENABLE_ALL_TAGS_IN_SUBSTITUTIONS')) {
11311 // to list all tags in odt template
11312 $tags = '';
11313 foreach ($substitutionarray as $key => $value) {
11314 $tags .= '{' . $key . '} => ' . $value . "\n";
11315 }
11316 $substitutionarray = array_merge($substitutionarray, array('__ALL_TAGS__' => $tags));
11317 }
11318}
11319
11329function print_date_range($date_start, $date_end, $format = '', $outputlangs = null)
11330{
11331 print get_date_range($date_start, $date_end, $format, $outputlangs);
11332}
11333
11344function get_date_range($date_start, $date_end, $format = '', $outputlangs = null, $withparenthesis = 1)
11345{
11346 global $langs;
11347
11348 $out = '';
11349
11350 if (!is_object($outputlangs)) {
11351 $outputlangs = $langs;
11352 }
11353
11354 if ($date_start && $date_end) {
11355 $out .= ($withparenthesis ? ($withparenthesis == 1 ? ' ' : '').'(' : '') . $outputlangs->transnoentitiesnoconv('DateFromTo', dol_print_date($date_start, $format, false, $outputlangs), dol_print_date($date_end, $format, false, $outputlangs)) . ($withparenthesis ? ')' : '');
11356 }
11357 if ($date_start && !$date_end) {
11358 $out .= ($withparenthesis ? ($withparenthesis == 1 ? ' ' : '').'(' : '') . $outputlangs->transnoentitiesnoconv('DateFrom', dol_print_date($date_start, $format, false, $outputlangs)) . ($withparenthesis ? ')' : '');
11359 }
11360 if (!$date_start && $date_end) {
11361 $out .= ($withparenthesis ? ($withparenthesis == 1 ? ' ' : '').'(' : '') . $outputlangs->transnoentitiesnoconv('DateUntil', dol_print_date($date_end, $format, false, $outputlangs)) . ($withparenthesis ? ')' : '');
11362 }
11363
11364 return $out;
11365}
11366
11375function dolGetFirstLastname($firstname, $lastname, $nameorder = -1)
11376{
11377 $ret = '';
11378 // If order not defined, we use the setup
11379 if ($nameorder < 0) {
11380 $nameorder = (!getDolGlobalString('MAIN_FIRSTNAME_NAME_POSITION') ? 1 : 0);
11381 }
11382 if ($nameorder == 1) {
11383 $ret .= $firstname;
11384 if ($firstname && $lastname) {
11385 $ret .= ' ';
11386 }
11387 $ret .= $lastname;
11388 } elseif ($nameorder == 2 || $nameorder == 3) {
11389 $ret .= $firstname;
11390 if (empty($ret) && $nameorder == 3) {
11391 $ret .= $lastname;
11392 }
11393 } else { // 0, 4 or 5
11394 $ret .= $lastname;
11395 if (empty($ret) && $nameorder == 5) {
11396 $ret .= $firstname;
11397 }
11398 if ($nameorder == 0) {
11399 if ($firstname && $lastname) {
11400 $ret .= ' ';
11401 }
11402 $ret .= $firstname;
11403 }
11404 }
11405 return $ret;
11406}
11407
11408
11421function setEventMessage($mesgs, $style = 'mesgs', $noduplicate = 0, $attop = 0)
11422{
11423 //dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING); This is not deprecated, it is used by setEventMessages function
11424 if (!is_array($mesgs)) {
11425 $mesgs = trim((string) $mesgs);
11426 // If mesgs is a not an empty string
11427 if ($mesgs) {
11428 if (!empty($noduplicate) && isset($_SESSION['dol_events'][$style]) && in_array($mesgs, $_SESSION['dol_events'][$style])) {
11429 return;
11430 }
11431 if ($attop) {
11432 array_unshift($_SESSION['dol_events'][$style], $mesgs);
11433 } else {
11434 $_SESSION['dol_events'][$style][] = $mesgs;
11435 }
11436 }
11437 } else {
11438 // If mesgs is an array
11439 foreach ($mesgs as $mesg) {
11440 $mesg = trim((string) $mesg);
11441 if ($mesg) {
11442 if (!empty($noduplicate) && isset($_SESSION['dol_events'][$style]) && in_array($mesg, $_SESSION['dol_events'][$style])) {
11443 return;
11444 }
11445 if ($attop) {
11446 array_unshift($_SESSION['dol_events'][$style], $mesgs);
11447 } else {
11448 $_SESSION['dol_events'][$style][] = $mesg;
11449 }
11450 }
11451 }
11452 }
11453}
11454
11468function setEventMessages($mesg, $mesgs, $style = 'mesgs', $messagekey = '', $noduplicate = 0, $attop = 0)
11469{
11470 if (empty($mesg) && empty($mesgs)) {
11471 dol_syslog("Try to add a message in stack, but value to add is empty message" . getCallerInfoString(), LOG_WARNING);
11472 } else {
11473 if ($messagekey) {
11474 // Complete message with a js link to set a cookie "DOLHIDEMESSAGE".$messagekey;
11475 // TODO
11476 $mesg .= '';
11477 }
11478 if (empty($messagekey) || empty($_COOKIE["DOLUSER_HIDEMESSAGE" . $messagekey])) {
11479 if (!in_array((string) $style, array('mesgs', 'warnings', 'errors'))) {
11480 dol_print_error(null, 'Bad parameter style=' . $style . ' for setEventMessages');
11481 }
11482 if (empty($mesgs)) {
11483 setEventMessage((string) $mesg, $style, $noduplicate, $attop);
11484 } else {
11485 if (!empty($mesg) && !in_array($mesg, $mesgs)) {
11486 setEventMessage($mesg, $style, $noduplicate, $attop); // Add message string if not already into array
11487 }
11488 setEventMessage($mesgs, $style, $noduplicate, $attop);
11489 }
11490 }
11491 }
11492}
11493
11503function dol_htmloutput_events($disabledoutputofmessages = 0)
11504{
11505 // Show mesgs
11506 if (isset($_SESSION['dol_events']['mesgs'])) {
11507 if (empty($disabledoutputofmessages)) {
11508 dol_htmloutput_mesg('', $_SESSION['dol_events']['mesgs']);
11509 }
11510 unset($_SESSION['dol_events']['mesgs']);
11511 }
11512 // Show errors
11513 if (isset($_SESSION['dol_events']['errors'])) {
11514 if (empty($disabledoutputofmessages)) {
11515 dol_htmloutput_mesg('', $_SESSION['dol_events']['errors'], 'error');
11516 }
11517 unset($_SESSION['dol_events']['errors']);
11518 }
11519
11520 // Show warnings
11521 if (isset($_SESSION['dol_events']['warnings'])) {
11522 if (empty($disabledoutputofmessages)) {
11523 dol_htmloutput_mesg('', $_SESSION['dol_events']['warnings'], 'warning');
11524 }
11525 unset($_SESSION['dol_events']['warnings']);
11526 }
11527}
11528
11543function get_htmloutput_mesg($mesgstring = '', $mesgarray = [], $style = 'ok', $keepembedded = 0)
11544{
11545 global $conf, $langs;
11546
11547 $ret = 0;
11548 $return = '';
11549 $out = '';
11550 $divstart = $divend = '';
11551
11552 // If inline message with no format, we add it.
11553 if ((empty($conf->use_javascript_ajax) || getDolGlobalString('MAIN_DISABLE_JQUERY_JNOTIFY') || defined('DISABLE_JQUERY_JNOTIFY') || $keepembedded) && !preg_match('/<div class=".*">/i', $out)) {
11554 $divstart = '<div class="' . $style . ' clearboth">';
11555 $divend = '</div>';
11556 }
11557
11558 if ((is_array($mesgarray) && count($mesgarray)) || $mesgstring) {
11559 $langs->load("errors");
11560 $out .= $divstart;
11561 if (is_array($mesgarray) && count($mesgarray)) {
11562 foreach ($mesgarray as $message) {
11563 $ret++;
11564 $out .= $langs->trans($message);
11565 if ($ret < count($mesgarray)) {
11566 $out .= "<br>\n";
11567 }
11568 }
11569 }
11570 if ($mesgstring) {
11571 $ret++;
11572 $out .= $langs->trans($mesgstring);
11573 }
11574 $out .= $divend;
11575 }
11576
11577 if ($out) {
11578 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_DISABLE_JQUERY_JNOTIFY') && !defined('DISABLE_JQUERY_JNOTIFY') && empty($keepembedded)) {
11579 $return = '<script nonce="' . getNonce() . '">
11580 $(document).ready(function() {
11581 /* jnotify(message, preset of message type, keepmessage) */
11582 $.jnotify("' . dol_escape_js($out) . '", "' . ($style == "ok" ? 3000 : $style) . '", ' . ($style == "ok" ? "false" : "true") . ',{ remove: function (){} } );
11583 });
11584 </script>';
11585 } else {
11586 $return = $out;
11587 }
11588 }
11589
11590 return $return;
11591}
11592
11604function get_htmloutput_errors($mesgstring = '', $mesgarray = array(), $keepembedded = 0)
11605{
11606 return get_htmloutput_mesg($mesgstring, $mesgarray, 'error', $keepembedded);
11607}
11608
11622function dol_htmloutput_mesg($mesgstring = '', $mesgarray = array(), $style = 'ok', $keepembedded = 0)
11623{
11624 if (empty($mesgstring) && (!is_array($mesgarray) || count($mesgarray) == 0)) {
11625 return;
11626 }
11627
11628 $iserror = 0;
11629 $iswarning = 0;
11630 if (is_array($mesgarray)) {
11631 foreach ($mesgarray as $val) {
11632 if ($val && preg_match('/class="error"/i', $val)) {
11633 $iserror++;
11634 break;
11635 }
11636 if ($val && preg_match('/class="warning"/i', $val)) {
11637 $iswarning++;
11638 break;
11639 }
11640 }
11641 } elseif ($mesgstring && preg_match('/class="error"/i', $mesgstring)) {
11642 $iserror++;
11643 } elseif ($mesgstring && preg_match('/class="warning"/i', $mesgstring)) {
11644 $iswarning++;
11645 }
11646 if ($style == 'error' || $style == 'errors') {
11647 $iserror++;
11648 }
11649 if ($style == 'warning' || $style == 'warnings') {
11650 $iswarning++;
11651 }
11652
11653 if ($iserror || $iswarning) {
11654 // Remove div from texts
11655 $mesgstring = preg_replace('/<\/div><div class="(error|warning)">/', '<br>', $mesgstring);
11656 $mesgstring = preg_replace('/<div class="(error|warning)">/', '', $mesgstring);
11657 $mesgstring = preg_replace('/<\/div>/', '', $mesgstring);
11658 // Remove div from texts array
11659 if (is_array($mesgarray)) {
11660 $newmesgarray = array();
11661 foreach ($mesgarray as $val) {
11662 if (is_string($val)) {
11663 $tmpmesgstring = preg_replace('/<\/div><div class="(error|warning)">/', '<br>', $val);
11664 $tmpmesgstring = preg_replace('/<div class="(error|warning)">/', '', $tmpmesgstring);
11665 $tmpmesgstring = preg_replace('/<\/div>/', '', $tmpmesgstring);
11666 $newmesgarray[] = $tmpmesgstring;
11667 } else {
11668 dol_syslog("Error call of dol_htmloutput_mesg with an array with a value that is not a string", LOG_WARNING);
11669 }
11670 }
11671 $mesgarray = $newmesgarray;
11672 }
11673 print get_htmloutput_mesg($mesgstring, $mesgarray, ($iserror ? 'error' : 'warning'), $keepembedded);
11674 } else {
11675 print get_htmloutput_mesg($mesgstring, $mesgarray, 'ok', $keepembedded);
11676 }
11677}
11678
11690function dol_htmloutput_errors($mesgstring = '', $mesgarray = array(), $keepembedded = 0)
11691{
11692 dol_htmloutput_mesg($mesgstring, $mesgarray, 'error', $keepembedded);
11693}
11694
11702function dolSort($arraytosort)
11703{
11704 sort($arraytosort);
11705 return $arraytosort;
11706}
11707
11729function dol_sort_array(&$array, $index, $order = 'asc', $natsort = 0, $case_sensitive = 0, $keepindex = 0)
11730{
11731 // Clean parameters
11732 $order = strtolower($order);
11733
11734 if (is_array($array)) {
11735 $sizearray = count($array);
11736 if ($sizearray > 0) {
11737 // Build a temp array with sorting key as value
11738 $temp = array();
11739 foreach (array_keys($array) as $key) {
11740 $tmpmultikey = explode(',', $index);
11741 $newindex = $tmpmultikey[0];
11742 if (is_object($array[$key])) {
11743 $temp[$key] = empty($array[$key]->$newindex) ? 0 : $array[$key]->$newindex;
11744 // Add other keys
11745 if (!empty($tmpmultikey[1])) {
11746 $newindex = $tmpmultikey[1];
11747 $temp[$key] .= '__' . (empty($array[$key]->$newindex) ? 0 : $array[$key]->$newindex);
11748 }
11749 } else {
11750 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable,PhanTypeArraySuspicious,PhanTypeMismatchDimFetch
11751 $temp[$key] = empty($array[$key][$newindex]) ? 0 : $array[$key][$newindex];
11752 // Add other keys
11753 if (!empty($tmpmultikey[1])) {
11754 $newindex = $tmpmultikey[1];
11755 // @phan-suppress-next-line PhanTypeArraySuspicious,PhanTypeMismatchDimFetch
11756 $temp[$key] .= '__' . (empty($array[$key][$newindex]) ? 0 : $array[$key][$newindex]);
11757 }
11758 }
11759 if ($natsort == -1) {
11760 $temp[$key] = '___' . $temp[$key]; // We add a string at begin of value to force an alpha order when using asort.
11761 }
11762 }
11763 if (empty($natsort) || $natsort == -1) {
11764 if ($order == 'asc') {
11765 asort($temp);
11766 } else {
11767 arsort($temp);
11768 }
11769 } else {
11770 if ($case_sensitive) {
11771 natsort($temp);
11772 } else {
11773 natcasesort($temp); // natecasesort is not sensible to case
11774 }
11775 if ($order != 'asc') {
11776 $temp = array_reverse($temp, true);
11777 }
11778 }
11779
11780 $sorted = array();
11781
11782 foreach (array_keys($temp) as $key) {
11783 (is_numeric($key) && empty($keepindex)) ? $sorted[] = $array[$key] : $sorted[$key] = $array[$key];
11784 }
11785
11786 return $sorted;
11787 }
11788 }
11789 return $array;
11790}
11791
11792
11800function utf8_check($str)
11801{
11802 $str = (string) $str; // Sometimes string is an int.
11803
11804 // We must use here a binary strlen function (so not dol_strlen)
11805 $strLength = strlen($str);
11806 for ($i = 0; $i < $strLength; $i++) {
11807 if (ord($str[$i]) < 0x80) {
11808 continue; // 0bbbbbbb
11809 } elseif ((ord($str[$i]) & 0xE0) == 0xC0) {
11810 $n = 1; // 110bbbbb
11811 } elseif ((ord($str[$i]) & 0xF0) == 0xE0) {
11812 $n = 2; // 1110bbbb
11813 } elseif ((ord($str[$i]) & 0xF8) == 0xF0) {
11814 $n = 3; // 11110bbb
11815 } elseif ((ord($str[$i]) & 0xFC) == 0xF8) {
11816 $n = 4; // 111110bb
11817 } elseif ((ord($str[$i]) & 0xFE) == 0xFC) {
11818 $n = 5; // 1111110b
11819 } else {
11820 return false; // Does not match any model
11821 }
11822 for ($j = 0; $j < $n; $j++) { // n bytes matching 10bbbbbb follow ?
11823 if ((++$i == strlen($str)) || ((ord($str[$i]) & 0xC0) != 0x80)) {
11824 return false;
11825 }
11826 }
11827 }
11828 return true;
11829}
11830
11838function utf8_valid($str)
11839{
11840 /* 2 other methods to test if string is utf8
11841 $validUTF8 = mb_check_encoding($messagetext, 'UTF-8');
11842 $validUTF8b = ! (false === mb_detect_encoding($messagetext, 'UTF-8', true));
11843 */
11844 return preg_match('//u', $str) ? true : false;
11845}
11846
11847
11854function ascii_check($str)
11855{
11856 if (function_exists('mb_check_encoding')) {
11857 //if (mb_detect_encoding($str, 'ASCII', true) return false;
11858 if (!mb_check_encoding($str, 'ASCII')) {
11859 return false;
11860 }
11861 } else {
11862 if (preg_match('/[^\x00-\x7f]/', $str)) {
11863 return false; // Contains a byte > 7f
11864 }
11865 }
11866
11867 return true;
11868}
11869
11870
11878function dol_osencode($str)
11879{
11880 $tmp = ini_get("unicode.filesystem_encoding");
11881 if (empty($tmp) && !empty($_SERVER["WINDIR"])) {
11882 $tmp = 'iso-8859-1'; // By default for windows
11883 }
11884 if (empty($tmp)) {
11885 $tmp = 'utf-8'; // By default for other
11886 }
11887 if (getDolGlobalString('MAIN_FILESYSTEM_ENCODING')) {
11888 $tmp = getDolGlobalString('MAIN_FILESYSTEM_ENCODING');
11889 }
11890
11891 if ($tmp == 'iso-8859-1') {
11892 return mb_convert_encoding($str, 'ISO-8859-1', 'UTF-8');
11893 }
11894 return $str;
11895}
11896
11897
11913function dol_getIdFromCode($db, $key, $tablename, $fieldkey = 'code', $fieldid = 'id', $entityfilter = 0, $filters = '', $useCache = true)
11914{
11915 global $conf;
11916
11917 // If key empty
11918 if ($key == '') {
11919 return 0;
11920 }
11921
11922 // Check in cache
11923 if ($useCache && isset($conf->cache['codeid'][$tablename][$key][$fieldid])) { // Can be defined to 0 or ''
11924 return $conf->cache['codeid'][$tablename][$key][$fieldid]; // Found in cache
11925 }
11926
11927 dol_syslog('dol_getIdFromCode (value for field ' . $fieldid . ' from key ' . $key . ' not found into cache)', LOG_DEBUG);
11928
11929 $sql = "SELECT " . $db->sanitize($fieldid) . " as valuetoget";
11930 $sql .= " FROM " . MAIN_DB_PREFIX . $db->sanitize($tablename);
11931 if ($fieldkey == 'id' || $fieldkey == 'rowid') {
11932 $sql .= " WHERE " . $db->sanitize($fieldkey) . " = " . ((int) $key);
11933 } else {
11934 $sql .= " WHERE " . $db->sanitize($fieldkey) . " = '" . $db->escape($key) . "'";
11935 }
11936 if (!empty($entityfilter)) {
11937 $sql .= " AND entity IN (" . getEntity($tablename) . ")";
11938 }
11939 if ($filters) {
11940 $sql .= $filters; // @phan-suppress-current-line SqlInjection
11941 }
11942
11943 $resql = $db->query($sql);
11944 if ($resql) {
11945 $obj = $db->fetch_object($resql);
11946 $valuetoget = '';
11947 if ($obj) {
11948 $valuetoget = $obj->valuetoget;
11949 $conf->cache['codeid'][$tablename][$key][$fieldid] = $valuetoget;
11950 } else {
11951 $conf->cache['codeid'][$tablename][$key][$fieldid] = '';
11952 }
11953 $db->free($resql);
11954
11955 return $valuetoget;
11956 } else {
11957 return -1;
11958 }
11959}
11960
11970function isStringVarMatching($var, $regextext, $matchrule = 1)
11971{
11972 // Tolerate callers (custom modules, older code) that already pass a full regex with delimiters
11973 // like '/^(aaa|bbb)/' instead of the bare body. Without this, the function would build
11974 // '/^/^(aaa|bbb)//' which trips preg_match() with 'Unknown modifier ^'.
11975 $regextext = preg_replace('#^/\^?#', '', (string) $regextext);
11976 $regextext = preg_replace('#\$?/[imsxuADSUXJ]*$#', '', $regextext);
11977
11978 if ($matchrule == 1) {
11979 if ($var == 'mainmenu') {
11980 global $mainmenu;
11981 return (preg_match('/^' . $regextext . '/', $mainmenu));
11982 } elseif ($var == 'leftmenu') {
11983 global $leftmenu;
11984 return (preg_match('/^' . $regextext . '/', $leftmenu));
11985 } else {
11986 return 'This variable is not accessible with dol_eval';
11987 }
11988 } else {
11989 return 'This value '.$matchrule.' for param $matchrule is not yet implemented';
11990 }
11991}
11992
11993
12003function verifCond($strToEvaluate, $onlysimplestring = '1')
12004{
12005 //print $strToEvaluate."<br>\n";
12006 $rights = true;
12007 if (isset($strToEvaluate) && $strToEvaluate !== '') {
12008 //var_dump($strToEvaluate);
12009 //$rep = dol_eval($strToEvaluate, 1, 0, '1'); // to show the error
12010 $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
12011
12012 // On string syntax error, dol_eval may return a string that start with 'Bad call of ...' or 'Bad string syntax to evaluate...' !!!
12013 //var_dump($strToEvaluate, $rep);
12014 $rights = (bool) $rep && (!is_string($rep) || (strpos($rep, 'Exception during') === false && strpos($rep, 'Bad call of') === false && strpos($rep, 'Bad string syntax to evaluate') === false));
12015 //var_dump($rights);
12016 }
12017 return $rights;
12018}
12019
12035function dol_eval($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestring = '1')
12036{
12037 if ($returnvalue != 1) {
12038 dol_syslog("Use of dol_eval with parameter returnvalue = 0 is now forbidden. Please fix this", LOG_ERR);
12039 }
12040
12041 global $dolibarr_main_use_dol_eval_new; // experimental option, not yet ready
12042 if (!empty($dolibarr_main_use_dol_eval_new)) {
12043 return dol_eval_new($s);
12044 } else {
12045 return dol_eval_standard($s, $hideerrors, $onlysimplestring);
12046 }
12047}
12048
12059function dol_eval_new($s)
12060{
12061 // Only this global variables can be read by eval function and returned to caller
12062 global $conf, // Read of const is done with getDolGlobalString() but we need $conf->currency for example
12063 $db, $langs, $user, $website, $websitepage,
12064 $action, $mainmenu, $leftmenu,
12065 $mysoc,
12066 $objectoffield, // To allow the use of $objectoffield in computed fields
12067
12068 // Old variables used
12069 $object;
12070
12071 if (getDolGlobalString('MAIN_ALLOW_OLD_VAR_OBJ_IN_DOL_EVAL')) {
12072 global $obj; // To get $obj used into list when dol_eval() is used for computed fields and $obj is not yet $object
12073 }
12074
12075 // PHP < 7.4.0
12076 defined('T_COALESCE_EQUAL') || define('T_COALESCE_EQUAL', PHP_INT_MAX);
12077 defined('T_FN') || define('T_FN', PHP_INT_MAX);
12078
12079 // PHP < 8.0.0
12080 defined('T_ATTRIBUTE') || define('T_ATTRIBUTE', PHP_INT_MAX);
12081 defined('T_MATCH') || define('T_MATCH', PHP_INT_MAX);
12082 defined('T_NAME_FULLY_QUALIFIED') || define('T_NAME_FULLY_QUALIFIED', PHP_INT_MAX);
12083 defined('T_NAME_QUALIFIED') || define('T_NAME_QUALIFIED', PHP_INT_MAX);
12084 defined('T_NAME_RELATIVE') || define('T_NAME_RELATIVE', PHP_INT_MAX);
12085
12086 // PHP < 8.1.0
12087 defined('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG') || define('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG', PHP_INT_MAX);
12088 defined('T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG') || define('T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG', PHP_INT_MAX);
12089 defined('T_ENUM') || define('T_ENUM', PHP_INT_MAX);
12090 defined('T_READONLY') || define('T_READONLY', PHP_INT_MAX);
12091
12092 // PHP < 8.4.0
12093 defined('T_PRIVATE_SET') || define('T_PRIVATE_SET', PHP_INT_MAX);
12094 defined('T_PROTECTED_SET') || define('T_PROTECTED_SET', PHP_INT_MAX);
12095 defined('T_PUBLIC_SET') || define('T_PUBLIC_SET', PHP_INT_MAX);
12096
12097 $prohibited_token_ids = [
12098 /*
12099 * Prohibited int tokens
12100 */
12101
12102 // T_AND_EQUAL', 'T_ARRAY', 'T_ARRAY_CAST', 'T_AS',
12103 'T_ABSTRACT',
12104 'T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG',
12105 'T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG',
12106 'T_ATTRIBUTE',
12107 // 'T_BOOLEAN_AND', 'T_BOOLEAN_OR', 'T_BOOL_CAST', 'T_BREAK',
12108 'T_BAD_CHARACTER',
12109 // 'T_CASE', 'T_CLASS_C', 'T_CLONE', 'T_COALESCE', 'T_COALESCE_EQUAL', 'T_COMMENT', 'T_CONCAT_EQUAL',
12110 // 'T_CONSTANT_ENCAPSED_STRING', 'T_CONTINUE', 'T_CURLY_OPEN',
12111 'T_CALLABLE',
12112 'T_CATCH',
12113 'T_CLASS',
12114 'T_CLOSE_TAG',
12115 'T_CONST',
12116 // 'T_DEC', 'T_DEFAULT', 'T_DIV_EQUAL', 'T_DNUMBER', 'T_DO', 'T_DOC_COMMENT',
12117 // 'T_DOLLAR_OPEN_CURLY_BRACES', 'T_DOUBLE_ARROW', 'T_DOUBLE_CAST', 'T_DOUBLE_COLON',
12118 'T_DECLARE',
12119 'T_DIR',
12120 // 'T_ELLIPSIS', 'T_ELSE', 'T_ELSEIF', 'T_EMPTY', 'T_ENCAPSED_AND_WHITESPACE', 'T_ENDFOR',
12121 // 'T_ENDFOREACH', 'T_ENDIF', 'T_ENDSWITCH', 'T_ENDWHILE', 'T_END_HEREDOC',
12122 'T_ECHO',
12123 'T_ENDDECLARE',
12124 'T_ENUM',
12125 'T_EVAL',
12126 'T_EXIT',
12127 'T_EXTENDS',
12128 // 'T_FOR', 'T_FOREACH',
12129 'T_FILE',
12130 'T_FINAL',
12131 'T_FINALLY',
12132 'T_FN',
12133 'T_FUNCTION',
12134 'T_FUNC_C',
12135 'T_GLOBAL',
12136 'T_GOTO',
12137 'T_HALT_COMPILER',
12138 // 'T_IF', 'T_INC', 'T_INLINE_HTML', 'T_INSTANCEOF', 'T_INT_CAST', 'T_ISSET', 'T_IS_EQUAL', 'T_IS_GREATER_OR_EQUAL',
12139 // 'T_IS_IDENTICAL', 'T_IS_NOT_EQUAL', 'T_IS_NOT_IDENTICAL', 'T_IS_SMALLER_OR_EQUAL',
12140 'T_IMPLEMENTS',
12141 'T_INCLUDE',
12142 'T_INCLUDE_ONCE',
12143 'T_INSTEADOF',
12144 'T_INTERFACE',
12145 // 'T_LIST', 'T_LNUMBER', 'T_LOGICAL_AND', 'T_LOGICAL_OR', 'T_LOGICAL_XOR',
12146 'T_LINE',
12147 // 'T_MINUS_EQUAL', 'T_MOD_EQUAL', 'T_MUL_EQUAL',
12148 'T_METHOD_C',
12149 // 'T_NEW',
12150 // 'T_NS_SEPARATOR', 'T_NUM_STRING',
12151 'T_NAMESPACE',
12152 // 'T_NAME_FULLY_QUALIFIED', 'T_NAME_QUALIFIED', 'T_NAME_RELATIVE', 'T_NS_C',
12153 // 'T_OBJECT_CAST', 'T_OBJECT_OPERATOR', 'T_OR_EQUAL',
12154 'T_OPEN_TAG',
12155 'T_OPEN_TAG_WITH_ECHO',
12156 // 'T_PAAMAYIM_NEKUDOTAYIM', 'T_PLUS_EQUAL', 'T_POW', 'T_POW_EQUAL',
12157 'T_PRINT',
12158 'T_PRIVATE',
12159 'T_PROTECTED',
12160 'T_PUBLIC',
12161 // 'T_PROPERTY_C',
12162 'T_READONLY',
12163 'T_REQUIRE',
12164 'T_REQUIRE_ONCE',
12165 'T_RETURN',
12166 // 'T_SL', 'T_SL_EQUAL', 'T_SPACESHIP', 'T_SR', 'T_SR_EQUAL', 'T_START_HEREDOC', 'T_STATIC',
12167 // 'T_STRING', 'T_STRING_CAST', 'T_STRING_VARNAME', 'T_SWITCH',
12168 'T_STATIC',
12169 'T_THROW',
12170 'T_TRAIT',
12171 'T_TRAIT_C',
12172 'T_TRY',
12173 'T_UNSET',
12174 'T_UNSET_CAST',
12175 'T_USE',
12176 // 'T_VARIABLE',
12177 'T_VAR',
12178 // 'T_WHILE', 'T_WHITESPACE',
12179 // 'T_XOR_EQUAL',
12180 // 'T_YIELD', 'T_YIELD_FROM',
12181
12182 /*
12183 * Prohibited string tokens
12184 */
12185 ';',
12186 '`',
12187 ];
12188
12189 $prohibited_variables = [
12190 '$_COOKIE',
12191 '$_ENV',
12192 '$_FILES',
12193 '$GLOBALS',
12194 '$_GET',
12195 '$_POST',
12196 '$_REQUEST',
12197 '$_SERVER',
12198 '$_SESSION',
12199 ];
12200
12201 $forbiddenphpfunctions = array();
12202 $forbiddenphpmethods = array();
12203
12204 // Same list than in dol_eval
12205 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("override_function", "session_id", "session_create_id", "session_regenerate_id"));
12206 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("get_defined_functions", "get_defined_vars", "get_defined_constants", "get_declared_classes"));
12207 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("function"));
12208
12209 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("GETPOST")); // native dolibarr functions
12210
12211 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("ob_start"));
12212
12213 // Functions with callable parameters
12214 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("call_user_func", "call_user_func_array"));
12215 $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"));
12216 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("usort", "uasort", "uksort"));
12217 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("preg_replace_callback", "preg_replace_callback_array", "header_register_callback"));
12218 $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"));
12219 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("spl_autoload_register", "spl_autoload_unregister", "iterator_apply", "session_set_save_handler"));
12220 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("forward_static_call", "forward_static_call_array", "register_postsend_function"));
12221 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("readline_completion_function", "readline_callback_handler_install"));
12222
12223 // Exec functions
12224 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("exec", "passthru", "shell_exec", "system", "proc_open", "popen"));
12225 $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"));
12226 $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", ));
12227 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("putenv", "dl", "apache_child_terminate", "apache_setenv"));
12228 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_eval", "dol_eval_new", "dol_eval_standard", "executeCLI", "verifCond", "dolEncrypt", "dolDecrypt")); // native dolibarr functions
12229 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("eval", "create_function", "assert", "mb_ereg_replace")); // function with eval capabilities
12230
12231 // Include functions
12232 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("require", "include", "require_once", "include_once"));
12233
12234 $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
12235 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("chdir", "dir", "fopen", "file", "file_exists", "file_get_contents", "file_put_contents", "fget", "fgetc", "fgetcsv", "flock", "fputs", "fputscsv", "fpassthru", "fscanf", "fseek", "fwrite", "is_file", "is_dir", "is_link", "mkdir", "opendir", "rmdir", "scandir", "symlink", "touch", "unlink", "umask"));
12236
12237 if (!getDolGlobalString('MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL')) { // We disallow all function that allow to obfuscate the real name of a function
12238 // @phpcs:ignore
12239 $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
12240 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_concat", "dol_concatdesc")); // native dolibarr functions
12241 }
12242
12243 $forbiddenphpmethods = array_merge($forbiddenphpmethods, array('invoke', 'invokeArgs')); // Methods of ReflectionFunction to execute a function
12244
12245 $prohibited_functions = array_merge($forbiddenphpfunctions, $forbiddenphpmethods);
12246
12247 $prohibited_token_arrangements = [
12248 // Variable functions "$a(", '"$a"(', "'FN_NAME'(", ('FN_NAME')()
12249 ' T_VARIABLE ( ',
12250 ' " ( ',
12251 ' \' ( ',
12252 ' T_CONSTANT_ENCAPSED_STRING ( ',
12253 ' ) ( ',
12254 ];
12255
12256 $tokens = token_get_all("<?php return {$s};", TOKEN_PARSE);
12257
12258 $tokens_arrangement = ' ';
12259
12260 for ($i = 2, $c = count($tokens) - 1; $i < $c; ++$i) { // ignore <?php return and ;
12261 if (is_array($tokens[$i])) {
12262 $token_id = $tokens[$i][0];
12263 $token_value = $tokens[$i][1];
12264 $token_name = token_name($tokens[$i][0]);
12265 } else {
12266 $token_id = $tokens[$i];
12267 $token_value = $tokens[$i];
12268 $token_name = $tokens[$i];
12269 }
12270
12271 // Ignore whitespaces
12272 if (T_WHITESPACE === $token_id) {
12273 continue;
12274 }
12275
12276 // Keep history to check arrangements
12277 $tokens_arrangement .= "{$token_name} ";
12278
12279 // Prohibited Variables
12280 if (
12281 T_VARIABLE === $token_id
12282 && in_array($token_value, $prohibited_variables, true)
12283 ) {
12284 return "Bad string syntax to evaluate. « {$token_value} » is prohibited in « {$s} »";
12285 }
12286
12287 // Prohibited Functions
12288 if (
12289 T_STRING === $token_id
12290 && in_array($token_value, $prohibited_functions, true)
12291 ) {
12292 return "Bad string syntax to evaluate. « {$token_value} » is prohibited in « {$s} »";
12293 }
12294 }
12295
12296 // Prohibited Token IDs
12297 $maxi = count($prohibited_token_ids);
12298 for ($i = 0; $i < $maxi; ++$i) {
12299 if (false !== strpos($tokens_arrangement, " {$prohibited_token_ids[$i]} ")) {
12300 return "Bad string syntax to evaluate. « {$prohibited_token_ids[$i]} » is prohibited in « {$s} »";
12301 }
12302 }
12303
12304 // Prohibited token arrangements
12305 $maxi = count($prohibited_token_arrangements);
12306 for ($i = 0; $i < $maxi; ++$i) {
12307 if (false !== strpos($tokens_arrangement, $prohibited_token_arrangements[$i])) {
12308 return "Bad string syntax to evaluate. « {$prohibited_token_arrangements[$i]} » is prohibited in « {$s} »";
12309 }
12310 }
12311
12312 // Return result
12313 try {
12314 return @eval("return {$s};") ?? '';
12315 } catch (Throwable $ex) {
12316 return "Bad string syntax to evaluate. Exception during evaluation: " . $s . " - " . $ex->getMessage();
12317 }
12318}
12319
12334function dol_eval_standard($s, $hideerrors = 1, $onlysimplestring = '1')
12335{
12336 // Only this global variables can be read by eval function and returned to caller
12337 // The less we have, the better it is.
12338
12339 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()
12340 global $db, $langs, $user, $website, $websitepage;
12341 global $action, $mainmenu, $leftmenu;
12342 global $mysoc;
12343 global $objectoffield; // To allow the use of $objectoffield in computed fields
12344 global $object;
12345
12346 // Old variables (deprecated since v23)
12347 if (getDolGlobalString('MAIN_ALLOW_OLD_VAR_OBJ_IN_DOL_EVAL')) {
12348 global $obj; // To get $obj used into list when dol_eval() is used for computed fields and $obj is not yet $objectoffield
12349 }
12350
12351 $isObBufferActive = false; // When true, the ObBuffer must be cleaned in the exception handler
12352 if ($onlysimplestring == '0') { // '0' is deprecated, we process it as the more secured '1'
12353 $onlysimplestring = '1';
12354 }
12355 if (!in_array($onlysimplestring, array('1', '2'))) {
12356 return "Bad call of dol_eval. Parameter onlysimplestring must be '1' or '2'.";
12357 }
12358 if (!is_scalar($s)) {
12359 return "Bad call of dol_eval. First parameter must be a string, found ".var_export($s, true);
12360 }
12361
12362 try {
12363 global $dolibarr_main_restrict_eval_methods;
12364
12365 // Set $dolibarr_main_restrict_eval_methods_array
12366 if (!isset($dolibarr_main_restrict_eval_methods)) {
12367 $dolibarr_main_restrict_eval_methods = 'getDolGlobalString, getDolGlobalInt, getDolCurrency, getDolEntity, getDolDBType, fetchNoCompute, hasRight, isAdmin, isExternalUser, isModEnabled, isStringVarMatching, abs, min, max, round, dol_now, preg_match';
12368 }
12369 //print '$dolibarr_main_restrict_eval_methods = '.$dolibarr_main_restrict_eval_methods."\n";
12370 $dolibarr_main_restrict_eval_methods_array = explode(',', str_replace(" ", "", $dolibarr_main_restrict_eval_methods));
12371
12372 // Test on dangerous char (used for RCE), we allow only characters to make PHP variable testing
12373 // We must accept with 1: '1 && getDolGlobalInt("doesnotexist1") && getDolGlobalString("MAIN_FEATURES_LEVEL")'
12374 // We must accept with 1: '$user->hasRight("cabinetmed", "read") && !$objectoffield->canvas == "patient@cabinetmed"'
12375 // 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"
12376
12377 // Check if there is dynamic call (first we check chars are all into a whitelist chars)
12378 $specialcharsallowed = '^$_+-.*>&|=!?():"\',/@';
12379 if ($onlysimplestring == '2') {
12380 $specialcharsallowed .= '<[]'; // Later we check that < has space before and after
12381 }
12382 global $dolibarr_main_allow_unsecured_special_chars_in_dol_eval;
12383 if (!empty($dolibarr_main_allow_unsecured_special_chars_in_dol_eval)) {
12384 $specialcharsallowed .= (string) $dolibarr_main_allow_unsecured_special_chars_in_dol_eval;
12385 }
12386 if (preg_match('/[^a-z0-9\s' . preg_quote($specialcharsallowed, '/') . ']/i', $s)) {
12387 return 'Bad string syntax to evaluate (found chars that are not chars for a simple one line clean eval string): ' . $s;
12388 }
12389
12390 // Check if we found a | without a space before and after
12391 /* Disabled to allow preg_match('/(AAA|BBB)/')
12392 $tmps = str_replace(' || ', '__XXX__', $s);
12393 if (strpos($tmps, '|') !== false) {
12394 return 'Bad string syntax to evaluate (The char | can be used only when duplicated || with a space before and after): ' . $s;
12395 }
12396 */
12397
12398 // Check if there is PHP comments (can be used to obfuscate code)
12399 if (strpos($s, '/*') !== false || strpos($s, '//') !== false) {
12400 return 'Bad string syntax to evaluate (The comment string /* and // are not allowed): ' . $s;
12401 }
12402
12403 // Check if we found a ? without a space before and after
12404 $tmps = str_replace(' ? ', '__XXX__', $s);
12405 if (strpos($tmps, '?') !== false) {
12406 return 'Bad string syntax to evaluate (The char ? can be used only with a space before and after): ' . $s;
12407 }
12408
12409 // Check if there is a < or <= without spaces after
12410 if (preg_match('/<=?[^\s]/', $s)) {
12411 return 'Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found a < or <= without space after): ' . $s;
12412 }
12413
12414 // Check if there is an include or a require
12415 if (preg_match('/(include|include_once|require|require_once)/', $s)) {
12416 return 'Bad string syntax to evaluate (found not allowed key include|include_once|require|require_once): ' . $s;
12417 }
12418
12419 // Check if there is dynamic call (first we use black list patterns)
12420 if (preg_match('/\$[\w]*\s*\‍(/', $s)) {
12421 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;
12422 }
12423
12424 if (empty($dolibarr_main_restrict_eval_methods)) {
12425 // If $dolibarr_main_restrict_eval_methods was set to '', we must check if we try dynamic call
12426
12427 // First we remove white list pattern of using parenthesis then testing if one open parenthesis exists
12428 $savescheck = '';
12429 $scheck = $s;
12430 while ($scheck && $savescheck != $scheck) {
12431 $savescheck = $scheck;
12432 $scheck = preg_replace('/->[a-zA-Z0-9_]+\‍(/', '->__METHOD__', $scheck); // accept parenthesis in '...->method(...'
12433 $scheck = preg_replace('/::[a-zA-Z0-9_]+\‍(/', '->__METHOD__', $scheck); // accept parenthesis in '...::method(...'
12434 $scheck = preg_replace('/^\‍(+/', '__PARENTHESIS__ ', $scheck); // accept parenthesis in '(...'. Must replace with "__PARENTHESIS__ with a space after "to allow following substitutions
12435 $scheck = preg_replace('/\&\&\s+\‍(/', '__ANDPARENTHESIS__ ', $scheck); // accept parenthesis in '&& ('. Must replace with "__PARENTHESIS__ with a space after" to allow following substitutions
12436 $scheck = preg_replace('/\|\|\s+\‍(/', '__ORPARENTHESIS__ ', $scheck); // accept parenthesis in '|| ('. Must replace with "__PARENTHESIS__ with a space after" to allow following substitutions
12437 $scheck = preg_replace('/^!?[a-zA-Z0-9_]+\‍(/', '__FUNCTION__', $scheck); // accept parenthesis in 'function(' and '!function('
12438 $scheck = preg_replace('/\s!?[a-zA-Z0-9_]+\‍(/', '__FUNCTION__', $scheck); // accept parenthesis in '... function(' and '... !function('
12439 $scheck = preg_replace('/^!\‍(/', '__NOTANDPARENTHESIS__', $scheck); // accept parenthesis in '!('
12440 $scheck = preg_replace('/\s!\‍(/', ' __NOTANDPARENTHESIS__', $scheck); // accept parenthesis in '... !('
12441 $scheck = preg_replace('/(\^|\')\‍(/', '__REGEXSTART__', $scheck); // To allow preg_match('/^(aaa|bbb)/'... or isStringVarMatching('leftmenu', '(aaa|bbb)')
12442 }
12443 //print 'scheck='.$scheck." : ".strpos($scheck, '(')."<br>\n";
12444
12445 // Now test if it remains 1 open parenthesis.
12446 if (strpos($scheck, '(') !== false) {
12447 return 'Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found call of a function or method without using the direct name of the function): ' . $s;
12448 }
12449 }
12450
12451 if (strpos($s, '`') !== false) {
12452 return 'Bad string syntax to evaluate (backtick char is forbidden): ' . $s;
12453 }
12454
12455 // Disallow also concat operator
12456 if (!getDolGlobalString('MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL')) {
12457 if (preg_match('/[^0-9]+\.[^0-9]+/', $s)) { // We refuse . if not between 2 numbers
12458 return 'Bad string syntax to evaluate (dot char is forbidden if not strictly between 2 numbers): ' . $s;
12459 }
12460 }
12461
12462 // We exclude string using a $ character that are not an expected global or temporary vars, so that are not:
12463 // $db, $langs, $leftmenu, $topmenu, $user, $langs, $objectoffield, $var....
12464 $savescheck = '';
12465 $scheck = $s;
12466 while ($scheck && $savescheck != $scheck) {
12467 $savescheck = $scheck;
12468 $scheck = preg_replace('/\$conf->[a-z\_]+->enabled/', '__VARCONFENABLED__', $scheck); // Remove this once $user->module->enabled has been replaced everywhere with isModEnabled.
12469 $scheck = preg_replace('/\$user->id/', '__VARUSERID__', $scheck);
12470 $scheck = preg_replace('/\$user->hasRight/', '__VARUSERHASRIGHT__', $scheck);
12471 $scheck = preg_replace('/\$user->rights/', '__VARUSERHASRIGHT__', $scheck); // Remove this once $user->rights->xxx is replaced everywhere with $user->hasRight()
12472 $scheck = preg_replace('/\$user->isAdmin/', '__VARUSERHASRIGHT__', $scheck);
12473 $scheck = preg_replace('/\$user->admin/', '__VARUSERISADMIN__', $scheck); // Remove this once $user->admin is replaced everywhere with $user->isAdmin()
12474 $scheck = preg_replace('/\$user->isExternalUser/', '__VARUSERSOCID__', $scheck);
12475 $scheck = preg_replace('/\$user->socid/', '__VARUSERSOCID__', $scheck); // Remove this once $user->admin is replaced everywhere with $user->isExternalUser()
12476 $scheck = preg_replace('/\‍(\$db\‍)/', '__VARDB__', $scheck);
12477 $scheck = preg_replace('/\$langs/', '__VARLANGSTRANS__', $scheck);
12478 $scheck = preg_replace('/\$mysoc/', '__VARMYSOC__', $scheck);
12479 $scheck = preg_replace('/\$action/', '__VARACTION__', $scheck);
12480 $scheck = preg_replace('/\$mainmenu/', '__VARMAINMENU__', $scheck); // Remove this once all tests on $mainmenu has been replaced with isStringVarMatching
12481 $scheck = preg_replace('/\$leftmenu/', '__VARLEFTMENU__', $scheck); // Remove this once all tests on $mainmenu has been replaced with isStringVarMatching
12482 $scheck = preg_replace('/\$websitepage/', '__VARWEBSITEPAGE__', $scheck);
12483 $scheck = preg_replace('/\$website/', '__VARWEBSITE__', $scheck);
12484 $scheck = preg_replace('/\$objectoffield/', '__VAROBJECTOFFIELD__', $scheck);
12485 $scheck = preg_replace('/\$object/', '__VAROBJECT__', $scheck);
12486 $scheck = preg_replace('/\$var/', '__VARVAR__', $scheck);
12487
12488 // deprecated (now we use $objecf->canvas or $objectoffield->canvas)
12489 $scheck = preg_replace('/\$soc->canvas/', '__VARSOCCANVAS__', $scheck);
12490 $scheck = preg_replace('/\$obj->canvas/', '__VAROBJCANVAS__', $scheck);
12491
12492 // Now test if it remains one '$'
12493 if (strpos($scheck, '$') !== false) {
12494 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);
12495 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;
12496 }
12497 }
12498
12499 // We block use of php exec or php file functions
12500 $forbiddenphpstrings = array('_ENV', '_SESSION', '_COOKIE', '_GET', '_GLOBAL', '_POST', '_REQUEST', 'ReflectionFunction', 'SplFileObject', 'SplTempFileObject');
12501
12502 if (empty($dolibarr_main_restrict_eval_methods)) { // If forced to ''
12503 // 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)
12504 // 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
12505 // like we can do with array_map and its callable parameter: dol_eval('json_encode(array_map(implode("",["ex","ec"]), ["id"]))', 1, 1, '0')
12506 $forbiddenphpfunctions = array();
12507 $forbiddenphpmethods = array();
12508
12509 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("override_function", "session_id", "session_create_id", "session_regenerate_id"));
12510 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("get_defined_functions", "get_defined_vars", "get_defined_constants", "get_declared_classes"));
12511 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("function"));
12512
12513 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("GETPOST")); // native dolibarr functions
12514
12515 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("ob_start"));
12516
12517 // Functions with callable parameters
12518 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("call_user_func", "call_user_func_array"));
12519 $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"));
12520 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("usort", "uasort", "uksort"));
12521 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("preg_replace_callback", "preg_replace_callback_array", "header_register_callback"));
12522 $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"));
12523 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("spl_autoload_register", "spl_autoload_unregister", "iterator_apply", "session_set_save_handler"));
12524 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("forward_static_call", "forward_static_call_array", "register_postsend_function"));
12525 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("readline_completion_function", "readline_callback_handler_install"));
12526
12527 // Exec functions
12528 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("exec", "passthru", "shell_exec", "system", "proc_open", "popen"));
12529 $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"));
12530 $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", ));
12531 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("putenv", "dl", "apache_child_terminate", "apache_setenv"));
12532 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_eval", "dol_eval_new", "dol_eval_standard", "executeCLI", "verifCond", "dolEncrypt", "dolDecrypt")); // native dolibarr functions
12533 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("eval", "create_function", "assert", "mb_ereg_replace")); // function with eval capabilities
12534
12535 // Include functions
12536 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("require", "include", "require_once", "include_once"));
12537
12538 $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
12539 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("chdir", "dir", "fopen", "file", "file_exists", "file_get_contents", "file_put_contents", "fget", "fgetc", "fgetcsv", "flock", "fputs", "fputscsv", "fpassthru", "fscanf", "fseek", "fwrite", "is_file", "is_dir", "is_link", "mkdir", "opendir", "rmdir", "scandir", "symlink", "touch", "unlink", "umask"));
12540
12541 if (!getDolGlobalString('MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL')) { // We disallow all function that allow to obfuscate the real name of a function
12542 // @phpcs:ignore
12543 $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
12544 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_concat", "dol_concatdesc")); // native dolibarr functions
12545 }
12546 // Remove from blacklist the function that are into the whitelist
12547 /*foreach ($forbiddenphpfunctions as $key => $forbiddenphpfunction) {
12548 if (in_array($forbiddenphpfunction, $dolibarr_main_restrict_eval_methods_array)) {
12549 unset($forbiddenphpfunctions[$key]);
12550 }
12551 }*/
12552
12553 $forbiddenphpmethods = array_merge($forbiddenphpmethods, array('invoke', 'invokeArgs')); // Methods of ReflectionFunction to execute a function
12554 // Remove from blacklist the function that are into the whitelist
12555 /*foreach ($forbiddenphpmethods as $key => $forbiddenphpmethod) {
12556 if (in_array($forbiddenphpmethod, $dolibarr_main_restrict_eval_methods_array)) {
12557 unset($forbiddenphpmethods[$key]);
12558 }
12559 }*/
12560
12561 $forbiddenphpregex = 'global\s*\$';
12562 $forbiddenphpregex .= '|posix_[a-zA-Z0-9_]*|'; // All posix functions
12563 $forbiddenphpregex .= '\b(' . implode('|', $forbiddenphpfunctions) . ')\b';
12564
12565 $forbiddenphpmethodsregex = '->(' . implode('|', $forbiddenphpmethods) . ')';
12566
12567 // Now scan all forbidden patterns
12568 do {
12569 $oldstringtoclean = $s;
12570 $s = str_ireplace($forbiddenphpstrings, '__forbiddenstring__', $s);
12571 $s = preg_replace('/' . $forbiddenphpregex . '/i', '__forbiddenstring__', $s);
12572 $s = preg_replace('/' . $forbiddenphpmethodsregex . '/i', '__forbiddenstring__', $s);
12573 //$s = preg_replace('/\$[a-zA-Z0-9_\->\$]+\‍(/i', '', $s); // Remove $function( call and $mycall->mymethod(
12574 } while ($oldstringtoclean != $s);
12575
12576 if (strpos($s, '__forbiddenstring__') !== false) {
12577 dol_syslog('Bad string syntax to evaluate: ' . $s, LOG_WARNING);
12578 return 'Bad string syntax to evaluate: ' . $s;
12579 }
12580 }
12581
12582 if (!empty($dolibarr_main_restrict_eval_methods)) {
12583 // Accept only white-listed allowed function and classes
12584 // TODO Get all pattern '/([\s\w]+)\‍(/', then check that $matches[1] is a defined class or a function into a given list
12585 $pattern = '/([\s\w\'\]\"]+)\‍(/';
12586
12587 $matches = array();
12588 preg_match_all($pattern, $s, $matches);
12589
12590 if (count($matches)) {
12591 foreach ($matches[1] as $m) {
12592 $m = trim($m);
12593 if (empty($m)) {
12594 continue;
12595 }
12596 $reg = array();
12597 if (!preg_match('/new ([A-Z][\w]+)/i', $m, $reg)) {
12598 if (!in_array($m, $dolibarr_main_restrict_eval_methods_array)) {
12599 if ($m != "'" && $m != '"') {
12600 dol_syslog('Bad string syntax to evaluate: ' . $s, LOG_WARNING);
12601 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;
12602 }
12603 }
12604 } else {
12605 if (!class_exists($reg[1])) {
12606 dol_syslog('Bad string syntax to evaluate: Class "'.$reg[1].'" does not exist. ' . $s, LOG_WARNING);
12607 return 'Bad string syntax to evaluate. Class "'.$reg[1].'" does not exist. ' . $s;
12608 }
12609 $parents = class_parents($reg[1]); // Get list of parent classes of class we want to check
12610 if (!in_array('CommonObject', $parents)) { // Only classes that inherit CommonObject are ok. This forbid dangerous classes like ReflectionFunction, SplFileObject, ...
12611 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);
12612 return 'Bad string syntax to evaluate. Class "'.$reg[1].'" is not allowed because only classes extended CommonObject can be used in dynamic evaluation. ' . $s;
12613 }
12614 }
12615 }
12616 }
12617
12618 $forbiddenphpregex = 'global\s*\$';
12619 $forbiddenphpregex .= '|'; // or
12620 $forbiddenphpregex .= '}\s*\[';
12621 $forbiddenphpregex .= '|'; // or
12622 $forbiddenphpregex .= '\‍)\s*\‍(';
12623
12624 // Now scan all forbidden patterns
12625 do {
12626 $oldstringtoclean = $s;
12627 $s = str_ireplace($forbiddenphpstrings, '__forbiddenstring__', $s);
12628 $s = preg_replace('/' . $forbiddenphpregex . '/i', '__forbiddenstring__', $s);
12629 //$s = preg_replace('/' . $forbiddenphpmethodsregex . '/i', '__forbiddenstring__', $s);
12630 //$s = preg_replace('/\$[a-zA-Z0-9_\->\$]+\‍(/i', '', $s); // Remove $function( call and $mycall->mymethod(
12631 } while ($oldstringtoclean != $s);
12632
12633 if (strpos($s, '__forbiddenstring__') !== false) {
12634 dol_syslog('Bad string syntax to evaluate: ' . $s, LOG_WARNING);
12635 return 'Bad string syntax to evaluate: ' . $s;
12636 }
12637 }
12638
12639 //print $s."<br>\n";
12640 ob_start(); // An evaluation has no reason to output data
12641 $isObBufferActive = true;
12642 $tmps = $hideerrors ? @eval('return ' . $s . ';') : eval('return ' . $s . ';');
12643 $tmpo = ob_get_clean(); // This close the buffer
12644 $isObBufferActive = false;
12645 if ($tmpo) {
12646 print 'Bad string syntax to evaluate. Some data were output when it should not when evaluating: ' . $s;
12647 }
12648 return $tmps;
12649 } catch (Exception $e) {
12650 if ($isObBufferActive) {
12651 // Clean up buffer which was left behind due to exception.
12652 $tmpo = ob_get_clean(); // This close the buffer
12653 $isObBufferActive = false;
12654 }
12655 $error = 'dol_eval try/catch error for string: ' . $s . ' - Error: ';
12656 $error .= $e->getMessage();
12657 dol_syslog($error, LOG_WARNING);
12658 return 'Exception during evaluation: ' . $s;
12659 } catch (Error $e) {
12660 if ($isObBufferActive) {
12661 // Clean up buffer which was left behind due to exception.
12662 $tmpo = ob_get_clean(); // This close the buffer
12663 $isObBufferActive = false;
12664 }
12665 $error = 'dol_eval try/catch error for string: ' . $s . ' - Error: ';
12666 $error .= $e->getMessage();
12667 dol_syslog($error, LOG_WARNING);
12668 return 'Exception during evaluation: ' . $s;
12669 }
12670}
12671
12679function dol_validElement($element)
12680{
12681 return (trim($element) != '');
12682}
12683
12692function picto_from_langcode($codelang, $moreatt = '', $notitlealt = 0)
12693{
12694 if (empty($codelang)) {
12695 return '';
12696 }
12697
12698 if ($codelang == 'auto') {
12699 return '<span class="fa fa-language"></span>';
12700 }
12701
12702 $langtocountryflag = array(
12703 'ar_AR' => '',
12704 'ca_ES' => 'catalonia',
12705 'da_DA' => 'dk',
12706 'fr_CA' => 'mq',
12707 'sv_SV' => 'se',
12708 'sw_SW' => 'unknown',
12709 'AQ' => 'unknown',
12710 'CW' => 'unknown',
12711 'IM' => 'unknown',
12712 'JE' => 'unknown',
12713 'MF' => 'unknown',
12714 'BL' => 'unknown',
12715 'SX' => 'unknown'
12716 );
12717
12718 if (isset($langtocountryflag[$codelang])) {
12719 $flagImage = $langtocountryflag[$codelang];
12720 } else {
12721 $tmparray = explode('_', $codelang);
12722 $flagImage = empty($tmparray[1]) ? $tmparray[0] : $tmparray[1];
12723 }
12724
12725 $morecss = '';
12726 $reg = array();
12727 if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
12728 $morecss = $reg[1];
12729 $moreatt = "";
12730 }
12731
12732 // return img_picto_common($codelang, 'flags/'.strtolower($flagImage).'.png', $moreatt, 0, $notitlealt);
12733 return '<span class="flag-sprite ' . strtolower($flagImage) . ($morecss ? ' ' . $morecss : '') . '"' . ($moreatt ? ' ' . $moreatt : '') . (!$notitlealt ? ' title="' . $codelang . '"' : '') . '></span>';
12734}
12735
12743function getLanguageCodeFromCountryCode($countrycode)
12744{
12745 global $mysoc;
12746
12747 if (empty($countrycode)) {
12748 return null;
12749 }
12750
12751 if (strtoupper($countrycode) == 'MQ') {
12752 return 'fr_CA';
12753 }
12754 if (strtoupper($countrycode) == 'SE') {
12755 return 'sv_SE'; // se_SE is Sami/Sweden, and we want in priority sv_SE for SE country
12756 }
12757 if (strtoupper($countrycode) == 'CH') {
12758 if ($mysoc->country_code == 'FR') {
12759 return 'fr_CH';
12760 }
12761 if ($mysoc->country_code == 'DE') {
12762 return 'de_CH';
12763 }
12764 if ($mysoc->country_code == 'IT') {
12765 return 'it_CH';
12766 }
12767 }
12768
12769 // Locale list taken from:
12770 // http://stackoverflow.com/questions/3191664/
12771 // list-of-all-locales-and-their-short-codes
12772 $locales = array(
12773 'af-ZA',
12774 'am-ET',
12775 'ar-AE',
12776 'ar-BH',
12777 'ar-DZ',
12778 'ar-EG',
12779 'ar-IQ',
12780 'ar-JO',
12781 'ar-KW',
12782 'ar-LB',
12783 'ar-LY',
12784 'ar-MA',
12785 'ar-OM',
12786 'ar-QA',
12787 'ar-SA',
12788 'ar-SY',
12789 'ar-TN',
12790 'ar-YE',
12791 //'as-IN', // Moved after en-IN
12792 'ba-RU',
12793 'be-BY',
12794 'bg-BG',
12795 'bn-BD',
12796 //'bn-IN', // Moved after en-IN
12797 'bo-CN',
12798 'br-FR',
12799 'ca-ES',
12800 'co-FR',
12801 'cs-CZ',
12802 'cy-GB',
12803 'da-DK',
12804 'de-AT',
12805 'de-CH',
12806 'de-DE',
12807 'de-LI',
12808 'de-LU',
12809 'dv-MV',
12810 'el-GR',
12811 'en-AU',
12812 'en-BZ',
12813 'en-CA',
12814 'en-GB',
12815 'en-IE',
12816 'en-IN',
12817 'as-IN', // as-IN must be after en-IN (en in priority if country is IN)
12818 'bn-IN', // bn-IN must be after en-IN (en in priority if country is IN)
12819 'en-JM',
12820 'en-MY',
12821 'en-NZ',
12822 'en-PH',
12823 'en-SG',
12824 'en-TT',
12825 'en-US',
12826 'en-ZA',
12827 'en-ZW',
12828 'es-AR',
12829 'es-BO',
12830 'es-CL',
12831 'es-CO',
12832 'es-CR',
12833 'es-DO',
12834 'es-EC',
12835 'es-ES',
12836 'es-GT',
12837 'es-HN',
12838 'es-MX',
12839 'es-NI',
12840 'es-PA',
12841 'es-PE',
12842 'es-PR',
12843 'es-PY',
12844 'es-SV',
12845 'es-US',
12846 'es-UY',
12847 'es-VE',
12848 'et-EE',
12849 'eu-ES',
12850 'fa-IR',
12851 'fi-FI',
12852 'fo-FO',
12853 'fr-BE',
12854 'fr-CA',
12855 'fr-CH',
12856 'fr-FR',
12857 'fr-LU',
12858 'fr-MC',
12859 'fy-NL',
12860 'ga-IE',
12861 'gd-GB',
12862 'gl-ES',
12863 'gu-IN',
12864 'he-IL',
12865 'hi-IN',
12866 'hr-BA',
12867 'hr-HR',
12868 'hu-HU',
12869 'hy-AM',
12870 'id-ID',
12871 'ig-NG',
12872 'ii-CN',
12873 'is-IS',
12874 'it-CH',
12875 'it-IT',
12876 'ja-JP',
12877 'ka-GE',
12878 'kk-KZ',
12879 'kl-GL',
12880 'km-KH',
12881 'kn-IN',
12882 'ko-KR',
12883 'ky-KG',
12884 'lb-LU',
12885 'lo-LA',
12886 'lt-LT',
12887 'lv-LV',
12888 'mi-NZ',
12889 'mk-MK',
12890 'ml-IN',
12891 'mn-MN',
12892 'mr-IN',
12893 'ms-BN',
12894 'ms-MY',
12895 'mt-MT',
12896 'nb-NO',
12897 'ne-NP',
12898 'nl-BE',
12899 'nl-NL',
12900 'nn-NO',
12901 'oc-FR',
12902 'or-IN',
12903 'pa-IN',
12904 'pl-PL',
12905 'ps-AF',
12906 'pt-BR',
12907 'pt-PT',
12908 'rm-CH',
12909 'ro-MD',
12910 'ro-RO',
12911 'ru-RU',
12912 'rw-RW',
12913 'sa-IN',
12914 'se-FI',
12915 'se-NO',
12916 'se-SE',
12917 'si-LK',
12918 'sk-SK',
12919 'sl-SI',
12920 'sq-AL',
12921 'sv-FI',
12922 'sv-SE',
12923 'sw-KE',
12924 'ta-IN',
12925 'te-IN',
12926 'th-TH',
12927 'tk-TM',
12928 'tn-ZA',
12929 'tr-TR',
12930 'tt-RU',
12931 'ug-CN',
12932 'uk-UA',
12933 'ur-PK',
12934 'vi-VN',
12935 'wo-SN',
12936 'xh-ZA',
12937 'yo-NG',
12938 'zh-CN',
12939 'zh-HK',
12940 'zh-MO',
12941 'zh-SG',
12942 'zh-TW',
12943 'zu-ZA',
12944 );
12945
12946 $buildprimarykeytotest = strtolower($countrycode) . '-' . strtoupper($countrycode);
12947 if (in_array($buildprimarykeytotest, $locales)) {
12948 return strtolower($countrycode) . '_' . strtoupper($countrycode);
12949 }
12950
12951 if (function_exists('locale_get_primary_language') && function_exists('locale_get_region')) { // Need extension php-intl
12952 foreach ($locales as $locale) {
12953 $locale_language = locale_get_primary_language($locale);
12954 $locale_region = locale_get_region($locale);
12955 if (strtoupper($countrycode) == $locale_region) {
12956 //var_dump($locale.' - '.$locale_language.' - '.$locale_region);
12957 return strtolower($locale_language) . '_' . strtoupper($locale_region);
12958 }
12959 }
12960 } else {
12961 dol_syslog("Warning Extension php-intl is not available", LOG_WARNING);
12962 }
12963
12964 return null;
12965}
12966
12997function complete_head_from_modules($conf, $langs, $object, &$head, &$h, $type, $mode = 'add', $filterorigmodule = '')
12998{
12999 global $hookmanager, $db;
13000
13001 if (isset($conf->modules_parts['tabs'][$type]) && is_array($conf->modules_parts['tabs'][$type])) {
13002 foreach ($conf->modules_parts['tabs'][$type] as $value) {
13003 $values = explode(':', $value);
13004
13005 $reg = array();
13006 if ($mode == 'add' && !preg_match('/^\-/', $values[1])) {
13007 if (count($values) !== 6) {
13008 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);
13009 continue;
13010 }
13011
13012 // new declaration with permissions:
13013 // $value='objecttype:+tabname1:Title1:langfile@mymodule:$user->rights->mymodule->read:/mymodule/mynewtab1.php?id=__ID__'
13014 // $value='objecttype:+tabname1:Title1,class,pathfile,method:langfile@mymodule:$user->rights->mymodule->read:/mymodule/mynewtab1.php?id=__ID__'
13015 if ($values[0] != $type) {
13016 continue;
13017 }
13018
13019 $newtab = array();
13020 $postab = $h;
13021 // detect if position set in $values[1] ie : +(2)mytab@mymodule (first tab is 0, second is one, ...)
13022 $str = $values[1];
13023 $posstart = strpos($str, '(');
13024 if ($posstart > 0) {
13025 $posend = strpos($str, ')');
13026 if ($posstart > 0) {
13027 $res1 = substr($str, $posstart + 1, $posend - $posstart - 1);
13028 if (is_numeric($res1)) {
13029 $postab = (int) $res1;
13030 $values[1] = '+' . substr($str, $posend + 1);
13031 }
13032 }
13033 }
13034
13035 global $objectoffield; // So we can use $objectoffield int verifCond
13036 $objectoffield = $object;
13037
13038 if (!verifCond($values[4], '2')) {
13039 continue;
13040 }
13041
13042 if ($values[3]) {
13043 if ($filterorigmodule) { // If a filter of module origin has been requested
13044 if (strpos($values[3], '@')) { // This is an external module
13045 if ($filterorigmodule != 'external') {
13046 continue;
13047 }
13048 } else { // This looks a core module
13049 if ($filterorigmodule != 'core') {
13050 continue;
13051 }
13052 }
13053 }
13054 $langs->load($values[3]);
13055 }
13056
13057 if (preg_match('/SUBSTITUTION_([^_]+)/i', $values[2], $reg)) {
13058 // If label is "SUBSTITUION_..."
13059 $substitutionarray = array();
13060 complete_substitutions_array($substitutionarray, $langs, $object, array('needforkey' => $values[2]));
13061 $label = make_substitutions($reg[1], $substitutionarray);
13062 } else {
13063 // If label is "Label,Class,File,Method", we call the method to show content inside the badge
13064 $labeltemp = explode(',', $values[2]);
13065 $label = $langs->trans($labeltemp[0]);
13066
13067 if (!empty($labeltemp[1]) && is_object($object) && !empty($object->id)) {
13068 dol_include_once($labeltemp[2]);
13069 $classtoload = $labeltemp[1];
13070 if (class_exists($classtoload)) {
13071 $obj = new $classtoload($db);
13072 $function = $labeltemp[3];
13073 if ($obj && $function && method_exists($obj, $function)) {
13074 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
13075 $nbrec = $obj->$function($object->id, $obj);
13076 if (!empty($nbrec)) {
13077 $label .= '<span class="badge marginleftonlyshort">' . $nbrec . '</span>';
13078 }
13079 }
13080 }
13081 }
13082 }
13083 $url = preg_replace('/__ID__/i', ((is_object($object) && !empty($object->id)) ? $object->id : ''), $values[5]);
13084 $link = parse_url($url);
13085 $query = [];
13086 if (isset($link['query'])) {
13087 parse_str($link['query'], $query);
13088 }
13089 $newtab[0] = dolBuildUrl(dol_buildpath($link['path'], 1), $query);
13090 $newtab[1] = $label;
13091 $newtab[2] = str_replace('+', '', $values[1]);
13092 $h++;
13093
13094 // set tab at its position
13095 $head = array_merge(array_slice($head, 0, $postab), array($newtab), array_slice($head, $postab));
13096 } elseif ($mode == 'remove' && preg_match('/^\-/', $values[1])) {
13097 if ($values[0] != $type) {
13098 continue;
13099 }
13100 $tabname = str_replace('-', '', $values[1]);
13101 foreach ($head as $key => $val) {
13102 $condition = (!empty($values[3]) ? verifCond($values[3], '2') : 1);
13103 //var_dump($key.' - '.$tabname.' - '.$head[$key][2].' - '.$values[3].' - '.$condition);
13104 if ($head[$key][2] == $tabname && $condition) {
13105 unset($head[$key]);
13106 break;
13107 }
13108 }
13109 }
13110 }
13111 }
13112
13113 // No need to make a return $head. Var is modified as a reference
13114 if (!empty($hookmanager)) {
13115 $parameters = array('object' => $object, 'mode' => $mode, 'head' => &$head, 'filterorigmodule' => $filterorigmodule, 'type' => $type);
13116 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
13117 $reshook = $hookmanager->executeHooks('completeTabsHead', $parameters, $object);
13118 if ($reshook > 0) { // Hook ask to replace completely the array
13119 $head = $hookmanager->resArray;
13120 } else { // Hook
13121 $head = array_merge($head, $hookmanager->resArray);
13122 }
13123 $h = count($head);
13124 }
13125}
13126
13138function printCommonFooter($zone = 'private')
13139{
13140 global $conf, $hookmanager, $user, $langs;
13141 global $action;
13142 global $micro_start_time;
13143
13144 if ($zone == 'private') {
13145 print "\n" . '<!-- Common footer for private page -->' . "\n";
13146 } else {
13147 print "\n" . '<!-- Common footer for public page -->' . "\n";
13148 }
13149
13150 // A div to store page_y POST parameter so we can read it using javascript
13151 print "\n<!-- A div to store page_y POST parameter -->\n";
13152 print '<div id="page_y" style="display: none;">' . (GETPOST('page_y') ? GETPOST('page_y') : '') . '</div>' . "\n";
13153
13154 $parameters = array('zone' => $zone);
13155 $tmpobject = null;
13156 // @phan-suppress-next-line PhanPluginConstantVariableNull
13157 $reshook = $hookmanager->executeHooks('printCommonFooter', $parameters, $tmpobject, $action); // Note that $action and $object may have been modified by some hooks
13158 if (empty($reshook)) {
13159 if (getDolGlobalString('MAIN_HTML_FOOTER')) {
13160 print getDolGlobalString('MAIN_HTML_FOOTER') . "\n";
13161 }
13162
13163 print "\n";
13164 if (!empty($conf->use_javascript_ajax)) {
13165 print "\n<!-- A script section to add menuhider handler on backoffice, manage focus and mandatory fields, tuning info, ... -->\n";
13166 print '<script>' . "\n";
13167 print 'jQuery(document).ready(function() {' . "\n";
13168
13169 if ($zone == 'private' && empty($conf->dol_use_jmobile)) {
13170 print "\n";
13171 print '/* JS CODE TO ENABLE to manage handler to switch left menu page (menuhider) */' . "\n";
13172 print 'jQuery("li.menuhider").click(function(event) {';
13173 print ' if (!$( "body" ).hasClass( "sidebar-collapse" )){ event.preventDefault(); }' . "\n";
13174 print ' console.log("We click on .menuhider");' . "\n";
13175 print ' $("body").toggleClass("sidebar-collapse")' . "\n";
13176 print '});' . "\n";
13177 }
13178
13179 // Management of focus and mandatory for fields
13180 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"])))) {
13181 print '/* JS CODE TO ENABLE to manage focus and mandatory form fields */' . "\n";
13182 $relativepathstring = $_SERVER["PHP_SELF"];
13183 // Clean $relativepathstring
13184 if (constant('DOL_URL_ROOT')) {
13185 $relativepathstring = preg_replace('/^' . preg_quote(constant('DOL_URL_ROOT'), '/') . '/', '', $relativepathstring);
13186 }
13187 $relativepathstring = preg_replace('/^\//', '', $relativepathstring);
13188 $relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
13189 //$tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
13190
13191 if (!empty($user->default_values[$relativepathstring]['focus'])) {
13192 foreach ($user->default_values[$relativepathstring]['focus'] as $defkey => $defval) {
13193 $qualified = 0;
13194 if ($defkey != '_noquery_') {
13195 $tmpqueryarraytohave = explode('&', $defkey);
13196 $foundintru = 0;
13197 foreach ($tmpqueryarraytohave as $tmpquerytohave) {
13198 $tmpquerytohaveparam = explode('=', $tmpquerytohave);
13199 //print "console.log('".$tmpquerytohaveparam[0]." ".$tmpquerytohaveparam[1]." ".GETPOST($tmpquerytohaveparam[0])."');";
13200 if (!GETPOSTISSET($tmpquerytohaveparam[0]) || ($tmpquerytohaveparam[1] != GETPOST($tmpquerytohaveparam[0]))) {
13201 $foundintru = 1;
13202 }
13203 }
13204 if (!$foundintru) {
13205 $qualified = 1;
13206 }
13207 //var_dump($defkey.'-'.$qualified);
13208 } else {
13209 $qualified = 1;
13210 }
13211
13212 if ($qualified) {
13213 print 'console.log("set the focus by executing jQuery(...).focus();")' . "\n";
13214 foreach ($defval as $paramkey => $paramval) {
13215 // Set focus on field
13216 print 'jQuery("input[name=\'' . $paramkey . '\']").focus();' . "\n";
13217 print 'jQuery("textarea[name=\'' . $paramkey . '\']").focus();' . "\n"; // TODO KO with ckeditor
13218 print 'jQuery("select[name=\'' . $paramkey . '\']").focus();' . "\n"; // Not really useful, but we keep it in case of.
13219 }
13220 }
13221 }
13222 }
13223 if (!empty($user->default_values[$relativepathstring]['mandatory'])) {
13224 foreach ($user->default_values[$relativepathstring]['mandatory'] as $defkey => $defval) {
13225 $qualified = 0;
13226 if ($defkey != '_noquery_') {
13227 $tmpqueryarraytohave = explode('&', $defkey);
13228 $foundintru = 0;
13229 foreach ($tmpqueryarraytohave as $tmpquerytohave) {
13230 $tmpquerytohaveparam = explode('=', $tmpquerytohave);
13231 //print "console.log('".$tmpquerytohaveparam[0]." ".$tmpquerytohaveparam[1]." ".GETPOST($tmpquerytohaveparam[0])."');";
13232 if (!GETPOSTISSET($tmpquerytohaveparam[0]) || ($tmpquerytohaveparam[1] != GETPOST($tmpquerytohaveparam[0]))) {
13233 $foundintru = 1;
13234 }
13235 }
13236 if (!$foundintru) {
13237 $qualified = 1;
13238 }
13239 //var_dump($defkey.'-'.$qualified);
13240 } else {
13241 $qualified = 1;
13242 }
13243
13244 if ($qualified) {
13245 print 'console.log("set the js code to manage fields that are set as mandatory");' . "\n";
13246
13247 foreach ($defval as $paramkey => $paramval) {
13248 // Solution 1: Add handler on submit to check if mandatory fields are empty
13249 print 'var form = $(\'[name="'.dol_escape_js($paramkey).'"]\').closest("form");'."\n";
13250 print "form.on('submit', function(event) {
13251 var submitter = \$(this).find(':submit:focus').get(0);
13252 var buttonName = submitter ? \$(submitter).attr('name') : 'save';
13253
13254 if (buttonName == 'cancel') {
13255 console.log('We click on cancel button so we accept submit with no need to check mandatory fields');
13256 return true;
13257 }
13258
13259 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');
13260
13261 var tmpvalue = jQuery('[name=\"".dol_escape_js($paramkey)."\"]').val();
13262 let tmptypefield = jQuery('[name=\"".dol_escape_js($paramkey)."\"]').prop('nodeName').toLowerCase(); // Get the tag name (div, section, footer...)
13263
13264 if (tmptypefield == 'textarea') {
13265 // We must instead check the content of ckeditor
13266 var tmpeditor = (typeof CKEDITOR !== 'undefined') ? CKEDITOR.instances['".dol_escape_js($paramkey)."'] : null;
13267 if (tmpeditor) {
13268 tmpvalue = tmpeditor.getData();
13269 console.log('For textarea tmpvalue is '+tmpvalue);
13270 }
13271 }
13272
13273 let tmpvalueisempty = false;
13274 if (tmpvalue === null || tmpvalue === undefined || tmpvalue === '' || tmpvalue === -1 || tmpvalue === '-1') {
13275 tmpvalueisempty = true;
13276 }
13277 if (tmpvalue === '0' && (tmptypefield == 'select' || tmptypefield == 'input')) {
13278 tmpvalueisempty = true;
13279 }
13280 if (tmpvalueisempty && buttonName !== 'cancel') {
13281 console.log('field has type '+tmptypefield+' and is empty, we cancel the submit');
13282 event.preventDefault(); // Stop submission of form to allow custom code to decide.
13283 event.stopPropagation(); // Stop other handlers.
13284
13285 alert('".dol_escape_js($langs->transnoentitiesnoconv("ErrorFieldRequired", $paramkey).' ('.$langs->transnoentitiesnoconv("CustomMandatoryFieldRule").')')."');
13286
13287 return false;
13288 }
13289 console.log('field has type '+tmptypefield+' and is defined to '+tmpvalue);
13290 return true;
13291 });
13292 \n";
13293
13294 // Solution 2: Add property 'required' on input
13295 // so browser will check value and try to focus on it when submitting the form.
13296 //print 'setTimeout(function() {'; // If we want to wait that ckeditor beuatifier has finished its job.
13297 //print 'jQuery("input[name=\''.$paramkey.'\']").prop(\'required\',true);'."\n";
13298 //print 'jQuery("textarea[id=\''.$paramkey.'\']").prop(\'required\',true);'."\n";
13299 //print 'jQuery("select[name=\''.$paramkey.'\']").prop(\'required\',true);'."\n";*/
13300 //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";
13301 //print 'jQuery("select[name=\''.$paramkey.'\'] option[value=\'-1\']").prop(\'value\', \'\');'."\n";
13302 //print 'jQuery("select[name=\''.$paramkey.'\'] option[value=\'0\']").prop(\'value\', \'\');'."\n";
13303 // Add 'field required' class on closest td for all input elements : input, textarea and select
13304 //print '}, 500);'; // 500 milliseconds delay
13305
13306 // Now set the class "fieldrequired"
13307 print 'jQuery(\':input[name="' . dol_escape_js($paramkey) . '"]\').closest("tr").find("td:first").addClass("fieldrequired");' . "\n";
13308 }
13309
13310 // If we submit using the cancel button, we remove the required attributes
13311 print 'jQuery("input[name=\'cancel\']").click(function() {
13312 console.log("We click on cancel button so removed all required attribute");
13313 jQuery("input, textarea, select").each(function(){this.removeAttribute(\'required\');});
13314 });' . "\n";
13315 }
13316 }
13317 }
13318 }
13319
13320 print '});' . "\n";
13321
13322 // End of tuning
13323 if (!empty($_SERVER['MAIN_SHOW_TUNING_INFO']) || getDolGlobalString('MAIN_SHOW_TUNING_INFO')) {
13324 print "\n";
13325 print "/* JS CODE TO ENABLE to add memory info */\n";
13326 print 'window.console && console.log("';
13327 if (getDolGlobalString('MEMCACHED_SERVER')) {
13328 print 'MEMCACHED_SERVER=' . getDolGlobalString('MEMCACHED_SERVER') . ' - ';
13329 }
13330 print 'MAIN_OPTIMIZE_SPEED=' . getDolGlobalString('MAIN_OPTIMIZE_SPEED', 'off');
13331 if (!empty($micro_start_time)) { // Works only if MAIN_SHOW_TUNING_INFO is defined at $_SERVER level. Not in global variable.
13332 $micro_end_time = microtime(true);
13333 print ' - Build time: ' . ceil(1000 * ($micro_end_time - $micro_start_time)) . ' ms';
13334 }
13335
13336 if (function_exists("memory_get_usage")) {
13337 print ' - Mem: ' . memory_get_usage(); // Do not use true here, it seems it takes the peak amount
13338 }
13339 if (function_exists("memory_get_peak_usage")) {
13340 print ' - Real mem peak: ' . memory_get_peak_usage(true);
13341 }
13342 if (function_exists("zend_loader_file_encoded")) {
13343 print ' - Zend encoded file: ' . (zend_loader_file_encoded() ? 'yes' : 'no');
13344 }
13345 print '");' . "\n";
13346 }
13347
13348 print "\n" . '</script>' . "\n";
13349
13350 // Google Analytics
13351 // TODO Remove this, can be replaced with the hook printCommonFooter
13352 if (isModEnabled('google') && getDolGlobalString('MAIN_GOOGLE_AN_ID')) {
13353 $tmptagarray = explode(',', getDolGlobalString('MAIN_GOOGLE_AN_ID'));
13354 foreach ($tmptagarray as $tmptag) {
13355 print "\n";
13356 print "<!-- JS CODE TO ENABLE for google analtics tag -->\n";
13357 print '
13358 <!-- Global site tag (gtag.js) - Google Analytics -->
13359 <script nonce="' . getNonce() . '" async src="https://www.googletagmanager.com/gtag/js?id=' . trim($tmptag) . '"></script>
13360 <script>
13361 window.dataLayer = window.dataLayer || [];
13362 function gtag(){dataLayer.push(arguments);}
13363 gtag(\'js\', new Date());
13364
13365 gtag(\'config\', \'' . trim($tmptag) . '\');
13366 </script>';
13367 print "\n";
13368 }
13369 }
13370 }
13371
13372 // Add Xdebug coverage of code
13373 if (defined('XDEBUGCOVERAGE')) {
13374 print_r(xdebug_get_code_coverage());
13375 }
13376
13377 // Output string from hooks
13378 if (!empty($hookmanager->resPrint)) {
13379 print $hookmanager->resPrint;
13380 }
13381
13382 // Add DebugBar data
13383 if ($user->hasRight('debugbar', 'read')) {
13384 global $debugbar;
13385 if ($debugbar instanceof DebugBar\DebugBar) {
13386 if (isset($debugbar['time'])) {
13387 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
13388 $debugbar['time']->stopMeasure('pageaftermaster');
13389 }
13390 print '<!-- Output debugbar data -->' . "\n";
13391 $renderer = $debugbar->getJavascriptRenderer();
13392 print $renderer->render();
13393 }
13394 } elseif (count($conf->logbuffer)) { // If there is some logs in buffer to show
13395 print "\n";
13396 print "<!-- Start of log output\n";
13397 //print '<div class="hidden">'."\n";
13398 foreach ($conf->logbuffer as $logline) {
13399 print $logline . "<br>\n";
13400 }
13401 //print '</div>'."\n";
13402 print "End of log output -->\n";
13403 }
13404 }
13405}
13406
13416function dolExplodeIntoArray($string, $delimiter = ';', $kv = '=')
13417{
13418 if (is_null($string)) {
13419 return array();
13420 }
13421
13422 if (preg_match('/^\[.*\]$/sm', $delimiter) || preg_match('/^\‍(.*\‍)$/sm', $delimiter)) {
13423 // This is a regex string
13424 $newdelimiter = $delimiter;
13425 } else {
13426 // This is a simple string
13427 // @phan-suppress-next-line PhanPluginSuspiciousParamPositionInternal
13428 $newdelimiter = preg_quote($delimiter, '/');
13429 }
13430
13431 if ($a = preg_split('/' . $newdelimiter . '/', $string)) {
13432 $ka = array();
13433 foreach ($a as $s) { // each part
13434 if ($s) {
13435 if ($pos = strpos($s, $kv)) { // key/value delimiter
13436 $ka[trim(substr($s, 0, $pos))] = trim(substr($s, $pos + strlen($kv)));
13437 } else { // key delimiter not found
13438 $ka[] = trim($s);
13439 }
13440 }
13441 }
13442 return $ka;
13443 }
13444
13445 return array();
13446}
13447
13455function dolExplodeKeepIfQuotes($input)
13456{
13457 // Use regexp to capture words and section in quotes
13458 $matches = array();
13459 preg_match_all('/"([^"]*)"|\'([^\']*)\'|(\S+)/', $input, $matches);
13460
13461 // Merge result and delete empty values
13462
13463 $result = array_map(
13470 static function ($a, $b, $c) {
13471 if ($a !== '') {
13472 return $a;
13473 }
13474 if ($b !== '') {
13475 return $b;
13476 }
13477 if ($c !== '') {
13478 return $c;
13479 }
13480 return '';
13481 },
13482 $matches[1],
13483 $matches[2],
13484 $matches[3]
13485 );
13486 return array_values(array_filter(
13487 $result,
13494 static function ($val) {
13495 return $val !== '';
13496 }
13497 ));
13498}
13499
13500
13507function dol_set_focus($selector)
13508{
13509 print "\n" . '<!-- Set focus onto a specific field -->' . "\n";
13510 print '<script nonce="' . getNonce() . '">jQuery(document).ready(function() { console.log("Force focus by dol_set_focus"); jQuery("' . dol_escape_js($selector) . '").focus(); });</script>' . "\n";
13511}
13512
13513
13521function dol_getmypid()
13522{
13523 if (!function_exists('getmypid')) {
13524 return mt_rand(99900000, 99965535);
13525 } else {
13526 return getmypid(); // May be a number on 64 bits (depending on OS)
13527 }
13528}
13529
13552function natural_search($fields, $value, $mode = 0, $nofirstand = 0, $sqltoadd = '')
13553{
13554 global $db, $langs;
13555
13556 $value = trim($value);
13557
13558 if ($mode == 0) {
13559 $value = preg_replace('/\*/', '%', $value); // Replace * with %
13560 }
13561 if ($mode == 1) {
13562 $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
13563 }
13564
13565 $value = preg_replace('/\s*\|\s*/', '|', $value);
13566
13567 // Split criteria on ' ' but not if we are inside quotes.
13568 // For mode 3, the split is done later on the , only and not on the ' '.
13569 if ($mode != -3 && $mode != 3) {
13570 $crits = dolExplodeKeepIfQuotes($value);
13571 } else {
13572 $crits = array($value);
13573 }
13574
13575 $res = '';
13576 if (!is_array($fields)) {
13577 $fields = array($fields);
13578 }
13579 $i1 = 0; // count the nb of "and" criteria added (all fields / criteria)
13580 foreach ($crits as $crit) { // Loop on each AND criteria
13581 $crit = trim($crit);
13582 $i2 = 0; // count the nb of valid criteria added for this this first criteria
13583 $newres = '';
13584
13585 foreach ($fields as $field) {
13586 if ($mode == 1) {
13587 $tmpcrits = explode('|', $crit);
13588 $i3 = 0; // count the nb of valid criteria added for this current field
13589 foreach ($tmpcrits as $tmpcrit) {
13590 if ($tmpcrit !== '0' && empty($tmpcrit)) {
13591 continue;
13592 }
13593 $tmpcrit = trim($tmpcrit);
13594
13595 $newres .= (($i2 > 0 || $i3 > 0) ? ' OR ' : '');
13596
13597 $operator = '=';
13598 $newcrit = preg_replace('/([!<>=]+)/', '', $tmpcrit);
13599
13600 $reg = array();
13601 preg_match('/([!<>=]+)/', $tmpcrit, $reg);
13602 if (!empty($reg[1])) {
13603 $operator = $reg[1];
13604 }
13605 if ($newcrit != '') {
13606 $numnewcrit = price2num($newcrit);
13607 if (is_numeric($numnewcrit)) {
13608 $newres .= $db->sanitize($field) . ' ' . $operator . ' ' . ((float) $numnewcrit); // should be a numeric
13609 } else {
13610 $newres .= '1 = 2'; // force false, we received a corrupted data
13611 }
13612 $i3++; // a criteria was added to string
13613 }
13614 }
13615 $i2++; // a criteria for 1 more field was added to string
13616 } elseif ($mode == 2 || $mode == -2) {
13617 $crit = preg_replace('/[^\-0-9,]/', '', $crit); // ID are always integer
13618 $newres .= ($i2 > 0 ? ' OR ' : '') . $db->sanitize($field) . " " . ($mode == -2 ? 'NOT ' : '');
13619 $newres .= $crit ? "IN (" . $db->sanitize($db->escape($crit)) . ")" : "IN (0)";
13620 if ($mode == -2) {
13621 $newres .= ' OR ' . $db->sanitize($field) . ' IS NULL';
13622 }
13623 $i2++; // a criteria for 1 more field was added to string
13624 } elseif ($mode == 3 || $mode == -3) {
13625 $tmparray = explode(',', $crit);
13626 if (count($tmparray)) {
13627 $listofcodes = '';
13628 $listofcodesnot = '';
13629 foreach ($tmparray as $val) {
13630 $val = trim($val);
13631 if ($val !== '') {
13632 if (preg_match('/^!/', $val)) {
13633 $listofcodesnot .= ($listofcodesnot ? ',' : '');
13634 $listofcodesnot .= "'" . $db->escape(preg_replace('/^!=?/', '', $val)) . "'";
13635 } else {
13636 $listofcodes .= ($listofcodes ? ',' : '');
13637 $listofcodes .= "'" . $db->escape($val) . "'";
13638 }
13639 }
13640 }
13641 $newres .= ($i2 > 0 ? ' OR ' : '');
13642 if ($listofcodes && $listofcodesnot) {
13643 $newres .= '(';
13644 }
13645 if ($listofcodes) {
13646 $newres .= $db->sanitize($field) . " " . ($mode == -3 ? 'NOT IN' : 'IN') . " (" . $db->sanitize($listofcodes, 1, 0, 1) . ")";
13647 }
13648 if ($listofcodes && $listofcodesnot) {
13649 $newres .= ' AND ';
13650 }
13651 if ($listofcodesnot) {
13652 $newres .= $db->sanitize($field) . " " . ($mode == -3 ? 'IN ' : 'NOT IN') . " (" . $db->sanitize($listofcodesnot, 1, 0, 1) . ")";
13653 }
13654 if ($listofcodes && $listofcodesnot) {
13655 $newres .= ')';
13656 }
13657 $i2++; // a criteria for 1 more field was added to string
13658 }
13659 if ($mode == -3) {
13660 $newres .= ' OR ' . $db->sanitize($field) . ' IS NULL';
13661 }
13662 } elseif ($mode == 4) {
13663 $tmparray = explode(',', $crit);
13664 if (count($tmparray)) {
13665 $listofcodes = '';
13666 foreach ($tmparray as $val) {
13667 $val = trim($val);
13668 if ($val) {
13669 $newres .= ($i2 > 0 ? " OR (" : "(") . $db->sanitize($field) . " LIKE '" . $db->escape($val) . ",%'";
13670 $newres .= ' OR ' . $db->sanitize($field) . " = '" . $db->escape($val) . "'";
13671 $newres .= ' OR ' . $db->sanitize($field) . " LIKE '%," . $db->escape($val) . "'";
13672 $newres .= ' OR ' . $db->sanitize($field) . " LIKE '%," . $db->escape($val) . ",%'";
13673 $newres .= ')';
13674 $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)
13675 }
13676 }
13677 }
13678 } else { // $mode=0
13679 $tmpcrits = explode('|', $crit);
13680 $i3 = 0; // count the nb of valid criteria added for the current couple criteria/field
13681 foreach ($tmpcrits as $tmpcrit) { // loop on each OR criteria
13682 if ($tmpcrit !== '0' && empty($tmpcrit)) {
13683 continue;
13684 }
13685 $tmpcrit = trim($tmpcrit);
13686
13687 if ($tmpcrit == '^$' || strpos($crit, '!') === 0) { // If we search empty, we must combined different OR fields with AND
13688 $newres .= (($i2 > 0 || $i3 > 0) ? ' AND ' : '');
13689 } else {
13690 $newres .= (($i2 > 0 || $i3 > 0) ? ' OR ' : '');
13691 }
13692
13693 $isSellist = false;
13694 $table = $label = $key = null;
13695
13696 if (strpos($field, 'ef.') === 0) {
13697 $extrafieldName = substr($field, 3);
13698 $extrafields = new ExtraFields($db);
13699 $extrafields->fetch_name_optionals_label('product');
13700
13701 if (isset($extrafields->attributes['product']['type'][$extrafieldName]) && $extrafields->attributes['product']['type'][$extrafieldName] === 'sellist') {
13702 $isSellist = true;
13703 $paramArray = $extrafields->attributes['product']['param'][$extrafieldName]['options'] ?? [];
13704 $param = array_key_first($paramArray);
13705 list($table, $label, $key) = explode(':', $param);
13706 }
13707 }
13708
13709 if (preg_match('/\.(id|rowid)$/', $field)) { // Special case for rowid that is sometimes a ref so used as a search field
13710 $newres .= $db->sanitize($field) . " = " . (is_numeric($tmpcrit) ? ((float) $tmpcrit) : '0');
13711 } else {
13712 $tmpcrit2 = $tmpcrit;
13713 $tmpbefore = '%';
13714 $tmpafter = '%';
13715 $tmps = '';
13716
13717 if ($isSellist && $key && $table && $label) {
13718 $newres .= $field . " IN (SELECT t." . $db->sanitize((string) $key) . " FROM " . $db->prefix() . $db->sanitize((string) $table) . " AS t WHERE t." . $db->sanitize((string) $label) . " LIKE '%" . $db->escape($tmpcrit2) . "%')";
13719 } else {
13720 if (preg_match('/^!/', $tmpcrit)) {
13721 $tmps .= $db->sanitize($field) . " NOT LIKE "; // ! as exclude character
13722 $tmpcrit2 = preg_replace('/^!/', '', $tmpcrit2);
13723 } else {
13724 $tmps .= $db->sanitize($field) . " LIKE ";
13725 }
13726 $tmps .= "'";
13727
13728 if (preg_match('/^[\^\$]/', $tmpcrit)) {
13729 $tmpbefore = '';
13730 $tmpcrit2 = preg_replace('/^[\^\$]/', '', $tmpcrit2);
13731 }
13732 if (preg_match('/[\^\$]$/', $tmpcrit)) {
13733 $tmpafter = '';
13734 $tmpcrit2 = preg_replace('/[\^\$]$/', '', $tmpcrit2);
13735 }
13736
13737 if ($tmpcrit2 == '' || preg_match('/^!/', $tmpcrit)) {
13738 $tmps = "(" . $tmps;
13739 }
13740 $newres .= $tmps;
13741 $newres .= $tmpbefore;
13742 $newres .= $db->escape($tmpcrit2);
13743 $newres .= $tmpafter;
13744 $newres .= "'";
13745 if ($tmpcrit2 == '' || preg_match('/^!/', $tmpcrit)) {
13746 $newres .= " OR " . $db->sanitize($field) . " IS NULL)";
13747 }
13748 }
13749 }
13750
13751 $i3++;
13752 }
13753
13754 $i2++; // a criteria for 1 more field was added to string
13755 }
13756 }
13757
13758 if ($sqltoadd) {
13759 $newres .= ($newres ? '' : ' OR ').str_replace('__KEYTOSEARCH__', $db->escape($crit), $sqltoadd);
13760 }
13761
13762 if ($newres) {
13763 $res = $res . ($res ? ' AND ' : '') . ($i2 > 1 ? '(' : '') . $newres . ($i2 > 1 ? ')' : '');
13764 }
13765 $i1++;
13766 }
13767 $res = ($nofirstand ? "" : " AND ") . "(" . $res . ")";
13768
13769 return $res;
13770}
13771
13779function showSimpleHTMLTable($outputlangs, $object)
13780{
13781 global $conf;
13782
13783 $discountIsAvailable = false;
13784 $orderPositionHasNoPrice = false;
13785
13786 if (!property_exists($object->lines[0], "remise_percent") ||
13787 !property_exists($object->lines[0], "fk_unit") ||
13788 !property_exists($object->lines[0], "multicurrency_total_ttc") ||
13789 !property_exists($object->lines[0], "description") ||
13790 !property_exists($object->lines[0], "qty")) {
13791 return"";
13792 }
13793
13794 foreach ($object->lines as $order_position) {
13795 if (!property_exists($order_position, "price")) {
13796 $orderPositionHasNoPrice = true;
13797 break;
13798 }
13799
13800 if (!empty($order_position->remise_percent)) {
13801 $discountIsAvailable = true;
13802 break;
13803 }
13804 };
13805
13806 if ($orderPositionHasNoPrice) {
13807 return "";
13808 }
13809
13810 $discountHeader = $discountIsAvailable ? '<th style="width:120px">'.$outputlangs->trans("Discount").'</th>' : '';
13811
13812 $table = '<table border="0" cellpadding="1" cellspacing="1">';
13813 $table .= '
13814 <thead>
13815 <tr>
13816 <th style="width:50px; text-align:left">#</th>
13817 <th style="text-align:left">'.$outputlangs->trans("Description").'</th>
13818 <th style="width:120px; text-align:right;">'.$outputlangs->trans("Price").'</th>
13819 <th style="width:100px; text-align:right;">'.$outputlangs->trans("Quantity").'</th>
13820 <th style="width:120px; text-align:right;">'.$outputlangs->trans("Unit").'</th>'.
13821 $discountHeader.'
13822 <th style="width:120px; text-align:right;">'.$outputlangs->trans("Sum").'</th>
13823 </tr>
13824 </thead>
13825 <tbody>';
13826
13827 foreach ($object->lines as $index => $order_position) {
13828 $position = $index + 1;
13829 $price = price($order_position->price, 0, $outputlangs, 0, -1, -1, $conf->currency);
13830 $unit = measuringUnitString($order_position->fk_unit, '', null, 1);
13831 $total = price($order_position->multicurrency_total_ttc, 0, $outputlangs, 0, -1, -1, $conf->currency);
13832 $discount = $discountIsAvailable ? '<td style="text-align:center">'.$order_position->remise_percent.'%</td>' : "";
13833
13834 $table .= '
13835 <tr>
13836 <td>'.$position.'</td>
13837 <td>'.$order_position->description.'</td>
13838 <td style="text-align:right">'.$price.'</td>
13839 <td style="text-align:right">'.$order_position->qty.'</td>
13840 <td style="text-align:right">'.$unit.'</td>'.
13841 $discount.'
13842 <td style="text-align:right">'.$total.'</td>
13843 </tr>';
13844 }
13845 $table .= '</tbody></table>';
13846
13847 return $table;
13848}
13849
13856function showDirectDownloadLink($object)
13857{
13858 global $langs;
13859
13860 $out = '';
13861 $url = $object->getLastMainDocLink($object->element);
13862
13863 $out .= img_picto($langs->trans("PublicDownloadLinkDesc"), 'globe') . ' <span class="opacitymedium">' . $langs->trans("DirectDownloadLink") . '</span><br>';
13864 if ($url) {
13865 $out .= '<div class="urllink"><input type="text" id="directdownloadlink" class="quatrevingtpercent" value="' . $url . '"></div>';
13866 $out .= ajax_autoselect("directdownloadlink", '');
13867 } else {
13868 $out .= '<div class="urllink">' . $langs->trans("FileNotShared") . '</div>';
13869 }
13870
13871 return $out;
13872}
13873
13882function getImageFileNameForSize($file, $extName, $extImgTarget = '')
13883{
13884 $dirName = dirname($file);
13885 if ($dirName == '.') {
13886 $dirName = '';
13887 }
13888
13889 if (!in_array($extName, array('', '_small', '_mini'))) {
13890 return 'Bad parameter extName';
13891 }
13892
13893 $fileName = preg_replace('/(\.gif|\.jpeg|\.jpg|\.png|\.bmp|\.webp|\.avif)$/i', '', $file); // We remove image extension, whatever is its case
13894 $fileName = basename($fileName);
13895
13896 if (empty($extImgTarget)) {
13897 $extImgTarget = (preg_match('/\.jpg$/i', $file) ? '.jpg' : '');
13898 }
13899 if (empty($extImgTarget)) {
13900 $extImgTarget = (preg_match('/\.jpeg$/i', $file) ? '.jpeg' : '');
13901 }
13902 if (empty($extImgTarget)) {
13903 $extImgTarget = (preg_match('/\.gif$/i', $file) ? '.gif' : '');
13904 }
13905 if (empty($extImgTarget)) {
13906 $extImgTarget = (preg_match('/\.png$/i', $file) ? '.png' : '');
13907 }
13908 if (empty($extImgTarget)) {
13909 $extImgTarget = (preg_match('/\.bmp$/i', $file) ? '.bmp' : '');
13910 }
13911 if (empty($extImgTarget)) {
13912 $extImgTarget = (preg_match('/\.webp$/i', $file) ? '.webp' : '');
13913 }
13914 if (empty($extImgTarget)) {
13915 $extImgTarget = (preg_match('/\.avif$/i', $file) ? '.avif' : '');
13916 }
13917
13918 if (!$extImgTarget) {
13919 return $file;
13920 }
13921
13922 $subdir = '';
13923 if ($extName) {
13924 $subdir = 'thumbs/';
13925 }
13926
13927 return ($dirName ? $dirName . '/' : '') . $subdir . $fileName . $extName . $extImgTarget; // New filename for thumb
13928}
13929
13930
13940function getAdvancedPreviewUrl($modulepart, $relativepath, $alldata = 0, $param = '')
13941{
13942 global $conf, $langs;
13943
13944 if (empty($conf->use_javascript_ajax)) {
13945 return '';
13946 }
13947
13948 $isAllowedForPreview = dolIsAllowedForPreview($relativepath);
13949
13950 if ($alldata == 1) {
13951 if ($isAllowedForPreview) {
13952 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));
13953 } else {
13954 return array();
13955 }
13956 }
13957
13958 // old behavior, return a string
13959 if ($isAllowedForPreview) {
13960 $tmpurl = DOL_URL_ROOT . '/document.php?modulepart=' . urlencode($modulepart) . '&attachment=0&file=' . urlencode($relativepath) . ($param ? '&' . $param : '');
13961 $title = $langs->transnoentities("Preview");
13962 //$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().
13963 //$tmpurl = 'file='.urlencode("'-alert(document.domain)-'_small.jpg"); // An example of tmpurl that should be blocked by the dol_escape_uri()
13964
13965 // 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,
13966 // 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.
13967 // Using the dol_escape_uri guarantee that we encode for URI so decode retrieve original expected value.
13968 return 'javascript:' . dol_escape_uri('document_preview(\'' . dol_escape_js($tmpurl) . '\', \'' . dol_escape_js(dol_mimetype($relativepath)) . '\', \'' . dol_escape_js($title) . '\')');
13969 } else {
13970 return '';
13971 }
13972}
13973
13980function getLabelSpecialCode($idcode)
13981{
13982 global $langs;
13983
13984 $arrayspecialines = array(1 => 'Transport', 2 => 'EcoTax', 3 => 'Option');
13985 if ($idcode > 10) {
13986 return 'Module ID ' . $idcode;
13987 }
13988 if (!empty($arrayspecialines[$idcode])) {
13989 return $langs->trans($arrayspecialines[$idcode]);
13990 }
13991 return '';
13992}
13993
14003function ajax_autoselect($htmlname, $addlink = '', $textonlink = 'Link')
14004{
14005 global $langs;
14006 $out = '<script nonce="' . getNonce() . '">
14007 jQuery(document).ready(function () {
14008 jQuery("' . ((strpos($htmlname, '.') === 0 ? '' : '#') . $htmlname) . '").click(function() { jQuery(this).select(); } );
14009 });
14010 </script>';
14011 if ($addlink) {
14012 if ($textonlink === 'image') {
14013 $out .= ' <a href="' . $addlink . '" target="_blank" rel="noopener noreferrer">' . img_picto('', 'globe') . '</a>';
14014 } else {
14015 $out .= ' <a href="' . $addlink . '" target="_blank" rel="noopener noreferrer">' . $langs->trans("Link") . '</a>';
14016 }
14017 }
14018 return $out;
14019}
14020
14028function dolIsAllowedForPreview($file)
14029{
14030 // Check .noexe extension in filename
14031 if (preg_match('/\.noexe$/i', $file)) {
14032 return 0;
14033 }
14034
14035 // Check mime types
14036 $mime_preview = array('avif', 'bmp', 'jpeg', 'png', 'gif', 'tiff', 'pdf', 'plain', 'css', 'webp', 'webm', 'mp4');
14037 if (getDolGlobalString('MAIN_ALLOW_SVG_FILES_AS_IMAGES')) {
14038 $mime_preview[] = 'svg+xml';
14039 }
14040 if (getDolGlobalString('MAIN_ALLOW_XML_FILES_AS_PREVIEW')) {
14041 $mime_preview[] = 'xml';
14042 }
14043
14044 //$mime_preview[]='vnd.oasis.opendocument.presentation';
14045 //$mime_preview[]='archive';
14046 $num_mime = array_search(dol_mimetype($file, '', 1), $mime_preview);
14047 if ($num_mime !== false) {
14048 return 1;
14049 }
14050
14051 // By default, not allowed for preview
14052 return 0;
14053}
14054
14055
14065function dol_mimetype($file, $default = 'application/octet-stream', $mode = 0)
14066{
14067 $mime = $default;
14068 $imgmime = 'other.png';
14069 $famime = 'file-o';
14070 $srclang = '';
14071
14072 $tmpfile = preg_replace('/\.noexe$/', '', $file);
14073
14074 // Plain text files
14075 if (preg_match('/\.txt$/i', $tmpfile)) {
14076 $mime = 'text/plain';
14077 $imgmime = 'text.png';
14078 $famime = 'file-alt';
14079 } elseif (preg_match('/\.rtx$/i', $tmpfile)) {
14080 $mime = 'text/richtext';
14081 $imgmime = 'text.png';
14082 $famime = 'file-alt';
14083 } elseif (preg_match('/\.csv$/i', $tmpfile)) {
14084 $mime = 'text/csv';
14085 $imgmime = 'text.png';
14086 $famime = 'file-csv';
14087 } elseif (preg_match('/\.tsv$/i', $tmpfile)) {
14088 $mime = 'text/tab-separated-values';
14089 $imgmime = 'text.png';
14090 $famime = 'file-alt';
14091 } elseif (preg_match('/\.(cf|conf|log)$/i', $tmpfile)) {
14092 $mime = 'text/plain';
14093 $imgmime = 'text.png';
14094 $famime = 'file-alt';
14095 } elseif (preg_match('/\.ini$/i', $tmpfile)) {
14096 $mime = 'text/plain';
14097 $imgmime = 'text.png';
14098 $srclang = 'ini';
14099 $famime = 'file-alt';
14100 } elseif (preg_match('/\.md$/i', $tmpfile)) {
14101 $mime = 'text/plain';
14102 $imgmime = 'text.png';
14103 $srclang = 'md';
14104 $famime = 'file-alt';
14105 } elseif (preg_match('/\.css$/i', $tmpfile)) {
14106 $mime = 'text/css';
14107 $imgmime = 'css.png';
14108 $srclang = 'css';
14109 $famime = 'file-alt';
14110 } elseif (preg_match('/\.lang$/i', $tmpfile)) {
14111 $mime = 'text/plain';
14112 $imgmime = 'text.png';
14113 $srclang = 'lang';
14114 $famime = 'file-alt';
14115 } elseif (preg_match('/\.(crt|cer|key|pub)$/i', $tmpfile)) { // Certificate files
14116 $mime = 'text/plain';
14117 $imgmime = 'text.png';
14118 $famime = 'file-alt';
14119 } elseif (preg_match('/\.(html|htm|shtml)$/i', $tmpfile)) { // XML based (HTML/XML/XAML)
14120 $mime = 'text/html';
14121 $imgmime = 'html.png';
14122 $srclang = 'html';
14123 $famime = 'file-alt';
14124 } elseif (preg_match('/\.(xml|xhtml)$/i', $tmpfile)) {
14125 $mime = 'text/xml';
14126 $imgmime = 'other.png';
14127 $srclang = 'xml';
14128 $famime = 'file-alt';
14129 } elseif (preg_match('/\.xaml$/i', $tmpfile)) {
14130 $mime = 'text/xml';
14131 $imgmime = 'other.png';
14132 $srclang = 'xaml';
14133 $famime = 'file-alt';
14134 } elseif (preg_match('/\.bas$/i', $tmpfile)) { // Languages
14135 $mime = 'text/plain';
14136 $imgmime = 'text.png';
14137 $srclang = 'bas';
14138 $famime = 'file-code';
14139 } elseif (preg_match('/\.(c)$/i', $tmpfile)) {
14140 $mime = 'text/plain';
14141 $imgmime = 'text.png';
14142 $srclang = 'c';
14143 $famime = 'file-code';
14144 } elseif (preg_match('/\.(cpp)$/i', $tmpfile)) {
14145 $mime = 'text/plain';
14146 $imgmime = 'text.png';
14147 $srclang = 'cpp';
14148 $famime = 'file-code';
14149 } elseif (preg_match('/\.cs$/i', $tmpfile)) {
14150 $mime = 'text/plain';
14151 $imgmime = 'text.png';
14152 $srclang = 'cs';
14153 $famime = 'file-code';
14154 } elseif (preg_match('/\.(h)$/i', $tmpfile)) {
14155 $mime = 'text/plain';
14156 $imgmime = 'text.png';
14157 $srclang = 'h';
14158 $famime = 'file-code';
14159 } elseif (preg_match('/\.(java|jsp)$/i', $tmpfile)) {
14160 $mime = 'text/plain';
14161 $imgmime = 'text.png';
14162 $srclang = 'java';
14163 $famime = 'file-code';
14164 } elseif (preg_match('/\.php([0-9]{1})?$/i', $tmpfile)) {
14165 $mime = 'text/plain';
14166 $imgmime = 'php.png';
14167 $srclang = 'php';
14168 $famime = 'file-code';
14169 } elseif (preg_match('/\.phtml$/i', $tmpfile)) {
14170 $mime = 'text/plain';
14171 $imgmime = 'php.png';
14172 $srclang = 'php';
14173 $famime = 'file-code';
14174 } elseif (preg_match('/\.(pl|pm)$/i', $tmpfile)) {
14175 $mime = 'text/plain';
14176 $imgmime = 'pl.png';
14177 $srclang = 'perl';
14178 $famime = 'file-code';
14179 } elseif (preg_match('/\.sql$/i', $tmpfile)) {
14180 $mime = 'text/plain';
14181 $imgmime = 'text.png';
14182 $srclang = 'sql';
14183 $famime = 'file-code';
14184 } elseif (preg_match('/\.js$/i', $tmpfile)) {
14185 $mime = 'text/x-javascript';
14186 $imgmime = 'jscript.png';
14187 $srclang = 'js';
14188 $famime = 'file-code';
14189 } elseif (preg_match('/\.odp$/i', $tmpfile)) { // Open office
14190 $mime = 'application/vnd.oasis.opendocument.presentation';
14191 $imgmime = 'ooffice.png';
14192 $famime = 'file-powerpoint';
14193 } elseif (preg_match('/\.ods$/i', $tmpfile)) {
14194 $mime = 'application/vnd.oasis.opendocument.spreadsheet';
14195 $imgmime = 'ooffice.png';
14196 $famime = 'file-excel';
14197 } elseif (preg_match('/\.odt$/i', $tmpfile)) {
14198 $mime = 'application/vnd.oasis.opendocument.text';
14199 $imgmime = 'ooffice.png';
14200 $famime = 'file-word';
14201 } elseif (preg_match('/\.mdb$/i', $tmpfile)) { // MS Office
14202 $mime = 'application/msaccess';
14203 $imgmime = 'mdb.png';
14204 $famime = 'file';
14205 } elseif (preg_match('/\.doc[xm]?$/i', $tmpfile)) {
14206 $mime = 'application/msword';
14207 $imgmime = 'doc.png';
14208 $famime = 'file-word';
14209 } elseif (preg_match('/\.dot[xm]?$/i', $tmpfile)) {
14210 $mime = 'application/msword';
14211 $imgmime = 'doc.png';
14212 $famime = 'file-word';
14213 } elseif (preg_match('/\.xlt(x)?$/i', $tmpfile)) {
14214 $mime = 'application/vnd.ms-excel';
14215 $imgmime = 'xls.png';
14216 $famime = 'file-excel';
14217 } elseif (preg_match('/\.xla(m)?$/i', $tmpfile)) {
14218 $mime = 'application/vnd.ms-excel';
14219 $imgmime = 'xls.png';
14220 $famime = 'file-excel';
14221 } elseif (preg_match('/\.xls$/i', $tmpfile)) {
14222 $mime = 'application/vnd.ms-excel';
14223 $imgmime = 'xls.png';
14224 $famime = 'file-excel';
14225 } elseif (preg_match('/\.xls[bmx]$/i', $tmpfile)) {
14226 $mime = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
14227 $imgmime = 'xls.png';
14228 $famime = 'file-excel';
14229 } elseif (preg_match('/\.pps[mx]?$/i', $tmpfile)) {
14230 $mime = 'application/vnd.ms-powerpoint';
14231 $imgmime = 'ppt.png';
14232 $famime = 'file-powerpoint';
14233 } elseif (preg_match('/\.ppt[mx]?$/i', $tmpfile)) {
14234 $mime = 'application/x-mspowerpoint';
14235 $imgmime = 'ppt.png';
14236 $famime = 'file-powerpoint';
14237 } elseif (preg_match('/\.pdf$/i', $tmpfile)) { // Other
14238 $mime = 'application/pdf';
14239 $imgmime = 'pdf.png';
14240 $famime = 'file-pdf';
14241 } elseif (preg_match('/\.bat$/i', $tmpfile)) { // Scripts
14242 $mime = 'text/x-bat';
14243 $imgmime = 'script.png';
14244 $srclang = 'dos';
14245 $famime = 'file-code';
14246 } elseif (preg_match('/\.sh$/i', $tmpfile)) {
14247 $mime = 'text/x-sh';
14248 $imgmime = 'script.png';
14249 $srclang = 'bash';
14250 $famime = 'file-code';
14251 } elseif (preg_match('/\.ksh$/i', $tmpfile)) {
14252 $mime = 'text/x-ksh';
14253 $imgmime = 'script.png';
14254 $srclang = 'bash';
14255 $famime = 'file-code';
14256 } elseif (preg_match('/\.bash$/i', $tmpfile)) {
14257 $mime = 'text/x-bash';
14258 $imgmime = 'script.png';
14259 $srclang = 'bash';
14260 $famime = 'file-code';
14261 } elseif (preg_match('/\.ico$/i', $tmpfile)) { // Images
14262 $mime = 'image/x-icon';
14263 $imgmime = 'image.png';
14264 $famime = 'file-image';
14265 } elseif (preg_match('/\.(jpg|jpeg)$/i', $tmpfile)) {
14266 $mime = 'image/jpeg';
14267 $imgmime = 'image.png';
14268 $famime = 'file-image';
14269 } elseif (preg_match('/\.png$/i', $tmpfile)) {
14270 $mime = 'image/png';
14271 $imgmime = 'image.png';
14272 $famime = 'file-image';
14273 } elseif (preg_match('/\.gif$/i', $tmpfile)) {
14274 $mime = 'image/gif';
14275 $imgmime = 'image.png';
14276 $famime = 'file-image';
14277 } elseif (preg_match('/\.bmp$/i', $tmpfile)) {
14278 $mime = 'image/bmp';
14279 $imgmime = 'image.png';
14280 $famime = 'file-image';
14281 } elseif (preg_match('/\.(tif|tiff)$/i', $tmpfile)) {
14282 $mime = 'image/tiff';
14283 $imgmime = 'image.png';
14284 $famime = 'file-image';
14285 } elseif (preg_match('/\.svg$/i', $tmpfile)) {
14286 $mime = 'image/svg+xml';
14287 $imgmime = 'image.png';
14288 $famime = 'file-image';
14289 } elseif (preg_match('/\.webp$/i', $tmpfile)) {
14290 $mime = 'image/webp';
14291 $imgmime = 'image.png';
14292 $famime = 'file-image';
14293 } elseif (preg_match('/\.vcs$/i', $tmpfile)) { // Calendar
14294 $mime = 'text/calendar';
14295 $imgmime = 'other.png';
14296 $famime = 'file-alt';
14297 } elseif (preg_match('/\.ics$/i', $tmpfile)) {
14298 $mime = 'text/calendar';
14299 $imgmime = 'other.png';
14300 $famime = 'file-alt';
14301 } elseif (preg_match('/\.torrent$/i', $tmpfile)) { // Other
14302 $mime = 'application/x-bittorrent';
14303 $imgmime = 'other.png';
14304 $famime = 'file-o';
14305 } elseif (preg_match('/\.(mp3|ogg|au|wav|wma|mid)$/i', $tmpfile)) { // Audio
14306 $mime = 'audio';
14307 $imgmime = 'audio.png';
14308 $famime = 'file-audio';
14309 } elseif (preg_match('/\.mp4$/i', $tmpfile)) { // Video
14310 $mime = 'video/mp4';
14311 $imgmime = 'video.png';
14312 $famime = 'file-video';
14313 } elseif (preg_match('/\.ogv$/i', $tmpfile)) {
14314 $mime = 'video/ogg';
14315 $imgmime = 'video.png';
14316 $famime = 'file-video';
14317 } elseif (preg_match('/\.webm$/i', $tmpfile)) {
14318 $mime = 'video/webm';
14319 $imgmime = 'video.png';
14320 $famime = 'file-video';
14321 } elseif (preg_match('/\.avif$/i', $tmpfile)) {
14322 $mime = 'image/avif';
14323 $imgmime = 'image.png';
14324 $famime = 'file-image';
14325 } elseif (preg_match('/\.avi$/i', $tmpfile)) {
14326 $mime = 'video/x-msvideo';
14327 $imgmime = 'video.png';
14328 $famime = 'file-video';
14329 } elseif (preg_match('/\.divx$/i', $tmpfile)) {
14330 $mime = 'video/divx';
14331 $imgmime = 'video.png';
14332 $famime = 'file-video';
14333 } elseif (preg_match('/\.xvid$/i', $tmpfile)) {
14334 $mime = 'video/xvid';
14335 $imgmime = 'video.png';
14336 $famime = 'file-video';
14337 } elseif (preg_match('/\.(wmv|mpg|mpeg)$/i', $tmpfile)) {
14338 $mime = 'video';
14339 $imgmime = 'video.png';
14340 $famime = 'file-video';
14341 } elseif (preg_match('/\.(zip|rar|gz|tgz|xz|z|cab|bz2|7z|tar|lzh|zst)$/i', $tmpfile)) { // Archive
14342 // application/xxx where zzz is zip, ...
14343 $mime = 'archive';
14344 $imgmime = 'archive.png';
14345 $famime = 'file-archive';
14346 } elseif (preg_match('/\.(exe|com)$/i', $tmpfile)) { // Exe
14347 $mime = 'application/octet-stream';
14348 $imgmime = 'other.png';
14349 $famime = 'file-o';
14350 } elseif (preg_match('/\.(dll|lib|o|so|a)$/i', $tmpfile)) { // Lib
14351 $mime = 'library';
14352 $imgmime = 'library.png';
14353 $famime = 'file-o';
14354 } elseif (preg_match('/\.err$/i', $tmpfile)) { // phpcs:ignore
14355 $mime = 'error';
14356 $imgmime = 'error.png';
14357 $famime = 'file-alt';
14358 }
14359
14360 if ($famime == 'file-o') {
14361 // file-o seems to not work in fontawesome 5
14362 $famime = 'file';
14363 }
14364
14365 // Return mimetype string
14366 switch ((int) $mode) {
14367 case 1:
14368 $tmp = explode('/', $mime);
14369 return (!empty($tmp[1]) ? $tmp[1] : $tmp[0]);
14370 case 2:
14371 return $imgmime;
14372 case 3:
14373 return $srclang;
14374 case 4:
14375 return $famime;
14376 }
14377 return $mime;
14378}
14379
14391function getDictionaryValue($tablename, $field, $id, $checkentity = false, $rowidfield = 'rowid')
14392{
14393 global $conf, $db;
14394
14395 $tablename = preg_replace('/^' . preg_quote(MAIN_DB_PREFIX, '/') . '/', '', $tablename); // Clean name of table for backward compatibility.
14396
14397 $dictvalues = (isset($conf->cache['dictvalues_' . $tablename]) ? $conf->cache['dictvalues_' . $tablename] : null);
14398
14399 if (is_null($dictvalues)) {
14400 $dictvalues = array();
14401
14402 $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
14403 if ($checkentity) {
14404 $sql .= ' AND entity IN (0,' . getEntity($tablename) . ')';
14405 }
14406
14407 $resql = $db->query($sql);
14408 if ($resql) {
14409 while ($obj = $db->fetch_object($resql)) {
14410 $dictvalues[$obj->$rowidfield] = $obj; // $obj is stdClass
14411 }
14412 } else {
14413 dol_print_error($db);
14414 }
14415
14416 $conf->cache['dictvalues_' . $tablename] = $dictvalues;
14417 }
14418
14419 if (!empty($dictvalues[$id])) {
14420 // Found
14421 $tmp = $dictvalues[$id];
14422 return (property_exists($tmp, $field) ? $tmp->$field : '');
14423 } else {
14424 // Not found
14425 return '';
14426 }
14427}
14428
14435function colorIsLight($stringcolor)
14436{
14437 $stringcolor = str_replace('#', '', $stringcolor);
14438 $res = -1;
14439 if (!empty($stringcolor)) {
14440 $res = 0;
14441 $tmp = explode(',', $stringcolor);
14442 if (count($tmp) > 1) { // This is a comma RGB ('255','255','255')
14443 $r = $tmp[0];
14444 $g = $tmp[1];
14445 $b = $tmp[2];
14446 } else {
14447 $hexr = $stringcolor[0] . $stringcolor[1];
14448 $hexg = $stringcolor[2] . $stringcolor[3];
14449 $hexb = $stringcolor[4] . $stringcolor[5];
14450 $r = hexdec($hexr);
14451 $g = hexdec($hexg);
14452 $b = hexdec($hexb);
14453 }
14454 $bright = (max($r, $g, $b) + min($r, $g, $b)) / 510.0; // HSL algorithm
14455 if ($bright > 0.6) {
14456 $res = 1;
14457 }
14458 }
14459 return $res;
14460}
14461
14470function isVisibleToUserType($type_user, &$menuentry, &$listofmodulesforexternal)
14471{
14472 //print 'type_user='.$type_user.' module='.$menuentry['module'].' enabled='.$menuentry['enabled'].' perms='.$menuentry['perms'];
14473 //print 'ok='.in_array($menuentry['module'], $listofmodulesforexternal);
14474 if (empty($menuentry['enabled'])) {
14475 return 0; // Entry disabled by condition
14476 }
14477 if ($type_user && array_key_exists('module', $menuentry) && $menuentry['module']) {
14478 $tmploops = explode('|', $menuentry['module']);
14479 $found = 0;
14480 foreach ($tmploops as $tmploop) {
14481 if (in_array($tmploop, $listofmodulesforexternal)) {
14482 $found++;
14483 break;
14484 }
14485 }
14486 if (!$found) {
14487 return 0; // Entry is for menus all excluded to external users
14488 }
14489 }
14490 if (!$menuentry['perms'] && $type_user) {
14491 return 0; // No permissions and user is external
14492 }
14493 if (!$menuentry['perms'] && getDolGlobalString('MAIN_MENU_HIDE_UNAUTHORIZED')) {
14494 return 0; // No permissions and option to hide when not allowed, even for internal user, is on
14495 }
14496 if (!$menuentry['perms']) {
14497 return 2; // No permissions and user is external
14498 }
14499 return 1;
14500}
14501
14509function roundUpToNextMultiple($n, $x = 5)
14510{
14511 $result = (ceil($n) % $x === 0) ? ceil($n) : (round(($n + $x / 2) / $x) * $x);
14512 return (int) $result;
14513}
14514
14526function dolGetBadge($label, $html = '', $type = 'primary', $mode = '', $url = '', $params = array())
14527{
14528 $csstouse = 'badge';
14529 $csstouse .= (!empty($mode) ? ' badge-' . $mode : '');
14530 $csstouse .= (!empty($type) ? ' badge-' . $type : '');
14531 $csstouse .= (empty($params['css']) ? '' : ' ' . $params['css']);
14532
14533 $attr = array(
14534 'class' => $csstouse
14535 );
14536
14537 if (empty($html)) {
14538 $html = $label;
14539 }
14540
14541 if (!empty($url)) {
14542 $attr['href'] = $url;
14543 }
14544
14545 if ($mode === 'dot') {
14546 $attr['class'] .= ' classfortooltip';
14547 $attr['title'] = $html;
14548 $attr['aria-label'] = $label;
14549 $html = '';
14550 }
14551
14552 // Override attr
14553 if (!empty($params['attr']) && is_array($params['attr'])) {
14554 foreach ($params['attr'] as $key => $value) {
14555 if ($key == 'class') {
14556 $attr['class'] .= ' ' . $value;
14557 } elseif ($key == 'classOverride') {
14558 $attr['class'] = $value;
14559 } else {
14560 $attr[$key] = $value;
14561 }
14562 }
14563 }
14564
14565 // TODO: add hook
14566
14567 // escape all attribute
14568 $attr = array_map('dolPrintHTMLForAttribute', $attr);
14569
14570 $TCompiledAttr = array();
14571 foreach ($attr as $key => $value) {
14572 $TCompiledAttr[] = $key . '="' . $value . '"';
14573 }
14574
14575 $compiledAttributes = !empty($TCompiledAttr) ? implode(' ', $TCompiledAttr) : '';
14576
14577 $tag = !empty($url) ? 'a' : 'span';
14578
14579 return '<' . $tag . ' ' . $compiledAttributes . '>' . $html . '</' . $tag . '>';
14580}
14581
14582
14595function dolGetStatus($statusLabel = '', $statusLabelShort = '', $html = '', $statusType = 'status0', $displayMode = 0, $url = '', $params = array())
14596{
14597 global $conf;
14598
14599 $return = '';
14600 $dolGetBadgeParams = array();
14601
14602 if (!empty($params['badgeParams'])) {
14603 $dolGetBadgeParams = $params['badgeParams'];
14604 }
14605
14606 // TODO : add a hook
14607 if ($displayMode == 0) {
14608 $return = !empty($html) ? $html : (empty($conf->dol_optimize_smallscreen) ? $statusLabel : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort));
14609 } elseif ($displayMode == 1) {
14610 $return = !empty($html) ? $html : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort);
14611 } elseif (getDolGlobalString('MAIN_STATUS_USES_IMAGES')) {
14612 // Use status with images (for backward compatibility)
14613 $return = '';
14614 $htmlLabel = (in_array($displayMode, array(1, 2, 5)) ? '<span class="hideonsmartphone">' : '') . (!empty($html) ? $html : $statusLabel) . (in_array($displayMode, array(1, 2, 5)) ? '</span>' : '');
14615 $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>' : '');
14616
14617 // For small screen, we always use the short label instead of long label.
14618 if (!empty($conf->dol_optimize_smallscreen)) {
14619 if ($displayMode == 0) {
14620 $displayMode = 1;
14621 } elseif ($displayMode == 4) {
14622 $displayMode = 2;
14623 } elseif ($displayMode == 6) {
14624 $displayMode = 5;
14625 }
14626 }
14627
14628 // For backward compatibility. Image's filename are still in French, so we use this array to convert
14629 $statusImg = array(
14630 'status0' => 'statut0',
14631 'status1' => 'statut1',
14632 'status2' => 'statut2',
14633 'status3' => 'statut3',
14634 'status4' => 'statut4',
14635 'status5' => 'statut5',
14636 'status6' => 'statut6',
14637 'status7' => 'statut7',
14638 'status8' => 'statut8',
14639 'status9' => 'statut9'
14640 );
14641
14642 if (!empty($statusImg[$statusType])) {
14643 $htmlImg = img_picto($statusLabel, $statusImg[$statusType]);
14644 } else {
14645 $htmlImg = img_picto($statusLabel, $statusType);
14646 }
14647
14648 if ($displayMode === 2) {
14649 $return = $htmlImg . ' ' . $htmlLabelShort;
14650 } elseif ($displayMode === 3) {
14651 $return = $htmlImg;
14652 } elseif ($displayMode === 4) {
14653 $return = $htmlImg . ' ' . $htmlLabel;
14654 } elseif ($displayMode === 5) {
14655 $return = $htmlLabelShort . ' ' . $htmlImg;
14656 } else { // $displayMode >= 6
14657 $return = $htmlLabel . ' ' . $htmlImg;
14658 }
14659 } elseif (!getDolGlobalString('MAIN_STATUS_USES_IMAGES') && !empty($displayMode)) {
14660 // Use new badge
14661 $statusLabelShort = (empty($statusLabelShort) ? $statusLabel : $statusLabelShort);
14662
14663 $dolGetBadgeParams['attr']['class'] = 'badge-status';
14664 if (empty($dolGetBadgeParams['attr']['title'])) {
14665 $dolGetBadgeParams['attr']['title'] = empty($params['tooltip']) ? $statusLabel : ($params['tooltip'] != 'no' ? $params['tooltip'] : '');
14666 } else { // If a title was forced from $params['badgeParams']['attr']['title'], we set the class to get it as a tooltip.
14667 $dolGetBadgeParams['attr']['class'] .= ' classfortooltip';
14668 // And if we use tooltip, we can output title in HTML @phan-suppress-next-line PhanTypeInvalidDimOffset
14669 $dolGetBadgeParams['attr']['title'] = dol_htmlentitiesbr((string) $dolGetBadgeParams['attr']['title'], 1);
14670 }
14671
14672 if ($displayMode == 3) {
14673 $return = dolGetBadge((empty($conf->dol_optimize_smallscreen) ? $statusLabel : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort)), '', $statusType, 'dot', $url, $dolGetBadgeParams);
14674 } elseif ($displayMode === 5) {
14675 $return = dolGetBadge($statusLabelShort, $html, $statusType, '', $url, $dolGetBadgeParams);
14676 } else {
14677 $return = dolGetBadge(((empty($conf->dol_optimize_smallscreen) && $displayMode != 2) ? $statusLabel : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort)), $html, $statusType, '', $url, $dolGetBadgeParams);
14678 }
14679 }
14680
14681 return $return;
14682}
14683
14684
14720function dolGetButtonAction($label, $text = '', $actionType = 'default', $url = '', $id = '', $userRight = 1, $params = array())
14721{
14722 global $hookmanager, $action, $object, $langs;
14723
14724 // If $url is an array, we must build a dropdown button or recursively iterate over each value
14725 if (is_array($url)) {
14726 // Loop on $url array to remove entries of disabled modules
14727 foreach ($url as $key => $subbutton) {
14728 if (isset($subbutton['enabled']) && empty($subbutton['enabled'])) {
14729 unset($url[$key]);
14730 }
14731 }
14732
14733 $out = '';
14734
14735 if (array_key_exists('areDropdownButtons', $params) && $params["areDropdownButtons"] === false) { // @phan-suppress-current-line PhanTypeInvalidDimOffset
14736 foreach ($url as $button) {
14737 if (!empty($button['lang'])) {
14738 $langs->load($button['lang']);
14739 }
14740 $label = $langs->trans($button['label']);
14741 $text = $button['text'] ?? '';
14742 $actionType = $button['actionType'] ?? '';
14743 $tmpUrl = DOL_URL_ROOT . $button['url'] . (empty($params['backtopage']) ? '' : '&amp;backtopage=' . urlencode($params['backtopage']));
14744 $id = $button['id'] ?? '';
14745 $userRight = $button['perm'] ?? 1;
14746 $button['params'] = $button['params'] ?? []; // @phan-suppress-current-line PhanPluginDuplicateExpressionAssignmentOperation
14747
14748 $out .= dolGetButtonAction($label, $text, $actionType, $tmpUrl, $id, $userRight, $button['params']);
14749 }
14750 return $out;
14751 }
14752
14753 if (count($url) > 1 || !empty($params["forceDropdownButtons"])) {
14754 $out .= '<div class="dropdown inline-block dropdown-holder">';
14755 $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>';
14756 $out .= '<div class="dropdown-content">';
14757 foreach ($url as $subbutton) {
14758 if (!empty($subbutton['lang'])) {
14759 $langs->load($subbutton['lang']);
14760 }
14761
14762 if (!empty($subbutton['urlraw'])) {
14763 $tmpurl = $subbutton['urlraw']; // Use raw url, no url completion, use only what developer send
14764 } else {
14765 $tmpurl = !empty($subbutton['urlroot']) ? $subbutton['urlroot'] : $subbutton['url'];
14766 $tmpurl = dolCompletUrlForDropdownButton($tmpurl, $params, empty($subbutton['urlroot']));
14767 }
14768
14769 $subbuttonparam = array();
14770 if (!empty($subbutton['attr'])) {
14771 $subbuttonparam['attr'] = $subbutton['attr'];
14772 }
14773 $subbuttonparam['isDropDown'] = (empty($params['isDropDown']) ? ($subbutton['isDropDown'] ?? false) : $params['isDropDown']);
14774
14775 $out .= dolGetButtonAction($subbutton['text'] ?? '', $langs->trans($subbutton['label']), 'default', $tmpurl, $subbutton['id'] ?? '', $subbutton['perm'], $subbuttonparam);
14776 }
14777 $out .= "</div>";
14778 $out .= "</div>";
14779 } else {
14780 foreach ($url as $subbutton) { // Should loop on 1 record only
14781 if (!empty($subbutton['lang'])) {
14782 $langs->load($subbutton['lang']);
14783 }
14784
14785 if (!empty($subbutton['urlraw'])) {
14786 $tmpurl = $subbutton['urlraw']; // Use raw url, no url completion, use only what developer send
14787 } else {
14788 $tmpurl = !empty($subbutton['urlroot']) ? $subbutton['urlroot'] : $subbutton['url'];
14789 $tmpurl = dolCompletUrlForDropdownButton($tmpurl, $params, empty($subbutton['urlroot']));
14790 }
14791
14792 $label = $langs->trans($subbutton['label']);
14793 $text = $subbutton['text'] ?? '';
14794 if (empty($text)) {
14795 $text = $label;
14796 $label = '';
14797 }
14798
14799 $out .= dolGetButtonAction($label, $text, 'default', $tmpurl, '', $subbutton['perm'], $params);
14800 }
14801 }
14802
14803 return $out;
14804 }
14805
14806 // Here, $url is a simple link
14807 if (!empty($params['isDropdown']) || !empty($params['isDropDown'])) { // Use the dropdown-item style (not for action button)
14808 $class = "dropdown-item";
14809 } else {
14810 $class = 'butAction';
14811 if ($actionType == 'edit') {
14812 $class = 'butAction butActionEdit';
14813 } elseif ($actionType == 'email') {
14814 $class = 'butAction butActionEmail';
14815 } elseif ($actionType == 'clone') {
14816 $class = 'butAction butActionClone';
14817 } elseif ($actionType == 'cancel') {
14818 $class = 'butAction butActionDelete';
14819 } elseif ($actionType == 'danger' || $actionType == 'delete') {
14820 $class = 'butAction butActionDelete';
14821 if (!empty($url) && strpos($url, 'token=') === false) {
14822 $url .= '&token=' . newToken();
14823 }
14824 }
14825 }
14826 $attr = array(
14827 'class' => $class,
14828 'href' => empty($url) ? '' : $url,
14829 'title' => $label
14830 );
14831
14832 if (empty($text)) {
14833 $text = $label;
14834 $attr['title'] = ''; // if html not set, using label on title is redundant
14835 } else {
14836 $attr['title'] = $label;
14837 $attr['aria-label'] = $label;
14838 }
14839
14840 if (empty($userRight) || $userRight < 0) {
14841 $attr['class'] = 'butActionRefused';
14842 $attr['href'] = '';
14843 $attr['title'] = (($label && $text && $label != $text) ? $label : '');
14844 $attr['title'] = ($attr['title'] ? $attr['title'] . (empty($userRight) ? '<br>' : '') : '');
14845 $attr['title'] .= ((empty($userRight) && empty($label)) ? $langs->trans('NotEnoughPermissions') : '');
14846 }
14847
14848 if (!empty($id)) {
14849 $attr['id'] = $id;
14850 }
14851
14852 // Override attr
14853 if (!empty($params['attr']) && is_array($params['attr'])) {
14854 foreach ($params['attr'] as $key => $value) {
14855 if ($key == 'class') {
14856 $attr['class'] .= ' ' . $value;
14857 } elseif ($key == 'classOverride') {
14858 $attr['class'] = $value;
14859 } else {
14860 $attr[$key] = $value;
14861 }
14862 }
14863 }
14864
14865 // automatic add tooltip when title is detected
14866 if (!empty($attr['title']) && !empty($attr['class']) && strpos($attr['class'], 'classfortooltip') === false) {
14867 $attr['class'] .= ' classfortooltip';
14868 }
14869
14870 // Js Confirm button
14871 if ($userRight && !empty($params['confirm'])) {
14872 if (!is_array($params['confirm'])) {
14873 $params['confirm'] = array();
14874 }
14875
14876 if (empty($params['confirm']['url'])) {
14877 $params['confirm']['url'] = $url . (strpos($url, '?') > 0 ? '&' : '?') . 'confirm=yes';
14878 }
14879
14880 // for js disabled compatibility set $url as call to confirm action and $params['confirm']['url'] to confirmed action
14881 $attr['data-confirm-url'] = $params['confirm']['url'];
14882 $attr['data-confirm-title'] = !empty($params['confirm']['title']) ? $params['confirm']['title'] : $langs->trans('ConfirmBtnCommonTitle', $label);
14883 $attr['data-confirm-content'] = !empty($params['confirm']['content']) ? $params['confirm']['content'] : $langs->trans('ConfirmBtnCommonContent', $label);
14884 $attr['data-confirm-content'] = preg_replace("/\r|\n/", "", $attr['data-confirm-content']);
14885 $attr['data-confirm-action-btn-label'] = !empty($params['confirm']['action-btn-label']) ? $params['confirm']['action-btn-label'] : $langs->trans('Confirm');
14886 $attr['data-confirm-cancel-btn-label'] = !empty($params['confirm']['cancel-btn-label']) ? $params['confirm']['cancel-btn-label'] : $langs->trans('CloseDialog');
14887 $attr['data-confirm-modal'] = !empty($params['confirm']['modal']) ? $params['confirm']['modal'] : true;
14888
14889 $attr['class'] .= ' butActionConfirm';
14890 }
14891
14892 if (isset($attr['href']) && empty($attr['href'])) {
14893 unset($attr['href']);
14894 }
14895
14896 // TODO replace this $TCompiledAttr generation block by commonHtmlAttributeBuilder like line below
14897 // $TCompiledAttr = commonHtmlAttributeBuilder($attr, $params['use_unsecured_unescapedattr'] ?? []);
14898 $TCompiledAttr = array();
14899 foreach ($attr as $key => $value) {
14900 if (!empty($params['use_unsecured_unescapedattr']) && is_array($params['use_unsecured_unescapedattr']) && in_array($key, $params['use_unsecured_unescapedattr'])) {
14901 // Deprecated, forbidden.
14902 $value = dol_htmlentities($value, ENT_QUOTES | ENT_SUBSTITUTE);
14903 } elseif (in_array($key, ['href', 'data-confirm-url'])) {
14904 $value = dolPrintHTMLForAttributeUrl($value);
14905 } else {
14906 $value = dolPrintHTMLForAttribute($value);
14907 }
14908
14909 $TCompiledAttr[] = $key . '="' . $value . '"'; // $value has been escaped by the dolPrintHTMLForAttribute... just before
14910 }
14911 $compiledAttributes = empty($TCompiledAttr) ? '' : implode(' ', $TCompiledAttr);
14912
14913 $tag = !empty($attr['href']) ? 'a' : 'span';
14914
14915 $parameters = array(
14916 'TCompiledAttr' => $TCompiledAttr, // array
14917 'compiledAttributes' => $compiledAttributes, // string
14918 'attr' => $attr,
14919 'tag' => $tag,
14920 'label' => $label,
14921 'html' => $text,
14922 'actionType' => $actionType,
14923 'url' => $url,
14924 'id' => $id,
14925 'userRight' => $userRight,
14926 'params' => $params
14927 );
14928
14929 $reshook = $hookmanager->executeHooks('dolGetButtonAction', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
14930 if ($reshook < 0) {
14931 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
14932 }
14933
14934 if (empty($reshook)) {
14935 if (dol_textishtml($text)) { // If content already HTML encoded
14936 return '<' . $tag . ' ' . $compiledAttributes . '><span class="textbutton">' . $text . '</span></' . $tag . '>';
14937 } else {
14938 return '<' . $tag . ' ' . $compiledAttributes . '><span class="textbutton">' . dol_escape_htmltag($text) . '</span></' . $tag . '>';
14939 }
14940 } else {
14941 return $hookmanager->resPrint;
14942 }
14943}
14944
14975function commonHtmlAttributeBuilder($attr, array $unescapedAttr = [])
14976{
14977 $TCompiledAttr = array();
14978 if (empty($attr)) {
14979 return [];
14980 }
14981
14982 foreach ($attr as $key => $value) {
14983 // special boolean attributes case
14984 if (in_array($key, getListOfHtmlBooleanAttributes())) {
14985 if ($value) {
14986 $TCompiledAttr[$key] = $key;
14987 }
14988 continue;
14989 }
14990
14991 if (!empty($unescapedAttr) && in_array($key, $unescapedAttr)) {
14992 // Not recommended
14993 $value = dol_htmlentities((string) $value, ENT_QUOTES | ENT_SUBSTITUTE);
14994 } elseif ($key == 'href') {
14995 $value = dolPrintHTMLForAttributeUrl((string) $value);
14996 } else {
14997 $value = dolPrintHTMLForAttribute((string) $value);
14998 }
14999
15000 $TCompiledAttr[$key] = $key . '="' . $value . '"'; // $value has been escaped by the dolPrintHTMLForAttribute... just before
15001 }
15002
15003 return $TCompiledAttr;
15004}
15005
15020function getListOfHtmlBooleanAttributes(): array
15021{
15022 return [
15023 // Input / Form
15024 'checked',
15025 'disabled',
15026 'readonly',
15027 'required',
15028 'autofocus',
15029 'multiple',
15030
15031 // Option
15032 'selected',
15033
15034 // Form / General
15035 'novalidate',
15036 'formnovalidate',
15037
15038 // Media
15039 'autoplay',
15040 'controls',
15041 'loop',
15042 'muted',
15043 'playsinline',
15044
15045 // Other elements
15046 'hidden',
15047 'open',
15048 'ismap',
15049 'reversed',
15050 'allowfullscreen',
15051 'itemscope',
15052 'nomodule',
15053 'defer',
15054 'async',
15055 'default',
15056 'inert',
15057 ];
15058}
15059
15060
15069function dolCompletUrlForDropdownButton(string $url, array $params, bool $addDolUrlRoot = true)
15070{
15071 if (empty($url)) {
15072 return '';
15073 }
15074
15075 $parsedUrl = parse_url($url);
15076 if ((isset($parsedUrl['scheme']) && in_array($parsedUrl['scheme'], ['javascript', 'mailto', 'tel'])) || strpos($url, '#') === 0) {
15077 return $url;
15078 }
15079
15080 if (!empty($parsedUrl['query'])) {
15081 // Use parse_str() function to parse the string passed via URL
15082 $urlQuery = '';
15083 parse_str($parsedUrl['query'], $urlQuery);
15084 if (!isset($urlQuery['backtopage']) && isset($params['backtopage'])) {
15085 $url .= '&amp;backtopage=' . urlencode($params['backtopage']);
15086 }
15087 }
15088
15089 if (!isset($parsedUrl['scheme']) && $addDolUrlRoot) {
15090 $url = DOL_URL_ROOT . $url;
15091 }
15092
15093 return $url;
15094}
15095
15096
15103function dolGetButtonTitleSeparator($moreClass = "")
15104{
15105 return '<span class="button-title-separator ' . $moreClass . '" ></span>';
15106}
15107
15114function getFieldErrorIcon($fieldValidationErrorMsg)
15115{
15116 $out = '';
15117 if (!empty($fieldValidationErrorMsg)) {
15118 $out .= '<span class="field-error-icon classfortooltip" title="' . dol_escape_htmltag($fieldValidationErrorMsg, 1) . '" role="alert" >'; // role alert is used for accessibility
15119 $out .= '<span class="fa fa-exclamation-circle" aria-hidden="true" ></span>'; // For accessibility icon is separated and aria-hidden
15120 $out .= '</span>';
15121 }
15122
15123 return $out;
15124}
15125
15138function dolGetButtonTitle($label, $helpText = '', $iconClass = 'fa fa-file', $url = '', $id = '', $status = 1, $params = array())
15139{
15140 global $langs, $user;
15141
15142 // Actually this conf is used in css too for external module compatibility and smooth transition to this function
15143 if (getDolGlobalString('MAIN_BUTTON_HIDE_UNAUTHORIZED') && (!$user->admin) && $status <= 0) {
15144 return '';
15145 }
15146 // Fix old picto fa-th-list to use fa-grid-vertical instead
15147 if ($iconClass == 'fa fa-th-list imgforviewmode') {
15148 $iconClass = ' fa fa-grip-horizontal imgforviewmode';
15149 }
15150
15151 $class = 'btnTitle';
15152 if (in_array($iconClass, array('fa fa-plus-circle', 'fa fa-plus-circle size15x', 'fa fa-comment-dots', 'fa fa-paper-plane'))) {
15153 $class .= ' btnTitlePlus';
15154 }
15155 $useclassfortooltip = 1;
15156
15157 if (!empty($params['morecss'])) {
15158 $class .= ' ' . $params['morecss'];
15159 }
15160
15161 $attr = array(
15162 'class' => $class,
15163 'href' => empty($url) ? '' : $url
15164 );
15165
15166 if (!empty($helpText)) {
15167 $attr['title'] = $helpText;
15168 } elseif ($label) { // empty($attr['title']) &&
15169 $attr['title'] = $label;
15170 $useclassfortooltip = 0;
15171 }
15172
15173 if ($status == 2) {
15174 $attr['class'] .= ' btnTitleSelected';
15175 } elseif ($status <= 0) {
15176 $attr['class'] .= ' refused';
15177
15178 $attr['href'] = '';
15179
15180 if ($status == -1) { // disable
15181 $attr['title'] = $langs->transnoentitiesnoconv("FeatureDisabled");
15182 } elseif ($status == 0) { // Not enough permissions
15183 $attr['title'] = $langs->transnoentitiesnoconv("NotEnoughPermissions");
15184 }
15185 }
15186
15187 if (!empty($attr['title']) && $useclassfortooltip) {
15188 $attr['class'] .= ' classfortooltip';
15189 }
15190
15191 if (!empty($id)) {
15192 $attr['id'] = $id;
15193 }
15194
15195 // Override attr
15196 if (!empty($params['attr']) && is_array($params['attr'])) {
15197 foreach ($params['attr'] as $key => $value) {
15198 if ($key == 'class') {
15199 $attr['class'] .= ' ' . $value;
15200 } elseif ($key == 'classOverride') {
15201 $attr['class'] = $value;
15202 } else {
15203 $attr[$key] = $value;
15204 }
15205 }
15206 }
15207
15208 if (isset($attr['href']) && empty($attr['href'])) {
15209 unset($attr['href']);
15210 }
15211
15212 // TODO : add a hook
15213
15214 // Generate attributes with escapement
15215 $TCompiledAttr = array();
15216 foreach ($attr as $key => $value) {
15217 $TCompiledAttr[] = $key . '="' . dol_escape_htmltag($value) . '"'; // Do not use dolPrintHTMLForAttribute() here, we must accept "javascript:string"
15218 }
15219
15220 $compiledAttributes = (empty($TCompiledAttr) ? '' : implode(' ', $TCompiledAttr));
15221
15222 $tag = (empty($attr['href']) ? 'span' : 'a');
15223
15224 $button = '<' . $tag . ' ' . $compiledAttributes . '>';
15225 $button .= '<span class="' . $iconClass . ' valignmiddle btnTitle-icon"></span>';
15226 if (!empty($params['forcenohideoftext'])) {
15227 $button .= '<span class="valignmiddle text-plus-circle btnTitle-label' . (empty($params['forcenohideoftext']) ? ' hideonsmartphone' : '') . '">' . $label . '</span>';
15228 }
15229 $button .= '</' . $tag . '>';
15230
15231 return $button;
15232}
15233
15244function getElementProperties($elementType)
15245{
15246 global $conf, $db, $hookmanager;
15247
15248 $regs = array();
15249
15250 //$element_type='facture';
15251
15252 $classfile = $classname = $classpath = $subdir = $dir_output = $dir_temp = $parent_element = '';
15253
15254 // Parse element/subelement
15255 $module = $elementType;
15256 $element = $elementType;
15257 $subelement = $elementType;
15258 $table_element = $elementType;
15259
15260 // If we ask a resource form external module (instead of default path)
15261 if (preg_match('/^([^@]+)@([^@]+)$/i', $elementType, $regs)) { // 'myobject@mymodule'
15262 $element = $subelement = $regs[1];
15263 $module = $regs[2];
15264 }
15265
15266 // If we ask a resource for a string with an element and a subelement
15267 // Example 'project_task'
15268 if (preg_match('/^([^_]+)_([^_]+)/i', $element, $regs)) { // 'myobject_mysubobject' with myobject=mymodule
15269 $module = $element = $regs[1];
15270 $subelement = $regs[2];
15271 }
15272
15273 // Object lines will use parent classpath and module ref
15274 if (substr($elementType, -3) == 'det') {
15275 $module = preg_replace('/det$/', '', $element);
15276 $subelement = preg_replace('/det$/', '', $subelement);
15277 $classpath = $module . '/class';
15278 $classfile = $module;
15279 $classname = preg_replace('/det$/', 'Line', $element);
15280 if (in_array($module, array('expedition', 'propale', 'facture', 'contrat', 'fichinter', 'supplier_order', 'commandefournisseur'))) {
15281 $classname = preg_replace('/det$/', 'Ligne', $element);
15282 }
15283 }
15284 // For compatibility and to work with non standard path
15285 if ($elementType == "action" || $elementType == "actioncomm") {
15286 $classpath = 'comm/action/class';
15287 $subelement = 'Actioncomm';
15288 $module = 'agenda';
15289 $table_element = 'actioncomm';
15290 } elseif ($elementType == 'cronjob') {
15291 $classpath = 'cron/class';
15292 $module = 'cron';
15293 $table_element = 'cron';
15294 } elseif ($elementType == 'adherent_type') {
15295 $classpath = 'adherents/class';
15296 $classfile = 'adherent_type';
15297 $module = 'adherent';
15298 $subelement = 'adherent_type';
15299 $classname = 'AdherentType';
15300 $table_element = 'adherent_type';
15301 } elseif ($elementType == 'bank_account') {
15302 $classpath = 'compta/bank/class';
15303 $module = 'bank'; // We need $conf->bank->dir_output and not $conf->banque->dir_output
15304 $classfile = 'account';
15305 $classname = 'Account';
15306 } elseif ($elementType == 'bank_line') {
15307 $classpath = 'compta/bank/class';
15308 $module = 'bank'; // We need $conf->bank->dir_output and not $conf->banque->dir_output
15309 $classfile = 'account';
15310 $classname = 'AccountLine';
15311 } elseif ($elementType == 'category') {
15312 $classpath = 'categories/class';
15313 $module = 'categorie';
15314 $subelement = 'categorie';
15315 $table_element = 'categorie';
15316 } elseif ($elementType == 'contact') {
15317 $classpath = 'contact/class';
15318 $classfile = 'contact';
15319 $module = 'societe';
15320 $subelement = 'contact';
15321 $table_element = 'socpeople';
15322 } elseif ($elementType == 'inventory') {
15323 $module = 'product';
15324 $classpath = 'product/inventory/class';
15325 } elseif ($elementType == 'inventoryline') {
15326 $module = 'product';
15327 $classpath = 'product/inventory/class';
15328 $table_element = 'inventorydet';
15329 $parent_element = 'inventory';
15330 } elseif ($elementType == 'stock' || $elementType == 'entrepot' || $elementType == 'warehouse') {
15331 $module = 'stock';
15332 $classpath = 'product/stock/class';
15333 $classfile = 'entrepot';
15334 $classname = 'Entrepot';
15335 $table_element = 'entrepot';
15336 } elseif ($elementType == 'project') {
15337 $classpath = 'projet/class';
15338 $module = 'projet';
15339 $table_element = 'projet';
15340 } elseif ($elementType == 'project_task') {
15341 $classpath = 'projet/class';
15342 $module = 'projet';
15343 $subelement = 'task';
15344 $table_element = 'projet_task';
15345 } elseif ($elementType == 'mo') {
15346 $classpath = 'mrp/class';
15347 $module = 'mrp';
15348 $classfile = 'mo';
15349 $classname = 'Mo';
15350 $table_element = 'mrp_mo';
15351 } elseif ($elementType == 'facture' || $elementType == 'invoice') {
15352 $classpath = 'compta/facture/class';
15353 $module = 'facture';
15354 $subelement = 'facture';
15355 $table_element = 'facture';
15356 } elseif ($elementType == 'facturedet') {
15357 $classpath = 'compta/facture/class';
15358 $classfile = 'facture';
15359 $classname = 'FactureLigne';
15360 $module = 'facture';
15361 $table_element = 'facturedet';
15362 $parent_element = 'facture';
15363 } elseif ($elementType == 'facturerec' || $elementType == 'facture_rec') {
15364 $classpath = 'compta/facture/class';
15365 $classfile = 'facture-rec';
15366 $module = 'facture';
15367 $classname = 'FactureRec';
15368 } elseif ($elementType == 'commande' || $elementType == 'order') {
15369 $classpath = 'commande/class';
15370 $module = 'commande';
15371 $subelement = 'commande';
15372 $table_element = 'commande';
15373 } elseif ($elementType == 'commandedet') {
15374 $classpath = 'commande/class';
15375 $classfile = 'commande';
15376 $classname = 'OrderLine';
15377 $module = 'commande';
15378 $table_element = 'commandedet';
15379 $parent_element = 'commande';
15380 } elseif ($elementType == 'propal') {
15381 $classpath = 'comm/propal/class';
15382 $table_element = 'propal';
15383 } elseif ($elementType == 'propaldet') {
15384 $classpath = 'comm/propal/class';
15385 $classfile = 'propal';
15386 $subelement = 'propaleligne';
15387 $module = 'propal';
15388 $table_element = 'propaldet';
15389 $parent_element = 'propal';
15390 } elseif ($elementType == 'shipping' || $elementType == 'shipment') {
15391 $classpath = 'expedition/class';
15392 $classfile = 'expedition';
15393 $classname = 'Expedition';
15394 $module = 'expedition';
15395 $table_element = 'expedition';
15396 } elseif ($elementType == 'expeditiondet' || $elementType == 'shippingdet') {
15397 $classpath = 'expedition/class';
15398 $classfile = 'expedition';
15399 $classname = 'ExpeditionLigne';
15400 $module = 'expedition';
15401 $table_element = 'expeditiondet';
15402 $parent_element = 'expedition';
15403 } elseif ($elementType == 'delivery_note') {
15404 $classpath = 'delivery/class';
15405 $subelement = 'delivery';
15406 $module = 'expedition';
15407 } elseif ($elementType == 'delivery') {
15408 $classpath = 'delivery/class';
15409 $subelement = 'delivery';
15410 $module = 'expedition';
15411 } elseif ($elementType == 'deliverydet') {
15412 // @todo
15413 } elseif ($elementType == 'supplier_proposal') {
15414 $classpath = 'supplier_proposal/class';
15415 $module = 'supplier_proposal';
15416 $element = 'supplierproposal';
15417 $classfile = 'supplier_proposal';
15418 $subelement = 'supplierproposal';
15419 } elseif ($elementType == 'supplier_proposaldet') {
15420 $classpath = 'supplier_proposal/class';
15421 $module = 'supplier_proposal';
15422 $classfile = 'supplier_proposal';
15423 $classname = 'SupplierProposalLine';
15424 $table_element = 'supplier_proposaldet';
15425 $parent_element = 'supplier_proposal';
15426 } elseif ($elementType == 'contract') {
15427 $classpath = 'contrat/class';
15428 $module = 'contrat';
15429 $subelement = 'contrat';
15430 $table_element = 'contract';
15431 } elseif ($elementType == 'contratdet') {
15432 $classpath = 'contrat/class';
15433 $module = 'contrat';
15434 $table_element = 'contratdet';
15435 $parent_element = 'contrat';
15436 } elseif ($elementType == 'mailing') {
15437 $classpath = 'comm/mailing/class';
15438 $module = 'mailing';
15439 $classfile = 'mailing';
15440 $classname = 'Mailing';
15441 $subelement = '';
15442 } elseif ($elementType == 'member' || $elementType == 'adherent') {
15443 $classpath = 'adherents/class';
15444 $module = 'adherent';
15445 $subelement = 'adherent';
15446 $table_element = 'adherent';
15447 } elseif ($elementType == 'subscription') {
15448 $classpath = 'adherents/class';
15449 $classfile = 'subscription';
15450 $module = 'adherent';
15451 $subelement = 'subscription';
15452 $classname = 'Subscription';
15453 $table_element = 'subscription';
15454 } elseif ($elementType == 'usergroup') {
15455 $classpath = 'user/class';
15456 $module = 'user';
15457 } elseif ($elementType == 'mo' || $elementType == 'mrp') {
15458 $classpath = 'mrp/class';
15459 $classfile = 'mo';
15460 $classname = 'Mo';
15461 $module = 'mrp';
15462 $subelement = '';
15463 $table_element = 'mrp_mo';
15464 } elseif ($elementType == 'mrp_production') {
15465 $classpath = 'mrp/class';
15466 $classfile = 'mo';
15467 $classname = 'MoLine';
15468 $module = 'mrp';
15469 $subelement = '';
15470 $table_element = 'mrp_production';
15471 $parent_element = 'mo';
15472 } elseif ($elementType == 'cabinetmed_cons') {
15473 $classpath = 'cabinetmed/class';
15474 $module = 'cabinetmed';
15475 $subelement = 'cabinetmedcons';
15476 $table_element = 'cabinetmedcons';
15477 } elseif ($elementType == 'fichinter') {
15478 $classpath = 'fichinter/class';
15479 $module = 'ficheinter';
15480 $subelement = 'fichinter';
15481 $table_element = 'fichinter';
15482 } elseif ($elementType == 'dolresource' || $elementType == 'resource') {
15483 $classpath = 'resource/class';
15484 $module = 'resource';
15485 $subelement = 'dolresource';
15486 $table_element = 'resource';
15487 } elseif ($elementType == 'opensurvey_sondage') {
15488 $classpath = 'opensurvey/class';
15489 $module = 'opensurvey';
15490 $subelement = 'opensurveysondage';
15491 } elseif ($elementType == 'order_supplier' || $elementType == 'supplier_order' || $elementType == 'commande_fournisseur' || $elementType == 'commandefournisseur') {
15492 $classpath = 'fourn/class';
15493 $module = 'fournisseur';
15494 $classfile = 'fournisseur.commande';
15495 $element = 'order_supplier';
15496 $subelement = '';
15497 $classname = 'CommandeFournisseur';
15498 $table_element = 'commande_fournisseur';
15499 } elseif ($elementType == 'commande_fournisseurdet') {
15500 $classpath = 'fourn/class';
15501 $module = 'fournisseur';
15502 $classfile = 'fournisseur.commande';
15503 $element = 'commande_fournisseurdet';
15504 $subelement = '';
15505 $classname = 'CommandeFournisseurLigne';
15506 $table_element = 'commande_fournisseurdet';
15507 $parent_element = 'commande_fournisseur';
15508 } elseif ($elementType == 'invoice_supplier' || $elementType == 'supplier_invoice' || $elementType == 'facture_fourn') {
15509 $classpath = 'fourn/class';
15510 $module = 'fournisseur';
15511 $classfile = 'fournisseur.facture';
15512 $element = 'invoice_supplier';
15513 $subelement = '';
15514 $classname = 'FactureFournisseur';
15515 $table_element = 'facture_fourn';
15516 } elseif ($elementType == 'facture_fourn_det') {
15517 $classpath = 'fourn/class';
15518 $module = 'fournisseur';
15519 $classfile = 'fournisseur.facture';
15520 $element = 'facture_fourn_det';
15521 $subelement = '';
15522 $classname = 'SupplierInvoiceLine';
15523 $table_element = 'facture_fourn_det';
15524 $parent_element = 'invoice_supplier';
15525 } elseif ($elementType == "service") {
15526 $classpath = 'product/class';
15527 $subelement = 'product';
15528 $table_element = 'product';
15529 } elseif ($elementType == 'product_attribute') {
15530 $module = 'variants';
15531 $element = 'product_attribute';
15532 $subelement = 'product_attribute';
15533 $classpath = 'variants/class';
15534 $classfile = 'ProductAttribute';
15535 $classname = 'ProductAttribute';
15536 $table_element = 'product_attribute';
15537 } elseif ($elementType == 'product_attribute_value') {
15538 $module = 'variants';
15539 $element = 'product_attribute_value';
15540 $subelement = 'product_attribute_value';
15541 $classpath = 'variants/class';
15542 $classfile = 'ProductAttributeValue';
15543 $classname = 'ProductAttributeValue';
15544 $table_element = 'product_attribute_value';
15545 $parent_element = 'product_attribute';
15546 } elseif ($elementType == 'salary') {
15547 $classpath = 'salaries/class';
15548 $module = 'salaries';
15549 } elseif ($elementType == 'payment_salary') {
15550 $classpath = 'salaries/class';
15551 $classfile = 'paymentsalary';
15552 $classname = 'PaymentSalary';
15553 $module = 'salaries';
15554 } elseif ($elementType == 'productlot') {
15555 $module = 'productbatch';
15556 $classpath = 'product/stock/class';
15557 $classfile = 'productlot';
15558 $classname = 'Productlot';
15559 $element = 'productlot';
15560 $subelement = '';
15561 $table_element = 'product_lot';
15562 } elseif ($elementType == 'societeaccount') {
15563 $classpath = 'societe/class';
15564 $classfile = 'societeaccount';
15565 $classname = 'SocieteAccount';
15566 $module = 'societe';
15567 } elseif ($elementType == 'websitepage' || $elementType == 'website_page') {
15568 $classpath = 'website/class';
15569 $classfile = 'websitepage';
15570 $classname = 'Websitepage';
15571 $module = 'website';
15572 $subelement = 'websitepage';
15573 $table_element = 'website_page';
15574 } elseif ($elementType == 'fiscalyear') {
15575 $classpath = 'core/class';
15576 $module = 'accounting';
15577 $subelement = 'fiscalyear';
15578 } elseif ($elementType == 'chargesociales') {
15579 $classpath = 'compta/sociales/class';
15580 $module = 'tax';
15581 $table_element = 'chargesociales';
15582 } elseif ($elementType == 'tva') {
15583 $classpath = 'compta/tva/class';
15584 $module = 'tax';
15585 $subdir = '/vat';
15586 $table_element = 'tva';
15587 } elseif ($elementType == 'emailsenderprofile') {
15588 $module = '';
15589 $classpath = 'core/class';
15590 $classfile = 'emailsenderprofile';
15591 $classname = 'EmailSenderProfile';
15592 $table_element = 'c_email_senderprofile';
15593 $subelement = '';
15594 } elseif ($elementType == 'conferenceorboothattendee') {
15595 $classpath = 'eventorganization/class';
15596 $classfile = 'conferenceorboothattendee';
15597 $classname = 'ConferenceOrBoothAttendee';
15598 $module = 'eventorganization';
15599 } elseif ($elementType == 'conferenceorbooth') {
15600 $classpath = 'eventorganization/class';
15601 $classfile = 'conferenceorbooth';
15602 $classname = 'ConferenceOrBooth';
15603 $module = 'eventorganization';
15604 } elseif ($elementType == 'ccountry') {
15605 $module = '';
15606 $classpath = 'core/class';
15607 $classfile = 'ccountry';
15608 $classname = 'Ccountry';
15609 $table_element = 'c_country';
15610 $subelement = '';
15611 } elseif ($elementType == 'ecmfiles') {
15612 $module = 'ecm';
15613 $classpath = 'ecm/class';
15614 $classfile = 'ecmfiles';
15615 $classname = 'Ecmfiles';
15616 $table_element = 'ecmfiles';
15617 $subelement = '';
15618 } elseif ($elementType == 'knowledgerecord' || $elementType == 'knowledgemanagement') {
15619 $module = 'knowledgemanagement';
15620 $classpath = 'knowledgemanagement/class';
15621 $classfile = 'knowledgerecord';
15622 $classname = 'KnowledgeRecord';
15623 $table_element = 'knowledgemanagement_knowledgerecord';
15624 $subelement = '';
15625 } elseif ($elementType == 'customer') {
15626 $module = 'societe';
15627 $classpath = 'societe/class';
15628 $classfile = 'client';
15629 $classname = 'Client';
15630 $table_element = 'societe';
15631 $subelement = '';
15632 } elseif ($elementType == 'fournisseur' || $elementType == 'supplier') {
15633 $module = 'societe';
15634 $classpath = 'fourn/class';
15635 $classfile = 'fournisseur';
15636 $classname = 'Fournisseur';
15637 $table_element = 'societe';
15638 $subelement = '';
15639 } elseif ($elementType == 'recruitmentcandidature') {
15640 $module = 'recruitment';
15641 $classfile = 'recruitmentcandidature';
15642 $classpath = 'recruitment/class';
15643 $classname = 'RecruitmentCandidature';
15644 $subelement = 'recruitmentcandidature';
15645 $subdir = '/recruitmentcandidature';
15646 } elseif ($elementType == 'recruitmentjobposition') {
15647 $module = 'recruitment';
15648 $classfile = 'recruitmentjobposition';
15649 $classpath = 'recruitment/class';
15650 $classname = 'RecruitmentJobPosition';
15651 $subelement = 'recruitmentjobposition';
15652 $subdir = '/recruitmentjobposition';
15653 }
15654
15655
15656 if (empty($classfile)) {
15657 $classfile = strtolower($subelement);
15658 }
15659 if (empty($classname)) {
15660 $classname = ucfirst($subelement);
15661 }
15662 if (empty($classpath)) {
15663 $classpath = $module . '/class';
15664 }
15665
15666 //print 'getElementProperties subdir='.$subdir;
15667
15668 // Set dir_output
15669 if ($module && isset($conf->$module)) { // The generic case
15670 if (!empty($conf->$module->multidir_output[$conf->entity])) {
15671 $dir_output = $conf->$module->multidir_output[$conf->entity];
15672 } elseif (!empty($conf->$module->output[$conf->entity])) {
15673 $dir_output = $conf->$module->output[$conf->entity];
15674 } elseif (!empty($conf->$module->dir_output)) {
15675 $dir_output = $conf->$module->dir_output;
15676 }
15677 if (!empty($conf->$module->multidir_temp[$conf->entity])) {
15678 $dir_temp = $conf->$module->multidir_temp[$conf->entity];
15679 } elseif (!empty($conf->$module->temp[$conf->entity])) {
15680 $dir_temp = $conf->$module->temp[$conf->entity];
15681 } elseif (!empty($conf->$module->dir_temp)) {
15682 $dir_temp = $conf->$module->dir_temp;
15683 }
15684 }
15685
15686 // Overwrite value for special cases
15687 if ($element == 'order_supplier' && isModEnabled('fournisseur')) {
15688 $dir_output = $conf->fournisseur->commande->dir_output;
15689 $dir_temp = $conf->fournisseur->commande->dir_temp;
15690 } elseif ($element == 'invoice_supplier' && isModEnabled('fournisseur')) {
15691 $dir_output = $conf->fournisseur->facture->dir_output;
15692 $dir_temp = $conf->fournisseur->facture->dir_temp;
15693 }
15694 $dir_output .= $subdir;
15695 $dir_temp .= $subdir;
15696
15697 $elementProperties = array(
15698 'module' => $module,
15699 'element' => $element,
15700 'table_element' => $table_element,
15701 'subelement' => $subelement,
15702 'classpath' => $classpath,
15703 'classfile' => $classfile,
15704 'classname' => $classname,
15705 'dir_output' => $dir_output,
15706 'dir_temp' => $dir_temp,
15707 'parent_element' => $parent_element,
15708 );
15709
15710
15711 // Add hook
15712 if (!is_object($hookmanager)) {
15713 include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
15714 $hookmanager = new HookManager($db);
15715 }
15716 $hookmanager->initHooks(array('elementproperties'));
15717
15718
15719 // Hook params
15720 $parameters = array(
15721 'elementType' => $elementType,
15722 'elementProperties' => $elementProperties
15723 );
15724
15725 $reshook = $hookmanager->executeHooks('getElementProperties', $parameters);
15726
15727 if ($reshook) {
15728 $elementProperties = $hookmanager->resArray;
15729 } elseif (!empty($hookmanager->resArray) && is_array($hookmanager->resArray)) { // resArray is always an array but for security against misconfigured external modules
15730 $elementProperties = array_replace($elementProperties, $hookmanager->resArray);
15731 }
15732
15733 // context of elementproperties doesn't need to exist out of this function so delete it to avoid elementproperties context is equal to all
15734 if (($key = array_search('elementproperties', $hookmanager->contextarray)) !== false) {
15735 unset($hookmanager->contextarray[$key]);
15736 }
15737
15738 return $elementProperties;
15739}
15740
15754function fetchObjectByElement($element_id, $element_type, $element_ref = '', $useCache = 0, $maxCacheByType = 10)
15755{
15756 global $db, $conf;
15757
15758 $ret = 0;
15759
15760 $element_prop = getElementProperties($element_type);
15761 //var_dump($element_prop); exit;
15762
15763 if ($element_prop['module'] == 'product' || $element_prop['module'] == 'service') {
15764 // For example, for an extrafield 'product' (shared for both product and service) that is a link to an object,
15765 // this is called with $element_type = 'product' when we need element properties of a service, we must return a product. If we create the
15766 // extrafield for a service, it is not supported and not found when editing the product/service card. So we must keep 'product' for extrafields
15767 // of service and we will return properties of a product.
15768 $ismodenabled = (isModEnabled('product') || isModEnabled('service'));
15769 } elseif ($element_prop['module'] == 'societeaccount') {
15770 $ismodenabled = isModEnabled('website') || isModEnabled('webportal');
15771 } else {
15772 $ismodenabled = isModEnabled($element_prop['module']);
15773 }
15774 //var_dump('element_type='.$element_type);
15775 //var_dump($element_prop);
15776 //var_dump($element_prop['module'].' '.$ismodenabled);
15777 if (is_array($element_prop) && (empty($element_prop['module']) || $ismodenabled)) {
15778 if ($useCache === 1 && $element_id > 0
15779 && !empty($conf->cache['fetchObjectByElement'][$element_type])
15780 && !empty($conf->cache['fetchObjectByElement'][$element_type][$element_id])
15781 && is_object($conf->cache['fetchObjectByElement'][$element_type][$element_id])
15782 ) {
15783 return $conf->cache['fetchObjectByElement'][$element_type][$element_id];
15784 }
15785
15786 dol_include_once('/' . $element_prop['classpath'] . '/' . $element_prop['classfile'] . '.class.php');
15787
15788 if (class_exists($element_prop['classname'])) {
15789 $className = $element_prop['classname'];
15790 $objecttmp = new $className($db);
15791 '@phan-var-force CommonObject $objecttmp';
15794 if ($element_id > 0 || !empty($element_ref)) {
15795 $ret = $objecttmp->fetch($element_id, $element_ref);
15796 if ($ret >= 0) {
15797 if (empty($objecttmp->module)) {
15798 $objecttmp->module = $element_prop['module'];
15799 }
15800
15801 if ($useCache > 0) {
15802 if (!isset($conf->cache['fetchObjectByElement'][$element_type])) {
15803 $conf->cache['fetchObjectByElement'][$element_type] = [];
15804 }
15805
15806 // Manage cache limit
15807 if (! empty($conf->cache['fetchObjectByElement'][$element_type]) && is_array($conf->cache['fetchObjectByElement'][$element_type]) && count($conf->cache['fetchObjectByElement'][$element_type]) >= $maxCacheByType) {
15808 array_shift($conf->cache['fetchObjectByElement'][$element_type]);
15809 }
15810
15811 $conf->cache['fetchObjectByElement'][$element_type][$element_id] = $objecttmp;
15812 }
15813
15814 return $objecttmp;
15815 }
15816 } else {
15817 return $objecttmp; // returned an object without fetch
15818 }
15819 } else {
15820 dol_syslog($element_prop['classname'] . ' doesn\'t exists in /' . $element_prop['classpath'] . '/' . $element_prop['classfile'] . '.class.php');
15821 return -1;
15822 }
15823 }
15824
15825 return $ret;
15826}
15827
15833function getExecutableContent()
15834{
15835 $arrayofregexextension = array(
15836 'htm',
15837 'html',
15838 'shtml',
15839 'js',
15840 'phar',
15841 'php',
15842 'php3',
15843 'php4',
15844 'php5',
15845 'phtml',
15846 'pht',
15847 'pl',
15848 'py',
15849 'cgi',
15850 'ksh',
15851 'sh',
15852 'shtml',
15853 'bash',
15854 'bat',
15855 'cmd',
15856 'wpk',
15857 'exe',
15858 'dmg',
15859 'appimage'
15860 );
15861
15862 return $arrayofregexextension;
15863}
15864
15871function isAFileWithExecutableContent($filename)
15872{
15873 $arrayofregexextension = getExecutableContent();
15874
15875 foreach ($arrayofregexextension as $fileextension) {
15876 if (preg_match('/\.' . preg_quote($fileextension, '/') . '$/i', $filename)) {
15877 return true;
15878 }
15879 }
15880
15881 return false;
15882}
15883
15891function newToken()
15892{
15893 return empty($_SESSION['newtoken']) ? '' : $_SESSION['newtoken'];
15894}
15895
15903function currentToken()
15904{
15905 return isset($_SESSION['token']) ? $_SESSION['token'] : '';
15906}
15907
15913function getNonce()
15914{
15915 global $conf;
15916
15917 if (empty($conf->cache['nonce'])) {
15918 include_once DOL_DOCUMENT_ROOT . '/core/lib/security.lib.php';
15919 $conf->cache['nonce'] = dolGetRandomBytes(8);
15920 }
15921
15922 return $conf->cache['nonce'];
15923}
15924
15925
15939function startSimpleTable($header, $link = "", $arguments = "", $emptyColumns = 0, $number = -1, $pictofulllist = '')
15940{
15941 global $langs;
15942
15943 print '<div class="div-table-responsive-no-min">';
15944 print '<table class="noborder centpercent">';
15945 print '<tr class="liste_titre">';
15946
15947 print ($emptyColumns < 1) ? '<th>' : '<th colspan="' . ($emptyColumns + 1) . '">';
15948
15949 print '<span class="valignmiddle">' . $langs->trans($header) . '</span>';
15950
15951 if (!empty($link)) {
15952 if (!empty($arguments)) {
15953 print '<a href="' . DOL_URL_ROOT . '/' . $link . '?' . $arguments . '">';
15954 } else {
15955 print '<a href="' . DOL_URL_ROOT . '/' . $link . '">';
15956 }
15957 }
15958
15959 if ($number > -1) {
15960 print '<span class="badge marginleftonlyshort">' . $number . '</span>';
15961 } elseif (!empty($link)) {
15962 print '<span class="badge marginleftonlyshort">...</span>';
15963 }
15964
15965 if (!empty($link)) {
15966 print '</a>';
15967 }
15968
15969 print '</th>';
15970
15971 if ($number < 0 && !empty($link)) {
15972 print '<th class="right">';
15973 print '</th>';
15974 }
15975
15976 print '</tr>';
15977}
15978
15987function finishSimpleTable($addLineBreak = false)
15988{
15989 print '</table>';
15990 print '</div>';
15991
15992 if ($addLineBreak) {
15993 print '<br>';
15994 }
15995}
15996
16008function addSummaryTableLine($tableColumnCount, $num, $nbofloop = 0, $total = 0, $noneWord = "None", $extraRightColumn = false)
16009{
16010 global $langs;
16011
16012 if ($num === 0) {
16013 print '<tr class="oddeven">';
16014 print '<td colspan="' . $tableColumnCount . '"><span class="opacitymedium">' . $langs->trans($noneWord) . '</span></td>';
16015 print '</tr>';
16016 return;
16017 }
16018
16019 if ($nbofloop === 0) {
16020 // don't show a summary line
16021 return;
16022 }
16023
16024 /* Case already handled above, commented to satisfy phpstan.
16025 if ($num === 0) {
16026 $colspan = $tableColumnCount;
16027 } else
16028 */
16029 if ($num > $nbofloop) {
16030 $colspan = $tableColumnCount;
16031 } else {
16032 $colspan = $tableColumnCount - 1;
16033 }
16034
16035 if ($extraRightColumn) {
16036 $colspan--;
16037 }
16038
16039 print '<tr class="liste_total">';
16040
16041 if ($nbofloop > 0 && $num > $nbofloop) {
16042 print '<td colspan="' . $colspan . '" class="right">' . $langs->trans("XMoreLines", ($num - $nbofloop)) . '</td>';
16043 } else {
16044 print '<td colspan="' . $colspan . '" class="right"> ' . $langs->trans("Total") . '</td>';
16045 print '<td class="right centpercent">' . price($total) . '</td>';
16046 }
16047
16048 if ($extraRightColumn) {
16049 print '<td></td>';
16050 }
16051
16052 print '</tr>';
16053}
16054
16063function readfileLowMemory($fullpath_original_file_osencoded, $method = -1)
16064{
16065 if ($method == -1) {
16066 $method = 0;
16067 if (getDolGlobalString('MAIN_FORCE_READFILE_WITH_FREAD')) {
16068 $method = 1;
16069 }
16070 if (getDolGlobalString('MAIN_FORCE_READFILE_WITH_STREAM_COPY')) {
16071 $method = 2;
16072 }
16073 }
16074
16075 // Be sure we don't have output buffering enabled to have readfile working correctly
16076 $level = ob_get_level();
16077 ob_start();
16078 while (ob_get_level() > $level) {
16079 ob_end_clean();
16080 }
16081
16082 // Solution 0
16083 if ($method == 0) {
16084 readfile($fullpath_original_file_osencoded);
16085 } elseif ($method == 1) {
16086 // Solution 1
16087 $handle = fopen($fullpath_original_file_osencoded, "rb");
16088 while (!feof($handle)) {
16089 print fread($handle, 8192);
16090 }
16091 fclose($handle);
16092 } elseif ($method == 2) {
16093 // Solution 2
16094 $handle1 = fopen($fullpath_original_file_osencoded, "rb");
16095 $handle2 = fopen("php://output", "wb");
16096 stream_copy_to_stream($handle1, $handle2);
16097 fclose($handle1);
16098 fclose($handle2);
16099 }
16100}
16101
16111function showValueWithClipboardCPButton($valuetocopy, $showonlyonhover = 1, $texttoshow = '')
16112{
16113 global $langs;
16114
16115 $tag = 'span'; // Using div (like any style of type 'block') does not work when using the js copy code.
16116
16117 $result = '<span class="clipboardCP' . ($showonlyonhover ? ' clipboardCPShowOnHover valignmiddle' : '') . '">';
16118 if ($texttoshow === 'none') {
16119 $result .= '<' . $tag . ' class="clipboardCPValue hidewithsize">' . dol_escape_htmltag($valuetocopy, 1, 1) . '</' . $tag . '>';
16120 $result .= '<span class="clipboardCPValueToPrint"></span>';
16121 } elseif ($texttoshow) {
16122 $result .= '<' . $tag . ' class="clipboardCPValue hidewithsize">' . dol_escape_htmltag($valuetocopy, 1, 1) . '</' . $tag . '>';
16123 $result .= '<span class="clipboardCPValueToPrint">' . dol_escape_htmltag($texttoshow, 1, 1) . '</span>';
16124 } else {
16125 $result .= '<' . $tag . ' class="clipboardCPValue">' . dol_escape_htmltag($valuetocopy, 1, 1) . '</' . $tag . '>';
16126 }
16127 $result .= '<span class="clipboardCPButton far fa-clipboard opacitymedium paddingleft pictomodule" title="' . dolPrintHTML($langs->trans("ClickToCopyToClipboard")) . '"></span>';
16128 $result .= img_picto('', 'tick', 'class="clipboardCPTick hidden paddingleft pictomodule"');
16129 $result .= '<span class="clipboardCPText"></span>';
16130 $result .= '</span>';
16131
16132 return $result;
16133}
16134
16135
16143function jsonOrUnserialize($stringtodecode, $assoc = true)
16144{
16145 $result = json_decode($stringtodecode, $assoc);
16146 if ($result === null) {
16147 $result = unserialize($stringtodecode, ['allowed_classes' => false]); // For backward compatibility. Is no more used in recent versions.
16148 }
16149
16150 return $result;
16151}
16152
16153
16171function forgeSQLFromUniversalSearchCriteria($filter, &$errorstr = '', $noand = 0, $nopar = 0, $noerror = 0, $forbiddenfields = array())
16172{
16173 global $db, $user;
16174
16175 if (is_null($filter) || !is_string($filter) || $filter === '') {
16176 return '';
16177 }
16178 if (!preg_match('/^\‍(.*\‍)$/', $filter)) { // If $filter does not start and end with ()
16179 $filter = '(' . $filter . ')';
16180 }
16181
16182 $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'
16183 $firstandlastparenthesis = 0;
16184
16185 if (!dolCheckFilters($filter, $errorstr, $firstandlastparenthesis)) {
16186 if ($noerror) {
16187 return '1 = 2';
16188 } else {
16189 return 'Filter syntax error - ' . $errorstr; // Bad balance of parenthesis, we return an error message or force a SQL not found
16190 }
16191 }
16192
16193 // Test the filter syntax
16194 $t = preg_replace_callback('/' . $regexstring . '/i', 'dolForgeDummyCriteriaCallback', $filter);
16195 $t = str_ireplace(array('and', 'or', ' '), '', $t); // Remove the only strings allowed between each () criteria
16196
16197 // If the string result contains something else than '()', the syntax was wrong
16198 if (preg_match('/[^\‍(\‍)]/', $t)) {
16199 $tmperrorstr = 'Bad syntax of the search string';
16200 $errorstr = 'Bad syntax of the search string: ' . $filter;
16201 if ($noerror) {
16202 return '1 = 2';
16203 } else {
16204 dol_syslog("forgeSQLFromUniversalSearchCriteria Filter error - " . $errorstr, LOG_WARNING);
16205 return 'Filter error - ' . $tmperrorstr; // Bad syntax of the search string, we return an error message or force a SQL not found
16206 }
16207 }
16208
16209 global $globalforbiddenfields; // For use by dolForgeSQLCriteriaCallback()
16210 $globalforbiddenfields = $forbiddenfields;
16211
16212 $ret = ($noand ? "" : " AND ") . ($nopar ? "" : '(');
16213 $ret .= preg_replace_callback('/' . $regexstring . '/i', 'dolForgeSQLCriteriaCallback', $filter);
16214 $ret .= ($nopar ? "" : ')');
16215
16216 if (is_object($db)) {
16217 $ret = str_replace('__NOW__', "'" . $db->idate(dol_now()) . "'", $ret);
16218 }
16219 if (is_object($user)) {
16220 $ret = str_replace('__USER_ID__', (string) $user->id, $ret);
16221 }
16222
16223 return $ret;
16224}
16225
16233function dolForgeExplodeAnd($sqlfilters)
16234{
16235 $arrayofandtags = array();
16236 $nbofchars = dol_strlen($sqlfilters);
16237
16238 $error = '';
16239 $parenthesislevel = 0;
16240 $result = dolCheckFilters($sqlfilters, $error, $parenthesislevel);
16241 if (!$result) {
16242 return array();
16243 }
16244 if ($parenthesislevel >= 1) {
16245 $sqlfilters = preg_replace('/^\‍(/', '', preg_replace('/\‍)$/', '', $sqlfilters));
16246 }
16247
16248 $i = 0;
16249 $s = '';
16250 $countparenthesis = 0;
16251 while ($i < $nbofchars) {
16252 $char = dol_substr($sqlfilters, $i, 1);
16253
16254 if ($char == '(') {
16255 $countparenthesis++;
16256 } elseif ($char == ')') {
16257 $countparenthesis--;
16258 }
16259
16260 if ($countparenthesis == 0) {
16261 $char2 = dol_substr($sqlfilters, $i + 1, 1);
16262 $char3 = dol_substr($sqlfilters, $i + 2, 1);
16263 if ($char == 'A' && $char2 == 'N' && $char3 == 'D') {
16264 // We found a AND
16265 $s = trim($s);
16266 if (!preg_match('/^\‍(.*\‍)$/', $s)) {
16267 $s = '(' . $s . ')';
16268 }
16269 $arrayofandtags[] = $s;
16270 $s = '';
16271 $i += 2;
16272 } else {
16273 $s .= $char;
16274 }
16275 } else {
16276 $s .= $char;
16277 }
16278 $i++;
16279 }
16280 if ($s) {
16281 $s = trim($s);
16282 if (!preg_match('/^\‍(.*\‍)$/', $s)) {
16283 $s = '(' . $s . ')';
16284 }
16285 $arrayofandtags[] = $s;
16286 }
16287
16288 return $arrayofandtags;
16289}
16290
16300function dolCheckFilters($sqlfilters, &$error = '', &$parenthesislevel = 0)
16301{
16302 //$regexstring='\‍(([^:\'\‍(\‍)]+:[^:\'\‍(\‍)]+:[^:\‍(\‍)]+)\‍)';
16303 //$tmp=preg_replace_all('/'.$regexstring.'/', '', $sqlfilters);
16304 $tmp = $sqlfilters;
16305
16306 $nb = dol_strlen($tmp);
16307 $counter = 0;
16308 $parenthesislevel = 0;
16309
16310 $error = '';
16311
16312 $i = 0;
16313 while ($i < $nb) {
16314 $char = dol_substr($tmp, $i, 1);
16315
16316 if ($char == '(') {
16317 if ($i == $parenthesislevel && $parenthesislevel == $counter) {
16318 // We open a parenthesis and it is the first char
16319 $parenthesislevel++;
16320 }
16321 $counter++;
16322 } elseif ($char == ')') {
16323 $nbcharremaining = ($nb - $i - 1);
16324 if ($nbcharremaining >= $counter) {
16325 $parenthesislevel = min($parenthesislevel, $counter - 1);
16326 }
16327 if ($parenthesislevel > $counter && $nbcharremaining >= $counter) {
16328 $parenthesislevel = $counter;
16329 }
16330 $counter--;
16331 }
16332
16333 if ($counter < 0) {
16334 $error = "Wrong balance of parenthesis in sqlfilters=" . $sqlfilters;
16335 $parenthesislevel = 0;
16336 dol_syslog($error, LOG_WARNING);
16337 return false;
16338 }
16339
16340 $i++;
16341 }
16342
16343 if ($counter > 0) {
16344 $error = "Wrong balance of parenthesis in sqlfilters=" . $sqlfilters;
16345 $parenthesislevel = 0;
16346 dol_syslog($error, LOG_WARNING);
16347 return false;
16348 }
16349
16350 return true;
16351}
16352
16360function dolForgeDummyCriteriaCallback($matches)
16361{
16362 //dol_syslog("Convert matches ".$matches[1]);
16363 if (empty($matches[1])) {
16364 return '';
16365 }
16366 $tmp = explode(':', $matches[1]);
16367 if (count($tmp) < 3) {
16368 return '';
16369 }
16370
16371 return '()'; // An empty criteria
16372}
16373
16383function dolForgeSQLCriteriaCallback($matches)
16384{
16385 global $db;
16386 global $globalforbiddenfields;
16387
16388 //dol_syslog("Convert matches ".$matches[1]);
16389 if (empty($matches[1])) {
16390 return '';
16391 }
16392 $tmp = explode(':', $matches[1], 3);
16393 if (count($tmp) < 3) {
16394 return '';
16395 }
16396
16397 // Add fields that are forbidden by the caller when using USF search criteria
16398 // so we can't guess them using sequantial comparisons
16399 $newforbiddenfields = $globalforbiddenfields;
16400 if (!is_array($newforbiddenfields)) {
16401 $newforbiddenfields = array();
16402 }
16403 // Add fields that are ALWAYS forbidden when using USF search criteria
16404 $newforbiddenfields[] = 'pass';
16405 $newforbiddenfields[] = 'pass_crypted';
16406 $newforbiddenfields[] = 'api_key';
16407
16408 $operand = preg_replace('/[^a-z0-9\._]/i', '', trim($tmp[0]));
16409
16410 // Test that operand is not a forbidden search field
16411 if (!empty($newforbiddenfields)) {
16412 $operandwithoutprefix = preg_replace('/^[a-z0-9_]+\./i', '', $operand); // Remove prefix like t. or o. or s. or u. or d. or ...
16413 if (in_array(strtolower($operandwithoutprefix), $newforbiddenfields)) {
16414 return '1=1';
16415 }
16416 }
16417
16418 $operator = strtoupper(preg_replace('/[^a-z<>!=]/i', '', trim($tmp[1])));
16419
16420 $realOperator = [
16421 'NOTLIKE' => 'NOT LIKE',
16422 'ISNOT' => 'IS NOT',
16423 'NOTIN' => 'NOT IN',
16424 '!=' => '<>',
16425 ];
16426
16427 if (array_key_exists($operator, $realOperator)) {
16428 $operator = $realOperator[$operator];
16429 }
16430
16431 $tmpescaped = $tmp[2];
16432
16433 //print "Case: ".$operator." ".$operand." ".$tmpescaped."\n";
16434
16435 $regbis = array();
16436
16437 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)
16438 global $dolibarr_allow_unsecured_select_in_extrafields_filter;
16439
16440 //if (!preg_match('/^\‍(.*\‍)$/', $tmpescaped)) {
16441 $tmpescaped2 = '(';
16442 // Explode and sanitize each element in list
16443 $tmpelemarray = explode(',', $tmpescaped);
16444 foreach ($tmpelemarray as $tmpkey => $tmpelem) {
16445 $reg = array();
16446 $tmpelem = trim($tmpelem);
16447 if (preg_match('/^\'(.*)\'$/', $tmpelem, $reg)) {
16448 $tmpelemarray[$tmpkey] = "'" . $db->escape($db->sanitize($reg[1], 2, 1, 1, 1)) . "'";
16449 $tmpelemarray[$tmpkey] = "'".$db->escape($db->sanitize($reg[1], 2, 1, 1, 1))."'";
16450 } elseif (preg_match('/^[0-9]+$/', (string) $tmpelem)) { // if only 0-9 chars, no .
16451 $tmpelemarray[$tmpkey] = (int) $tmpelem;
16452 } elseif (is_numeric((string) $tmpelem)) { // it can be a float with a .
16453 $tmpelemarray[$tmpkey] = (float) $tmpelem;
16454 } elseif (!empty($dolibarr_allow_unsecured_select_in_extrafields_filter)) {
16455 $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)
16456 } else {
16457 $tmpelemarray[$tmpkey] = preg_replace('/[^a-z0-9_]/i', '', $tmpelem); // it can be a name of field or a substitution variable like '__NOW__'
16458 }
16459 }
16460 $tmpescaped2 .= implode(',', $tmpelemarray);
16461 $tmpescaped2 .= ')';
16462
16463 $tmpescaped = $tmpescaped2;
16464 } elseif ($operator == 'LIKE' || $operator == 'NOT LIKE') {
16465 if (preg_match('/^\'([^\']*)\'$/', $tmpescaped, $regbis)) {
16466 $tmpescaped = $regbis[1];
16467 }
16468 //$tmpescaped = "'".$db->escape($db->escapeforlike($regbis[1]))."'";
16469 $tmpescaped = "'" . $db->escape($tmpescaped) . "'"; // We do not escape the _ and % so the LIKE will work as expected
16470 } elseif (preg_match('/^\'(.*)\'$/', $tmpescaped, $regbis)) {
16471 // TODO Retrieve type of field for $operand field name.
16472 // So we can complete format. For example we could complete a year with month and day.
16473 $tmpescaped = "'" . $db->escape($regbis[1]) . "'";
16474 } else {
16475 if (strtoupper($tmpescaped) == 'NULL') {
16476 $tmpescaped = 'NULL';
16477 } elseif (preg_match('/^[0-9]+$/', (string) $tmpescaped)) { // if only 0-9 chars, no .
16478 $tmpescaped = (int) $tmpescaped;
16479 } elseif (is_numeric((string) $tmpescaped)) { // it can be a float with a .
16480 $tmpescaped = (float) $tmpescaped;
16481 } else {
16482 $tmpescaped = preg_replace('/[^a-z0-9_]/i', '', $tmpescaped); // it can be a name of field or a substitution variable like '__NOW__'
16483 }
16484 }
16485
16486 return '(' . $db->escape($operand) . ' ' . strtoupper($operator) . ' ' . $tmpescaped . ')';
16487}
16488
16489
16499function getTimelineIcon($actionstatic, &$histo, $key)
16500{
16501 dol_syslog('getTimelineIcon::begin', LOG_DEBUG);
16502 global $langs;
16503
16504 $out = '<!-- timeline icon -->' . "\n";
16505 $iconClass = 'fa fa-comments';
16506 $img_picto = '';
16507 $colorClass = '';
16508 $pictoTitle = '';
16509
16510 if ($histo[$key]['percent'] == -1) {
16511 $colorClass = 'timeline-icon-not-applicble';
16512 $pictoTitle = $langs->trans('StatusNotApplicable');
16513 } elseif ($histo[$key]['percent'] == 0) {
16514 $colorClass = 'timeline-icon-todo';
16515 $pictoTitle = $langs->trans('StatusActionToDo') . ' (0%)';
16516 } elseif ($histo[$key]['percent'] > 0 && $histo[$key]['percent'] < 100) {
16517 $colorClass = 'timeline-icon-in-progress';
16518 $pictoTitle = $langs->trans('StatusActionInProcess') . ' (' . $histo[$key]['percent'] . '%)';
16519 } elseif ($histo[$key]['percent'] >= 100) {
16520 $colorClass = 'timeline-icon-done';
16521 $pictoTitle = $langs->trans('StatusActionDone') . ' (100%)';
16522 }
16523
16524 if ($actionstatic->code == 'AC_TICKET_CREATE') {
16525 $iconClass = 'fa fa-ticket';
16526 } elseif ($actionstatic->code == 'AC_TICKET_MODIFY') {
16527 $iconClass = 'fa fa-pencilxxx';
16528 } elseif (preg_match('/^TICKET_MSG/', $actionstatic->code)) {
16529 $iconClass = 'fa fa-comments';
16530 } elseif (preg_match('/^TICKET_MSG_PRIVATE/', $actionstatic->code)) {
16531 $iconClass = 'fa fa-mask';
16532 } elseif (getDolGlobalString('AGENDA_USE_EVENT_TYPE')) {
16533 if ($actionstatic->type_picto) {
16534 $img_picto = img_picto('', $actionstatic->type_picto);
16535 } else {
16536 if ($actionstatic->type_code == 'AC_RDV') {
16537 $iconClass = 'fa fa-handshake';
16538 } elseif ($actionstatic->type_code == 'AC_TEL') {
16539 $iconClass = 'fa fa-phone';
16540 } elseif ($actionstatic->type_code == 'AC_FAX') {
16541 $iconClass = 'fa fa-fax';
16542 } elseif ($actionstatic->type_code == 'AC_EMAIL') {
16543 $iconClass = 'fa fa-envelope';
16544 } elseif ($actionstatic->type_code == 'AC_INT') {
16545 $iconClass = 'fa fa-shipping-fast';
16546 } elseif ($actionstatic->type_code == 'AC_OTH_AUTO') {
16547 $iconClass = 'fa fa-robot';
16548 } elseif (!preg_match('/_AUTO/', $actionstatic->type_code)) {
16549 $iconClass = 'fa fa-robot';
16550 }
16551 }
16552 }
16553
16554 $out .= '<i class="' . $iconClass . ' ' . $colorClass . '" title="' . $pictoTitle . '">' . $img_picto . '</i>' . "\n";
16555 return $out;
16556}
16557
16565{
16566 global $db;
16567
16568 $documents = array();
16569
16570 $sql = 'SELECT ecm.rowid as id, ecm.src_object_type, ecm.src_object_id, ecm.filepath, ecm.filename, ecm.agenda_id';
16571 $sql .= ' FROM ' . MAIN_DB_PREFIX . 'ecm_files ecm';
16572 $sql .= " WHERE ecm.filepath = 'agenda/" . ((int) $object->id) . "'";
16573 //$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
16574 $sql .= ' OR ecm.agenda_id = ' . (int) $object->id;
16575 $sql .= ' ORDER BY ecm.position ASC';
16576
16577 $resql = $db->query($sql);
16578 if ($resql) {
16579 if ($db->num_rows($resql)) {
16580 while ($obj = $db->fetch_object($resql)) {
16581 $documents[$obj->id] = $obj;
16582 }
16583 }
16584 }
16585
16586 return $documents;
16587}
16588
16589
16607function show_actions_messaging($conf, $langs, $db, $filterobj, $objcon = null, $noprint = 0, $actioncode = '', $donetodo = 'done', $filters = array(), $sortfield = 'a.datep,a.id', $sortorder = 'DESC')
16608{
16609 dol_syslog('show_actions_messaging::begin', LOG_DEBUG);
16610 global $user, $conf;
16611 global $form;
16612
16613 global $param, $massactionbutton;
16614
16615 require_once DOL_DOCUMENT_ROOT . '/comm/action/class/actioncomm.class.php';
16616
16617 // Check parameters
16618 if (!is_object($filterobj) && !is_object($objcon)) {
16619 dol_print_error(null, 'BadParameter');
16620 }
16621
16622 $histo = array();
16623 '@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';
16624
16625 $numaction = 0;
16626 $now = dol_now();
16627
16628 $sortfield_list = explode(',', $sortfield);
16629 $sortfield_label_list = array('a.id' => 'id', 'a.datep' => 'dp', 'a.percent' => 'percent');
16630 $sanitized_sortfield_new_list = array();
16631 foreach ($sortfield_list as $sortfield_value) {
16632 $sanitized_sortfield_new_list[] = $sortfield_label_list[trim($sortfield_value)]; //@phan-suppress-current-line SqlInjection
16633 }
16634 $sanitized_sortfield_new = implode(',', $sanitized_sortfield_new_list);
16635
16636 $sql = null;
16637 $sql2 = null;
16638
16639 if (isModEnabled('agenda')) {
16640 // Search histo on actioncomm
16641 if (is_object($objcon) && $objcon->id > 0) {
16642 $sql = "SELECT DISTINCT a.id, a.label as label,";
16643 } else {
16644 $sql = "SELECT a.id, a.label as label,";
16645 }
16646 $sql .= " a.datep as dp,";
16647 $sql .= " a.note as message,";
16648 $sql .= " a.datep2 as dp2,";
16649 $sql .= " a.percent as percent, 'action' as type,";
16650 $sql .= " a.fk_element, a.elementtype,";
16651 $sql .= " a.fk_contact, a.fulldayevent,";
16652 $sql .= " a.email_from as msg_from,";
16653 $sql .= " c.code as acode, c.libelle as alabel, c.picto as apicto,";
16654 $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";
16655 if (is_object($filterobj) && get_class($filterobj) == 'Societe') {
16656 $sql .= ", sp.lastname, sp.firstname";
16657 } elseif (is_object($filterobj) && get_class($filterobj) == 'Adherent') {
16658 $sql .= ", m.lastname, m.firstname";
16659 } elseif (is_object($filterobj) && in_array(get_class($filterobj), array('Commande', 'CommandeFournisseur', 'Product', 'Ticket', 'BOM', 'Contrat', 'Facture', 'FactureFournisseur', 'Propal', 'Expedition'))) {
16660 $sql .= ", o.ref";
16661 } else {
16662 if (is_object($filterobj) && !empty($filterobj->table_element) && !empty($filterobj->element) && !empty($filterobj->id) && array_key_exists('ref', $filterobj->fields)) {
16663 $sql .= ", o.ref";
16664 }
16665 }
16666 $sql .= " FROM " . MAIN_DB_PREFIX . "actioncomm as a";
16667 $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "user as u on u.rowid = a.fk_user_action";
16668 $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "c_actioncomm as c ON a.fk_action = c.id";
16669
16670 $force_filter_contact = $filterobj instanceof User;
16671
16672 if (is_object($objcon) && $objcon->id > 0) {
16673 $force_filter_contact = true;
16674 $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "actioncomm_resources as r ON a.id = r.fk_actioncomm";
16675 $sql .= " AND r.element_type = '" . $db->escape($objcon->table_element) . "' AND r.fk_element = " . ((int) $objcon->id);
16676 }
16677
16678 if ((is_object($filterobj) && get_class($filterobj) == 'Societe') || (is_object($filterobj) && get_class($filterobj) == 'Contact')) {
16679 $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "socpeople as sp ON a.fk_contact = sp.rowid";
16680 } elseif (is_object($filterobj) && get_class($filterobj) == 'Dolresource') {
16681 $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "element_resources as er";
16682 $sql .= " ON er.resource_type = 'dolresource'";
16683 $sql .= " AND er.element_id = a.id";
16684 $sql .= " AND er.resource_id = " . ((int) $filterobj->id);
16685 } elseif (is_object($filterobj) && get_class($filterobj) == 'Adherent') {
16686 $sql .= ", " . MAIN_DB_PREFIX . "adherent as m";
16687 } elseif (is_object($filterobj) && get_class($filterobj) == 'CommandeFournisseur') {
16688 $sql .= ", " . MAIN_DB_PREFIX . "commande_fournisseur as o";
16689 } elseif (is_object($filterobj) && get_class($filterobj) == 'Product') {
16690 $sql .= ", " . MAIN_DB_PREFIX . "product as o";
16691 } elseif (is_object($filterobj) && get_class($filterobj) == 'Ticket') {
16692 $sql .= ", " . MAIN_DB_PREFIX . "ticket as o";
16693 } elseif (is_object($filterobj) && get_class($filterobj) == 'BOM') {
16694 $sql .= ", " . MAIN_DB_PREFIX . "bom_bom as o";
16695 } elseif (is_object($filterobj) && get_class($filterobj) == 'Contrat') {
16696 $sql .= ", " . MAIN_DB_PREFIX . "contrat as o";
16697 } elseif (is_object($filterobj) && get_class($filterobj) == 'Facture') {
16698 $sql .= ", " . MAIN_DB_PREFIX . "facture as o";
16699 } elseif (is_object($filterobj) && get_class($filterobj) == 'FactureFournisseur') {
16700 $sql .= ", " . MAIN_DB_PREFIX . "facture_fourn as o";
16701 } elseif (is_object($filterobj) && get_class($filterobj) == 'Commande') {
16702 $sql .= ", " . MAIN_DB_PREFIX . "commande as o";
16703 } elseif (is_object($filterobj) && get_class($filterobj) == 'Expedition') {
16704 $sql .= ", " . MAIN_DB_PREFIX . "expedition as o";
16705 } elseif (is_object($filterobj) && get_class($filterobj) == 'Propal') {
16706 $sql .= ", " . MAIN_DB_PREFIX . "propal as o";
16707 } else {
16708 if (is_object($filterobj) && !empty($filterobj->table_element) && !empty($filterobj->element) && !empty($filterobj->id) && array_key_exists('ref', $filterobj->fields)) {
16709 $sql .= ", " . MAIN_DB_PREFIX . $filterobj->table_element . " as o";
16710 }
16711 }
16712 $sql .= " WHERE a.entity IN (" . getEntity('agenda') . ")";
16713 if (!$force_filter_contact) {
16714 if (is_object($filterobj) && in_array(get_class($filterobj), array('Societe', 'Client', 'Fournisseur')) && $filterobj->id) {
16715 $sql .= " AND a.fk_soc = " . ((int) $filterobj->id);
16716 } elseif (is_object($filterobj) && get_class($filterobj) == 'Project' && $filterobj->id) {
16717 $sql .= " AND a.fk_project = o.rowid AND a.fk_project = " . ((int) $filterobj->id);
16718 } elseif (is_object($filterobj) && get_class($filterobj) == 'Adherent') {
16719 $sql .= " AND a.fk_element = m.rowid AND a.elementtype = 'member'";
16720 if ($filterobj->id) {
16721 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16722 }
16723 } elseif (is_object($filterobj) && get_class($filterobj) == 'Commande') {
16724 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'order'";
16725 if ($filterobj->id) {
16726 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16727 }
16728 } elseif (is_object($filterobj) && get_class($filterobj) == 'Expedition') {
16729 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'shipping'";
16730 if ($filterobj->id) {
16731 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16732 }
16733 } elseif (is_object($filterobj) && get_class($filterobj) == 'Propal') {
16734 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'propal'";
16735 if ($filterobj->id) {
16736 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16737 }
16738 } elseif (is_object($filterobj) && get_class($filterobj) == 'CommandeFournisseur') {
16739 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'order_supplier'";
16740 if ($filterobj->id) {
16741 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16742 }
16743 } elseif (is_object($filterobj) && get_class($filterobj) == 'Product') {
16744 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'product'";
16745 if ($filterobj->id) {
16746 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16747 }
16748 } elseif (is_object($filterobj) && get_class($filterobj) == 'Ticket') {
16749 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'ticket'";
16750 if ($filterobj->id) {
16751 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16752 }
16753 } elseif (is_object($filterobj) && get_class($filterobj) == 'BOM') {
16754 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'bom'";
16755 if ($filterobj->id) {
16756 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16757 }
16758 } elseif (is_object($filterobj) && get_class($filterobj) == 'Contrat') {
16759 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'contract'";
16760 if ($filterobj->id) {
16761 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16762 }
16763 } elseif (is_object($filterobj) && get_class($filterobj) == 'Contact' && $filterobj->id) {
16764 $sql .= " AND a.fk_contact = sp.rowid";
16765 if ($filterobj->id) {
16766 $sql .= " AND a.fk_contact = " . ((int) $filterobj->id);
16767 }
16768 } elseif (is_object($filterobj) && get_class($filterobj) == 'Facture') {
16769 $sql .= " AND a.fk_element = o.rowid";
16770 if ($filterobj->id) {
16771 $sql .= " AND a.fk_element = " . ((int) $filterobj->id) . " AND a.elementtype = 'invoice'";
16772 }
16773 } elseif (is_object($filterobj) && get_class($filterobj) == 'FactureFournisseur') {
16774 $sql .= " AND a.fk_element = o.rowid";
16775 if ($filterobj->id) {
16776 $sql .= " AND a.fk_element = " . ((int) $filterobj->id) . " AND a.elementtype = 'invoice_supplier'";
16777 }
16778 } else {
16779 if (is_object($filterobj) && !empty($filterobj->element) && !empty($filterobj->id) && array_key_exists('ref', $filterobj->fields)) {
16780 $sql .= " AND a.fk_element = o.rowid";
16781 $sql .= " AND a.elementtype = '" . $db->escape($filterobj->element) . "'";
16782 if ($filterobj->id) {
16783 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
16784 }
16785 }
16786 }
16787 } else {
16788 $sql .= " AND u.rowid = " . ((int) $filterobj->id);
16789 }
16790
16791 // Condition on actioncode
16792 if (!empty($actioncode) && $actioncode != '-1') {
16793 if (!getDolGlobalString('AGENDA_USE_EVENT_TYPE')) {
16794 if ($actioncode == 'AC_NON_AUTO') {
16795 $sql .= " AND c.type != 'systemauto'";
16796 } elseif ($actioncode == 'AC_ALL_AUTO') {
16797 $sql .= " AND c.type = 'systemauto'";
16798 } else {
16799 if ($actioncode == 'AC_OTH') {
16800 $sql .= " AND c.type != 'systemauto'";
16801 } elseif ($actioncode == 'AC_OTH_AUTO') {
16802 $sql .= " AND c.type = 'systemauto'";
16803 }
16804 }
16805 } else {
16806 if ($actioncode == 'AC_NON_AUTO') {
16807 $sql .= " AND c.type != 'systemauto'";
16808 } elseif ($actioncode == 'AC_ALL_AUTO') {
16809 $sql .= " AND c.type = 'systemauto'";
16810 } else {
16811 $sql .= " AND c.code = '" . $db->escape($actioncode) . "'";
16812 }
16813 }
16814 }
16815 if ($donetodo == 'todo') {
16816 $sql .= " AND ((a.percent >= 0 AND a.percent < 100) OR (a.percent = -1 AND a.datep > '" . $db->idate($now) . "'))";
16817 } elseif ($donetodo == 'done') {
16818 $sql .= " AND (a.percent = 100 OR (a.percent = -1 AND a.datep <= '" . $db->idate($now) . "'))";
16819 }
16820 if (is_array($filters) && $filters['search_agenda_label']) {
16821 $sql .= natural_search('a.label', $filters['search_agenda_label']);
16822 }
16823 }
16824
16825 // Add also event from emailings. TODO This should be replaced by an automatic event ? May be it's too much for very large emailing.
16826 if (
16827 isModEnabled('mailing') && !empty($objcon->email)
16828 && (empty($actioncode) || $actioncode == 'AC_OTH_AUTO' || $actioncode == 'AC_EMAILING')
16829 ) {
16830 $langs->load("mails");
16831
16832 $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";
16833 $sql2 .= ", null as fk_element, '' as elementtype, null as contact_id";
16834 $sql2 .= ", 'AC_EMAILING' as acode, '' as alabel, '' as apicto";
16835 $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
16836 if (is_object($filterobj) && get_class($filterobj) == 'Societe') {
16837 $sql2 .= ", '' as lastname, '' as firstname";
16838 } elseif (is_object($filterobj) && get_class($filterobj) == 'Adherent') {
16839 $sql2 .= ", '' as lastname, '' as firstname";
16840 } elseif (is_object($filterobj) && get_class($filterobj) == 'CommandeFournisseur') {
16841 $sql2 .= ", '' as ref";
16842 } elseif (is_object($filterobj) && get_class($filterobj) == 'Product') {
16843 $sql2 .= ", '' as ref";
16844 } elseif (is_object($filterobj) && get_class($filterobj) == 'Ticket') {
16845 $sql2 .= ", '' as ref";
16846 }
16847 $sql2 .= " FROM " . MAIN_DB_PREFIX . "mailing as m, " . MAIN_DB_PREFIX . "mailing_cibles as mc, " . MAIN_DB_PREFIX . "user as u";
16848 $sql2 .= " WHERE mc.email = '" . $db->escape($objcon->email) . "'"; // Search is done on email.
16849 $sql2 .= " AND mc.statut = 1";
16850 $sql2 .= " AND u.rowid = m.fk_user_valid";
16851 $sql2 .= " AND mc.fk_mailing=m.rowid";
16852 }
16853
16854 $num = 0;
16855 $MAXWITHOUTPAGINATION = getDolGlobalInt('AGENDA_MAX_EVENTS_ON_PAGE_WITHOUT_PAGINATION', 100);
16856
16857 if ($sql || $sql2) { // May not be defined if module Agenda is not enabled and mailing module disabled too
16858 if (!empty($sql) && !empty($sql2)) {
16859 $sql = $sql . " UNION " . $sql2;
16860 } elseif (empty($sql) && !empty($sql2)) {
16861 $sql = $sql2;
16862 }
16863
16864 //TODO Add navigation with this limits...
16865 $offset = 0;
16866 $limit = $MAXWITHOUTPAGINATION;
16867
16868 // Complete request and execute it with limit
16869 $sql .= $db->order($sanitized_sortfield_new, $sortorder);
16870 if ($limit) {
16871 $sql .= $db->plimit($limit + 1, $offset);
16872 }
16873
16874 dol_syslog("function.lib::show_actions_messaging", LOG_DEBUG);
16875
16876 $resql = $db->query($sql);
16877 if ($resql) {
16878 $i = 0;
16879 $num = $db->num_rows($resql);
16880
16881 $imaxinloop = ($limit ? min($num, $limit) : $num);
16882 while ($i < $imaxinloop) {
16883 $obj = $db->fetch_object($resql);
16884
16885 if ($obj->type == 'action') {
16886 $contactaction = new ActionComm($db);
16887 $contactaction->id = $obj->id;
16888 $result = $contactaction->fetchResources();
16889 if ($result < 0) {
16891 setEventMessage("actions.lib::show_actions_messaging Error fetch resource", 'errors');
16892 }
16893
16894 //if ($donetodo == 'todo') $sql.= " AND ((a.percent >= 0 AND a.percent < 100) OR (a.percent = -1 AND a.datep > '".$db->idate($now)."'))";
16895 //elseif ($donetodo == 'done') $sql.= " AND (a.percent = 100 OR (a.percent = -1 AND a.datep <= '".$db->idate($now)."'))";
16896 $tododone = '';
16897 if (($obj->percent >= 0 and $obj->percent < 100) || ($obj->percent == -1 && $obj->dp > $now)) {
16898 $tododone = 'todo';
16899 }
16900
16901 $histo[$numaction] = array(
16902 'type' => $obj->type,
16903 'tododone' => $tododone,
16904 'id' => $obj->id,
16905 'datestart' => $db->jdate($obj->dp),
16906 'dateend' => $db->jdate($obj->dp2),
16907 'fulldayevent' => (int) $obj->fulldayevent,
16908 'note' => $obj->label,
16909 'message' => $obj->message,
16910 'percent' => $obj->percent,
16911
16912 'userid' => $obj->user_id,
16913 'login' => $obj->user_login,
16914 'userfirstname' => $obj->user_firstname,
16915 'userlastname' => $obj->user_lastname,
16916 'userphoto' => $obj->user_photo,
16917 'msg_from' => $obj->msg_from,
16918
16919 'contact_id' => $obj->fk_contact,
16920 'socpeopleassigned' => $contactaction->socpeopleassigned,
16921 'lastname' => (empty($obj->lastname) ? '' : $obj->lastname),
16922 'firstname' => (empty($obj->firstname) ? '' : $obj->firstname),
16923 'fk_element' => $obj->fk_element,
16924 'elementtype' => $obj->elementtype,
16925 // Type of event
16926 'acode' => $obj->acode,
16927 'alabel' => $obj->alabel,
16928 'libelle' => $obj->alabel, // deprecated
16929 'apicto' => $obj->apicto
16930 );
16931 } else {
16932 $histo[$numaction] = array(
16933 'type' => $obj->type,
16934 'tododone' => 'done',
16935 'id' => $obj->id,
16936 'datestart' => $db->jdate($obj->dp),
16937 'dateend' => $db->jdate($obj->dp2),
16938 'fulldayevent' => (int) $obj->fulldayevent,
16939 'note' => $obj->label,
16940 'message' => $obj->message,
16941 'percent' => $obj->percent,
16942 'acode' => $obj->acode,
16943
16944 'userid' => $obj->user_id,
16945 'login' => $obj->user_login,
16946 'userfirstname' => $obj->user_firstname,
16947 'userlastname' => $obj->user_lastname,
16948 'userphoto' => $obj->user_photo
16949 );
16950 }
16951
16952 $numaction++;
16953 $i++;
16954 }
16955 } else {
16957 }
16958 }
16959
16960 // Set $out to show events
16961 $out = '';
16962
16963 if (!isModEnabled('agenda')) {
16964 $langs->loadLangs(array("admin", "errors"));
16965 $out = info_admin($langs->trans("WarningModuleXDisabledSoYouMayMissEventHere", $langs->transnoentitiesnoconv("Module2400Name")), 0, 0, 'warning');
16966 }
16967
16968 if (isModEnabled('agenda') || (isModEnabled('mailing') && !empty($objcon->email))) {
16969 $delay_warning = getDolGlobalInt('MAIN_DELAY_ACTIONS_TODO') * 24 * 60 * 60;
16970
16971 require_once DOL_DOCUMENT_ROOT . '/comm/action/class/actioncomm.class.php';
16972 include_once DOL_DOCUMENT_ROOT . '/core/lib/functions2.lib.php';
16973 require_once DOL_DOCUMENT_ROOT . '/core/class/html.formactions.class.php';
16974 require_once DOL_DOCUMENT_ROOT . '/contact/class/contact.class.php';
16975
16976 $formactions = new FormActions($db);
16977
16978 $actionstatic = new ActionComm($db);
16979 $userstatic = new User($db);
16980 $contactstatic = new Contact($db);
16981 $userGetNomUrlCache = array();
16982 $contactGetNomUrlCache = array();
16983
16984 $out .= '<div class="filters-container" >';
16985 $out .= '<form name="listactionsfilter" class="listactionsfilter" action="' . $_SERVER["PHP_SELF"] . '" method="POST">';
16986 $out .= '<input type="hidden" name="token" value="' . newToken() . '">';
16987
16988 if (
16989 $objcon && get_class($objcon) == 'Contact' &&
16990 (is_null($filterobj) || get_class($filterobj) == 'Societe')
16991 ) {
16992 $out .= '<input type="hidden" name="id" value="' . $objcon->id . '" />';
16993 } else {
16994 $out .= '<input type="hidden" name="id" value="' . $filterobj->id . '" />';
16995 }
16996 if (($filterobj && get_class($filterobj) == 'Societe')) {
16997 $out .= '<input type="hidden" name="socid" value="' . $filterobj->id . '" />';
16998 } else {
16999 $out .= '<input type="hidden" name="userid" value="' . $filterobj->id . '" />';
17000 }
17001
17002 $out .= "\n";
17003
17004 $out .= '<div class="div-table-responsive-no-min">';
17005 $out .= '<table class="noborder borderbottom centpercent">';
17006
17007 $out .= '<tr class="liste_titre">';
17008
17009 // Action column
17010 if ($conf->main_checkbox_left_column) {
17011 $out .= '<th class="liste_titre width50 middle">';
17012 $searchpicto = $form->showFilterAndCheckAddButtons($massactionbutton ? 1 : 0, 'checkforselect', 1);
17013 $out .= $searchpicto;
17014 $out .= '</th>';
17015 }
17016
17017 // Date
17018 $out .= getTitleFieldOfList('Date', 0, $_SERVER["PHP_SELF"], 'a.datep', '', $param, '', $sortfield, $sortorder, 'nowraponall nopaddingleftimp ') . "\n";
17019
17020 $out .= '<th class="liste_titre hideonsmartphone"><strong class="hideonsmartphone">' . $langs->trans("Search") . ' : </strong></th>';
17021 if ($donetodo) {
17022 $out .= '<th class="liste_titre"></th>';
17023 }
17024 // Type of event
17025 $out .= '<th class="liste_titre">';
17026 $out .= '<span class="fas fa-square inline-block fawidth30 hideonsmartphone" style="color: #ddd;" title="' . $langs->trans("ActionType") . '"></span>';
17027 $out .= $formactions->select_type_actions($actioncode, "actioncode", '', getDolGlobalString('AGENDA_USE_EVENT_TYPE') ? -1 : 1, 0, 0, 1, 'selecttype minwidth100', $langs->trans("Type"));
17028 $out .= '</th>';
17029 // Label
17030 $out .= '<th class="liste_titre maxwidth100onsmartphone">';
17031 $out .= '<input type="text" class="maxwidth100onsmartphone" name="search_agenda_label" value="' . $filters['search_agenda_label'] . '" placeholder="' . $langs->trans("Label") . '">';
17032 $out .= '</th>';
17033
17034 // Action column
17035 if (!$conf->main_checkbox_left_column) {
17036 $out .= '<th class="liste_titre width50 middle">';
17037 $searchpicto = $form->showFilterAndCheckAddButtons($massactionbutton ? 1 : 0, 'checkforselect', 1);
17038 $out .= $searchpicto;
17039 $out .= '</th>';
17040 }
17041
17042 $out .= '</tr>';
17043
17044 $out .= '</table>';
17045
17046 $out .= '</form>';
17047 $out .= '</div>';
17048
17049 $out .= "\n";
17050
17051 $out .= '<ul class="timeline">';
17052
17053 if ($donetodo) {
17054 $tmp = '';
17055 if ($filterobj instanceof Societe) {
17056 $tmp .= '<a href="' . DOL_URL_ROOT . '/comm/action/list.php?mode=show_list&socid=' . $filterobj->id . '&status=done">';
17057 }
17058 if ($filterobj instanceof User) {
17059 $tmp .= '<a href="' . DOL_URL_ROOT . '/comm/action/list.php?mode=show_list&socid=' . $filterobj->id . '&status=done">';
17060 }
17061 $tmp .= ($donetodo != 'done' ? $langs->trans("ActionsToDoShort") : '');
17062 $tmp .= ($donetodo != 'done' && $donetodo != 'todo' ? ' / ' : '');
17063 $tmp .= ($donetodo != 'todo' ? $langs->trans("ActionsDoneShort") : '');
17064 //$out.=$langs->trans("ActionsToDoShort").' / '.$langs->trans("ActionsDoneShort");
17065 if ($filterobj instanceof Societe) {
17066 $tmp .= '</a>';
17067 }
17068 if ($filterobj instanceof User) {
17069 $tmp .= '</a>';
17070 }
17071 $out .= getTitleFieldOfList($tmp);
17072 }
17073
17074 require_once DOL_DOCUMENT_ROOT . '/comm/action/class/cactioncomm.class.php';
17075 $caction = new CActionComm($db);
17076 $arraylist = $caction->liste_array(1, 'code', '', (!getDolGlobalString('AGENDA_USE_EVENT_TYPE') ? 1 : 0), '', 1);
17077
17078 $actualCycleDate = false;
17079
17080 // Loop on each event to show it
17081 foreach ($histo as $key => $value) {
17082 $actionstatic->fetch($histo[$key]['id']); // TODO Do we need this, we already have a lot of data of line into $histo
17083
17084 $actionstatic->type_picto = $histo[$key]['apicto'];
17085 $actionstatic->type_code = $histo[$key]['acode'];
17086
17087 $labeltype = $actionstatic->type_code;
17088 if (!getDolGlobalString('AGENDA_USE_EVENT_TYPE') && empty($arraylist[$labeltype])) {
17089 $labeltype = 'AC_OTH';
17090 }
17091 if (!empty($actionstatic->code) && preg_match('/^TICKET_MSG/', $actionstatic->code)) {
17092 $labeltype = $langs->trans("Message");
17093 } else {
17094 if (!empty($arraylist[$labeltype])) {
17095 $labeltype = $arraylist[$labeltype];
17096 }
17097 if ($actionstatic->type_code == 'AC_OTH_AUTO' && ($actionstatic->type_code != $actionstatic->code) && $labeltype && !empty($arraylist[$actionstatic->code])) {
17098 $labeltype .= ' - ' . $arraylist[$actionstatic->code]; // Use code in priority on type_code
17099 }
17100 }
17101
17102 $url = DOL_URL_ROOT . '/comm/action/card.php?id=' . $histo[$key]['id'];
17103
17104 $tmpa = dol_getdate($histo[$key]['datestart'], false);
17105
17106 if (isset($tmpa['year']) && isset($tmpa['yday']) && $actualCycleDate !== $tmpa['year'] . '-' . $tmpa['yday']) {
17107 $actualCycleDate = $tmpa['year'] . '-' . $tmpa['yday'];
17108 $out .= '<!-- timeline time label -->';
17109 $out .= '<li class="time-label">';
17110 $out .= '<span class="timeline-badge-date">';
17111 $out .= dol_print_date($histo[$key]['datestart'], 'daytext', 'tzuserrel', $langs);
17112 $out .= '</span>';
17113 $out .= '</li>';
17114 $out .= '<!-- /.timeline-label -->';
17115 }
17116
17117
17118 $out .= '<!-- timeline item -->' . "\n";
17119 $out .= '<li class="timeline-code-' . (!empty($actionstatic->code) ? strtolower($actionstatic->code) : "none") . '">';
17120
17121 //$timelineicon = getTimelineIcon($actionstatic, $histo, $key);
17122 $typeicon = $actionstatic->getTypePicto('pictofixedwidth timeline-icon-not-applicble', $labeltype);
17123 //$out .= $timelineicon;
17124 //var_dump($timelineicon);
17125 $out .= $typeicon;
17126
17127 $out .= '<div class="timeline-item">' . "\n";
17128
17129 $out .= '<span class="time timeline-header-action2">';
17130
17131 if (isset($histo[$key]['type']) && $histo[$key]['type'] == 'mailing') {
17132 $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") . ' ';
17133 $out .= $histo[$key]['id'];
17134 $out .= '</a> ';
17135 } else {
17136 $out .= $actionstatic->getNomUrl(1, -1, 'valignmiddle') . ' ';
17137 }
17138
17139 if (
17140 $user->hasRight('agenda', 'allactions', 'create') ||
17141 (($actionstatic->authorid == $user->id || $actionstatic->userownerid == $user->id) && $user->hasRight('agenda', 'myactions', 'create'))
17142 ) {
17143 $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) . '">';
17144 //$out .= '<i class="fa fa-pencil" title="'.$langs->trans("Modify").'" ></i>';
17145 $out .= img_picto($langs->trans("Modify"), 'edit', 'class="edita"');
17146 $out .= '</a>';
17147 }
17148
17149 $out .= '</span>';
17150
17151 // Date
17152 $out .= '<span class="time"><i class="fa fa-clock valignmiddle"></i> ';
17153 $out .= '<span class="valignmiddle marginrightonly">';
17154 $out .= dol_print_date($histo[$key]['datestart'], 'day', 'tzuserrel');
17155 //$out .= '</span>';
17156 //$out .= '<span class="valignmiddle">'.
17157 $out .= ' '.dol_print_date($histo[$key]['datestart'], 'hour', 'tzuserrel', null, false, 'opacitymedium');
17158 //$out .= '</span>';
17159 if ($histo[$key]['dateend'] && $histo[$key]['dateend'] != $histo[$key]['datestart']) {
17160 $tmpa = dol_getdate($histo[$key]['datestart'], true);
17161 $tmpb = dol_getdate($histo[$key]['dateend'], true);
17162 if ($tmpa['mday'] == $tmpb['mday'] && $tmpa['mon'] == $tmpb['mon'] && $tmpa['year'] == $tmpb['year']) {
17163 $out .= ' - ' . dol_print_date($histo[$key]['dateend'], 'hour', 'tzuserrel', null, false, 1);
17164 } else {
17165 $out .= ' - ' . dol_print_date($histo[$key]['dateend'], 'day', 'tzuserrel');
17166 //$out .= '<span class="valignmiddle marginrightonly">';
17167 $out .= ' '.dol_print_date($histo[$key]['dateend'], 'hour', 'tzuserrel', null, false, 'opacitymedium');
17168 //$out .= '</span>';
17169 }
17170 }
17171 $late = 0;
17172 if ($histo[$key]['percent'] == 0 && $histo[$key]['datestart'] && $histo[$key]['datestart'] < ($now - $delay_warning)) {
17173 $late = 1;
17174 }
17175 if ($histo[$key]['percent'] == 0 && !$histo[$key]['datestart'] && $histo[$key]['dateend'] && $histo[$key]['datestart'] < ($now - $delay_warning)) {
17176 $late = 1;
17177 }
17178 if ($histo[$key]['percent'] > 0 && $histo[$key]['percent'] < 100 && $histo[$key]['dateend'] && $histo[$key]['dateend'] < ($now - $delay_warning)) {
17179 $late = 1;
17180 }
17181 if ($histo[$key]['percent'] > 0 && $histo[$key]['percent'] < 100 && !$histo[$key]['dateend'] && $histo[$key]['datestart'] && $histo[$key]['datestart'] < ($now - $delay_warning)) {
17182 $late = 1;
17183 }
17184 if ($late) {
17185 $out .= img_warning($langs->trans("Late")) . ' ';
17186 }
17187 $out .= "</span></span>\n";
17188
17189 $out .= '<span class="time">';
17190 $out .= $actionstatic->getLibStatut(2);
17191 $out .= '</span>';
17192
17193 // Ref
17194 $out .= '<h3 class="timeline-header">';
17195
17196 // Author of event
17197 $out .= '<div class="messaging-author inline-block tdoverflowmax150 valignmiddle marginrightonly">';
17198 if ($histo[$key]['userid'] > 0) {
17199 if (!isset($userGetNomUrlCache[$histo[$key]['userid']])) { // is in cache ?
17200 $userstatic->fetch($histo[$key]['userid']);
17201 $userGetNomUrlCache[$histo[$key]['userid']] = $userstatic->getNomUrl(-1, '', 0, 0, 16, 0, 'firstelselast', '');
17202 }
17203 $out .= $userGetNomUrlCache[$histo[$key]['userid']];
17204 } elseif (!empty($histo[$key]['msg_from']) && $actionstatic->code == 'TICKET_MSG') {
17205 if (!isset($contactGetNomUrlCache[$histo[$key]['msg_from']])) {
17206 if ($contactstatic->fetch(0, null, '', $histo[$key]['msg_from']) > 0) {
17207 $contactGetNomUrlCache[$histo[$key]['msg_from']] = $contactstatic->getNomUrl(-1, '', 16);
17208 } else {
17209 $contactGetNomUrlCache[$histo[$key]['msg_from']] = $histo[$key]['msg_from'];
17210 }
17211 }
17212 $out .= $contactGetNomUrlCache[$histo[$key]['msg_from']];
17213 } else {
17214 $out .= '<img class="photomemberphoto userphoto" alt="" src="/public/theme/common/user_anonymous.png">'.$langs->trans("Anonymous");
17215 }
17216 $out .= '</div>';
17217
17218 // Title
17219 $out .= ' <div class="messaging-title inline-block">';
17220 //$out .= $actionstatic->getTypePicto(); // The type of event is already into the timeline on left.
17221 if (empty($conf->dol_optimize_smallscreen) && $actionstatic->type_code != 'AC_OTH_AUTO') {
17222 $out .= $labeltype . ' - ';
17223 }
17224
17225 $libelle = '';
17226
17227 if (!empty($actionstatic->code) && preg_match('/^TICKET_MSG_PRIVATE/', $actionstatic->code)) {
17228 $out .= $langs->trans('TicketNewMessage').' - <em>'.img_picto($langs->trans('Private'), 'lock', 'class="valignmiddle"').' '.$langs->trans('Private').'</em>';
17229 $summary = preg_replace('/\[[^\]]*\]\s*/', '', $actionstatic->label);
17230 //if ($summary != $object->title) {
17231 $out .= ' - '.dolPrintHTML($summary);
17232 //}
17233 } elseif (!empty($actionstatic->code) && preg_match('/^TICKET_MSG/', $actionstatic->code)) {
17234 $out .= $langs->trans('TicketNewMessage');
17235 } elseif (isset($histo[$key]['type'])) {
17236 if ($histo[$key]['type'] == 'action') {
17237 $transcode = $langs->transnoentitiesnoconv("Action" . $histo[$key]['acode']);
17238 $libelle = ($transcode != "Action" . $histo[$key]['acode'] ? $transcode : $histo[$key]['alabel']);
17239 $libelle = $histo[$key]['note'];
17240 $actionstatic->id = $histo[$key]['id'];
17241 if ($libelle != $labeltype) {
17242 $out .= dol_escape_htmltag(dol_trunc($libelle, 120));
17243 }
17244 } elseif ($histo[$key]['type'] == 'mailing') {
17245 $out .= '<a href="' . DOL_URL_ROOT . '/comm/mailing/card.php?id=' . $histo[$key]['id'] . '">' . img_object($langs->trans("ShowEMailing"), "email") . ' ';
17246 $transcode = $langs->transnoentitiesnoconv("Action" . $histo[$key]['acode']);
17247 $libelle = ($transcode != "Action" . $histo[$key]['acode'] ? $transcode : 'Send mass mailing');
17248 $out .= dol_escape_htmltag(dol_trunc($libelle, 120));
17249 } else {
17250 $libelle .= $histo[$key]['note'];
17251 $out .= dol_escape_htmltag(dol_trunc($libelle, 120));
17252 }
17253 }
17254 $out = preg_replace('/ - $/', '', $out); // Remove ending ' - '
17255
17256 if (isset($histo[$key]['elementtype']) && !empty($histo[$key]['fk_element'])) {
17257 if (isset($conf->cache['elementlinkcache'][$histo[$key]['elementtype']]) && isset($conf->cache['elementlinkcache'][$histo[$key]['elementtype']][$histo[$key]['fk_element']])) {
17258 $link = $conf->cache['elementlinkcache'][$histo[$key]['elementtype']][$histo[$key]['fk_element']];
17259 } else {
17260 if (!isset($conf->cache['elementlinkcache'][$histo[$key]['elementtype']])) {
17261 $conf->cache['elementlinkcache'][$histo[$key]['elementtype']] = array();
17262 }
17263 $link = dolGetElementUrl($histo[$key]['fk_element'], $histo[$key]['elementtype'], 1);
17264 $conf->cache['elementlinkcache'][$histo[$key]['elementtype']][$histo[$key]['fk_element']] = $link;
17265 }
17266
17267 // 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)
17268 $showlink = 1;
17269 if (is_object($filterobj) && get_class($filterobj) == 'Ticket') {
17270 if ($histo[$key]['elementtype'] == 'ticket') {
17271 $showlink = 0;
17272 }
17273 }
17274
17275 if ($link && $showlink) {
17276 $out .= ' - ' . $link;
17277 }
17278 }
17279
17280 $out .= '</div>';
17281
17282 $out .= '</h3>';
17283
17284 // Message
17285 if ($actionstatic->code == 'AC_TICKET_CREATE') {
17286 $newmess = $filterobj->message;
17287 } else {
17288 $newmess = $histo[$key]['message'];
17289 }
17290 if (
17291 !empty($newmess && $newmess != $libelle)
17292 && $actionstatic->code != 'AC_TICKET_MODIFY'
17293 ) {
17294 $out .= '<div class="timeline-body wordbreak small">';
17295 $truncateLines = getDolGlobalInt('MAIN_TRUNCATE_TIMELINE_MESSAGE', 3);
17296 $truncatedText = dolGetFirstLineOfText($newmess, $truncateLines);
17297 // dolGetFirstLineOfText() cuts on <br> without caring about tag balance, so a message wrapped in
17298 // a block tag leaves the excerpt with an unclosed tag. The browser then nests the read more link
17299 // and the full text inside the excerpt, and hiding the excerpt hides the whole message (#39035).
17300 $truncatedText = dolCloseUnclosedHtmlTags($truncatedText);
17301 if ($truncateLines > 0 && strlen($newmess) > strlen($truncatedText)) {
17302 $out .= '<div class="readmore-block --closed" >';
17303 $out .= ' <div class="readmore-block__excerpt">';
17304 $out .= dolPrintHTML($truncatedText, 0, array('pre', 'code'));
17305 $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>';
17306 $out .= ' </div>';
17307 $out .= ' <div class="readmore-block__full-text" >';
17308
17309 $out .= dolPrintHTML($newmess, 0, array('pre', 'code'));
17310
17311 $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>';
17312 $out .= ' </div>';
17313 $out .= '</div>';
17314 } else {
17315 $out .= dolPrintHTML($newmess, 0, array('pre', 'code'));
17316 }
17317 $out .= '</div>';
17318 }
17319
17320 // Timeline footer
17321 $footer = '';
17322
17323 // Contact for this action
17324 if (isset($histo[$key]['socpeopleassigned']) && is_array($histo[$key]['socpeopleassigned']) && count($histo[$key]['socpeopleassigned']) > 0) {
17325 $contactList = '';
17326 foreach ($histo[$key]['socpeopleassigned'] as $cid => $Tab) {
17327 if (empty($conf->cache['contact'][$cid])) {
17328 $contact = new Contact($db);
17329 $contact->fetch($cid);
17330 $conf->cache['contact'][$cid] = $contact;
17331 } else {
17332 $contact = $conf->cache['contact'][$cid];
17333 }
17334
17335 if ($contact) {
17336 $contactList .= !empty($contactList) ? ', ' : '';
17337 $contactList .= $contact->getNomUrl(1);
17338 if (isset($histo[$key]['acode']) && $histo[$key]['acode'] == 'AC_TEL') {
17339 if (!empty($contact->phone_pro)) {
17340 $contactList .= '(' . dol_print_phone($contact->phone_pro) . ')';
17341 }
17342 }
17343 }
17344 }
17345
17346 $footer .= $langs->trans('ActionOnContact') . ' : ' . $contactList;
17347 } elseif (empty($objcon->id) && isset($histo[$key]['contact_id']) && $histo[$key]['contact_id'] > 0) {
17348 if (empty($conf->cache['contact'][$histo[$key]['contact_id']])) {
17349 $contact = new Contact($db);
17350 $result = $contact->fetch($histo[$key]['contact_id']);
17351 $conf->cache['contact'][$histo[$key]['contact_id']] = $contact;
17352 } else {
17353 $contact = $conf->cache['contact'][$histo[$key]['contact_id']];
17354 $result = ($contact instanceof Contact) ? $contact->id : 0;
17355 }
17356
17357 if ($result > 0) {
17358 $footer .= $contact->getNomUrl(1);
17359 if (isset($histo[$key]['acode']) && $histo[$key]['acode'] == 'AC_TEL') {
17360 if (!empty($contact->phone_pro)) {
17361 $footer .= '(' . dol_print_phone($contact->phone_pro) . ')';
17362 }
17363 }
17364 }
17365 }
17366
17367 $documents = getActionCommEcmList($actionstatic);
17368 if (!empty($documents)) {
17369 $footer .= '<div class="timeline-documents-container">';
17370 foreach ($documents as $doc) {
17371 $footer .= '<span id="document_' . $doc->id . '" class="timeline-documents" ';
17372 $footer .= ' data-id="' . $doc->id . '" ';
17373 $footer .= ' data-path="' . $doc->filepath . '"';
17374 $footer .= ' data-filename="' . dol_escape_htmltag($doc->filename) . '" ';
17375 $footer .= '>';
17376
17377 $filePath = DOL_DATA_ROOT . '/' . $doc->filepath . '/' . $doc->filename;
17378 $mime = dol_mimetype($filePath);
17379 if (empty($doc->agenda_id)) {
17380 $dir_ref = $actionstatic->id;
17381 $modulepart = 'actions';
17382 } else {
17383 $split_dir = explode('/', $doc->filepath);
17384 $modulepart = array_shift($split_dir);
17385 $dir_ref = implode('/', $split_dir);
17386 }
17387
17388 $file = $dir_ref . '/' . $doc->filename;
17389 $thumb = $dir_ref . '/thumbs/' . substr($doc->filename, 0, strrpos($doc->filename, '.')) . '_mini' . substr($doc->filename, strrpos($doc->filename, '.'));
17390 $doclink = dol_buildpath('document.php', 1) . '?modulepart=' . $modulepart . '&attachment=0&file=' . urlencode($file) . '&entity=' . $conf->entity;
17391 $viewlink = dol_buildpath('viewimage.php', 1) . '?modulepart=' . $modulepart . '&file=' . urlencode($thumb) . '&entity=' . $conf->entity;
17392
17393
17394
17395 $mimeAttr = ' mime="' . $mime . '" ';
17396 $class = '';
17397 if (in_array($mime, array('image/png', 'image/jpeg', 'application/pdf'))) {
17398 $class .= ' documentpreview';
17399 }
17400
17401 $footer .= '<a href="' . $doclink . '" class="btn-link ' . $class . '" target="_blank" rel="noopener noreferrer" ' . $mimeAttr . ' >';
17402 $footer .= img_mime($filePath) . ' ' . $doc->filename;
17403 $footer .= '</a>';
17404
17405 $footer .= '</span>';
17406 }
17407 $footer .= '</div>';
17408 }
17409
17410 if (!empty($footer)) {
17411 $out .= '<div class="timeline-footer">' . $footer . '</div>';
17412 }
17413
17414 $out .= '</div>' . "\n"; // end timeline-item
17415
17416 $out .= '</li>';
17417 $out .= '<!-- END timeline item -->';
17418 }
17419
17420 $out .= "</ul>\n";
17421
17422 // Code to manage the click on button data-read-more-action to show full description of an event
17423 $out .= '<script>
17424 jQuery(document).ready(function () {
17425 $(document).on("click", "[data-read-more-action]", function(e){
17426 console.log("We click on data-read-more-action");
17427 let readMoreBloc = $(this).closest(".readmore-block");
17428 if(readMoreBloc.length > 0){
17429 e.preventDefault();
17430 if($(this).attr("data-read-more-action") == "close"){
17431 readMoreBloc.addClass("--closed").removeClass("--open");
17432 $("html, body").animate({
17433 scrollTop: readMoreBloc.offset().top - 200
17434 }, 100);
17435 }else{
17436 readMoreBloc.addClass("--open").removeClass("--closed");
17437 }
17438 }
17439 });
17440 });
17441 </script>';
17442
17443
17444 if (empty($histo)) {
17445 $out .= '<span class="opacitymedium">' . $langs->trans("NoRecordFound") . '</span>';
17446 }
17447
17448 if ($num > $MAXWITHOUTPAGINATION) {
17449 $langs->load("errors");
17450 $out .= '<center><span class="opacitymedium">...' . $langs->trans("WarningTooManyDataPleaseUseMoreFilters", $MAXWITHOUTPAGINATION) . '...</span></center>';
17451 }
17452 }
17453
17454 if ($noprint) {
17455 return $out;
17456 } else {
17457 print $out;
17458 return null;
17459 }
17460}
17461
17473function buildParamDate($prefix, $timestamp = null, $hourTime = '', $gm = 'auto')
17474{
17475 if ($timestamp === null) {
17476 $timestamp = GETPOSTDATE($prefix, $hourTime, $gm);
17477 }
17478 $TParam = array(
17479 $prefix . 'day' => intval(dol_print_date($timestamp, '%d')),
17480 $prefix . 'month' => intval(dol_print_date($timestamp, '%m')),
17481 $prefix . 'year' => intval(dol_print_date($timestamp, '%Y')),
17482 );
17483 if ($hourTime === 'getpost' || ($timestamp !== null && dol_print_date($timestamp, '%H:%M:%S') !== '00:00:00')) {
17484 $TParam = array_merge($TParam, array(
17485 $prefix . 'hour' => intval(dol_print_date($timestamp, '%H')),
17486 $prefix . 'min' => intval(dol_print_date($timestamp, '%M')),
17487 $prefix . 'sec' => intval(dol_print_date($timestamp, '%S'))
17488 ));
17489 }
17490
17491 return '&' . http_build_query($TParam);
17492}
17493
17512function recordNotFound($message = '', $printheader = 1, $printfooter = 1, $showonlymessage = 0, $params = null)
17513{
17514 global $conf, $db, $langs, $hookmanager;
17515 global $action, $object;
17516
17517 if (!is_object($langs)) {
17518 include_once DOL_DOCUMENT_ROOT . '/core/class/translate.class.php';
17519 $langs = new Translate('', $conf);
17520 $langs->setDefaultLang();
17521 }
17522
17523 $langs->load("errors");
17524
17525 if ($printheader) {
17526 if (function_exists("llxHeader")) {
17527 llxHeader('');
17528 } elseif (function_exists("llxHeaderVierge")) {
17529 llxHeaderVierge('');
17530 }
17531 }
17532
17533 print '<div class="error">';
17534 if (empty($message)) {
17535 print $langs->trans("ErrorRecordNotFound");
17536 } else {
17537 print $langs->trans($message);
17538 }
17539 print '</div>';
17540 print '<br>';
17541
17542 if (empty($showonlymessage)) {
17543 if (empty($hookmanager)) {
17544 include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
17545 $hookmanager = new HookManager($db);
17546 // Initialize a technical object to manage hooks of page. Note that conf->hooks_modules contains an array of hook context
17547 $hookmanager->initHooks(array('main'));
17548 }
17549
17550 $parameters = array('message' => $message, 'params' => $params);
17551 $reshook = $hookmanager->executeHooks('getErrorRecordNotFound', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
17552 print $hookmanager->resPrint;
17553 }
17554
17555 if ($printfooter && function_exists("llxFooter")) {
17556 llxFooter();
17557 if (is_object($db)) {
17558 $db->close();
17559 }
17560 }
17561 exit(0);
17562}
17563
17594function array_merge_recursive_distinct(array $array1, array $array2): array
17595{
17596 $merged = $array1;
17597
17598 foreach ($array2 as $key => $value) {
17599 if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
17600 $merged[$key] = array_merge_recursive_distinct($merged[$key], $value);
17601 } else {
17602 $merged[$key] = $value;
17603 }
17604 }
17605
17606 return $merged;
17607}
17608
17615function getObjectSocId($obj)
17616{
17617 if (!empty($obj->socid)) {
17618 return (int) $obj->socid;
17619 } elseif (!empty($obj->soc_id)) {
17620 return (int) $obj->soc_id;
17621 } elseif (!empty($obj->societe_id)) {
17622 return (int) $obj->societe_id;
17623 }
17624 return null;
17625}
17626
17633{
17634 $default = 10;
17635 if (!empty($_SESSION['dol_screenheight']) && $_SESSION['dol_screenheight'] < 700) {
17636 $default = 8;
17637 } elseif (!empty($_SESSION['dol_screenheight']) && $_SESSION['dol_screenheight'] < 950) {
17638 $default = 10;
17639 } elseif (!empty($_SESSION['dol_screenheight']) && $_SESSION['dol_screenheight'] > 1130) {
17640 $default = 15;
17641 }
17642
17643 return $default;
17644}
$id
Support class for third parties, contacts, members, users or resources.
Definition account.php:47
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
global $dolibarr_main_url_root
if(!defined( 'NOTOKENRENEWAL')) if(!defined('NOREQUIREMENU')) if(!defined( 'NOREQUIREHTML')) if(!defined('NOREQUIREAJAX')) if(!defined( 'NOLOGIN')) if(!defined('NOCSRFCHECK')) if(!defined( 'NOIPCHECK')) llxHeaderVierge($title, $head="", $disablejs=0, $disablehead=0, $arrayofjs=[], $arrayofcss=[], $ws='')
Header function.
ajax_combobox($htmlname, $events=array(), $minLengthToAutocomplete=0, $forcefocus=0, $widthTypeOfAutocomplete='resolve', $idforemptyvalue='-1', $morecss='')
Convert a html select field into an ajax combobox.
Definition ajax.lib.php:476
ajax_object_onoff($object, $code, $field, $text_on, $text_off, $input=array(), $morecss='', $htmlname='', $forcenojs=0, $moreparam='', $readonly=0)
On/off button to change a property status of an object This uses the ajax service objectonoff....
Definition ajax.lib.php:804
llxFooter($comment='', $zone='private', $disabledoutputofmessages=0)
Empty footer.
Definition wrapper.php:91
if(!defined('NOREQUIRESOC')) if(!defined( 'NOREQUIRETRAN')) if(!defined('NOTOKENRENEWAL')) if(!defined( 'NOREQUIREMENU')) if(!defined('NOREQUIREHTML')) if(!defined( 'NOREQUIREAJAX')) llxHeader($head='', $title='', $help_url='', $target='', $disablejs=0, $disablehead=0, $arrayofjs='', $arrayofcss='', $morequerystring='', $morecssonbody='', $replacemainareaby='', $disablenofollow=0, $disablenoindex=0)
Empty header.
Definition wrapper.php:73
$c
Definition line.php:334
$object ref
Definition info.php:90
Class to manage agenda events (actions)
Class to manage different types of events.
static getValidAddress($address, $format, $encode=0, $maxnumberofemail=0)
Return a formatted address string for SMTP protocol.
Class to manage contact/addresses.
Class to manage GeoIP conversion Usage: $geoip=new GeoIP('country',$datfile); $geoip->getCountryCodeF...
Class to manage standard extra fields.
Class to manage invoices.
Class to manage building of HTML components.
Class to manage generation of HTML components Only common components must be here.
Class to manage hooks.
Class to manage predefined suppliers products.
Class to manage products or services.
Class to manage third parties objects (customers, suppliers, prospects...)
isACompany()
Check if third party is a company (Business) or an end user (Consumer)
Class to manage translations.
Class to manage Dolibarr users.
isInEEC($object)
Return if a country of an object is inside the EEC (European Economic Community)
global $mysoc
dol_get_prev_month($month, $year)
Return previous month.
Definition date.lib.php:539
dol_get_next_day($day, $month, $year)
Return next day.
Definition date.lib.php:524
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:508
dol_get_next_month($month, $year)
Return next month.
Definition date.lib.php:558
print $script_file $mode $langs defaultlang(is_numeric($duration_value) ? " delay=". $duration_value :"").(is_numeric($duration_value2) ? " after cd cd cd description as description
Only used if Module[ID]Desc translation string is not found.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
dol_convert_file($fileinput, $ext='png', $fileoutput='', $page='')
Convert a PDF file into another image format.
dragAndDropFileUpload($htmlname)
Function to manage the drag and drop of a file.
dol_is_file($pathoffile)
Return if path is a file.
dol_dir_list($utf8_path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="", $donotfollowsymlinks=0, $nbsecondsold=0)
Scan a directory and return a list of files/directories.
Definition files.lib.php:64
dol_is_dir($folder)
Test if filename is a directory.
$date_start
Variables from include:
dolGetElementUrl($objectid, $objecttype, $withpicto=0, $option='')
Return link url to an object.
isValidMailDomain($mail)
Return true if email has a domain name that can be resolved to MX type.
isValidVATID($company)
Check if VAT numero is valid (check done on syntax only, no database or remote access)
dol_html_entity_decode($a, $b, $c='UTF-8', $keepsomeentities=0)
Replace html_entity_decode functions to manage errors.
dol_now($mode='gmt')
Return date for now.
dol_fiche_end($notab=0)
Show tab footer of a card.
dolSort($arraytosort)
Sort an array using a user defined function.
verifCond($strToEvaluate, $onlysimplestring='1')
Verify if condition in string is ok or not.
getDolGlobalLoginBadCharUnauthorized()
Return the list of unauthorized characters in user logins.
recordNotFound($message='', $printheader=1, $printfooter=1, $showonlymessage=0, $params=null)
Displays an error page when a record is not found.
getDolGlobalFloat($key, $default=0)
Return a Dolibarr global constant float value.
dol_print_size($size, $shortvalue=0, $shortunit=0)
Return string with formatted size.
isOnlyOneLocalTax($local)
Return true if LocalTax (1 or 2) is unique.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed information (by default a local PHP server timestamp) Rep...
dol_print_email($email, $contactid=0, $socid=0, $addlink=0, $max=0, $showinvalid=2, $withpicto=0, $morecss='paddingrightonly')
Show EMail link formatted for HTML output.
get_default_localtax($thirdparty_seller, $thirdparty_buyer, $local, $idprod=0)
Function that return localtax of a product line (according to seller, buyer and product vat rate) If ...
img_weather($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $morecss='')
Show weather picto.
dol_getIdFromCode($db, $key, $tablename, $fieldkey='code', $fieldid='id', $entityfilter=0, $filters='', $useCache=true)
Return an id or code from a code or id.
dolCheckFilters($sqlfilters, &$error='', &$parenthesislevel=0)
Return if a $sqlfilters parameter has a valid balance of parenthesis.
show_actions_messaging($conf, $langs, $db, $filterobj, $objcon=null, $noprint=0, $actioncode='', $donetodo='done', $filters=array(), $sortfield='a.datep, a.id', $sortorder='DESC')
Show html area with actions in messaging format.
dol_getmypid()
Return getmypid() or random PID when function is disabled Some web hosts disable this php function fo...
getLanguageCodeFromCountryCode($countrycode)
Return default language from country code.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0, $attop=0)
Set event messages in dol_events session object.
setEntity($currentobject)
Set entity id to use when to create an object.
dolForgeExplodeAnd($sqlfilters)
Explode an universal search string with AND parts.
vatrate($rate, $addpercent=false, $info_bits=0, $usestarfornpr=0, $html=0)
Return a string with VAT rate label formatted for view output Used into pdf and HTML pages.
img_credit_card($brand, $morecss='fa-2x inline-block valignmiddle')
Return image of a credit card according to its brand name.
GETPOSTDATE($prefix, $hourTime='', $gm='auto', $saverestore='')
Helper function that combines values of a dolibarr DatePicker (such as Form\selectDate) for year,...
dol_print_ip($ip, $mode=0, $showname=0)
Return an IP formatted to be shown on screen.
picto_from_langcode($codelang, $moreatt='', $notitlealt=0)
Return img flag of country for a language code or country code.
dol_ucfirst($string, $encoding="UTF-8")
Convert first character of the first word of a string to upper.
print_liste_field_titre($name, $file="", $field="", $begin="", $param="", $moreattrib="", $sortfield="", $sortorder="", $prefix="", $tooltip="", $forcenowrapcolumntitle=0)
Show title line of an array.
img_right($titlealt='default', $selected=0, $moreatt='')
Show right arrow logo.
dol_print_phone($phone, $countrycode='', $contactid=0, $socid=0, $addlink='', $separ="&nbsp;", $withpicto='', $titlealt='', $adddivfloat=0, $morecss='paddingright')
Format phone numbers according to country.
print_barre_liste($title, $page, $file, $options='', $sortfield='', $sortorder='', $morehtmlcenter='', $num=-1, $totalnboflines='', $picto='generic', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limit=-1, $selectlimitsuffix=0, $hidenavigation=0, $pagenavastextinput=0, $morehtmlrightbeforearrow='')
Print a title with navigation controls for pagination.
img_help($usehelpcursor=1, $usealttitle=1)
Show help logo with cursor "?".
dol_strtolower($string, $encoding="UTF-8")
Convert a string to lower.
showValueWithClipboardCPButton($valuetocopy, $showonlyonhover=1, $texttoshow='')
Create a button to copy $valuetocopy in the clipboard (for copy and paste feature).
dol_htmlentitiesbr_decode($stringtodecode, $pagecodeto='UTF-8')
This function is called to decode a HTML string (it decodes entities and br tags)
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
img_warning($titlealt='default', $moreatt='', $morecss='pictowarning')
Show warning logo.
img_left($titlealt='default', $selected=0, $moreatt='')
Show left arrow logo.
img_delete($titlealt='default', $other='class="pictodelete"', $morecss='')
Show delete logo.
dol_mimetype($file, $default='application/octet-stream', $mode=0)
Return MIME type of a file from its name with extension.
dolPrintHTML($s, $allowiframe=0, $moreallowedtags=array())
Return a string (that can be on several lines) ready to be output on a HTML page.
dolGetFirstLineOfText($text, $nboflines=1, $charset='UTF-8')
Return first line of text.
dol_format_address($object, $withcountry=0, $sep="\n", $outputlangs=null, $mode=0, $extralangcode='')
Return a formatted address (part address/zip/town/state) according to country rules.
getDolUserInt($key, $default=0, $tmpuser=null)
Return Dolibarr user constant int value.
dol_osencode($str)
Return a string encoded into OS filesystem encoding.
getObjectSocId($obj)
Get the socid of an object, supporting legacy attribute names.
getListLimitFromScreenHeight()
Get the limit of list to show according to the screen height.
dol_get_fiche_head($links=array(), $active='', $title='', $notab=0, $picto='', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limittoshow=0, $moretabssuffix='', $dragdropfile=0, $morecssdiv='')
Show tabs of a record.
isASecretKey($keyname)
Return if string has a name dedicated to store a secret.
dolPrintHTMLForTextArea($s, $allowiframe=0)
Return a string ready to be output on input textarea.
dol_string_nohtmltag($stringtoclean, $removelinefeed=1, $pagecodeto='UTF-8', $strip_tags=0, $removedoublespaces=1)
Clean a string from all HTML tags and entities.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_eval_new($s)
Replace eval function to add more security.
getCallerInfoString()
Get caller info as a string that can be appended to a log message.
get_htmloutput_errors($mesgstring='', $mesgarray=array(), $keepembedded=0)
Get formatted error messages to output (Used to show messages on html output).
dol_user_country()
Return country code for current user.
dol_string_onlythesehtmlattributes($stringtoclean, $allowed_attributes=null)
Clean a string from some undesirable HTML tags.
getMultidirTemp($object, $module='', $forobject=0)
Return the full path of the directory where a module (or an object of a module) stores its temporary ...
dolBuildUrl($url, $params=[], $addtoken=false, $anchor='')
Return path of url.
dolOutputDates($datep, $datef=null, $fullday=0, $addseconds=0, $pictotoadd='', $tzoutput='tzuserrel', $reduceformat=0)
Print decorated date-hour.
dol_get_fiche_end($notab=0)
Return tab footer of a card.
getDolEntity()
Return the current entity.
picto_required()
Return picto saying a field is required.
isDolTms($timestamp)
isDolTms check if a timestamp is valid.
dol_string_nospecial($str, $newstr='_', $badcharstoreplace='', $badcharstoremove='', $keepspaces=0)
Clean a string from all punctuation characters to use it as a ref or login.
dol_eval($s, $returnvalue=1, $hideerrors=1, $onlysimplestring='1')
Replace eval function to add more security.
img_action($titlealt, $numaction, $picto='', $moreatt='')
Show logo action.
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
dol_nl2br($stringtoencode, $nl2brmode=0, $forxml=false)
Replace CRLF in string with a HTML BR tag.
dol_print_url($url, $target='_blank', $max=32, $withpicto=0, $morecss='')
Show Url link.
setEventMessage($mesgs, $style='mesgs', $noduplicate=0, $attop=0)
Set event message in dol_events session object.
printCommonFooter($zone='private')
Print common footer : conf->global->MAIN_HTML_FOOTER js for switch of menu hider js for conf->global-...
dol_sanitizePathName($str, $newstr='_', $unaccent=0, $allowdash=0)
Clean a string to use it as a path name.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0, $allowdash=0)
Clean a string to use it as a file name.
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
img_down($titlealt='default', $selected=0, $moreclass='')
Show down arrow logo.
getTaxesFromId($vatrate, $buyer=null, $seller=null, $firstparamisid=1)
Get tax (VAT) main information from Id.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
dolPrintText($s)
Return a string label (possible on several lines and that should not contains any HTML) ready to be o...
utf8_valid($str)
Check if a string is in UTF8.
getPictoForType($key, $morecss='')
Return the picto for a data type.
getDolUserString($key, $default='', $tmpuser=null)
Return Dolibarr user constant string value.
getDolOptimizeSmallScreen()
Return if render must be optimized for small screen.
img_allow($allow, $titlealt='default')
Show tick logo if allowed.
isValidMXRecord($domain)
Return if the domain name has a valid MX record.
dolButtonToOpenExportDialog($name, $label, $buttonstring, $exportSiteName, $overwriteGitUrl, $website)
Create a dialog with two buttons for export and overwrite of a website.
GETPOSTISARRAY($paramname, $method=0)
Return true if the parameter $paramname is submit from a POST OR GET as an array.
jsonOrUnserialize($stringtodecode, $assoc=true)
Decode an encoded string.
dol_print_socialnetworks($value, $contactid, $socid, $type, $dictsocialnetworks=array())
Show social network link.
dolChmod($filepath, $newmask='')
Change mod of a file.
dol_fiche_head($links=array(), $active='0', $title='', $notab=0, $picto='', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limittoshow=0, $moretabssuffix='')
Show tab header of a card.
natural_search($fields, $value, $mode=0, $nofirstand=0, $sqltoadd='')
Generate natural SQL search string for a criteria (this criteria can be tested on one or several fiel...
img_mime($file, $titlealt='', $morecss='')
Show MIME img of a file.
get_localtax_by_third($local)
Get values of localtaxes (1 or 2) for company country for the common vat with the highest value.
dol_escape_php($stringtoescape, $stringforquotes=2)
Returns text escaped for inclusion into a php string, build with double quotes " or '.
dolSetCookie(string $cookiename, string $cookievalue, int $expire=-1)
Set a cookie.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_escape_js($stringtoescape, $mode=0, $noescapebackslashn=0)
Returns text escaped for inclusion into JavaScript code.
getLocalTaxesFromRate($vatrate, $local, $buyer, $seller, $firstparamisid=0)
Get type and rate of localtaxes for a particular vat rate/country of a thirdparty.
ajax_autoselect($htmlname, $addlink='', $textonlink='Link')
Make content of an input box selected when we click into input field.
img_view($titlealt='default', $float=0, $other='class="valignmiddle"')
Show logo view card.
dol_get_object_properties($obj, $properties=[])
Get value of properties for an object - including magic properties when requested.
dol_sort_array(&$array, $index, $order='asc', $natsort=0, $case_sensitive=0, $keepindex=0)
Advanced sort array by the value of a given key, which produces ascending (default) or descending out...
if(!function_exists( 'dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
dol_set_focus($selector)
Set focus onto field with selector (similar behaviour of 'autofocus' HTML5 tag)
getMultidirOutput($object, $module='', $forobject=0, $mode='output')
Return the full path of the directory where a module (or an object of a module) stores its files.
showDimensionInBestUnit($dimension, $unit, $type, $outputlangs, $round=-1, $forceunitoutput='no', $use_short_label=0)
Output a dimension with best unit.
newToken()
Return the value of token currently saved into session with name 'newtoken'.
dol_string_unaccent($str)
Clean a string from all accent characters to be used as ref, login or by dol_sanitizeFileName.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0, $nodefault=0)
Return value of a param into GET or POST supervariable.
dol_strftime($fmt, $ts=false, $is_gmt=false)
Format a string.
img_picto_common($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $notitle=0)
Show picto (generic function)
GETPOSTFLOAT($paramname, $rounding='', $option=2)
Return the value of a $_GET or $_POST supervariable, converted into float.
img_search($titlealt='default', $other='')
Show search logo.
dolGetFirstLastname($firstname, $lastname, $nameorder=-1)
Return firstname and lastname in correct order.
dol_string_neverthesehtmltags($stringtoclean, $disallowed_tags=array('textarea'), $cleanalsosomestyles=0)
Clean a string from some undesirable HTML tags.
isValidPhone($phone)
Return true if phone number syntax is ok TODO Decide what to do with this.
dol_htmlcleanlastbr($stringtodecode)
This function remove all ending and br at end.
img_previous($titlealt='default', $moreatt='')
Show previous logo.
get_default_npr(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod=0, $idprodfournprice=0)
Function that returns whether VAT must be recoverable collected VAT (e.g.: VAT NPR in France)
dol_concatdesc($text1, $text2, $forxml=false, $invert=false)
Concat 2 descriptions with a new line between them (second operand after first one with appropriate n...
dol_htmlentities($string, $flags=ENT_QUOTES|ENT_SUBSTITUTE, $encoding='UTF-8', $double_encode=false)
Replace htmlentities functions.
dolPrintHTMLForAttribute($s, $escapeonlyhtmltags=0, $allowothertags=array())
Return a string ready to be output into an HTML attribute (alt, title, data-html, ....
fieldLabel($langkey, $fieldkey, $fieldrequired=0)
Show a string with the label tag dedicated to the HTML edit field.
getBrowserInfo($user_agent)
Return information about user browser.
dolGetFirstLetters($s, $nbofchar=1)
Return first letters of a strings.
dolPrintLabel($s, $escapeonlyhtmltags=0)
Return a string label (so on 1 line only and that should not contains any HTML) ready to be output on...
forgeSQLFromUniversalSearchCriteria($filter, &$errorstr='', $noand=0, $nopar=0, $noerror=0, $forbiddenfields=array())
forgeSQLFromUniversalSearchCriteria
dol_clone_in_array($srcobject, $startlevel=0)
Create a clone of instance of object into a full array, using recursive call.
dol_strtoupper($string, $encoding="UTF-8")
Convert a string to upper.
getMultidirVersion($object, $module='', $forobject=0)
Return the full path of the directory where a module (or an object of a module) stores its versioned ...
getDolCurrency()
Return the main currency ('EUR', 'USD', ...)
dol_sanitizeUrl($stringtoclean, $type=1)
Clean a string to use it as an URL (into a href or src attribute)
yn($yesno, $format=1, $color=0)
Return yes or no in current language.
img_printer($titlealt="default", $other='')
Show printer logo.
dol_htmloutput_events($disabledoutputofmessages=0)
Print formatted messages to output (Used to show messages on html output).
getTitleFieldOfList($name, $thead=0, $file="", $field="", $begin="", $moreparam="", $moreattrib="", $sortfield="", $sortorder="", $prefix="", $disablesortlink=0, $tooltip='', $forcenowrapcolumntitle=0)
Get title line of an array.
complete_substitutions_array(&$substitutionarray, $outputlangs, $object=null, $parameters=null, $callfunc="completesubstitutionarray")
Complete the $substitutionarray with more entries coming from external module that had set the "subst...
dol_substr($string, $start, $length=null, $stringencoding='', $trunconbytes=0)
Make a substring.
ascii_check($str)
Check if a string is in ASCII.
get_date_range($date_start, $date_end, $format='', $outputlangs=null, $withparenthesis=1)
Format output for start and end date.
make_substitutions($text, $substitutionarray, $outputlangs=null, $converttextinhtmlifnecessary=0)
Make substitution into a text string, replacing keys with vals from $substitutionarray (oldval=>newva...
getImgPictoConv($mode='fa')
Get array to convert the Dolibarr picto keys into Font awesome keys.
print_date_range($date_start, $date_end, $format='', $outputlangs=null)
Format output for start and end date.
getArrayOfSocialNetworks()
Get array of social network dictionary.
getDolDefaultContextPage($s)
Return the default context page string.
safeArrayMap($callback, array $array)
Add a function to replace array_map with allowed callback.
num2Alpha($n)
Return a numeric value into an Excel like column number.
dol_size($size, $type='')
Optimize a size for some browsers (phone, smarphone...)
img_split($titlealt='default', $other='class="pictosplit"')
Show split logo.
dolGetCountryCodeFromIp($ip)
Return a country code from IP.
dol_textishtml($msg, $option=0)
Return if a text is a html content.
dolPrintPassword($s)
Return a string ready to be output on an HTML attribute (alt, title, ...)
dol_escape_all($stringtoescape)
Returns text escaped for all protocols (so only alpha chars and numbers)
GETPOSTINT($paramname, $method=0, $nodefault=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
dolForgeSQLCriteriaCallback($matches)
Function to forge a SQL criteria from a USF (Universal Filter Syntax) string.
dol_shutdown()
Function called at end of web php process.
dol_print_address($address, $htmlid, $element, $id, $noprint=0, $charfornl='')
Format address string.
dol_print_error_email($prefixcode, $errormessage='', $errormessages=array(), $morecss='error', $email='')
Show a public email and error code to contact if technical error.
dol_escape_uri($stringtoescape)
Returns text escaped by RFC 3986 for inclusion into a clickable link.
dol_print_profids($profID, $profIDtype, $countrycode='', $addcpButton=1)
Format professional IDs according to their country.
if(!function_exists( 'utf8_encode')) if(!function_exists('utf8_decode')) if(!function_exists( 'str_starts_with')) if(!function_exists('str_ends_with')) if(!function_exists( 'str_contains')) formatLogObject($data)
Return a string serialized to be output on log with dol_syslog() An option allow to output log in one...
getDolDBType()
Return the current entity.
print_titre($title)
Show a title.
showSimpleHTMLTable($outputlangs, $object)
Returns simple order table template as string.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_clone($srcobject, $native=2)
Create a clone of instance of object (new instance with same value for each properties) With native =...
dol_string_nounprintableascii($str, $removetabcrlf=1)
Clean a string from all non printable ASCII chars (0x00-0x1F and 0x7F).
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
getDolGlobalBool($key, $default=false)
Return a Dolibarr global constant boolean value.
img_error($titlealt='default')
Show error logo.
getTimelineIcon($actionstatic, &$histo, $key)
Get timeline icon.
dol_htmloutput_mesg($mesgstring='', $mesgarray=array(), $style='ok', $keepembedded=0)
Print formatted messages to output (Used to show messages on html output).
dolCloseUnclosedHtmlTags($text)
Close the HTML tags left open in a truncated HTML string.
get_product_localtax_for_country($idprod, $local, $thirdpartytouseforcountry)
Return localtax vat rate of a product in a particular country or default country vat if product is un...
getUserRemoteIP($trusted=0)
Return the real IP of remote user.
buildParamDate($prefix, $timestamp=null, $hourTime='', $gm='auto')
Helper function that combines values of a dolibarr DatePicker (such as Form\selectDate) for year,...
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
img_next($titlealt='default', $moreatt='')
Show next logo.
load_fiche_titre($title, $morehtmlright='', $picto='generic', $pictoisfullpath=0, $id='', $morecssontable='', $morehtmlcenter='', $morecssonpicto='widthpictotitle')
Load a title with picto.
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '...' if string larger than length.
dol_string_is_good_iso($s, $clean=0)
Check if a string is a correct iso string If not, it will not be considered as HTML encoded even if i...
getNonce()
Return a random string to be used as a nonce value for js.
GETPOSTISSET($paramname)
Return true if we are in a context of submitting the parameter $paramname from a POST of a form.
isStringVarMatching($var, $regextext, $matchrule=1)
Check if a variable with name $var start with $regextext.
dolSlugify($stringtoslugify)
Returns text slugified (lowercase and no special char, separator is "-").
dol_concat($text1, $text2)
Concat 2 strings.
complete_head_from_modules($conf, $langs, $object, &$head, &$h, $type, $mode='add', $filterorigmodule='')
Complete or removed entries into a head array (used to build tabs).
get_htmloutput_mesg($mesgstring='', $mesgarray=[], $style='ok', $keepembedded=0)
Get formatted messages to output (Used to show messages on html output).
dol_htmlentitiesbr($stringtoencode, $nl2brmode=0, $pagecodefrom='UTF-8', $removelasteolbr=1)
This function is called to encode a string into a HTML string but differs from htmlentities because a...
print_fleche_navigation($page, $file, $options='', $nextpage=0, $betweenarrows='', $afterarrows='', $limit=-1, $totalnboflines=0, $selectlimitsuffix='', $beforearrows='', $hidenavigation=0)
Function to show navigation arrows into lists.
dol_nboflines($s, $maxchar=0)
Return nb of lines of a clear text.
dol_htmlwithnojs($stringtoencode, $nouseofiframesandbox=0, $check='restricthtml')
Sanitize a HTML to remove js, dangerous content and external links.
isValidEmail($address, $acceptsupervisorkey=0, $acceptuserkey=0)
Return true if email syntax is ok.
dol_escape_xml($stringtoescape)
Returns text escaped for inclusion into a XML string.
getActionCommEcmList($object)
getActionCommEcmList
dol_ucwords($string, $encoding="UTF-8")
Convert first character of all the words of a string to upper.
img_edit_add($titlealt='default', $other='')
Show logo "+".
print_fiche_titre($title, $mesg='', $picto='generic', $pictoisfullpath=0, $id='')
Show a title with picto.
dolForgeDummyCriteriaCallback($matches)
Function to forge a SQL criteria from a Dolibarr filter syntax string.
dol_string_onlythesehtmltags($stringtoclean, $cleanalsosomestyles=1, $removeclassattribute=1, $cleanalsojavascript=0, $allowiframe=0, $allowed_tags=array(), $allowlink=0, $allowscript=0, $allowstyle=0, $allowphp=0)
Clean a string to keep only desirable HTML tags.
dol_escape_json($stringtoescape)
Returns text escaped for inclusion into javascript code.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
sanitizeVal($out='', $check='alphanohtml', $filter=null, $options=null)
Return a sanitized or empty value after checking value against a rule.
dol_validElement($element)
Return if var element is ok.
dol_sanitizeKeyCode($str)
Clean a string to use it as a key or code.
isModEnabled($module)
Is Dolibarr module enabled.
array_merge_recursive_distinct(array $array1, array $array2)
Recursively merges two arrays while preserving keys and replacing existing values.
img_searchclear($titlealt='default', $other='')
Show search logo.
getWarningDelay($module, $parmlevel1, $parmlevel2='')
Return a warning delay You can use it like this: if (getWarningDelay('module', 'paramlevel1')) It rep...
utf8_check($str)
Check if a string is in UTF8.
img_edit($titlealt='default', $float=0, $other='')
Show logo edit/modify fiche.
get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod=0, $idprodfournprice=0)
Function that return vat rate of a product line (according to seller, buyer and product vat rate) VAT...
img_up($titlealt='default', $selected=0, $moreclass='')
Show top arrow logo.
dol_htmloutput_errors($mesgstring='', $mesgarray=array(), $keepembedded=0)
Print formatted error messages to output (Used to show messages on html output).
info_admin($text, $infoonimgalt=0, $nodiv=0, $admin='1', $morecss='hideonsmartphone', $textfordropdown='', $picto='', $textonpictotooltip='', $cssfordropdown='info_admin')
Show information in HTML for admin users or standard users.
get_localtax($vatrate, $local, $thirdparty_buyer=null, $thirdparty_seller=null, $vatnpr=0)
Return localtax rate for a particular VAT rate, when selling a product with vat $vatrate,...
dol_eval_standard($s, $hideerrors=1, $onlysimplestring='1')
Replace eval function to add more security.
get_product_vat_for_country($idprod, $thirdpartytouseforcountry, $idprodfournprice=0)
Return vat rate of a product in a particular country, or default country vat if product is unknown.
get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart='')
Return a path to have a the directory according to object where files are stored.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
dolPrintHTMLForAttributeUrl($s)
Return a string ready to be output on a href attribute (this one need a special because we need conte...
dol_getdate($timestamp, $fast=false, $forcetimezone='')
Return an array with locale date info.
dol_mkdir($dir, $dataroot='', $newmask='')
Creation of a directory (this can create recursive subdir)
img_edit_remove($titlealt='default', $other='')
Show logo "-".
img_info($titlealt='default')
Show info logo.
getDoliDBInstance($type, $host, $user, $pass, $name, $port)
Return a DoliDB instance (database handler).
dol_sanitizeEmail($stringtoclean)
Clean a string to use it as an Email.
dol_nboflines_bis($text, $maxlinesize=0, $charset='UTF-8')
Return nb of lines of a formatted text with and (WARNING: string must not have mixed and br sep...
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
const MODULE_MAPPING
This mapping defines the conversion to the current internal names from the alternative allowed names ...
dolBECalculateStructuredCommunication($invoice_number, $invoice_type)
Calculate Structured Communication / BE Bank payment reference number.
dol_convertToWord($num, $langs, $currency='', $centimes=false)
Function to return a number into a text.
multi select button
0 = Do not include form tag and submit button -1 = Do not include form tag but include submit button
treeview li table
No Email.
div refaddress div address
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string
buildzip.php
if(!defined( 'NOREQUIREMENU')) if(!empty(GETPOST('seteventmessages', 'alpha'))) if(!function_exists("llxHeader")) top_httphead($contenttype='text/html', $forcenocache=0)
Show HTTP header.
dol_setcache($memoryid, $data, $expire=0, $filecache=0, $replace=0)
Save data into a memory area shared by all users, all sessions on server.
dol_getcache($memoryid, $filecache=0)
Read a memory area shared by all users, all sessions on server.
measuringUnitString($unitid, $measuring_style='', $unitscale=null, $use_short_label=0, $outputlangs=null)
Return translation label of a unit key.
if(preg_match('/(crypted|dolcrypt):/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
'integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]',...
Definition repair.php:137
$conf db name
Only used if Module[ID]Name translation string is not found.
Definition repair.php:140
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