dolibarr  16.0.5
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-2018 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-2021 Alexandre Spangaro <aspangaro@open-dsi.fr>
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-2022 Frédéric France <frederic.france@netlogic.fr>
17  * Copyright (C) 2019 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  *
23  * This program is free software; you can redistribute it and/or modify
24  * it under the terms of the GNU General Public License as published by
25  * the Free Software Foundation; either version 3 of the License, or
26  * (at your option) any later version.
27  *
28  * This program is distributed in the hope that it will be useful,
29  * but WITHOUT ANY WARRANTY; without even the implied warranty of
30  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31  * GNU General Public License for more details.
32  *
33  * You should have received a copy of the GNU General Public License
34  * along with this program. If not, see <https://www.gnu.org/licenses/>.
35  * or see https://www.gnu.org/
36  */
37 
44 include_once DOL_DOCUMENT_ROOT.'/core/lib/json.lib.php';
45 
46 
47 if (!function_exists('utf8_encode')) {
54  function utf8_encode($elements)
55  {
56  return mb_convert_encoding($elements, 'UTF-8', 'ISO-8859-1');
57  }
58 }
59 
60 if (!function_exists('utf8_decode')) {
67  function utf8_decode($elements)
68  {
69  return mb_convert_encoding($elements, 'ISO-8859-1', 'UTF-8');
70  }
71 }
72 
73 
80 function getDolGlobalString($key, $default = '')
81 {
82  global $conf;
83  // return $conf->global->$key ?? $default;
84  return (string) (empty($conf->global->$key) ? $default : $conf->global->$key);
85 }
86 
93 function getDolGlobalInt($key, $default = 0)
94 {
95  global $conf;
96  // return $conf->global->$key ?? $default;
97  return (int) (empty($conf->global->$key) ? $default : $conf->global->$key);
98 }
99 
105 function isModEnabled($module)
106 {
107  global $conf;
108  return !empty($conf->$module->enabled);
109 }
110 
122 function getDoliDBInstance($type, $host, $user, $pass, $name, $port)
123 {
124  require_once DOL_DOCUMENT_ROOT."/core/db/".$type.'.class.php';
125 
126  $class = 'DoliDB'.ucfirst($type);
127  $dolidb = new $class($type, $host, $user, $pass, $name, $port);
128  return $dolidb;
129 }
130 
148 function getEntity($element, $shared = 1, $currentobject = null)
149 {
150  global $conf, $mc, $hookmanager, $object, $action, $db;
151 
152  if (! is_object($hookmanager)) {
153  $hookmanager = new HookManager($db);
154  }
155 
156  // fix different element names (France to English)
157  switch ($element) {
158  case 'contrat':
159  $element = 'contract';
160  break; // "/contrat/class/contrat.class.php"
161  case 'order_supplier':
162  $element = 'supplier_order';
163  break; // "/fourn/class/fournisseur.commande.class.php"
164  case 'invoice_supplier':
165  $element = 'supplier_invoice';
166  break; // "/fourn/class/fournisseur.facture.class.php"
167  }
168 
169  if (is_object($mc)) {
170  $out = $mc->getEntity($element, $shared, $currentobject);
171  } else {
172  $out = '';
173  $addzero = array('user', 'usergroup', 'c_email_templates', 'email_template', 'default_values');
174  if (in_array($element, $addzero)) {
175  $out .= '0,';
176  }
177  $out .= ((int) $conf->entity);
178  }
179 
180  // Manipulate entities to query on the fly
181  $parameters = array(
182  'element' => $element,
183  'shared' => $shared,
184  'object' => $object,
185  'currentobject' => $currentobject,
186  'out' => $out
187  );
188  $reshook = $hookmanager->executeHooks('hookGetEntity', $parameters, $currentobject, $action); // Note that $action and $object may have been modified by some hooks
189 
190  if (is_numeric($reshook)) {
191  if ($reshook == 0 && !empty($hookmanager->resPrint)) {
192  $out .= ','.$hookmanager->resPrint; // add
193  } elseif ($reshook == 1) {
194  $out = $hookmanager->resPrint; // replace
195  }
196  }
197 
198  return $out;
199 }
200 
207 function setEntity($currentobject)
208 {
209  global $conf, $mc;
210 
211  if (is_object($mc) && method_exists($mc, 'setEntity')) {
212  return $mc->setEntity($currentobject);
213  } else {
214  return ((is_object($currentobject) && $currentobject->id > 0 && $currentobject->entity > 0) ? $currentobject->entity : $conf->entity);
215  }
216 }
217 
224 function isASecretKey($keyname)
225 {
226  return preg_match('/(_pass|password|_pw|_key|securekey|serverkey|secret\d?|p12key|exportkey|_PW_[a-z]+|token)$/i', $keyname);
227 }
228 
229 
236 function num2Alpha($n)
237 {
238  for ($r = ""; $n >= 0; $n = intval($n / 26) - 1)
239  $r = chr($n % 26 + 0x41) . $r;
240  return $r;
241 }
242 
243 
260 function getBrowserInfo($user_agent)
261 {
262  include_once DOL_DOCUMENT_ROOT.'/includes/mobiledetect/mobiledetectlib/Mobile_Detect.php';
263 
264  $name = 'unknown';
265  $version = '';
266  $os = 'unknown';
267  $phone = '';
268 
269  $user_agent = substr($user_agent, 0, 512); // Avoid to process too large user agent
270 
271  $detectmobile = new Mobile_Detect(null, $user_agent);
272  $tablet = $detectmobile->isTablet();
273 
274  if ($detectmobile->isMobile()) {
275  $phone = 'unknown';
276 
277  // If phone/smartphone, we set phone os name.
278  if ($detectmobile->is('AndroidOS')) {
279  $os = $phone = 'android';
280  } elseif ($detectmobile->is('BlackBerryOS')) {
281  $os = $phone = 'blackberry';
282  } elseif ($detectmobile->is('iOS')) {
283  $os = 'ios';
284  $phone = 'iphone';
285  } elseif ($detectmobile->is('PalmOS')) {
286  $os = $phone = 'palm';
287  } elseif ($detectmobile->is('SymbianOS')) {
288  $os = 'symbian';
289  } elseif ($detectmobile->is('webOS')) {
290  $os = 'webos';
291  } elseif ($detectmobile->is('MaemoOS')) {
292  $os = 'maemo';
293  } elseif ($detectmobile->is('WindowsMobileOS') || $detectmobile->is('WindowsPhoneOS')) {
294  $os = 'windows';
295  }
296  }
297 
298  // OS
299  if (preg_match('/linux/i', $user_agent)) {
300  $os = 'linux';
301  } elseif (preg_match('/macintosh/i', $user_agent)) {
302  $os = 'macintosh';
303  } elseif (preg_match('/windows/i', $user_agent)) {
304  $os = 'windows';
305  }
306 
307  // Name
308  $reg = array();
309  if (preg_match('/firefox(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
310  $name = 'firefox';
311  $version = $reg[2];
312  } elseif (preg_match('/edge(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
313  $name = 'edge';
314  $version = $reg[2];
315  } elseif (preg_match('/chrome(\/|\s)([\d\.]+)/i', $user_agent, $reg)) {
316  $name = 'chrome';
317  $version = $reg[2];
318  } elseif (preg_match('/chrome/i', $user_agent, $reg)) {
319  // we can have 'chrome (Mozilla...) chrome x.y' in one string
320  $name = 'chrome';
321  } elseif (preg_match('/iceweasel/i', $user_agent)) {
322  $name = 'iceweasel';
323  } elseif (preg_match('/epiphany/i', $user_agent)) {
324  $name = 'epiphany';
325  } elseif (preg_match('/safari(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
326  $name = 'safari';
327  $version = $reg[2];
328  } elseif (preg_match('/opera(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
329  // Safari is often present in string for mobile but its not.
330  $name = 'opera';
331  $version = $reg[2];
332  } elseif (preg_match('/(MSIE\s([0-9]+\.[0-9]))|.*(Trident\/[0-9]+.[0-9];.*rv:([0-9]+\.[0-9]+))/i', $user_agent, $reg)) {
333  $name = 'ie';
334  $version = end($reg);
335  } elseif (preg_match('/(Windows NT\s([0-9]+\.[0-9])).*(Trident\/[0-9]+.[0-9];.*rv:([0-9]+\.[0-9]+))/i', $user_agent, $reg)) {
336  // MS products at end
337  $name = 'ie';
338  $version = end($reg);
339  } elseif (preg_match('/l(i|y)n(x|ks)(\(|\/|\s)*([\d\.]+)/i', $user_agent, $reg)) {
340  // MS products at end
341  $name = 'lynxlinks';
342  $version = $reg[4];
343  }
344 
345  if ($tablet) {
346  $layout = 'tablet';
347  } elseif ($phone) {
348  $layout = 'phone';
349  } else {
350  $layout = 'classic';
351  }
352 
353  return array(
354  'browsername' => $name,
355  'browserversion' => $version,
356  'browseros' => $os,
357  'layout' => $layout,
358  'phone' => $phone,
359  'tablet' => $tablet
360  );
361 }
362 
368 function dol_shutdown()
369 {
370  global $conf, $user, $langs, $db;
371  $disconnectdone = false;
372  $depth = 0;
373  if (is_object($db) && !empty($db->connected)) {
374  $depth = $db->transaction_opened;
375  $disconnectdone = $db->close();
376  }
377  dol_syslog("--- End access to ".$_SERVER["PHP_SELF"].(($disconnectdone && $depth) ? ' (Warn: db disconnection forced, transaction depth was '.$depth.')' : ''), (($disconnectdone && $depth) ? LOG_WARNING : LOG_INFO));
378 }
379 
386 function GETPOSTISSET($paramname)
387 {
388  $isset = false;
389 
390  $relativepathstring = $_SERVER["PHP_SELF"];
391  // Clean $relativepathstring
392  if (constant('DOL_URL_ROOT')) {
393  $relativepathstring = preg_replace('/^'.preg_quote(constant('DOL_URL_ROOT'), '/').'/', '', $relativepathstring);
394  }
395  $relativepathstring = preg_replace('/^\//', '', $relativepathstring);
396  $relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
397  //var_dump($relativepathstring);
398  //var_dump($user->default_values);
399 
400  // Code for search criteria persistence.
401  // Retrieve values if restore_lastsearch_values
402  if (!empty($_GET['restore_lastsearch_values'])) { // Use $_GET here and not GETPOST
403  if (!empty($_SESSION['lastsearch_values_'.$relativepathstring])) { // If there is saved values
404  $tmp = json_decode($_SESSION['lastsearch_values_'.$relativepathstring], true);
405  if (is_array($tmp)) {
406  foreach ($tmp as $key => $val) {
407  if ($key == $paramname) { // We are on the requested parameter
408  $isset = true;
409  break;
410  }
411  }
412  }
413  }
414  // If there is saved contextpage, limit, page or mode
415  if ($paramname == 'contextpage' && !empty($_SESSION['lastsearch_contextpage_'.$relativepathstring])) {
416  $isset = true;
417  } elseif ($paramname == 'limit' && !empty($_SESSION['lastsearch_limit_'.$relativepathstring])) {
418  $isset = true;
419  } elseif ($paramname == 'page' && !empty($_SESSION['lastsearch_page_'.$relativepathstring])) {
420  $isset = true;
421  } elseif ($paramname == 'mode' && !empty($_SESSION['lastsearch_mode_'.$relativepathstring])) {
422  $isset = true;
423  }
424  } else {
425  $isset = (isset($_POST[$paramname]) || isset($_GET[$paramname])); // We must keep $_POST and $_GET here
426  }
427 
428  return $isset;
429 }
430 
439 function GETPOSTISARRAY($paramname, $method = 0)
440 {
441  // for $method test need return the same $val as GETPOST
442  if (empty($method)) {
443  $val = isset($_GET[$paramname]) ? $_GET[$paramname] : (isset($_POST[$paramname]) ? $_POST[$paramname] : '');
444  } elseif ($method == 1) {
445  $val = isset($_GET[$paramname]) ? $_GET[$paramname] : '';
446  } elseif ($method == 2) {
447  $val = isset($_POST[$paramname]) ? $_POST[$paramname] : '';
448  } elseif ($method == 3) {
449  $val = isset($_POST[$paramname]) ? $_POST[$paramname] : (isset($_GET[$paramname]) ? $_GET[$paramname] : '');
450  } else {
451  $val = 'BadFirstParameterForGETPOST';
452  }
453 
454  return is_array($val);
455 }
456 
484 function GETPOST($paramname, $check = 'alphanohtml', $method = 0, $filter = null, $options = null, $noreplace = 0)
485 {
486  global $mysoc, $user, $conf;
487 
488  if (empty($paramname)) {
489  return 'BadFirstParameterForGETPOST';
490  }
491  if (empty($check)) {
492  dol_syslog("Deprecated use of GETPOST, called with 1st param = ".$paramname." and 2nd param is '', when calling page ".$_SERVER["PHP_SELF"], LOG_WARNING);
493  // Enable this line to know who call the GETPOST with '' $check parameter.
494  //var_dump(debug_backtrace()[0]);
495  }
496 
497  if (empty($method)) {
498  $out = isset($_GET[$paramname]) ? $_GET[$paramname] : (isset($_POST[$paramname]) ? $_POST[$paramname] : '');
499  } elseif ($method == 1) {
500  $out = isset($_GET[$paramname]) ? $_GET[$paramname] : '';
501  } elseif ($method == 2) {
502  $out = isset($_POST[$paramname]) ? $_POST[$paramname] : '';
503  } elseif ($method == 3) {
504  $out = isset($_POST[$paramname]) ? $_POST[$paramname] : (isset($_GET[$paramname]) ? $_GET[$paramname] : '');
505  } else {
506  return 'BadThirdParameterForGETPOST';
507  }
508 
509  if (empty($method) || $method == 3 || $method == 4) {
510  $relativepathstring = $_SERVER["PHP_SELF"];
511  // Clean $relativepathstring
512  if (constant('DOL_URL_ROOT')) {
513  $relativepathstring = preg_replace('/^'.preg_quote(constant('DOL_URL_ROOT'), '/').'/', '', $relativepathstring);
514  }
515  $relativepathstring = preg_replace('/^\//', '', $relativepathstring);
516  $relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
517  //var_dump($relativepathstring);
518  //var_dump($user->default_values);
519 
520  // Code for search criteria persistence.
521  // Retrieve values if restore_lastsearch_values
522  if (!empty($_GET['restore_lastsearch_values'])) { // Use $_GET here and not GETPOST
523  if (!empty($_SESSION['lastsearch_values_'.$relativepathstring])) { // If there is saved values
524  $tmp = json_decode($_SESSION['lastsearch_values_'.$relativepathstring], true);
525  if (is_array($tmp)) {
526  foreach ($tmp as $key => $val) {
527  if ($key == $paramname) { // We are on the requested parameter
528  $out = $val;
529  break;
530  }
531  }
532  }
533  }
534  // If there is saved contextpage, page or limit
535  if ($paramname == 'contextpage' && !empty($_SESSION['lastsearch_contextpage_'.$relativepathstring])) {
536  $out = $_SESSION['lastsearch_contextpage_'.$relativepathstring];
537  } elseif ($paramname == 'limit' && !empty($_SESSION['lastsearch_limit_'.$relativepathstring])) {
538  $out = $_SESSION['lastsearch_limit_'.$relativepathstring];
539  } elseif ($paramname == 'page' && !empty($_SESSION['lastsearch_page_'.$relativepathstring])) {
540  $out = $_SESSION['lastsearch_page_'.$relativepathstring];
541  } elseif ($paramname == 'mode' && !empty($_SESSION['lastsearch_mode_'.$relativepathstring])) {
542  $out = $_SESSION['lastsearch_mode_'.$relativepathstring];
543  }
544  } elseif (!isset($_GET['sortfield'])) {
545  // Else, retrieve default values if we are not doing a sort
546  // 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
547  if (!empty($_GET['action']) && $_GET['action'] == 'create' && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) {
548  // Search default value from $object->field
549  global $object;
550  if (is_object($object) && isset($object->fields[$paramname]['default'])) {
551  $out = $object->fields[$paramname]['default'];
552  }
553  }
554  if (!empty($conf->global->MAIN_ENABLE_DEFAULT_VALUES)) {
555  if (!empty($_GET['action']) && (preg_match('/^create/', $_GET['action']) || preg_match('/^presend/', $_GET['action'])) && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) {
556  // Now search in setup to overwrite default values
557  if (!empty($user->default_values)) { // $user->default_values defined from menu 'Setup - Default values'
558  if (isset($user->default_values[$relativepathstring]['createform'])) {
559  foreach ($user->default_values[$relativepathstring]['createform'] as $defkey => $defval) {
560  $qualified = 0;
561  if ($defkey != '_noquery_') {
562  $tmpqueryarraytohave = explode('&', $defkey);
563  $tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
564  $foundintru = 0;
565  foreach ($tmpqueryarraytohave as $tmpquerytohave) {
566  if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) {
567  $foundintru = 1;
568  }
569  }
570  if (!$foundintru) {
571  $qualified = 1;
572  }
573  //var_dump($defkey.'-'.$qualified);
574  } else {
575  $qualified = 1;
576  }
577 
578  if ($qualified) {
579  if (isset($user->default_values[$relativepathstring]['createform'][$defkey][$paramname])) {
580  $out = $user->default_values[$relativepathstring]['createform'][$defkey][$paramname];
581  break;
582  }
583  }
584  }
585  }
586  }
587  } elseif (!empty($paramname) && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) {
588  // Management of default search_filters and sort order
589  if (!empty($user->default_values)) {
590  // $user->default_values defined from menu 'Setup - Default values'
591  //var_dump($user->default_values[$relativepathstring]);
592  if ($paramname == 'sortfield' || $paramname == 'sortorder') {
593  // Sorted on which fields ? ASC or DESC ?
594  if (isset($user->default_values[$relativepathstring]['sortorder'])) {
595  // Even if paramname is sortfield, data are stored into ['sortorder...']
596  foreach ($user->default_values[$relativepathstring]['sortorder'] as $defkey => $defval) {
597  $qualified = 0;
598  if ($defkey != '_noquery_') {
599  $tmpqueryarraytohave = explode('&', $defkey);
600  $tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
601  $foundintru = 0;
602  foreach ($tmpqueryarraytohave as $tmpquerytohave) {
603  if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) {
604  $foundintru = 1;
605  }
606  }
607  if (!$foundintru) {
608  $qualified = 1;
609  }
610  //var_dump($defkey.'-'.$qualified);
611  } else {
612  $qualified = 1;
613  }
614 
615  if ($qualified) {
616  $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
617  foreach ($user->default_values[$relativepathstring]['sortorder'][$defkey] as $key => $val) {
618  if ($out) {
619  $out .= ', ';
620  }
621  if ($paramname == 'sortfield') {
622  $out .= dol_string_nospecial($key, '', $forbidden_chars_to_replace);
623  }
624  if ($paramname == 'sortorder') {
625  $out .= dol_string_nospecial($val, '', $forbidden_chars_to_replace);
626  }
627  }
628  //break; // No break for sortfield and sortorder so we can cumulate fields (is it realy usefull ?)
629  }
630  }
631  }
632  } elseif (isset($user->default_values[$relativepathstring]['filters'])) {
633  foreach ($user->default_values[$relativepathstring]['filters'] as $defkey => $defval) { // $defkey is a querystring like 'a=b&c=d', $defval is key of user
634  if (!empty($_GET['disabledefaultvalues'])) { // If set of default values has been disabled by a request parameter
635  continue;
636  }
637  $qualified = 0;
638  if ($defkey != '_noquery_') {
639  $tmpqueryarraytohave = explode('&', $defkey);
640  $tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
641  $foundintru = 0;
642  foreach ($tmpqueryarraytohave as $tmpquerytohave) {
643  if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) {
644  $foundintru = 1;
645  }
646  }
647  if (!$foundintru) {
648  $qualified = 1;
649  }
650  //var_dump($defkey.'-'.$qualified);
651  } else {
652  $qualified = 1;
653  }
654 
655  if ($qualified) {
656  // We must keep $_POST and $_GET here
657  if (isset($_POST['sall']) || isset($_POST['search_all']) || isset($_GET['sall']) || isset($_GET['search_all'])) {
658  // We made a search from quick search menu, do we still use default filter ?
659  if (empty($conf->global->MAIN_DISABLE_DEFAULT_FILTER_FOR_QUICK_SEARCH)) {
660  $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
661  $out = dol_string_nospecial($user->default_values[$relativepathstring]['filters'][$defkey][$paramname], '', $forbidden_chars_to_replace);
662  }
663  } else {
664  $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
665  $out = dol_string_nospecial($user->default_values[$relativepathstring]['filters'][$defkey][$paramname], '', $forbidden_chars_to_replace);
666  }
667  break;
668  }
669  }
670  }
671  }
672  }
673  }
674  }
675  }
676 
677  // Substitution variables for GETPOST (used to get final url with variable parameters or final default value with variable parameters)
678  // Example of variables: __DAY__, __MONTH__, __YEAR__, __MYCOMPANY_COUNTRY_ID__, __USER_ID__, ...
679  // 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.
680  if (!is_array($out) && empty($_POST[$paramname]) && empty($noreplace)) {
681  $reg = array();
682  $maxloop = 20;
683  $loopnb = 0; // Protection against infinite loop
684  while (preg_match('/__([A-Z0-9]+_?[A-Z0-9]+)__/i', $out, $reg) && ($loopnb < $maxloop)) { // Detect '__ABCDEF__' as key 'ABCDEF' and '__ABC_DEF__' as key 'ABC_DEF'. Detection is also correct when 2 vars are side by side.
685  $loopnb++;
686  $newout = '';
687 
688  if ($reg[1] == 'DAY') {
689  $tmp = dol_getdate(dol_now(), true);
690  $newout = $tmp['mday'];
691  } elseif ($reg[1] == 'MONTH') {
692  $tmp = dol_getdate(dol_now(), true);
693  $newout = $tmp['mon'];
694  } elseif ($reg[1] == 'YEAR') {
695  $tmp = dol_getdate(dol_now(), true);
696  $newout = $tmp['year'];
697  } elseif ($reg[1] == 'PREVIOUS_DAY') {
698  $tmp = dol_getdate(dol_now(), true);
699  $tmp2 = dol_get_prev_day($tmp['mday'], $tmp['mon'], $tmp['year']);
700  $newout = $tmp2['day'];
701  } elseif ($reg[1] == 'PREVIOUS_MONTH') {
702  $tmp = dol_getdate(dol_now(), true);
703  $tmp2 = dol_get_prev_month($tmp['mon'], $tmp['year']);
704  $newout = $tmp2['month'];
705  } elseif ($reg[1] == 'PREVIOUS_YEAR') {
706  $tmp = dol_getdate(dol_now(), true);
707  $newout = ($tmp['year'] - 1);
708  } elseif ($reg[1] == 'NEXT_DAY') {
709  $tmp = dol_getdate(dol_now(), true);
710  $tmp2 = dol_get_next_day($tmp['mday'], $tmp['mon'], $tmp['year']);
711  $newout = $tmp2['day'];
712  } elseif ($reg[1] == 'NEXT_MONTH') {
713  $tmp = dol_getdate(dol_now(), true);
714  $tmp2 = dol_get_next_month($tmp['mon'], $tmp['year']);
715  $newout = $tmp2['month'];
716  } elseif ($reg[1] == 'NEXT_YEAR') {
717  $tmp = dol_getdate(dol_now(), true);
718  $newout = ($tmp['year'] + 1);
719  } elseif ($reg[1] == 'MYCOMPANY_COUNTRY_ID' || $reg[1] == 'MYCOUNTRY_ID' || $reg[1] == 'MYCOUNTRYID') {
720  $newout = $mysoc->country_id;
721  } elseif ($reg[1] == 'USER_ID' || $reg[1] == 'USERID') {
722  $newout = $user->id;
723  } elseif ($reg[1] == 'USER_SUPERVISOR_ID' || $reg[1] == 'SUPERVISOR_ID' || $reg[1] == 'SUPERVISORID') {
724  $newout = $user->fk_user;
725  } elseif ($reg[1] == 'ENTITY_ID' || $reg[1] == 'ENTITYID') {
726  $newout = $conf->entity;
727  } else {
728  $newout = ''; // Key not found, we replace with empty string
729  }
730  //var_dump('__'.$reg[1].'__ -> '.$newout);
731  $out = preg_replace('/__'.preg_quote($reg[1], '/').'__/', $newout, $out);
732  }
733  }
734 
735  // Check rule
736  if (preg_match('/^array/', $check)) { // If 'array' or 'array:restricthtml' or 'array:aZ09' or 'array:intcomma'
737  if (!is_array($out) || empty($out)) {
738  $out = array();
739  } else {
740  $tmparray = explode(':', $check);
741  if (!empty($tmparray[1])) {
742  $tmpcheck = $tmparray[1];
743  } else {
744  $tmpcheck = 'alphanohtml';
745  }
746  foreach ($out as $outkey => $outval) {
747  $out[$outkey] = sanitizeVal($outval, $tmpcheck, $filter, $options);
748  }
749  }
750  } else {
751  $out = sanitizeVal($out, $check, $filter, $options);
752  }
753 
754  // Sanitizing for special parameters.
755  // Note: There is no reason to allow the backtopage, backtolist or backtourl parameter to contains an external URL. Only relative URLs are allowed.
756  if ($paramname == 'backtopage' || $paramname == 'backtolist' || $paramname == 'backtourl') {
757  $out = str_replace('\\', '/', $out); // Can be before the loop because only 1 char is replaced. No risk to get it after other replacements.
758  $out = str_replace(array(':', ';', '@', "\t", ' '), '', $out); // Can be before the loop because only 1 char is replaced. No risk to retreive it after other replacements.
759  do {
760  $oldstringtoclean = $out;
761  $out = str_ireplace(array('javascript', 'vbscript', '&colon', '&#'), '', $out);
762  $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'
763  $out = preg_replace(array('/^[a-z]*\/\s*\/+/i'), '', $out); // We remove schema*// to remove external URL
764  } while ($oldstringtoclean != $out);
765  }
766 
767  // Code for search criteria persistence.
768  // Save data into session if key start with 'search_' or is 'smonth', 'syear', 'month', 'year'
769  if (empty($method) || $method == 3 || $method == 4) {
770  if (preg_match('/^search_/', $paramname) || in_array($paramname, array('sortorder', 'sortfield'))) {
771  //var_dump($paramname.' - '.$out.' '.$user->default_values[$relativepathstring]['filters'][$paramname]);
772 
773  // We save search key only if $out not empty that means:
774  // - posted value not empty, or
775  // - 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).
776 
777  if ($out != '' && isset($user)) {// $out = '0' or 'abc', it is a search criteria to keep
778  $user->lastsearch_values_tmp[$relativepathstring][$paramname] = $out;
779  }
780  }
781  }
782 
783  return $out;
784 }
785 
795 function GETPOSTINT($paramname, $method = 0)
796 {
797  return (int) GETPOST($paramname, 'int', $method, null, null, 0);
798 }
799 
800 
811 function checkVal($out = '', $check = 'alphanohtml', $filter = null, $options = null)
812 {
813  return sanitizeVal($out, $check, $filter, $options);
814 }
815 
825 function sanitizeVal($out = '', $check = 'alphanohtml', $filter = null, $options = null)
826 {
827  global $conf;
828 
829  // TODO : use class "Validate" to perform tests (and add missing tests) if needed for factorize
830  // Check is done after replacement
831  switch ($check) {
832  case 'none':
833  break;
834  case 'int': // Check param is a numeric value (integer but also float or hexadecimal)
835  if (!is_numeric($out)) {
836  $out = '';
837  }
838  break;
839  case 'intcomma':
840  if (preg_match('/[^0-9,-]+/i', $out)) {
841  $out = '';
842  }
843  break;
844  case 'san_alpha':
845  $out = filter_var($out, FILTER_SANITIZE_STRING);
846  break;
847  case 'email':
848  $out = filter_var($out, FILTER_SANITIZE_EMAIL);
849  break;
850  case 'aZ':
851  if (!is_array($out)) {
852  $out = trim($out);
853  if (preg_match('/[^a-z]+/i', $out)) {
854  $out = '';
855  }
856  }
857  break;
858  case 'aZ09':
859  if (!is_array($out)) {
860  $out = trim($out);
861  if (preg_match('/[^a-z0-9_\-\.]+/i', $out)) {
862  $out = '';
863  }
864  }
865  break;
866  case 'aZ09comma': // great to sanitize sortfield or sortorder params that can be t.abc,t.def_gh
867  if (!is_array($out)) {
868  $out = trim($out);
869  if (preg_match('/[^a-z0-9_\-\.,]+/i', $out)) {
870  $out = '';
871  }
872  }
873  break;
874  case 'nohtml': // No html
875  $out = dol_string_nohtmltag($out, 0);
876  break;
877  case 'alpha': // No html and no ../ and "
878  case 'alphanohtml': // Recommended for most scalar parameters and search parameters
879  if (!is_array($out)) {
880  $out = trim($out);
881  do {
882  $oldstringtoclean = $out;
883  // Remove html tags
884  $out = dol_string_nohtmltag($out, 0);
885  // Remove also other dangerous string sequences
886  // '"' is dangerous because param in url can close the href= or src= and add javascript functions.
887  // '../' or '..\' is dangerous because it allows dir transversals
888  // Note &#38, '&#0000038', '&#x26'... is a simple char like '&' alone but there is no reason to accept such way to encode input data.
889  $out = str_ireplace(array('&#38', '&#0000038', '&#x26', '&quot', '&#34', '&#0000034', '&#x22', '"', '&#47', '&#0000047', '&#92', '&#0000092', '&#x2F', '../', '..\\'), '', $out);
890  } while ($oldstringtoclean != $out);
891  // keep lines feed
892  }
893  break;
894  case 'alphawithlgt': // No " and no ../ but we keep balanced < > tags with no special chars inside. Can be used for email string like "Name <email>". Less secured than 'alphanohtml'
895  if (!is_array($out)) {
896  $out = trim($out);
897  do {
898  $oldstringtoclean = $out;
899  // Remove html tags
900  $out = dol_html_entity_decode($out, ENT_COMPAT | ENT_HTML5, 'UTF-8');
901  // '"' is dangerous because param in url can close the href= or src= and add javascript functions.
902  // '../' or '..\' is dangerous because it allows dir transversals
903  // Note &#38, '&#0000038', '&#x26'... is a simple char like '&' alone but there is no reason to accept such way to encode input data.
904  $out = str_ireplace(array('&#38', '&#0000038', '&#x26', '&quot', '&#34', '&#0000034', '&#x22', '"', '&#47', '&#0000047', '&#92', '&#0000092', '&#x2F', '../', '..\\'), '', $out);
905  } while ($oldstringtoclean != $out);
906  }
907  break;
908  case 'restricthtml': // Recommended for most html textarea
909  case 'restricthtmlallowunvalid':
910  do {
911  $oldstringtoclean = $out;
912 
913  if (!empty($out) && !empty($conf->global->MAIN_RESTRICTHTML_ONLY_VALID_HTML) && $check != 'restricthtmlallowunvalid') {
914  try {
915  $dom = new DOMDocument;
916  // Add a trick to solve pb with text without parent tag
917  // like '<h1>Foo</h1><p>bar</p>' that ends up with '<h1>Foo<p>bar</p></h1>'
918  // like 'abc' that ends up with '<p>abc</p>'
919  $out = '<div class="tricktoremove">'.$out.'</div>';
920 
921  $dom->loadHTML($out, LIBXML_ERR_NONE|LIBXML_HTML_NOIMPLIED|LIBXML_HTML_NODEFDTD|LIBXML_NONET|LIBXML_NOWARNING|LIBXML_NOXMLDECL);
922  $out = trim($dom->saveHTML());
923 
924  // Remove the trick added to solve pb with text without parent tag
925  $out = preg_replace('/^<div class="tricktoremove">/', '', $out);
926  $out = preg_replace('/<\/div>$/', '', $out);
927  } catch (Exception $e) {
928  //print $e->getMessage();
929  return 'InvalidHTMLString';
930  }
931  }
932 
933  // Ckeditor use the numeric entitic for apostrophe so we force it to text entity (all other special chars are
934  // encoded using text entities) so we can then exclude all numeric entities.
935  $out = preg_replace('/&#39;/i', '&apos;', $out);
936 
937  // 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).
938  // 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
939  // using a non coventionnel way to be encoded, to not have them sanitized just after)
940  //$out = preg_replace_callback('/&#(x?[0-9][0-9a-f]+;?)/i', 'realCharForNumericEntities', $out);
941  $out = preg_replace_callback('/&#(x?[0-9][0-9a-f]+;?)/i', function ($m) {
942  return realCharForNumericEntities($m); }, $out);
943 
944 
945  // Now we remove all remaining HTML entities starting with a number. We don't want such entities.
946  $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'.
947 
948  $out = dol_string_onlythesehtmltags($out, 0, 1, 1);
949 
950  // We should also exclude non expected HTML attributes and clean content of some attributes.
951  if (!empty($conf->global->MAIN_RESTRICTHTML_REMOVE_ALSO_BAD_ATTRIBUTES)) {
952  // Warning, the function may add a LF so we are forced to trim to compare with old $out without having always a difference and an infinit loop.
954  }
955 
956  // Restore entity &apos; into &#39; (restricthtml is for html content so we can use html entity)
957  $out = preg_replace('/&apos;/i', "&#39;", $out);
958  } while ($oldstringtoclean != $out);
959  break;
960  case 'custom':
961  if (empty($filter)) {
962  return 'BadFourthParameterForGETPOST';
963  }
964  $out = filter_var($out, $filter, $options);
965  break;
966  }
967 
968  return $out;
969 }
970 
971 
972 if (!function_exists('dol_getprefix')) {
982  function dol_getprefix($mode = '')
983  {
984  // If prefix is for email (we need to have $conf already loaded for this case)
985  if ($mode == 'email') {
986  global $conf;
987 
988  if (!empty($conf->global->MAIL_PREFIX_FOR_EMAIL_ID)) { // If MAIL_PREFIX_FOR_EMAIL_ID is set
989  if ($conf->global->MAIL_PREFIX_FOR_EMAIL_ID != 'SERVER_NAME') {
990  return $conf->global->MAIL_PREFIX_FOR_EMAIL_ID;
991  } elseif (isset($_SERVER["SERVER_NAME"])) { // If MAIL_PREFIX_FOR_EMAIL_ID is set to 'SERVER_NAME'
992  return $_SERVER["SERVER_NAME"];
993  }
994  }
995 
996  // The recommended value if MAIL_PREFIX_FOR_EMAIL_ID is not defined (may be not defined for old versions)
997  if (!empty($conf->file->instance_unique_id)) {
998  return sha1('dolibarr'.$conf->file->instance_unique_id);
999  }
1000 
1001  // For backward compatibility when instance_unique_id is not set
1002  return sha1(DOL_DOCUMENT_ROOT.DOL_URL_ROOT);
1003  }
1004 
1005  // If prefix is for session (no need to have $conf loaded)
1006  global $dolibarr_main_instance_unique_id, $dolibarr_main_cookie_cryptkey; // This is loaded by filefunc.inc.php
1007  $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
1008 
1009  // The recommended value (may be not defined for old versions)
1010  if (!empty($tmp_instance_unique_id)) {
1011  return sha1('dolibarr'.$tmp_instance_unique_id);
1012  }
1013 
1014  // For backward compatibility when instance_unique_id is not set
1015  if (isset($_SERVER["SERVER_NAME"]) && isset($_SERVER["DOCUMENT_ROOT"])) {
1016  return sha1($_SERVER["SERVER_NAME"].$_SERVER["DOCUMENT_ROOT"].DOL_DOCUMENT_ROOT.DOL_URL_ROOT);
1017  } else {
1018  return sha1(DOL_DOCUMENT_ROOT.DOL_URL_ROOT);
1019  }
1020  }
1021 }
1022 
1033 function dol_include_once($relpath, $classname = '')
1034 {
1035  global $conf, $langs, $user, $mysoc; // Do not remove this. They must be defined for files we include. Other globals var must be retrieved with $GLOBALS['var']
1036 
1037  $fullpath = dol_buildpath($relpath);
1038 
1039  if (!file_exists($fullpath)) {
1040  dol_syslog('functions::dol_include_once Tried to load unexisting file: '.$relpath, LOG_WARNING);
1041  return false;
1042  }
1043 
1044  if (!empty($classname) && !class_exists($classname)) {
1045  return include $fullpath;
1046  } else {
1047  return include_once $fullpath;
1048  }
1049 }
1050 
1051 
1062 function dol_buildpath($path, $type = 0, $returnemptyifnotfound = 0)
1063 {
1064  global $conf;
1065 
1066  $path = preg_replace('/^\//', '', $path);
1067 
1068  if (empty($type)) { // For a filesystem path
1069  $res = DOL_DOCUMENT_ROOT.'/'.$path; // Standard default path
1070  if (is_array($conf->file->dol_document_root)) {
1071  foreach ($conf->file->dol_document_root as $key => $dirroot) { // ex: array("main"=>"/home/main/htdocs", "alt0"=>"/home/dirmod/htdocs", ...)
1072  if ($key == 'main') {
1073  continue;
1074  }
1075  if (file_exists($dirroot.'/'.$path)) {
1076  $res = $dirroot.'/'.$path;
1077  return $res;
1078  }
1079  }
1080  }
1081  if ($returnemptyifnotfound) {
1082  // Not found into alternate dir
1083  if ($returnemptyifnotfound == 1 || !file_exists($res)) {
1084  return '';
1085  }
1086  }
1087  } else {
1088  // For an url path
1089  // We try to get local path of file on filesystem from url
1090  // Note that trying to know if a file on disk exist by forging path on disk from url
1091  // works only for some web server and some setup. This is bugged when
1092  // using proxy, rewriting, virtual path, etc...
1093  $res = '';
1094  if ($type == 1) {
1095  $res = DOL_URL_ROOT.'/'.$path; // Standard value
1096  }
1097  if ($type == 2) {
1098  $res = DOL_MAIN_URL_ROOT.'/'.$path; // Standard value
1099  }
1100  if ($type == 3) {
1101  $res = DOL_URL_ROOT.'/'.$path;
1102  }
1103 
1104  foreach ($conf->file->dol_document_root as $key => $dirroot) { // ex: array(["main"]=>"/home/main/htdocs", ["alt0"]=>"/home/dirmod/htdocs", ...)
1105  if ($key == 'main') {
1106  if ($type == 3) {
1107  global $dolibarr_main_url_root;
1108 
1109  // Define $urlwithroot
1110  $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
1111  $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file
1112  //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current
1113 
1114  $res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : $urlwithroot).'/'.$path; // Test on start with http is for old conf syntax
1115  }
1116  continue;
1117  }
1118  preg_match('/^([^\?]+(\.css\.php|\.css|\.js\.php|\.js|\.png|\.jpg|\.php)?)/i', $path, $regs); // Take part before '?'
1119  if (!empty($regs[1])) {
1120  //print $key.'-'.$dirroot.'/'.$path.'-'.$conf->file->dol_url_root[$type].'<br>'."\n";
1121  if (file_exists($dirroot.'/'.$regs[1])) {
1122  if ($type == 1) {
1123  $res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : DOL_URL_ROOT).$conf->file->dol_url_root[$key].'/'.$path;
1124  }
1125  if ($type == 2) {
1126  $res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : DOL_MAIN_URL_ROOT).$conf->file->dol_url_root[$key].'/'.$path;
1127  }
1128  if ($type == 3) {
1129  global $dolibarr_main_url_root;
1130 
1131  // Define $urlwithroot
1132  $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
1133  $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file
1134  //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current
1135 
1136  $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
1137  }
1138  break;
1139  }
1140  }
1141  }
1142  }
1143 
1144  return $res;
1145 }
1146 
1157 function dol_clone($object, $native = 0)
1158 {
1159  if (empty($native)) {
1160  $tmpsavdb = null;
1161  if (isset($object->db) && isset($object->db->db) && is_object($object->db->db) && get_class($object->db->db) == 'PgSql\Connection') {
1162  $tmpsavdb = $object->db;
1163  unset($object->db); // Such property can not be serialized when PgSql/Connection
1164  }
1165 
1166  $myclone = unserialize(serialize($object)); // serialize then unserialize is hack to be sure to have a new object for all fields
1167 
1168  if ($tmpsavdb) {
1169  $object->db = $tmpsavdb;
1170  }
1171  } else {
1172  $myclone = clone $object; // PHP clone is a shallow copy only, not a real clone, so properties of references will keep the reference (refering to the same target/variable)
1173  }
1174 
1175  return $myclone;
1176 }
1177 
1187 function dol_size($size, $type = '')
1188 {
1189  global $conf;
1190  if (empty($conf->dol_optimize_smallscreen)) {
1191  return $size;
1192  }
1193  if ($type == 'width' && $size > 250) {
1194  return 250;
1195  } else {
1196  return 10;
1197  }
1198 }
1199 
1200 
1212 function dol_sanitizeFileName($str, $newstr = '_', $unaccent = 1)
1213 {
1214  // List of special chars for filenames in windows are defined on page https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
1215  // Char '>' '<' '|' '$' and ';' are special chars for shells.
1216  // Char '/' and '\' are file delimiters.
1217  // Chars '--' can be used into filename to inject special paramaters like --use-compress-program to make command with file as parameter making remote execution of command
1218  $filesystem_forbidden_chars = array('<', '>', '/', '\\', '?', '*', '|', '"', ':', '°', '$', ';');
1219  $tmp = dol_string_nospecial($unaccent ? dol_string_unaccent($str) : $str, $newstr, $filesystem_forbidden_chars);
1220  $tmp = preg_replace('/\-\-+/', '_', $tmp);
1221  $tmp = preg_replace('/\s+\-([^\s])/', ' _$1', $tmp);
1222  $tmp = str_replace('..', '', $tmp);
1223  return $tmp;
1224 }
1225 
1237 function dol_sanitizePathName($str, $newstr = '_', $unaccent = 1)
1238 {
1239  // List of special chars for filenames in windows are defined on page https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
1240  // Char '>' '<' '|' '$' and ';' are special chars for shells.
1241  // Chars '--' can be used into filename to inject special paramaters like --use-compress-program to make command with file as parameter making remote execution of command
1242  $filesystem_forbidden_chars = array('<', '>', '?', '*', '|', '"', '°', '$', ';');
1243  $tmp = dol_string_nospecial($unaccent ? dol_string_unaccent($str) : $str, $newstr, $filesystem_forbidden_chars);
1244  $tmp = preg_replace('/\-\-+/', '_', $tmp);
1245  $tmp = preg_replace('/\s+\-([^\s])/', ' _$1', $tmp);
1246  $tmp = str_replace('..', '', $tmp);
1247  return $tmp;
1248 }
1249 
1257 function dol_sanitizeUrl($stringtoclean, $type = 1)
1258 {
1259  // 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)
1260  // We should use dol_string_nounprintableascii but function may not be yet loaded/available
1261  $stringtoclean = preg_replace('/[\x00-\x1F\x7F]/u', '', $stringtoclean); // /u operator makes UTF8 valid characters being ignored so are not included into the replace
1262  // We clean html comments because some hacks try to obfuscate evil strings by inserting HTML comments. Example: on<!-- -->error=alert(1)
1263  $stringtoclean = preg_replace('/<!--[^>]*-->/', '', $stringtoclean);
1264 
1265  $stringtoclean = str_replace('\\', '/', $stringtoclean);
1266  if ($type == 1) {
1267  // removing : should disable links to external url like http:aaa)
1268  // removing ';' should disable "named" html entities encode into an url (we should not have this into an url)
1269  $stringtoclean = str_replace(array(':', ';', '@'), '', $stringtoclean);
1270  }
1271 
1272  do {
1273  $oldstringtoclean = $stringtoclean;
1274  // removing '&colon' should disable links to external url like http:aaa)
1275  // removing '&#' should disable "numeric" html entities encode into an url (we should not have this into an url)
1276  $stringtoclean = str_ireplace(array('javascript', 'vbscript', '&colon', '&#'), '', $stringtoclean);
1277  } while ($oldstringtoclean != $stringtoclean);
1278 
1279  if ($type == 1) {
1280  // removing '//' should disable links to external url like //aaa or http//)
1281  $stringtoclean = preg_replace(array('/^[a-z]*\/\/+/i'), '', $stringtoclean);
1282  }
1283 
1284  return $stringtoclean;
1285 }
1286 
1295 function dol_string_unaccent($str)
1296 {
1297  global $conf;
1298 
1299  if (utf8_check($str)) {
1300  if (extension_loaded('intl') && !empty($conf->global->MAIN_UNACCENT_USE_TRANSLITERATOR)) {
1301  $transliterator = \Transliterator::createFromRules(':: Any-Latin; :: Latin-ASCII; :: NFD; :: [:Nonspacing Mark:] Remove; :: NFC;', \Transliterator::FORWARD);
1302  return $transliterator->transliterate($str);
1303  }
1304  // See http://www.utf8-chartable.de/
1305  $string = rawurlencode($str);
1306  $replacements = array(
1307  '%C3%80' => 'A', '%C3%81' => 'A', '%C3%82' => 'A', '%C3%83' => 'A', '%C3%84' => 'A', '%C3%85' => 'A',
1308  '%C3%87' => 'C',
1309  '%C3%88' => 'E', '%C3%89' => 'E', '%C3%8A' => 'E', '%C3%8B' => 'E',
1310  '%C3%8C' => 'I', '%C3%8D' => 'I', '%C3%8E' => 'I', '%C3%8F' => 'I',
1311  '%C3%91' => 'N',
1312  '%C3%92' => 'O', '%C3%93' => 'O', '%C3%94' => 'O', '%C3%95' => 'O', '%C3%96' => 'O',
1313  '%C5%A0' => 'S',
1314  '%C3%99' => 'U', '%C3%9A' => 'U', '%C3%9B' => 'U', '%C3%9C' => 'U',
1315  '%C3%9D' => 'Y', '%C5%B8' => 'y',
1316  '%C3%A0' => 'a', '%C3%A1' => 'a', '%C3%A2' => 'a', '%C3%A3' => 'a', '%C3%A4' => 'a', '%C3%A5' => 'a',
1317  '%C3%A7' => 'c',
1318  '%C3%A8' => 'e', '%C3%A9' => 'e', '%C3%AA' => 'e', '%C3%AB' => 'e',
1319  '%C3%AC' => 'i', '%C3%AD' => 'i', '%C3%AE' => 'i', '%C3%AF' => 'i',
1320  '%C3%B1' => 'n',
1321  '%C3%B2' => 'o', '%C3%B3' => 'o', '%C3%B4' => 'o', '%C3%B5' => 'o', '%C3%B6' => 'o',
1322  '%C5%A1' => 's',
1323  '%C3%B9' => 'u', '%C3%BA' => 'u', '%C3%BB' => 'u', '%C3%BC' => 'u',
1324  '%C3%BD' => 'y', '%C3%BF' => 'y'
1325  );
1326  $string = strtr($string, $replacements);
1327  return rawurldecode($string);
1328  } else {
1329  // See http://www.ascii-code.com/
1330  $string = strtr(
1331  $str,
1332  "\xC0\xC1\xC2\xC3\xC4\xC5\xC7
1333  \xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1
1334  \xD2\xD3\xD4\xD5\xD8\xD9\xDA\xDB\xDD
1335  \xE0\xE1\xE2\xE3\xE4\xE5\xE7\xE8\xE9\xEA\xEB
1336  \xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF8
1337  \xF9\xFA\xFB\xFC\xFD\xFF",
1338  "AAAAAAC
1339  EEEEIIIIDN
1340  OOOOOUUUY
1341  aaaaaaceeee
1342  iiiidnooooo
1343  uuuuyy"
1344  );
1345  $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"));
1346  return $string;
1347  }
1348 }
1349 
1362 function dol_string_nospecial($str, $newstr = '_', $badcharstoreplace = '', $badcharstoremove = '')
1363 {
1364  $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ",", ";", "=", '°'); // more complete than dol_sanitizeFileName
1365  $forbidden_chars_to_remove = array();
1366  //$forbidden_chars_to_remove=array("(",")");
1367 
1368  if (is_array($badcharstoreplace)) {
1369  $forbidden_chars_to_replace = $badcharstoreplace;
1370  }
1371  if (is_array($badcharstoremove)) {
1372  $forbidden_chars_to_remove = $badcharstoremove;
1373  }
1374 
1375  return str_replace($forbidden_chars_to_replace, $newstr, str_replace($forbidden_chars_to_remove, "", $str));
1376 }
1377 
1378 
1392 function dol_string_nounprintableascii($str, $removetabcrlf = 1)
1393 {
1394  if ($removetabcrlf) {
1395  return preg_replace('/[\x00-\x1F\x7F]/u', '', $str); // /u operator makes UTF8 valid characters being ignored so are not included into the replace
1396  } else {
1397  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
1398  }
1399 }
1400 
1409 function dol_escape_js($stringtoescape, $mode = 0, $noescapebackslashn = 0)
1410 {
1411  // escape quotes and backslashes, newlines, etc.
1412  $substitjs = array("&#039;"=>"\\'", "\r"=>'\\r');
1413  //$substitjs['</']='<\/'; // We removed this. Should be useless.
1414  if (empty($noescapebackslashn)) {
1415  $substitjs["\n"] = '\\n';
1416  $substitjs['\\'] = '\\\\';
1417  }
1418  if (empty($mode)) {
1419  $substitjs["'"] = "\\'";
1420  $substitjs['"'] = "\\'";
1421  } elseif ($mode == 1) {
1422  $substitjs["'"] = "\\'";
1423  } elseif ($mode == 2) {
1424  $substitjs['"'] = '\\"';
1425  } elseif ($mode == 3) {
1426  $substitjs["'"] = "\\'";
1427  $substitjs['"'] = "\\\"";
1428  }
1429  return strtr($stringtoescape, $substitjs);
1430 }
1431 
1438 function dol_escape_json($stringtoescape)
1439 {
1440  return str_replace('"', '\"', $stringtoescape);
1441 }
1442 
1454 function dol_escape_htmltag($stringtoescape, $keepb = 0, $keepn = 0, $noescapetags = '', $escapeonlyhtmltags = 0)
1455 {
1456  if ($noescapetags == 'common') {
1457  $noescapetags = 'html,body,a,b,em,hr,i,u,ul,li,br,div,img,font,p,span,strong,table,tr,td,th,tbody';
1458  }
1459 
1460  // escape quotes and backslashes, newlines, etc.
1461  if ($escapeonlyhtmltags) {
1462  $tmp = htmlspecialchars_decode((string) $stringtoescape, ENT_COMPAT);
1463  } else {
1464  $tmp = html_entity_decode((string) $stringtoescape, ENT_COMPAT, 'UTF-8');
1465  }
1466  if (!$keepb) {
1467  $tmp = strtr($tmp, array("<b>"=>'', '</b>'=>''));
1468  }
1469  if (!$keepn) {
1470  $tmp = strtr($tmp, array("\r"=>'\\r', "\n"=>'\\n'));
1471  }
1472 
1473  if ($escapeonlyhtmltags) {
1474  return htmlspecialchars($tmp, ENT_COMPAT, 'UTF-8');
1475  } else {
1476  // Escape tags to keep
1477  // TODO Does not works yet when there is attributes to tag
1478  $tmparrayoftags = array();
1479  if ($noescapetags) {
1480  $tmparrayoftags = explode(',', $noescapetags);
1481  }
1482  if (count($tmparrayoftags)) {
1483  foreach ($tmparrayoftags as $tagtoreplace) {
1484  $tmp = str_ireplace('<'.$tagtoreplace.'>', '__BEGINTAGTOREPLACE'.$tagtoreplace.'__', $tmp);
1485  $tmp = str_ireplace('</'.$tagtoreplace.'>', '__ENDTAGTOREPLACE'.$tagtoreplace.'__', $tmp);
1486  $tmp = str_ireplace('<'.$tagtoreplace.' />', '__BEGINENDTAGTOREPLACE'.$tagtoreplace.'__', $tmp);
1487  }
1488  }
1489 
1490  $result = htmlentities($tmp, ENT_COMPAT, 'UTF-8');
1491 
1492  if (count($tmparrayoftags)) {
1493  foreach ($tmparrayoftags as $tagtoreplace) {
1494  $result = str_ireplace('__BEGINTAGTOREPLACE'.$tagtoreplace.'__', '<'.$tagtoreplace.'>', $result);
1495  $result = str_ireplace('__ENDTAGTOREPLACE'.$tagtoreplace.'__', '</'.$tagtoreplace.'>', $result);
1496  $result = str_ireplace('__BEGINENDTAGTOREPLACE'.$tagtoreplace.'__', '<'.$tagtoreplace.' />', $result);
1497  }
1498  }
1499 
1500  return $result;
1501  }
1502 }
1503 
1511 function dol_strtolower($string, $encoding = "UTF-8")
1512 {
1513  if (function_exists('mb_strtolower')) {
1514  return mb_strtolower($string, $encoding);
1515  } else {
1516  return strtolower($string);
1517  }
1518 }
1519 
1527 function dol_strtoupper($string, $encoding = "UTF-8")
1528 {
1529  if (function_exists('mb_strtoupper')) {
1530  return mb_strtoupper($string, $encoding);
1531  } else {
1532  return strtoupper($string);
1533  }
1534 }
1535 
1543 function dol_ucfirst($string, $encoding = "UTF-8")
1544 {
1545  if (function_exists('mb_substr')) {
1546  return mb_strtoupper(mb_substr($string, 0, 1, $encoding), $encoding).mb_substr($string, 1, null, $encoding);
1547  } else {
1548  return ucfirst($string);
1549  }
1550 }
1551 
1559 function dol_ucwords($string, $encoding = "UTF-8")
1560 {
1561  if (function_exists('mb_convert_case')) {
1562  return mb_convert_case($string, MB_CASE_TITLE, $encoding);
1563  } else {
1564  return ucwords($string);
1565  }
1566 }
1567 
1589 function dol_syslog($message, $level = LOG_INFO, $ident = 0, $suffixinfilename = '', $restricttologhandler = '', $logcontext = null)
1590 {
1591  global $conf, $user, $debugbar;
1592 
1593  // If syslog module enabled
1594  if (empty($conf->syslog->enabled)) {
1595  return;
1596  }
1597 
1598  // Check if we are into execution of code of a website
1599  if (defined('USEEXTERNALSERVER') && !defined('USEDOLIBARRSERVER') && !defined('USEDOLIBARREDITOR')) {
1600  global $website, $websitekey;
1601  if (is_object($website) && !empty($website->ref)) {
1602  $suffixinfilename .= '_website_'.$website->ref;
1603  } elseif (!empty($websitekey)) {
1604  $suffixinfilename .= '_website_'.$websitekey;
1605  }
1606  }
1607 
1608  // Check if we have a forced suffix
1609  if (defined('USESUFFIXINLOG')) {
1610  $suffixinfilename .= constant('USESUFFIXINLOG');
1611  }
1612 
1613  if ($ident < 0) {
1614  foreach ($conf->loghandlers as $loghandlerinstance) {
1615  $loghandlerinstance->setIdent($ident);
1616  }
1617  }
1618 
1619  if (!empty($message)) {
1620  // Test log level
1621  $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');
1622  if (!array_key_exists($level, $logLevels)) {
1623  throw new Exception('Incorrect log level');
1624  }
1625  if ($level > $conf->global->SYSLOG_LEVEL) {
1626  return;
1627  }
1628 
1629  if (empty($conf->global->MAIN_SHOW_PASSWORD_INTO_LOG)) {
1630  $message = preg_replace('/password=\'[^\']*\'/', 'password=\'hidden\'', $message); // protection to avoid to have value of password in log
1631  }
1632 
1633  // If adding log inside HTML page is required
1634  if ((!empty($_REQUEST['logtohtml']) && !empty($conf->global->MAIN_ENABLE_LOG_TO_HTML))
1635  || (!empty($user->rights->debugbar->read) && is_object($debugbar))) {
1636  $conf->logbuffer[] = dol_print_date(time(), "%Y-%m-%d %H:%M:%S")." ".$logLevels[$level]." ".$message;
1637  }
1638 
1639  //TODO: Remove this. MAIN_ENABLE_LOG_INLINE_HTML should be deprecated and use a log handler dedicated to HTML output
1640  // If html log tag enabled and url parameter log defined, we show output log on HTML comments
1641  if (!empty($conf->global->MAIN_ENABLE_LOG_INLINE_HTML) && !empty($_GET["log"])) {
1642  print "\n\n<!-- Log start\n";
1643  print dol_escape_htmltag($message)."\n";
1644  print "Log end -->\n";
1645  }
1646 
1647  $data = array(
1648  'message' => $message,
1649  'script' => (isset($_SERVER['PHP_SELF']) ? basename($_SERVER['PHP_SELF'], '.php') : false),
1650  'level' => $level,
1651  'user' => ((is_object($user) && $user->id) ? $user->login : false),
1652  'ip' => false
1653  );
1654 
1655  $remoteip = getUserRemoteIP(); // Get ip when page run on a web server
1656  if (!empty($remoteip)) {
1657  $data['ip'] = $remoteip;
1658  // This is when server run behind a reverse proxy
1659  if (!empty($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR'] != $remoteip) {
1660  $data['ip'] = $_SERVER['HTTP_X_FORWARDED_FOR'].' -> '.$data['ip'];
1661  } elseif (!empty($_SERVER['HTTP_CLIENT_IP']) && $_SERVER['HTTP_CLIENT_IP'] != $remoteip) {
1662  $data['ip'] = $_SERVER['HTTP_CLIENT_IP'].' -> '.$data['ip'];
1663  }
1664  } elseif (!empty($_SERVER['SERVER_ADDR'])) {
1665  // This is when PHP session is ran inside a web server but not inside a client request (example: init code of apache)
1666  $data['ip'] = $_SERVER['SERVER_ADDR'];
1667  } elseif (!empty($_SERVER['COMPUTERNAME'])) {
1668  // This is when PHP session is ran outside a web server, like from Windows command line (Not always defined, but useful if OS defined it).
1669  $data['ip'] = $_SERVER['COMPUTERNAME'].(empty($_SERVER['USERNAME']) ? '' : '@'.$_SERVER['USERNAME']);
1670  } elseif (!empty($_SERVER['LOGNAME'])) {
1671  // This is when PHP session is ran outside a web server, like from Linux command line (Not always defined, but usefull if OS defined it).
1672  $data['ip'] = '???@'.$_SERVER['LOGNAME'];
1673  }
1674 
1675  // Loop on each log handler and send output
1676  foreach ($conf->loghandlers as $loghandlerinstance) {
1677  if ($restricttologhandler && $loghandlerinstance->code != $restricttologhandler) {
1678  continue;
1679  }
1680  $loghandlerinstance->export($data, $suffixinfilename);
1681  }
1682  unset($data);
1683  }
1684 
1685  if ($ident > 0) {
1686  foreach ($conf->loghandlers as $loghandlerinstance) {
1687  $loghandlerinstance->setIdent($ident);
1688  }
1689  }
1690 }
1691 
1706 function dolButtonToOpenUrlInDialogPopup($name, $label, $buttonstring, $url, $disabled = '', $morecss = 'button bordertransp', $backtopagejsfields = '')
1707 {
1708  if (strpos($url, '?') > 0) {
1709  $url .= '&dol_hide_topmenu=1&dol_hide_leftmenu=1&dol_openinpopup='.urlencode($name);
1710  } else {
1711  $url .= '?dol_hide_topmenu=1&dol_hide_leftmenu=1&dol_openinpopup='.urlencode($name);
1712  }
1713 
1714  $out = '';
1715 
1716  $backtopagejsfieldsid = ''; $backtopagejsfieldslabel = '';
1717  if ($backtopagejsfields) {
1718  $tmpbacktopagejsfields = explode(':', $backtopagejsfields);
1719  if (empty($tmpbacktopagejsfields[1])) { // If the part 'keyforpopupid:' is missing, we add $name for it.
1720  $backtopagejsfields = $name.":".$backtopagejsfields;
1721  $tmp2backtopagejsfields = explode(',', $tmpbacktopagejsfields[0]);
1722  } else {
1723  $tmp2backtopagejsfields = explode(',', $tmpbacktopagejsfields[1]);
1724  }
1725  $backtopagejsfieldsid = empty($tmp2backtopagejsfields[0]) ? '' : $tmp2backtopagejsfields[0];
1726  $backtopagejsfieldslabel = empty($tmp2backtopagejsfields[1]) ? '' : $tmp2backtopagejsfields[1];
1727  $url .= '&backtopagejsfields='.urlencode($backtopagejsfields);
1728  }
1729 
1730  //print '<input type="submit" class="button bordertransp"'.$disabled.' value="'.dol_escape_htmltag($langs->trans("MediaFiles")).'" name="file_manager">';
1731  $out .= '<!-- a link for button to open url into a dialog popup backtopagejsfields = '.$backtopagejsfields.' -->'."\n";
1732  $out .= '<a class="cursorpointer button_'.$name.($morecss ? ' '.$morecss : '').'"'.$disabled.' title="'.dol_escape_htmltag($label).'">'.$buttonstring.'</a>';
1733  $out .= '<div id="idfordialog'.$name.'" class="hidden">div for dialog</div>';
1734  $out .= '<div id="varforreturndialogid'.$name.'" class="hidden">div for returned id</div>';
1735  $out .= '<div id="varforreturndialoglabel'.$name.'" class="hidden">div for returned label</div>';
1736  $out .= '<!-- Add js code to open dialog popup on dialog -->';
1737  $out .= '<script type="text/javascript">
1738  jQuery(document).ready(function () {
1739  jQuery(".button_'.$name.'").click(function () {
1740  console.log(\'Open popup with jQuery(...).dialog() on URL '.dol_escape_js(DOL_URL_ROOT.$url).'\');
1741  var $tmpdialog = $(\'#idfordialog'.$name.'\');
1742  $tmpdialog.html(\'<iframe class="iframedialog" id="iframedialog'.$name.'" style="border: 0px;" src="'.DOL_URL_ROOT.$url.'" width="100%" height="98%"></iframe>\');
1743  $tmpdialog.dialog({
1744  autoOpen: false,
1745  modal: true,
1746  height: (window.innerHeight - 150),
1747  width: \'80%\',
1748  title: \''.dol_escape_js($label).'\',
1749  open: function (event, ui) {
1750  console.log("open popup name='.$name.', backtopagejsfields='.$backtopagejsfields.'");
1751  },
1752  close: function (event, ui) {
1753  returnedid = jQuery("#varforreturndialogid'.$name.'").text();
1754  returnedlabel = jQuery("#varforreturndialoglabel'.$name.'").text();
1755  console.log("popup has been closed. returnedid (js var defined into parent page)="+returnedid+" returnedlabel="+returnedlabel);
1756  if (returnedid != "" && returnedid != "div for returned id") {
1757  jQuery("#'.(empty($backtopagejsfieldsid)?"none":$backtopagejsfieldsid).'").val(returnedid);
1758  }
1759  if (returnedlabel != "" && returnedlabel != "div for returned label") {
1760  jQuery("#'.(empty($backtopagejsfieldslabel)?"none":$backtopagejsfieldslabel).'").val(returnedlabel);
1761  }
1762  }
1763  });
1764 
1765  $tmpdialog.dialog(\'open\');
1766  });
1767  });
1768  </script>';
1769  return $out;
1770 }
1771 
1788 function dol_fiche_head($links = array(), $active = '0', $title = '', $notab = 0, $picto = '', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '', $limittoshow = 0, $moretabssuffix = '')
1789 {
1790  print dol_get_fiche_head($links, $active, $title, $notab, $picto, $pictoisfullpath, $morehtmlright, $morecss, $limittoshow, $moretabssuffix);
1791 }
1792 
1808 function dol_get_fiche_head($links = array(), $active = '', $title = '', $notab = 0, $picto = '', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '', $limittoshow = 0, $moretabssuffix = '')
1809 {
1810  global $conf, $langs, $hookmanager;
1811 
1812  // Show title
1813  $showtitle = 1;
1814  if (!empty($conf->dol_optimize_smallscreen)) {
1815  $showtitle = 0;
1816  }
1817 
1818  $out = "\n".'<!-- dol_fiche_head - dol_get_fiche_head -->';
1819 
1820  if ((!empty($title) && $showtitle) || $morehtmlright || !empty($links)) {
1821  $out .= '<div class="tabs'.($picto ? '' : ' nopaddingleft').'" data-role="controlgroup" data-type="horizontal">'."\n";
1822  }
1823 
1824  // Show right part
1825  if ($morehtmlright) {
1826  $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.
1827  }
1828 
1829  // Show title
1830  if (!empty($title) && $showtitle && empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
1831  $limittitle = 30;
1832  $out .= '<a class="tabTitle">';
1833  if ($picto) {
1834  $noprefix = $pictoisfullpath;
1835  if (strpos($picto, 'fontawesome_') !== false) {
1836  $noprefix = 1;
1837  }
1838  $out .= img_picto($title, ($noprefix ? '' : 'object_').$picto, '', $pictoisfullpath, 0, 0, '', 'imgTabTitle').' ';
1839  }
1840  $out .= '<span class="tabTitleText">'.dol_escape_htmltag(dol_trunc($title, $limittitle)).'</span>';
1841  $out .= '</a>';
1842  }
1843 
1844  // Show tabs
1845 
1846  // Define max of key (max may be higher than sizeof because of hole due to module disabling some tabs).
1847  $maxkey = -1;
1848  if (is_array($links) && !empty($links)) {
1849  $keys = array_keys($links);
1850  if (count($keys)) {
1851  $maxkey = max($keys);
1852  }
1853  }
1854 
1855  // Show tabs
1856  // if =0 we don't use the feature
1857  if (empty($limittoshow)) {
1858  $limittoshow = (empty($conf->global->MAIN_MAXTABS_IN_CARD) ? 99 : $conf->global->MAIN_MAXTABS_IN_CARD);
1859  }
1860  if (!empty($conf->dol_optimize_smallscreen)) {
1861  $limittoshow = 2;
1862  }
1863 
1864  $displaytab = 0;
1865  $nbintab = 0;
1866  $popuptab = 0;
1867  $outmore = '';
1868  for ($i = 0; $i <= $maxkey; $i++) {
1869  if ((is_numeric($active) && $i == $active) || (!empty($links[$i][2]) && !is_numeric($active) && $active == $links[$i][2])) {
1870  // If active tab is already present
1871  if ($i >= $limittoshow) {
1872  $limittoshow--;
1873  }
1874  }
1875  }
1876 
1877  for ($i = 0; $i <= $maxkey; $i++) {
1878  if ((is_numeric($active) && $i == $active) || (!empty($links[$i][2]) && !is_numeric($active) && $active == $links[$i][2])) {
1879  $isactive = true;
1880  } else {
1881  $isactive = false;
1882  }
1883 
1884  if ($i < $limittoshow || $isactive) {
1885  // Output entry with a visible tab
1886  $out .= '<div class="inline-block tabsElem'.($isactive ? ' tabsElemActive' : '').((!$isactive && !empty($conf->global->MAIN_HIDE_INACTIVETAB_ON_PRINT)) ? ' hideonprint' : '').'"><!-- id tab = '.(empty($links[$i][2]) ? '' : dol_escape_htmltag($links[$i][2])).' -->';
1887 
1888  if (isset($links[$i][2]) && $links[$i][2] == 'image') {
1889  if (!empty($links[$i][0])) {
1890  $out .= '<a class="tabimage'.($morecss ? ' '.$morecss : '').'" href="'.$links[$i][0].'">'.$links[$i][1].'</a>'."\n";
1891  } else {
1892  $out .= '<span class="tabspan">'.$links[$i][1].'</span>'."\n";
1893  }
1894  } elseif (!empty($links[$i][1])) {
1895  //print "x $i $active ".$links[$i][2]." z";
1896  $out .= '<div class="tab tab'.($isactive?'active':'unactive').'" style="margin: 0 !important">';
1897  if (!empty($links[$i][0])) {
1898  $titletoshow = preg_replace('/<.*$/', '', $links[$i][1]);
1899  $out .= '<a'.(!empty($links[$i][2]) ? ' id="'.$links[$i][2].'"' : '').' class="tab inline-block valignmiddle'.($morecss ? ' '.$morecss : '').'" href="'.$links[$i][0].'" title="'.dol_escape_htmltag($titletoshow).'">';
1900  }
1901  $out .= $links[$i][1];
1902  if (!empty($links[$i][0])) {
1903  $out .= '</a>'."\n";
1904  }
1905  $out .= empty($links[$i][4]) ? '' : $links[$i][4];
1906  $out .= '</div>';
1907  }
1908 
1909  $out .= '</div>';
1910  } else {
1911  // Add entry into the combo popup with the other tabs
1912  if (!$popuptab) {
1913  $popuptab = 1;
1914  $outmore .= '<div class="popuptabset wordwrap">'; // The css used to hide/show popup
1915  }
1916  $outmore .= '<div class="popuptab wordwrap" style="display:inherit;">';
1917  if (isset($links[$i][2]) && $links[$i][2] == 'image') {
1918  if (!empty($links[$i][0])) {
1919  $outmore .= '<a class="tabimage'.($morecss ? ' '.$morecss : '').'" href="'.$links[$i][0].'">'.$links[$i][1].'</a>'."\n";
1920  } else {
1921  $outmore .= '<span class="tabspan">'.$links[$i][1].'</span>'."\n";
1922  }
1923  } elseif (!empty($links[$i][1])) {
1924  $outmore .= '<a'.(!empty($links[$i][2]) ? ' id="'.$links[$i][2].'"' : '').' class="wordwrap inline-block'.($morecss ? ' '.$morecss : '').'" href="'.$links[$i][0].'">';
1925  $outmore .= 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.
1926  $outmore .= '</a>'."\n";
1927  }
1928  $outmore .= '</div>';
1929 
1930  $nbintab++;
1931  }
1932  $displaytab = $i;
1933  }
1934  if ($popuptab) {
1935  $outmore .= '</div>';
1936  }
1937 
1938  if ($popuptab) { // If there is some tabs not shown
1939  $left = ($langs->trans("DIRECTION") == 'rtl' ? 'right' : 'left');
1940  $right = ($langs->trans("DIRECTION") == 'rtl' ? 'left' : 'right');
1941  $widthofpopup = 200;
1942 
1943  $tabsname = $moretabssuffix;
1944  if (empty($tabsname)) {
1945  $tabsname = str_replace("@", "", $picto);
1946  }
1947  $out .= '<div id="moretabs'.$tabsname.'" class="inline-block tabsElem valignmiddle">';
1948  $out .= '<div class="tab"><a href="#" class="tab moretab inline-block tabunactive"><span class="hideonsmartphone">'.$langs->trans("More").'</span>... ('.$nbintab.')</a></div>'; // Do not use "reposition" class in the "More".
1949  $out .= '<div id="moretabsList'.$tabsname.'" style="width: '.$widthofpopup.'px; position: absolute; '.$left.': -999em; text-align: '.$left.'; margin:0px; padding:2px; z-index:10;">';
1950  $out .= $outmore;
1951  $out .= '</div>';
1952  $out .= '<div></div>';
1953  $out .= "</div>\n";
1954 
1955  $out .= "<script>";
1956  $out .= "$('#moretabs".$tabsname."').mouseenter( function() {
1957  var x = this.offsetLeft, y = this.offsetTop;
1958  console.log('mouseenter ".$left." x='+x+' y='+y+' window.innerWidth='+window.innerWidth);
1959  if ((window.innerWidth - x) < ".($widthofpopup + 10).") {
1960  $('#moretabsList".$tabsname."').css('".$right."','8px');
1961  }
1962  $('#moretabsList".$tabsname."').css('".$left."','auto');
1963  });
1964  ";
1965  $out .= "$('#moretabs".$tabsname."').mouseleave( function() { console.log('mouseleave ".$left."'); $('#moretabsList".$tabsname."').css('".$left."','-999em');});";
1966  $out .= "</script>";
1967  }
1968 
1969  if ((!empty($title) && $showtitle) || $morehtmlright || !empty($links)) {
1970  $out .= "</div>\n";
1971  }
1972 
1973  if (!$notab || $notab == -1 || $notab == -2) {
1974  $out .= "\n".'<div class="tabBar'.($notab == -1 ? '' : ($notab == -2 ? ' tabBarNoTop' : ' tabBarWithBottom')).'">'."\n";
1975  }
1976 
1977  $parameters = array('tabname' => $active, 'out' => $out);
1978  $reshook = $hookmanager->executeHooks('printTabsHead', $parameters); // This hook usage is called just before output the head of tabs. Take also a look at "completeTabsHead"
1979  if ($reshook > 0) {
1980  $out = $hookmanager->resPrint;
1981  }
1982 
1983  return $out;
1984 }
1985 
1993 function dol_fiche_end($notab = 0)
1994 {
1995  print dol_get_fiche_end($notab);
1996 }
1997 
2004 function dol_get_fiche_end($notab = 0)
2005 {
2006  if (!$notab || $notab == -1) {
2007  return "\n</div>\n";
2008  } else {
2009  return '';
2010  }
2011 }
2012 
2032 function dol_banner_tab($object, $paramid, $morehtml = '', $shownav = 1, $fieldid = 'rowid', $fieldref = 'ref', $morehtmlref = '', $moreparam = '', $nodbprefix = 0, $morehtmlleft = '', $morehtmlstatus = '', $onlybanner = 0, $morehtmlright = '')
2033 {
2034  global $conf, $form, $user, $langs, $hookmanager, $action;
2035 
2036  $error = 0;
2037 
2038  $maxvisiblephotos = 1;
2039  $showimage = 1;
2040  $entity = (empty($object->entity) ? $conf->entity : $object->entity);
2041  $showbarcode = empty($conf->barcode->enabled) ? 0 : (empty($object->barcode) ? 0 : 1);
2042  if (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && empty($user->rights->barcode->lire_advance)) {
2043  $showbarcode = 0;
2044  }
2045  $modulepart = 'unknown';
2046 
2047  if ($object->element == 'societe' || $object->element == 'contact' || $object->element == 'product' || $object->element == 'ticket') {
2048  $modulepart = $object->element;
2049  } elseif ($object->element == 'member') {
2050  $modulepart = 'memberphoto';
2051  } elseif ($object->element == 'user') {
2052  $modulepart = 'userphoto';
2053  }
2054 
2055  if (class_exists("Imagick")) {
2056  if ($object->element == 'expensereport' || $object->element == 'propal' || $object->element == 'commande' || $object->element == 'facture' || $object->element == 'supplier_proposal') {
2057  $modulepart = $object->element;
2058  } elseif ($object->element == 'fichinter') {
2059  $modulepart = 'ficheinter';
2060  } elseif ($object->element == 'contrat') {
2061  $modulepart = 'contract';
2062  } elseif ($object->element == 'order_supplier') {
2063  $modulepart = 'supplier_order';
2064  } elseif ($object->element == 'invoice_supplier') {
2065  $modulepart = 'supplier_invoice';
2066  }
2067  }
2068 
2069  if ($object->element == 'product') {
2070  $width = 80;
2071  $cssclass = 'photowithmargin photoref';
2072  $showimage = $object->is_photo_available($conf->product->multidir_output[$entity]);
2073  $maxvisiblephotos = (isset($conf->global->PRODUCT_MAX_VISIBLE_PHOTO) ? $conf->global->PRODUCT_MAX_VISIBLE_PHOTO : 5);
2074  if ($conf->browser->layout == 'phone') {
2075  $maxvisiblephotos = 1;
2076  }
2077  if ($showimage) {
2078  $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">'.$object->show_photos('product', $conf->product->multidir_output[$entity], 'small', $maxvisiblephotos, 0, 0, 0, $width, 0).'</div>';
2079  } else {
2080  if (!empty($conf->global->PRODUCT_NODISPLAYIFNOPHOTO)) {
2081  $nophoto = '';
2082  $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
2083  } else { // Show no photo link
2084  $nophoto = '/public/theme/common/nophoto.png';
2085  $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><img class="photo'.$modulepart.($cssclass ? ' '.$cssclass : '').'" alt="No photo"'.($width ? ' style="width: '.$width.'px"' : '').' src="'.DOL_URL_ROOT.$nophoto.'"></div>';
2086  }
2087  }
2088  } elseif ($object->element == 'ticket') {
2089  $width = 80;
2090  $cssclass = 'photoref';
2091  $showimage = $object->is_photo_available($conf->ticket->multidir_output[$entity].'/'.$object->ref);
2092  $maxvisiblephotos = (isset($conf->global->TICKET_MAX_VISIBLE_PHOTO) ? $conf->global->TICKET_MAX_VISIBLE_PHOTO : 2);
2093  if ($conf->browser->layout == 'phone') {
2094  $maxvisiblephotos = 1;
2095  }
2096 
2097  if ($showimage) {
2098  $showphoto = $object->show_photos('ticket', $conf->ticket->multidir_output[$entity], 'small', $maxvisiblephotos, 0, 0, 0, $width, 0);
2099  if ($object->nbphoto > 0) {
2100  $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">'.$showphoto.'</div>';
2101  } else {
2102  $showimage = 0;
2103  }
2104  }
2105  if (!$showimage) {
2106  if (!empty($conf->global->TICKET_NODISPLAYIFNOPHOTO)) {
2107  $nophoto = '';
2108  $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
2109  } else { // Show no photo link
2110  $nophoto = img_picto('No photo', 'object_ticket');
2111  $morehtmlleft .= '<!-- No photo to show -->';
2112  $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref">';
2113  $morehtmlleft .= $nophoto;
2114  $morehtmlleft .= '</div></div>';
2115  }
2116  }
2117  } else {
2118  if ($showimage) {
2119  if ($modulepart != 'unknown') {
2120  $phototoshow = '';
2121  // Check if a preview file is available
2122  if (in_array($modulepart, array('propal', 'commande', 'facture', 'ficheinter', 'contract', 'supplier_order', 'supplier_proposal', 'supplier_invoice', 'expensereport')) && class_exists("Imagick")) {
2123  $objectref = dol_sanitizeFileName($object->ref);
2124  $dir_output = (empty($conf->$modulepart->multidir_output[$entity]) ? $conf->$modulepart->dir_output : $conf->$modulepart->multidir_output[$entity])."/";
2125  if (in_array($modulepart, array('invoice_supplier', 'supplier_invoice'))) {
2126  $subdir = get_exdir($object->id, 2, 0, 1, $object, $modulepart);
2127  $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
2128  } else {
2129  $subdir = get_exdir($object->id, 0, 0, 1, $object, $modulepart);
2130  }
2131  if (empty($subdir)) {
2132  $subdir = 'errorgettingsubdirofobject'; // Protection to avoid to return empty path
2133  }
2134 
2135  $filepath = $dir_output.$subdir."/";
2136 
2137  $filepdf = $filepath.$objectref.".pdf";
2138  $relativepath = $subdir.'/'.$objectref.'.pdf';
2139 
2140  // Define path to preview pdf file (preview precompiled "file.ext" are "file.ext_preview.png")
2141  $fileimage = $filepdf.'_preview.png';
2142  $relativepathimage = $relativepath.'_preview.png';
2143 
2144  $pdfexists = file_exists($filepdf);
2145 
2146  // If PDF file exists
2147  if ($pdfexists) {
2148  // Conversion du PDF en image png si fichier png non existant
2149  if (!file_exists($fileimage) || (filemtime($fileimage) < filemtime($filepdf))) {
2150  if (empty($conf->global->MAIN_DISABLE_PDF_THUMBS)) { // If you experience trouble with pdf thumb generation and imagick, you can disable here.
2151  include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2152  $ret = dol_convert_file($filepdf, 'png', $fileimage, '0'); // Convert first page of PDF into a file _preview.png
2153  if ($ret < 0) {
2154  $error++;
2155  }
2156  }
2157  }
2158  }
2159 
2160  if ($pdfexists && !$error) {
2161  $heightforphotref = 80;
2162  if (!empty($conf->dol_optimize_smallscreen)) {
2163  $heightforphotref = 60;
2164  }
2165  // If the preview file is found
2166  if (file_exists($fileimage)) {
2167  $phototoshow = '<div class="photoref">';
2168  $phototoshow .= '<img height="'.$heightforphotref.'" class="photo photowithmargin photowithborder" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart=apercu'.$modulepart.'&amp;file='.urlencode($relativepathimage).'">';
2169  $phototoshow .= '</div>';
2170  }
2171  }
2172  } elseif (!$phototoshow) { // example if modulepart = 'societe' or 'photo'
2173  $phototoshow .= $form->showphoto($modulepart, $object, 0, 0, 0, 'photowithmargin photoref', 'small', 1, 0, $maxvisiblephotos);
2174  }
2175 
2176  if ($phototoshow) {
2177  $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">';
2178  $morehtmlleft .= $phototoshow;
2179  $morehtmlleft .= '</div>';
2180  }
2181  }
2182 
2183  if (empty($phototoshow)) { // Show No photo link (picto of object)
2184  if ($object->element == 'action') {
2185  $width = 80;
2186  $cssclass = 'photorefcenter';
2187  $nophoto = img_picto('No photo', 'title_agenda');
2188  } else {
2189  $width = 14;
2190  $cssclass = 'photorefcenter';
2191  $picto = $object->picto;
2192  $prefix = 'object_';
2193  if ($object->element == 'project' && !$object->public) {
2194  $picto = 'project'; // instead of projectpub
2195  }
2196  if (strpos($picto, 'fontawesome_') !== false) {
2197  $prefix = '';
2198  }
2199  $nophoto = img_picto('No photo', $prefix.$picto);
2200  }
2201  $morehtmlleft .= '<!-- No photo to show -->';
2202  $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref">';
2203  $morehtmlleft .= $nophoto;
2204  $morehtmlleft .= '</div></div>';
2205  }
2206  }
2207  }
2208 
2209  if ($showbarcode) {
2210  $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">'.$form->showbarcode($object, 100, 'photoref valignmiddle').'</div>';
2211  }
2212 
2213  if ($object->element == 'societe') {
2214  if (!empty($conf->use_javascript_ajax) && $user->rights->societe->creer && !empty($conf->global->MAIN_DIRECT_STATUS_UPDATE)) {
2215  $morehtmlstatus .= ajax_object_onoff($object, 'status', 'status', 'InActivity', 'ActivityCeased');
2216  } else {
2217  $morehtmlstatus .= $object->getLibStatut(6);
2218  }
2219  } elseif ($object->element == 'product') {
2220  //$morehtmlstatus.=$langs->trans("Status").' ('.$langs->trans("Sell").') ';
2221  if (!empty($conf->use_javascript_ajax) && $user->rights->produit->creer && !empty($conf->global->MAIN_DIRECT_STATUS_UPDATE)) {
2222  $morehtmlstatus .= ajax_object_onoff($object, 'status', 'tosell', 'ProductStatusOnSell', 'ProductStatusNotOnSell');
2223  } else {
2224  $morehtmlstatus .= '<span class="statusrefsell">'.$object->getLibStatut(6, 0).'</span>';
2225  }
2226  $morehtmlstatus .= ' &nbsp; ';
2227  //$morehtmlstatus.=$langs->trans("Status").' ('.$langs->trans("Buy").') ';
2228  if (!empty($conf->use_javascript_ajax) && $user->rights->produit->creer && !empty($conf->global->MAIN_DIRECT_STATUS_UPDATE)) {
2229  $morehtmlstatus .= ajax_object_onoff($object, 'status_buy', 'tobuy', 'ProductStatusOnBuy', 'ProductStatusNotOnBuy');
2230  } else {
2231  $morehtmlstatus .= '<span class="statusrefbuy">'.$object->getLibStatut(6, 1).'</span>';
2232  }
2233  } elseif (in_array($object->element, array('facture', 'invoice', 'invoice_supplier', 'chargesociales', 'loan', 'tva', 'salary'))) {
2234  $tmptxt = $object->getLibStatut(6, $object->totalpaid);
2235  if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) {
2236  $tmptxt = $object->getLibStatut(5, $object->totalpaid);
2237  }
2238  $morehtmlstatus .= $tmptxt;
2239  } elseif ($object->element == 'contrat' || $object->element == 'contract') {
2240  if ($object->statut == 0) {
2241  $morehtmlstatus .= $object->getLibStatut(5);
2242  } else {
2243  $morehtmlstatus .= $object->getLibStatut(4);
2244  }
2245  } elseif ($object->element == 'facturerec') {
2246  if ($object->frequency == 0) {
2247  $morehtmlstatus .= $object->getLibStatut(2);
2248  } else {
2249  $morehtmlstatus .= $object->getLibStatut(5);
2250  }
2251  } elseif ($object->element == 'project_task') {
2252  $object->fk_statut = 1;
2253  if ($object->progress > 0) {
2254  $object->fk_statut = 2;
2255  }
2256  if ($object->progress >= 100) {
2257  $object->fk_statut = 3;
2258  }
2259  $tmptxt = $object->getLibStatut(5);
2260  $morehtmlstatus .= $tmptxt; // No status on task
2261  } else { // Generic case
2262  $tmptxt = $object->getLibStatut(6);
2263  if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) {
2264  $tmptxt = $object->getLibStatut(5);
2265  }
2266  $morehtmlstatus .= $tmptxt;
2267  }
2268 
2269  // Add if object was dispatched "into accountancy"
2270  if (!empty($conf->accounting->enabled) && in_array($object->element, array('bank', 'paiementcharge', 'facture', 'invoice', 'invoice_supplier', 'expensereport', 'payment_various'))) {
2271  // Note: For 'chargesociales', 'salaries'... this is the payments that are dispatched (so element = 'bank')
2272  if (method_exists($object, 'getVentilExportCompta')) {
2273  $accounted = $object->getVentilExportCompta();
2274  $langs->load("accountancy");
2275  $morehtmlstatus .= '</div><div class="statusref statusrefbis"><span class="opacitymedium">'.($accounted > 0 ? $langs->trans("Accounted") : $langs->trans("NotYetAccounted")).'</span>';
2276  }
2277  }
2278 
2279  // Add alias for thirdparty
2280  if (!empty($object->name_alias)) {
2281  $morehtmlref .= '<div class="refidno">'.$object->name_alias.'</div>';
2282  }
2283 
2284  // Add label
2285  if (in_array($object->element, array('product', 'bank_account', 'project_task'))) {
2286  if (!empty($object->label)) {
2287  $morehtmlref .= '<div class="refidno">'.$object->label.'</div>';
2288  }
2289  }
2290 
2291  // Show address and email
2292  if (method_exists($object, 'getBannerAddress') && !in_array($object->element, array('product', 'bookmark', 'ecm_directories', 'ecm_files'))) {
2293  $moreaddress = $object->getBannerAddress('refaddress', $object);
2294  if ($moreaddress) {
2295  $morehtmlref .= '<div class="refidno">';
2296  $morehtmlref .= $moreaddress;
2297  $morehtmlref .= '</div>';
2298  }
2299  }
2300  if (!empty($conf->global->MAIN_SHOW_TECHNICAL_ID) && ($conf->global->MAIN_SHOW_TECHNICAL_ID == '1' || preg_match('/'.preg_quote($object->element, '/').'/i', $conf->global->MAIN_SHOW_TECHNICAL_ID)) && !empty($object->id)) {
2301  $morehtmlref .= '<div style="clear: both;"></div>';
2302  $morehtmlref .= '<div class="refidno">';
2303  $morehtmlref .= $langs->trans("TechnicalID").': '.$object->id;
2304  $morehtmlref .= '</div>';
2305  }
2306 
2307  $parameters=array('morehtmlref'=>$morehtmlref);
2308  $reshook = $hookmanager->executeHooks('formDolBanner', $parameters, $object, $action);
2309  if ($reshook < 0) {
2310  setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
2311  } elseif (empty($reshook)) {
2312  $morehtmlref .= $hookmanager->resPrint;
2313  } elseif ($reshook > 0) {
2314  $morehtmlref = $hookmanager->resPrint;
2315  }
2316 
2317 
2318  print '<div class="'.($onlybanner ? 'arearefnobottom ' : 'arearef ').'heightref valignmiddle centpercent">';
2319  print $form->showrefnav($object, $paramid, $morehtml, $shownav, $fieldid, $fieldref, $morehtmlref, $moreparam, $nodbprefix, $morehtmlleft, $morehtmlstatus, $morehtmlright);
2320  print '</div>';
2321  print '<div class="underrefbanner clearboth"></div>';
2322 }
2323 
2333 function fieldLabel($langkey, $fieldkey, $fieldrequired = 0)
2334 {
2335  global $langs;
2336  $ret = '';
2337  if ($fieldrequired) {
2338  $ret .= '<span class="fieldrequired">';
2339  }
2340  $ret .= '<label for="'.$fieldkey.'">';
2341  $ret .= $langs->trans($langkey);
2342  $ret .= '</label>';
2343  if ($fieldrequired) {
2344  $ret .= '</span>';
2345  }
2346  return $ret;
2347 }
2348 
2356 function dol_bc($var, $moreclass = '')
2357 {
2358  global $bc;
2359  $ret = ' '.$bc[$var];
2360  if ($moreclass) {
2361  $ret = preg_replace('/class=\"/', 'class="'.$moreclass.' ', $ret);
2362  }
2363  return $ret;
2364 }
2365 
2379 function dol_format_address($object, $withcountry = 0, $sep = "\n", $outputlangs = '', $mode = 0, $extralangcode = '')
2380 {
2381  global $conf, $langs, $hookmanager;
2382 
2383  $ret = '';
2384  $countriesusingstate = array('AU', 'CA', 'US', 'IN', 'GB', 'ES', 'UK', 'TR', 'CN'); // See also MAIN_FORCE_STATE_INTO_ADDRESS
2385 
2386  // See format of addresses on https://en.wikipedia.org/wiki/Address
2387  // Address
2388  if (empty($mode)) {
2389  $ret .= ($extralangcode ? $object->array_languages['address'][$extralangcode] : (empty($object->address) ? '' : $object->address));
2390  }
2391  // Zip/Town/State
2392  if (isset($object->country_code) && in_array($object->country_code, array('AU', 'CA', 'US', 'CN')) || !empty($conf->global->MAIN_FORCE_STATE_INTO_ADDRESS)) {
2393  // US: title firstname name \n address lines \n town, state, zip \n country
2394  $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2395  $ret .= (($ret && $town) ? $sep : '').$town;
2396 
2397  if (!empty($object->state)) {
2398  $ret .= ($ret ? ($town ? ", " : $sep) : '').$object->state;
2399  }
2400  if (!empty($object->zip)) {
2401  $ret .= ($ret ? (($town || $object->state) ? ", " : $sep) : '').$object->zip;
2402  }
2403  } elseif (isset($object->country_code) && in_array($object->country_code, array('GB', 'UK'))) {
2404  // UK: title firstname name \n address lines \n town state \n zip \n country
2405  $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2406  $ret .= ($ret ? $sep : '').$town;
2407  if (!empty($object->state)) {
2408  $ret .= ($ret ? ", " : '').$object->state;
2409  }
2410  if (!empty($object->zip)) {
2411  $ret .= ($ret ? $sep : '').$object->zip;
2412  }
2413  } elseif (isset($object->country_code) && in_array($object->country_code, array('ES', 'TR'))) {
2414  // ES: title firstname name \n address lines \n zip town \n state \n country
2415  $ret .= ($ret ? $sep : '').$object->zip;
2416  $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2417  $ret .= ($town ? (($object->zip ? ' ' : '').$town) : '');
2418  if (!empty($object->state)) {
2419  $ret .= "\n".$object->state;
2420  }
2421  } elseif (isset($object->country_code) && in_array($object->country_code, array('JP'))) {
2422  // JP: In romaji, title firstname name\n address lines \n [state,] town zip \n country
2423  // See https://www.sljfaq.org/afaq/addresses.html
2424  $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2425  $ret .= ($ret ? $sep : '').($object->state ? $object->state.', ' : '').$town.($object->zip ? ' ' : '').$object->zip;
2426  } elseif (isset($object->country_code) && in_array($object->country_code, array('IT'))) {
2427  // IT: title firstname name\n address lines \n zip town state_code \n country
2428  $ret .= ($ret ? $sep : '').$object->zip;
2429  $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2430  $ret .= ($town ? (($object->zip ? ' ' : '').$town) : '');
2431  $ret .= (empty($object->state_code) ? '' : (' '.$object->state_code));
2432  } else {
2433  // Other: title firstname name \n address lines \n zip town[, state] \n country
2434  $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2435  $ret .= !empty($object->zip) ? (($ret ? $sep : '').$object->zip) : '';
2436  $ret .= ($town ? (($object->zip ? ' ' : ($ret ? $sep : '')).$town) : '');
2437  if (!empty($object->state) && in_array($object->country_code, $countriesusingstate)) {
2438  $ret .= ($ret ? ", " : '').$object->state;
2439  }
2440  }
2441  if (!is_object($outputlangs)) {
2442  $outputlangs = $langs;
2443  }
2444  if ($withcountry) {
2445  $langs->load("dict");
2446  $ret .= (empty($object->country_code) ? '' : ($ret ? $sep : '').$outputlangs->convToOutputCharset($outputlangs->transnoentitiesnoconv("Country".$object->country_code)));
2447  }
2448  if ($hookmanager) {
2449  $parameters = array('withcountry' => $withcountry, 'sep' => $sep, 'outputlangs' => $outputlangs,'mode' => $mode, 'extralangcode' => $extralangcode);
2450  $reshook = $hookmanager->executeHooks('formatAddress', $parameters, $object);
2451  if ($reshook > 0) {
2452  $ret = '';
2453  }
2454  $ret .= $hookmanager->resPrint;
2455  }
2456 
2457  return $ret;
2458 }
2459 
2460 
2461 
2470 function dol_strftime($fmt, $ts = false, $is_gmt = false)
2471 {
2472  if ((abs($ts) <= 0x7FFFFFFF)) { // check if number in 32-bit signed range
2473  return ($is_gmt) ? @gmstrftime($fmt, $ts) : @strftime($fmt, $ts);
2474  } else {
2475  return 'Error date into a not supported range';
2476  }
2477 }
2478 
2500 function dol_print_date($time, $format = '', $tzoutput = 'auto', $outputlangs = '', $encodetooutput = false)
2501 {
2502  global $conf, $langs;
2503 
2504  // If date undefined or "", we return ""
2505  if (dol_strlen($time) == 0) {
2506  return ''; // $time=0 allowed (it means 01/01/1970 00:00:00)
2507  }
2508 
2509  if ($tzoutput === 'auto') {
2510  $tzoutput = (empty($conf) ? 'tzserver' : (isset($conf->tzuserinputkey) ? $conf->tzuserinputkey : 'tzserver'));
2511  }
2512 
2513  // Clean parameters
2514  $to_gmt = false;
2515  $offsettz = $offsetdst = 0;
2516  if ($tzoutput) {
2517  $to_gmt = true; // For backward compatibility
2518  if (is_string($tzoutput)) {
2519  if ($tzoutput == 'tzserver') {
2520  $to_gmt = false;
2521  $offsettzstring = @date_default_timezone_get(); // Example 'Europe/Berlin' or 'Indian/Reunion'
2522  $offsettz = 0; // Timezone offset with server timezone, so 0
2523  $offsetdst = 0; // Dst offset with server timezone, so 0
2524  } elseif ($tzoutput == 'tzuser' || $tzoutput == 'tzuserrel') {
2525  $to_gmt = true;
2526  $offsettzstring = (empty($_SESSION['dol_tz_string']) ? 'UTC' : $_SESSION['dol_tz_string']); // Example 'Europe/Berlin' or 'Indian/Reunion'
2527 
2528  if (class_exists('DateTimeZone')) {
2529  $user_date_tz = new DateTimeZone($offsettzstring);
2530  $user_dt = new DateTime();
2531  $user_dt->setTimezone($user_date_tz);
2532  $user_dt->setTimestamp($tzoutput == 'tzuser' ? dol_now() : (int) $time);
2533  $offsettz = $user_dt->getOffset();
2534  } else { // old method (The 'tzuser' was processed like the 'tzuserrel')
2535  $offsettz = (empty($_SESSION['dol_tz']) ? 0 : $_SESSION['dol_tz']) * 60 * 60; // Will not be used anymore
2536  $offsetdst = (empty($_SESSION['dol_dst']) ? 0 : $_SESSION['dol_dst']) * 60 * 60; // Will not be used anymore
2537  }
2538  }
2539  }
2540  }
2541  if (!is_object($outputlangs)) {
2542  $outputlangs = $langs;
2543  }
2544  if (!$format) {
2545  $format = 'daytextshort';
2546  }
2547 
2548  // Do we have to reduce the length of date (year on 2 chars) to save space.
2549  // Note: dayinputnoreduce is same than day but no reduction of year length will be done
2550  $reduceformat = (!empty($conf->dol_optimize_smallscreen) && in_array($format, array('day', 'dayhour'))) ? 1 : 0; // Test on original $format param.
2551  $format = preg_replace('/inputnoreduce/', '', $format); // so format 'dayinputnoreduce' is processed like day
2552  $formatwithoutreduce = preg_replace('/reduceformat/', '', $format);
2553  if ($formatwithoutreduce != $format) {
2554  $format = $formatwithoutreduce;
2555  $reduceformat = 1;
2556  } // so format 'dayreduceformat' is processed like day
2557 
2558  // Change predefined format into computer format. If found translation in lang file we use it, otherwise we use default.
2559  // TODO Add format daysmallyear and dayhoursmallyear
2560  if ($format == 'day') {
2561  $format = ($outputlangs->trans("FormatDateShort") != "FormatDateShort" ? $outputlangs->trans("FormatDateShort") : $conf->format_date_short);
2562  } elseif ($format == 'hour') {
2563  $format = ($outputlangs->trans("FormatHourShort") != "FormatHourShort" ? $outputlangs->trans("FormatHourShort") : $conf->format_hour_short);
2564  } elseif ($format == 'hourduration') {
2565  $format = ($outputlangs->trans("FormatHourShortDuration") != "FormatHourShortDuration" ? $outputlangs->trans("FormatHourShortDuration") : $conf->format_hour_short_duration);
2566  } elseif ($format == 'daytext') {
2567  $format = ($outputlangs->trans("FormatDateText") != "FormatDateText" ? $outputlangs->trans("FormatDateText") : $conf->format_date_text);
2568  } elseif ($format == 'daytextshort') {
2569  $format = ($outputlangs->trans("FormatDateTextShort") != "FormatDateTextShort" ? $outputlangs->trans("FormatDateTextShort") : $conf->format_date_text_short);
2570  } elseif ($format == 'dayhour') {
2571  $format = ($outputlangs->trans("FormatDateHourShort") != "FormatDateHourShort" ? $outputlangs->trans("FormatDateHourShort") : $conf->format_date_hour_short);
2572  } elseif ($format == 'dayhoursec') {
2573  $format = ($outputlangs->trans("FormatDateHourSecShort") != "FormatDateHourSecShort" ? $outputlangs->trans("FormatDateHourSecShort") : $conf->format_date_hour_sec_short);
2574  } elseif ($format == 'dayhourtext') {
2575  $format = ($outputlangs->trans("FormatDateHourText") != "FormatDateHourText" ? $outputlangs->trans("FormatDateHourText") : $conf->format_date_hour_text);
2576  } elseif ($format == 'dayhourtextshort') {
2577  $format = ($outputlangs->trans("FormatDateHourTextShort") != "FormatDateHourTextShort" ? $outputlangs->trans("FormatDateHourTextShort") : $conf->format_date_hour_text_short);
2578  } elseif ($format == 'dayhourlog') {
2579  // Format not sensitive to language
2580  $format = '%Y%m%d%H%M%S';
2581  } elseif ($format == 'dayhourlogsmall') {
2582  // Format not sensitive to language
2583  $format = '%Y%m%d%H%M';
2584  } elseif ($format == 'dayhourldap') {
2585  $format = '%Y%m%d%H%M%SZ';
2586  } elseif ($format == 'dayhourxcard') {
2587  $format = '%Y%m%dT%H%M%SZ';
2588  } elseif ($format == 'dayxcard') {
2589  $format = '%Y%m%d';
2590  } elseif ($format == 'dayrfc') {
2591  $format = '%Y-%m-%d'; // DATE_RFC3339
2592  } elseif ($format == 'dayhourrfc') {
2593  $format = '%Y-%m-%dT%H:%M:%SZ'; // DATETIME RFC3339
2594  } elseif ($format == 'standard') {
2595  $format = '%Y-%m-%d %H:%M:%S';
2596  }
2597 
2598  if ($reduceformat) {
2599  $format = str_replace('%Y', '%y', $format);
2600  $format = str_replace('yyyy', 'yy', $format);
2601  }
2602 
2603  // Clean format
2604  if (preg_match('/%b/i', $format)) { // There is some text to translate
2605  // We inhibate translation to text made by strftime functions. We will use trans instead later.
2606  $format = str_replace('%b', '__b__', $format);
2607  $format = str_replace('%B', '__B__', $format);
2608  }
2609  if (preg_match('/%a/i', $format)) { // There is some text to translate
2610  // We inhibate translation to text made by strftime functions. We will use trans instead later.
2611  $format = str_replace('%a', '__a__', $format);
2612  $format = str_replace('%A', '__A__', $format);
2613  }
2614 
2615 
2616  // Analyze date
2617  $reg = array();
2618  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', $time, $reg)) { // Deprecated. Ex: 1970-01-01, 1970-01-01 01:00:00, 19700101010000
2619  dol_print_error('', "Functions.lib::dol_print_date function called with a bad value from page ".$_SERVER["PHP_SELF"]);
2620  return '';
2621  } elseif (preg_match('/^([0-9]+)\-([0-9]+)\-([0-9]+) ?([0-9]+)?:?([0-9]+)?:?([0-9]+)?/i', $time, $reg)) { // Still available to solve problems in extrafields of type date
2622  // This part of code should not be used anymore.
2623  dol_syslog("Functions.lib::dol_print_date function called with a bad value from page ".$_SERVER["PHP_SELF"], LOG_WARNING);
2624  //if (function_exists('debug_print_backtrace')) debug_print_backtrace();
2625  // Date has format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'
2626  $syear = (!empty($reg[1]) ? $reg[1] : '');
2627  $smonth = (!empty($reg[2]) ? $reg[2] : '');
2628  $sday = (!empty($reg[3]) ? $reg[3] : '');
2629  $shour = (!empty($reg[4]) ? $reg[4] : '');
2630  $smin = (!empty($reg[5]) ? $reg[5] : '');
2631  $ssec = (!empty($reg[6]) ? $reg[6] : '');
2632 
2633  $time = dol_mktime($shour, $smin, $ssec, $smonth, $sday, $syear, true);
2634  $ret = adodb_strftime($format, $time + $offsettz + $offsetdst, $to_gmt);
2635  } else {
2636  // Date is a timestamps
2637  if ($time < 100000000000) { // Protection against bad date values
2638  $timetouse = $time + $offsettz + $offsetdst; // TODO Replace this with function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
2639 
2640  $ret = adodb_strftime($format, $timetouse, $to_gmt); // If to_gmt = false then adodb_strftime use TZ of server
2641  } else {
2642  $ret = 'Bad value '.$time.' for date';
2643  }
2644  }
2645 
2646  if (preg_match('/__b__/i', $format)) {
2647  $timetouse = $time + $offsettz + $offsetdst; // TODO Replace this with function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
2648 
2649  // Here ret is string in PHP setup language (strftime was used). Now we convert to $outputlangs.
2650  $month = adodb_strftime('%m', $timetouse, $to_gmt); // If to_gmt = false then adodb_strftime use TZ of server
2651  $month = sprintf("%02d", $month); // $month may be return with format '06' on some installation and '6' on other, so we force it to '06'.
2652  if ($encodetooutput) {
2653  $monthtext = $outputlangs->transnoentities('Month'.$month);
2654  $monthtextshort = $outputlangs->transnoentities('MonthShort'.$month);
2655  } else {
2656  $monthtext = $outputlangs->transnoentitiesnoconv('Month'.$month);
2657  $monthtextshort = $outputlangs->transnoentitiesnoconv('MonthShort'.$month);
2658  }
2659  //print 'monthtext='.$monthtext.' monthtextshort='.$monthtextshort;
2660  $ret = str_replace('__b__', $monthtextshort, $ret);
2661  $ret = str_replace('__B__', $monthtext, $ret);
2662  //print 'x'.$outputlangs->charset_output.'-'.$ret.'x';
2663  //return $ret;
2664  }
2665  if (preg_match('/__a__/i', $format)) {
2666  //print "time=$time offsettz=$offsettz offsetdst=$offsetdst offsettzstring=$offsettzstring";
2667  $timetouse = $time + $offsettz + $offsetdst; // TODO Replace this with function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
2668 
2669  $w = adodb_strftime('%w', $timetouse, $to_gmt); // If to_gmt = false then adodb_strftime use TZ of server
2670  $dayweek = $outputlangs->transnoentitiesnoconv('Day'.$w);
2671  $ret = str_replace('__A__', $dayweek, $ret);
2672  $ret = str_replace('__a__', dol_substr($dayweek, 0, 3), $ret);
2673  }
2674 
2675  return $ret;
2676 }
2677 
2678 
2699 function dol_getdate($timestamp, $fast = false, $forcetimezone = '')
2700 {
2701  //$datetimeobj = new DateTime('@'.$timestamp);
2702  $datetimeobj = new DateTime();
2703  $datetimeobj->setTimestamp($timestamp); // Use local PHP server timezone
2704  if ($forcetimezone) {
2705  $datetimeobj->setTimezone(new DateTimeZone($forcetimezone == 'gmt' ? 'UTC' : $forcetimezone)); // (add timezone relative to the date entered)
2706  }
2707  $arrayinfo = array(
2708  'year'=>((int) date_format($datetimeobj, 'Y')),
2709  'mon'=>((int) date_format($datetimeobj, 'm')),
2710  'mday'=>((int) date_format($datetimeobj, 'd')),
2711  'wday'=>((int) date_format($datetimeobj, 'w')),
2712  'yday'=>((int) date_format($datetimeobj, 'z')),
2713  'hours'=>((int) date_format($datetimeobj, 'H')),
2714  'minutes'=>((int) date_format($datetimeobj, 'i')),
2715  'seconds'=>((int) date_format($datetimeobj, 's')),
2716  '0'=>$timestamp
2717  );
2718 
2719  return $arrayinfo;
2720 }
2721 
2743 function dol_mktime($hour, $minute, $second, $month, $day, $year, $gm = 'auto', $check = 1)
2744 {
2745  global $conf;
2746  //print "- ".$hour.",".$minute.",".$second.",".$month.",".$day.",".$year.",".$_SERVER["WINDIR"]." -";
2747 
2748  if ($gm === 'auto') {
2749  $gm = (empty($conf) ? 'tzserver' : $conf->tzuserinputkey);
2750  }
2751  //print 'gm:'.$gm.' gm === auto:'.($gm === 'auto').'<br>';exit;
2752 
2753  // Clean parameters
2754  if ($hour == -1 || empty($hour)) {
2755  $hour = 0;
2756  }
2757  if ($minute == -1 || empty($minute)) {
2758  $minute = 0;
2759  }
2760  if ($second == -1 || empty($second)) {
2761  $second = 0;
2762  }
2763 
2764  // Check parameters
2765  if ($check) {
2766  if (!$month || !$day) {
2767  return '';
2768  }
2769  if ($day > 31) {
2770  return '';
2771  }
2772  if ($month > 12) {
2773  return '';
2774  }
2775  if ($hour < 0 || $hour > 24) {
2776  return '';
2777  }
2778  if ($minute < 0 || $minute > 60) {
2779  return '';
2780  }
2781  if ($second < 0 || $second > 60) {
2782  return '';
2783  }
2784  }
2785 
2786  if (empty($gm) || ($gm === 'server' || $gm === 'tzserver')) {
2787  $default_timezone = @date_default_timezone_get(); // Example 'Europe/Berlin'
2788  $localtz = new DateTimeZone($default_timezone);
2789  } elseif ($gm === 'user' || $gm === 'tzuser' || $gm === 'tzuserrel') {
2790  // We use dol_tz_string first because it is more reliable.
2791  $default_timezone = (empty($_SESSION["dol_tz_string"]) ? @date_default_timezone_get() : $_SESSION["dol_tz_string"]); // Example 'Europe/Berlin'
2792  try {
2793  $localtz = new DateTimeZone($default_timezone);
2794  } catch (Exception $e) {
2795  dol_syslog("Warning dol_tz_string contains an invalid value ".$_SESSION["dol_tz_string"], LOG_WARNING);
2796  $default_timezone = @date_default_timezone_get();
2797  }
2798  } elseif (strrpos($gm, "tz,") !== false) {
2799  $timezone = str_replace("tz,", "", $gm); // Example 'tz,Europe/Berlin'
2800  try {
2801  $localtz = new DateTimeZone($timezone);
2802  } catch (Exception $e) {
2803  dol_syslog("Warning passed timezone contains an invalid value ".$timezone, LOG_WARNING);
2804  }
2805  }
2806 
2807  if (empty($localtz)) {
2808  $localtz = new DateTimeZone('UTC');
2809  }
2810  //var_dump($localtz);
2811  //var_dump($year.'-'.$month.'-'.$day.'-'.$hour.'-'.$minute);
2812  $dt = new DateTime('now', $localtz);
2813  $dt->setDate((int) $year, (int) $month, (int) $day);
2814  $dt->setTime((int) $hour, (int) $minute, (int) $second);
2815  $date = $dt->getTimestamp(); // should include daylight saving time
2816  //var_dump($date);
2817  return $date;
2818 }
2819 
2820 
2831 function dol_now($mode = 'auto')
2832 {
2833  $ret = 0;
2834 
2835  if ($mode === 'auto') {
2836  $mode = 'gmt';
2837  }
2838 
2839  if ($mode == 'gmt') {
2840  $ret = time(); // Time for now at greenwich.
2841  } elseif ($mode == 'tzserver') { // Time for now with PHP server timezone added
2842  require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
2843  $tzsecond = getServerTimeZoneInt('now'); // Contains tz+dayling saving time
2844  $ret = (int) (dol_now('gmt') + ($tzsecond * 3600));
2845  //} elseif ($mode == 'tzref') {// Time for now with parent company timezone is added
2846  // require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
2847  // $tzsecond=getParentCompanyTimeZoneInt(); // Contains tz+dayling saving time
2848  // $ret=dol_now('gmt')+($tzsecond*3600);
2849  //}
2850  } elseif ($mode == 'tzuser' || $mode == 'tzuserrel') {
2851  // Time for now with user timezone added
2852  //print 'time: '.time();
2853  $offsettz = (empty($_SESSION['dol_tz']) ? 0 : $_SESSION['dol_tz']) * 60 * 60;
2854  $offsetdst = (empty($_SESSION['dol_dst']) ? 0 : $_SESSION['dol_dst']) * 60 * 60;
2855  $ret = (int) (dol_now('gmt') + ($offsettz + $offsetdst));
2856  }
2857 
2858  return $ret;
2859 }
2860 
2861 
2870 function dol_print_size($size, $shortvalue = 0, $shortunit = 0)
2871 {
2872  global $conf, $langs;
2873  $level = 1024;
2874 
2875  if (!empty($conf->dol_optimize_smallscreen)) {
2876  $shortunit = 1;
2877  }
2878 
2879  // Set value text
2880  if (empty($shortvalue) || $size < ($level * 10)) {
2881  $ret = $size;
2882  $textunitshort = $langs->trans("b");
2883  $textunitlong = $langs->trans("Bytes");
2884  } else {
2885  $ret = round($size / $level, 0);
2886  $textunitshort = $langs->trans("Kb");
2887  $textunitlong = $langs->trans("KiloBytes");
2888  }
2889  // Use long or short text unit
2890  if (empty($shortunit)) {
2891  $ret .= ' '.$textunitlong;
2892  } else {
2893  $ret .= ' '.$textunitshort;
2894  }
2895 
2896  return $ret;
2897 }
2898 
2908 function dol_print_url($url, $target = '_blank', $max = 32, $withpicto = 0)
2909 {
2910  global $langs;
2911 
2912  if (empty($url)) {
2913  return '';
2914  }
2915 
2916  $link = '<a href="';
2917  if (!preg_match('/^http/i', $url)) {
2918  $link .= 'http://';
2919  }
2920  $link .= $url;
2921  $link .= '"';
2922  if ($target) {
2923  $link .= ' target="'.$target.'"';
2924  }
2925  $link .= '>';
2926  if (!preg_match('/^http/i', $url)) {
2927  $link .= 'http://';
2928  }
2929  $link .= dol_trunc($url, $max);
2930  $link .= '</a>';
2931  return '<div class="nospan float" style="margin-right: 10px">'.($withpicto ?img_picto($langs->trans("Url"), 'globe').' ' : '').$link.'</div>';
2932 }
2933 
2946 function dol_print_email($email, $cid = 0, $socid = 0, $addlink = 0, $max = 64, $showinvalid = 1, $withpicto = 0)
2947 {
2948  global $conf, $user, $langs, $hookmanager;
2949 
2950  $newemail = dol_escape_htmltag($email);
2951 
2952  if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER) && $withpicto) {
2953  $withpicto = 0;
2954  }
2955 
2956  if (empty($email)) {
2957  return '&nbsp;';
2958  }
2959 
2960  if (!empty($addlink)) {
2961  $newemail = '<a style="text-overflow: ellipsis;" href="';
2962  if (!preg_match('/^mailto:/i', $email)) {
2963  $newemail .= 'mailto:';
2964  }
2965  $newemail .= $email;
2966  $newemail .= '">';
2967  $newemail .= dol_trunc($email, $max);
2968  $newemail .= '</a>';
2969  if ($showinvalid && !isValidEmail($email)) {
2970  $langs->load("errors");
2971  $newemail .= img_warning($langs->trans("ErrorBadEMail", $email));
2972  }
2973 
2974  if (($cid || $socid) && !empty($conf->agenda->enabled) && $user->rights->agenda->myactions->create) {
2975  $type = 'AC_EMAIL';
2976  $link = '';
2977  if (!empty($conf->global->AGENDA_ADDACTIONFOREMAIL)) {
2978  $link = '<a href="'.DOL_URL_ROOT.'/comm/action/card.php?action=create&amp;backtopage=1&amp;actioncode='.$type.'&amp;contactid='.$cid.'&amp;socid='.$socid.'">'.img_object($langs->trans("AddAction"), "calendar").'</a>';
2979  }
2980  if ($link) {
2981  $newemail = '<div>'.$newemail.' '.$link.'</div>';
2982  }
2983  }
2984  } else {
2985  if ($showinvalid && !isValidEmail($email)) {
2986  $langs->load("errors");
2987  $newemail .= img_warning($langs->trans("ErrorBadEMail", $email));
2988  }
2989  }
2990 
2991  //$rep = '<div class="nospan" style="margin-right: 10px">';
2992  $rep = ($withpicto ? img_picto($langs->trans("EMail").' : '.$email, (is_numeric($withpicto) ? 'email' : $withpicto)).' ' : '').$newemail;
2993  //$rep .= '</div>';
2994  if ($hookmanager) {
2995  $parameters = array('cid' => $cid, 'socid' => $socid, 'addlink' => $addlink, 'picto' => $withpicto);
2996 
2997  $reshook = $hookmanager->executeHooks('printEmail', $parameters, $email);
2998  if ($reshook > 0) {
2999  $rep = '';
3000  }
3001  $rep .= $hookmanager->resPrint;
3002  }
3003 
3004  return $rep;
3005 }
3006 
3013 {
3014  global $conf, $db;
3015 
3016  $socialnetworks = array();
3017  // Enable caching of array
3018  require_once DOL_DOCUMENT_ROOT.'/core/lib/memory.lib.php';
3019  $cachekey = 'socialnetworks_' . $conf->entity;
3020  $dataretrieved = dol_getcache($cachekey);
3021  if (!is_null($dataretrieved)) {
3022  $socialnetworks = $dataretrieved;
3023  } else {
3024  $sql = "SELECT rowid, code, label, url, icon, active FROM ".MAIN_DB_PREFIX."c_socialnetworks";
3025  $sql .= " WHERE entity=".$conf->entity;
3026  $resql = $db->query($sql);
3027  if ($resql) {
3028  while ($obj = $db->fetch_object($resql)) {
3029  $socialnetworks[$obj->code] = array(
3030  'rowid' => $obj->rowid,
3031  'label' => $obj->label,
3032  'url' => $obj->url,
3033  'icon' => $obj->icon,
3034  'active' => $obj->active,
3035  );
3036  }
3037  }
3038  dol_setcache($cachekey, $socialnetworks); // If setting cache fails, this is not a problem, so we do not test result.
3039  }
3040 
3041  return $socialnetworks;
3042 }
3043 
3054 function dol_print_socialnetworks($value, $cid, $socid, $type, $dictsocialnetworks = array())
3055 {
3056  global $conf, $user, $langs;
3057 
3058  $htmllink = $value;
3059 
3060  if (empty($value)) {
3061  return '&nbsp;';
3062  }
3063 
3064  if (!empty($type)) {
3065  $htmllink = '<div class="divsocialnetwork inline-block valignmiddle">';
3066  // Use dictionary definition for picto $dictsocialnetworks[$type]['icon']
3067  $htmllink .= '<span class="fa paddingright '.($dictsocialnetworks[$type]['icon'] ? $dictsocialnetworks[$type]['icon'] : 'fa-link').'"></span>';
3068  if ($type == 'skype') {
3069  $htmllink .= dol_escape_htmltag($value);
3070  $htmllink .= '&nbsp;';
3071  $htmllink .= '<a href="skype:';
3072  $htmllink .= dol_string_nospecial($value, '_', '', array('@'));
3073  $htmllink .= '?call" alt="'.$langs->trans("Call").'&nbsp;'.$value.'" title="'.dol_escape_htmltag($langs->trans("Call").' '.$value).'">';
3074  $htmllink .= '<img src="'.DOL_URL_ROOT.'/theme/common/skype_callbutton.png" border="0">';
3075  $htmllink .= '</a><a href="skype:';
3076  $htmllink .= dol_string_nospecial($value, '_', '', array('@'));
3077  $htmllink .= '?chat" alt="'.$langs->trans("Chat").'&nbsp;'.$value.'" title="'.dol_escape_htmltag($langs->trans("Chat").' '.$value).'">';
3078  $htmllink .= '<img class="paddingleft" src="'.DOL_URL_ROOT.'/theme/common/skype_chatbutton.png" border="0">';
3079  $htmllink .= '</a>';
3080  if (($cid || $socid) && !empty($conf->agenda->enabled) && $user->rights->agenda->myactions->create) {
3081  $addlink = 'AC_SKYPE';
3082  $link = '';
3083  if (!empty($conf->global->AGENDA_ADDACTIONFORSKYPE)) {
3084  $link = '<a href="'.DOL_URL_ROOT.'/comm/action/card.php?action=create&amp;backtopage=1&amp;actioncode='.$addlink.'&amp;contactid='.$cid.'&amp;socid='.$socid.'">'.img_object($langs->trans("AddAction"), "calendar").'</a>';
3085  }
3086  $htmllink .= ($link ? ' '.$link : '');
3087  }
3088  } else {
3089  if (!empty($dictsocialnetworks[$type]['url'])) {
3090  $tmpvirginurl = preg_replace('/\/?{socialid}/', '', $dictsocialnetworks[$type]['url']);
3091  if ($tmpvirginurl) {
3092  $value = preg_replace('/^www\.'.preg_quote($tmpvirginurl, '/').'\/?/', '', $value);
3093  $value = preg_replace('/^'.preg_quote($tmpvirginurl, '/').'\/?/', '', $value);
3094 
3095  $tmpvirginurl3 = preg_replace('/^https:\/\//i', 'https://www.', $tmpvirginurl);
3096  if ($tmpvirginurl3) {
3097  $value = preg_replace('/^www\.'.preg_quote($tmpvirginurl3, '/').'\/?/', '', $value);
3098  $value = preg_replace('/^'.preg_quote($tmpvirginurl3, '/').'\/?/', '', $value);
3099  }
3100 
3101  $tmpvirginurl2 = preg_replace('/^https?:\/\//i', '', $tmpvirginurl);
3102  if ($tmpvirginurl2) {
3103  $value = preg_replace('/^www\.'.preg_quote($tmpvirginurl2, '/').'\/?/', '', $value);
3104  $value = preg_replace('/^'.preg_quote($tmpvirginurl2, '/').'\/?/', '', $value);
3105  }
3106  }
3107  $link = str_replace('{socialid}', $value, $dictsocialnetworks[$type]['url']);
3108  if (preg_match('/^https?:\/\//i', $link)) {
3109  $htmllink .= '&nbsp;<a href="'.dol_sanitizeUrl($link, 0).'" target="_blank" rel="noopener noreferrer">'.dol_escape_htmltag($value).'</a>';
3110  } else {
3111  $htmllink .= '&nbsp;<a href="'.dol_sanitizeUrl($link, 1).'" target="_blank" rel="noopener noreferrer">'.dol_escape_htmltag($value).'</a>';
3112  }
3113  } else {
3114  $htmllink .= dol_escape_htmltag($value);
3115  }
3116  }
3117  $htmllink .= '</div>';
3118  } else {
3119  $langs->load("errors");
3120  $htmllink .= img_warning($langs->trans("ErrorBadSocialNetworkValue", $value));
3121  }
3122  return $htmllink;
3123 }
3124 
3135 function dol_print_profids($profID, $profIDtype, $countrycode = '', $addcpButton = 1, $separ = '&nbsp;')
3136 {
3137  global $mysoc;
3138 
3139  if (empty($profID) || empty($profIDtype)) {
3140  return '';
3141  }
3142  if (empty($countrycode)) $countrycode = $mysoc->country_code;
3143  $newProfID = $profID;
3144  $id = substr($profIDtype, -1);
3145  $ret = '';
3146  if (strtoupper($countrycode) == 'FR') {
3147  // France
3148  if ($id == 1 && dol_strlen($newProfID) == 9) $newProfID = substr($newProfID, 0, 3).$separ.substr($newProfID, 3, 3).$separ.substr($newProfID, 6, 3);
3149  if ($id == 2 && dol_strlen($newProfID) == 14) $newProfID = substr($newProfID, 0, 3).$separ.substr($newProfID, 3, 3).$separ.substr($newProfID, 6, 3).$separ.substr($newProfID, 9, 5);
3150  if ($profIDtype === 'VAT' && dol_strlen($newProfID) == 13) $newProfID = substr($newProfID, 0, 4).$separ.substr($newProfID, 4, 3).$separ.substr($newProfID, 7, 3).$separ.substr($newProfID, 10, 3);
3151  }
3152  if (!empty($addcpButton)) $ret = showValueWithClipboardCPButton(dol_escape_htmltag($profID), ($addcpButton == 1 ? 1 : 0), $newProfID);
3153  else $ret = $newProfID;
3154  return $ret;
3155 }
3156 
3171 function dol_print_phone($phone, $countrycode = '', $cid = 0, $socid = 0, $addlink = '', $separ = "&nbsp;", $withpicto = '', $titlealt = '', $adddivfloat = 0)
3172 {
3173  global $conf, $user, $langs, $mysoc, $hookmanager;
3174 
3175  // Clean phone parameter
3176  $phone = preg_replace("/[\s.-]/", "", trim($phone));
3177  if (empty($phone)) {
3178  return '';
3179  }
3180  if (!empty($conf->global->MAIN_PHONE_SEPAR)) {
3181  $separ = $conf->global->MAIN_PHONE_SEPAR;
3182  }
3183  if (empty($countrycode) && is_object($mysoc)) {
3184  $countrycode = $mysoc->country_code;
3185  }
3186 
3187  // Short format for small screens
3188  if ($conf->dol_optimize_smallscreen) {
3189  $separ = '';
3190  }
3191 
3192  $newphone = $phone;
3193  if (strtoupper($countrycode) == "FR") {
3194  // France
3195  if (dol_strlen($phone) == 10) {
3196  $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);
3197  } elseif (dol_strlen($phone) == 7) {
3198  $newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 2).$separ.substr($newphone, 5, 2);
3199  } elseif (dol_strlen($phone) == 9) {
3200  $newphone = substr($newphone, 0, 2).$separ.substr($newphone, 2, 3).$separ.substr($newphone, 5, 2).$separ.substr($newphone, 7, 2);
3201  } elseif (dol_strlen($phone) == 11) {
3202  $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);
3203  } elseif (dol_strlen($phone) == 12) {
3204  $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);
3205  } elseif (dol_strlen($phone) == 13) {
3206  $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);
3207  }
3208  } elseif (strtoupper($countrycode) == "CA") {
3209  if (dol_strlen($phone) == 10) {
3210  $newphone = ($separ != '' ? '(' : '').substr($newphone, 0, 3).($separ != '' ? ')' : '').$separ.substr($newphone, 3, 3).($separ != '' ? '-' : '').substr($newphone, 6, 4);
3211  }
3212  } elseif (strtoupper($countrycode) == "PT") {//Portugal
3213  if (dol_strlen($phone) == 13) {//ex: +351_ABC_DEF_GHI
3214  $newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 3);
3215  }
3216  } elseif (strtoupper($countrycode) == "SR") {//Suriname
3217  if (dol_strlen($phone) == 10) {//ex: +597_ABC_DEF
3218  $newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3);
3219  } elseif (dol_strlen($phone) == 11) {//ex: +597_ABC_DEFG
3220  $newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 4);
3221  }
3222  } elseif (strtoupper($countrycode) == "DE") {//Allemagne
3223  if (dol_strlen($phone) == 14) {//ex: +49_ABCD_EFGH_IJK
3224  $newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 4).$separ.substr($newphone, 7, 4).$separ.substr($newphone, 11, 3);
3225  } elseif (dol_strlen($phone) == 13) {//ex: +49_ABC_DEFG_HIJ
3226  $newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 4).$separ.substr($newphone, 10, 3);
3227  }
3228  } elseif (strtoupper($countrycode) == "ES") {//Espagne
3229  if (dol_strlen($phone) == 12) {//ex: +34_ABC_DEF_GHI
3230  $newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 3);
3231  }
3232  } elseif (strtoupper($countrycode) == "BF") {// Burkina Faso
3233  if (dol_strlen($phone) == 12) {//ex : +22 A BC_DE_FG_HI
3234  $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);
3235  }
3236  } elseif (strtoupper($countrycode) == "RO") {// Roumanie
3237  if (dol_strlen($phone) == 12) {//ex : +40 AB_CDE_FG_HI
3238  $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);
3239  }
3240  } elseif (strtoupper($countrycode) == "TR") {//Turquie
3241  if (dol_strlen($phone) == 13) {//ex : +90 ABC_DEF_GHIJ
3242  $newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 4);
3243  }
3244  } elseif (strtoupper($countrycode) == "US") {//Etat-Unis
3245  if (dol_strlen($phone) == 12) {//ex: +1 ABC_DEF_GHIJ
3246  $newphone = substr($newphone, 0, 2).$separ.substr($newphone, 2, 3).$separ.substr($newphone, 5, 3).$separ.substr($newphone, 8, 4);
3247  }
3248  } elseif (strtoupper($countrycode) == "MX") {//Mexique
3249  if (dol_strlen($phone) == 12) {//ex: +52 ABCD_EFG_HI
3250  $newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 4).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 2);
3251  } elseif (dol_strlen($phone) == 11) {//ex: +52 AB_CD_EF_GH
3252  $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);
3253  } elseif (dol_strlen($phone) == 13) {//ex: +52 ABC_DEF_GHIJ
3254  $newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 4);
3255  }
3256  } elseif (strtoupper($countrycode) == "ML") {//Mali
3257  if (dol_strlen($phone) == 12) {//ex: +223 AB_CD_EF_GH
3258  $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);
3259  }
3260  } elseif (strtoupper($countrycode) == "TH") {//Thaïlande
3261  if (dol_strlen($phone) == 11) {//ex: +66_ABC_DE_FGH
3262  $newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 2).$separ.substr($newphone, 8, 3);
3263  } elseif (dol_strlen($phone) == 12) {//ex: +66_A_BCD_EF_GHI
3264  $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);
3265  }
3266  } elseif (strtoupper($countrycode) == "MU") {
3267  //Maurice
3268  if (dol_strlen($phone) == 11) {//ex: +230_ABC_DE_FG
3269  $newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 2).$separ.substr($newphone, 9, 2);
3270  } elseif (dol_strlen($phone) == 12) {//ex: +230_ABCD_EF_GH
3271  $newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 4).$separ.substr($newphone, 8, 2).$separ.substr($newphone, 10, 2);
3272  }
3273  } elseif (strtoupper($countrycode) == "ZA") {//Afrique du sud
3274  if (dol_strlen($phone) == 12) {//ex: +27_AB_CDE_FG_HI
3275  $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);
3276  }
3277  } elseif (strtoupper($countrycode) == "SY") {//Syrie
3278  if (dol_strlen($phone) == 12) {//ex: +963_AB_CD_EF_GH
3279  $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);
3280  } elseif (dol_strlen($phone) == 13) {//ex: +963_AB_CD_EF_GHI
3281  $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);
3282  }
3283  } elseif (strtoupper($countrycode) == "AE") {//Emirats Arabes Unis
3284  if (dol_strlen($phone) == 12) {//ex: +971_ABC_DEF_GH
3285  $newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 2);
3286  } elseif (dol_strlen($phone) == 13) {//ex: +971_ABC_DEF_GHI
3287  $newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 3);
3288  } elseif (dol_strlen($phone) == 14) {//ex: +971_ABC_DEF_GHIK
3289  $newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 4);
3290  }
3291  } elseif (strtoupper($countrycode) == "DZ") {//Algérie
3292  if (dol_strlen($phone) == 13) {//ex: +213_ABC_DEF_GHI
3293  $newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 3);
3294  }
3295  } elseif (strtoupper($countrycode) == "BE") {//Belgique
3296  if (dol_strlen($phone) == 11) {//ex: +32_ABC_DE_FGH
3297  $newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 2).$separ.substr($newphone, 8, 3);
3298  } elseif (dol_strlen($phone) == 12) {//ex: +32_ABC_DEF_GHI
3299  $newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 3);
3300  }
3301  } elseif (strtoupper($countrycode) == "PF") {//Polynésie française
3302  if (dol_strlen($phone) == 12) {//ex: +689_AB_CD_EF_GH
3303  $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);
3304  }
3305  } elseif (strtoupper($countrycode) == "CO") {//Colombie
3306  if (dol_strlen($phone) == 13) {//ex: +57_ABC_DEF_GH_IJ
3307  $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);
3308  }
3309  } elseif (strtoupper($countrycode) == "JO") {//Jordanie
3310  if (dol_strlen($phone) == 12) {//ex: +962_A_BCD_EF_GH
3311  $newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 1).$separ.substr($newphone, 5, 3).$separ.substr($newphone, 7, 2).$separ.substr($newphone, 9, 2);
3312  }
3313  } elseif (strtoupper($countrycode) == "JM") {//Jamaïque
3314  if (dol_strlen($newphone) == 12) {//ex: +1867_ABC_DEFG
3315  $newphone = substr($newphone, 0, 5).$separ.substr($newphone, 5, 3).$separ.substr($newphone, 8, 4);
3316  }
3317  } elseif (strtoupper($countrycode) == "MG") {//Madagascar
3318  if (dol_strlen($phone) == 13) {//ex: +261_AB_CD_EFG_HI
3319  $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);
3320  }
3321  } elseif (strtoupper($countrycode) == "GB") {//Royaume uni
3322  if (dol_strlen($phone) == 13) {//ex: +44_ABCD_EFG_HIJ
3323  $newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 4).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 3);
3324  }
3325  } elseif (strtoupper($countrycode) == "CH") {//Suisse
3326  if (dol_strlen($phone) == 12) {//ex: +41_AB_CDE_FG_HI
3327  $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);
3328  } elseif (dol_strlen($phone) == 15) {// +41_AB_CDE_FGH_IJKL
3329  $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);
3330  }
3331  } elseif (strtoupper($countrycode) == "TN") {//Tunisie
3332  if (dol_strlen($phone) == 12) {//ex: +216_AB_CDE_FGH
3333  $newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 2).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 3);
3334  }
3335  } elseif (strtoupper($countrycode) == "GF") {//Guyane francaise
3336  if (dol_strlen($phone) == 13) {//ex: +594_ABC_DE_FG_HI (ABC=594 de nouveau)
3337  $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);
3338  }
3339  } elseif (strtoupper($countrycode) == "GP") {//Guadeloupe
3340  if (dol_strlen($phone) == 13) {//ex: +590_ABC_DE_FG_HI (ABC=590 de nouveau)
3341  $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);
3342  }
3343  } elseif (strtoupper($countrycode) == "MQ") {//Martinique
3344  if (dol_strlen($phone) == 13) {//ex: +596_ABC_DE_FG_HI (ABC=596 de nouveau)
3345  $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);
3346  }
3347  } elseif (strtoupper($countrycode) == "IT") {//Italie
3348  if (dol_strlen($phone) == 12) {//ex: +39_ABC_DEF_GHI
3349  $newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 3);
3350  } elseif (dol_strlen($phone) == 13) {//ex: +39_ABC_DEF_GH_IJ
3351  $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);
3352  }
3353  } elseif (strtoupper($countrycode) == "AU") {
3354  //Australie
3355  if (dol_strlen($phone) == 12) {
3356  //ex: +61_A_BCDE_FGHI
3357  $newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 1).$separ.substr($newphone, 4, 4).$separ.substr($newphone, 8, 4);
3358  }
3359  }
3360  if (!empty($addlink)) { // Link on phone number (+ link to add action if conf->global->AGENDA_ADDACTIONFORPHONE set)
3361  if ($conf->browser->layout == 'phone' || (!empty($conf->clicktodial->enabled) && !empty($conf->global->CLICKTODIAL_USE_TEL_LINK_ON_PHONE_NUMBERS))) { // If phone or option for, we use link of phone
3362  $newphoneform = $newphone;
3363  $newphone = '<a href="tel:'.$phone.'"';
3364  $newphone .= '>'.$newphoneform.'</a>';
3365  } elseif (!empty($conf->clicktodial->enabled) && $addlink == 'AC_TEL') { // If click to dial, we use click to dial url
3366  if (empty($user->clicktodial_loaded)) {
3367  $user->fetch_clicktodial();
3368  }
3369 
3370  // Define urlmask
3371  $urlmask = 'ErrorClickToDialModuleNotConfigured';
3372  if (!empty($conf->global->CLICKTODIAL_URL)) {
3373  $urlmask = $conf->global->CLICKTODIAL_URL;
3374  }
3375  if (!empty($user->clicktodial_url)) {
3376  $urlmask = $user->clicktodial_url;
3377  }
3378 
3379  $clicktodial_poste = (!empty($user->clicktodial_poste) ?urlencode($user->clicktodial_poste) : '');
3380  $clicktodial_login = (!empty($user->clicktodial_login) ?urlencode($user->clicktodial_login) : '');
3381  $clicktodial_password = (!empty($user->clicktodial_password) ?urlencode($user->clicktodial_password) : '');
3382  // This line is for backward compatibility
3383  $url = sprintf($urlmask, urlencode($phone), $clicktodial_poste, $clicktodial_login, $clicktodial_password);
3384  // Thoose lines are for substitution
3385  $substitarray = array('__PHONEFROM__'=>$clicktodial_poste,
3386  '__PHONETO__'=>urlencode($phone),
3387  '__LOGIN__'=>$clicktodial_login,
3388  '__PASS__'=>$clicktodial_password);
3389  $url = make_substitutions($url, $substitarray);
3390  $newphonesav = $newphone;
3391  if (empty($conf->global->CLICKTODIAL_DO_NOT_USE_AJAX_CALL)) {
3392  // Default and recommended: New method using ajax without submiting a page making a javascript history.go(-1) back
3393  $newphone = '<a href="'.$url.'" class="cssforclicktodial"'; // Call of ajax is handled by the lib_foot.js.php on class 'cssforclicktodial'
3394  $newphone .= '>'.$newphonesav.'</a>';
3395  } else {
3396  // Old method
3397  $newphone = '<a href="'.$url.'"';
3398  if (!empty($conf->global->CLICKTODIAL_FORCENEWTARGET)) {
3399  $newphone .= ' target="_blank" rel="noopener noreferrer"';
3400  }
3401  $newphone .= '>'.$newphonesav.'</a>';
3402  }
3403  }
3404 
3405  //if (($cid || $socid) && ! empty($conf->agenda->enabled) && $user->rights->agenda->myactions->create)
3406  if (isModEnabled('agenda') && $user->rights->agenda->myactions->create) {
3407  $type = 'AC_TEL';
3408  $link = '';
3409  if ($addlink == 'AC_FAX') {
3410  $type = 'AC_FAX';
3411  }
3412  if (!empty($conf->global->AGENDA_ADDACTIONFORPHONE)) {
3413  $link = '<a href="'.DOL_URL_ROOT.'/comm/action/card.php?action=create&amp;backtopage='. urlencode($_SERVER['REQUEST_URI']) .'&amp;actioncode='.$type.($cid ? '&amp;contactid='.$cid : '').($socid ? '&amp;socid='.$socid : '').'">'.img_object($langs->trans("AddAction"), "calendar").'</a>';
3414  }
3415  if ($link) {
3416  $newphone = '<div>'.$newphone.' '.$link.'</div>';
3417  }
3418  }
3419  }
3420 
3421  if (empty($titlealt)) {
3422  $titlealt = ($withpicto == 'fax' ? $langs->trans("Fax") : $langs->trans("Phone"));
3423  }
3424  $rep = '';
3425 
3426  if ($hookmanager) {
3427  $parameters = array('countrycode' => $countrycode, 'cid' => $cid, 'socid' => $socid, 'titlealt' => $titlealt, 'picto' => $withpicto);
3428  $reshook = $hookmanager->executeHooks('printPhone', $parameters, $phone);
3429  $rep .= $hookmanager->resPrint;
3430  }
3431  if (empty($reshook)) {
3432  $picto = '';
3433  if ($withpicto) {
3434  if ($withpicto == 'fax') {
3435  $picto = 'phoning_fax';
3436  } elseif ($withpicto == 'phone') {
3437  $picto = 'phoning';
3438  } elseif ($withpicto == 'mobile') {
3439  $picto = 'phoning_mobile';
3440  } else {
3441  $picto = '';
3442  }
3443  }
3444  if ($adddivfloat) {
3445  $rep .= '<div class="nospan float" style="margin-right: 10px">';
3446  } else {
3447  $rep .= '<span style="margin-right: 10px;">';
3448  }
3449  $rep .= ($withpicto ?img_picto($titlealt, 'object_'.$picto.'.png').' ' : '').$newphone;
3450  if ($adddivfloat) {
3451  $rep .= '</div>';
3452  } else {
3453  $rep .= '</span>';
3454  }
3455  }
3456 
3457  return $rep;
3458 }
3459 
3467 function dol_print_ip($ip, $mode = 0)
3468 {
3469  global $conf, $langs;
3470 
3471  $ret = '';
3472 
3473  if (empty($mode)) {
3474  $ret .= $ip;
3475  }
3476 
3477  if ($mode != 2) {
3478  $countrycode = dolGetCountryCodeFromIp($ip);
3479  if ($countrycode) { // If success, countrycode is us, fr, ...
3480  if (file_exists(DOL_DOCUMENT_ROOT.'/theme/common/flags/'.$countrycode.'.png')) {
3481  $ret .= ' '.img_picto($countrycode.' '.$langs->trans("AccordingToGeoIPDatabase"), DOL_URL_ROOT.'/theme/common/flags/'.$countrycode.'.png', '', 1);
3482  } else {
3483  $ret .= ' ('.$countrycode.')';
3484  }
3485  } else {
3486  // Nothing
3487  }
3488  }
3489 
3490  return $ret;
3491 }
3492 
3502 {
3503  if (empty($_SERVER['HTTP_X_FORWARDED_FOR']) || preg_match('/[^0-9\.\:,\[\]]/', $_SERVER['HTTP_X_FORWARDED_FOR'])) {
3504  if (empty($_SERVER['HTTP_CLIENT_IP']) || preg_match('/[^0-9\.\:,\[\]]/', $_SERVER['HTTP_CLIENT_IP'])) {
3505  if (empty($_SERVER["HTTP_CF_CONNECTING_IP"])) {
3506  $ip = (empty($_SERVER['REMOTE_ADDR']) ? '' : $_SERVER['REMOTE_ADDR']); // value may have been the IP of the proxy and not the client
3507  } else {
3508  $ip = $_SERVER["HTTP_CF_CONNECTING_IP"]; // value here may have been forged by client
3509  }
3510  } else {
3511  $ip = $_SERVER['HTTP_CLIENT_IP']; // value is clean here but may have been forged by proxy
3512  }
3513  } else {
3514  $ip = $_SERVER['HTTP_X_FORWARDED_FOR']; // value is clean here but may have been forged by proxy
3515  }
3516  return $ip;
3517 }
3518 
3527 function isHTTPS()
3528 {
3529  $isSecure = false;
3530  if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
3531  $isSecure = true;
3532  } elseif (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' || !empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on') {
3533  $isSecure = true;
3534  }
3535  return $isSecure;
3536 }
3537 
3545 {
3546  global $conf;
3547 
3548  $countrycode = '';
3549 
3550  if (!empty($conf->geoipmaxmind->enabled)) {
3551  $datafile = getDolGlobalString('GEOIPMAXMIND_COUNTRY_DATAFILE');
3552  //$ip='24.24.24.24';
3553  //$datafile='/usr/share/GeoIP/GeoIP.dat'; Note that this must be downloaded datafile (not same than datafile provided with ubuntu packages)
3554  include_once DOL_DOCUMENT_ROOT.'/core/class/dolgeoip.class.php';
3555  $geoip = new DolGeoIP('country', $datafile);
3556  //print 'ip='.$ip.' databaseType='.$geoip->gi->databaseType." GEOIP_CITY_EDITION_REV1=".GEOIP_CITY_EDITION_REV1."\n";
3557  $countrycode = $geoip->getCountryCodeFromIP($ip);
3558  }
3559 
3560  return $countrycode;
3561 }
3562 
3563 
3571 {
3572  global $conf, $langs, $user;
3573 
3574  //$ret=$user->xxx;
3575  $ret = '';
3576  if (!empty($conf->geoipmaxmind->enabled)) {
3577  $ip = getUserRemoteIP();
3578  $datafile = getDolGlobalString('GEOIPMAXMIND_COUNTRY_DATAFILE');
3579  //$ip='24.24.24.24';
3580  //$datafile='E:\Mes Sites\Web\Admin1\awstats\maxmind\GeoIP.dat';
3581  include_once DOL_DOCUMENT_ROOT.'/core/class/dolgeoip.class.php';
3582  $geoip = new DolGeoIP('country', $datafile);
3583  $countrycode = $geoip->getCountryCodeFromIP($ip);
3584  $ret = $countrycode;
3585  }
3586  return $ret;
3587 }
3588 
3601 function dol_print_address($address, $htmlid, $element, $id, $noprint = 0, $charfornl = '')
3602 {
3603  global $conf, $user, $langs, $hookmanager;
3604 
3605  $out = '';
3606 
3607  if ($address) {
3608  if ($hookmanager) {
3609  $parameters = array('element' => $element, 'id' => $id);
3610  $reshook = $hookmanager->executeHooks('printAddress', $parameters, $address);
3611  $out .= $hookmanager->resPrint;
3612  }
3613  if (empty($reshook)) {
3614  if (empty($charfornl)) {
3615  $out .= nl2br($address);
3616  } else {
3617  $out .= preg_replace('/[\r\n]+/', $charfornl, $address);
3618  }
3619 
3620  // TODO Remove this block, we can add this using the hook now
3621  $showgmap = $showomap = 0;
3622  if (($element == 'thirdparty' || $element == 'societe') && !empty($conf->google->enabled) && !empty($conf->global->GOOGLE_ENABLE_GMAPS)) {
3623  $showgmap = 1;
3624  }
3625  if ($element == 'contact' && !empty($conf->google->enabled) && !empty($conf->global->GOOGLE_ENABLE_GMAPS_CONTACTS)) {
3626  $showgmap = 1;
3627  }
3628  if ($element == 'member' && !empty($conf->google->enabled) && !empty($conf->global->GOOGLE_ENABLE_GMAPS_MEMBERS)) {
3629  $showgmap = 1;
3630  }
3631  if (($element == 'thirdparty' || $element == 'societe') && !empty($conf->openstreetmap->enabled) && !empty($conf->global->OPENSTREETMAP_ENABLE_MAPS)) {
3632  $showomap = 1;
3633  }
3634  if ($element == 'contact' && !empty($conf->openstreetmap->enabled) && !empty($conf->global->OPENSTREETMAP_ENABLE_MAPS_CONTACTS)) {
3635  $showomap = 1;
3636  }
3637  if ($element == 'member' && !empty($conf->openstreetmap->enabled) && !empty($conf->global->OPENSTREETMAP_ENABLE_MAPS_MEMBERS)) {
3638  $showomap = 1;
3639  }
3640  if ($showgmap) {
3641  $url = dol_buildpath('/google/gmaps.php?mode='.$element.'&id='.$id, 1);
3642  $out .= ' <a href="'.$url.'" target="_gmaps"><img id="'.$htmlid.'" class="valigntextbottom" src="'.DOL_URL_ROOT.'/theme/common/gmap.png"></a>';
3643  }
3644  if ($showomap) {
3645  $url = dol_buildpath('/openstreetmap/maps.php?mode='.$element.'&id='.$id, 1);
3646  $out .= ' <a href="'.$url.'" target="_gmaps"><img id="'.$htmlid.'_openstreetmap" class="valigntextbottom" src="'.DOL_URL_ROOT.'/theme/common/gmap.png"></a>';
3647  }
3648  }
3649  }
3650  if ($noprint) {
3651  return $out;
3652  } else {
3653  print $out;
3654  }
3655 }
3656 
3657 
3667 function isValidEmail($address, $acceptsupervisorkey = 0, $acceptuserkey = 0)
3668 {
3669  if ($acceptsupervisorkey && $address == '__SUPERVISOREMAIL__') {
3670  return true;
3671  }
3672  if ($acceptuserkey && $address == '__USER_EMAIL__') {
3673  return true;
3674  }
3675  if (filter_var($address, FILTER_VALIDATE_EMAIL)) {
3676  return true;
3677  }
3678 
3679  return false;
3680 }
3681 
3690 function isValidMXRecord($domain)
3691 {
3692  if (function_exists('idn_to_ascii') && function_exists('checkdnsrr')) {
3693  if (!checkdnsrr(idn_to_ascii($domain), 'MX')) {
3694  return 0;
3695  }
3696  if (function_exists('getmxrr')) {
3697  $mxhosts = array();
3698  $weight = array();
3699  getmxrr(idn_to_ascii($domain), $mxhosts, $weight);
3700  if (count($mxhosts) > 1) {
3701  return 1;
3702  }
3703  if (count($mxhosts) == 1 && !empty($mxhosts[0])) {
3704  return 1;
3705  }
3706 
3707  return 0;
3708  }
3709  }
3710  return -1;
3711 }
3712 
3720 function isValidPhone($phone)
3721 {
3722  return true;
3723 }
3724 
3725 
3733 function dol_strlen($string, $stringencoding = 'UTF-8')
3734 {
3735  if (function_exists('mb_strlen')) {
3736  return mb_strlen($string, $stringencoding);
3737  } else {
3738  return strlen($string);
3739  }
3740 }
3741 
3752 function dol_substr($string, $start, $length, $stringencoding = '', $trunconbytes = 0)
3753 {
3754  global $langs;
3755 
3756  if (empty($stringencoding)) {
3757  $stringencoding = $langs->charset_output;
3758  }
3759 
3760  $ret = '';
3761  if (empty($trunconbytes)) {
3762  if (function_exists('mb_substr')) {
3763  $ret = mb_substr($string, $start, $length, $stringencoding);
3764  } else {
3765  $ret = substr($string, $start, $length);
3766  }
3767  } else {
3768  if (function_exists('mb_strcut')) {
3769  $ret = mb_strcut($string, $start, $length, $stringencoding);
3770  } else {
3771  $ret = substr($string, $start, $length);
3772  }
3773  }
3774  return $ret;
3775 }
3776 
3777 
3791 function dol_trunc($string, $size = 40, $trunc = 'right', $stringencoding = 'UTF-8', $nodot = 0, $display = 0)
3792 {
3793  global $conf;
3794 
3795  if (empty($size) || !empty($conf->global->MAIN_DISABLE_TRUNC)) {
3796  return $string;
3797  }
3798 
3799  if (empty($stringencoding)) {
3800  $stringencoding = 'UTF-8';
3801  }
3802  // reduce for small screen
3803  if ($conf->dol_optimize_smallscreen == 1 && $display == 1) {
3804  $size = round($size / 3);
3805  }
3806 
3807  // We go always here
3808  if ($trunc == 'right') {
3809  $newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
3810  if (dol_strlen($newstring, $stringencoding) > ($size + ($nodot ? 0 : 1))) {
3811  // If nodot is 0 and size is 1 chars more, we don't trunc and don't add …
3812  return dol_substr($newstring, 0, $size, $stringencoding).($nodot ? '' : '…');
3813  } else {
3814  //return 'u'.$size.'-'.$newstring.'-'.dol_strlen($newstring,$stringencoding).'-'.$string;
3815  return $string;
3816  }
3817  } elseif ($trunc == 'middle') {
3818  $newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
3819  if (dol_strlen($newstring, $stringencoding) > 2 && dol_strlen($newstring, $stringencoding) > ($size + 1)) {
3820  $size1 = round($size / 2);
3821  $size2 = round($size / 2);
3822  return dol_substr($newstring, 0, $size1, $stringencoding).'…'.dol_substr($newstring, dol_strlen($newstring, $stringencoding) - $size2, $size2, $stringencoding);
3823  } else {
3824  return $string;
3825  }
3826  } elseif ($trunc == 'left') {
3827  $newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
3828  if (dol_strlen($newstring, $stringencoding) > ($size + ($nodot ? 0 : 1))) {
3829  // If nodot is 0 and size is 1 chars more, we don't trunc and don't add …
3830  return '…'.dol_substr($newstring, dol_strlen($newstring, $stringencoding) - $size, $size, $stringencoding);
3831  } else {
3832  return $string;
3833  }
3834  } elseif ($trunc == 'wrap') {
3835  $newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
3836  if (dol_strlen($newstring, $stringencoding) > ($size + 1)) {
3837  return dol_substr($newstring, 0, $size, $stringencoding)."\n".dol_trunc(dol_substr($newstring, $size, dol_strlen($newstring, $stringencoding) - $size, $stringencoding), $size, $trunc);
3838  } else {
3839  return $string;
3840  }
3841  } else {
3842  return 'BadParam3CallingDolTrunc';
3843  }
3844 }
3845 
3866 function img_picto($titlealt, $picto, $moreatt = '', $pictoisfullpath = false, $srconly = 0, $notitle = 0, $alt = '', $morecss = '', $marginleftonlyshort = 2)
3867 {
3868  global $conf, $langs;
3869 
3870  // We forge fullpathpicto for image to $path/img/$picto. By default, we take DOL_URL_ROOT/theme/$conf->theme/img/$picto
3871  $url = DOL_URL_ROOT;
3872  $theme = isset($conf->theme) ? $conf->theme : null;
3873  $path = 'theme/'.$theme;
3874  // Define fullpathpicto to use into src
3875  if ($pictoisfullpath) {
3876  // Clean parameters
3877  if (!preg_match('/(\.png|\.gif|\.svg)$/i', $picto)) {
3878  $picto .= '.png';
3879  }
3880  $fullpathpicto = $picto;
3881  $reg = array();
3882  if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
3883  $morecss .= ($morecss ? ' ' : '').$reg[1];
3884  $moreatt = str_replace('class="'.$reg[1].'"', '', $moreatt);
3885  }
3886  } else {
3887  $pictowithouttext = preg_replace('/(\.png|\.gif|\.svg)$/', '', $picto);
3888  $pictowithouttext = str_replace('object_', '', $pictowithouttext);
3889 
3890  if (strpos($pictowithouttext, 'fontawesome_') !== false || preg_match('/^fa-/', $pictowithouttext)) {
3891  // This is a font awesome image 'fonwtawesome_xxx' or 'fa-xxx'
3892  $pictowithouttext = str_replace('fa-', '', $pictowithouttext);
3893  $pictowithouttextarray = explode('_', $pictowithouttext);
3894  $marginleftonlyshort = 0;
3895 
3896  if (!empty($pictowithouttextarray[1])) {
3897  $fakey = 'fa-'.$pictowithouttextarray[1];
3898  $fa = empty($pictowithouttextarray[2]) ? 'fa' : $pictowithouttextarray[2];
3899  $facolor = empty($pictowithouttextarray[3]) ? '' : $pictowithouttextarray[3];
3900  $fasize = empty($pictowithouttextarray[4]) ? '' : $pictowithouttextarray[4];
3901  } else {
3902  $fakey = 'fa-'.$pictowithouttext;
3903  $fa = 'fa';
3904  $facolor = '';
3905  $fasize = '';
3906  }
3907 
3908  // This snippet only needed since function img_edit accepts only one additional parameter: no separate one for css only.
3909  // class/style need to be extracted to avoid duplicate class/style validation errors when $moreatt is added to the end of the attributes.
3910  $morestyle = '';
3911  $reg = array();
3912  if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
3913  $morecss .= ($morecss ? ' ' : '').$reg[1];
3914  $moreatt = str_replace('class="'.$reg[1].'"', '', $moreatt);
3915  }
3916  if (preg_match('/style="([^"]+)"/', $moreatt, $reg)) {
3917  $morestyle = $reg[1];
3918  $moreatt = str_replace('style="'.$reg[1].'"', '', $moreatt);
3919  }
3920  $moreatt = trim($moreatt);
3921 
3922  $enabledisablehtml = '<span class="'.$fa.' '.$fakey.($marginleftonlyshort ? ($marginleftonlyshort == 1 ? ' marginleftonlyshort' : ' marginleftonly') : '');
3923  $enabledisablehtml .= ($morecss ? ' '.$morecss : '').'" style="'.($fasize ? ('font-size: '.$fasize.';') : '').($facolor ? (' color: '.$facolor.';') : '').($morestyle ? ' '.$morestyle : '').'"'.(($notitle || empty($titlealt)) ? '' : ' title="'.dol_escape_htmltag($titlealt).'"').($moreatt ? ' '.$moreatt : '').'>';
3924  /*if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
3925  $enabledisablehtml .= $titlealt;
3926  }*/
3927  $enabledisablehtml .= '</span>';
3928 
3929  return $enabledisablehtml;
3930  }
3931 
3932  if (empty($srconly) && in_array($pictowithouttext, array(
3933  '1downarrow', '1uparrow', '1leftarrow', '1rightarrow', '1uparrow_selected', '1downarrow_selected', '1leftarrow_selected', '1rightarrow_selected',
3934  'accountancy', 'accounting_account', 'account', 'accountline', 'action', 'add', 'address', 'angle-double-down', 'angle-double-up', 'asset',
3935  'bank_account', 'barcode', 'bank', 'bell', 'bill', 'billa', 'billr', 'billd', 'bookmark', 'bom', 'briefcase-medical', 'bug', 'building',
3936  'card', 'calendarlist', 'calendar', 'calendarmonth', 'calendarweek', 'calendarday', 'calendarperuser', 'calendarpertype',
3937  'cash-register', 'category', 'chart', 'check', 'clock', 'close_title', 'cog', 'collab', 'company', 'contact', 'country', 'contract', 'conversation', 'cron', 'cubes',
3938  'currency', 'multicurrency',
3939  'delete', 'dolly', 'dollyrevert', 'donation', 'download', 'dynamicprice',
3940  'edit', 'ellipsis-h', 'email', 'entity', 'envelope', 'eraser', 'establishment', 'expensereport', 'external-link-alt', 'external-link-square-alt',
3941  'filter', 'file-code', 'file-export', 'file-import', 'file-upload', 'autofill', 'folder', 'folder-open', 'folder-plus',
3942  'generate', 'globe', 'globe-americas', 'graph', 'grip', 'grip_title', 'group',
3943  'help', 'holiday',
3944  'images', 'incoterm', 'info', 'intervention', 'inventory', 'intracommreport', 'knowledgemanagement',
3945  'label', 'language', 'line', 'link', 'list', 'list-alt', 'listlight', 'loan', 'lot', 'long-arrow-alt-right',
3946  'margin', 'map-marker-alt', 'member', 'meeting', 'money-bill-alt', 'movement', 'mrp', 'note', 'next',
3947  'off', 'on', 'order',
3948  'paiment', 'paragraph', 'play', 'pdf', 'phone', 'phoning', 'phoning_mobile', 'phoning_fax', 'playdisabled', 'previous', 'poll', 'pos', 'printer', 'product', 'propal', 'puce',
3949  'stock', 'resize', 'service', 'stats', 'trip',
3950  'security', 'setup', 'share-alt', 'sign-out', 'split', 'stripe', 'stripe-s', 'switch_off', 'switch_on', 'switch_on_red', 'tools', 'unlink', 'uparrow', 'user', 'user-tie', 'vcard', 'wrench',
3951  'github', 'google', 'jabber', 'skype', 'twitter', 'facebook', 'linkedin', 'instagram', 'snapchat', 'youtube', 'google-plus-g', 'whatsapp',
3952  'chevron-left', 'chevron-right', 'chevron-down', 'chevron-top', 'commercial', 'companies',
3953  'generic', 'home', 'hrm', 'members', 'products', 'invoicing',
3954  'partnership', 'payment', 'payment_vat', 'pencil-ruler', 'preview', 'project', 'projectpub', 'projecttask', 'question', 'refresh', 'region',
3955  'salary', 'shipment', 'state', 'supplier_invoice', 'supplier_invoicea', 'supplier_invoicer', 'supplier_invoiced',
3956  'technic', 'ticket',
3957  'error', 'warning',
3958  'recent', 'reception', 'recruitmentcandidature', 'recruitmentjobposition', 'resource', 'recurring',
3959  'shapes', 'square', 'stop-circle', 'supplier', 'supplier_proposal', 'supplier_order', 'supplier_invoice',
3960  'timespent', 'title_setup', 'title_accountancy', 'title_bank', 'title_hrm', 'title_agenda',
3961  'uncheck', 'user-cog', 'user-injured', 'user-md', 'vat', 'website', 'workstation', 'webhook', 'world', 'private',
3962  'conferenceorbooth', 'eventorganization',
3963  'stamp', 'signature'
3964  ))) {
3965  $fakey = $pictowithouttext;
3966  $facolor = '';
3967  $fasize = '';
3968  $fa = 'fas';
3969  if (in_array($pictowithouttext, array('card', 'bell', 'clock', 'establishment', 'generic', 'minus-square', 'object_generic', 'pdf', 'plus-square', 'timespent', 'note', 'off', 'on', 'object_bookmark', 'bookmark', 'vcard'))) {
3970  $fa = 'far';
3971  }
3972  if (in_array($pictowithouttext, array('black-tie', 'github', 'google', 'skype', 'twitter', 'facebook', 'linkedin', 'instagram', 'snapchat', 'stripe', 'stripe-s', 'youtube', 'google-plus-g', 'whatsapp'))) {
3973  $fa = 'fab';
3974  }
3975 
3976  $arrayconvpictotofa = array(
3977  'account'=>'university', 'accounting_account'=>'clipboard-list', 'accountline'=>'receipt', 'accountancy'=>'search-dollar', 'action'=>'calendar-alt', 'add'=>'plus-circle', 'address'=> 'address-book', 'asset'=>'money-check-alt', 'autofill'=>'fill',
3978  'bank_account'=>'university',
3979  'bill'=>'file-invoice-dollar', 'billa'=>'file-excel', 'billr'=>'file-invoice-dollar', 'billd'=>'file-medical',
3980  'supplier_invoice'=>'file-invoice-dollar', 'supplier_invoicea'=>'file-excel', 'supplier_invoicer'=>'file-invoice-dollar', 'supplier_invoiced'=>'file-medical',
3981  'bom'=>'shapes',
3982  'card'=>'address-card', 'chart'=>'chart-line', 'company'=>'building', 'contact'=>'address-book', 'contract'=>'suitcase', 'collab'=>'people-arrows', 'conversation'=>'comments', 'country'=>'globe-americas', 'cron'=>'business-time',
3983  'donation'=>'file-alt', 'dynamicprice'=>'hand-holding-usd',
3984  'setup'=>'cog', 'companies'=>'building', 'products'=>'cube', 'commercial'=>'suitcase', 'invoicing'=>'coins',
3985  'accounting'=>'search-dollar', 'category'=>'tag', 'dollyrevert'=>'dolly',
3986  'generate'=>'plus-square', 'hrm'=>'user-tie', 'incoterm'=>'truck-loading',
3987  'margin'=>'calculator', 'members'=>'user-friends', 'ticket'=>'ticket-alt', 'globe'=>'external-link-alt', 'lot'=>'barcode',
3988  'email'=>'at', 'establishment'=>'building', 'edit'=>'pencil-alt', 'entity'=>'globe',
3989  'graph'=>'chart-line', 'grip_title'=>'arrows-alt', 'grip'=>'arrows-alt', 'help'=>'question-circle',
3990  'generic'=>'file', 'holiday'=>'umbrella-beach',
3991  'info'=>'info-circle', 'inventory'=>'boxes', 'intracommreport'=>'globe-europe', 'knowledgemanagement'=>'ticket-alt', 'label'=>'layer-group', 'line'=>'bars', 'loan'=>'money-bill-alt',
3992  'member'=>'user-alt', 'meeting'=>'chalkboard-teacher', 'mrp'=>'cubes', 'next'=>'arrow-alt-circle-right',
3993  'trip'=>'wallet', 'expensereport'=>'wallet', 'group'=>'users', 'movement'=>'people-carry',
3994  'sign-out'=>'sign-out-alt',
3995  'switch_off'=>'toggle-off', 'switch_on'=>'toggle-on', 'switch_on_red'=>'toggle-on', 'check'=>'check', 'bookmark'=>'star',
3996  'bank'=>'university', 'close_title'=>'times', 'delete'=>'trash', 'filter'=>'filter',
3997  'list-alt'=>'list-alt', 'calendarlist'=>'bars', 'calendar'=>'calendar-alt', 'calendarmonth'=>'calendar-alt', 'calendarweek'=>'calendar-week', 'calendarday'=>'calendar-day', 'calendarperuser'=>'table',
3998  'intervention'=>'ambulance', 'invoice'=>'file-invoice-dollar', 'currency'=>'dollar-sign', 'multicurrency'=>'dollar-sign', 'order'=>'file-invoice',
3999  'error'=>'exclamation-triangle', 'warning'=>'exclamation-triangle',
4000  'other'=>'square',
4001  'playdisabled'=>'play', 'pdf'=>'file-pdf', 'poll'=>'check-double', 'pos'=>'cash-register', 'preview'=>'binoculars', 'project'=>'project-diagram', 'projectpub'=>'project-diagram', 'projecttask'=>'tasks', 'propal'=>'file-signature',
4002  'partnership'=>'handshake', 'payment'=>'money-check-alt', 'payment_vat'=>'money-check-alt', 'phoning'=>'phone', 'phoning_mobile'=>'mobile-alt', 'phoning_fax'=>'fax', 'previous'=>'arrow-alt-circle-left', 'printer'=>'print', 'product'=>'cube', 'puce'=>'angle-right',
4003  'recent' => 'question', 'reception'=>'dolly', 'recruitmentjobposition'=>'id-card-alt', 'recruitmentcandidature'=>'id-badge',
4004  'resize'=>'crop', 'supplier_order'=>'dol-order_supplier', 'supplier_proposal'=>'file-signature',
4005  'refresh'=>'redo', 'region'=>'map-marked', 'resource'=>'laptop-house', 'recurring'=>'history',
4006  'service'=>'concierge-bell',
4007  'state'=>'map-marked-alt', 'security'=>'key', 'salary'=>'wallet', 'shipment'=>'dolly', 'stock'=>'box-open', 'stats' => 'chart-bar', 'split'=>'code-branch', 'stripe'=>'stripe-s',
4008  'supplier'=>'building', 'technic'=>'cogs',
4009  'timespent'=>'clock', 'title_setup'=>'tools', 'title_accountancy'=>'money-check-alt', 'title_bank'=>'university', 'title_hrm'=>'umbrella-beach',
4010  'title_agenda'=>'calendar-alt',
4011  'uncheck'=>'times', 'uparrow'=>'share', 'vat'=>'money-check-alt', 'vcard'=>'address-card',
4012  'jabber'=>'comment-o',
4013  'website'=>'globe-americas', 'workstation'=>'pallet', 'webhook'=>'bullseye', 'world'=>'globe', 'private'=>'user-lock',
4014  'conferenceorbooth'=>'chalkboard-teacher', 'eventorganization'=>'project-diagram'
4015  );
4016  if ($pictowithouttext == 'off') {
4017  $fakey = 'fa-square';
4018  $fasize = '1.3em';
4019  } elseif ($pictowithouttext == 'on') {
4020  $fakey = 'fa-check-square';
4021  $fasize = '1.3em';
4022  } elseif ($pictowithouttext == 'listlight') {
4023  $fakey = 'fa-download';
4024  $marginleftonlyshort = 1;
4025  } elseif ($pictowithouttext == 'printer') {
4026  $fakey = 'fa-print';
4027  $fasize = '1.2em';
4028  } elseif ($pictowithouttext == 'note') {
4029  $fakey = 'fa-sticky-note';
4030  $marginleftonlyshort = 1;
4031  } elseif (in_array($pictowithouttext, array('1uparrow', '1downarrow', '1leftarrow', '1rightarrow', '1uparrow_selected', '1downarrow_selected', '1leftarrow_selected', '1rightarrow_selected'))) {
4032  $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');
4033  $fakey = 'fa-'.$convertarray[$pictowithouttext];
4034  if (preg_match('/selected/', $pictowithouttext)) {
4035  $facolor = '#888';
4036  }
4037  $marginleftonlyshort = 1;
4038  } elseif (!empty($arrayconvpictotofa[$pictowithouttext])) {
4039  $fakey = 'fa-'.$arrayconvpictotofa[$pictowithouttext];
4040  } else {
4041  $fakey = 'fa-'.$pictowithouttext;
4042  }
4043 
4044  if (in_array($pictowithouttext, array('dollyrevert', 'member', 'members', 'contract', 'group', 'resource', 'shipment'))) {
4045  $morecss .= ' em092';
4046  }
4047  if (in_array($pictowithouttext, array('conferenceorbooth', 'collab', 'eventorganization', 'holiday', 'info', 'project', 'workstation'))) {
4048  $morecss .= ' em088';
4049  }
4050  if (in_array($pictowithouttext, array('asset', 'intervention', 'payment', 'loan', 'partnership', 'stock', 'technic'))) {
4051  $morecss .= ' em080';
4052  }
4053 
4054  // Define $marginleftonlyshort
4055  $arrayconvpictotomarginleftonly = array(
4056  'bank', 'check', 'delete', 'generic', 'grip', 'grip_title', 'jabber',
4057  'grip_title', 'grip', 'listlight', 'note', 'on', 'off', 'playdisabled', 'printer', 'resize', 'sign-out', 'stats', 'switch_on', 'switch_on_red', 'switch_off',
4058  'uparrow', '1uparrow', '1downarrow', '1leftarrow', '1rightarrow', '1uparrow_selected', '1downarrow_selected', '1leftarrow_selected', '1rightarrow_selected'
4059  );
4060  if (!isset($arrayconvpictotomarginleftonly[$pictowithouttext])) {
4061  $marginleftonlyshort = 0;
4062  }
4063 
4064  // Add CSS
4065  $arrayconvpictotomorcess = array(
4066  'action'=>'infobox-action', 'account'=>'infobox-bank_account', 'accounting_account'=>'infobox-bank_account', 'accountline'=>'infobox-bank_account', 'accountancy'=>'infobox-bank_account', 'asset'=>'infobox-bank_account',
4067  'bank_account'=>'infobox-bank_account',
4068  'bill'=>'infobox-commande', 'billa'=>'infobox-commande', 'billr'=>'infobox-commande', 'billd'=>'infobox-commande',
4069  'margin'=>'infobox-bank_account', 'conferenceorbooth'=>'infobox-project',
4070  'cash-register'=>'infobox-bank_account', 'contract'=>'infobox-contrat', 'check'=>'font-status4', 'collab'=>'infobox-action', 'conversation'=>'infobox-contrat',
4071  'donation'=>'infobox-commande', 'dolly'=>'infobox-commande', 'dollyrevert'=>'flip infobox-order_supplier',
4072  'ecm'=>'infobox-action', 'eventorganization'=>'infobox-project',
4073  'hrm'=>'infobox-adherent', 'group'=>'infobox-adherent', 'intervention'=>'infobox-contrat',
4074  'incoterm'=>'infobox-supplier_proposal',
4075  'currency'=>'infobox-bank_account', 'multicurrency'=>'infobox-bank_account',
4076  'members'=>'infobox-adherent', 'member'=>'infobox-adherent', 'money-bill-alt'=>'infobox-bank_account',
4077  'order'=>'infobox-commande',
4078  'user'=>'infobox-adherent', 'users'=>'infobox-adherent',
4079  'error'=>'pictoerror', 'warning'=>'pictowarning', 'switch_on'=>'font-status4', 'switch_on_red'=>'font-status8',
4080  'holiday'=>'infobox-holiday', 'info'=>'opacityhigh', 'invoice'=>'infobox-commande',
4081  'knowledgemanagement'=>'infobox-contrat rotate90', 'loan'=>'infobox-bank_account',
4082  'payment'=>'infobox-bank_account', 'payment_vat'=>'infobox-bank_account', 'poll'=>'infobox-adherent', 'pos'=>'infobox-bank_account', 'project'=>'infobox-project', 'projecttask'=>'infobox-project',
4083  'propal'=>'infobox-propal', 'private'=>'infobox-project',
4084  'reception'=>'flip', 'recruitmentjobposition'=>'infobox-adherent', 'recruitmentcandidature'=>'infobox-adherent',
4085  'resource'=>'infobox-action',
4086  'salary'=>'infobox-bank_account', 'shipment'=>'infobox-commande', 'supplier_invoice'=>'infobox-order_supplier', 'supplier_invoicea'=>'infobox-order_supplier', 'supplier_invoiced'=>'infobox-order_supplier',
4087  'supplier'=>'infobox-order_supplier', 'supplier_order'=>'infobox-order_supplier', 'supplier_proposal'=>'infobox-supplier_proposal',
4088  'ticket'=>'infobox-contrat', 'title_accountancy'=>'infobox-bank_account', 'title_hrm'=>'infobox-holiday', 'expensereport'=>'infobox-expensereport', 'trip'=>'infobox-expensereport', 'title_agenda'=>'infobox-action',
4089  'vat'=>'infobox-bank_account',
4090  //'title_setup'=>'infobox-action', 'tools'=>'infobox-action',
4091  'list-alt'=>'imgforviewmode', 'calendar'=>'imgforviewmode', 'calendarweek'=>'imgforviewmode', 'calendarmonth'=>'imgforviewmode', 'calendarday'=>'imgforviewmode', 'calendarperuser'=>'imgforviewmode'
4092  );
4093  if (!empty($arrayconvpictotomorcess[$pictowithouttext])) {
4094  $morecss .= ($morecss ? ' ' : '').$arrayconvpictotomorcess[$pictowithouttext];
4095  }
4096 
4097  // Define $color
4098  $arrayconvpictotocolor = array(
4099  'address'=>'#6c6aa8', 'building'=>'#6c6aa8', 'bom'=>'#a69944',
4100  'cog'=>'#999', 'companies'=>'#6c6aa8', 'company'=>'#6c6aa8', 'contact'=>'#6c6aa8', 'cron'=>'#555',
4101  'dynamicprice'=>'#a69944',
4102  'edit'=>'#444', 'note'=>'#999', 'error'=>'', 'help'=>'#bbb', 'listlight'=>'#999', 'language'=>'#555',
4103  //'dolly'=>'#a69944', 'dollyrevert'=>'#a69944',
4104  'lot'=>'#a69944',
4105  'map-marker-alt'=>'#aaa', 'mrp'=>'#a69944', 'product'=>'#a69944', 'service'=>'#a69944', 'inventory'=>'#a69944', 'stock'=>'#a69944', 'movement'=>'#a69944',
4106  'other'=>'#ddd', 'world'=>'#986c6a',
4107  'partnership'=>'#6c6aa8', 'playdisabled'=>'#ccc', 'printer'=>'#444', 'projectpub'=>'#986c6a', 'reception'=>'#a69944', 'resize'=>'#444', 'rss'=>'#cba',
4108  //'shipment'=>'#a69944',
4109  'security'=>'#999', 'square'=>'#888', 'stop-circle'=>'#888', 'stats'=>'#444', 'switch_off'=>'#999', 'technic'=>'#999', 'timespent'=>'#555',
4110  'uncheck'=>'#800', 'uparrow'=>'#555', 'user-cog'=>'#999', 'country'=>'#aaa', 'globe-americas'=>'#aaa', 'region'=>'#aaa', 'state'=>'#aaa',
4111  'website'=>'#304', 'workstation'=>'#a69944'
4112  );
4113  if (isset($arrayconvpictotocolor[$pictowithouttext])) {
4114  $facolor = $arrayconvpictotocolor[$pictowithouttext];
4115  }
4116 
4117  // This snippet only needed since function img_edit accepts only one additional parameter: no separate one for css only.
4118  // class/style need to be extracted to avoid duplicate class/style validation errors when $moreatt is added to the end of the attributes.
4119  $morestyle = '';
4120  $reg = array();
4121  if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
4122  $morecss .= ($morecss ? ' ' : '').$reg[1];
4123  $moreatt = str_replace('class="'.$reg[1].'"', '', $moreatt);
4124  }
4125  if (preg_match('/style="([^"]+)"/', $moreatt, $reg)) {
4126  $morestyle = $reg[1];
4127  $moreatt = str_replace('style="'.$reg[1].'"', '', $moreatt);
4128  }
4129  $moreatt = trim($moreatt);
4130 
4131  $enabledisablehtml = '<span class="'.$fa.' '.$fakey.($marginleftonlyshort ? ($marginleftonlyshort == 1 ? ' marginleftonlyshort' : ' marginleftonly') : '');
4132  $enabledisablehtml .= ($morecss ? ' '.$morecss : '').'" style="'.($fasize ? ('font-size: '.$fasize.';') : '').($facolor ? (' color: '.$facolor.';') : '').($morestyle ? ' '.$morestyle : '').'"'.(($notitle || empty($titlealt)) ? '' : ' title="'.dol_escape_htmltag($titlealt).'"').($moreatt ? ' '.$moreatt : '').'>';
4133  /*if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
4134  $enabledisablehtml .= $titlealt;
4135  }*/
4136  $enabledisablehtml .= '</span>';
4137 
4138  return $enabledisablehtml;
4139  }
4140 
4141  if (!empty($conf->global->MAIN_OVERWRITE_THEME_PATH)) {
4142  $path = $conf->global->MAIN_OVERWRITE_THEME_PATH.'/theme/'.$theme; // If the theme does not have the same name as the module
4143  } elseif (!empty($conf->global->MAIN_OVERWRITE_THEME_RES)) {
4144  $path = $conf->global->MAIN_OVERWRITE_THEME_RES.'/theme/'.$conf->global->MAIN_OVERWRITE_THEME_RES; // To allow an external module to overwrite image resources whatever is activated theme
4145  } elseif (!empty($conf->modules_parts['theme']) && array_key_exists($theme, $conf->modules_parts['theme'])) {
4146  $path = $theme.'/theme/'.$theme; // If the theme have the same name as the module
4147  }
4148 
4149  // If we ask an image into $url/$mymodule/img (instead of default path)
4150  $regs = array();
4151  if (preg_match('/^([^@]+)@([^@]+)$/i', $picto, $regs)) {
4152  $picto = $regs[1];
4153  $path = $regs[2]; // $path is $mymodule
4154  }
4155 
4156  // Clean parameters
4157  if (!preg_match('/(\.png|\.gif|\.svg)$/i', $picto)) {
4158  $picto .= '.png';
4159  }
4160  // If alt path are defined, define url where img file is, according to physical path
4161  // ex: array(["main"]=>"/home/maindir/htdocs", ["alt0"]=>"/home/moddir0/htdocs", ...)
4162  foreach ($conf->file->dol_document_root as $type => $dirroot) {
4163  if ($type == 'main') {
4164  continue;
4165  }
4166  // This need a lot of time, that's why enabling alternative dir like "custom" dir is not recommanded
4167  if (file_exists($dirroot.'/'.$path.'/img/'.$picto)) {
4168  $url = DOL_URL_ROOT.$conf->file->dol_url_root[$type];
4169  break;
4170  }
4171  }
4172 
4173  // $url is '' or '/custom', $path is current theme or
4174  $fullpathpicto = $url.'/'.$path.'/img/'.$picto;
4175  }
4176 
4177  if ($srconly) {
4178  return $fullpathpicto;
4179  }
4180  // tag title is used for tooltip on <a>, tag alt can be used with very simple text on image for blind people
4181  return '<img src="'.$fullpathpicto.'"'.($notitle ? '' : ' alt="'.dol_escape_htmltag($alt).'"').(($notitle || empty($titlealt)) ? '' : ' title="'.dol_escape_htmltag($titlealt).'"').($moreatt ? ' '.$moreatt.($morecss ? ' class="'.$morecss.'"' : '') : ' class="inline-block'.($morecss ? ' '.$morecss : '').'"').'>'; // Alt is used for accessibility, title for popup
4182 }
4183 
4197 function img_object($titlealt, $picto, $moreatt = '', $pictoisfullpath = false, $srconly = 0, $notitle = 0)
4198 {
4199  if (strpos($picto, '^') === 0) {
4200  return img_picto($titlealt, str_replace('^', '', $picto), $moreatt, $pictoisfullpath, $srconly, $notitle);
4201  } else {
4202  return img_picto($titlealt, 'object_'.$picto, $moreatt, $pictoisfullpath, $srconly, $notitle);
4203  }
4204 }
4205 
4217 function img_weather($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0, $morecss = '')
4218 {
4219  global $conf;
4220 
4221  if (is_numeric($picto)) {
4222  //$leveltopicto = array(0=>'weather-clear.png', 1=>'weather-few-clouds.png', 2=>'weather-clouds.png', 3=>'weather-many-clouds.png', 4=>'weather-storm.png');
4223  //$picto = $leveltopicto[$picto];
4224  return '<i class="fa fa-weather-level'.$picto.'"></i>';
4225  } elseif (!preg_match('/(\.png|\.gif)$/i', $picto)) {
4226  $picto .= '.png';
4227  }
4228 
4229  $path = DOL_URL_ROOT.'/theme/'.$conf->theme.'/img/weather/'.$picto;
4230 
4231  return img_picto($titlealt, $path, $moreatt, 1, 0, 0, '', $morecss);
4232 }
4233 
4245 function img_picto_common($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0, $notitle = 0)
4246 {
4247  global $conf;
4248 
4249  if (!preg_match('/(\.png|\.gif)$/i', $picto)) {
4250  $picto .= '.png';
4251  }
4252 
4253  if ($pictoisfullpath) {
4254  $path = $picto;
4255  } else {
4256  $path = DOL_URL_ROOT.'/theme/common/'.$picto;
4257 
4258  if (!empty($conf->global->MAIN_MODULE_CAN_OVERWRITE_COMMONICONS)) {
4259  $themepath = DOL_DOCUMENT_ROOT.'/theme/'.$conf->theme.'/img/'.$picto;
4260 
4261  if (file_exists($themepath)) {
4262  $path = $themepath;
4263  }
4264  }
4265  }
4266 
4267  return img_picto($titlealt, $path, $moreatt, 1, 0, $notitle);
4268 }
4269 
4282 function img_action($titlealt, $numaction, $picto = '')
4283 {
4284  global $langs;
4285 
4286  if (empty($titlealt) || $titlealt == 'default') {
4287  if ($numaction == '-1' || $numaction == 'ST_NO') {
4288  $numaction = -1;
4289  $titlealt = $langs->transnoentitiesnoconv('ChangeDoNotContact');
4290  } elseif ($numaction == '0' || $numaction == 'ST_NEVER') {
4291  $numaction = 0;
4292  $titlealt = $langs->transnoentitiesnoconv('ChangeNeverContacted');
4293  } elseif ($numaction == '1' || $numaction == 'ST_TODO') {
4294  $numaction = 1;
4295  $titlealt = $langs->transnoentitiesnoconv('ChangeToContact');
4296  } elseif ($numaction == '2' || $numaction == 'ST_PEND') {
4297  $numaction = 2;
4298  $titlealt = $langs->transnoentitiesnoconv('ChangeContactInProcess');
4299  } elseif ($numaction == '3' || $numaction == 'ST_DONE') {
4300  $numaction = 3;
4301  $titlealt = $langs->transnoentitiesnoconv('ChangeContactDone');
4302  } else {
4303  $titlealt = $langs->transnoentitiesnoconv('ChangeStatus '.$numaction);
4304  $numaction = 0;
4305  }
4306  }
4307  if (!is_numeric($numaction)) {
4308  $numaction = 0;
4309  }
4310 
4311  return img_picto($titlealt, !empty($picto) ? $picto : 'stcomm'.$numaction.'.png');
4312 }
4313 
4321 function img_pdf($titlealt = 'default', $size = 3)
4322 {
4323  global $langs;
4324 
4325  if ($titlealt == 'default') {
4326  $titlealt = $langs->trans('Show');
4327  }
4328 
4329  return img_picto($titlealt, 'pdf'.$size.'.png');
4330 }
4331 
4339 function img_edit_add($titlealt = 'default', $other = '')
4340 {
4341  global $langs;
4342 
4343  if ($titlealt == 'default') {
4344  $titlealt = $langs->trans('Add');
4345  }
4346 
4347  return img_picto($titlealt, 'edit_add.png', $other);
4348 }
4356 function img_edit_remove($titlealt = 'default', $other = '')
4357 {
4358  global $langs;
4359 
4360  if ($titlealt == 'default') {
4361  $titlealt = $langs->trans('Remove');
4362  }
4363 
4364  return img_picto($titlealt, 'edit_remove.png', $other);
4365 }
4366 
4375 function img_edit($titlealt = 'default', $float = 0, $other = '')
4376 {
4377  global $langs;
4378 
4379  if ($titlealt == 'default') {
4380  $titlealt = $langs->trans('Modify');
4381  }
4382 
4383  return img_picto($titlealt, 'edit.png', ($float ? 'style="float: '.($langs->tab_translate["DIRECTION"] == 'rtl' ? 'left' : 'right').'"' : "").($other ? ' '.$other : ''));
4384 }
4385 
4394 function img_view($titlealt = 'default', $float = 0, $other = 'class="valignmiddle"')
4395 {
4396  global $langs;
4397 
4398  if ($titlealt == 'default') {
4399  $titlealt = $langs->trans('View');
4400  }
4401 
4402  $moreatt = ($float ? 'style="float: right" ' : '').$other;
4403 
4404  return img_picto($titlealt, 'view.png', $moreatt);
4405 }
4406 
4415 function img_delete($titlealt = 'default', $other = 'class="pictodelete"', $morecss = '')
4416 {
4417  global $langs;
4418 
4419  if ($titlealt == 'default') {
4420  $titlealt = $langs->trans('Delete');
4421  }
4422 
4423  return img_picto($titlealt, 'delete.png', $other, false, 0, 0, '', $morecss);
4424 }
4425 
4433 function img_printer($titlealt = "default", $other = '')
4434 {
4435  global $langs;
4436  if ($titlealt == "default") {
4437  $titlealt = $langs->trans("Print");
4438  }
4439  return img_picto($titlealt, 'printer.png', $other);
4440 }
4441 
4449 function img_split($titlealt = 'default', $other = 'class="pictosplit"')
4450 {
4451  global $langs;
4452 
4453  if ($titlealt == 'default') {
4454  $titlealt = $langs->trans('Split');
4455  }
4456 
4457  return img_picto($titlealt, 'split.png', $other);
4458 }
4459 
4467 function img_help($usehelpcursor = 1, $usealttitle = 1)
4468 {
4469  global $langs;
4470 
4471  if ($usealttitle) {
4472  if (is_string($usealttitle)) {
4473  $usealttitle = dol_escape_htmltag($usealttitle);
4474  } else {
4475  $usealttitle = $langs->trans('Info');
4476  }
4477  }
4478 
4479  return img_picto($usealttitle, 'info.png', 'style="vertical-align: middle;'.($usehelpcursor == 1 ? ' cursor: help' : ($usehelpcursor == 2 ? ' cursor: pointer' : '')).'"');
4480 }
4481 
4488 function img_info($titlealt = 'default')
4489 {
4490  global $langs;
4491 
4492  if ($titlealt == 'default') {
4493  $titlealt = $langs->trans('Informations');
4494  }
4495 
4496  return img_picto($titlealt, 'info.png', 'style="vertical-align: middle;"');
4497 }
4498 
4507 function img_warning($titlealt = 'default', $moreatt = '', $morecss = 'pictowarning')
4508 {
4509  global $langs;
4510 
4511  if ($titlealt == 'default') {
4512  $titlealt = $langs->trans('Warning');
4513  }
4514 
4515  //return '<div class="imglatecoin">'.img_picto($titlealt, 'warning_white.png', 'class="pictowarning valignmiddle"'.($moreatt ? ($moreatt == '1' ? ' style="float: right"' : ' '.$moreatt): '')).'</div>';
4516  return img_picto($titlealt, 'warning.png', 'class="'.$morecss.'"'.($moreatt ? ($moreatt == '1' ? ' style="float: right"' : ' '.$moreatt) : ''));
4517 }
4518 
4525 function img_error($titlealt = 'default')
4526 {
4527  global $langs;
4528 
4529  if ($titlealt == 'default') {
4530  $titlealt = $langs->trans('Error');
4531  }
4532 
4533  return img_picto($titlealt, 'error.png');
4534 }
4535 
4543 function img_next($titlealt = 'default', $moreatt = '')
4544 {
4545  global $langs;
4546 
4547  if ($titlealt == 'default') {
4548  $titlealt = $langs->trans('Next');
4549  }
4550 
4551  //return img_picto($titlealt, 'next.png', $moreatt);
4552  return '<span class="fa fa-chevron-right paddingright paddingleft" title="'.dol_escape_htmltag($titlealt).'"></span>';
4553 }
4554 
4562 function img_previous($titlealt = 'default', $moreatt = '')
4563 {
4564  global $langs;
4565 
4566  if ($titlealt == 'default') {
4567  $titlealt = $langs->trans('Previous');
4568  }
4569 
4570  //return img_picto($titlealt, 'previous.png', $moreatt);
4571  return '<span class="fa fa-chevron-left paddingright paddingleft" title="'.dol_escape_htmltag($titlealt).'"></span>';
4572 }
4573 
4582 function img_down($titlealt = 'default', $selected = 0, $moreclass = '')
4583 {
4584  global $langs;
4585 
4586  if ($titlealt == 'default') {
4587  $titlealt = $langs->trans('Down');
4588  }
4589 
4590  return img_picto($titlealt, ($selected ? '1downarrow_selected.png' : '1downarrow.png'), 'class="imgdown'.($moreclass ? " ".$moreclass : "").'"');
4591 }
4592 
4601 function img_up($titlealt = 'default', $selected = 0, $moreclass = '')
4602 {
4603  global $langs;
4604 
4605  if ($titlealt == 'default') {
4606  $titlealt = $langs->trans('Up');
4607  }
4608 
4609  return img_picto($titlealt, ($selected ? '1uparrow_selected.png' : '1uparrow.png'), 'class="imgup'.($moreclass ? " ".$moreclass : "").'"');
4610 }
4611 
4620 function img_left($titlealt = 'default', $selected = 0, $moreatt = '')
4621 {
4622  global $langs;
4623 
4624  if ($titlealt == 'default') {
4625  $titlealt = $langs->trans('Left');
4626  }
4627 
4628  return img_picto($titlealt, ($selected ? '1leftarrow_selected.png' : '1leftarrow.png'), $moreatt);
4629 }
4630 
4639 function img_right($titlealt = 'default', $selected = 0, $moreatt = '')
4640 {
4641  global $langs;
4642 
4643  if ($titlealt == 'default') {
4644  $titlealt = $langs->trans('Right');
4645  }
4646 
4647  return img_picto($titlealt, ($selected ? '1rightarrow_selected.png' : '1rightarrow.png'), $moreatt);
4648 }
4649 
4657 function img_allow($allow, $titlealt = 'default')
4658 {
4659  global $langs;
4660 
4661  if ($titlealt == 'default') {
4662  $titlealt = $langs->trans('Active');
4663  }
4664 
4665  if ($allow == 1) {
4666  return img_picto($titlealt, 'tick.png');
4667  }
4668 
4669  return '-';
4670 }
4671 
4679 function img_credit_card($brand, $morecss = null)
4680 {
4681  if (is_null($morecss)) {
4682  $morecss = 'fa-2x';
4683  }
4684 
4685  if ($brand == 'visa' || $brand == 'Visa') {
4686  $brand = 'cc-visa';
4687  } elseif ($brand == 'mastercard' || $brand == 'MasterCard') {
4688  $brand = 'cc-mastercard';
4689  } elseif ($brand == 'amex' || $brand == 'American Express') {
4690  $brand = 'cc-amex';
4691  } elseif ($brand == 'discover' || $brand == 'Discover') {
4692  $brand = 'cc-discover';
4693  } elseif ($brand == 'jcb' || $brand == 'JCB') {
4694  $brand = 'cc-jcb';
4695  } elseif ($brand == 'diners' || $brand == 'Diners club') {
4696  $brand = 'cc-diners-club';
4697  } elseif (!in_array($brand, array('cc-visa', 'cc-mastercard', 'cc-amex', 'cc-discover', 'cc-jcb', 'cc-diners-club'))) {
4698  $brand = 'credit-card';
4699  }
4700 
4701  return '<span class="fa fa-'.$brand.' fa-fw'.($morecss ? ' '.$morecss : '').'"></span>';
4702 }
4703 
4712 function img_mime($file, $titlealt = '', $morecss = '')
4713 {
4714  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
4715 
4716  $mimetype = dol_mimetype($file, '', 1);
4717  $mimeimg = dol_mimetype($file, '', 2);
4718  $mimefa = dol_mimetype($file, '', 4);
4719 
4720  if (empty($titlealt)) {
4721  $titlealt = 'Mime type: '.$mimetype;
4722  }
4723 
4724  //return img_picto_common($titlealt, 'mime/'.$mimeimg, 'class="'.$morecss.'"');
4725  return '<i class="fa fa-'.$mimefa.' paddingright'.($morecss ? ' '.$morecss : '').'"'.($titlealt ? ' title="'.$titlealt.'"' : '').'></i>';
4726 }
4727 
4728 
4736 function img_search($titlealt = 'default', $other = '')
4737 {
4738  global $conf, $langs;
4739 
4740  if ($titlealt == 'default') {
4741  $titlealt = $langs->trans('Search');
4742  }
4743 
4744  $img = img_picto($titlealt, 'search.png', $other, false, 1);
4745 
4746  $input = '<input type="image" class="liste_titre" name="button_search" src="'.$img.'" ';
4747  $input .= 'value="'.dol_escape_htmltag($titlealt).'" title="'.dol_escape_htmltag($titlealt).'" >';
4748 
4749  return $input;
4750 }
4751 
4759 function img_searchclear($titlealt = 'default', $other = '')
4760 {
4761  global $conf, $langs;
4762 
4763  if ($titlealt == 'default') {
4764  $titlealt = $langs->trans('Search');
4765  }
4766 
4767  $img = img_picto($titlealt, 'searchclear.png', $other, false, 1);
4768 
4769  $input = '<input type="image" class="liste_titre" name="button_removefilter" src="'.$img.'" ';
4770  $input .= 'value="'.dol_escape_htmltag($titlealt).'" title="'.dol_escape_htmltag($titlealt).'" >';
4771 
4772  return $input;
4773 }
4774 
4786 function info_admin($text, $infoonimgalt = 0, $nodiv = 0, $admin = '1', $morecss = 'hideonsmartphone', $textfordropdown = '')
4787 {
4788  global $conf, $langs;
4789 
4790  if ($infoonimgalt) {
4791  $result = img_picto($text, 'info', 'class="'.($morecss ? ' '.$morecss : '').'"');
4792  } else {
4793  if (empty($conf->use_javascript_ajax)) {
4794  $textfordropdown = '';
4795  }
4796 
4797  $class = (empty($admin) ? 'undefined' : ($admin == '1' ? 'info' : $admin));
4798  $result = ($nodiv ? '' : '<div class="'.$class.($morecss ? ' '.$morecss : '').($textfordropdown ? ' hidden' : '').'">').'<span class="fa fa-info-circle" title="'.dol_escape_htmltag($admin ? $langs->trans('InfoAdmin') : $langs->trans('Note')).'"></span> '.$text.($nodiv ? '' : '</div>');
4799 
4800  if ($textfordropdown) {
4801  $tmpresult = '<span class="'.$class.'text opacitymedium cursorpointer">'.$langs->trans($textfordropdown).' '.img_picto($langs->trans($textfordropdown), '1downarrow').'</span>';
4802  $tmpresult .= '<script type="text/javascript">
4803  jQuery(document).ready(function() {
4804  jQuery(".'.$class.'text").click(function() {
4805  console.log("toggle text");
4806  jQuery(".'.$class.'").toggle();
4807  });
4808  });
4809  </script>';
4810 
4811  $result = $tmpresult.$result;
4812  }
4813  }
4814 
4815  return $result;
4816 }
4817 
4818 
4830 function dol_print_error($db = '', $error = '', $errors = null)
4831 {
4832  global $conf, $langs, $argv;
4833  global $dolibarr_main_prod;
4834 
4835  $out = '';
4836  $syslog = '';
4837 
4838  // If error occurs before the $lang object was loaded
4839  if (!$langs) {
4840  require_once DOL_DOCUMENT_ROOT.'/core/class/translate.class.php';
4841  $langs = new Translate('', $conf);
4842  $langs->load("main");
4843  }
4844 
4845  // Load translation files required by the error messages
4846  $langs->loadLangs(array('main', 'errors'));
4847 
4848  if ($_SERVER['DOCUMENT_ROOT']) { // Mode web
4849  $out .= $langs->trans("DolibarrHasDetectedError").".<br>\n";
4850  if (getDolGlobalInt('MAIN_FEATURES_LEVEL') > 0) {
4851  $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";
4852  }
4853  $out .= $langs->trans("InformationToHelpDiagnose").":<br>\n";
4854 
4855  $out .= "<b>".$langs->trans("Date").":</b> ".dol_print_date(time(), 'dayhourlog')."<br>\n";
4856  $out .= "<b>".$langs->trans("Dolibarr").":</b> ".DOL_VERSION." - https://www.dolibarr.org<br>\n";
4857  if (isset($conf->global->MAIN_FEATURES_LEVEL)) {
4858  $out .= "<b>".$langs->trans("LevelOfFeature").":</b> ".getDolGlobalInt('MAIN_FEATURES_LEVEL')."<br>\n";
4859  }
4860  if (function_exists("phpversion")) {
4861  $out .= "<b>".$langs->trans("PHP").":</b> ".phpversion()."<br>\n";
4862  }
4863  $out .= "<b>".$langs->trans("Server").":</b> ".(isset($_SERVER["SERVER_SOFTWARE"]) ? dol_htmlentities($_SERVER["SERVER_SOFTWARE"], ENT_COMPAT) : '')."<br>\n";
4864  if (function_exists("php_uname")) {
4865  $out .= "<b>".$langs->trans("OS").":</b> ".php_uname()."<br>\n";
4866  }
4867  $out .= "<b>".$langs->trans("UserAgent").":</b> ".(isset($_SERVER["HTTP_USER_AGENT"]) ? dol_htmlentities($_SERVER["HTTP_USER_AGENT"], ENT_COMPAT) : '')."<br>\n";
4868  $out .= "<br>\n";
4869  $out .= "<b>".$langs->trans("RequestedUrl").":</b> ".dol_htmlentities($_SERVER["REQUEST_URI"], ENT_COMPAT)."<br>\n";
4870  $out .= "<b>".$langs->trans("Referer").":</b> ".(isset($_SERVER["HTTP_REFERER"]) ? dol_htmlentities($_SERVER["HTTP_REFERER"], ENT_COMPAT) : '')."<br>\n";
4871  $out .= "<b>".$langs->trans("MenuManager").":</b> ".(isset($conf->standard_menu) ? dol_htmlentities($conf->standard_menu, ENT_COMPAT) : '')."<br>\n";
4872  $out .= "<br>\n";
4873  $syslog .= "url=".dol_escape_htmltag($_SERVER["REQUEST_URI"]);
4874  $syslog .= ", query_string=".dol_escape_htmltag($_SERVER["QUERY_STRING"]);
4875  } else // Mode CLI
4876  {
4877  $out .= '> '.$langs->transnoentities("ErrorInternalErrorDetected").":\n".$argv[0]."\n";
4878  $syslog .= "pid=".dol_getmypid();
4879  }
4880 
4881  if (!empty($conf->modules)) {
4882  $out .= "<b>".$langs->trans("Modules").":</b> ".join(', ', $conf->modules)."<br>\n";
4883  }
4884 
4885  if (is_object($db)) {
4886  if ($_SERVER['DOCUMENT_ROOT']) { // Mode web
4887  $out .= "<b>".$langs->trans("DatabaseTypeManager").":</b> ".$db->type."<br>\n";
4888  $out .= "<b>".$langs->trans("RequestLastAccessInError").":</b> ".($db->lastqueryerror() ? dol_escape_htmltag($db->lastqueryerror()) : $langs->trans("ErrorNoRequestInError"))."<br>\n";
4889  $out .= "<b>".$langs->trans("ReturnCodeLastAccessInError").":</b> ".($db->lasterrno() ? dol_escape_htmltag($db->lasterrno()) : $langs->trans("ErrorNoRequestInError"))."<br>\n";
4890  $out .= "<b>".$langs->trans("InformationLastAccessInError").":</b> ".($db->lasterror() ? dol_escape_htmltag($db->lasterror()) : $langs->trans("ErrorNoRequestInError"))."<br>\n";
4891  $out .= "<br>\n";
4892  } else // Mode CLI
4893  {
4894  // No dol_escape_htmltag for output, we are in CLI mode
4895  $out .= '> '.$langs->transnoentities("DatabaseTypeManager").":\n".$db->type."\n";
4896  $out .= '> '.$langs->transnoentities("RequestLastAccessInError").":\n".($db->lastqueryerror() ? $db->lastqueryerror() : $langs->transnoentities("ErrorNoRequestInError"))."\n";
4897  $out .= '> '.$langs->transnoentities("ReturnCodeLastAccessInError").":\n".($db->lasterrno() ? $db->lasterrno() : $langs->transnoentities("ErrorNoRequestInError"))."\n";
4898  $out .= '> '.$langs->transnoentities("InformationLastAccessInError").":\n".($db->lasterror() ? $db->lasterror() : $langs->transnoentities("ErrorNoRequestInError"))."\n";
4899  }
4900  $syslog .= ", sql=".$db->lastquery();
4901  $syslog .= ", db_error=".$db->lasterror();
4902  }
4903 
4904  if ($error || $errors) {
4905  $langs->load("errors");
4906 
4907  // Merge all into $errors array
4908  if (is_array($error) && is_array($errors)) {
4909  $errors = array_merge($error, $errors);
4910  } elseif (is_array($error)) {
4911  $errors = $error;
4912  } elseif (is_array($errors)) {
4913  $errors = array_merge(array($error), $errors);
4914  } else {
4915  $errors = array_merge(array($error));
4916  }
4917 
4918  foreach ($errors as $msg) {
4919  if (empty($msg)) {
4920  continue;
4921  }
4922  if ($_SERVER['DOCUMENT_ROOT']) { // Mode web
4923  $out .= "<b>".$langs->trans("Message").":</b> ".dol_escape_htmltag($msg)."<br>\n";
4924  } else // Mode CLI
4925  {
4926  $out .= '> '.$langs->transnoentities("Message").":\n".$msg."\n";
4927  }
4928  $syslog .= ", msg=".$msg;
4929  }
4930  }
4931  if (empty($dolibarr_main_prod) && $_SERVER['DOCUMENT_ROOT'] && function_exists('xdebug_print_function_stack') && function_exists('xdebug_call_file')) {
4932  xdebug_print_function_stack();
4933  $out .= '<b>XDebug informations:</b>'."<br>\n";
4934  $out .= 'File: '.xdebug_call_file()."<br>\n";
4935  $out .= 'Line: '.xdebug_call_line()."<br>\n";
4936  $out .= 'Function: '.xdebug_call_function()."<br>\n";
4937  $out .= "<br>\n";
4938  }
4939 
4940  // Return a http error code if possible
4941  if (!headers_sent()) {
4942  http_response_code(500);
4943  }
4944 
4945  if (empty($dolibarr_main_prod)) {
4946  print $out;
4947  } else {
4948  if (empty($langs->defaultlang)) {
4949  $langs->setDefaultLang();
4950  }
4951  $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.
4952  // This should not happen, except if there is a bug somewhere. Enabled and check log in such case.
4953  print 'This website or feature is currently temporarly 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";
4954  print $langs->trans("DolibarrHasDetectedError").'. ';
4955  print $langs->trans("YouCanSetOptionDolibarrMainProdToZero");
4956  define("MAIN_CORE_ERROR", 1);
4957  }
4958 
4959  dol_syslog("Error ".$syslog, LOG_ERR);
4960 }
4961 
4972 function dol_print_error_email($prefixcode, $errormessage = '', $errormessages = array(), $morecss = 'error', $email = '')
4973 {
4974  global $langs, $conf;
4975 
4976  if (empty($email)) {
4977  $email = $conf->global->MAIN_INFO_SOCIETE_MAIL;
4978  }
4979 
4980  $langs->load("errors");
4981  $now = dol_now();
4982 
4983  print '<br><div class="center login_main_message"><div class="'.$morecss.'">';
4984  print $langs->trans("ErrorContactEMail", $email, $prefixcode.dol_print_date($now, '%Y%m%d%H%M%S'));
4985  if ($errormessage) {
4986  print '<br><br>'.$errormessage;
4987  }
4988  if (is_array($errormessages) && count($errormessages)) {
4989  foreach ($errormessages as $mesgtoshow) {
4990  print '<br><br>'.$mesgtoshow;
4991  }
4992  }
4993  print '</div></div>';
4994 }
4995 
5012 function print_liste_field_titre($name, $file = "", $field = "", $begin = "", $moreparam = "", $moreattrib = "", $sortfield = "", $sortorder = "", $prefix = "", $tooltip = "", $forcenowrapcolumntitle = 0)
5013 {
5014  print getTitleFieldOfList($name, 0, $file, $field, $begin, $moreparam, $moreattrib, $sortfield, $sortorder, $prefix, 0, $tooltip, $forcenowrapcolumntitle);
5015 }
5016 
5035 function getTitleFieldOfList($name, $thead = 0, $file = "", $field = "", $begin = "", $moreparam = "", $moreattrib = "", $sortfield = "", $sortorder = "", $prefix = "", $disablesortlink = 0, $tooltip = '', $forcenowrapcolumntitle = 0)
5036 {
5037  global $conf, $langs, $form;
5038  //print "$name, $file, $field, $begin, $options, $moreattrib, $sortfield, $sortorder<br>\n";
5039 
5040  if ($moreattrib == 'class="right"') {
5041  $prefix .= 'right '; // For backward compatibility
5042  }
5043 
5044  $sortorder = strtoupper($sortorder);
5045  $out = '';
5046  $sortimg = '';
5047 
5048  $tag = 'th';
5049  if ($thead == 2) {
5050  $tag = 'div';
5051  }
5052 
5053  $tmpsortfield = explode(',', $sortfield);
5054  $sortfield1 = trim($tmpsortfield[0]); // If $sortfield is 'd.datep,d.id', it becomes 'd.datep'
5055  $tmpfield = explode(',', $field);
5056  $field1 = trim($tmpfield[0]); // If $field is 'd.datep,d.id', it becomes 'd.datep'
5057 
5058  if (empty($conf->global->MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE) && empty($forcenowrapcolumntitle)) {
5059  $prefix = 'wrapcolumntitle '.$prefix;
5060  }
5061 
5062  //var_dump('field='.$field.' field1='.$field1.' sortfield='.$sortfield.' sortfield1='.$sortfield1);
5063  // If field is used as sort criteria we use a specific css class liste_titre_sel
5064  // Example if (sortfield,field)=("nom","xxx.nom") or (sortfield,field)=("nom","nom")
5065  $liste_titre = 'liste_titre';
5066  if ($field1 && ($sortfield1 == $field1 || $sortfield1 == preg_replace("/^[^\.]+\./", "", $field1))) {
5067  $liste_titre = 'liste_titre_sel';
5068  }
5069 
5070  $tagstart = '<'.$tag.' class="'.$prefix.$liste_titre.'" '.$moreattrib;
5071  //$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)).'"' : '');
5072  $tagstart .= ($name && empty($conf->global->MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE) && empty($forcenowrapcolumntitle) && !dol_textishtml($name)) ? ' title="'.dol_escape_htmltag($langs->trans($name)).'"' : '';
5073  $tagstart .= '>';
5074 
5075  if (empty($thead) && $field && empty($disablesortlink)) { // If this is a sort field
5076  $options = preg_replace('/sortfield=([a-zA-Z0-9,\s\.]+)/i', '', (is_scalar($moreparam) ? $moreparam : ''));
5077  $options = preg_replace('/sortorder=([a-zA-Z0-9,\s\.]+)/i', '', $options);
5078  $options = preg_replace('/&+/i', '&', $options);
5079  if (!preg_match('/^&/', $options)) {
5080  $options = '&'.$options;
5081  }
5082 
5083  $sortordertouseinlink = '';
5084  if ($field1 != $sortfield1) { // We are on another field than current sorted field
5085  if (preg_match('/^DESC/i', $sortorder)) {
5086  $sortordertouseinlink .= str_repeat('desc,', count(explode(',', $field)));
5087  } else { // We reverse the var $sortordertouseinlink
5088  $sortordertouseinlink .= str_repeat('asc,', count(explode(',', $field)));
5089  }
5090  } else { // We are on field that is the first current sorting criteria
5091  if (preg_match('/^ASC/i', $sortorder)) { // We reverse the var $sortordertouseinlink
5092  $sortordertouseinlink .= str_repeat('desc,', count(explode(',', $field)));
5093  } else {
5094  $sortordertouseinlink .= str_repeat('asc,', count(explode(',', $field)));
5095  }
5096  }
5097  $sortordertouseinlink = preg_replace('/,$/', '', $sortordertouseinlink);
5098  $out .= '<a class="reposition" href="'.$file.'?sortfield='.$field.'&sortorder='.$sortordertouseinlink.'&begin='.$begin.$options.'"';
5099  //$out .= (empty($conf->global->MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE) ? ' title="'.dol_escape_htmltag($langs->trans($name)).'"' : '');
5100  $out .= '>';
5101  }
5102  if ($tooltip) {
5103  // You can also use 'TranslationString:keyfortooltiponlick' for a tooltip on click.
5104  $tmptooltip = explode(':', $tooltip);
5105  $out .= $form->textwithpicto($langs->trans($name), $langs->trans($tmptooltip[0]), 1, 'help', '', 0, 3, (empty($tmptooltip[1]) ? '' : 'extra_'.str_replace('.', '_', $field).'_'.$tmptooltip[1]));
5106  } else {
5107  $out .= $langs->trans($name);
5108  }
5109 
5110  if (empty($thead) && $field && empty($disablesortlink)) { // If this is a sort field
5111  $out .= '</a>';
5112  }
5113 
5114  if (empty($thead) && $field) { // If this is a sort field
5115  $options = preg_replace('/sortfield=([a-zA-Z0-9,\s\.]+)/i', '', (is_scalar($moreparam) ? $moreparam : ''));
5116  $options = preg_replace('/sortorder=([a-zA-Z0-9,\s\.]+)/i', '', $options);
5117  $options = preg_replace('/&+/i', '&', $options);
5118  if (!preg_match('/^&/', $options)) {
5119  $options = '&'.$options;
5120  }
5121 
5122  if (!$sortorder || $field1 != $sortfield1) {
5123  //$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=asc&begin='.$begin.$options.'">'.img_down("A-Z",0).'</a>';
5124  //$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=desc&begin='.$begin.$options.'">'.img_up("Z-A",0).'</a>';
5125  } else {
5126  if (preg_match('/^DESC/', $sortorder)) {
5127  //$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=asc&begin='.$begin.$options.'">'.img_down("A-Z",0).'</a>';
5128  //$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=desc&begin='.$begin.$options.'">'.img_up("Z-A",1).'</a>';
5129  $sortimg .= '<span class="nowrap">'.img_up("Z-A", 0, 'paddingright').'</span>';
5130  }
5131  if (preg_match('/^ASC/', $sortorder)) {
5132  //$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=asc&begin='.$begin.$options.'">'.img_down("A-Z",1).'</a>';
5133  //$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=desc&begin='.$begin.$options.'">'.img_up("Z-A",0).'</a>';
5134  $sortimg .= '<span class="nowrap">'.img_down("A-Z", 0, 'paddingright').'</span>';
5135  }
5136  }
5137  }
5138 
5139  $tagend = '</'.$tag.'>';
5140 
5141  $out = $tagstart.$sortimg.$out.$tagend;
5142 
5143  return $out;
5144 }
5145 
5154 function print_titre($title)
5155 {
5156  dol_syslog(__FUNCTION__." is deprecated", LOG_WARNING);
5157 
5158  print '<div class="titre">'.$title.'</div>';
5159 }
5160 
5172 function print_fiche_titre($title, $mesg = '', $picto = 'generic', $pictoisfullpath = 0, $id = '')
5173 {
5174  print load_fiche_titre($title, $mesg, $picto, $pictoisfullpath, $id);
5175 }
5176 
5190 function load_fiche_titre($titre, $morehtmlright = '', $picto = 'generic', $pictoisfullpath = 0, $id = '', $morecssontable = '', $morehtmlcenter = '')
5191 {
5192  global $conf;
5193 
5194  $return = '';
5195 
5196  if ($picto == 'setup') {
5197  $picto = 'generic';
5198  }
5199 
5200  $return .= "\n";
5201  $return .= '<table '.($id ? 'id="'.$id.'" ' : '').'class="centpercent notopnoleftnoright table-fiche-title'.($morecssontable ? ' '.$morecssontable : '').'">'; // maring bottom must be same than into print_barre_list
5202  $return .= '<tr class="titre">';
5203  if ($picto) {
5204  $return .= '<td class="nobordernopadding widthpictotitle valignmiddle col-picto">'.img_picto('', $picto, 'class="valignmiddle widthpictotitle pictotitle"', $pictoisfullpath).'</td>';
5205  }
5206  $return .= '<td class="nobordernopadding valignmiddle col-title">';
5207  $return .= '<div class="titre inline-block">'.$titre.'</div>';
5208  $return .= '</td>';
5209  if (dol_strlen($morehtmlcenter)) {
5210  $return .= '<td class="nobordernopadding center valignmiddle">'.$morehtmlcenter.'</td>';
5211  }
5212  if (dol_strlen($morehtmlright)) {
5213  $return .= '<td class="nobordernopadding titre_right wordbreakimp right valignmiddle">'.$morehtmlright.'</td>';
5214  }
5215  $return .= '</tr></table>'."\n";
5216 
5217  return $return;
5218 }
5219 
5243 function print_barre_liste($titre, $page, $file, $options = '', $sortfield = '', $sortorder = '', $morehtmlcenter = '', $num = -1, $totalnboflines = '', $picto = 'generic', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '', $limit = -1, $hideselectlimit = 0, $hidenavigation = 0, $pagenavastextinput = 0, $morehtmlrightbeforearrow = '')
5244 {
5245  global $conf, $langs;
5246 
5247  $savlimit = $limit;
5248  $savtotalnboflines = $totalnboflines;
5249  $totalnboflines = abs((int) $totalnboflines);
5250 
5251  if ($picto == 'setup') {
5252  $picto = 'title_setup.png';
5253  }
5254  if (($conf->browser->name == 'ie') && $picto == 'generic') {
5255  $picto = 'title.gif';
5256  }
5257  if ($limit < 0) {
5258  $limit = $conf->liste_limit;
5259  }
5260  if ($savlimit != 0 && (($num > $limit) || ($num == -1) || ($limit == 0))) {
5261  $nextpage = 1;
5262  } else {
5263  $nextpage = 0;
5264  }
5265  //print 'totalnboflines='.$totalnboflines.'-savlimit='.$savlimit.'-limit='.$limit.'-num='.$num.'-nextpage='.$nextpage;
5266 
5267  print "\n";
5268  print "<!-- Begin title -->\n";
5269  print '<table class="centpercent notopnoleftnoright table-fiche-title'.($morecss ? ' '.$morecss : '').'"><tr>'; // maring bottom must be same than into load_fiche_tire
5270 
5271  // Left
5272 
5273  if ($picto && $titre) {
5274  print '<td class="nobordernopadding widthpictotitle valignmiddle col-picto">'.img_picto('', $picto, 'class="valignmiddle pictotitle widthpictotitle"', $pictoisfullpath).'</td>';
5275  }
5276  print '<td class="nobordernopadding valignmiddle col-title">';
5277  print '<div class="titre inline-block">'.$titre;
5278  if (!empty($titre) && $savtotalnboflines >= 0 && (string) $savtotalnboflines != '') {
5279  print '<span class="opacitymedium colorblack paddingleft">('.$totalnboflines.')</span>';
5280  }
5281  print '</div></td>';
5282 
5283  // Center
5284  if ($morehtmlcenter) {
5285  print '<td class="nobordernopadding center valignmiddle">'.$morehtmlcenter.'</td>';
5286  }
5287 
5288  // Right
5289  print '<td class="nobordernopadding valignmiddle right">';
5290  print '<input type="hidden" name="pageplusoneold" value="'.((int) $page + 1).'">';
5291  if ($sortfield) {
5292  $options .= "&sortfield=".urlencode($sortfield);
5293  }
5294  if ($sortorder) {
5295  $options .= "&sortorder=".urlencode($sortorder);
5296  }
5297  // Show navigation bar
5298  $pagelist = '';
5299  if ($savlimit != 0 && ($page > 0 || $num > $limit)) {
5300  if ($totalnboflines) { // If we know total nb of lines
5301  // Define nb of extra page links before and after selected page + ... + first or last
5302  $maxnbofpage = (empty($conf->dol_optimize_smallscreen) ? 4 : 0);
5303 
5304  if ($limit > 0) {
5305  $nbpages = ceil($totalnboflines / $limit);
5306  } else {
5307  $nbpages = 1;
5308  }
5309  $cpt = ($page - $maxnbofpage);
5310  if ($cpt < 0) {
5311  $cpt = 0;
5312  }
5313 
5314  if ($cpt >= 1) {
5315  if (empty($pagenavastextinput)) {
5316  $pagelist .= '<li class="pagination"><a href="'.$file.'?page=0'.$options.'">1</a></li>';
5317  if ($cpt > 2) {
5318  $pagelist .= '<li class="pagination"><span class="inactive">...</span></li>';
5319  } elseif ($cpt == 2) {
5320  $pagelist .= '<li class="pagination"><a href="'.$file.'?page=1'.$options.'">2</a></li>';
5321  }
5322  }
5323  }
5324 
5325  do {
5326  if ($pagenavastextinput) {
5327  if ($cpt == $page) {
5328  $pagelist .= '<li class="pagination"><input type="text" class="width25 center pageplusone" name="pageplusone" value="'.($page + 1).'"></li>';
5329  $pagelist .= '/';
5330  }
5331  } else {
5332  if ($cpt == $page) {
5333  $pagelist .= '<li class="pagination"><span class="active">'.($page + 1).'</span></li>';
5334  } else {
5335  $pagelist .= '<li class="pagination"><a href="'.$file.'?page='.$cpt.$options.'">'.($cpt + 1).'</a></li>';
5336  }
5337  }
5338  $cpt++;
5339  } while ($cpt < $nbpages && $cpt <= ($page + $maxnbofpage));
5340 
5341  if (empty($pagenavastextinput)) {
5342  if ($cpt < $nbpages) {
5343  if ($cpt < $nbpages - 2) {
5344  $pagelist .= '<li class="pagination"><span class="inactive">...</span></li>';
5345  } elseif ($cpt == $nbpages - 2) {
5346  $pagelist .= '<li class="pagination"><a href="'.$file.'?page='.($nbpages - 2).$options.'">'.($nbpages - 1).'</a></li>';
5347  }
5348  $pagelist .= '<li class="pagination"><a href="'.$file.'?page='.($nbpages - 1).$options.'">'.$nbpages.'</a></li>';
5349  }
5350  } else {
5351  //var_dump($page.' '.$cpt.' '.$nbpages);
5352  $pagelist .= '<li class="pagination paginationlastpage"><a href="'.$file.'?page='.($nbpages - 1).$options.'">'.$nbpages.'</a></li>';
5353  }
5354  } else {
5355  $pagelist .= '<li class="pagination"><span class="active">'.($page + 1)."</li>";
5356  }
5357  }
5358 
5359  if ($savlimit || $morehtmlright || $morehtmlrightbeforearrow) {
5360  print_fleche_navigation($page, $file, $options, $nextpage, $pagelist, $morehtmlright, $savlimit, $totalnboflines, $hideselectlimit, $morehtmlrightbeforearrow); // output the div and ul for previous/last completed with page numbers into $pagelist
5361  }
5362 
5363  // js to autoselect page field on focus
5364  if ($pagenavastextinput) {
5365  print ajax_autoselect('.pageplusone');
5366  }
5367 
5368  print '</td>';
5369 
5370  print '</tr></table>'."\n";
5371  print "<!-- End title -->\n\n";
5372 }
5373 
5389 function print_fleche_navigation($page, $file, $options = '', $nextpage = 0, $betweenarrows = '', $afterarrows = '', $limit = -1, $totalnboflines = 0, $hideselectlimit = 0, $beforearrows = '')
5390 {
5391  global $conf, $langs;
5392 
5393  print '<div class="pagination"><ul>';
5394  if ($beforearrows) {
5395  print '<li class="paginationbeforearrows">';
5396  print $beforearrows;
5397  print '</li>';
5398  }
5399  if ((int) $limit > 0 && empty($hideselectlimit)) {
5400  $pagesizechoices = '10:10,15:15,20:20,30:30,40:40,50:50,100:100,250:250,500:500,1000:1000';
5401  $pagesizechoices .= ',5000:5000,10000:10000,20000:20000';
5402  //$pagesizechoices.=',0:'.$langs->trans("All"); // Not yet supported
5403  //$pagesizechoices.=',2:2';
5404  if (!empty($conf->global->MAIN_PAGESIZE_CHOICES)) {
5405  $pagesizechoices = $conf->global->MAIN_PAGESIZE_CHOICES;
5406  }
5407 
5408  print '<li class="pagination">';
5409  print '<select class="flat selectlimit" name="limit" title="'.dol_escape_htmltag($langs->trans("MaxNbOfRecordPerPage")).'">';
5410  $tmpchoice = explode(',', $pagesizechoices);
5411  $tmpkey = $limit.':'.$limit;
5412  if (!in_array($tmpkey, $tmpchoice)) {
5413  $tmpchoice[] = $tmpkey;
5414  }
5415  $tmpkey = $conf->liste_limit.':'.$conf->liste_limit;
5416  if (!in_array($tmpkey, $tmpchoice)) {
5417  $tmpchoice[] = $tmpkey;
5418  }
5419  asort($tmpchoice, SORT_NUMERIC);
5420  foreach ($tmpchoice as $val) {
5421  $selected = '';
5422  $tmp = explode(':', $val);
5423  $key = $tmp[0];
5424  $val = $tmp[1];
5425  if ($key != '' && $val != '') {
5426  if ((int) $key == (int) $limit) {
5427  $selected = ' selected="selected"';
5428  }
5429  print '<option name="'.$key.'"'.$selected.'>'.dol_escape_htmltag($val).'</option>'."\n";
5430  }
5431  }
5432  print '</select>';
5433  if ($conf->use_javascript_ajax) {
5434  print '<!-- JS CODE TO ENABLE select limit to launch submit of page -->
5435  <script>
5436  jQuery(document).ready(function () {
5437  jQuery(".selectlimit").change(function() {
5438  console.log("Change limit. Send submit");
5439  $(this).parents(\'form:first\').submit();
5440  });
5441  });
5442  </script>
5443  ';
5444  }
5445  print '</li>';
5446  }
5447  if ($page > 0) {
5448  print '<li class="pagination paginationpage paginationpageleft"><a class="paginationprevious" href="'.$file.'?page='.($page - 1).$options.'"><i class="fa fa-chevron-left" title="'.dol_escape_htmltag($langs->trans("Previous")).'"></i></a></li>';
5449  }
5450  if ($betweenarrows) {
5451  print '<!--<div class="betweenarrows nowraponall inline-block">-->';
5452  print $betweenarrows;
5453  print '<!--</div>-->';
5454  }
5455  if ($nextpage > 0) {
5456  print '<li class="pagination paginationpage paginationpageright"><a class="paginationnext" href="'.$file.'?page='.($page + 1).$options.'"><i class="fa fa-chevron-right" title="'.dol_escape_htmltag($langs->trans("Next")).'"></i></a></li>';
5457  }
5458  if ($afterarrows) {
5459  print '<li class="paginationafterarrows">';
5460  print $afterarrows;
5461  print '</li>';
5462  }
5463  print '</ul></div>'."\n";
5464 }
5465 
5466 
5478 function vatrate($rate, $addpercent = false, $info_bits = 0, $usestarfornpr = 0, $html = 0)
5479 {
5480  $morelabel = '';
5481 
5482  if (preg_match('/%/', $rate)) {
5483  $rate = str_replace('%', '', $rate);
5484  $addpercent = true;
5485  }
5486  $reg = array();
5487  if (preg_match('/\((.*)\)/', $rate, $reg)) {
5488  $morelabel = ' ('.$reg[1].')';
5489  $rate = preg_replace('/\s*'.preg_quote($morelabel, '/').'/', '', $rate);
5490  $morelabel = ' '.($html ? '<span class="opacitymedium">' : '').'('.$reg[1].')'.($html ? '</span>' : '');
5491  }
5492  if (preg_match('/\*/', $rate)) {
5493  $rate = str_replace('*', '', $rate);
5494  $info_bits |= 1;
5495  }
5496 
5497  // If rate is '9/9/9' we don't change it. If rate is '9.000' we apply price()
5498  if (!preg_match('/\//', $rate)) {
5499  $ret = price($rate, 0, '', 0, 0).($addpercent ? '%' : '');
5500  } else {
5501  // TODO Split on / and output with a price2num to have clean numbers without ton of 000.
5502  $ret = $rate.($addpercent ? '%' : '');
5503  }
5504  if (($info_bits & 1) && $usestarfornpr >= 0) {
5505  $ret .= ' *';
5506  }
5507  $ret .= $morelabel;
5508  return $ret;
5509 }
5510 
5511 
5527 function price($amount, $form = 0, $outlangs = '', $trunc = 1, $rounding = -1, $forcerounding = -1, $currency_code = '')
5528 {
5529  global $langs, $conf;
5530 
5531  // Clean parameters
5532  if (empty($amount)) {
5533  $amount = 0; // To have a numeric value if amount not defined or = ''
5534  }
5535  $amount = (is_numeric($amount) ? $amount : 0); // Check if amount is numeric, for example, an error occured when amount value = o (letter) instead 0 (number)
5536  if ($rounding < 0) {
5537  $rounding = min($conf->global->MAIN_MAX_DECIMALS_UNIT, $conf->global->MAIN_MAX_DECIMALS_TOT);
5538  }
5539  $nbdecimal = $rounding;
5540 
5541  if ($outlangs === 'none') {
5542  // Use international separators
5543  $dec = '.';
5544  $thousand = '';
5545  } else {
5546  // Output separators by default (french)
5547  $dec = ',';
5548  $thousand = ' ';
5549 
5550  // If $outlangs not forced, we use use language
5551  if (!is_object($outlangs)) {
5552  $outlangs = $langs;
5553  }
5554 
5555  if ($outlangs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal") {
5556  $dec = $outlangs->transnoentitiesnoconv("SeparatorDecimal");
5557  }
5558  if ($outlangs->transnoentitiesnoconv("SeparatorThousand") != "SeparatorThousand") {
5559  $thousand = $outlangs->transnoentitiesnoconv("SeparatorThousand");
5560  }
5561  if ($thousand == 'None') {
5562  $thousand = '';
5563  } elseif ($thousand == 'Space') {
5564  $thousand = ' ';
5565  }
5566  }
5567  //print "outlangs=".$outlangs->defaultlang." amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
5568 
5569  //print "amount=".$amount."-";
5570  $amount = str_replace(',', '.', $amount); // should be useless
5571  //print $amount."-";
5572  $datas = explode('.', $amount);
5573  $decpart = isset($datas[1]) ? $datas[1] : '';
5574  $decpart = preg_replace('/0+$/i', '', $decpart); // Supprime les 0 de fin de partie decimale
5575  //print "decpart=".$decpart."<br>";
5576  $end = '';
5577 
5578  // We increase nbdecimal if there is more decimal than asked (to not loose information)
5579  if (dol_strlen($decpart) > $nbdecimal) {
5580  $nbdecimal = dol_strlen($decpart);
5581  }
5582  // Si on depasse max
5583  if ($trunc && $nbdecimal > $conf->global->MAIN_MAX_DECIMALS_SHOWN) {
5584  $nbdecimal = $conf->global->MAIN_MAX_DECIMALS_SHOWN;
5585  if (preg_match('/\.\.\./i', $conf->global->MAIN_MAX_DECIMALS_SHOWN)) {
5586  // Si un affichage est tronque, on montre des ...
5587  $end = '...';
5588  }
5589  }
5590 
5591  // If force rounding
5592  if ($forcerounding >= 0) {
5593  $nbdecimal = $forcerounding;
5594  }
5595 
5596  // Format number
5597  $output = number_format($amount, $nbdecimal, $dec, $thousand);
5598  if ($form) {
5599  $output = preg_replace('/\s/', '&nbsp;', $output);
5600  $output = preg_replace('/\'/', '&#039;', $output);
5601  }
5602  // Add symbol of currency if requested
5603  $cursymbolbefore = $cursymbolafter = '';
5604  if ($currency_code && is_object($outlangs)) {
5605  if ($currency_code == 'auto') {
5606  $currency_code = $conf->currency;
5607  }
5608 
5609  $listofcurrenciesbefore = array('AUD', 'CAD', 'CNY', 'COP', 'CLP', 'GBP', 'HKD', 'MXN', 'PEN', 'USD');
5610  $listoflanguagesbefore = array('nl_NL');
5611  if (in_array($currency_code, $listofcurrenciesbefore) || in_array($outlangs->defaultlang, $listoflanguagesbefore)) {
5612  $cursymbolbefore .= $outlangs->getCurrencySymbol($currency_code);
5613  } else {
5614  $tmpcur = $outlangs->getCurrencySymbol($currency_code);
5615  $cursymbolafter .= ($tmpcur == $currency_code ? ' '.$tmpcur : $tmpcur);
5616  }
5617  }
5618  $output = $cursymbolbefore.$output.$end.($cursymbolafter ? ' ' : '').$cursymbolafter;
5619 
5620  return $output;
5621 }
5622 
5647 function price2num($amount, $rounding = '', $option = 0)
5648 {
5649  global $langs, $conf;
5650 
5651  // Round PHP function does not allow number like '1,234.56' nor '1.234,56' nor '1 234,56'
5652  // Numbers must be '1234.56'
5653  // Decimal delimiter for PHP and database SQL requests must be '.'
5654  $dec = ',';
5655  $thousand = ' ';
5656  if (is_null($langs)) { // $langs is not defined, we use english values.
5657  $dec = '.';
5658  $thousand = ',';
5659  } else {
5660  if ($langs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal") {
5661  $dec = $langs->transnoentitiesnoconv("SeparatorDecimal");
5662  }
5663  if ($langs->transnoentitiesnoconv("SeparatorThousand") != "SeparatorThousand") {
5664  $thousand = $langs->transnoentitiesnoconv("SeparatorThousand");
5665  }
5666  }
5667  if ($thousand == 'None') {
5668  $thousand = '';
5669  } elseif ($thousand == 'Space') {
5670  $thousand = ' ';
5671  }
5672  //print "amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
5673 
5674  // Convert value to universal number format (no thousand separator, '.' as decimal separator)
5675  if ($option != 1) { // If not a PHP number or unknown, we change or clean format
5676  //print "\n".'PP'.$amount.' - '.$dec.' - '.$thousand.' - '.intval($amount).'<br>';
5677  if (!is_numeric($amount)) {
5678  $amount = preg_replace('/[a-zA-Z\/\\\*\(\)<>\_]/', '', $amount);
5679  }
5680 
5681  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
5682  $amount = str_replace($thousand, '', $amount);
5683  }
5684 
5685  // Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
5686  // to format defined by LC_NUMERIC after a calculation and we want source format to be like defined by Dolibarr setup.
5687  // So if number was already a good number, it is converted into local Dolibarr setup.
5688  if (is_numeric($amount)) {
5689  // We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
5690  $temps = sprintf("%0.10F", $amount - intval($amount)); // temps=0.0000000000 or 0.0000200000 or 9999.1000000000
5691  $temps = preg_replace('/([\.1-9])0+$/', '\\1', $temps); // temps=0. or 0.00002 or 9999.1
5692  $nbofdec = max(0, dol_strlen($temps) - 2); // -2 to remove "0."
5693  $amount = number_format($amount, $nbofdec, $dec, $thousand);
5694  }
5695  //print "QQ".$amount."<br>\n";
5696 
5697  // Now make replace (the main goal of function)
5698  if ($thousand != ',' && $thousand != '.') {
5699  $amount = str_replace(',', '.', $amount); // To accept 2 notations for french users
5700  }
5701 
5702  $amount = str_replace(' ', '', $amount); // To avoid spaces
5703  $amount = str_replace($thousand, '', $amount); // Replace of thousand before replace of dec to avoid pb if thousand is .
5704  $amount = str_replace($dec, '.', $amount);
5705 
5706  $amount = preg_replace('/[^0-9\-\.]/', '', $amount); // Clean non numeric chars (so it clean some UTF8 spaces for example.
5707  }
5708  //print ' XX'.$amount.' '.$rounding;
5709 
5710  // Now, $amount is a real PHP float number. We make a rounding if required.
5711  if ($rounding) {
5712  $nbofdectoround = '';
5713  if ($rounding == 'MU') {
5714  $nbofdectoround = $conf->global->MAIN_MAX_DECIMALS_UNIT;
5715  } elseif ($rounding == 'MT') {
5716  $nbofdectoround = $conf->global->MAIN_MAX_DECIMALS_TOT;
5717  } elseif ($rounding == 'MS') {
5718  $nbofdectoround = isset($conf->global->MAIN_MAX_DECIMALS_STOCK) ? $conf->global->MAIN_MAX_DECIMALS_STOCK : 5;
5719  } elseif ($rounding == 'CU') {
5720  $nbofdectoround = max($conf->global->MAIN_MAX_DECIMALS_UNIT, 8); // TODO Use param of currency
5721  } elseif ($rounding == 'CT') {
5722  $nbofdectoround = max($conf->global->MAIN_MAX_DECIMALS_TOT, 8); // TODO Use param of currency
5723  } elseif (is_numeric($rounding)) {
5724  $nbofdectoround = (int) $rounding;
5725  }
5726 
5727  //print " RR".$amount.' - '.$nbofdectoround.'<br>';
5728  if (dol_strlen($nbofdectoround)) {
5729  $amount = round(is_string($amount) ? (float) $amount : $amount, $nbofdectoround); // $nbofdectoround can be 0.
5730  } else {
5731  return 'ErrorBadParameterProvidedToFunction';
5732  }
5733  //print ' SS'.$amount.' - '.$nbofdec.' - '.$dec.' - '.$thousand.' - '.$nbofdectoround.'<br>';
5734 
5735  // Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
5736  // to format defined by LC_NUMERIC after a calculation and we want source format to be defined by Dolibarr setup.
5737  if (is_numeric($amount)) {
5738  // We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
5739  $temps = sprintf("%0.10F", $amount - intval($amount)); // temps=0.0000000000 or 0.0000200000 or 9999.1000000000
5740  $temps = preg_replace('/([\.1-9])0+$/', '\\1', $temps); // temps=0. or 0.00002 or 9999.1
5741  $nbofdec = max(0, dol_strlen($temps) - 2); // -2 to remove "0."
5742  $amount = number_format($amount, min($nbofdec, $nbofdectoround), $dec, $thousand); // Convert amount to format with dolibarr dec and thousand
5743  }
5744  //print "TT".$amount.'<br>';
5745 
5746  // Always make replace because each math function (like round) replace
5747  // with local values and we want a number that has a SQL string format x.y
5748  if ($thousand != ',' && $thousand != '.') {
5749  $amount = str_replace(',', '.', $amount); // To accept 2 notations for french users
5750  }
5751 
5752  $amount = str_replace(' ', '', $amount); // To avoid spaces
5753  $amount = str_replace($thousand, '', $amount); // Replace of thousand before replace of dec to avoid pb if thousand is .
5754  $amount = str_replace($dec, '.', $amount);
5755 
5756  $amount = preg_replace('/[^0-9\-\.]/', '', $amount); // Clean non numeric chars (so it clean some UTF8 spaces for example.
5757  }
5758 
5759  return $amount;
5760 }
5761 
5774 function showDimensionInBestUnit($dimension, $unit, $type, $outputlangs, $round = -1, $forceunitoutput = 'no', $use_short_label = 0)
5775 {
5776  require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
5777 
5778  if (($forceunitoutput == 'no' && $dimension < 1 / 10000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == -6)) {
5779  $dimension = $dimension * 1000000;
5780  $unit = $unit - 6;
5781  } elseif (($forceunitoutput == 'no' && $dimension < 1 / 10 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == -3)) {
5782  $dimension = $dimension * 1000;
5783  $unit = $unit - 3;
5784  } elseif (($forceunitoutput == 'no' && $dimension > 100000000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == 6)) {
5785  $dimension = $dimension / 1000000;
5786  $unit = $unit + 6;
5787  } elseif (($forceunitoutput == 'no' && $dimension > 100000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == 3)) {
5788  $dimension = $dimension / 1000;
5789  $unit = $unit + 3;
5790  }
5791  // Special case when we want output unit into pound or ounce
5792  /* TODO
5793  if ($unit < 90 && $type == 'weight' && is_numeric($forceunitoutput) && (($forceunitoutput == 98) || ($forceunitoutput == 99))
5794  {
5795  $dimension = // convert dimension from standard unit into ounce or pound
5796  $unit = $forceunitoutput;
5797  }
5798  if ($unit > 90 && $type == 'weight' && is_numeric($forceunitoutput) && $forceunitoutput < 90)
5799  {
5800  $dimension = // convert dimension from standard unit into ounce or pound
5801  $unit = $forceunitoutput;
5802  }*/
5803 
5804  $ret = price($dimension, 0, $outputlangs, 0, 0, $round);
5805  $ret .= ' '.measuringUnitString(0, $type, $unit, $use_short_label, $outputlangs);
5806 
5807  return $ret;
5808 }
5809 
5810 
5823 function get_localtax($vatrate, $local, $thirdparty_buyer = "", $thirdparty_seller = "", $vatnpr = 0)
5824 {
5825  global $db, $conf, $mysoc;
5826 
5827  if (empty($thirdparty_seller) || !is_object($thirdparty_seller)) {
5828  $thirdparty_seller = $mysoc;
5829  }
5830 
5831  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);
5832 
5833  $vatratecleaned = $vatrate;
5834  $reg = array();
5835  if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg)) { // If vat is "xx (yy)"
5836  $vatratecleaned = trim($reg[1]);
5837  $vatratecode = $reg[2];
5838  }
5839 
5840  /*if ($thirdparty_buyer->country_code != $thirdparty_seller->country_code)
5841  {
5842  return 0;
5843  }*/
5844 
5845  // Some test to guess with no need to make database access
5846  if ($mysoc->country_code == 'ES') { // For spain localtaxes 1 and 2, tax is qualified if buyer use local tax
5847  if ($local == 1) {
5848  if (!$mysoc->localtax1_assuj || (string) $vatratecleaned == "0") {
5849  return 0;
5850  }
5851  if ($thirdparty_seller->id == $mysoc->id) {
5852  if (!$thirdparty_buyer->localtax1_assuj) {
5853  return 0;
5854  }
5855  } else {
5856  if (!$thirdparty_seller->localtax1_assuj) {
5857  return 0;
5858  }
5859  }
5860  }
5861 
5862  if ($local == 2) {
5863  //if (! $mysoc->localtax2_assuj || (string) $vatratecleaned == "0") return 0;
5864  if (!$mysoc->localtax2_assuj) {
5865  return 0; // If main vat is 0, IRPF may be different than 0.
5866  }
5867  if ($thirdparty_seller->id == $mysoc->id) {
5868  if (!$thirdparty_buyer->localtax2_assuj) {
5869  return 0;
5870  }
5871  } else {
5872  if (!$thirdparty_seller->localtax2_assuj) {
5873  return 0;
5874  }
5875  }
5876  }
5877  } else {
5878  if ($local == 1 && !$thirdparty_seller->localtax1_assuj) {
5879  return 0;
5880  }
5881  if ($local == 2 && !$thirdparty_seller->localtax2_assuj) {
5882  return 0;
5883  }
5884  }
5885 
5886  // For some country MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY is forced to on.
5887  if (in_array($mysoc->country_code, array('ES'))) {
5888  $conf->global->MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY = 1;
5889  }
5890 
5891  // Search local taxes
5892  if (!empty($conf->global->MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY)) {
5893  if ($local == 1) {
5894  if ($thirdparty_seller != $mysoc) {
5895  if (!isOnlyOneLocalTax($local)) { // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
5896  return $thirdparty_seller->localtax1_value;
5897  }
5898  } else { // i am the seller
5899  if (!isOnlyOneLocalTax($local)) { // TODO If seller is me, why not always returning this, even if there is only one locatax vat.
5900  return $conf->global->MAIN_INFO_VALUE_LOCALTAX1;
5901  }
5902  }
5903  }
5904  if ($local == 2) {
5905  if ($thirdparty_seller != $mysoc) {
5906  if (!isOnlyOneLocalTax($local)) { // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
5907  // TODO We should also return value defined on thirdparty only if defined
5908  return $thirdparty_seller->localtax2_value;
5909  }
5910  } else { // i am the seller
5911  if (in_array($mysoc->country_code, array('ES'))) {
5912  return $thirdparty_buyer->localtax2_value;
5913  } else {
5914  return $conf->global->MAIN_INFO_VALUE_LOCALTAX2;
5915  }
5916  }
5917  }
5918  }
5919 
5920  // By default, search value of local tax on line of common tax
5921  $sql = "SELECT t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
5922  $sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
5923  $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($thirdparty_seller->country_code)."'";
5924  $sql .= " AND t.taux = ".((float) $vatratecleaned)." AND t.active = 1";
5925  if (!empty($vatratecode)) {
5926  $sql .= " AND t.code ='".$db->escape($vatratecode)."'"; // If we have the code, we use it in priority
5927  } else {
5928  $sql .= " AND t.recuperableonly = '".$db->escape($vatnpr)."'";
5929  }
5930 
5931  $resql = $db->query($sql);
5932 
5933  if ($resql) {
5934  $obj = $db->fetch_object($resql);
5935  if ($obj) {
5936  if ($local == 1) {
5937  return $obj->localtax1;
5938  } elseif ($local == 2) {
5939  return $obj->localtax2;
5940  }
5941  }
5942  }
5943 
5944  return 0;
5945 }
5946 
5947 
5956 function isOnlyOneLocalTax($local)
5957 {
5958  $tax = get_localtax_by_third($local);
5959 
5960  $valors = explode(":", $tax);
5961 
5962  if (count($valors) > 1) {
5963  return false;
5964  } else {
5965  return true;
5966  }
5967 }
5968 
5975 function get_localtax_by_third($local)
5976 {
5977  global $db, $mysoc;
5978 
5979  $sql = " SELECT t.localtax".$local." as localtax";
5980  $sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t INNER JOIN ".MAIN_DB_PREFIX."c_country as c ON c.rowid = t.fk_pays";
5981  $sql .= " WHERE c.code = '".$db->escape($mysoc->country_code)."' AND t.active = 1 AND t.taux = (";
5982  $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";
5983  $sql .= " WHERE c.code = '".$db->escape($mysoc->country_code)."' AND tt.active = 1)";
5984  $sql .= " AND t.localtax".$local."_type <> '0'";
5985  $sql .= " ORDER BY t.rowid DESC";
5986 
5987  $resql = $db->query($sql);
5988  if ($resql) {
5989  $obj = $db->fetch_object($resql);
5990  return $obj->localtax;
5991  } else {
5992  return 'Error';
5993  }
5994 
5995  return '0';
5996 }
5997 
5998 
6010 function getTaxesFromId($vatrate, $buyer = null, $seller = null, $firstparamisid = 1)
6011 {
6012  global $db, $mysoc;
6013 
6014  dol_syslog("getTaxesFromId vat id or rate = ".$vatrate);
6015 
6016  // Search local taxes
6017  $sql = "SELECT t.rowid, t.code, t.taux as rate, t.recuperableonly as npr, t.accountancy_code_sell, t.accountancy_code_buy,";
6018  $sql .= " t.localtax1, t.localtax1_type, t.localtax2, t.localtax2_type";
6019  $sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t";
6020  if ($firstparamisid) {
6021  $sql .= " WHERE t.rowid = ".(int) $vatrate;
6022  } else {
6023  $vatratecleaned = $vatrate;
6024  $vatratecode = '';
6025  $reg = array();
6026  if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg)) { // If vat is "xx (yy)"
6027  $vatratecleaned = $reg[1];
6028  $vatratecode = $reg[2];
6029  }
6030 
6031  $sql .= ", ".MAIN_DB_PREFIX."c_country as c";
6032  /*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 ??
6033  else $sql.= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($seller->country_code)."'";*/
6034  $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($seller->country_code)."'";
6035  $sql .= " AND t.taux = ".((float) $vatratecleaned)." AND t.active = 1";
6036  if ($vatratecode) {
6037  $sql .= " AND t.code = '".$db->escape($vatratecode)."'";
6038  }
6039  }
6040 
6041  $resql = $db->query($sql);
6042  if ($resql) {
6043  $obj = $db->fetch_object($resql);
6044  if ($obj) {
6045  return array(
6046  'rowid'=>$obj->rowid,
6047  'code'=>$obj->code,
6048  'rate'=>$obj->rate,
6049  'localtax1'=>$obj->localtax1,
6050  'localtax1_type'=>$obj->localtax1_type,
6051  'localtax2'=>$obj->localtax2,
6052  'localtax2_type'=>$obj->localtax2_type,
6053  'npr'=>$obj->npr,
6054  'accountancy_code_sell'=>$obj->accountancy_code_sell,
6055  'accountancy_code_buy'=>$obj->accountancy_code_buy
6056  );
6057  } else {
6058  return array();
6059  }
6060  } else {
6061  dol_print_error($db);
6062  }
6063 
6064  return array();
6065 }
6066 
6083 function getLocalTaxesFromRate($vatrate, $local, $buyer, $seller, $firstparamisid = 0)
6084 {
6085  global $db, $mysoc;
6086 
6087  dol_syslog("getLocalTaxesFromRate vatrate=".$vatrate." local=".$local);
6088 
6089  // Search local taxes
6090  $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";
6091  $sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t";
6092  if ($firstparamisid) {
6093  $sql .= " WHERE t.rowid = ".(int) $vatrate;
6094  } else {
6095  $vatratecleaned = $vatrate;
6096  $vatratecode = '';
6097  $reg = array();
6098  if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg)) { // If vat is "x.x (yy)"
6099  $vatratecleaned = $reg[1];
6100  $vatratecode = $reg[2];
6101  }
6102 
6103  $sql .= ", ".MAIN_DB_PREFIX."c_country as c";
6104  if (!empty($mysoc) && $mysoc->country_code == 'ES') {
6105  $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($buyer->country_code)."'"; // local tax in spain use the buyer country ??
6106  } else {
6107  $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape(empty($seller->country_code) ? $mysoc->country_code : $seller->country_code)."'";
6108  }
6109  $sql .= " AND t.taux = ".((float) $vatratecleaned)." AND t.active = 1";
6110  if ($vatratecode) {
6111  $sql .= " AND t.code = '".$db->escape($vatratecode)."'";
6112  }
6113  }
6114 
6115  $resql = $db->query($sql);
6116  if ($resql) {
6117  $obj = $db->fetch_object($resql);
6118 
6119  if ($obj) {
6120  $vateratestring = $obj->rate.($obj->code ? ' ('.$obj->code.')' : '');
6121 
6122  if ($local == 1) {
6123  return array($obj->localtax1_type, get_localtax($vateratestring, $local, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
6124  } elseif ($local == 2) {
6125  return array($obj->localtax2_type, get_localtax($vateratestring, $local, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
6126  } else {
6127  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);
6128  }
6129  }
6130  }
6131 
6132  return array();
6133 }
6134 
6145 function get_product_vat_for_country($idprod, $thirdpartytouse, $idprodfournprice = 0)
6146 {
6147  global $db, $conf, $mysoc;
6148 
6149  require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
6150 
6151  $ret = 0;
6152  $found = 0;
6153 
6154  if ($idprod > 0) {
6155  // Load product
6156  $product = new Product($db);
6157  $result = $product->fetch($idprod);
6158 
6159  if ($mysoc->country_code == $thirdpartytouse->country_code) { // If country to consider is ours
6160  if ($idprodfournprice > 0) { // We want vat for product for a "supplier" object
6161  $product->get_buyprice($idprodfournprice, 0, 0, 0);
6162  $ret = $product->vatrate_supplier;
6163  if ($product->default_vat_code) {
6164  $ret .= ' ('.$product->default_vat_code.')';
6165  }
6166  } else {
6167  $ret = $product->tva_tx; // Default vat of product we defined
6168  if ($product->default_vat_code) {
6169  $ret .= ' ('.$product->default_vat_code.')';
6170  }
6171  }
6172  $found = 1;
6173  } else {
6174  // TODO Read default product vat according to product and another countrycode.
6175  // Vat for couple anothercountrycode/product is data that is not managed and store yet, so we will fallback on next rule.
6176  }
6177  }
6178 
6179  if (!$found) {
6180  if (empty($conf->global->MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS)) {
6181  // If vat of product for the country not found or not defined, we return the first higher vat of country.
6182  $sql = "SELECT t.taux as vat_rate, t.code as default_vat_code";
6183  $sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
6184  $sql .= " WHERE t.active=1 AND t.fk_pays = c.rowid AND c.code='".$db->escape($thirdpartytouse->country_code)."'";
6185  $sql .= " ORDER BY t.taux DESC, t.code ASC, t.recuperableonly ASC";
6186  $sql .= $db->plimit(1);
6187 
6188  $resql = $db->query($sql);
6189  if ($resql) {
6190  $obj = $db->fetch_object($resql);
6191  if ($obj) {
6192  $ret = $obj->vat_rate;
6193  if ($obj->default_vat_code) {
6194  $ret .= ' ('.$obj->default_vat_code.')';
6195  }
6196  }
6197  $db->free($resql);
6198  } else {
6199  dol_print_error($db);
6200  }
6201  } else {
6202  $ret = $conf->global->MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS; // Forced value if autodetect fails
6203  }
6204  }
6205 
6206  dol_syslog("get_product_vat_for_country: ret=".$ret);
6207  return $ret;
6208 }
6209 
6219 function get_product_localtax_for_country($idprod, $local, $thirdpartytouse)
6220 {
6221  global $db, $mysoc;
6222 
6223  if (!class_exists('Product')) {
6224  require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
6225  }
6226 
6227  $ret = 0;
6228  $found = 0;
6229 
6230  if ($idprod > 0) {
6231  // Load product
6232  $product = new Product($db);
6233  $result = $product->fetch($idprod);
6234 
6235  if ($mysoc->country_code == $thirdpartytouse->country_code) { // If selling country is ours
6236  /* Not defined yet, so we don't use this
6237  if ($local==1) $ret=$product->localtax1_tx;
6238  elseif ($local==2) $ret=$product->localtax2_tx;
6239  $found=1;
6240  */
6241  } else {
6242  // TODO Read default product vat according to product and another countrycode.
6243  // Vat for couple anothercountrycode/product is data that is not managed and store yet, so we will fallback on next rule.
6244  }
6245  }
6246 
6247  if (!$found) {
6248  // If vat of product for the country not found or not defined, we return higher vat of country.
6249  $sql = "SELECT taux as vat_rate, localtax1, localtax2";
6250  $sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
6251  $sql .= " WHERE t.active=1 AND t.fk_pays = c.rowid AND c.code='".$db->escape($thirdpartytouse->country_code)."'";
6252  $sql .= " ORDER BY t.taux DESC, t.recuperableonly ASC";
6253  $sql .= $db->plimit(1);
6254 
6255  $resql = $db->query($sql);
6256  if ($resql) {
6257  $obj = $db->fetch_object($resql);
6258  if ($obj) {
6259  if ($local == 1) {
6260  $ret = $obj->localtax1;
6261  } elseif ($local == 2) {
6262  $ret = $obj->localtax2;
6263  }
6264  }
6265  } else {
6266  dol_print_error($db);
6267  }
6268  }
6269 
6270  dol_syslog("get_product_localtax_for_country: ret=".$ret);
6271  return $ret;
6272 }
6273 
6290 function get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0)
6291 {
6292  global $conf;
6293 
6294  require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
6295 
6296  // Note: possible values for tva_assuj are 0/1 or franchise/reel
6297  $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;
6298 
6299  $seller_country_code = $thirdparty_seller->country_code;
6300  $seller_in_cee = isInEEC($thirdparty_seller);
6301 
6302  $buyer_country_code = $thirdparty_buyer->country_code;
6303  $buyer_in_cee = isInEEC($thirdparty_buyer);
6304 
6305  dol_syslog("get_default_tva: seller use vat=".$seller_use_vat.", seller country=".$seller_country_code.", seller in cee=".$seller_in_cee.", buyer vat number=".$thirdparty_buyer->tva_intra." buyer country=".$buyer_country_code.", buyer in cee=".$buyer_in_cee.", idprod=".$idprod.", idprodfournprice=".$idprodfournprice.", SERVICE_ARE_ECOMMERCE_200238EC=".(!empty($conf->global->SERVICES_ARE_ECOMMERCE_200238EC) ? $conf->global->SERVICES_ARE_ECOMMERCE_200238EC : ''));
6306 
6307  // 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)
6308  // we use the buyer VAT.
6309  if (!empty($conf->global->SERVICE_ARE_ECOMMERCE_200238EC)) {
6310  if ($seller_in_cee && $buyer_in_cee) {
6311  $isacompany = $thirdparty_buyer->isACompany();
6312  if ($isacompany && !empty($conf->global->MAIN_USE_VAT_COMPANIES_IN_EEC_WITH_INVALID_VAT_ID_ARE_INDIVIDUAL)) {
6313  require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
6314  if (!isValidVATID($thirdparty_buyer)) {
6315  $isacompany = 0;
6316  }
6317  }
6318 
6319  if (!$isacompany) {
6320  //print 'VATRULE 0';
6321  return get_product_vat_for_country($idprod, $thirdparty_buyer, $idprodfournprice);
6322  }
6323  }
6324  }
6325 
6326  // If seller does not use VAT
6327  if (!$seller_use_vat) {
6328  //print 'VATRULE 1';
6329  return 0;
6330  }
6331 
6332  // Le test ci-dessus ne devrait pas etre necessaire. Me signaler l'exemple du cas juridique concerne si le test suivant n'est pas suffisant.
6333 
6334  // Si le (pays vendeur = pays acheteur) alors la TVA par defaut=TVA du produit vendu. Fin de regle.
6335  if (($seller_country_code == $buyer_country_code)
6336  || (in_array($seller_country_code, array('FR', 'MC')) && in_array($buyer_country_code, array('FR', 'MC')))) { // Warning ->country_code not always defined
6337  //print 'VATRULE 2';
6338  return get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
6339  }
6340 
6341  // Si (vendeur et acheteur dans Communaute europeenne) et (bien vendu = moyen de transports neuf comme auto, bateau, avion) alors TVA par defaut=0 (La TVA doit etre paye par l'acheteur au centre d'impots de son pays et non au vendeur). Fin de regle.
6342  // 'VATRULE 3' - Not supported
6343 
6344  // Si (vendeur et acheteur dans Communaute europeenne) et (acheteur = entreprise) alors TVA par defaut=0. Fin de regle
6345  // Si (vendeur et acheteur dans Communaute europeenne) et (acheteur = particulier) alors TVA par defaut=TVA du produit vendu. Fin de regle
6346  if (($seller_in_cee && $buyer_in_cee)) {
6347  $isacompany = $thirdparty_buyer->isACompany();
6348  if ($isacompany && !empty($conf->global->MAIN_USE_VAT_COMPANIES_IN_EEC_WITH_INVALID_VAT_ID_ARE_INDIVIDUAL)) {
6349  require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
6350  if (!isValidVATID($thirdparty_buyer)) {
6351  $isacompany = 0;
6352  }
6353  }
6354 
6355  if (!$isacompany) {
6356  //print 'VATRULE 4';
6357  return get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
6358  } else {
6359  //print 'VATRULE 5';
6360  return 0;
6361  }
6362  }
6363 
6364  // Si (vendeur dans Communaute europeene et acheteur hors Communaute europeenne et acheteur particulier) alors TVA par defaut=TVA du produit vendu. Fin de regle
6365  // I don't see any use case that need this rule.
6366  if (!empty($conf->global->MAIN_USE_VAT_OF_PRODUCT_FOR_INDIVIDUAL_CUSTOMER_OUT_OF_EEC) && empty($buyer_in_cee)) {
6367  $isacompany = $thirdparty_buyer->isACompany();
6368  if (!$isacompany) {
6369  return get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
6370  //print 'VATRULE extra';
6371  }
6372  }
6373 
6374  // Sinon la TVA proposee par defaut=0. Fin de regle.
6375  // Rem: Cela signifie qu'au moins un des 2 est hors Communaute europeenne et que le pays differe
6376  //print 'VATRULE 6';
6377  return 0;
6378 }
6379 
6380 
6391 function get_default_npr(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0)
6392 {
6393  global $db;
6394 
6395  if ($idprodfournprice > 0) {
6396  if (!class_exists('ProductFournisseur')) {
6397  require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
6398  }
6399  $prodprice = new ProductFournisseur($db);
6400  $prodprice->fetch_product_fournisseur_price($idprodfournprice);
6401  return $prodprice->fourn_tva_npr;
6402  } elseif ($idprod > 0) {
6403  if (!class_exists('Product')) {
6404  require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
6405  }
6406  $prod = new Product($db);
6407  $prod->fetch($idprod);
6408  return $prod->tva_npr;
6409  }
6410 
6411  return 0;
6412 }
6413 
6427 function get_default_localtax($thirdparty_seller, $thirdparty_buyer, $local, $idprod = 0)
6428 {
6429  global $mysoc;
6430 
6431  if (!is_object($thirdparty_seller)) {
6432  return -1;
6433  }
6434  if (!is_object($thirdparty_buyer)) {
6435  return -1;
6436  }
6437 
6438  if ($local == 1) { // Localtax 1
6439  if ($mysoc->country_code == 'ES') {
6440  if (is_numeric($thirdparty_buyer->localtax1_assuj) && !$thirdparty_buyer->localtax1_assuj) {
6441  return 0;
6442  }
6443  } else {
6444  // Si vendeur non assujeti a Localtax1, localtax1 par default=0
6445  if (is_numeric($thirdparty_seller->localtax1_assuj) && !$thirdparty_seller->localtax1_assuj) {
6446  return 0;
6447  }
6448  if (!is_numeric($thirdparty_seller->localtax1_assuj) && $thirdparty_seller->localtax1_assuj == 'localtax1off') {
6449  return 0;
6450  }
6451  }
6452  } elseif ($local == 2) { //I Localtax 2
6453  // Si vendeur non assujeti a Localtax2, localtax2 par default=0
6454  if (is_numeric($thirdparty_seller->localtax2_assuj) && !$thirdparty_seller->localtax2_assuj) {
6455  return 0;
6456  }
6457  if (!is_numeric($thirdparty_seller->localtax2_assuj) && $thirdparty_seller->localtax2_assuj == 'localtax2off') {
6458  return 0;
6459  }
6460  }
6461 
6462  if ($thirdparty_seller->country_code == $thirdparty_buyer->country_code) {
6463  return get_product_localtax_for_country($idprod, $local, $thirdparty_seller);
6464  }
6465 
6466  return 0;
6467 }
6468 
6477 function yn($yesno, $case = 1, $color = 0)
6478 {
6479  global $langs;
6480 
6481  $result = 'unknown';
6482  $classname = '';
6483  if ($yesno == 1 || strtolower($yesno) == 'yes' || strtolower($yesno) == 'true') { // A mettre avant test sur no a cause du == 0
6484  $result = $langs->trans('yes');
6485  if ($case == 1 || $case == 3) {
6486  $result = $langs->trans("Yes");
6487  }
6488  if ($case == 2) {
6489  $result = '<input type="checkbox" value="1" checked disabled>';
6490  }
6491  if ($case == 3) {
6492  $result = '<input type="checkbox" value="1" checked disabled> '.$result;
6493  }
6494 
6495  $classname = 'ok';
6496  } elseif ($yesno == 0 || strtolower($yesno) == 'no' || strtolower($yesno) == 'false') {
6497  $result = $langs->trans("no");
6498  if ($case == 1 || $case == 3) {
6499  $result = $langs->trans("No");
6500  }
6501  if ($case == 2) {
6502  $result = '<input type="checkbox" value="0" disabled>';
6503  }
6504  if ($case == 3) {
6505  $result = '<input type="checkbox" value="0" disabled> '.$result;
6506  }
6507 
6508  if ($color == 2) {
6509  $classname = 'ok';
6510  } else {
6511  $classname = 'error';
6512  }
6513  }
6514  if ($color) {
6515  return '<span class="'.$classname.'">'.$result.'</span>';
6516  }
6517  return $result;
6518 }
6519 
6535 function get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart = '')
6536 {
6537  global $conf;
6538 
6539  if (empty($modulepart) && !empty($object->module)) {
6540  $modulepart = $object->module;
6541  }
6542 
6543  $path = '';
6544 
6545  $arrayforoldpath = array('cheque', 'category', 'holiday', 'supplier_invoice', 'invoice_supplier', 'mailing', 'supplier_payment');
6546  if (!empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO)) {
6547  $arrayforoldpath[] = 'product';
6548  }
6549  if (!empty($level) && in_array($modulepart, $arrayforoldpath)) {
6550  // This part should be removed once all code is using "get_exdir" to forge path, with parameter $object and $modulepart provided.
6551  if (empty($alpha)) {
6552  $num = preg_replace('/([^0-9])/i', '', $num);
6553  } else {
6554  $num = preg_replace('/^.*\-/i', '', $num);
6555  }
6556  $num = substr("000".$num, -$level);
6557  if ($level == 1) {
6558  $path = substr($num, 0, 1);
6559  }
6560  if ($level == 2) {
6561  $path = substr($num, 1, 1).'/'.substr($num, 0, 1);
6562  }
6563  if ($level == 3) {
6564  $path = substr($num, 2, 1).'/'.substr($num, 1, 1).'/'.substr($num, 0, 1);
6565  }
6566  } else {
6567  // We will enhance here a common way of forging path for document storage.
6568  // In a future, we may distribute directories on several levels depending on setup and object.
6569  // Here, $object->id, $object->ref and $modulepart are required.
6570  //var_dump($modulepart);
6571  $path = dol_sanitizeFileName(empty($object->ref) ? (string) $object->id : $object->ref);
6572  }
6573 
6574  if (empty($withoutslash) && !empty($path)) {
6575  $path .= '/';
6576  }
6577 
6578  return $path;
6579 }
6580 
6589 function dol_mkdir($dir, $dataroot = '', $newmask = '')
6590 {
6591  global $conf;
6592 
6593  dol_syslog("functions.lib::dol_mkdir: dir=".$dir, LOG_INFO);
6594 
6595  $dir_osencoded = dol_osencode($dir);
6596  if (@is_dir($dir_osencoded)) {
6597  return 0;
6598  }
6599 
6600  $nberr = 0;
6601  $nbcreated = 0;
6602 
6603  $ccdir = '';
6604  if (!empty($dataroot)) {
6605  // Remove data root from loop
6606  $dir = str_replace($dataroot.'/', '', $dir);
6607  $ccdir = $dataroot.'/';
6608  }
6609 
6610  $cdir = explode("/", $dir);
6611  $num = count($cdir);
6612  for ($i = 0; $i < $num; $i++) {
6613  if ($i > 0) {
6614  $ccdir .= '/'.$cdir[$i];
6615  } else {
6616  $ccdir .= $cdir[$i];
6617  }
6618  if (preg_match("/^.:$/", $ccdir, $regs)) {
6619  continue; // Si chemin Windows incomplet, on poursuit par rep suivant
6620  }
6621 
6622  // Attention, le is_dir() peut echouer bien que le rep existe.
6623  // (ex selon config de open_basedir)
6624  if ($ccdir) {
6625  $ccdir_osencoded = dol_osencode($ccdir);
6626  if (!@is_dir($ccdir_osencoded)) {
6627  dol_syslog("functions.lib::dol_mkdir: Directory '".$ccdir."' does not exists or is outside open_basedir PHP setting.", LOG_DEBUG);
6628 
6629  umask(0);
6630  $dirmaskdec = octdec((string) $newmask);
6631  if (empty($newmask)) {
6632  $dirmaskdec = empty($conf->global->MAIN_UMASK) ? octdec('0755') : octdec($conf->global->MAIN_UMASK);
6633  }
6634  $dirmaskdec |= octdec('0111'); // Set x bit required for directories
6635  if (!@mkdir($ccdir_osencoded, $dirmaskdec)) {
6636  // Si le is_dir a renvoye une fausse info, alors on passe ici.
6637  dol_syslog("functions.lib::dol_mkdir: Fails to create directory '".$ccdir."' or directory already exists.", LOG_WARNING);
6638  $nberr++;
6639  } else {
6640  dol_syslog("functions.lib::dol_mkdir: Directory '".$ccdir."' created", LOG_DEBUG);
6641  $nberr = 0; // On remet a zero car si on arrive ici, cela veut dire que les echecs precedents peuvent etre ignore
6642  $nbcreated++;
6643  }
6644  } else {
6645  $nberr = 0; // On remet a zero car si on arrive ici, cela veut dire que les echecs precedents peuvent etre ignores
6646  }
6647  }
6648  }
6649  return ($nberr ? -$nberr : $nbcreated);
6650 }
6651 
6652 
6658 function picto_required()
6659 {
6660  return '<span class="fieldrequired">*</span>';
6661 }
6662 
6663 
6680 function dol_string_nohtmltag($stringtoclean, $removelinefeed = 1, $pagecodeto = 'UTF-8', $strip_tags = 0, $removedoublespaces = 1)
6681 {
6682  if ($removelinefeed == 2) {
6683  $stringtoclean = preg_replace('/<br[^>]*>(\n|\r)+/ims', '<br>', $stringtoclean);
6684  }
6685  $temp = preg_replace('/<br[^>]*>/i', "\n", $stringtoclean);
6686 
6687  // 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)
6688  $temp = dol_html_entity_decode($temp, ENT_COMPAT | ENT_HTML5, $pagecodeto);
6689 
6690  $temp = str_replace('< ', '__ltspace__', $temp);
6691 
6692  if ($strip_tags) {
6693  $temp = strip_tags($temp);
6694  } else {
6695  // Remove '<' into remainging, 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).
6696  $pattern = "/<[^<>]+>/";
6697  // Example of $temp: <a href="/myurl" title="<u>A title</u>">0000-021</a>
6698  // pass 1 - $temp after pass 1: <a href="/myurl" title="A title">0000-021
6699  // pass 2 - $temp after pass 2: 0000-021
6700  $tempbis = $temp;
6701  do {
6702  $temp = $tempbis;
6703  $tempbis = str_replace('<>', '', $temp); // No reason to have this into a text, except if value is to try bypass the next html cleaning
6704  $tempbis = preg_replace($pattern, '', $tempbis);
6705  //$idowhile++; print $temp.'-'.$tempbis."\n"; if ($idowhile > 100) break;
6706  } while ($tempbis != $temp);
6707 
6708  $temp = $tempbis;
6709 
6710  // 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).
6711  $temp = preg_replace('/<+([a-z]+)/i', '\1', $temp);
6712  }
6713 
6714  $temp = dol_html_entity_decode($temp, ENT_COMPAT, $pagecodeto);
6715 
6716  // Remove also carriage returns
6717  if ($removelinefeed == 1) {
6718  $temp = str_replace(array("\r\n", "\r", "\n"), " ", $temp);
6719  }
6720 
6721  // And double quotes
6722  if ($removedoublespaces) {
6723  while (strpos($temp, " ")) {
6724  $temp = str_replace(" ", " ", $temp);
6725  }
6726  }
6727 
6728  $temp = str_replace('__ltspace__', '< ', $temp);
6729 
6730  return trim($temp);
6731 }
6732 
6746 function dol_string_onlythesehtmltags($stringtoclean, $cleanalsosomestyles = 1, $removeclassattribute = 1, $cleanalsojavascript = 0, $allowiframe = 0)
6747 {
6748  $allowed_tags = array(
6749  "html", "head", "meta", "body", "article", "a", "abbr", "b", "blockquote", "br", "cite", "div", "dl", "dd", "dt", "em", "font", "img", "ins", "hr", "i", "li", "link",
6750  "ol", "p", "q", "s", "section", "span", "strike", "strong", "title", "table", "tr", "th", "td", "u", "ul", "sup", "sub", "blockquote", "pre", "h1", "h2", "h3", "h4", "h5", "h6",
6751  "comment" // this tags is added to manage comment <!--...--> that are replaced into <comment>...</comment>
6752  );
6753  if ($allowiframe) {
6754  $allowed_tags[] = "iframe";
6755  }
6756 
6757  $allowed_tags_string = join("><", $allowed_tags);
6758  $allowed_tags_string = '<'.$allowed_tags_string.'>';
6759 
6760  $stringtoclean = str_replace('<!DOCTYPE html>', '__!DOCTYPE_HTML__', $stringtoclean); // Replace DOCTYPE to avoid to have it removed by the strip_tags
6761 
6762  $stringtoclean = dol_string_nounprintableascii($stringtoclean, 0);
6763 
6764  //$stringtoclean = preg_replace('/<!--[^>]*-->/', '', $stringtoclean);
6765  $stringtoclean = preg_replace('/<!--([^>]*)-->/', '<comment>\1</comment>', $stringtoclean);
6766 
6767  $stringtoclean = preg_replace('/&colon;/i', ':', $stringtoclean);
6768  $stringtoclean = preg_replace('/&#58;|&#0+58|&#x3A/i', '', $stringtoclean); // refused string ':' encoded (no reason to have a : encoded like this) to disable 'javascript:...'
6769  $stringtoclean = preg_replace('/javascript\s*:/i', '', $stringtoclean);
6770 
6771  $temp = strip_tags($stringtoclean, $allowed_tags_string); // Warning: This remove also undesired </> changing string obfuscated with </> that pass injection detection into harmfull string
6772 
6773  if ($cleanalsosomestyles) { // Clean for remaining html tags
6774  $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
6775  }
6776  if ($removeclassattribute) { // Clean for remaining html tags
6777  $temp = preg_replace('/(<[^>]+)\s+class=((["\']).*?\\3|\\w*)/i', '\\1', $temp);
6778  }
6779 
6780  // Remove 'javascript:' that we should not find into a text with
6781  // 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)).
6782  if ($cleanalsojavascript) {
6783  $temp = preg_replace('/javascript\s*:/i', '', $temp);
6784  }
6785 
6786  $temp = str_replace('__!DOCTYPE_HTML__', '<!DOCTYPE html>', $temp); // Restore the DOCTYPE
6787 
6788  $temp = preg_replace('/<comment>([^>]*)<\/comment>/', '<!--\1-->', $temp); // Restore html comments
6789 
6790 
6791  return $temp;
6792 }
6793 
6794 
6806 function dol_string_onlythesehtmlattributes($stringtoclean, $allowed_attributes = array("allow", "allowfullscreen", "alt", "class", "contenteditable", "data-html", "frameborder", "height", "href", "id", "name", "src", "style", "target", "title", "width"))
6807 {
6808  if (class_exists('DOMDocument') && !empty($stringtoclean)) {
6809  $stringtoclean = '<?xml encoding="UTF-8"><html><body>'.$stringtoclean.'</body></html>';
6810 
6811  $dom = new DOMDocument(null, 'UTF-8');
6812  $dom->loadHTML($stringtoclean, LIBXML_ERR_NONE|LIBXML_HTML_NOIMPLIED|LIBXML_HTML_NODEFDTD|LIBXML_NONET|LIBXML_NOWARNING|LIBXML_NOXMLDECL);
6813 
6814  if (is_object($dom)) {
6815  for ($els = $dom->getElementsByTagname('*'), $i = $els->length - 1; $i >= 0; $i--) {
6816  for ($attrs = $els->item($i)->attributes, $ii = $attrs->length - 1; $ii >= 0; $ii--) {
6817  //var_dump($attrs->item($ii));
6818  if (! empty($attrs->item($ii)->name)) {
6819  // Delete attribute if not into allowed_attributes
6820  if (! in_array($attrs->item($ii)->name, $allowed_attributes)) {
6821  $els->item($i)->removeAttribute($attrs->item($ii)->name);
6822  } elseif (in_array($attrs->item($ii)->name, array('style'))) {
6823  $valuetoclean = $attrs->item($ii)->value;
6824 
6825  if (isset($valuetoclean)) {
6826  do {
6827  $oldvaluetoclean = $valuetoclean;
6828  $valuetoclean = preg_replace('/\/\*.*\*\//m', '', $valuetoclean); // clean css comments
6829  $valuetoclean = preg_replace('/position\s*:\s*[a-z]+/mi', '', $valuetoclean);
6830  if ($els->item($i)->tagName == 'a') { // more paranoiac cleaning for clickable tags.
6831  $valuetoclean = preg_replace('/display\s*://m', '', $valuetoclean);
6832  $valuetoclean = preg_replace('/z-index\s*://m', '', $valuetoclean);
6833  $valuetoclean = preg_replace('/\s+(top|left|right|bottom)\s*://m', '', $valuetoclean);
6834  }
6835  } while ($oldvaluetoclean != $valuetoclean);
6836  }
6837 
6838  $attrs->item($ii)->value = $valuetoclean;
6839  }
6840  }
6841  }
6842  }
6843  }
6844 
6845  $return = $dom->saveHTML();
6846  //$return = '<html><body>aaaa</p>bb<p>ssdd</p>'."\n<p>aaa</p>aa<p>bb</p>";
6847 
6848  $return = preg_replace('/^'.preg_quote('<?xml encoding="UTF-8">', '/').'/', '', $return);
6849  $return = preg_replace('/^'.preg_quote('<html><body>', '/').'/', '', $return);
6850  $return = preg_replace('/'.preg_quote('</body></html>', '/').'$/', '', $return);
6851  return trim($return);
6852  } else {
6853  return $stringtoclean;
6854  }
6855 }
6856 
6868 function dol_string_neverthesehtmltags($stringtoclean, $disallowed_tags = array('textarea'), $cleanalsosomestyles = 0)
6869 {
6870  $temp = $stringtoclean;
6871  foreach ($disallowed_tags as $tagtoremove) {
6872  $temp = preg_replace('/<\/?'.$tagtoremove.'>/', '', $temp);
6873  $temp = preg_replace('/<\/?'.$tagtoremove.'\s+[^>]*>/', '', $temp);
6874  }
6875 
6876  if ($cleanalsosomestyles) {
6877  $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
6878  }
6879 
6880  return $temp;
6881 }
6882 
6883 
6893 function dolGetFirstLineOfText($text, $nboflines = 1, $charset = 'UTF-8')
6894 {
6895  if ($nboflines == 1) {
6896  if (dol_textishtml($text)) {
6897  $firstline = preg_replace('/<br[^>]*>.*$/s', '', $text); // The s pattern modifier means the . can match newline characters
6898  $firstline = preg_replace('/<div[^>]*>.*$/s', '', $firstline); // The s pattern modifier means the . can match newline characters
6899  } else {
6900  $firstline = preg_replace('/[\n\r].*/', '', $text);
6901  }
6902  return $firstline.((strlen($firstline) != strlen($text)) ? '...' : '');
6903  } else {
6904  $ishtml = 0;
6905  if (dol_textishtml($text)) {
6906  $text = preg_replace('/\n/', '', $text);
6907  $ishtml = 1;
6908  $repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
6909  } else {
6910  $repTable = array("\t" => " ", "\n" => "<br>", "\r" => " ", "\0" => " ", "\x0B" => " ");
6911  }
6912 
6913  $text = strtr($text, $repTable);
6914  if ($charset == 'UTF-8') {
6915  $pattern = '/(<br[^>]*>)/Uu';
6916  } else {
6917  // /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
6918  $pattern = '/(<br[^>]*>)/U'; // /U is to have UNGREEDY regex to limit to one html tag.
6919  }
6920  $a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
6921 
6922  $firstline = '';
6923  $i = 0;
6924  $nba = count($a); // 2x nb of lines in $a because $a contains also a line for each new line separator
6925  while (($i < $nba) && ($i < ($nboflines * 2))) {
6926  if ($i % 2 == 0) {
6927  $firstline .= $a[$i];
6928  } elseif (($i < (($nboflines * 2) - 1)) && ($i < ($nba - 1))) {
6929  $firstline .= ($ishtml ? "<br>\n" : "\n");
6930  }
6931  $i++;
6932  }
6933  unset($a);
6934  return $firstline.(($i < $nba) ? '...' : '');
6935  }
6936 }
6937 
6938 
6949 function dol_nl2br($stringtoencode, $nl2brmode = 0, $forxml = false)
6950 {
6951  if (!$nl2brmode) {
6952  return nl2br($stringtoencode, $forxml);
6953  } else {
6954  $ret = preg_replace('/(\r\n|\r|\n)/i', ($forxml ? '<br />' : '<br>'), $stringtoencode);
6955  return $ret;
6956  }
6957 }
6958 
6959 
6977 function dol_htmlentitiesbr($stringtoencode, $nl2brmode = 0, $pagecodefrom = 'UTF-8', $removelasteolbr = 1)
6978 {
6979  $newstring = $stringtoencode;
6980  if (dol_textishtml($stringtoencode)) { // Check if text is already HTML or not
6981  $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.
6982  if ($removelasteolbr) {
6983  $newstring = preg_replace('/<br>$/i', '', $newstring); // Remove last <br> (remove only last one)
6984  }
6985  $newstring = strtr($newstring, array('&'=>'__and__', '<'=>'__lt__', '>'=>'__gt__', '"'=>'__dquot__'));
6986  $newstring = dol_htmlentities($newstring, ENT_COMPAT, $pagecodefrom); // Make entity encoding
6987  $newstring = strtr($newstring, array('__and__'=>'&', '__lt__'=>'<', '__gt__'=>'>', '__dquot__'=>'"'));
6988  } else {
6989  if ($removelasteolbr) {
6990  $newstring = preg_replace('/(\r\n|\r|\n)$/i', '', $newstring); // Remove last \n (may remove several)
6991  }
6992  $newstring = dol_nl2br(dol_htmlentities($newstring, ENT_COMPAT, $pagecodefrom), $nl2brmode);
6993  }
6994  // Other substitutions that htmlentities does not do
6995  //$newstring=str_replace(chr(128),'&euro;',$newstring); // 128 = 0x80. Not in html entity table. // Seems useles with TCPDF. Make bug with UTF8 languages
6996  return $newstring;
6997 }
6998 
7006 function dol_htmlentitiesbr_decode($stringtodecode, $pagecodeto = 'UTF-8')
7007 {
7008  $ret = dol_html_entity_decode($stringtodecode, ENT_COMPAT | ENT_HTML5, $pagecodeto);
7009  $ret = preg_replace('/'."\r\n".'<br(\s[\sa-zA-Z_="]*)?\/?>/i', "<br>", $ret);
7010  $ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>'."\r\n".'/i', "\r\n", $ret);
7011  $ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>'."\n".'/i', "\n", $ret);
7012  $ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>/i', "\n", $ret);
7013  return $ret;