dolibarr  19.0.0-dev
html.form.class.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (c) 2002-2007 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3  * Copyright (C) 2004-2012 Laurent Destailleur <eldy@users.sourceforge.net>
4  * Copyright (C) 2004 Benoit Mortier <benoit.mortier@opensides.be>
5  * Copyright (C) 2004 Sebastien Di Cintio <sdicintio@ressource-toi.org>
6  * Copyright (C) 2004 Eric Seigne <eric.seigne@ryxeo.com>
7  * Copyright (C) 2005-2017 Regis Houssin <regis.houssin@inodbox.com>
8  * Copyright (C) 2006 Andre Cianfarani <acianfa@free.fr>
9  * Copyright (C) 2006 Marc Barilley/Ocebo <marc@ocebo.com>
10  * Copyright (C) 2007 Franky Van Liedekerke <franky.van.liedekerker@telenet.be>
11  * Copyright (C) 2007 Patrick Raguin <patrick.raguin@gmail.com>
12  * Copyright (C) 2010 Juanjo Menent <jmenent@2byte.es>
13  * Copyright (C) 2010-2021 Philippe Grand <philippe.grand@atoo-net.com>
14  * Copyright (C) 2011 Herve Prot <herve.prot@symeos.com>
15  * Copyright (C) 2012-2016 Marcos García <marcosgdf@gmail.com>
16  * Copyright (C) 2012 Cedric Salvador <csalvador@gpcsolutions.fr>
17  * Copyright (C) 2012-2015 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr>
18  * Copyright (C) 2014-2020 Alexandre Spangaro <aspangaro@open-dsi.fr>
19  * Copyright (C) 2018-2022 Ferran Marcet <fmarcet@2byte.es>
20  * Copyright (C) 2018-2021 Frédéric France <frederic.france@netlogic.fr>
21  * Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
22  * Copyright (C) 2018 Christophe Battarel <christophe@altairis.fr>
23  * Copyright (C) 2018 Josep Lluis Amador <joseplluis@lliuretic.cat>
24  * Copyright (C) 2023 Joachim Kueter <git-jk@bloxera.com>
25  *
26  * This program is free software; you can redistribute it and/or modify
27  * it under the terms of the GNU General Public License as published by
28  * the Free Software Foundation; either version 3 of the License, or
29  * (at your option) any later version.
30  *
31  * This program is distributed in the hope that it will be useful,
32  * but WITHOUT ANY WARRANTY; without even the implied warranty of
33  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34  * GNU General Public License for more details.
35  *
36  * You should have received a copy of the GNU General Public License
37  * along with this program. If not, see <https://www.gnu.org/licenses/>.
38  */
39 
53 class Form
54 {
58  public $db;
59 
63  public $error = '';
64 
68  public $errors = array();
69 
70  // Some properties used to return data by some methods
71  public $result;
72  public $num;
73 
74  // Cache arrays
75  public $cache_types_paiements = array();
76  public $cache_conditions_paiements = array();
77  public $cache_transport_mode = array();
78  public $cache_availability = array();
79  public $cache_demand_reason = array();
80  public $cache_types_fees = array();
81  public $cache_vatrates = array();
82 
83 
89  public function __construct($db)
90  {
91  $this->db = $db;
92  }
93 
110  public function editfieldkey($text, $htmlname, $preselected, $object, $perm, $typeofdata = 'string', $moreparam = '', $fieldrequired = 0, $notabletag = 0, $paramid = 'id', $help = '')
111  {
112  global $conf, $langs;
113 
114  $ret = '';
115 
116  // TODO change for compatibility
117  if (!empty($conf->global->MAIN_USE_JQUERY_JEDITABLE) && !preg_match('/^select;/', $typeofdata)) {
118  if (!empty($perm)) {
119  $tmp = explode(':', $typeofdata);
120  $ret .= '<div class="editkey_' . $tmp[0] . (!empty($tmp[1]) ? ' ' . $tmp[1] : '') . '" id="' . $htmlname . '">';
121  if ($fieldrequired) {
122  $ret .= '<span class="fieldrequired">';
123  }
124  if ($help) {
125  $ret .= $this->textwithpicto($langs->trans($text), $help);
126  } else {
127  $ret .= $langs->trans($text);
128  }
129  if ($fieldrequired) {
130  $ret .= '</span>';
131  }
132  $ret .= '</div>' . "\n";
133  } else {
134  if ($fieldrequired) {
135  $ret .= '<span class="fieldrequired">';
136  }
137  if ($help) {
138  $ret .= $this->textwithpicto($langs->trans($text), $help);
139  } else {
140  $ret .= $langs->trans($text);
141  }
142  if ($fieldrequired) {
143  $ret .= '</span>';
144  }
145  }
146  } else {
147  if (empty($notabletag) && $perm) {
148  $ret .= '<table class="nobordernopadding centpercent"><tr><td class="nowrap">';
149  }
150  if ($fieldrequired) {
151  $ret .= '<span class="fieldrequired">';
152  }
153  if ($help) {
154  $ret .= $this->textwithpicto($langs->trans($text), $help);
155  } else {
156  $ret .= $langs->trans($text);
157  }
158  if ($fieldrequired) {
159  $ret .= '</span>';
160  }
161  if (!empty($notabletag)) {
162  $ret .= ' ';
163  }
164  if (empty($notabletag) && $perm) {
165  $ret .= '</td>';
166  }
167  if (empty($notabletag) && $perm) {
168  $ret .= '<td class="right">';
169  }
170  if ($htmlname && GETPOST('action', 'aZ09') != 'edit' . $htmlname && $perm) {
171  $ret .= '<a class="editfielda reposition" href="' . $_SERVER["PHP_SELF"] . '?action=edit' . $htmlname . '&token=' . newToken() . '&' . $paramid . '=' . $object->id . $moreparam . '">' . img_edit($langs->trans('Edit'), ($notabletag ? 0 : 1)) . '</a>';
172  }
173  if (!empty($notabletag) && $notabletag == 1) {
174  if ($text) {
175  $ret .= ' : ';
176  } else {
177  $ret .= ' ';
178  }
179  }
180  if (!empty($notabletag) && $notabletag == 3) {
181  $ret .= ' ';
182  }
183  if (empty($notabletag) && $perm) {
184  $ret .= '</td>';
185  }
186  if (empty($notabletag) && $perm) {
187  $ret .= '</tr></table>';
188  }
189  }
190 
191  return $ret;
192  }
193 
215  public function editfieldval($text, $htmlname, $value, $object, $perm, $typeofdata = 'string', $editvalue = '', $extObject = null, $custommsg = null, $moreparam = '', $notabletag = 1, $formatfunc = '', $paramid = 'id', $gm = 'auto', $moreoptions = array(), $editaction = '')
216  {
217  global $conf, $langs;
218 
219  $ret = '';
220 
221  // Check parameters
222  if (empty($typeofdata)) {
223  return 'ErrorBadParameter typeofdata is empty';
224  }
225  // Clean paramater $typeofdata
226  if ($typeofdata == 'datetime') {
227  $typeofdata = 'dayhour';
228  }
229  $reg = array();
230  if (preg_match('/^(\w+)\((\d+)\)$/', $typeofdata, $reg)) {
231  if ($reg[1] == 'varchar') {
232  $typeofdata = 'string';
233  } elseif ($reg[1] == 'int') {
234  $typeofdata = 'numeric';
235  } else {
236  return 'ErrorBadParameter ' . $typeofdata;
237  }
238  }
239 
240  // When option to edit inline is activated
241  if (!empty($conf->global->MAIN_USE_JQUERY_JEDITABLE) && !preg_match('/^select;|day|datepicker|dayhour|datehourpicker/', $typeofdata)) { // TODO add jquery timepicker and support select
242  $ret .= $this->editInPlace($object, $value, $htmlname, $perm, $typeofdata, $editvalue, $extObject, $custommsg);
243  } else {
244  if ($editaction == '') {
245  $editaction = GETPOST('action', 'aZ09');
246  }
247  $editmode = ($editaction == 'edit' . $htmlname);
248  if ($editmode) {
249  $ret .= "\n";
250  $ret .= '<form method="post" action="' . $_SERVER["PHP_SELF"] . ($moreparam ? '?' . $moreparam : '') . '">';
251  $ret .= '<input type="hidden" name="action" value="set' . $htmlname . '">';
252  $ret .= '<input type="hidden" name="token" value="' . newToken() . '">';
253  $ret .= '<input type="hidden" name="' . $paramid . '" value="' . $object->id . '">';
254  if (empty($notabletag)) {
255  $ret .= '<table class="nobordernopadding centpercent">';
256  }
257  if (empty($notabletag)) {
258  $ret .= '<tr><td>';
259  }
260  if (preg_match('/^(string|safehtmlstring|email|phone|url)/', $typeofdata)) {
261  $tmp = explode(':', $typeofdata);
262  $ret .= '<input type="text" id="' . $htmlname . '" name="' . $htmlname . '" value="' . ($editvalue ? $editvalue : $value) . '"' . (empty($tmp[1]) ? '' : ' size="' . $tmp[1] . '"') . ' autofocus>';
263  } elseif (preg_match('/^(integer)/', $typeofdata)) {
264  $tmp = explode(':', $typeofdata);
265  $valuetoshow = price2num($editvalue ? $editvalue : $value, 0);
266  $ret .= '<input type="text" id="' . $htmlname . '" name="' . $htmlname . '" value="' . $valuetoshow . '"' . (empty($tmp[1]) ? '' : ' size="' . $tmp[1] . '"') . ' autofocus>';
267  } elseif (preg_match('/^(numeric|amount)/', $typeofdata)) {
268  $tmp = explode(':', $typeofdata);
269  $valuetoshow = price2num($editvalue ? $editvalue : $value);
270  $ret .= '<input type="text" id="' . $htmlname . '" name="' . $htmlname . '" value="' . ($valuetoshow != '' ? price($valuetoshow) : '') . '"' . (empty($tmp[1]) ? '' : ' size="' . $tmp[1] . '"') . ' autofocus>';
271  } elseif (preg_match('/^(checkbox)/', $typeofdata)) {
272  $tmp = explode(':', $typeofdata);
273  $ret .= '<input type="checkbox" id="' . $htmlname . '" name="' . $htmlname . '" value="' . ($value ? $value : 'on') . '"' . ($value ? ' checked' : '') . (empty($tmp[1]) ? '' : $tmp[1]) . '/>';
274  } elseif (preg_match('/^text/', $typeofdata) || preg_match('/^note/', $typeofdata)) { // if wysiwyg is enabled $typeofdata = 'ckeditor'
275  $tmp = explode(':', $typeofdata);
276  $cols = (empty($tmp[2]) ? '' : $tmp[2]);
277  $morealt = '';
278  if (preg_match('/%/', $cols)) {
279  $morealt = ' style="width: ' . $cols . '"';
280  $cols = '';
281  }
282 
283  $valuetoshow = ($editvalue ? $editvalue : $value);
284  $ret .= '<textarea id="' . $htmlname . '" name="' . $htmlname . '" wrap="soft" rows="' . (empty($tmp[1]) ? '20' : $tmp[1]) . '"' . ($cols ? ' cols="' . $cols . '"' : 'class="quatrevingtpercent"') . $morealt . '" autofocus>';
285  // textarea convert automatically entities chars into simple chars.
286  // So we convert & into &amp; so a string like 'a &lt; <b>b</b><br>é<br>&lt;script&gt;alert('X');&lt;script&gt;' stay a correct html and is not converted by textarea component when wysiwig is off.
287  $valuetoshow = str_replace('&', '&amp;', $valuetoshow);
288  $ret .= dol_string_neverthesehtmltags($valuetoshow, array('textarea'));
289  $ret .= '</textarea>';
290  } elseif ($typeofdata == 'day' || $typeofdata == 'datepicker') {
291  $addnowlink = empty($moreoptions['addnowlink']) ? 0 : $moreoptions['addnowlink'];
292  $adddateof = empty($moreoptions['adddateof']) ? '' : $moreoptions['adddateof'];
293  $labeladddateof = empty($moreoptions['labeladddateof']) ? '' : $moreoptions['labeladddateof'];
294  $ret .= $this->selectDate($value, $htmlname, 0, 0, 1, 'form' . $htmlname, 1, $addnowlink, 0, '', '', $adddateof, '', 1, $labeladddateof, '', $gm);
295  } elseif ($typeofdata == 'dayhour' || $typeofdata == 'datehourpicker') {
296  $addnowlink = empty($moreoptions['addnowlink']) ? 0 : $moreoptions['addnowlink'];
297  $adddateof = empty($moreoptions['adddateof']) ? '' : $moreoptions['adddateof'];
298  $labeladddateof = empty($moreoptions['labeladddateof']) ? '' : $moreoptions['labeladddateof'];
299  $ret .= $this->selectDate($value, $htmlname, 1, 1, 1, 'form' . $htmlname, 1, $addnowlink, 0, '', '', $adddateof, '', 1, $labeladddateof, '', $gm);
300  } elseif (preg_match('/^select;/', $typeofdata)) {
301  $arraydata = explode(',', preg_replace('/^select;/', '', $typeofdata));
302  $arraylist = array();
303  foreach ($arraydata as $val) {
304  $tmp = explode(':', $val);
305  $tmpkey = str_replace('|', ':', $tmp[0]);
306  $arraylist[$tmpkey] = $tmp[1];
307  }
308  $ret .= $this->selectarray($htmlname, $arraylist, $value);
309  } elseif (preg_match('/^link/', $typeofdata)) {
310  // TODO Not yet implemented. See code for extrafields
311  } elseif (preg_match('/^ckeditor/', $typeofdata)) {
312  $tmp = explode(':', $typeofdata); // Example: ckeditor:dolibarr_zzz:width:height:savemethod:toolbarstartexpanded:rows:cols:uselocalbrowser
313  require_once DOL_DOCUMENT_ROOT . '/core/class/doleditor.class.php';
314  $doleditor = new DolEditor($htmlname, ($editvalue ? $editvalue : $value), (empty($tmp[2]) ? '' : $tmp[2]), (empty($tmp[3]) ? '100' : $tmp[3]), (empty($tmp[1]) ? 'dolibarr_notes' : $tmp[1]), 'In', (empty($tmp[5]) ? 0 : $tmp[5]), (isset($tmp[8]) ? ($tmp[8] ? true : false) : true), true, (empty($tmp[6]) ? '20' : $tmp[6]), (empty($tmp[7]) ? '100' : $tmp[7]));
315  $ret .= $doleditor->Create(1);
316  } elseif ($typeofdata == 'asis') {
317  $ret .= ($editvalue ? $editvalue : $value);
318  }
319  if (empty($notabletag)) {
320  $ret .= '</td>';
321  }
322 
323  // Button save-cancel
324  if (empty($notabletag)) {
325  $ret .= '<td>';
326  }
327  //else $ret.='<div class="clearboth"></div>';
328  $ret .= '<input type="submit" class="smallpaddingimp button' . (empty($notabletag) ? '' : ' ') . '" name="modify" value="' . $langs->trans("Modify") . '">';
329  if (preg_match('/ckeditor|textarea/', $typeofdata) && empty($notabletag)) {
330  $ret .= '<br>' . "\n";
331  }
332  $ret .= '<input type="submit" class="smallpaddingimp button button-cancel' . (empty($notabletag) ? '' : ' ') . '" name="cancel" value="' . $langs->trans("Cancel") . '">';
333  if (empty($notabletag)) {
334  $ret .= '</td>';
335  }
336 
337  if (empty($notabletag)) {
338  $ret .= '</tr></table>' . "\n";
339  }
340  $ret .= '</form>' . "\n";
341  } else {
342  if (preg_match('/^email/', $typeofdata)) {
343  $ret .= dol_print_email($value, 0, 0, 0, 0, 1);
344  } elseif (preg_match('/^phone/', $typeofdata)) {
345  $ret .= dol_print_phone($value, '_blank', 32, 1);
346  } elseif (preg_match('/^url/', $typeofdata)) {
347  $ret .= dol_print_url($value, '_blank', 32, 1);
348  } elseif (preg_match('/^(amount|numeric)/', $typeofdata)) {
349  $ret .= ($value != '' ? price($value, '', $langs, 0, -1, -1, $conf->currency) : '');
350  } elseif (preg_match('/^checkbox/', $typeofdata)) {
351  $tmp = explode(':', $typeofdata);
352  $ret .= '<input type="checkbox" disabled id="' . $htmlname . '" name="' . $htmlname . '" value="' . $value . '"' . ($value ? ' checked' : '') . ($tmp[1] ? $tmp[1] : '') . '/>';
353  } elseif (preg_match('/^text/', $typeofdata) || preg_match('/^note/', $typeofdata)) {
354  $ret .= dol_htmlentitiesbr($value);
355  } elseif (preg_match('/^safehtmlstring/', $typeofdata)) {
356  $ret .= dol_string_onlythesehtmltags($value);
357  } elseif (preg_match('/^restricthtml/', $typeofdata)) {
358  $ret .= dol_string_onlythesehtmltags($value);
359  } elseif ($typeofdata == 'day' || $typeofdata == 'datepicker') {
360  $ret .= '<span class="valuedate">' . dol_print_date($value, 'day', $gm) . '</span>';
361  } elseif ($typeofdata == 'dayhour' || $typeofdata == 'datehourpicker') {
362  $ret .= '<span class="valuedate">' . dol_print_date($value, 'dayhour', $gm) . '</span>';
363  } elseif (preg_match('/^select;/', $typeofdata)) {
364  $arraydata = explode(',', preg_replace('/^select;/', '', $typeofdata));
365  $arraylist = array();
366  foreach ($arraydata as $val) {
367  $tmp = explode(':', $val);
368  $arraylist[$tmp[0]] = $tmp[1];
369  }
370  $ret .= $arraylist[$value];
371  if ($htmlname == 'fk_product_type') {
372  if ($value == 0) {
373  $ret = img_picto($langs->trans("Product"), 'product', 'class="paddingleftonly paddingrightonly colorgrey"') . $ret;
374  } else {
375  $ret = img_picto($langs->trans("Service"), 'service', 'class="paddingleftonly paddingrightonly colorgrey"') . $ret;
376  }
377  }
378  } elseif (preg_match('/^ckeditor/', $typeofdata)) {
379  $tmpcontent = dol_htmlentitiesbr($value);
380  if (!empty($conf->global->MAIN_DISABLE_NOTES_TAB)) {
381  $firstline = preg_replace('/<br>.*/', '', $tmpcontent);
382  $firstline = preg_replace('/[\n\r].*/', '', $firstline);
383  $tmpcontent = $firstline . ((strlen($firstline) != strlen($tmpcontent)) ? '...' : '');
384  }
385  // We dont use dol_escape_htmltag to get the html formating active, but this need we must also
386  // clean data from some dangerous html
387  $ret .= dol_string_onlythesehtmltags(dol_htmlentitiesbr($tmpcontent));
388  } else {
389  if (empty($moreoptions['valuealreadyhtmlescaped'])) {
390  $ret .= dol_escape_htmltag($value);
391  } else {
392  $ret .= $value; // $value must be already html escaped.
393  }
394  }
395 
396  // Custom format if parameter $formatfunc has been provided
397  if ($formatfunc && method_exists($object, $formatfunc)) {
398  $ret = $object->$formatfunc($ret);
399  }
400  }
401  }
402  return $ret;
403  }
404 
416  public function widgetForTranslation($fieldname, $object, $perm, $typeofdata = 'string', $check = '', $morecss = '')
417  {
418  global $conf, $langs, $extralanguages;
419 
420  $result = '';
421 
422  // List of extra languages
423  $arrayoflangcode = array();
424  if (!empty($conf->global->PDF_USE_ALSO_LANGUAGE_CODE)) {
425  $arrayoflangcode[] = $conf->global->PDF_USE_ALSO_LANGUAGE_CODE;
426  }
427 
428  if (is_array($arrayoflangcode) && count($arrayoflangcode)) {
429  if (!is_object($extralanguages)) {
430  include_once DOL_DOCUMENT_ROOT . '/core/class/extralanguages.class.php';
431  $extralanguages = new ExtraLanguages($this->db);
432  }
433  $extralanguages->fetch_name_extralanguages('societe');
434 
435  if (!is_array($extralanguages->attributes[$object->element]) || empty($extralanguages->attributes[$object->element][$fieldname])) {
436  return ''; // No extralang field to show
437  }
438 
439  $result .= '<!-- Widget for translation -->' . "\n";
440  $result .= '<div class="inline-block paddingleft image-' . $object->element . '-' . $fieldname . '">';
441  $s = img_picto($langs->trans("ShowOtherLanguages"), 'language', '', false, 0, 0, '', 'fa-15 editfieldlang');
442  $result .= $s;
443  $result .= '</div>';
444 
445  $result .= '<div class="inline-block hidden field-' . $object->element . '-' . $fieldname . '">';
446 
447  $resultforextrlang = '';
448  foreach ($arrayoflangcode as $langcode) {
449  $valuetoshow = GETPOSTISSET('field-' . $object->element . "-" . $fieldname . "-" . $langcode) ? GETPOST('field-' . $object->element . '-' . $fieldname . "-" . $langcode, $check) : '';
450  if (empty($valuetoshow)) {
451  $object->fetchValuesForExtraLanguages();
452  //var_dump($object->array_languages);
453  $valuetoshow = $object->array_languages[$fieldname][$langcode];
454  }
455 
456  $s = picto_from_langcode($langcode, 'class="pictoforlang paddingright"');
457  $resultforextrlang .= $s;
458 
459  // TODO Use the showInputField() method of ExtraLanguages object
460  if ($typeofdata == 'textarea') {
461  $resultforextrlang .= '<textarea name="field-' . $object->element . "-" . $fieldname . "-" . $langcode . '" id="' . $fieldname . "-" . $langcode . '" class="' . $morecss . '" rows="' . ROWS_2 . '" wrap="soft">';
462  $resultforextrlang .= $valuetoshow;
463  $resultforextrlang .= '</textarea>';
464  } else {
465  $resultforextrlang .= '<input type="text" class="inputfieldforlang ' . ($morecss ? ' ' . $morecss : '') . '" name="field-' . $object->element . '-' . $fieldname . '-' . $langcode . '" value="' . $valuetoshow . '">';
466  }
467  }
468  $result .= $resultforextrlang;
469 
470  $result .= '</div>';
471  $result .= '<script nonce="' . getNonce() . '">$(".image-' . $object->element . '-' . $fieldname . '").click(function() { console.log("Toggle lang widget"); jQuery(".field-' . $object->element . '-' . $fieldname . '").toggle(); });</script>';
472  }
473 
474  return $result;
475  }
476 
490  protected function editInPlace($object, $value, $htmlname, $condition, $inputType = 'textarea', $editvalue = null, $extObject = null, $custommsg = null)
491  {
492  global $conf;
493 
494  $out = '';
495 
496  // Check parameters
497  if (preg_match('/^text/', $inputType)) {
498  $value = dol_nl2br($value);
499  } elseif (preg_match('/^numeric/', $inputType)) {
500  $value = price($value);
501  } elseif ($inputType == 'day' || $inputType == 'datepicker') {
502  $value = dol_print_date($value, 'day');
503  }
504 
505  if ($condition) {
506  $element = false;
507  $table_element = false;
508  $fk_element = false;
509  $loadmethod = false;
510  $savemethod = false;
511  $ext_element = false;
512  $button_only = false;
513  $inputOption = '';
514  $rows = '';
515  $cols = '';
516 
517  if (is_object($object)) {
518  $element = $object->element;
519  $table_element = $object->table_element;
520  $fk_element = $object->id;
521  }
522 
523  if (is_object($extObject)) {
524  $ext_element = $extObject->element;
525  }
526 
527  if (preg_match('/^(string|email|numeric)/', $inputType)) {
528  $tmp = explode(':', $inputType);
529  $inputType = $tmp[0];
530  if (!empty($tmp[1])) {
531  $inputOption = $tmp[1];
532  }
533  if (!empty($tmp[2])) {
534  $savemethod = $tmp[2];
535  }
536  $out .= '<input id="width_' . $htmlname . '" value="' . $inputOption . '" type="hidden"/>' . "\n";
537  } elseif ((preg_match('/^day$/', $inputType)) || (preg_match('/^datepicker/', $inputType)) || (preg_match('/^datehourpicker/', $inputType))) {
538  $tmp = explode(':', $inputType);
539  $inputType = $tmp[0];
540  if (!empty($tmp[1])) {
541  $inputOption = $tmp[1];
542  }
543  if (!empty($tmp[2])) {
544  $savemethod = $tmp[2];
545  }
546 
547  $out .= '<input id="timestamp" type="hidden"/>' . "\n"; // Use for timestamp format
548  } elseif (preg_match('/^(select|autocomplete)/', $inputType)) {
549  $tmp = explode(':', $inputType);
550  $inputType = $tmp[0];
551  $loadmethod = $tmp[1];
552  if (!empty($tmp[2])) {
553  $savemethod = $tmp[2];
554  }
555  if (!empty($tmp[3])) {
556  $button_only = true;
557  }
558  } elseif (preg_match('/^textarea/', $inputType)) {
559  $tmp = explode(':', $inputType);
560  $inputType = $tmp[0];
561  $rows = (empty($tmp[1]) ? '8' : $tmp[1]);
562  $cols = (empty($tmp[2]) ? '80' : $tmp[2]);
563  } elseif (preg_match('/^ckeditor/', $inputType)) {
564  $tmp = explode(':', $inputType);
565  $inputType = $tmp[0];
566  $toolbar = $tmp[1];
567  if (!empty($tmp[2])) {
568  $width = $tmp[2];
569  }
570  if (!empty($tmp[3])) {
571  $heigth = $tmp[3];
572  }
573  if (!empty($tmp[4])) {
574  $savemethod = $tmp[4];
575  }
576 
577  if (isModEnabled('fckeditor')) {
578  $out .= '<input id="ckeditor_toolbar" value="' . $toolbar . '" type="hidden"/>' . "\n";
579  } else {
580  $inputType = 'textarea';
581  }
582  }
583 
584  $out .= '<input id="element_' . $htmlname . '" value="' . $element . '" type="hidden"/>' . "\n";
585  $out .= '<input id="table_element_' . $htmlname . '" value="' . $table_element . '" type="hidden"/>' . "\n";
586  $out .= '<input id="fk_element_' . $htmlname . '" value="' . $fk_element . '" type="hidden"/>' . "\n";
587  $out .= '<input id="loadmethod_' . $htmlname . '" value="' . $loadmethod . '" type="hidden"/>' . "\n";
588  if (!empty($savemethod)) {
589  $out .= '<input id="savemethod_' . $htmlname . '" value="' . $savemethod . '" type="hidden"/>' . "\n";
590  }
591  if (!empty($ext_element)) {
592  $out .= '<input id="ext_element_' . $htmlname . '" value="' . $ext_element . '" type="hidden"/>' . "\n";
593  }
594  if (!empty($custommsg)) {
595  if (is_array($custommsg)) {
596  if (!empty($custommsg['success'])) {
597  $out .= '<input id="successmsg_' . $htmlname . '" value="' . $custommsg['success'] . '" type="hidden"/>' . "\n";
598  }
599  if (!empty($custommsg['error'])) {
600  $out .= '<input id="errormsg_' . $htmlname . '" value="' . $custommsg['error'] . '" type="hidden"/>' . "\n";
601  }
602  } else {
603  $out .= '<input id="successmsg_' . $htmlname . '" value="' . $custommsg . '" type="hidden"/>' . "\n";
604  }
605  }
606  if ($inputType == 'textarea') {
607  $out .= '<input id="textarea_' . $htmlname . '_rows" value="' . $rows . '" type="hidden"/>' . "\n";
608  $out .= '<input id="textarea_' . $htmlname . '_cols" value="' . $cols . '" type="hidden"/>' . "\n";
609  }
610  $out .= '<span id="viewval_' . $htmlname . '" class="viewval_' . $inputType . ($button_only ? ' inactive' : ' active') . '">' . $value . '</span>' . "\n";
611  $out .= '<span id="editval_' . $htmlname . '" class="editval_' . $inputType . ($button_only ? ' inactive' : ' active') . ' hideobject">' . (!empty($editvalue) ? $editvalue : $value) . '</span>' . "\n";
612  } else {
613  $out = $value;
614  }
615 
616  return $out;
617  }
618 
637  public function textwithtooltip($text, $htmltext, $tooltipon = 1, $direction = 0, $img = '', $extracss = '', $notabs = 3, $incbefore = '', $noencodehtmltext = 0, $tooltiptrigger = '', $forcenowrap = 0)
638  {
639  if ($incbefore) {
640  $text = $incbefore . $text;
641  }
642  if (!$htmltext) {
643  return $text;
644  }
645  $direction = (int) $direction; // For backward compatibility when $direction was set to '' instead of 0
646 
647  $tag = 'td';
648  if ($notabs == 2) {
649  $tag = 'div';
650  }
651  if ($notabs == 3) {
652  $tag = 'span';
653  }
654  // Sanitize tooltip
655  $htmltext = str_replace(array("\r", "\n"), '', $htmltext);
656 
657  $extrastyle = '';
658  if ($direction < 0) {
659  $extracss = ($extracss ? $extracss . ' ' : '') . ($notabs != 3 ? 'inline-block' : '');
660  $extrastyle = 'padding: 0px; padding-left: 3px;';
661  }
662  if ($direction > 0) {
663  $extracss = ($extracss ? $extracss . ' ' : '') . ($notabs != 3 ? 'inline-block' : '');
664  $extrastyle = 'padding: 0px; padding-right: 3px;';
665  }
666 
667  $classfortooltip = 'classfortooltip';
668 
669  $s = '';
670  $textfordialog = '';
671 
672  if ($tooltiptrigger == '') {
673  $htmltext = str_replace('"', '&quot;', $htmltext);
674  } else {
675  $classfortooltip = 'classfortooltiponclick';
676  $textfordialog .= '<div style="display: none;" id="idfortooltiponclick_' . $tooltiptrigger . '" class="classfortooltiponclicktext">' . $htmltext . '</div>';
677  }
678  if ($tooltipon == 2 || $tooltipon == 3) {
679  $paramfortooltipimg = ' class="' . $classfortooltip . ($notabs != 3 ? ' inline-block' : '') . ($extracss ? ' ' . $extracss : '') . '" style="padding: 0px;' . ($extrastyle ? ' ' . $extrastyle : '') . '"';
680  if ($tooltiptrigger == '') {
681  $paramfortooltipimg .= ' title="' . ($noencodehtmltext ? $htmltext : dol_escape_htmltag($htmltext, 1)) . '"'; // Attribut to put on img tag to store tooltip
682  } else {
683  $paramfortooltipimg .= ' dolid="' . $tooltiptrigger . '"';
684  }
685  } else {
686  $paramfortooltipimg = ($extracss ? ' class="' . $extracss . '"' : '') . ($extrastyle ? ' style="' . $extrastyle . '"' : ''); // Attribut to put on td text tag
687  }
688  if ($tooltipon == 1 || $tooltipon == 3) {
689  $paramfortooltiptd = ' class="' . ($tooltipon == 3 ? 'cursorpointer ' : '') . $classfortooltip . ' inline-block' . ($extracss ? ' ' . $extracss : '') . '" style="padding: 0px;' . ($extrastyle ? ' ' . $extrastyle : '') . '" ';
690  if ($tooltiptrigger == '') {
691  $paramfortooltiptd .= ' title="' . ($noencodehtmltext ? $htmltext : dol_escape_htmltag($htmltext, 1)) . '"'; // Attribut to put on td tag to store tooltip
692  } else {
693  $paramfortooltiptd .= ' dolid="' . $tooltiptrigger . '"';
694  }
695  } else {
696  $paramfortooltiptd = ($extracss ? ' class="' . $extracss . '"' : '') . ($extrastyle ? ' style="' . $extrastyle . '"' : ''); // Attribut to put on td text tag
697  }
698  if (empty($notabs)) {
699  $s .= '<table class="nobordernopadding"><tr style="height: auto;">';
700  } elseif ($notabs == 2) {
701  $s .= '<div class="inline-block' . ($forcenowrap ? ' nowrap' : '') . '">';
702  }
703  // Define value if value is before
704  if ($direction < 0) {
705  $s .= '<' . $tag . $paramfortooltipimg;
706  if ($tag == 'td') {
707  $s .= ' class="valigntop" width="14"';
708  }
709  $s .= '>' . $textfordialog . $img . '</' . $tag . '>';
710  }
711  // Use another method to help avoid having a space in value in order to use this value with jquery
712  // Define label
713  if ((string) $text != '') {
714  $s .= '<' . $tag . $paramfortooltiptd . '>' . $text . '</' . $tag . '>';
715  }
716  // Define value if value is after
717  if ($direction > 0) {
718  $s .= '<' . $tag . $paramfortooltipimg;
719  if ($tag == 'td') {
720  $s .= ' class="valignmiddle" width="14"';
721  }
722  $s .= '>' . $textfordialog . $img . '</' . $tag . '>';
723  }
724  if (empty($notabs)) {
725  $s .= '</tr></table>';
726  } elseif ($notabs == 2) {
727  $s .= '</div>';
728  }
729 
730  return $s;
731  }
732 
747  public function textwithpicto($text, $htmltext, $direction = 1, $type = 'help', $extracss = '', $noencodehtmltext = 0, $notabs = 3, $tooltiptrigger = '', $forcenowrap = 0)
748  {
749  global $conf, $langs;
750 
751  //For backwards compatibility
752  if ($type == '0') {
753  $type = 'info';
754  } elseif ($type == '1') {
755  $type = 'help';
756  }
757 
758  if (preg_match('/onsmartphone$/', $tooltiptrigger) && empty($conf->dol_no_mouse_hover)) {
759  $tooltiptrigger = preg_replace('/^.*onsmartphone$/', '', $tooltiptrigger);
760  }
761 
762  $alt = '';
763  if ($tooltiptrigger) {
764  $alt = $langs->transnoentitiesnoconv("ClickToShowHelp");
765  }
766 
767  // If info or help with no javascript, show only text
768  if (empty($conf->use_javascript_ajax)) {
769  if ($type == 'info' || $type == 'infoclickable' || $type == 'help' || $type == 'helpclickable') {
770  return $text;
771  } else {
772  $alt = $htmltext;
773  $htmltext = '';
774  }
775  }
776 
777  // If info or help with smartphone, show only text (tooltip hover can't works)
778  if (!empty($conf->dol_no_mouse_hover) && empty($tooltiptrigger)) {
779  if ($type == 'info' || $type == 'infoclickable' || $type == 'help' || $type == 'helpclickable') {
780  return $text;
781  }
782  }
783  // If info or help with smartphone, show only text (tooltip on click does not works with dialog on smaprtphone)
784  //if (!empty($conf->dol_no_mouse_hover) && !empty($tooltiptrigger))
785  //{
786  //if ($type == 'info' || $type == 'help') return '<a href="'..'">'.$text.'</a>';
787  //}
788 
789  $img = '';
790  if ($type == 'info') {
791  $img = img_help(0, $alt);
792  } elseif ($type == 'help') {
793  $img = img_help(($tooltiptrigger != '' ? 2 : 1), $alt);
794  } elseif ($type == 'helpclickable') {
795  $img = img_help(($tooltiptrigger != '' ? 2 : 1), $alt);
796  } elseif ($type == 'superadmin') {
797  $img = img_picto($alt, 'redstar');
798  } elseif ($type == 'admin') {
799  $img = img_picto($alt, 'star');
800  } elseif ($type == 'warning') {
801  $img = img_warning($alt);
802  } elseif ($type != 'none') {
803  $img = img_picto($alt, $type); // $type can be an image path
804  }
805 
806  return $this->textwithtooltip($text, $htmltext, ((($tooltiptrigger && !$img) || strpos($type, 'clickable')) ? 3 : 2), $direction, $img, $extracss, $notabs, '', $noencodehtmltext, $tooltiptrigger, $forcenowrap);
807  }
808 
819  public function selectMassAction($selected, $arrayofaction, $alwaysvisible = 0, $name = 'massaction', $cssclass = 'checkforselect')
820  {
821  global $conf, $langs, $hookmanager;
822 
823  $disabled = 0;
824  $ret = '<div class="centpercent center">';
825  $ret .= '<select class="flat' . (empty($conf->use_javascript_ajax) ? '' : ' hideobject') . ' ' . $name . ' ' . $name . 'select valignmiddle alignstart" id="' . $name . '" name="' . $name . '"' . ($disabled ? ' disabled="disabled"' : '') . '>';
826 
827  // Complete list with data from external modules. THe module can use $_SERVER['PHP_SELF'] to know on which page we are, or use the $parameters['currentcontext'] completed by executeHooks.
828  $parameters = array();
829  $reshook = $hookmanager->executeHooks('addMoreMassActions', $parameters); // Note that $action and $object may have been modified by hook
830  // check if there is a mass action
831  if (count($arrayofaction) == 0 && empty($hookmanager->resPrint)) {
832  return;
833  }
834  if (empty($reshook)) {
835  $ret .= '<option value="0"' . ($disabled ? ' disabled="disabled"' : '') . '>-- ' . $langs->trans("SelectAction") . ' --</option>';
836  foreach ($arrayofaction as $code => $label) {
837  $ret .= '<option value="' . $code . '"' . ($disabled ? ' disabled="disabled"' : '') . ' data-html="' . dol_escape_htmltag($label) . '">' . $label . '</option>';
838  }
839  }
840  $ret .= $hookmanager->resPrint;
841 
842  $ret .= '</select>';
843 
844  if (empty($conf->dol_optimize_smallscreen)) {
845  $ret .= ajax_combobox('.' . $name . 'select');
846  }
847 
848  // Warning: if you set submit button to disabled, post using 'Enter' will no more work if there is no another input submit. So we add a hidden button
849  $ret .= '<input type="submit" name="confirmmassactioninvisible" style="display: none" tabindex="-1">'; // Hidden button BEFORE so it is the one used when we submit with ENTER.
850  $ret .= '<input type="submit" disabled name="confirmmassaction"' . (empty($conf->use_javascript_ajax) ? '' : ' style="display: none"') . ' class="reposition button smallpaddingimp' . (empty($conf->use_javascript_ajax) ? '' : ' hideobject') . ' ' . $name . ' ' . $name . 'confirmed" value="' . dol_escape_htmltag($langs->trans("Confirm")) . '">';
851  $ret .= '</div>';
852 
853  if (!empty($conf->use_javascript_ajax)) {
854  $ret .= '<!-- JS CODE TO ENABLE mass action select -->
855  <script nonce="' . getNonce() . '">
856  function initCheckForSelect(mode, name, cssclass) /* mode is 0 during init of page or click all, 1 when we click on 1 checkboxi, "name" refers to the class of the massaction button, "cssclass" to the class of the checkfor select boxes */
857  {
858  atleastoneselected=0;
859  jQuery("."+cssclass).each(function( index ) {
860  /* console.log( index + ": " + $( this ).text() ); */
861  if ($(this).is(\':checked\')) atleastoneselected++;
862  });
863 
864  console.log("initCheckForSelect mode="+mode+" name="+name+" cssclass="+cssclass+" atleastoneselected="+atleastoneselected);
865 
866  if (atleastoneselected || ' . $alwaysvisible . ')
867  {
868  jQuery("."+name).show();
869  ' . ($selected ? 'if (atleastoneselected) { jQuery("."+name+"select").val("' . $selected . '").trigger(\'change\'); jQuery("."+name+"confirmed").prop(\'disabled\', false); }' : '') . '
870  ' . ($selected ? 'if (! atleastoneselected) { jQuery("."+name+"select").val("0").trigger(\'change\'); jQuery("."+name+"confirmed").prop(\'disabled\', true); } ' : '') . '
871  }
872  else
873  {
874  jQuery("."+name).hide();
875  jQuery("."+name+"other").hide();
876  }
877  }
878 
879  jQuery(document).ready(function () {
880  initCheckForSelect(0, "' . $name . '", "' . $cssclass . '");
881  jQuery(".' . $cssclass . '").click(function() {
882  initCheckForSelect(1, "' . $name . '", "' . $cssclass . '");
883  });
884  jQuery(".' . $name . 'select").change(function() {
885  var massaction = $( this ).val();
886  var urlform = $( this ).closest("form").attr("action").replace("#show_files","");
887  if (massaction == "builddoc")
888  {
889  urlform = urlform + "#show_files";
890  }
891  $( this ).closest("form").attr("action", urlform);
892  console.log("we select a mass action name=' . $name . ' massaction="+massaction+" - "+urlform);
893  /* Warning: if you set submit button to disabled, post using Enter will no more work if there is no other button */
894  if ($(this).val() != \'0\')
895  {
896  jQuery(".' . $name . 'confirmed").prop(\'disabled\', false);
897  jQuery(".' . $name . 'other").hide(); /* To disable if another div was open */
898  jQuery(".' . $name . '"+massaction).show();
899  }
900  else
901  {
902  jQuery(".' . $name . 'confirmed").prop(\'disabled\', true);
903  jQuery(".' . $name . 'other").hide(); /* To disable any div open */
904  }
905  });
906  });
907  </script>
908  ';
909  }
910 
911  return $ret;
912  }
913 
914  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
915 
932  public function select_country($selected = '', $htmlname = 'country_id', $htmloption = '', $maxlength = 0, $morecss = 'minwidth300', $usecodeaskey = '', $showempty = 1, $disablefavorites = 0, $addspecialentries = 0, $exclude_country_code = array(), $hideflags = 0)
933  {
934  // phpcs:enable
935  global $conf, $langs, $mysoc;
936 
937  $langs->load("dict");
938 
939  $out = '';
940  $countryArray = array();
941  $favorite = array();
942  $label = array();
943  $atleastonefavorite = 0;
944 
945  $sql = "SELECT rowid, code as code_iso, code_iso as code_iso3, label, favorite, eec";
946  $sql .= " FROM " . $this->db->prefix() . "c_country";
947  $sql .= " WHERE active > 0";
948  //$sql.= " ORDER BY code ASC";
949 
950  dol_syslog(get_class($this) . "::select_country", LOG_DEBUG);
951  $resql = $this->db->query($sql);
952  if ($resql) {
953  $out .= '<select id="select' . $htmlname . '" class="flat maxwidth200onsmartphone selectcountry' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" ' . $htmloption . '>';
954  $num = $this->db->num_rows($resql);
955  $i = 0;
956  if ($num) {
957  while ($i < $num) {
958  $obj = $this->db->fetch_object($resql);
959 
960  $countryArray[$i]['rowid'] = $obj->rowid;
961  $countryArray[$i]['code_iso'] = $obj->code_iso;
962  $countryArray[$i]['code_iso3'] = $obj->code_iso3;
963  $countryArray[$i]['label'] = ($obj->code_iso && $langs->transnoentitiesnoconv("Country" . $obj->code_iso) != "Country" . $obj->code_iso ? $langs->transnoentitiesnoconv("Country" . $obj->code_iso) : ($obj->label != '-' ? $obj->label : ''));
964  $countryArray[$i]['favorite'] = $obj->favorite;
965  $countryArray[$i]['eec'] = $obj->eec;
966  $favorite[$i] = $obj->favorite;
967  $label[$i] = dol_string_unaccent($countryArray[$i]['label']);
968  $i++;
969  }
970 
971  if (empty($disablefavorites)) {
972  $array1_sort_order = SORT_DESC;
973  $array2_sort_order = SORT_ASC;
974  array_multisort($favorite, $array1_sort_order, $label, $array2_sort_order, $countryArray);
975  } else {
976  $countryArray = dol_sort_array($countryArray, 'label');
977  }
978 
979  if ($showempty) {
980  if (is_numeric($showempty)) {
981  $out .= '<option value="">&nbsp;</option>' . "\n";
982  } else {
983  $out .= '<option value="-1">' . $langs->trans($showempty) . '</option>' . "\n";
984  }
985  }
986 
987  if ($addspecialentries) { // Add dedicated entries for groups of countries
988  //if ($showempty) $out.= '<option value="" disabled class="selectoptiondisabledwhite">--------------</option>';
989  $out .= '<option value="special_allnotme"' . ($selected == 'special_allnotme' ? ' selected' : '') . '>' . $langs->trans("CountriesExceptMe", $langs->transnoentitiesnoconv("Country" . $mysoc->country_code)) . '</option>';
990  $out .= '<option value="special_eec"' . ($selected == 'special_eec' ? ' selected' : '') . '>' . $langs->trans("CountriesInEEC") . '</option>';
991  if ($mysoc->isInEEC()) {
992  $out .= '<option value="special_eecnotme"' . ($selected == 'special_eecnotme' ? ' selected' : '') . '>' . $langs->trans("CountriesInEECExceptMe", $langs->transnoentitiesnoconv("Country" . $mysoc->country_code)) . '</option>';
993  }
994  $out .= '<option value="special_noteec"' . ($selected == 'special_noteec' ? ' selected' : '') . '>' . $langs->trans("CountriesNotInEEC") . '</option>';
995  $out .= '<option value="" disabled class="selectoptiondisabledwhite">------------</option>';
996  }
997 
998  foreach ($countryArray as $row) {
999  //if (empty($showempty) && empty($row['rowid'])) continue;
1000  if (empty($row['rowid'])) {
1001  continue;
1002  }
1003  if (is_array($exclude_country_code) && count($exclude_country_code) && in_array($row['code_iso'], $exclude_country_code)) {
1004  continue; // exclude some countries
1005  }
1006 
1007  if (empty($disablefavorites) && $row['favorite'] && $row['code_iso']) {
1008  $atleastonefavorite++;
1009  }
1010  if (empty($row['favorite']) && $atleastonefavorite) {
1011  $atleastonefavorite = 0;
1012  $out .= '<option value="" disabled class="selectoptiondisabledwhite">------------</option>';
1013  }
1014 
1015  $labeltoshow = '';
1016  if ($row['label']) {
1017  $labeltoshow .= dol_trunc($row['label'], $maxlength, 'middle');
1018  } else {
1019  $labeltoshow .= '&nbsp;';
1020  }
1021  if ($row['code_iso']) {
1022  $labeltoshow .= ' <span class="opacitymedium">(' . $row['code_iso'] . ')</span>';
1023  if (empty($hideflags)) {
1024  $tmpflag = picto_from_langcode($row['code_iso'], 'class="saturatemedium paddingrightonly"', 1);
1025  $labeltoshow = $tmpflag . ' ' . $labeltoshow;
1026  }
1027  }
1028 
1029  if ($selected && $selected != '-1' && ($selected == $row['rowid'] || $selected == $row['code_iso'] || $selected == $row['code_iso3'] || $selected == $row['label'])) {
1030  $out .= '<option value="' . ($usecodeaskey ? ($usecodeaskey == 'code2' ? $row['code_iso'] : $row['code_iso3']) : $row['rowid']) . '" selected data-html="' . dol_escape_htmltag($labeltoshow) . '" data-eec="' . ((int) $row['eec']) . '">';
1031  } else {
1032  $out .= '<option value="' . ($usecodeaskey ? ($usecodeaskey == 'code2' ? $row['code_iso'] : $row['code_iso3']) : $row['rowid']) . '" data-html="' . dol_escape_htmltag($labeltoshow) . '" data-eec="' . ((int) $row['eec']) . '">';
1033  }
1034  $out .= $labeltoshow;
1035  $out .= '</option>' . "\n";
1036  }
1037  }
1038  $out .= '</select>';
1039  } else {
1040  dol_print_error($this->db);
1041  }
1042 
1043  // Make select dynamic
1044  include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
1045  $out .= ajax_combobox('select' . $htmlname, array(), 0, 0, 'resolve');
1046 
1047  return $out;
1048  }
1049 
1050  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1051 
1065  public function select_incoterms($selected = '', $location_incoterms = '', $page = '', $htmlname = 'incoterm_id', $htmloption = '', $forcecombo = 1, $events = array(), $disableautocomplete = 0)
1066  {
1067  // phpcs:enable
1068  global $conf, $langs;
1069 
1070  $langs->load("dict");
1071 
1072  $out = '';
1073  $moreattrib = '';
1074  $incotermArray = array();
1075 
1076  $sql = "SELECT rowid, code";
1077  $sql .= " FROM " . $this->db->prefix() . "c_incoterms";
1078  $sql .= " WHERE active > 0";
1079  $sql .= " ORDER BY code ASC";
1080 
1081  dol_syslog(get_class($this) . "::select_incoterm", LOG_DEBUG);
1082  $resql = $this->db->query($sql);
1083  if ($resql) {
1084  if ($conf->use_javascript_ajax && !$forcecombo) {
1085  include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
1086  $out .= ajax_combobox($htmlname, $events);
1087  }
1088 
1089  if (!empty($page)) {
1090  $out .= '<form method="post" action="' . $page . '">';
1091  $out .= '<input type="hidden" name="action" value="set_incoterms">';
1092  $out .= '<input type="hidden" name="token" value="' . newToken() . '">';
1093  }
1094 
1095  $out .= '<select id="' . $htmlname . '" class="flat selectincoterm width75" name="' . $htmlname . '" ' . $htmloption . '>';
1096  $out .= '<option value="0">&nbsp;</option>';
1097  $num = $this->db->num_rows($resql);
1098  $i = 0;
1099  if ($num) {
1100  while ($i < $num) {
1101  $obj = $this->db->fetch_object($resql);
1102  $incotermArray[$i]['rowid'] = $obj->rowid;
1103  $incotermArray[$i]['code'] = $obj->code;
1104  $i++;
1105  }
1106 
1107  foreach ($incotermArray as $row) {
1108  if ($selected && ($selected == $row['rowid'] || $selected == $row['code'])) {
1109  $out .= '<option value="' . $row['rowid'] . '" selected>';
1110  } else {
1111  $out .= '<option value="' . $row['rowid'] . '">';
1112  }
1113 
1114  if ($row['code']) {
1115  $out .= $row['code'];
1116  }
1117 
1118  $out .= '</option>';
1119  }
1120  }
1121  $out .= '</select>';
1122 
1123  if ($conf->use_javascript_ajax && empty($disableautocomplete)) {
1124  $out .= ajax_multiautocompleter('location_incoterms', array(), DOL_URL_ROOT . '/core/ajax/locationincoterms.php') . "\n";
1125  $moreattrib .= ' autocomplete="off"';
1126  }
1127  $out .= '<input id="location_incoterms" class="maxwidthonsmartphone type="text" name="location_incoterms" value="' . $location_incoterms . '">' . "\n";
1128 
1129  if (!empty($page)) {
1130  $out .= '<input type="submit" class="button valignmiddle smallpaddingimp nomargintop nomarginbottom" value="' . $langs->trans("Modify") . '"></form>';
1131  }
1132  } else {
1133  dol_print_error($this->db);
1134  }
1135 
1136  return $out;
1137  }
1138 
1139  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1140 
1152  public function select_type_of_lines($selected = '', $htmlname = 'type', $showempty = 0, $hidetext = 0, $forceall = 0)
1153  {
1154  // phpcs:enable
1155  global $langs, $conf;
1156 
1157  // If product & services are enabled or both disabled.
1158  if ($forceall == 1 || (empty($forceall) && isModEnabled("product") && isModEnabled("service"))
1159  || (empty($forceall) && !isModEnabled('product') && !isModEnabled('service'))) {
1160  if (empty($hidetext)) {
1161  print $langs->trans("Type") . ': ';
1162  }
1163  print '<select class="flat" id="select_' . $htmlname . '" name="' . $htmlname . '">';
1164  if ($showempty) {
1165  print '<option value="-1"';
1166  if ($selected == -1) {
1167  print ' selected';
1168  }
1169  print '>&nbsp;</option>';
1170  }
1171 
1172  print '<option value="0"';
1173  if (0 == $selected || ($selected == -1 && getDolGlobalString('MAIN_FREE_PRODUCT_CHECKED_BY_DEFAULT') == 'product')) {
1174  print ' selected';
1175  }
1176  print '>' . $langs->trans("Product");
1177 
1178  print '<option value="1"';
1179  if (1 == $selected || ($selected == -1 && getDolGlobalString('MAIN_FREE_PRODUCT_CHECKED_BY_DEFAULT') == 'service')) {
1180  print ' selected';
1181  }
1182  print '>' . $langs->trans("Service");
1183 
1184  print '</select>';
1185  print ajax_combobox('select_' . $htmlname);
1186  //if ($user->admin) print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"),1);
1187  }
1188  if ((empty($forceall) && !isModEnabled('product') && isModEnabled("service")) || $forceall == 3) {
1189  print $langs->trans("Service");
1190  print '<input type="hidden" name="' . $htmlname . '" value="1">';
1191  }
1192  if ((empty($forceall) && isModEnabled("product") && !isModEnabled('service')) || $forceall == 2) {
1193  print $langs->trans("Product");
1194  print '<input type="hidden" name="' . $htmlname . '" value="0">';
1195  }
1196  if ($forceall < 0) { // This should happened only for contracts when both predefined product and service are disabled.
1197  print '<input type="hidden" name="' . $htmlname . '" value="1">'; // By default we set on service for contract. If CONTRACT_SUPPORT_PRODUCTS is set, forceall should be 1 not -1
1198  }
1199  }
1200 
1201  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1202 
1208  public function load_cache_types_fees()
1209  {
1210  // phpcs:enable
1211  global $langs;
1212 
1213  $num = count($this->cache_types_fees);
1214  if ($num > 0) {
1215  return 0; // Cache already loaded
1216  }
1217 
1218  dol_syslog(__METHOD__, LOG_DEBUG);
1219 
1220  $langs->load("trips");
1221 
1222  $sql = "SELECT c.code, c.label";
1223  $sql .= " FROM " . $this->db->prefix() . "c_type_fees as c";
1224  $sql .= " WHERE active > 0";
1225 
1226  $resql = $this->db->query($sql);
1227  if ($resql) {
1228  $num = $this->db->num_rows($resql);
1229  $i = 0;
1230 
1231  while ($i < $num) {
1232  $obj = $this->db->fetch_object($resql);
1233 
1234  // Si traduction existe, on l'utilise, sinon on prend le libelle par defaut
1235  $label = ($obj->code != $langs->trans($obj->code) ? $langs->trans($obj->code) : $langs->trans($obj->label));
1236  $this->cache_types_fees[$obj->code] = $label;
1237  $i++;
1238  }
1239 
1240  asort($this->cache_types_fees);
1241 
1242  return $num;
1243  } else {
1244  dol_print_error($this->db);
1245  return -1;
1246  }
1247  }
1248 
1249  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1250 
1259  public function select_type_fees($selected = '', $htmlname = 'type', $showempty = 0)
1260  {
1261  // phpcs:enable
1262  global $user, $langs;
1263 
1264  dol_syslog(__METHOD__ . " selected=" . $selected . ", htmlname=" . $htmlname, LOG_DEBUG);
1265 
1266  $this->load_cache_types_fees();
1267 
1268  print '<select id="select_' . $htmlname . '" class="flat" name="' . $htmlname . '">';
1269  if ($showempty) {
1270  print '<option value="-1"';
1271  if ($selected == -1) {
1272  print ' selected';
1273  }
1274  print '>&nbsp;</option>';
1275  }
1276 
1277  foreach ($this->cache_types_fees as $key => $value) {
1278  print '<option value="' . $key . '"';
1279  if ($key == $selected) {
1280  print ' selected';
1281  }
1282  print '>';
1283  print $value;
1284  print '</option>';
1285  }
1286 
1287  print '</select>';
1288  if ($user->admin) {
1289  print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
1290  }
1291  }
1292 
1293 
1294  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1295 
1317  public function select_company($selected = '', $htmlname = 'socid', $filter = '', $showempty = '', $showtype = 0, $forcecombo = 0, $events = array(), $limit = 0, $morecss = 'minwidth100', $moreparam = '', $selected_input_value = '', $hidelabel = 1, $ajaxoptions = array(), $multiple = false, $excludeids = array(), $showcode = 0)
1318  {
1319  // phpcs:enable
1320  global $conf, $user, $langs;
1321 
1322  $out = '';
1323 
1324  if (!empty($conf->use_javascript_ajax) && !empty($conf->global->COMPANY_USE_SEARCH_TO_SELECT) && !$forcecombo) {
1325  if (is_null($ajaxoptions)) {
1326  $ajaxoptions = array();
1327  }
1328 
1329  require_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
1330 
1331  // No immediate load of all database
1332  $placeholder = '';
1333  if ($selected && empty($selected_input_value)) {
1334  require_once DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php';
1335  $societetmp = new Societe($this->db);
1336  $societetmp->fetch($selected);
1337  $selected_input_value = $societetmp->name;
1338  unset($societetmp);
1339  }
1340 
1341  // mode 1
1342  $urloption = 'htmlname=' . urlencode(str_replace('.', '_', $htmlname)) . '&outjson=1&filter=' . urlencode($filter) . (empty($excludeids) ? '' : '&excludeids=' . join(',', $excludeids)) . ($showtype ? '&showtype=' . urlencode($showtype) : '') . ($showcode ? '&showcode=' . urlencode($showcode) : '');
1343 
1344  $out .= '<!-- force css to be higher than dialog popup --><style type="text/css">.ui-autocomplete { z-index: 1010; }</style>';
1345  if (empty($hidelabel)) {
1346  print $langs->trans("RefOrLabel") . ' : ';
1347  } elseif ($hidelabel > 1) {
1348  $placeholder = $langs->trans("RefOrLabel");
1349  if ($hidelabel == 2) {
1350  $out .= img_picto($langs->trans("Search"), 'search');
1351  }
1352  }
1353  $out .= '<input type="text" class="' . $morecss . '" name="search_' . $htmlname . '" id="search_' . $htmlname . '" value="' . $selected_input_value . '"' . ($placeholder ? ' placeholder="' . dol_escape_htmltag($placeholder) . '"' : '') . ' ' . (!empty($conf->global->THIRDPARTY_SEARCH_AUTOFOCUS) ? 'autofocus' : '') . ' />';
1354  if ($hidelabel == 3) {
1355  $out .= img_picto($langs->trans("Search"), 'search');
1356  }
1357 
1358  $out .= ajax_event($htmlname, $events);
1359 
1360  $out .= ajax_autocompleter($selected, $htmlname, DOL_URL_ROOT . '/societe/ajax/company.php', $urloption, $conf->global->COMPANY_USE_SEARCH_TO_SELECT, 0, $ajaxoptions);
1361  } else {
1362  // Immediate load of all database
1363  $out .= $this->select_thirdparty_list($selected, $htmlname, $filter, $showempty, $showtype, $forcecombo, $events, '', 0, $limit, $morecss, $moreparam, $multiple, $excludeids, $showcode);
1364  }
1365 
1366  return $out;
1367  }
1368 
1369  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1370 
1394  public function select_thirdparty_list($selected = '', $htmlname = 'socid', $filter = '', $showempty = '', $showtype = 0, $forcecombo = 0, $events = array(), $filterkey = '', $outputmode = 0, $limit = 0, $morecss = 'minwidth100', $moreparam = '', $multiple = false, $excludeids = array(), $showcode = 0)
1395  {
1396  // phpcs:enable
1397  global $conf, $user, $langs;
1398  global $hookmanager;
1399 
1400  $out = '';
1401  $num = 0;
1402  $outarray = array();
1403 
1404  if ($selected === '') {
1405  $selected = array();
1406  } elseif (!is_array($selected)) {
1407  $selected = array($selected);
1408  }
1409 
1410  // Clean $filter that may contains sql conditions so sql code
1411  if (function_exists('testSqlAndScriptInject')) {
1412  if (testSqlAndScriptInject($filter, 3) > 0) {
1413  $filter = '';
1414  return 'SQLInjectionTryDetected';
1415  }
1416  }
1417 
1418  if (preg_match('/[\(\)]/', $filter)) {
1419  // If there is one parenthesis inside the criteria, we assume it is an Universal Filter Syntax.
1420  $errormsg = '';
1421  $filter = forgeSQLFromUniversalSearchCriteria($filter, $errormsg, 1);
1422 
1423  // Redo clean $filter that may contains sql conditions so sql code
1424  if (function_exists('testSqlAndScriptInject')) {
1425  if (testSqlAndScriptInject($filter, 3) > 0) {
1426  $filter = '';
1427  return 'SQLInjectionTryDetected';
1428  }
1429  }
1430  } else {
1431  // If not, we do nothing. We already no that there is no parenthesis
1432  // TODO Disallow this case in a future.
1433  dol_syslog("Warning, select_thirdparty_list was called with a filter criteria not using the Universal Search Syntax.", LOG_WARNING);
1434  }
1435 
1436  // We search companies
1437  $sql = "SELECT s.rowid, s.nom as name, s.name_alias, s.tva_intra, s.client, s.fournisseur, s.code_client, s.code_fournisseur";
1438  if (!empty($conf->global->COMPANY_SHOW_ADDRESS_SELECTLIST)) {
1439  $sql .= ", s.address, s.zip, s.town";
1440  $sql .= ", dictp.code as country_code";
1441  }
1442  $sql .= " FROM " . $this->db->prefix() . "societe as s";
1443  if (!empty($conf->global->COMPANY_SHOW_ADDRESS_SELECTLIST)) {
1444  $sql .= " LEFT JOIN " . $this->db->prefix() . "c_country as dictp ON dictp.rowid = s.fk_pays";
1445  }
1446  if (empty($user->rights->societe->client->voir) && !$user->socid) {
1447  $sql .= ", " . $this->db->prefix() . "societe_commerciaux as sc";
1448  }
1449  $sql .= " WHERE s.entity IN (" . getEntity('societe') . ")";
1450  if (!empty($user->socid)) {
1451  $sql .= " AND s.rowid = " . ((int) $user->socid);
1452  }
1453  if ($filter) {
1454  // $filter is safe because, if it contains '(' or ')', it has been sanitized by testSqlAndScriptInject() and forgeSQLFromUniversalSearchCriteria()
1455  // if not, by testSqlAndScriptInject() only.
1456  $sql .= " AND (" . $filter . ")";
1457  }
1458  if (empty($user->rights->societe->client->voir) && !$user->socid) {
1459  $sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = " . ((int) $user->id);
1460  }
1461  if (!empty($conf->global->COMPANY_HIDE_INACTIVE_IN_COMBOBOX)) {
1462  $sql .= " AND s.status <> 0";
1463  }
1464  if (!empty($excludeids)) {
1465  $sql .= " AND s.rowid NOT IN (" . $this->db->sanitize(join(',', $excludeids)) . ")";
1466  }
1467  // Add where from hooks
1468  $parameters = array();
1469  $reshook = $hookmanager->executeHooks('selectThirdpartyListWhere', $parameters); // Note that $action and $object may have been modified by hook
1470  $sql .= $hookmanager->resPrint;
1471  // Add criteria
1472  if ($filterkey && $filterkey != '') {
1473  $sql .= " AND (";
1474  $prefix = empty($conf->global->COMPANY_DONOTSEARCH_ANYWHERE) ? '%' : ''; // Can use index if COMPANY_DONOTSEARCH_ANYWHERE is on
1475  // For natural search
1476  $scrit = explode(' ', $filterkey);
1477  $i = 0;
1478  if (count($scrit) > 1) {
1479  $sql .= "(";
1480  }
1481  foreach ($scrit as $crit) {
1482  if ($i > 0) {
1483  $sql .= " AND ";
1484  }
1485  $sql .= "(s.nom LIKE '" . $this->db->escape($prefix . $crit) . "%')";
1486  $i++;
1487  }
1488  if (count($scrit) > 1) {
1489  $sql .= ")";
1490  }
1491  if (isModEnabled('barcode')) {
1492  $sql .= " OR s.barcode LIKE '" . $this->db->escape($prefix . $filterkey) . "%'";
1493  }
1494  $sql .= " OR s.code_client LIKE '" . $this->db->escape($prefix . $filterkey) . "%' OR s.code_fournisseur LIKE '" . $this->db->escape($prefix . $filterkey) . "%'";
1495  $sql .= " OR s.name_alias LIKE '" . $this->db->escape($prefix . $filterkey) . "%' OR s.tva_intra LIKE '" . $this->db->escape($prefix . $filterkey) . "%'";
1496  $sql .= ")";
1497  }
1498  $sql .= $this->db->order("nom", "ASC");
1499  $sql .= $this->db->plimit($limit, 0);
1500 
1501  // Build output string
1502  dol_syslog(get_class($this)."::select_thirdparty_list", LOG_DEBUG);
1503  $resql = $this->db->query($sql);
1504  if ($resql) {
1505  if (!$forcecombo) {
1506  include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
1507  $out .= ajax_combobox($htmlname, $events, getDolGlobalString("COMPANY_USE_SEARCH_TO_SELECT"));
1508  }
1509 
1510  // Construct $out and $outarray
1511  $out .= '<select id="' . $htmlname . '" class="flat' . ($morecss ? ' ' . $morecss : '') . '"' . ($moreparam ? ' ' . $moreparam : '') . ' name="' . $htmlname . ($multiple ? '[]' : '') . '" ' . ($multiple ? 'multiple' : '') . '>' . "\n";
1512 
1513  $textifempty = (($showempty && !is_numeric($showempty)) ? $langs->trans($showempty) : '');
1514  if (!empty($conf->global->COMPANY_USE_SEARCH_TO_SELECT)) {
1515  // Do not use textifempty = ' ' or '&nbsp;' here, or search on key will search on ' key'.
1516  //if (!empty($conf->use_javascript_ajax) || $forcecombo) $textifempty='';
1517  if ($showempty && !is_numeric($showempty)) {
1518  $textifempty = $langs->trans($showempty);
1519  } else {
1520  $textifempty .= $langs->trans("All");
1521  }
1522  }
1523  if ($showempty) {
1524  $out .= '<option value="-1" data-html="' . dol_escape_htmltag('<span class="opacitymedium">' . ($textifempty ? $textifempty : '&nbsp;') . '</span>') . '">' . $textifempty . '</option>' . "\n";
1525  }
1526 
1527  $companytemp = new Societe($this->db);
1528 
1529  $num = $this->db->num_rows($resql);
1530  $i = 0;
1531  if ($num) {
1532  while ($i < $num) {
1533  $obj = $this->db->fetch_object($resql);
1534  $label = '';
1535  if ($showcode || !empty($conf->global->SOCIETE_ADD_REF_IN_LIST)) {
1536  if (($obj->client) && (!empty($obj->code_client))) {
1537  $label = $obj->code_client . ' - ';
1538  }
1539  if (($obj->fournisseur) && (!empty($obj->code_fournisseur))) {
1540  $label .= $obj->code_fournisseur . ' - ';
1541  }
1542  $label .= ' ' . $obj->name;
1543  } else {
1544  $label = $obj->name;
1545  }
1546 
1547  if (!empty($obj->name_alias)) {
1548  $label .= ' (' . $obj->name_alias . ')';
1549  }
1550 
1551  if (!empty($conf->global->SOCIETE_SHOW_VAT_IN_LIST) && !empty($obj->tva_intra)) {
1552  $label .= ' - '.$obj->tva_intra;
1553  }
1554 
1555  $labelhtml = $label;
1556 
1557  if ($showtype) {
1558  $companytemp->id = $obj->rowid;
1559  $companytemp->client = $obj->client;
1560  $companytemp->fournisseur = $obj->fournisseur;
1561  $tmptype = $companytemp->getTypeUrl(1, '', 0, 'span');
1562  if ($tmptype) {
1563  $labelhtml .= ' ' . $tmptype;
1564  }
1565 
1566  if ($obj->client || $obj->fournisseur) {
1567  $label .= ' (';
1568  }
1569  if ($obj->client == 1 || $obj->client == 3) {
1570  $label .= $langs->trans("Customer");
1571  }
1572  if ($obj->client == 2 || $obj->client == 3) {
1573  $label .= ($obj->client == 3 ? ', ' : '') . $langs->trans("Prospect");
1574  }
1575  if ($obj->fournisseur) {
1576  $label .= ($obj->client ? ', ' : '') . $langs->trans("Supplier");
1577  }
1578  if ($obj->client || $obj->fournisseur) {
1579  $label .= ')';
1580  }
1581  }
1582 
1583  if (!empty($conf->global->COMPANY_SHOW_ADDRESS_SELECTLIST)) {
1584  $s = ($obj->address ? ' - ' . $obj->address : '') . ($obj->zip ? ' - ' . $obj->zip : '') . ($obj->town ? ' ' . $obj->town : '');
1585  if (!empty($obj->country_code)) {
1586  $s .= ', ' . $langs->trans('Country' . $obj->country_code);
1587  }
1588  $label .= $s;
1589  $labelhtml .= $s;
1590  }
1591 
1592  if (empty($outputmode)) {
1593  if (in_array($obj->rowid, $selected)) {
1594  $out .= '<option value="' . $obj->rowid . '" selected data-html="' . dol_escape_htmltag($labelhtml, 0, 0, '', 0, 1) . '">' . dol_escape_htmltag($label, 0, 0, '', 0, 1) . '</option>';
1595  } else {
1596  $out .= '<option value="' . $obj->rowid . '" data-html="' . dol_escape_htmltag($labelhtml, 0, 0, '', 0, 1) . '">' . dol_escape_htmltag($label, 0, 0, '', 0, 1) . '</option>';
1597  }
1598  } else {
1599  array_push($outarray, array('key' => $obj->rowid, 'value' => $label, 'label' => $label, 'labelhtml' => $labelhtml));
1600  }
1601 
1602  $i++;
1603  if (($i % 10) == 0) {
1604  $out .= "\n";
1605  }
1606  }
1607  }
1608  $out .= '</select>' . "\n";
1609  } else {
1610  dol_print_error($this->db);
1611  }
1612 
1613  $this->result = array('nbofthirdparties' => $num);
1614 
1615  if ($outputmode) {
1616  return $outarray;
1617  }
1618  return $out;
1619  }
1620 
1621 
1622  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1623 
1634  public function select_remises($selected, $htmlname, $filter, $socid, $maxvalue = 0)
1635  {
1636  // phpcs:enable
1637  global $langs, $conf;
1638 
1639  // On recherche les remises
1640  $sql = "SELECT re.rowid, re.amount_ht, re.amount_tva, re.amount_ttc,";
1641  $sql .= " re.description, re.fk_facture_source";
1642  $sql .= " FROM " . $this->db->prefix() . "societe_remise_except as re";
1643  $sql .= " WHERE re.fk_soc = " . (int) $socid;
1644  $sql .= " AND re.entity = " . $conf->entity;
1645  if ($filter) {
1646  $sql .= " AND " . $filter;
1647  }
1648  $sql .= " ORDER BY re.description ASC";
1649 
1650  dol_syslog(get_class($this) . "::select_remises", LOG_DEBUG);
1651  $resql = $this->db->query($sql);
1652  if ($resql) {
1653  print '<select id="select_' . $htmlname . '" class="flat maxwidthonsmartphone" name="' . $htmlname . '">';
1654  $num = $this->db->num_rows($resql);
1655 
1656  $qualifiedlines = $num;
1657 
1658  $i = 0;
1659  if ($num) {
1660  print '<option value="0">&nbsp;</option>';
1661  while ($i < $num) {
1662  $obj = $this->db->fetch_object($resql);
1663  $desc = dol_trunc($obj->description, 40);
1664  if (preg_match('/\(CREDIT_NOTE\)/', $desc)) {
1665  $desc = preg_replace('/\(CREDIT_NOTE\)/', $langs->trans("CreditNote"), $desc);
1666  }
1667  if (preg_match('/\(DEPOSIT\)/', $desc)) {
1668  $desc = preg_replace('/\(DEPOSIT\)/', $langs->trans("Deposit"), $desc);
1669  }
1670  if (preg_match('/\(EXCESS RECEIVED\)/', $desc)) {
1671  $desc = preg_replace('/\(EXCESS RECEIVED\)/', $langs->trans("ExcessReceived"), $desc);
1672  }
1673  if (preg_match('/\(EXCESS PAID\)/', $desc)) {
1674  $desc = preg_replace('/\(EXCESS PAID\)/', $langs->trans("ExcessPaid"), $desc);
1675  }
1676 
1677  $selectstring = '';
1678  if ($selected > 0 && $selected == $obj->rowid) {
1679  $selectstring = ' selected';
1680  }
1681 
1682  $disabled = '';
1683  if ($maxvalue > 0 && $obj->amount_ttc > $maxvalue) {
1684  $qualifiedlines--;
1685  $disabled = ' disabled';
1686  }
1687 
1688  if (!empty($conf->global->MAIN_SHOW_FACNUMBER_IN_DISCOUNT_LIST) && !empty($obj->fk_facture_source)) {
1689  $tmpfac = new Facture($this->db);
1690  if ($tmpfac->fetch($obj->fk_facture_source) > 0) {
1691  $desc = $desc . ' - ' . $tmpfac->ref;
1692  }
1693  }
1694 
1695  print '<option value="' . $obj->rowid . '"' . $selectstring . $disabled . '>' . $desc . ' (' . price($obj->amount_ht) . ' ' . $langs->trans("HT") . ' - ' . price($obj->amount_ttc) . ' ' . $langs->trans("TTC") . ')</option>';
1696  $i++;
1697  }
1698  }
1699  print '</select>';
1700  print ajax_combobox('select_' . $htmlname);
1701 
1702  return $qualifiedlines;
1703  } else {
1704  dol_print_error($this->db);
1705  return -1;
1706  }
1707  }
1708 
1709  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1710 
1731  public function select_contacts($socid, $selected = '', $htmlname = 'contactid', $showempty = 0, $exclude = '', $limitto = '', $showfunction = 0, $morecss = '', $showsoc = 0, $forcecombo = 0, $events = array(), $options_only = false, $moreparam = '', $htmlid = '')
1732  {
1733  // phpcs:enable
1734  print $this->selectcontacts($socid, $selected, $htmlname, $showempty, $exclude, $limitto, $showfunction, $morecss, $options_only, $showsoc, $forcecombo, $events, $moreparam, $htmlid);
1735  return $this->num;
1736  }
1737 
1762  public function selectcontacts($socid, $selected = '', $htmlname = 'contactid', $showempty = 0, $exclude = '', $limitto = '', $showfunction = 0, $morecss = '', $options_only = false, $showsoc = 0, $forcecombo = 0, $events = array(), $moreparam = '', $htmlid = '', $multiple = false, $disableifempty = 0)
1763  {
1764  global $conf, $langs, $hookmanager, $action;
1765 
1766  $langs->load('companies');
1767 
1768  if (empty($htmlid)) {
1769  $htmlid = $htmlname;
1770  }
1771  $num = 0;
1772 
1773  if ($selected === '') {
1774  $selected = array();
1775  } elseif (!is_array($selected)) {
1776  $selected = array($selected);
1777  }
1778  $out = '';
1779 
1780  if (!is_object($hookmanager)) {
1781  include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
1782  $hookmanager = new HookManager($this->db);
1783  }
1784 
1785  // We search third parties
1786  $sql = "SELECT sp.rowid, sp.lastname, sp.statut, sp.firstname, sp.poste, sp.email, sp.phone, sp.phone_perso, sp.phone_mobile, sp.town AS contact_town";
1787  if ($showsoc > 0 || !empty($conf->global->CONTACT_SHOW_EMAIL_PHONE_TOWN_SELECTLIST)) {
1788  $sql .= ", s.nom as company, s.town AS company_town";
1789  }
1790  $sql .= " FROM " . $this->db->prefix() . "socpeople as sp";
1791  if ($showsoc > 0 || !empty($conf->global->CONTACT_SHOW_EMAIL_PHONE_TOWN_SELECTLIST)) {
1792  $sql .= " LEFT OUTER JOIN " . $this->db->prefix() . "societe as s ON s.rowid=sp.fk_soc";
1793  }
1794  $sql .= " WHERE sp.entity IN (" . getEntity('contact') . ")";
1795  if ($socid > 0 || $socid == -1) {
1796  $sql .= " AND sp.fk_soc = " . ((int) $socid);
1797  }
1798  if (!empty($conf->global->CONTACT_HIDE_INACTIVE_IN_COMBOBOX)) {
1799  $sql .= " AND sp.statut <> 0";
1800  }
1801  // Add where from hooks
1802  $parameters = array();
1803  $reshook = $hookmanager->executeHooks('selectContactListWhere', $parameters); // Note that $action and $object may have been modified by hook
1804  $sql .= $hookmanager->resPrint;
1805  $sql .= " ORDER BY sp.lastname ASC";
1806 
1807  dol_syslog(get_class($this) . "::selectcontacts", LOG_DEBUG);
1808  $resql = $this->db->query($sql);
1809  if ($resql) {
1810  $num = $this->db->num_rows($resql);
1811 
1812  if ($htmlname != 'none' && !$options_only) {
1813  $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" id="' . $htmlid . '" name="' . $htmlname . (($num || empty($disableifempty)) ? '' : ' disabled') . ($multiple ? '[]' : '') . '" ' . ($multiple ? 'multiple' : '') . ' ' . (!empty($moreparam) ? $moreparam : '') . '>';
1814  }
1815 
1816  if ($showempty && !is_numeric($showempty)) {
1817  $textforempty = $showempty;
1818  $out .= '<option class="optiongrey" value="-1"' . (in_array(-1, $selected) ? ' selected' : '') . '>' . $textforempty . '</option>';
1819  } else {
1820  if (($showempty == 1 || ($showempty == 3 && $num > 1)) && !$multiple) {
1821  $out .= '<option value="0"' . (in_array(0, $selected) ? ' selected' : '') . '>&nbsp;</option>';
1822  }
1823  if ($showempty == 2) {
1824  $out .= '<option value="0"' . (in_array(0, $selected) ? ' selected' : '') . '>-- ' . $langs->trans("Internal") . ' --</option>';
1825  }
1826  }
1827 
1828  $i = 0;
1829  if ($num) {
1830  include_once DOL_DOCUMENT_ROOT . '/contact/class/contact.class.php';
1831  $contactstatic = new Contact($this->db);
1832 
1833  while ($i < $num) {
1834  $obj = $this->db->fetch_object($resql);
1835 
1836  // Set email (or phones) and town extended infos
1837  $extendedInfos = '';
1838  if (!empty($conf->global->CONTACT_SHOW_EMAIL_PHONE_TOWN_SELECTLIST)) {
1839  $extendedInfos = array();
1840  $email = trim($obj->email);
1841  if (!empty($email)) {
1842  $extendedInfos[] = $email;
1843  } else {
1844  $phone = trim($obj->phone);
1845  $phone_perso = trim($obj->phone_perso);
1846  $phone_mobile = trim($obj->phone_mobile);
1847  if (!empty($phone)) {
1848  $extendedInfos[] = $phone;
1849  }
1850  if (!empty($phone_perso)) {
1851  $extendedInfos[] = $phone_perso;
1852  }
1853  if (!empty($phone_mobile)) {
1854  $extendedInfos[] = $phone_mobile;
1855  }
1856  }
1857  $contact_town = trim($obj->contact_town);
1858  $company_town = trim($obj->company_town);
1859  if (!empty($contact_town)) {
1860  $extendedInfos[] = $contact_town;
1861  } elseif (!empty($company_town)) {
1862  $extendedInfos[] = $company_town;
1863  }
1864  $extendedInfos = implode(' - ', $extendedInfos);
1865  if (!empty($extendedInfos)) {
1866  $extendedInfos = ' - ' . $extendedInfos;
1867  }
1868  }
1869 
1870  $contactstatic->id = $obj->rowid;
1871  $contactstatic->lastname = $obj->lastname;
1872  $contactstatic->firstname = $obj->firstname;
1873  if ($obj->statut == 1) {
1874  if ($htmlname != 'none') {
1875  $disabled = 0;
1876  if (is_array($exclude) && count($exclude) && in_array($obj->rowid, $exclude)) {
1877  $disabled = 1;
1878  }
1879  if (is_array($limitto) && count($limitto) && !in_array($obj->rowid, $limitto)) {
1880  $disabled = 1;
1881  }
1882  if (!empty($selected) && in_array($obj->rowid, $selected)) {
1883  $out .= '<option value="' . $obj->rowid . '"';
1884  if ($disabled) {
1885  $out .= ' disabled';
1886  }
1887  $out .= ' selected>';
1888  $out .= $contactstatic->getFullName($langs) . $extendedInfos;
1889  if ($showfunction && $obj->poste) {
1890  $out .= ' (' . $obj->poste . ')';
1891  }
1892  if (($showsoc > 0) && $obj->company) {
1893  $out .= ' - (' . $obj->company . ')';
1894  }
1895  $out .= '</option>';
1896  } else {
1897  $out .= '<option value="' . $obj->rowid . '"';
1898  if ($disabled) {
1899  $out .= ' disabled';
1900  }
1901  $out .= '>';
1902  $out .= $contactstatic->getFullName($langs) . $extendedInfos;
1903  if ($showfunction && $obj->poste) {
1904  $out .= ' (' . $obj->poste . ')';
1905  }
1906  if (($showsoc > 0) && $obj->company) {
1907  $out .= ' - (' . $obj->company . ')';
1908  }
1909  $out .= '</option>';
1910  }
1911  } else {
1912  if (in_array($obj->rowid, $selected)) {
1913  $out .= $contactstatic->getFullName($langs) . $extendedInfos;
1914  if ($showfunction && $obj->poste) {
1915  $out .= ' (' . $obj->poste . ')';
1916  }
1917  if (($showsoc > 0) && $obj->company) {
1918  $out .= ' - (' . $obj->company . ')';
1919  }
1920  }
1921  }
1922  }
1923  $i++;
1924  }
1925  } else {
1926  $labeltoshow = ($socid != -1) ? ($langs->trans($socid ? "NoContactDefinedForThirdParty" : "NoContactDefined")) : $langs->trans('SelectAThirdPartyFirst');
1927  $out .= '<option class="disabled" value="-1"' . (($showempty == 2 || $multiple) ? '' : ' selected') . ' disabled="disabled">';
1928  $out .= $labeltoshow;
1929  $out .= '</option>';
1930  }
1931 
1932  $parameters = array(
1933  'socid' => $socid,
1934  'htmlname' => $htmlname,
1935  'resql' => $resql,
1936  'out' => &$out,
1937  'showfunction' => $showfunction,
1938  'showsoc' => $showsoc,
1939  );
1940 
1941  $reshook = $hookmanager->executeHooks('afterSelectContactOptions', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1942 
1943  if ($htmlname != 'none' && !$options_only) {
1944  $out .= '</select>';
1945  }
1946 
1947  if ($conf->use_javascript_ajax && !$forcecombo && !$options_only) {
1948  include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
1949  $out .= ajax_combobox($htmlid, $events, getDolGlobalString("CONTACT_USE_SEARCH_TO_SELECT"));
1950  }
1951 
1952  $this->num = $num;
1953  return $out;
1954  } else {
1955  dol_print_error($this->db);
1956  return -1;
1957  }
1958  }
1959 
1960  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1961 
1977  public function select_users($selected = '', $htmlname = 'userid', $show_empty = 0, $exclude = null, $disabled = 0, $include = '', $enableonly = '', $force_entity = '0')
1978  {
1979  // phpcs:enable
1980  print $this->select_dolusers($selected, $htmlname, $show_empty, $exclude, $disabled, $include, $enableonly, $force_entity);
1981  }
1982 
1983  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1984 
2009  public function select_dolusers($selected = '', $htmlname = 'userid', $show_empty = 0, $exclude = null, $disabled = 0, $include = '', $enableonly = '', $force_entity = '0', $maxlength = 0, $showstatus = 0, $morefilter = '', $show_every = 0, $enableonlytext = '', $morecss = '', $notdisabled = 0, $outputmode = 0, $multiple = false, $forcecombo = 0)
2010  {
2011  // phpcs:enable
2012  global $conf, $user, $langs, $hookmanager;
2013  global $action;
2014 
2015  // If no preselected user defined, we take current user
2016  if ((is_numeric($selected) && ($selected < -2 || empty($selected))) && empty($conf->global->SOCIETE_DISABLE_DEFAULT_SALESREPRESENTATIVE)) {
2017  $selected = $user->id;
2018  }
2019 
2020  if ($selected === '') {
2021  $selected = array();
2022  } elseif (!is_array($selected)) {
2023  $selected = array($selected);
2024  }
2025 
2026  $excludeUsers = null;
2027  $includeUsers = null;
2028 
2029  // Exclude some users
2030  if (is_array($exclude)) {
2031  $excludeUsers = implode(",", $exclude);
2032  }
2033  // Include some uses
2034  if (is_array($include)) {
2035  $includeUsers = implode(",", $include);
2036  } elseif ($include == 'hierarchy') {
2037  // Build list includeUsers to have only hierarchy
2038  $includeUsers = implode(",", $user->getAllChildIds(0));
2039  } elseif ($include == 'hierarchyme') {
2040  // Build list includeUsers to have only hierarchy and current user
2041  $includeUsers = implode(",", $user->getAllChildIds(1));
2042  }
2043 
2044  $out = '';
2045  $outarray = array();
2046  $outarray2 = array();
2047 
2048  // Forge request to select users
2049  $sql = "SELECT DISTINCT u.rowid, u.lastname as lastname, u.firstname, u.statut as status, u.login, u.admin, u.entity, u.photo";
2050  if (isModEnabled('multicompany') && $conf->entity == 1 && $user->admin && !$user->entity) {
2051  $sql .= ", e.label";
2052  }
2053  $sql .= " FROM " . $this->db->prefix() . "user as u";
2054  if (isModEnabled('multicompany') && $conf->entity == 1 && $user->admin && !$user->entity) {
2055  $sql .= " LEFT JOIN " . $this->db->prefix() . "entity as e ON e.rowid = u.entity";
2056  if ($force_entity) {
2057  $sql .= " WHERE u.entity IN (0, " . $this->db->sanitize($force_entity) . ")";
2058  } else {
2059  $sql .= " WHERE u.entity IS NOT NULL";
2060  }
2061  } else {
2062  if (isModEnabled('multicompany') && !empty($conf->global->MULTICOMPANY_TRANSVERSE_MODE)) {
2063  $sql .= " LEFT JOIN " . $this->db->prefix() . "usergroup_user as ug";
2064  $sql .= " ON ug.fk_user = u.rowid";
2065  $sql .= " WHERE ug.entity = " . (int) $conf->entity;
2066  } else {
2067  $sql .= " WHERE u.entity IN (0, " . ((int) $conf->entity) . ")";
2068  }
2069  }
2070  if (!empty($user->socid)) {
2071  $sql .= " AND u.fk_soc = " . ((int) $user->socid);
2072  }
2073  if (is_array($exclude) && $excludeUsers) {
2074  $sql .= " AND u.rowid NOT IN (" . $this->db->sanitize($excludeUsers) . ")";
2075  }
2076  if ($includeUsers) {
2077  $sql .= " AND u.rowid IN (" . $this->db->sanitize($includeUsers) . ")";
2078  }
2079  if (!empty($conf->global->USER_HIDE_INACTIVE_IN_COMBOBOX) || $notdisabled) {
2080  $sql .= " AND u.statut <> 0";
2081  }
2082  if (!empty($morefilter)) {
2083  $sql .= " " . $morefilter;
2084  }
2085 
2086  //Add hook to filter on user (for exemple on usergroup define in custom modules)
2087  $reshook = $hookmanager->executeHooks('addSQLWhereFilterOnSelectUsers', array(), $this, $action);
2088  if (!empty($reshook)) {
2089  $sql .= $hookmanager->resPrint;
2090  }
2091 
2092  if (empty($conf->global->MAIN_FIRSTNAME_NAME_POSITION)) { // MAIN_FIRSTNAME_NAME_POSITION is 0 means firstname+lastname
2093  $sql .= " ORDER BY u.statut DESC, u.firstname ASC, u.lastname ASC";
2094  } else {
2095  $sql .= " ORDER BY u.statut DESC, u.lastname ASC, u.firstname ASC";
2096  }
2097 
2098  dol_syslog(get_class($this) . "::select_dolusers", LOG_DEBUG);
2099 
2100  $resql = $this->db->query($sql);
2101  if ($resql) {
2102  $num = $this->db->num_rows($resql);
2103  $i = 0;
2104  if ($num) {
2105  // do not use maxwidthonsmartphone by default. Set it by caller so auto size to 100% will work when not defined
2106  $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : ' minwidth200') . '" id="' . $htmlname . '" name="' . $htmlname . ($multiple ? '[]' : '') . '" ' . ($multiple ? 'multiple' : '') . ' ' . ($disabled ? ' disabled' : '') . '>';
2107  if ($show_empty && !$multiple) {
2108  $textforempty = ' ';
2109  if (!empty($conf->use_javascript_ajax)) {
2110  $textforempty = '&nbsp;'; // If we use ajaxcombo, we need &nbsp; here to avoid to have an empty element that is too small.
2111  }
2112  if (!is_numeric($show_empty)) {
2113  $textforempty = $show_empty;
2114  }
2115  $out .= '<option class="optiongrey" value="' . ($show_empty < 0 ? $show_empty : -1) . '"' . ((empty($selected) || in_array(-1, $selected)) ? ' selected' : '') . '>' . $textforempty . '</option>' . "\n";
2116  }
2117  if ($show_every) {
2118  $out .= '<option value="-2"' . ((in_array(-2, $selected)) ? ' selected' : '') . '>-- ' . $langs->trans("Everybody") . ' --</option>' . "\n";
2119  }
2120 
2121  $userstatic = new User($this->db);
2122 
2123  while ($i < $num) {
2124  $obj = $this->db->fetch_object($resql);
2125 
2126  $userstatic->id = $obj->rowid;
2127  $userstatic->lastname = $obj->lastname;
2128  $userstatic->firstname = $obj->firstname;
2129  $userstatic->photo = $obj->photo;
2130  $userstatic->statut = $obj->status;
2131  $userstatic->entity = $obj->entity;
2132  $userstatic->admin = $obj->admin;
2133 
2134  $disableline = '';
2135  if (is_array($enableonly) && count($enableonly) && !in_array($obj->rowid, $enableonly)) {
2136  $disableline = ($enableonlytext ? $enableonlytext : '1');
2137  }
2138 
2139  $labeltoshow = '';
2140  $labeltoshowhtml = '';
2141 
2142  // $fullNameMode is 0=Lastname+Firstname (MAIN_FIRSTNAME_NAME_POSITION=1), 1=Firstname+Lastname (MAIN_FIRSTNAME_NAME_POSITION=0)
2143  $fullNameMode = 0;
2144  if (empty($conf->global->MAIN_FIRSTNAME_NAME_POSITION)) {
2145  $fullNameMode = 1; //Firstname+lastname
2146  }
2147  $labeltoshow .= $userstatic->getFullName($langs, $fullNameMode, -1, $maxlength);
2148  $labeltoshowhtml .= $userstatic->getFullName($langs, $fullNameMode, -1, $maxlength);
2149  if (empty($obj->firstname) && empty($obj->lastname)) {
2150  $labeltoshow .= $obj->login;
2151  $labeltoshowhtml .= $obj->login;
2152  }
2153 
2154  // Complete name with a more info string like: ' (info1 - info2 - ...)'
2155  $moreinfo = '';
2156  $moreinfohtml = '';
2157  if (!empty($conf->global->MAIN_SHOW_LOGIN)) {
2158  $moreinfo .= ($moreinfo ? ' - ' : ' (');
2159  $moreinfohtml .= ($moreinfohtml ? ' - ' : ' <span class="opacitymedium">(');
2160  $moreinfo .= $obj->login;
2161  $moreinfohtml .= $obj->login;
2162  }
2163  if ($showstatus >= 0) {
2164  if ($obj->status == 1 && $showstatus == 1) {
2165  $moreinfo .= ($moreinfo ? ' - ' : ' (') . $langs->trans('Enabled');
2166  $moreinfohtml .= ($moreinfohtml ? ' - ' : ' <span class="opacitymedium">(') . $langs->trans('Enabled');
2167  }
2168  if ($obj->status == 0 && $showstatus == 1) {
2169  $moreinfo .= ($moreinfo ? ' - ' : ' (') . $langs->trans('Disabled');
2170  $moreinfohtml .= ($moreinfohtml ? ' - ' : ' <span class="opacitymedium">(') . $langs->trans('Disabled');
2171  }
2172  }
2173  if (isModEnabled('multicompany') && empty($conf->global->MULTICOMPANY_TRANSVERSE_MODE) && $conf->entity == 1 && $user->admin && !$user->entity) {
2174  if (!$obj->entity) {
2175  $moreinfo .= ($moreinfo ? ' - ' : ' (') . $langs->trans("AllEntities");
2176  $moreinfohtml .= ($moreinfohtml ? ' - ' : ' <span class="opacitymedium">(') . $langs->trans("AllEntities");
2177  } else {
2178  if ($obj->entity != $conf->entity) {
2179  $moreinfo .= ($moreinfo ? ' - ' : ' (') . ($obj->label ? $obj->label : $langs->trans("EntityNameNotDefined"));
2180  $moreinfohtml .= ($moreinfohtml ? ' - ' : ' <span class="opacitymedium">(') . ($obj->label ? $obj->label : $langs->trans("EntityNameNotDefined"));
2181  }
2182  }
2183  }
2184  $moreinfo .= ($moreinfo ? ')' : '');
2185  $moreinfohtml .= ($moreinfohtml ? ')</span>' : '');
2186  if ($disableline && $disableline != '1') {
2187  // Add text from $enableonlytext parameter
2188  $moreinfo .= ' - ' . $disableline;
2189  $moreinfohtml .= ' - ' . $disableline;
2190  }
2191  $labeltoshow .= $moreinfo;
2192  $labeltoshowhtml .= $moreinfohtml;
2193 
2194  $out .= '<option value="' . $obj->rowid . '"';
2195  if ($disableline) {
2196  $out .= ' disabled';
2197  }
2198  if ((is_object($selected) && $selected->id == $obj->rowid) || (!is_object($selected) && in_array($obj->rowid, $selected))) {
2199  $out .= ' selected';
2200  }
2201  $out .= ' data-html="';
2202  $outhtml = $userstatic->getNomUrl(-3, '', 0, 1, 24, 1, 'login', '', 1) . ' ';
2203  if ($showstatus >= 0 && $obj->status == 0) {
2204  $outhtml .= '<strike class="opacitymediumxxx">';
2205  }
2206  $outhtml .= $labeltoshowhtml;
2207  if ($showstatus >= 0 && $obj->status == 0) {
2208  $outhtml .= '</strike>';
2209  }
2210  $out .= dol_escape_htmltag($outhtml);
2211  $out .= '">';
2212  $out .= $labeltoshow;
2213  $out .= '</option>';
2214 
2215  $outarray[$userstatic->id] = $userstatic->getFullName($langs, $fullNameMode, -1, $maxlength) . $moreinfo;
2216  $outarray2[$userstatic->id] = array(
2217  'id'=>$userstatic->id,
2218  'label'=>$labeltoshow,
2219  'labelhtml'=>$labeltoshowhtml,
2220  'color'=>'',
2221  'picto'=>''
2222  );
2223 
2224  $i++;
2225  }
2226  } else {
2227  $out .= '<select class="flat" id="' . $htmlname . '" name="' . $htmlname . '" disabled>';
2228  $out .= '<option value="">' . $langs->trans("None") . '</option>';
2229  }
2230  $out .= '</select>';
2231 
2232  if ($num && !$forcecombo) {
2233  // Enhance with select2
2234  include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
2235  $out .= ajax_combobox($htmlname);
2236  }
2237  } else {
2238  dol_print_error($this->db);
2239  }
2240 
2241  if ($outputmode == 2) {
2242  return $outarray2;
2243  } elseif ($outputmode) {
2244  return $outarray;
2245  }
2246 
2247  return $out;
2248  }
2249 
2250 
2251  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2252 
2275  public function select_dolusers_forevent($action = '', $htmlname = 'userid', $show_empty = 0, $exclude = null, $disabled = 0, $include = '', $enableonly = '', $force_entity = '0', $maxlength = 0, $showstatus = 0, $morefilter = '', $showproperties = 0, $listofuserid = array(), $listofcontactid = array(), $listofotherid = array())
2276  {
2277  // phpcs:enable
2278  global $conf, $user, $langs;
2279 
2280  $userstatic = new User($this->db);
2281  $out = '';
2282 
2283 
2284  $assignedtouser = array();
2285  if (!empty($_SESSION['assignedtouser'])) {
2286  $assignedtouser = json_decode($_SESSION['assignedtouser'], true);
2287  }
2288  $nbassignetouser = count($assignedtouser);
2289 
2290  //if ($nbassignetouser && $action != 'view') $out .= '<br>';
2291  if ($nbassignetouser) {
2292  $out .= '<ul class="attendees">';
2293  }
2294  $i = 0;
2295  $ownerid = 0;
2296  foreach ($assignedtouser as $key => $value) {
2297  if ($value['id'] == $ownerid) {
2298  continue;
2299  }
2300 
2301  $out .= '<li>';
2302  $userstatic->fetch($value['id']);
2303  $out .= $userstatic->getNomUrl(-1);
2304  if ($i == 0) {
2305  $ownerid = $value['id'];
2306  $out .= ' (' . $langs->trans("Owner") . ')';
2307  }
2308  if ($nbassignetouser > 1 && $action != 'view') {
2309  $out .= ' <input type="image" style="border: 0px;" src="' . img_picto($langs->trans("Remove"), 'delete', '', 0, 1) . '" value="' . $userstatic->id . '" class="removedassigned reposition" id="removedassigned_' . $userstatic->id . '" name="removedassigned_' . $userstatic->id . '">';
2310  }
2311  // Show my availability
2312  if ($showproperties) {
2313  if ($ownerid == $value['id'] && is_array($listofuserid) && count($listofuserid) && in_array($ownerid, array_keys($listofuserid))) {
2314  $out .= '<div class="myavailability inline-block">';
2315  $out .= '<span class="hideonsmartphone">&nbsp;-&nbsp;<span class="opacitymedium">' . $langs->trans("Availability") . ':</span> </span><input id="transparency" class="paddingrightonly" ' . ($action == 'view' ? 'disabled' : '') . ' type="checkbox" name="transparency"' . ($listofuserid[$ownerid]['transparency'] ? ' checked' : '') . '><label for="transparency">' . $langs->trans("Busy") . '</label>';
2316  $out .= '</div>';
2317  }
2318  }
2319  //$out.=' '.($value['mandatory']?$langs->trans("Mandatory"):$langs->trans("Optional"));
2320  //$out.=' '.($value['transparency']?$langs->trans("Busy"):$langs->trans("NotBusy"));
2321 
2322  $out .= '</li>';
2323  $i++;
2324  }
2325  if ($nbassignetouser) {
2326  $out .= '</ul>';
2327  }
2328 
2329  // Method with no ajax
2330  if ($action != 'view') {
2331  $out .= '<input type="hidden" class="removedassignedhidden" name="removedassigned" value="">';
2332  $out .= '<script nonce="' . getNonce() . '" type="text/javascript">jQuery(document).ready(function () {';
2333  $out .= 'jQuery(".removedassigned").click(function() { jQuery(".removedassignedhidden").val(jQuery(this).val()); });';
2334  $out .= 'jQuery(".assignedtouser").change(function() { console.log(jQuery(".assignedtouser option:selected").val());';
2335  $out .= ' if (jQuery(".assignedtouser option:selected").val() > 0) { jQuery("#' . $action . 'assignedtouser").attr("disabled", false); }';
2336  $out .= ' else { jQuery("#' . $action . 'assignedtouser").attr("disabled", true); }';
2337  $out .= '});';
2338  $out .= '})</script>';
2339  $out .= $this->select_dolusers('', $htmlname, $show_empty, $exclude, $disabled, $include, $enableonly, $force_entity, $maxlength, $showstatus, $morefilter);
2340  $out .= ' <input type="submit" disabled class="button valignmiddle smallpaddingimp reposition" id="' . $action . 'assignedtouser" name="' . $action . 'assignedtouser" value="' . dol_escape_htmltag($langs->trans("Add")) . '">';
2341  $out .= '<br>';
2342  }
2343 
2344  return $out;
2345  }
2346 
2347 
2348  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2349 
2377  public function select_produits($selected = '', $htmlname = 'productid', $filtertype = '', $limit = 0, $price_level = 0, $status = 1, $finished = 2, $selected_input_value = '', $hidelabel = 0, $ajaxoptions = array(), $socid = 0, $showempty = '1', $forcecombo = 0, $morecss = '', $hidepriceinlabel = 0, $warehouseStatus = '', $selected_combinations = null, $nooutput = 0, $status_purchase = -1)
2378  {
2379  // phpcs:enable
2380  global $langs, $conf;
2381 
2382  $out = '';
2383 
2384  // check parameters
2385  $price_level = (!empty($price_level) ? $price_level : 0);
2386  if (is_null($ajaxoptions)) {
2387  $ajaxoptions = array();
2388  }
2389 
2390  if (strval($filtertype) === '' && (isModEnabled("product") || isModEnabled("service"))) {
2391  if (isModEnabled("product") && !isModEnabled('service')) {
2392  $filtertype = '0';
2393  } elseif (!isModEnabled('product') && isModEnabled("service")) {
2394  $filtertype = '1';
2395  }
2396  }
2397 
2398  if (!empty($conf->use_javascript_ajax) && !empty($conf->global->PRODUIT_USE_SEARCH_TO_SELECT)) {
2399  $placeholder = '';
2400 
2401  if ($selected && empty($selected_input_value)) {
2402  require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
2403  $producttmpselect = new Product($this->db);
2404  $producttmpselect->fetch($selected);
2405  $selected_input_value = $producttmpselect->ref;
2406  unset($producttmpselect);
2407  }
2408  // handle case where product or service module is disabled + no filter specified
2409  if ($filtertype == '') {
2410  if (!isModEnabled('product')) { // when product module is disabled, show services only
2411  $filtertype = 1;
2412  } elseif (!isModEnabled('service')) { // when service module is disabled, show products only
2413  $filtertype = 0;
2414  }
2415  }
2416  // mode=1 means customers products
2417  $urloption = ($socid > 0 ? 'socid=' . $socid . '&' : '') . 'htmlname=' . $htmlname . '&outjson=1&price_level=' . $price_level . '&type=' . $filtertype . '&mode=1&status=' . $status . '&status_purchase=' . $status_purchase . '&finished=' . $finished . '&hidepriceinlabel=' . $hidepriceinlabel . '&warehousestatus=' . $warehouseStatus;
2418  $out .= ajax_autocompleter($selected, $htmlname, DOL_URL_ROOT . '/product/ajax/products.php', $urloption, $conf->global->PRODUIT_USE_SEARCH_TO_SELECT, 1, $ajaxoptions);
2419 
2420  if (isModEnabled('variants') && is_array($selected_combinations)) {
2421  // Code to automatically insert with javascript the select of attributes under the select of product
2422  // when a parent of variant has been selected.
2423  $out .= '
2424  <!-- script to auto show attributes select tags if a variant was selected -->
2425  <script nonce="' . getNonce() . '">
2426  // auto show attributes fields
2427  selected = ' . json_encode($selected_combinations) . ';
2428  combvalues = {};
2429 
2430  jQuery(document).ready(function () {
2431 
2432  jQuery("input[name=\'prod_entry_mode\']").change(function () {
2433  if (jQuery(this).val() == \'free\') {
2434  jQuery(\'div#attributes_box\').empty();
2435  }
2436  });
2437 
2438  jQuery("input#' . $htmlname . '").change(function () {
2439 
2440  if (!jQuery(this).val()) {
2441  jQuery(\'div#attributes_box\').empty();
2442  return;
2443  }
2444 
2445  console.log("A change has started. We get variants fields to inject html select");
2446 
2447  jQuery.getJSON("' . DOL_URL_ROOT . '/variants/ajax/getCombinations.php", {
2448  id: jQuery(this).val()
2449  }, function (data) {
2450  jQuery(\'div#attributes_box\').empty();
2451 
2452  jQuery.each(data, function (key, val) {
2453 
2454  combvalues[val.id] = val.values;
2455 
2456  var span = jQuery(document.createElement(\'div\')).css({
2457  \'display\': \'table-row\'
2458  });
2459 
2460  span.append(
2461  jQuery(document.createElement(\'div\')).text(val.label).css({
2462  \'font-weight\': \'bold\',
2463  \'display\': \'table-cell\'
2464  })
2465  );
2466 
2467  var html = jQuery(document.createElement(\'select\')).attr(\'name\', \'combinations[\' + val.id + \']\').css({
2468  \'margin-left\': \'15px\',
2469  \'white-space\': \'pre\'
2470  }).append(
2471  jQuery(document.createElement(\'option\')).val(\'\')
2472  );
2473 
2474  jQuery.each(combvalues[val.id], function (key, val) {
2475  var tag = jQuery(document.createElement(\'option\')).val(val.id).html(val.value);
2476 
2477  if (selected[val.fk_product_attribute] == val.id) {
2478  tag.attr(\'selected\', \'selected\');
2479  }
2480 
2481  html.append(tag);
2482  });
2483 
2484  span.append(html);
2485  jQuery(\'div#attributes_box\').append(span);
2486  });
2487  })
2488  });
2489 
2490  ' . ($selected ? 'jQuery("input#' . $htmlname . '").change();' : '') . '
2491  });
2492  </script>
2493  ';
2494  }
2495 
2496  if (empty($hidelabel)) {
2497  $out .= $langs->trans("RefOrLabel") . ' : ';
2498  } elseif ($hidelabel > 1) {
2499  $placeholder = ' placeholder="' . $langs->trans("RefOrLabel") . '"';
2500  if ($hidelabel == 2) {
2501  $out .= img_picto($langs->trans("Search"), 'search');
2502  }
2503  }
2504  $out .= '<input type="text" class="minwidth100' . ($morecss ? ' ' . $morecss : '') . '" name="search_' . $htmlname . '" id="search_' . $htmlname . '" value="' . $selected_input_value . '"' . $placeholder . ' ' . (!empty($conf->global->PRODUCT_SEARCH_AUTOFOCUS) ? 'autofocus' : '') . ' />';
2505  if ($hidelabel == 3) {
2506  $out .= img_picto($langs->trans("Search"), 'search');
2507  }
2508  } else {
2509  $out .= $this->select_produits_list($selected, $htmlname, $filtertype, $limit, $price_level, '', $status, $finished, 0, $socid, $showempty, $forcecombo, $morecss, $hidepriceinlabel, $warehouseStatus, $status_purchase);
2510  }
2511 
2512  if (empty($nooutput)) {
2513  print $out;
2514  } else {
2515  return $out;
2516  }
2517  }
2518 
2519  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2520 
2536  public function select_bom($selected = '', $htmlname = 'bom_id', $limit = 0, $status = 1, $type = 0, $showempty = '1', $morecss = '', $nooutput = '', $forcecombo = 0, $TProducts = [])
2537  {
2538  // phpcs:enable
2539  global $conf, $user, $langs, $db;
2540 
2541  require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
2542 
2543  $error = 0;
2544  $out = '';
2545 
2546  if (!$forcecombo) {
2547  include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
2548  $events = array();
2549  $out .= ajax_combobox($htmlname, $events, getDolGlobalInt("PRODUIT_USE_SEARCH_TO_SELECT"));
2550  }
2551 
2552  $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" id="' . $htmlname . '">';
2553 
2554  $sql = 'SELECT b.rowid, b.ref, b.label, b.fk_product';
2555  $sql .= ' FROM ' . MAIN_DB_PREFIX . 'bom_bom as b';
2556  $sql .= ' WHERE b.entity IN (' . getEntity('bom') . ')';
2557  if (!empty($status)) $sql .= ' AND status = ' . (int) $status;
2558  if (!empty($type)) $sql .= ' AND bomtype = ' . (int) $type;
2559  if (!empty($TProducts)) $sql .= ' AND fk_product IN (' . $this->db->sanitize(implode(',', $TProducts)) . ')';
2560  if (!empty($limit)) $sql .= ' LIMIT ' . (int) $limit;
2561  $resql = $db->query($sql);
2562  if ($resql) {
2563  if ($showempty) {
2564  $out .= '<option value="-1"';
2565  if (empty($selected)) $out .= ' selected';
2566  $out .= '>&nbsp;</option>';
2567  }
2568  while ($obj = $db->fetch_object($resql)) {
2569  $product = new Product($db);
2570  $res = $product->fetch($obj->fk_product);
2571  $out .= '<option value="' . $obj->rowid . '"';
2572  if ($obj->rowid == $selected) $out .= 'selected';
2573  $out .= '>' . $obj->ref . ' - ' . $product->label . ' - ' . $obj->label . '</option>';
2574  }
2575  } else {
2576  $error++;
2577  dol_print_error($db);
2578  }
2579  $out .= '</select>';
2580  if (empty($nooutput)) {
2581  print $out;
2582  } else {
2583  return $out;
2584  }
2585  }
2586 
2587  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2588 
2614  public function select_produits_list($selected = '', $htmlname = 'productid', $filtertype = '', $limit = 20, $price_level = 0, $filterkey = '', $status = 1, $finished = 2, $outputmode = 0, $socid = 0, $showempty = '1', $forcecombo = 0, $morecss = '', $hidepriceinlabel = 0, $warehouseStatus = '', $status_purchase = -1)
2615  {
2616  // phpcs:enable
2617  global $langs, $conf;
2618  global $hookmanager;
2619 
2620  $out = '';
2621  $outarray = array();
2622 
2623  // Units
2624  if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
2625  $langs->load('other');
2626  }
2627 
2628  $warehouseStatusArray = array();
2629  if (!empty($warehouseStatus)) {
2630  require_once DOL_DOCUMENT_ROOT . '/product/stock/class/entrepot.class.php';
2631  if (preg_match('/warehouseclosed/', $warehouseStatus)) {
2632  $warehouseStatusArray[] = Entrepot::STATUS_CLOSED;
2633  }
2634  if (preg_match('/warehouseopen/', $warehouseStatus)) {
2635  $warehouseStatusArray[] = Entrepot::STATUS_OPEN_ALL;
2636  }
2637  if (preg_match('/warehouseinternal/', $warehouseStatus)) {
2638  $warehouseStatusArray[] = Entrepot::STATUS_OPEN_INTERNAL;
2639  }
2640  }
2641 
2642  $selectFields = " p.rowid, p.ref, p.label, p.description, p.barcode, p.fk_country, p.fk_product_type, p.price, p.price_ttc, p.price_base_type, p.tva_tx, p.default_vat_code, p.duration, p.fk_price_expression";
2643  if (count($warehouseStatusArray)) {
2644  $selectFieldsGrouped = ", sum(" . $this->db->ifsql("e.statut IS NULL", "0", "ps.reel") . ") as stock"; // e.statut is null if there is no record in stock
2645  } else {
2646  $selectFieldsGrouped = ", " . $this->db->ifsql("p.stock IS NULL", 0, "p.stock") . " AS stock";
2647  }
2648 
2649  $sql = "SELECT ";
2650 
2651  // Add select from hooks
2652  $parameters = array();
2653  $reshook = $hookmanager->executeHooks('selectProductsListSelect', $parameters); // Note that $action and $object may have been modified by hook
2654  if (empty($reshook)) {
2655  $sql .= $selectFields.$selectFieldsGrouped.$hookmanager->resPrint;
2656  } else {
2657  $sql .= $hookmanager->resPrint;
2658  }
2659 
2660  if (!empty($conf->global->PRODUCT_SORT_BY_CATEGORY)) {
2661  //Product category
2662  $sql .= ", (SELECT " . $this->db->prefix() . "categorie_product.fk_categorie
2663  FROM " . $this->db->prefix() . "categorie_product
2664  WHERE " . $this->db->prefix() . "categorie_product.fk_product=p.rowid
2665  LIMIT 1
2666  ) AS categorie_product_id ";
2667  }
2668 
2669  //Price by customer
2670  if (!empty($conf->global->PRODUIT_CUSTOMER_PRICES) && !empty($socid)) {
2671  $sql .= ', pcp.rowid as idprodcustprice, pcp.price as custprice, pcp.price_ttc as custprice_ttc,';
2672  $sql .= ' pcp.price_base_type as custprice_base_type, pcp.tva_tx as custtva_tx, pcp.default_vat_code as custdefault_vat_code, pcp.ref_customer as custref';
2673  $selectFields .= ", idprodcustprice, custprice, custprice_ttc, custprice_base_type, custtva_tx, custdefault_vat_code, custref";
2674  }
2675  // Units
2676  if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
2677  $sql .= ", u.label as unit_long, u.short_label as unit_short, p.weight, p.weight_units, p.length, p.length_units, p.width, p.width_units, p.height, p.height_units, p.surface, p.surface_units, p.volume, p.volume_units";
2678  $selectFields .= ', unit_long, unit_short, p.weight, p.weight_units, p.length, p.length_units, p.width, p.width_units, p.height, p.height_units, p.surface, p.surface_units, p.volume, p.volume_units';
2679  }
2680 
2681  // Multilang : we add translation
2682  if (getDolGlobalInt('MAIN_MULTILANGS')) {
2683  $sql .= ", pl.label as label_translated";
2684  $sql .= ", pl.description as description_translated";
2685  $selectFields .= ", label_translated";
2686  $selectFields .= ", description_translated";
2687  }
2688  // Price by quantity
2689  if (!empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY) || !empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES)) {
2690  $sql .= ", (SELECT pp.rowid FROM " . $this->db->prefix() . "product_price as pp WHERE pp.fk_product = p.rowid";
2691  if ($price_level >= 1 && !empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES)) {
2692  $sql .= " AND price_level = " . ((int) $price_level);
2693  }
2694  $sql .= " ORDER BY date_price";
2695  $sql .= " DESC LIMIT 1) as price_rowid";
2696  $sql .= ", (SELECT pp.price_by_qty FROM " . $this->db->prefix() . "product_price as pp WHERE pp.fk_product = p.rowid"; // price_by_qty is 1 if some prices by qty exists in subtable
2697  if ($price_level >= 1 && !empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES)) {
2698  $sql .= " AND price_level = " . ((int) $price_level);
2699  }
2700  $sql .= " ORDER BY date_price";
2701  $sql .= " DESC LIMIT 1) as price_by_qty";
2702  $selectFields .= ", price_rowid, price_by_qty";
2703  }
2704 
2705  $sql .= " FROM ".$this->db->prefix()."product as p";
2706  // Add from (left join) from hooks
2707  $parameters = array();
2708  $reshook = $hookmanager->executeHooks('selectProductsListFrom', $parameters); // Note that $action and $object may have been modified by hook
2709  $sql .= $hookmanager->resPrint;
2710 
2711  if (count($warehouseStatusArray)) {
2712  $sql .= " LEFT JOIN " . $this->db->prefix() . "product_stock as ps on ps.fk_product = p.rowid";
2713  $sql .= " LEFT JOIN " . $this->db->prefix() . "entrepot as e on ps.fk_entrepot = e.rowid AND e.entity IN (" . getEntity('stock') . ")";
2714  $sql .= ' AND e.statut IN (' . $this->db->sanitize($this->db->escape(implode(',', $warehouseStatusArray))) . ')'; // Return line if product is inside the selected stock. If not, an empty line will be returned so we will count 0.
2715  }
2716 
2717  // include search in supplier ref
2718  if (!empty($conf->global->MAIN_SEARCH_PRODUCT_BY_FOURN_REF)) {
2719  $sql .= " LEFT JOIN " . $this->db->prefix() . "product_fournisseur_price as pfp ON p.rowid = pfp.fk_product";
2720  }
2721 
2722  //Price by customer
2723  if (!empty($conf->global->PRODUIT_CUSTOMER_PRICES) && !empty($socid)) {
2724  $sql .= " LEFT JOIN " . $this->db->prefix() . "product_customer_price as pcp ON pcp.fk_soc=" . ((int) $socid) . " AND pcp.fk_product=p.rowid";
2725  }
2726  // Units
2727  if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
2728  $sql .= " LEFT JOIN " . $this->db->prefix() . "c_units u ON u.rowid = p.fk_unit";
2729  }
2730  // Multilang : we add translation
2731  if (getDolGlobalInt('MAIN_MULTILANGS')) {
2732  $sql .= " LEFT JOIN " . $this->db->prefix() . "product_lang as pl ON pl.fk_product = p.rowid ";
2733  if (!empty($conf->global->PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE) && !empty($socid)) {
2734  require_once DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php';
2735  $soc = new Societe($this->db);
2736  $result = $soc->fetch($socid);
2737  if ($result > 0 && !empty($soc->default_lang)) {
2738  $sql .= " AND pl.lang = '" . $this->db->escape($soc->default_lang) . "'";
2739  } else {
2740  $sql .= " AND pl.lang = '" . $this->db->escape($langs->getDefaultLang()) . "'";
2741  }
2742  } else {
2743  $sql .= " AND pl.lang = '" . $this->db->escape($langs->getDefaultLang()) . "'";
2744  }
2745  }
2746 
2747  if (!empty($conf->global->PRODUIT_ATTRIBUTES_HIDECHILD)) {
2748  $sql .= " LEFT JOIN " . $this->db->prefix() . "product_attribute_combination pac ON pac.fk_product_child = p.rowid";
2749  }
2750 
2751  $sql .= ' WHERE p.entity IN (' . getEntity('product') . ')';
2752 
2753  if (!empty($conf->global->PRODUIT_ATTRIBUTES_HIDECHILD)) {
2754  $sql .= " AND pac.rowid IS NULL";
2755  }
2756 
2757  if ($finished == 0) {
2758  $sql .= " AND p.finished = " . ((int) $finished);
2759  } elseif ($finished == 1) {
2760  $sql .= " AND p.finished = " . ((int) $finished);
2761  if ($status >= 0) {
2762  $sql .= " AND p.tosell = " . ((int) $status);
2763  }
2764  } elseif ($status >= 0) {
2765  $sql .= " AND p.tosell = " . ((int) $status);
2766  }
2767  if ($status_purchase >= 0) {
2768  $sql .= " AND p.tobuy = " . ((int) $status_purchase);
2769  }
2770  // Filter by product type
2771  if (strval($filtertype) != '') {
2772  $sql .= " AND p.fk_product_type = " . ((int) $filtertype);
2773  } elseif (!isModEnabled('product')) { // when product module is disabled, show services only
2774  $sql .= " AND p.fk_product_type = 1";
2775  } elseif (!isModEnabled('service')) { // when service module is disabled, show products only
2776  $sql .= " AND p.fk_product_type = 0";
2777  }
2778  // Add where from hooks
2779  $parameters = array();
2780  $reshook = $hookmanager->executeHooks('selectProductsListWhere', $parameters); // Note that $action and $object may have been modified by hook
2781  $sql .= $hookmanager->resPrint;
2782  // Add criteria on ref/label
2783  if ($filterkey != '') {
2784  $sql .= ' AND (';
2785  $prefix = empty($conf->global->PRODUCT_DONOTSEARCH_ANYWHERE) ? '%' : ''; // Can use index if PRODUCT_DONOTSEARCH_ANYWHERE is on
2786  // For natural search
2787  $scrit = explode(' ', $filterkey);
2788  $i = 0;
2789  if (count($scrit) > 1) {
2790  $sql .= "(";
2791  }
2792  foreach ($scrit as $crit) {
2793  if ($i > 0) {
2794  $sql .= " AND ";
2795  }
2796  $sql .= "(p.ref LIKE '" . $this->db->escape($prefix . $crit) . "%' OR p.label LIKE '" . $this->db->escape($prefix . $crit) . "%'";
2797  if (getDolGlobalInt('MAIN_MULTILANGS')) {
2798  $sql .= " OR pl.label LIKE '" . $this->db->escape($prefix . $crit) . "%'";
2799  }
2800  if (!empty($conf->global->PRODUIT_CUSTOMER_PRICES) && !empty($socid)) {
2801  $sql .= " OR pcp.ref_customer LIKE '" . $this->db->escape($prefix . $crit) . "%'";
2802  }
2803  if (!empty($conf->global->PRODUCT_AJAX_SEARCH_ON_DESCRIPTION)) {
2804  $sql .= " OR p.description LIKE '" . $this->db->escape($prefix . $crit) . "%'";
2805  if (getDolGlobalInt('MAIN_MULTILANGS')) {
2806  $sql .= " OR pl.description LIKE '" . $this->db->escape($prefix . $crit) . "%'";
2807  }
2808  }
2809  if (!empty($conf->global->MAIN_SEARCH_PRODUCT_BY_FOURN_REF)) {
2810  $sql .= " OR pfp.ref_fourn LIKE '" . $this->db->escape($prefix . $crit) . "%'";
2811  }
2812  $sql .= ")";
2813  $i++;
2814  }
2815  if (count($scrit) > 1) {
2816  $sql .= ")";
2817  }
2818  if (isModEnabled('barcode')) {
2819  $sql .= " OR p.barcode LIKE '" . $this->db->escape($prefix . $filterkey) . "%'";
2820  }
2821  $sql .= ')';
2822  }
2823  if (count($warehouseStatusArray)) {
2824  $sql .= " GROUP BY " . $selectFields;
2825  }
2826 
2827  //Sort by category
2828  if (!empty($conf->global->PRODUCT_SORT_BY_CATEGORY)) {
2829  $sql .= " ORDER BY categorie_product_id ";
2830  //ASC OR DESC order
2831  ($conf->global->PRODUCT_SORT_BY_CATEGORY == 1) ? $sql .= "ASC" : $sql .= "DESC";
2832  } else {
2833  $sql .= $this->db->order("p.ref");
2834  }
2835 
2836  $sql .= $this->db->plimit($limit, 0);
2837 
2838  // Build output string
2839  dol_syslog(get_class($this) . "::select_produits_list search products", LOG_DEBUG);
2840  $result = $this->db->query($sql);
2841  if ($result) {
2842  require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
2843  require_once DOL_DOCUMENT_ROOT . '/product/dynamic_price/class/price_parser.class.php';
2844  require_once DOL_DOCUMENT_ROOT . '/core/lib/product.lib.php';
2845 
2846  $num = $this->db->num_rows($result);
2847 
2848  $events = null;
2849 
2850  if (!$forcecombo) {
2851  include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
2852  $out .= ajax_combobox($htmlname, $events, getDolGlobalInt("PRODUIT_USE_SEARCH_TO_SELECT"));
2853  }
2854 
2855  $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" id="' . $htmlname . '">';
2856 
2857  $textifempty = '';
2858  // Do not use textifempty = ' ' or '&nbsp;' here, or search on key will search on ' key'.
2859  //if (!empty($conf->use_javascript_ajax) || $forcecombo) $textifempty='';
2860  if (!empty($conf->global->PRODUIT_USE_SEARCH_TO_SELECT)) {
2861  if ($showempty && !is_numeric($showempty)) {
2862  $textifempty = $langs->trans($showempty);
2863  } else {
2864  $textifempty .= $langs->trans("All");
2865  }
2866  } else {
2867  if ($showempty && !is_numeric($showempty)) {
2868  $textifempty = $langs->trans($showempty);
2869  }
2870  }
2871  if ($showempty) {
2872  $out .= '<option value="-1" selected>' . ($textifempty ? $textifempty : '&nbsp;') . '</option>';
2873  }
2874 
2875  $i = 0;
2876  while ($num && $i < $num) {
2877  $opt = '';
2878  $optJson = array();
2879  $objp = $this->db->fetch_object($result);
2880 
2881  if ((!empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY) || !empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES)) && !empty($objp->price_by_qty) && $objp->price_by_qty == 1) { // Price by quantity will return many prices for the same product
2882  $sql = "SELECT rowid, quantity, price, unitprice, remise_percent, remise, price_base_type";
2883  $sql .= " FROM " . $this->db->prefix() . "product_price_by_qty";
2884  $sql .= " WHERE fk_product_price = " . ((int) $objp->price_rowid);
2885  $sql .= " ORDER BY quantity ASC";
2886 
2887  dol_syslog(get_class($this) . "::select_produits_list search prices by qty", LOG_DEBUG);
2888  $result2 = $this->db->query($sql);
2889  if ($result2) {
2890  $nb_prices = $this->db->num_rows($result2);
2891  $j = 0;
2892  while ($nb_prices && $j < $nb_prices) {
2893  $objp2 = $this->db->fetch_object($result2);
2894 
2895  $objp->price_by_qty_rowid = $objp2->rowid;
2896  $objp->price_by_qty_price_base_type = $objp2->price_base_type;
2897  $objp->price_by_qty_quantity = $objp2->quantity;
2898  $objp->price_by_qty_unitprice = $objp2->unitprice;
2899  $objp->price_by_qty_remise_percent = $objp2->remise_percent;
2900  // For backward compatibility
2901  $objp->quantity = $objp2->quantity;
2902  $objp->price = $objp2->price;
2903  $objp->unitprice = $objp2->unitprice;
2904  $objp->remise_percent = $objp2->remise_percent;
2905 
2906  //$objp->tva_tx is not overwritten by $objp2 value
2907  //$objp->default_vat_code is not overwritten by $objp2 value
2908 
2909  $this->constructProductListOption($objp, $opt, $optJson, 0, $selected, $hidepriceinlabel, $filterkey);
2910 
2911  $j++;
2912 
2913  // Add new entry
2914  // "key" value of json key array is used by jQuery automatically as selected value
2915  // "label" value of json key array is used by jQuery automatically as text for combo box
2916  $out .= $opt;
2917  array_push($outarray, $optJson);
2918  }
2919  }
2920  } else {
2921  if (isModEnabled('dynamicprices') && !empty($objp->fk_price_expression)) {
2922  $price_product = new Product($this->db);
2923  $price_product->fetch($objp->rowid, '', '', 1);
2924 
2925  require_once DOL_DOCUMENT_ROOT . '/product/dynamic_price/class/price_parser.class.php';
2926  $priceparser = new PriceParser($this->db);
2927  $price_result = $priceparser->parseProduct($price_product);
2928  if ($price_result >= 0) {
2929  $objp->price = $price_result;
2930  $objp->unitprice = $price_result;
2931  //Calculate the VAT
2932  $objp->price_ttc = price2num($objp->price) * (1 + ($objp->tva_tx / 100));
2933  $objp->price_ttc = price2num($objp->price_ttc, 'MU');
2934  }
2935  }
2936 
2937  $this->constructProductListOption($objp, $opt, $optJson, $price_level, $selected, $hidepriceinlabel, $filterkey);
2938  // Add new entry
2939  // "key" value of json key array is used by jQuery automatically as selected value
2940  // "label" value of json key array is used by jQuery automatically as text for combo box
2941  $out .= $opt;
2942  array_push($outarray, $optJson);
2943  }
2944 
2945  $i++;
2946  }
2947 
2948  $out .= '</select>';
2949 
2950  $this->db->free($result);
2951 
2952  if (empty($outputmode)) {
2953  return $out;
2954  }
2955 
2956  return $outarray;
2957  } else {
2958  dol_print_error($this->db);
2959  }
2960 
2961  return '';
2962  }
2963 
2979  protected function constructProductListOption(&$objp, &$opt, &$optJson, $price_level, $selected, $hidepriceinlabel = 0, $filterkey = '', $novirtualstock = 0)
2980  {
2981  global $langs, $conf, $user;
2982  global $hookmanager;
2983 
2984  $outkey = '';
2985  $outval = '';
2986  $outref = '';
2987  $outlabel = '';
2988  $outlabel_translated = '';
2989  $outdesc = '';
2990  $outdesc_translated = '';
2991  $outbarcode = '';
2992  $outorigin = '';
2993  $outtype = '';
2994  $outprice_ht = '';
2995  $outprice_ttc = '';
2996  $outpricebasetype = '';
2997  $outtva_tx = '';
2998  $outdefault_vat_code = '';
2999  $outqty = 1;
3000  $outdiscount = 0;
3001 
3002  $maxlengtharticle = (empty($conf->global->PRODUCT_MAX_LENGTH_COMBO) ? 48 : $conf->global->PRODUCT_MAX_LENGTH_COMBO);
3003 
3004  $label = $objp->label;
3005  if (!empty($objp->label_translated)) {
3006  $label = $objp->label_translated;
3007  }
3008  if (!empty($filterkey) && $filterkey != '') {
3009  $label = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', $label, 1);
3010  }
3011 
3012  $outkey = $objp->rowid;
3013  $outref = $objp->ref;
3014  $outrefcust = empty($objp->custref) ? '' : $objp->custref;
3015  $outlabel = $objp->label;
3016  $outdesc = $objp->description;
3017  if (getDolGlobalInt('MAIN_MULTILANGS')) {
3018  $outlabel_translated = $objp->label_translated;
3019  $outdesc_translated = $objp->description_translated;
3020  }
3021  $outbarcode = $objp->barcode;
3022  $outorigin = $objp->fk_country;
3023  $outpbq = empty($objp->price_by_qty_rowid) ? '' : $objp->price_by_qty_rowid;
3024 
3025  $outtype = $objp->fk_product_type;
3026  $outdurationvalue = $outtype == Product::TYPE_SERVICE ? substr($objp->duration, 0, dol_strlen($objp->duration) - 1) : '';
3027  $outdurationunit = $outtype == Product::TYPE_SERVICE ? substr($objp->duration, -1) : '';
3028 
3029  if ($outorigin && !empty($conf->global->PRODUCT_SHOW_ORIGIN_IN_COMBO)) {
3030  require_once DOL_DOCUMENT_ROOT . '/core/lib/company.lib.php';
3031  }
3032 
3033  // Units
3034  $outvalUnits = '';
3035  if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
3036  if (!empty($objp->unit_short)) {
3037  $outvalUnits .= ' - ' . $objp->unit_short;
3038  }
3039  }
3040  if (!empty($conf->global->PRODUCT_SHOW_DIMENSIONS_IN_COMBO)) {
3041  if (!empty($objp->weight) && $objp->weight_units !== null) {
3042  $unitToShow = showDimensionInBestUnit($objp->weight, $objp->weight_units, 'weight', $langs);
3043  $outvalUnits .= ' - ' . $unitToShow;
3044  }
3045  if ((!empty($objp->length) || !empty($objp->width) || !empty($objp->height)) && $objp->length_units !== null) {
3046  $unitToShow = $objp->length . ' x ' . $objp->width . ' x ' . $objp->height . ' ' . measuringUnitString(0, 'size', $objp->length_units);
3047  $outvalUnits .= ' - ' . $unitToShow;
3048  }
3049  if (!empty($objp->surface) && $objp->surface_units !== null) {
3050  $unitToShow = showDimensionInBestUnit($objp->surface, $objp->surface_units, 'surface', $langs);
3051  $outvalUnits .= ' - ' . $unitToShow;
3052  }
3053  if (!empty($objp->volume) && $objp->volume_units !== null) {
3054  $unitToShow = showDimensionInBestUnit($objp->volume, $objp->volume_units, 'volume', $langs);
3055  $outvalUnits .= ' - ' . $unitToShow;
3056  }
3057  }
3058  if ($outdurationvalue && $outdurationunit) {
3059  $da = array(
3060  'h' => $langs->trans('Hour'),
3061  'd' => $langs->trans('Day'),
3062  'w' => $langs->trans('Week'),
3063  'm' => $langs->trans('Month'),
3064  'y' => $langs->trans('Year')
3065  );
3066  if (isset($da[$outdurationunit])) {
3067  $outvalUnits .= ' - ' . $outdurationvalue . ' ' . $langs->transnoentities($da[$outdurationunit] . ($outdurationvalue > 1 ? 's' : ''));
3068  }
3069  }
3070 
3071  $opt = '<option value="' . $objp->rowid . '"';
3072  $opt .= ($objp->rowid == $selected) ? ' selected' : '';
3073  if (!empty($objp->price_by_qty_rowid) && $objp->price_by_qty_rowid > 0) {
3074  $opt .= ' pbq="' . $objp->price_by_qty_rowid . '" data-pbq="' . $objp->price_by_qty_rowid . '" data-pbqup="' . $objp->price_by_qty_unitprice . '" data-pbqbase="' . $objp->price_by_qty_price_base_type . '" data-pbqqty="' . $objp->price_by_qty_quantity . '" data-pbqpercent="' . $objp->price_by_qty_remise_percent . '"';
3075  }
3076  if (isModEnabled('stock') && isset($objp->stock) && ($objp->fk_product_type == Product::TYPE_PRODUCT || !empty($conf->global->STOCK_SUPPORTS_SERVICES))) {
3077  if (!empty($user->rights->stock->lire)) {
3078  if ($objp->stock > 0) {
3079  $opt .= ' class="product_line_stock_ok"';
3080  } elseif ($objp->stock <= 0) {
3081  $opt .= ' class="product_line_stock_too_low"';
3082  }
3083  }
3084  }
3085  if (!empty($conf->global->PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE)) {
3086  $opt .= ' data-labeltrans="' . $outlabel_translated . '"';
3087  $opt .= ' data-desctrans="' . dol_escape_htmltag($outdesc_translated) . '"';
3088  }
3089  $opt .= '>';
3090  $opt .= $objp->ref;
3091  if (!empty($objp->custref)) {
3092  $opt .= ' (' . $objp->custref . ')';
3093  }
3094  if ($outbarcode) {
3095  $opt .= ' (' . $outbarcode . ')';
3096  }
3097  $opt .= ' - ' . dol_trunc($label, $maxlengtharticle);
3098  if ($outorigin && !empty($conf->global->PRODUCT_SHOW_ORIGIN_IN_COMBO)) {
3099  $opt .= ' (' . getCountry($outorigin, 1) . ')';
3100  }
3101 
3102  $objRef = $objp->ref;
3103  if (!empty($objp->custref)) {
3104  $objRef .= ' (' . $objp->custref . ')';
3105  }
3106  if (!empty($filterkey) && $filterkey != '') {
3107  $objRef = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', $objRef, 1);
3108  }
3109  $outval .= $objRef;
3110  if ($outbarcode) {
3111  $outval .= ' (' . $outbarcode . ')';
3112  }
3113  $outval .= ' - ' . dol_trunc($label, $maxlengtharticle);
3114  if ($outorigin && !empty($conf->global->PRODUCT_SHOW_ORIGIN_IN_COMBO)) {
3115  $outval .= ' (' . getCountry($outorigin, 1) . ')';
3116  }
3117 
3118  // Units
3119  $opt .= $outvalUnits;
3120  $outval .= $outvalUnits;
3121 
3122  $found = 0;
3123 
3124  // Multiprice
3125  // If we need a particular price level (from 1 to n)
3126  if (empty($hidepriceinlabel) && $price_level >= 1 && (!empty($conf->global->PRODUIT_MULTIPRICES) || !empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES))) {
3127  $sql = "SELECT price, price_ttc, price_base_type, tva_tx, default_vat_code";
3128  $sql .= " FROM " . $this->db->prefix() . "product_price";
3129  $sql .= " WHERE fk_product = " . ((int) $objp->rowid);
3130  $sql .= " AND entity IN (" . getEntity('productprice') . ")";
3131  $sql .= " AND price_level = " . ((int) $price_level);
3132  $sql .= " ORDER BY date_price DESC, rowid DESC"; // Warning DESC must be both on date_price and rowid.
3133  $sql .= " LIMIT 1";
3134 
3135  dol_syslog(get_class($this) . '::constructProductListOption search price for product ' . $objp->rowid . ' AND level ' . $price_level, LOG_DEBUG);
3136  $result2 = $this->db->query($sql);
3137  if ($result2) {
3138  $objp2 = $this->db->fetch_object($result2);
3139  if ($objp2) {
3140  $found = 1;
3141  if ($objp2->price_base_type == 'HT') {
3142  $opt .= ' - ' . price($objp2->price, 1, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->trans("HT");
3143  $outval .= ' - ' . price($objp2->price, 0, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->transnoentities("HT");
3144  } else {
3145  $opt .= ' - ' . price($objp2->price_ttc, 1, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->trans("TTC");
3146  $outval .= ' - ' . price($objp2->price_ttc, 0, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->transnoentities("TTC");
3147  }
3148  $outprice_ht = price($objp2->price);
3149  $outprice_ttc = price($objp2->price_ttc);
3150  $outpricebasetype = $objp2->price_base_type;
3151  if (!empty($conf->global->PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL)) { // using this option is a bug. kept for backward compatibility
3152  $outtva_tx = $objp2->tva_tx; // We use the vat rate on line of multiprice
3153  $outdefault_vat_code = $objp2->default_vat_code; // We use the vat code on line of multiprice
3154  } else {
3155  $outtva_tx = $objp->tva_tx; // We use the vat rate of product, not the one on line of multiprice
3156  $outdefault_vat_code = $objp->default_vat_code; // We use the vat code or product, not the one on line of multiprice
3157  }
3158  }
3159  } else {
3160  dol_print_error($this->db);
3161  }
3162  }
3163 
3164  // Price by quantity
3165  if (empty($hidepriceinlabel) && !empty($objp->quantity) && $objp->quantity >= 1 && (!empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY) || !empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES))) {
3166  $found = 1;
3167  $outqty = $objp->quantity;
3168  $outdiscount = $objp->remise_percent;
3169  if ($objp->quantity == 1) {
3170  $opt .= ' - ' . price($objp->unitprice, 1, $langs, 0, 0, -1, $conf->currency) . "/";
3171  $outval .= ' - ' . price($objp->unitprice, 0, $langs, 0, 0, -1, $conf->currency) . "/";
3172  $opt .= $langs->trans("Unit"); // Do not use strtolower because it breaks utf8 encoding
3173  $outval .= $langs->transnoentities("Unit");
3174  } else {
3175  $opt .= ' - ' . price($objp->price, 1, $langs, 0, 0, -1, $conf->currency) . "/" . $objp->quantity;
3176  $outval .= ' - ' . price($objp->price, 0, $langs, 0, 0, -1, $conf->currency) . "/" . $objp->quantity;
3177  $opt .= $langs->trans("Units"); // Do not use strtolower because it breaks utf8 encoding
3178  $outval .= $langs->transnoentities("Units");
3179  }
3180 
3181  $outprice_ht = price($objp->unitprice);
3182  $outprice_ttc = price($objp->unitprice * (1 + ($objp->tva_tx / 100)));
3183  $outpricebasetype = $objp->price_base_type;
3184  $outtva_tx = $objp->tva_tx; // This value is the value on product when constructProductListOption is called by select_produits_list even if other field $objp-> are from table price_by_qty
3185  $outdefault_vat_code = $objp->default_vat_code; // This value is the value on product when constructProductListOption is called by select_produits_list even if other field $objp-> are from table price_by_qty
3186  }
3187  if (empty($hidepriceinlabel) && !empty($objp->quantity) && $objp->quantity >= 1) {
3188  $opt .= " (" . price($objp->unitprice, 1, $langs, 0, 0, -1, $conf->currency) . "/" . $langs->trans("Unit") . ")"; // Do not use strtolower because it breaks utf8 encoding
3189  $outval .= " (" . price($objp->unitprice, 0, $langs, 0, 0, -1, $conf->currency) . "/" . $langs->transnoentities("Unit") . ")"; // Do not use strtolower because it breaks utf8 encoding
3190  }
3191  if (empty($hidepriceinlabel) && !empty($objp->remise_percent) && $objp->remise_percent >= 1) {
3192  $opt .= " - " . $langs->trans("Discount") . " : " . vatrate($objp->remise_percent) . ' %';
3193  $outval .= " - " . $langs->transnoentities("Discount") . " : " . vatrate($objp->remise_percent) . ' %';
3194  }
3195 
3196  // Price by customer
3197  if (empty($hidepriceinlabel) && !empty($conf->global->PRODUIT_CUSTOMER_PRICES)) {
3198  if (!empty($objp->idprodcustprice)) {
3199  $found = 1;
3200 
3201  if ($objp->custprice_base_type == 'HT') {
3202  $opt .= ' - ' . price($objp->custprice, 1, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->trans("HT");
3203  $outval .= ' - ' . price($objp->custprice, 0, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->transnoentities("HT");
3204  } else {
3205  $opt .= ' - ' . price($objp->custprice_ttc, 1, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->trans("TTC");
3206  $outval .= ' - ' . price($objp->custprice_ttc, 0, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->transnoentities("TTC");
3207  }
3208 
3209  $outprice_ht = price($objp->custprice);
3210  $outprice_ttc = price($objp->custprice_ttc);
3211  $outpricebasetype = $objp->custprice_base_type;
3212  $outtva_tx = $objp->custtva_tx;
3213  $outdefault_vat_code = $objp->custdefault_vat_code;
3214  }
3215  }
3216 
3217  // If level no defined or multiprice not found, we used the default price
3218  if (empty($hidepriceinlabel) && !$found) {
3219  if ($objp->price_base_type == 'HT') {
3220  $opt .= ' - ' . price($objp->price, 1, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->trans("HT");
3221  $outval .= ' - ' . price($objp->price, 0, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->transnoentities("HT");
3222  } else {
3223  $opt .= ' - ' . price($objp->price_ttc, 1, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->trans("TTC");
3224  $outval .= ' - ' . price($objp->price_ttc, 0, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->transnoentities("TTC");
3225  }
3226  $outprice_ht = price($objp->price);
3227  $outprice_ttc = price($objp->price_ttc);
3228  $outpricebasetype = $objp->price_base_type;
3229  $outtva_tx = $objp->tva_tx;
3230  $outdefault_vat_code = $objp->default_vat_code;
3231  }
3232 
3233  if (isModEnabled('stock') && isset($objp->stock) && ($objp->fk_product_type == Product::TYPE_PRODUCT || !empty($conf->global->STOCK_SUPPORTS_SERVICES))) {
3234  if (!empty($user->rights->stock->lire)) {
3235  $opt .= ' - ' . $langs->trans("Stock") . ': ' . price(price2num($objp->stock, 'MS'));
3236 
3237  if ($objp->stock > 0) {
3238  $outval .= ' - <span class="product_line_stock_ok">';
3239  } elseif ($objp->stock <= 0) {
3240  $outval .= ' - <span class="product_line_stock_too_low">';
3241  }
3242  $outval .= $langs->transnoentities("Stock") . ': ' . price(price2num($objp->stock, 'MS'));
3243  $outval .= '</span>';
3244  if (empty($novirtualstock) && !empty($conf->global->STOCK_SHOW_VIRTUAL_STOCK_IN_PRODUCTS_COMBO)) { // Warning, this option may slow down combo list generation
3245  $langs->load("stocks");
3246 
3247  $tmpproduct = new Product($this->db);
3248  $tmpproduct->fetch($objp->rowid, '', '', '', 1, 1, 1); // Load product without lang and prices arrays (we just need to make ->virtual_stock() after)
3249  $tmpproduct->load_virtual_stock();
3250  $virtualstock = $tmpproduct->stock_theorique;
3251 
3252  $opt .= ' - ' . $langs->trans("VirtualStock") . ':' . $virtualstock;
3253 
3254  $outval .= ' - ' . $langs->transnoentities("VirtualStock") . ':';
3255  if ($virtualstock > 0) {
3256  $outval .= '<span class="product_line_stock_ok">';
3257  } elseif ($virtualstock <= 0) {
3258  $outval .= '<span class="product_line_stock_too_low">';
3259  }
3260  $outval .= $virtualstock;
3261  $outval .= '</span>';
3262 
3263  unset($tmpproduct);
3264  }
3265  }
3266  }
3267 
3268  $parameters = array('objp'=>$objp);
3269  $reshook = $hookmanager->executeHooks('constructProductListOption', $parameters); // Note that $action and $object may have been modified by hook
3270  if (empty($reshook)) {
3271  $opt .= $hookmanager->resPrint;
3272  } else {
3273  $opt = $hookmanager->resPrint;
3274  }
3275 
3276  $opt .= "</option>\n";
3277  $optJson = array(
3278  'key' => $outkey,
3279  'value' => $outref,
3280  'label' => $outval,
3281  'label2' => $outlabel,
3282  'desc' => $outdesc,
3283  'type' => $outtype,
3284  'price_ht' => price2num($outprice_ht),
3285  'price_ttc' => price2num($outprice_ttc),
3286  'price_ht_locale' => price(price2num($outprice_ht)),
3287  'price_ttc_locale' => price(price2num($outprice_ttc)),
3288  'pricebasetype' => $outpricebasetype,
3289  'tva_tx' => $outtva_tx,
3290  'default_vat_code' => $outdefault_vat_code,
3291  'qty' => $outqty,
3292  'discount' => $outdiscount,
3293  'duration_value' => $outdurationvalue,
3294  'duration_unit' => $outdurationunit,
3295  'pbq' => $outpbq,
3296  'labeltrans' => $outlabel_translated,
3297  'desctrans' => $outdesc_translated,
3298  'ref_customer' => $outrefcust
3299  );
3300  }
3301 
3302  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3303 
3319  public function select_produits_fournisseurs($socid, $selected = '', $htmlname = 'productid', $filtertype = '', $filtre = '', $ajaxoptions = array(), $hidelabel = 0, $alsoproductwithnosupplierprice = 0, $morecss = '', $placeholder = '')
3320  {
3321  // phpcs:enable
3322  global $langs, $conf;
3323  global $price_level, $status, $finished;
3324 
3325  if (!isset($status)) {
3326  $status = 1;
3327  }
3328 
3329  $selected_input_value = '';
3330  if (!empty($conf->use_javascript_ajax) && !empty($conf->global->PRODUIT_USE_SEARCH_TO_SELECT)) {
3331  if ($selected > 0) {
3332  require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
3333  $producttmpselect = new Product($this->db);
3334  $producttmpselect->fetch($selected);
3335  $selected_input_value = $producttmpselect->ref;
3336  unset($producttmpselect);
3337  }
3338 
3339  // mode=2 means suppliers products
3340  $urloption = ($socid > 0 ? 'socid=' . $socid . '&' : '') . 'htmlname=' . $htmlname . '&outjson=1&price_level=' . $price_level . '&type=' . $filtertype . '&mode=2&status=' . $status . '&finished=' . $finished . '&alsoproductwithnosupplierprice=' . $alsoproductwithnosupplierprice;
3341  print ajax_autocompleter($selected, $htmlname, DOL_URL_ROOT . '/product/ajax/products.php', $urloption, $conf->global->PRODUIT_USE_SEARCH_TO_SELECT, 0, $ajaxoptions);
3342 
3343  print ($hidelabel ? '' : $langs->trans("RefOrLabel") . ' : ') . '<input type="text" class="minwidth300" name="search_' . $htmlname . '" id="search_' . $htmlname . '" value="' . $selected_input_value . '"' . ($placeholder ? ' placeholder="' . $placeholder . '"' : '') . '>';
3344  } else {
3345  print $this->select_produits_fournisseurs_list($socid, $selected, $htmlname, $filtertype, $filtre, '', $status, 0, 0, $alsoproductwithnosupplierprice, $morecss, 0, $placeholder);
3346  }
3347  }
3348 
3349  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3350 
3369  public function select_produits_fournisseurs_list($socid, $selected = '', $htmlname = 'productid', $filtertype = '', $filtre = '', $filterkey = '', $statut = -1, $outputmode = 0, $limit = 100, $alsoproductwithnosupplierprice = 0, $morecss = '', $showstockinlist = 0, $placeholder = '')
3370  {
3371  // phpcs:enable
3372  global $langs, $conf, $user;
3373  global $hookmanager;
3374 
3375  $out = '';
3376  $outarray = array();
3377 
3378  $maxlengtharticle = (empty($conf->global->PRODUCT_MAX_LENGTH_COMBO) ? 48 : $conf->global->PRODUCT_MAX_LENGTH_COMBO);
3379 
3380  $langs->load('stocks');
3381  // Units
3382  if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
3383  $langs->load('other');
3384  }
3385 
3386  $sql = "SELECT p.rowid, p.ref, p.label, p.price, p.duration, p.fk_product_type, p.stock, p.tva_tx as tva_tx_sale, p.default_vat_code as default_vat_code_sale,";
3387  $sql .= " pfp.ref_fourn, pfp.rowid as idprodfournprice, pfp.price as fprice, pfp.quantity, pfp.remise_percent, pfp.remise, pfp.unitprice,";
3388  $sql .= " pfp.fk_supplier_price_expression, pfp.fk_product, pfp.tva_tx, pfp.default_vat_code, pfp.fk_soc, s.nom as name,";
3389  $sql .= " pfp.supplier_reputation";
3390  // if we use supplier description of the products
3391  if (!empty($conf->global->PRODUIT_FOURN_TEXTS)) {
3392  $sql .= ", pfp.desc_fourn as description";
3393  } else {
3394  $sql .= ", p.description";
3395  }
3396  // Units
3397  if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
3398  $sql .= ", u.label as unit_long, u.short_label as unit_short, p.weight, p.weight_units, p.length, p.length_units, p.width, p.width_units, p.height, p.height_units, p.surface, p.surface_units, p.volume, p.volume_units";
3399  }
3400  if (isModEnabled('barcode')) {
3401  $sql .= ", pfp.barcode";
3402  }
3403  $sql .= " FROM " . $this->db->prefix() . "product as p";
3404  $sql .= " LEFT JOIN " . $this->db->prefix() . "product_fournisseur_price as pfp ON ( p.rowid = pfp.fk_product AND pfp.entity IN (" . getEntity('product') . ") )";
3405  if ($socid > 0) {
3406  $sql .= " AND pfp.fk_soc = " . ((int) $socid);
3407  }
3408  $sql .= " LEFT JOIN " . $this->db->prefix() . "societe as s ON pfp.fk_soc = s.rowid";
3409  // Units
3410  if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
3411  $sql .= " LEFT JOIN " . $this->db->prefix() . "c_units u ON u.rowid = p.fk_unit";
3412  }
3413  $sql .= " WHERE p.entity IN (" . getEntity('product') . ")";
3414  if ($statut != -1) {
3415  $sql .= " AND p.tobuy = " . ((int) $statut);
3416  }
3417  if (strval($filtertype) != '') {
3418  $sql .= " AND p.fk_product_type = " . ((int) $filtertype);
3419  }
3420  if (!empty($filtre)) {
3421  $sql .= " " . $filtre;
3422  }
3423  // Add where from hooks
3424  $parameters = array();
3425  $reshook = $hookmanager->executeHooks('selectSuppliersProductsListWhere', $parameters); // Note that $action and $object may have been modified by hook
3426  $sql .= $hookmanager->resPrint;
3427  // Add criteria on ref/label
3428  if ($filterkey != '') {
3429  $sql .= ' AND (';
3430  $prefix = empty($conf->global->PRODUCT_DONOTSEARCH_ANYWHERE) ? '%' : ''; // Can use index if PRODUCT_DONOTSEARCH_ANYWHERE is on
3431  // For natural search
3432  $scrit = explode(' ', $filterkey);
3433  $i = 0;
3434  if (count($scrit) > 1) {
3435  $sql .= "(";
3436  }
3437  foreach ($scrit as $crit) {
3438  if ($i > 0) {
3439  $sql .= " AND ";
3440  }
3441  $sql .= "(pfp.ref_fourn LIKE '" . $this->db->escape($prefix . $crit) . "%' OR p.ref LIKE '" . $this->db->escape($prefix . $crit) . "%' OR p.label LIKE '" . $this->db->escape($prefix . $crit) . "%'";
3442  if (!empty($conf->global->PRODUIT_FOURN_TEXTS)) {
3443  $sql .= " OR pfp.desc_fourn LIKE '" . $this->db->escape($prefix . $crit) . "%'";
3444  }
3445  $sql .= ")";
3446  $i++;
3447  }
3448  if (count($scrit) > 1) {
3449  $sql .= ")";
3450  }
3451  if (isModEnabled('barcode')) {
3452  $sql .= " OR p.barcode LIKE '" . $this->db->escape($prefix . $filterkey) . "%'";
3453  $sql .= " OR pfp.barcode LIKE '" . $this->db->escape($prefix . $filterkey) . "%'";
3454  }
3455  $sql .= ')';
3456  }
3457  $sql .= " ORDER BY pfp.ref_fourn DESC, pfp.quantity ASC";
3458  $sql .= $this->db->plimit($limit, 0);
3459 
3460  // Build output string
3461 
3462  dol_syslog(get_class($this) . "::select_produits_fournisseurs_list", LOG_DEBUG);
3463  $result = $this->db->query($sql);
3464  if ($result) {
3465  require_once DOL_DOCUMENT_ROOT . '/product/dynamic_price/class/price_parser.class.php';
3466  require_once DOL_DOCUMENT_ROOT . '/core/lib/product.lib.php';
3467 
3468  $num = $this->db->num_rows($result);
3469 
3470  //$out.='<select class="flat" id="select'.$htmlname.'" name="'.$htmlname.'">'; // remove select to have id same with combo and ajax
3471  $out .= '<select class="flat ' . ($morecss ? ' ' . $morecss : '') . '" id="' . $htmlname . '" name="' . $htmlname . '">';
3472  if (!$selected) {
3473  $out .= '<option value="-1" selected>' . ($placeholder ? $placeholder : '&nbsp;') . '</option>';
3474  } else {
3475  $out .= '<option value="-1">' . ($placeholder ? $placeholder : '&nbsp;') . '</option>';
3476  }
3477 
3478  $i = 0;
3479  while ($i < $num) {
3480  $objp = $this->db->fetch_object($result);
3481 
3482  if (is_null($objp->idprodfournprice)) {
3483  // There is no supplier price found, we will use the vat rate for sale
3484  $objp->tva_tx = $objp->tva_tx_sale;
3485  $objp->default_vat_code = $objp->default_vat_code_sale;
3486  }
3487 
3488  $outkey = $objp->idprodfournprice; // id in table of price
3489  if (!$outkey && $alsoproductwithnosupplierprice) {
3490  $outkey = 'idprod_' . $objp->rowid; // id of product
3491  }
3492 
3493  $outref = $objp->ref;
3494  $outbarcode = $objp->barcode;
3495  $outqty = 1;
3496  $outdiscount = 0;
3497  $outtype = $objp->fk_product_type;
3498  $outdurationvalue = $outtype == Product::TYPE_SERVICE ? substr($objp->duration, 0, dol_strlen($objp->duration) - 1) : '';
3499  $outdurationunit = $outtype == Product::TYPE_SERVICE ? substr($objp->duration, -1) : '';
3500 
3501  // Units
3502  $outvalUnits = '';
3503  if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
3504  if (!empty($objp->unit_short)) {
3505  $outvalUnits .= ' - ' . $objp->unit_short;
3506  }
3507  if (!empty($objp->weight) && $objp->weight_units !== null) {
3508  $unitToShow = showDimensionInBestUnit($objp->weight, $objp->weight_units, 'weight', $langs);
3509  $outvalUnits .= ' - ' . $unitToShow;
3510  }
3511  if ((!empty($objp->length) || !empty($objp->width) || !empty($objp->height)) && $objp->length_units !== null) {
3512  $unitToShow = $objp->length . ' x ' . $objp->width . ' x ' . $objp->height . ' ' . measuringUnitString(0, 'size', $objp->length_units);
3513  $outvalUnits .= ' - ' . $unitToShow;
3514  }
3515  if (!empty($objp->surface) && $objp->surface_units !== null) {
3516  $unitToShow = showDimensionInBestUnit($objp->surface, $objp->surface_units, 'surface', $langs);
3517  $outvalUnits .= ' - ' . $unitToShow;
3518  }
3519  if (!empty($objp->volume) && $objp->volume_units !== null) {
3520  $unitToShow = showDimensionInBestUnit($objp->volume, $objp->volume_units, 'volume', $langs);
3521  $outvalUnits .= ' - ' . $unitToShow;
3522  }
3523  if ($outdurationvalue && $outdurationunit) {
3524  $da = array(
3525  'h' => $langs->trans('Hour'),
3526  'd' => $langs->trans('Day'),
3527  'w' => $langs->trans('Week'),
3528  'm' => $langs->trans('Month'),
3529  'y' => $langs->trans('Year')
3530  );
3531  if (isset($da[$outdurationunit])) {
3532  $outvalUnits .= ' - ' . $outdurationvalue . ' ' . $langs->transnoentities($da[$outdurationunit] . ($outdurationvalue > 1 ? 's' : ''));
3533  }
3534  }
3535  }
3536 
3537  $objRef = $objp->ref;
3538  if ($filterkey && $filterkey != '') {
3539  $objRef = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', $objRef, 1);
3540  }
3541  $objRefFourn = $objp->ref_fourn;
3542  if ($filterkey && $filterkey != '') {
3543  $objRefFourn = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', $objRefFourn, 1);
3544  }
3545  $label = $objp->label;
3546  if ($filterkey && $filterkey != '') {
3547  $label = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', $label, 1);
3548  }
3549 
3550  switch ($objp->fk_product_type) {
3551  case Product::TYPE_PRODUCT:
3552  $picto = 'product';
3553  break;
3554  case Product::TYPE_SERVICE:
3555  $picto = 'service';
3556  break;
3557  default:
3558  $picto = '';
3559  break;
3560  }
3561 
3562  if (empty($picto)) {
3563  $optlabel = '';
3564  } else {
3565  $optlabel = img_object('', $picto, 'class="paddingright classfortooltip"', 0, 0, 1);
3566  }
3567 
3568  $optlabel .= $objp->ref;
3569  if (!empty($objp->idprodfournprice) && ($objp->ref != $objp->ref_fourn)) {
3570  $optlabel .= ' <span class="opacitymedium">(' . $objp->ref_fourn . ')</span>';
3571  }
3572  if (isModEnabled('barcode') && !empty($objp->barcode)) {
3573  $optlabel .= ' (' . $outbarcode . ')';
3574  }
3575  $optlabel .= ' - ' . dol_trunc($label, $maxlengtharticle);
3576 
3577  $outvallabel = $objRef;
3578  if (!empty($objp->idprodfournprice) && ($objp->ref != $objp->ref_fourn)) {
3579  $outvallabel .= ' (' . $objRefFourn . ')';
3580  }
3581  if (isModEnabled('barcode') && !empty($objp->barcode)) {
3582  $outvallabel .= ' (' . $outbarcode . ')';
3583  }
3584  $outvallabel .= ' - ' . dol_trunc($label, $maxlengtharticle);
3585 
3586  // Units
3587  $optlabel .= $outvalUnits;
3588  $outvallabel .= $outvalUnits;
3589 
3590  if (!empty($objp->idprodfournprice)) {
3591  $outqty = $objp->quantity;
3592  $outdiscount = $objp->remise_percent;
3593  if (isModEnabled('dynamicprices') && !empty($objp->fk_supplier_price_expression)) {
3594  $prod_supplier = new ProductFournisseur($this->db);
3595  $prod_supplier->product_fourn_price_id = $objp->idprodfournprice;
3596  $prod_supplier->id = $objp->fk_product;
3597  $prod_supplier->fourn_qty = $objp->quantity;
3598  $prod_supplier->fourn_tva_tx = $objp->tva_tx;
3599  $prod_supplier->fk_supplier_price_expression = $objp->fk_supplier_price_expression;
3600 
3601  require_once DOL_DOCUMENT_ROOT . '/product/dynamic_price/class/price_parser.class.php';
3602  $priceparser = new PriceParser($this->db);
3603  $price_result = $priceparser->parseProductSupplier($prod_supplier);
3604  if ($price_result >= 0) {
3605  $objp->fprice = $price_result;
3606  if ($objp->quantity >= 1) {
3607  $objp->unitprice = $objp->fprice / $objp->quantity; // Replace dynamically unitprice
3608  }
3609  }
3610  }
3611  if ($objp->quantity == 1) {
3612  $optlabel .= ' - ' . price($objp->fprice * (!empty($conf->global->DISPLAY_DISCOUNTED_SUPPLIER_PRICE) ? (1 - $objp->remise_percent / 100) : 1), 1, $langs, 0, 0, -1, $conf->currency) . "/";
3613  $outvallabel .= ' - ' . price($objp->fprice * (!empty($conf->global->DISPLAY_DISCOUNTED_SUPPLIER_PRICE) ? (1 - $objp->remise_percent / 100) : 1), 0, $langs, 0, 0, -1, $conf->currency) . "/";
3614  $optlabel .= $langs->trans("Unit"); // Do not use strtolower because it breaks utf8 encoding
3615  $outvallabel .= $langs->transnoentities("Unit");
3616  } else {
3617  $optlabel .= ' - ' . price($objp->fprice * (!empty($conf->global->DISPLAY_DISCOUNTED_SUPPLIER_PRICE) ? (1 - $objp->remise_percent / 100) : 1), 1, $langs, 0, 0, -1, $conf->currency) . "/" . $objp->quantity;
3618  $outvallabel .= ' - ' . price($objp->fprice * (!empty($conf->global->DISPLAY_DISCOUNTED_SUPPLIER_PRICE) ? (1 - $objp->remise_percent / 100) : 1), 0, $langs, 0, 0, -1, $conf->currency) . "/" . $objp->quantity;
3619  $optlabel .= ' ' . $langs->trans("Units"); // Do not use strtolower because it breaks utf8 encoding
3620  $outvallabel .= ' ' . $langs->transnoentities("Units");
3621  }
3622 
3623  if ($objp->quantity > 1) {
3624  $optlabel .= " (" . price($objp->unitprice * (!empty($conf->global->DISPLAY_DISCOUNTED_SUPPLIER_PRICE) ? (1 - $objp->remise_percent / 100) : 1), 1, $langs, 0, 0, -1, $conf->currency) . "/" . $langs->trans("Unit") . ")"; // Do not use strtolower because it breaks utf8 encoding
3625  $outvallabel .= " (" . price($objp->unitprice * (!empty($conf->global->DISPLAY_DISCOUNTED_SUPPLIER_PRICE) ? (1 - $objp->remise_percent / 100) : 1), 0, $langs, 0, 0, -1, $conf->currency) . "/" . $langs->transnoentities("Unit") . ")"; // Do not use strtolower because it breaks utf8 encoding
3626  }
3627  if ($objp->remise_percent >= 1) {
3628  $optlabel .= " - " . $langs->trans("Discount") . " : " . vatrate($objp->remise_percent) . ' %';
3629  $outvallabel .= " - " . $langs->transnoentities("Discount") . " : " . vatrate($objp->remise_percent) . ' %';
3630  }
3631  if ($objp->duration) {
3632  $optlabel .= " - " . $objp->duration;
3633  $outvallabel .= " - " . $objp->duration;
3634  }
3635  if (!$socid) {
3636  $optlabel .= " - " . dol_trunc($objp->name, 8);
3637  $outvallabel .= " - " . dol_trunc($objp->name, 8);
3638  }
3639  if ($objp->supplier_reputation) {
3640  //TODO dictionary
3641  $reputations = array('' => $langs->trans('Standard'), 'FAVORITE' => $langs->trans('Favorite'), 'NOTTHGOOD' => $langs->trans('NotTheGoodQualitySupplier'), 'DONOTORDER' => $langs->trans('DoNotOrderThisProductToThisSupplier'));
3642 
3643  $optlabel .= " - " . $reputations[$objp->supplier_reputation];
3644  $outvallabel .= " - " . $reputations[$objp->supplier_reputation];
3645  }
3646  } else {
3647  if (empty($alsoproductwithnosupplierprice)) { // No supplier price defined for couple product/supplier
3648  $optlabel .= " - <span class='opacitymedium'>" . $langs->trans("NoPriceDefinedForThisSupplier") . '</span>';
3649  $outvallabel .= ' - ' . $langs->transnoentities("NoPriceDefinedForThisSupplier");
3650  } else // No supplier price defined for product, even on other suppliers
3651  {
3652  $optlabel .= " - <span class='opacitymedium'>" . $langs->trans("NoPriceDefinedForThisSupplier") . '</span>';
3653  $outvallabel .= ' - ' . $langs->transnoentities("NoPriceDefinedForThisSupplier");
3654  }
3655  }
3656 
3657  if (isModEnabled('stock') && $showstockinlist && isset($objp->stock) && ($objp->fk_product_type == Product::TYPE_PRODUCT || !empty($conf->global->STOCK_SUPPORTS_SERVICES))) {
3658  $novirtualstock = ($showstockinlist == 2);
3659 
3660  if (!empty($user->rights->stock->lire)) {
3661  $outvallabel .= ' - ' . $langs->trans("Stock") . ': ' . price(price2num($objp->stock, 'MS'));
3662 
3663  if ($objp->stock > 0) {
3664  $optlabel .= ' - <span class="product_line_stock_ok">';
3665  } elseif ($objp->stock <= 0) {
3666  $optlabel .= ' - <span class="product_line_stock_too_low">';
3667  }
3668  $optlabel .= $langs->transnoentities("Stock") . ':' . price(price2num($objp->stock, 'MS'));
3669  $optlabel .= '</span>';
3670  if (empty($novirtualstock) && !empty($conf->global->STOCK_SHOW_VIRTUAL_STOCK_IN_PRODUCTS_COMBO)) { // Warning, this option may slow down combo list generation
3671  $langs->load("stocks");
3672 
3673  $tmpproduct = new Product($this->db);
3674  $tmpproduct->fetch($objp->rowid, '', '', '', 1, 1, 1); // Load product without lang and prices arrays (we just need to make ->virtual_stock() after)
3675  $tmpproduct->load_virtual_stock();
3676  $virtualstock = $tmpproduct->stock_theorique;
3677 
3678  $outvallabel .= ' - ' . $langs->trans("VirtualStock") . ':' . $virtualstock;
3679 
3680  $optlabel .= ' - ' . $langs->transnoentities("VirtualStock") . ':';
3681  if ($virtualstock > 0) {
3682  $optlabel .= '<span class="product_line_stock_ok">';
3683  } elseif ($virtualstock <= 0) {
3684  $optlabel .= '<span class="product_line_stock_too_low">';
3685  }
3686  $optlabel .= $virtualstock;
3687  $optlabel .= '</span>';
3688 
3689  unset($tmpproduct);
3690  }
3691  }
3692  }
3693 
3694  $optstart = '<option value="' . $outkey . '"';
3695  if ($selected && $selected == $objp->idprodfournprice) {
3696  $optstart .= ' selected';
3697  }
3698  if (empty($objp->idprodfournprice) && empty($alsoproductwithnosupplierprice)) {
3699  $optstart .= ' disabled';
3700  }
3701 
3702  if (!empty($objp->idprodfournprice) && $objp->idprodfournprice > 0) {
3703  $optstart .= ' data-product-id="' . dol_escape_htmltag($objp->rowid) . '"';
3704  $optstart .= ' data-price-id="' . dol_escape_htmltag($objp->idprodfournprice) . '"';
3705  $optstart .= ' data-qty="' . dol_escape_htmltag($objp->quantity) . '"';
3706  $optstart .= ' data-up="' . dol_escape_htmltag(price2num($objp->unitprice)) . '"';
3707  $optstart .= ' data-up-locale="' . dol_escape_htmltag(price($objp->unitprice)) . '"';
3708  $optstart .= ' data-discount="' . dol_escape_htmltag($outdiscount) . '"';
3709  $optstart .= ' data-tvatx="' . dol_escape_htmltag(price2num($objp->tva_tx)) . '"';
3710  $optstart .= ' data-tvatx-formated="' . dol_escape_htmltag(price($objp->tva_tx, 0, $langs, 1, -1, 2)) . '"';
3711  $optstart .= ' data-default-vat-code="' . dol_escape_htmltag($objp->default_vat_code) . '"';
3712  }
3713  $optstart .= ' data-description="' . dol_escape_htmltag($objp->description, 0, 1) . '"';
3714 
3715  $outarrayentry = array(
3716  'key' => $outkey,
3717  'value' => $outref,
3718  'label' => $outvallabel,
3719  'qty' => $outqty,
3720  'price_qty_ht' => price2num($objp->fprice, 'MU'), // Keep higher resolution for price for the min qty
3721  'price_unit_ht' => price2num($objp->unitprice, 'MU'), // This is used to fill the Unit Price
3722  'price_ht' => price2num($objp->unitprice, 'MU'), // This is used to fill the Unit Price (for compatibility)
3723  'tva_tx_formated' => price($objp->tva_tx, 0, $langs, 1, -1, 2),
3724  'tva_tx' => price2num($objp->tva_tx),
3725  'default_vat_code' => $objp->default_vat_code,
3726  'discount' => $outdiscount,
3727  'type' => $outtype,
3728  'duration_value' => $outdurationvalue,
3729  'duration_unit' => $outdurationunit,
3730  'disabled' => (empty($objp->idprodfournprice) ? true : false),
3731  'description' => $objp->description
3732  );
3733 
3734  $parameters = array(
3735  'objp' => &$objp,
3736  'optstart' => &$optstart,
3737  'optlabel' => &$optlabel,
3738  'outvallabel' => &$outvallabel,
3739  'outarrayentry' => &$outarrayentry
3740  );
3741  $reshook = $hookmanager->executeHooks('selectProduitsFournisseurListOption', $parameters, $this);
3742 
3743 
3744  // Add new entry
3745  // "key" value of json key array is used by jQuery automatically as selected value. Example: 'type' = product or service, 'price_ht' = unit price without tax
3746  // "label" value of json key array is used by jQuery automatically as text for combo box
3747  $out .= $optstart . ' data-html="' . dol_escape_htmltag($optlabel) . '">' . $optlabel . "</option>\n";
3748  array_push(
3749  $outarray,
3750  array('key' => $outkey,
3751  'value' => $outref,
3752  'label' => $outvallabel,
3753  'qty' => $outqty,
3754  'price_qty_ht' => price2num($objp->fprice, 'MU'), // Keep higher resolution for price for the min qty
3755  'price_qty_ht_locale' => price($objp->fprice),
3756  'price_unit_ht' => price2num($objp->unitprice, 'MU'), // This is used to fill the Unit Price
3757  'price_unit_ht_locale' => price($objp->unitprice),
3758  'price_ht' => price2num($objp->unitprice, 'MU'), // This is used to fill the Unit Price (for compatibility)
3759  'tva_tx_formated' => price($objp->tva_tx),
3760  'tva_tx' => price2num($objp->tva_tx),
3761  'default_vat_code' => $objp->default_vat_code,
3762  'discount' => $outdiscount,
3763  'type' => $outtype,
3764  'duration_value' => $outdurationvalue,
3765  'duration_unit' => $outdurationunit,
3766  'disabled' => (empty($objp->idprodfournprice) ? true : false),
3767  'description' => $objp->description
3768  )
3769  );
3770  // Exemple of var_dump $outarray
3771  // array(1) {[0]=>array(6) {[key"]=>string(1) "2" ["value"]=>string(3) "ppp"
3772  // ["label"]=>string(76) "ppp (<strong>f</strong>ff2) - ppp - 20,00 Euros/1unité (20,00 Euros/unité)"
3773  // ["qty"]=>string(1) "1" ["discount"]=>string(1) "0" ["disabled"]=>bool(false)
3774  //}
3775  //var_dump($outval); var_dump(utf8_check($outval)); var_dump(json_encode($outval));
3776  //$outval=array('label'=>'ppp (<strong>f</strong>ff2) - ppp - 20,00 Euros/ Unité (20,00 Euros/unité)');
3777  //var_dump($outval); var_dump(utf8_check($outval)); var_dump(json_encode($outval));
3778 
3779  $i++;
3780  }
3781  $out .= '</select>';
3782 
3783  $this->db->free($result);
3784 
3785  include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
3786  $out .= ajax_combobox($htmlname);
3787  } else {
3788  dol_print_error($this->db);
3789  }
3790 
3791  if (empty($outputmode)) {
3792  return $out;
3793  }
3794  return $outarray;
3795  }
3796 
3797  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3798 
3807  public function select_product_fourn_price($productid, $htmlname = 'productfournpriceid', $selected_supplier = '')
3808  {
3809  // phpcs:enable
3810  global $langs, $conf;
3811 
3812  $langs->load('stocks');
3813 
3814  $sql = "SELECT p.rowid, p.ref, p.label, p.price, p.duration, pfp.fk_soc,";
3815  $sql .= " pfp.ref_fourn, pfp.rowid as idprodfournprice, pfp.price as fprice, pfp.remise_percent, pfp.quantity, pfp.unitprice,";
3816  $sql .= " pfp.fk_supplier_price_expression, pfp.fk_product, pfp.tva_tx, s.nom as name";
3817  $sql .= " FROM " . $this->db->prefix() . "product as p";
3818  $sql .= " LEFT JOIN " . $this->db->prefix() . "product_fournisseur_price as pfp ON p.rowid = pfp.fk_product";
3819  $sql .= " LEFT JOIN " . $this->db->prefix() . "societe as s ON pfp.fk_soc = s.rowid";
3820  $sql .= " WHERE pfp.entity IN (" . getEntity('productsupplierprice') . ")";
3821  $sql .= " AND p.tobuy = 1";
3822  $sql .= " AND s.fournisseur = 1";
3823  $sql .= " AND p.rowid = " . ((int) $productid);
3824  if (empty($conf->global->PRODUCT_BEST_SUPPLIER_PRICE_PRESELECTED)) {
3825  $sql .= " ORDER BY s.nom, pfp.ref_fourn DESC";
3826  } else {
3827  $sql .= " ORDER BY pfp.unitprice ASC";
3828  }
3829 
3830  dol_syslog(get_class($this) . "::select_product_fourn_price", LOG_DEBUG);
3831  $result = $this->db->query($sql);
3832 
3833  if ($result) {
3834  $num = $this->db->num_rows($result);
3835 
3836  $form = '<select class="flat" id="select_' . $htmlname . '" name="' . $htmlname . '">';
3837 
3838  if (!$num) {
3839  $form .= '<option value="0">-- ' . $langs->trans("NoSupplierPriceDefinedForThisProduct") . ' --</option>';
3840  } else {
3841  require_once DOL_DOCUMENT_ROOT . '/product/dynamic_price/class/price_parser.class.php';
3842  $form .= '<option value="0">&nbsp;</option>';
3843 
3844  $i = 0;
3845  while ($i < $num) {
3846  $objp = $this->db->fetch_object($result);
3847 
3848  $opt = '<option value="' . $objp->idprodfournprice . '"';
3849  //if there is only one supplier, preselect it
3850  if ($num == 1 || ($selected_supplier > 0 && $objp->fk_soc == $selected_supplier) || ($i == 0 && !empty($conf->global->PRODUCT_BEST_SUPPLIER_PRICE_PRESELECTED))) {
3851  $opt .= ' selected';
3852  }
3853  $opt .= '>' . $objp->name . ' - ' . $objp->ref_fourn . ' - ';
3854 
3855  if (isModEnabled('dynamicprices') && !empty($objp->fk_supplier_price_expression)) {
3856  $prod_supplier = new ProductFournisseur($this->db);
3857  $prod_supplier->product_fourn_price_id = $objp->idprodfournprice;
3858  $prod_supplier->id = $productid;
3859  $prod_supplier->fourn_qty = $objp->quantity;
3860  $prod_supplier->fourn_tva_tx = $objp->tva_tx;
3861  $prod_supplier->fk_supplier_price_expression = $objp->fk_supplier_price_expression;
3862 
3863  require_once DOL_DOCUMENT_ROOT . '/product/dynamic_price/class/price_parser.class.php';
3864  $priceparser = new PriceParser($this->db);
3865  $price_result = $priceparser->parseProductSupplier($prod_supplier);
3866  if ($price_result >= 0) {
3867  $objp->fprice = $price_result;
3868  if ($objp->quantity >= 1) {
3869  $objp->unitprice = $objp->fprice / $objp->quantity;
3870  }
3871  }
3872  }
3873  if ($objp->quantity == 1) {
3874  $opt .= price($objp->fprice * (!empty($conf->global->DISPLAY_DISCOUNTED_SUPPLIER_PRICE) ? (1 - $objp->remise_percent / 100) : 1), 1, $langs, 0, 0, -1, $conf->currency) . "/";
3875  }
3876 
3877  $opt .= $objp->quantity . ' ';
3878 
3879  if ($objp->quantity == 1) {
3880  $opt .= $langs->trans("Unit");
3881  } else {
3882  $opt .= $langs->trans("Units");
3883  }
3884  if ($objp->quantity > 1) {
3885  $opt .= " - ";
3886  $opt .= price($objp->unitprice * (!empty($conf->global->DISPLAY_DISCOUNTED_SUPPLIER_PRICE) ? (1 - $objp->remise_percent / 100) : 1), 1, $langs, 0, 0, -1, $conf->currency) . "/" . $langs->trans("Unit");
3887  }
3888  if ($objp->duration) {
3889  $opt .= " - " . $objp->duration;
3890  }
3891  $opt .= "</option>\n";
3892 
3893  $form .= $opt;
3894  $i++;
3895  }
3896  }
3897 
3898  $form .= '</select>';
3899  $this->db->free($result);
3900  return $form;
3901  } else {
3902  dol_print_error($this->db);
3903  return '';
3904  }
3905  }
3906 
3907  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3908 
3918  public function select_address($selected, $socid, $htmlname = 'address_id', $showempty = 0)
3919  {
3920  // phpcs:enable
3921  // looking for users
3922  $sql = "SELECT a.rowid, a.label";
3923  $sql .= " FROM " . $this->db->prefix() . "societe_address as a";
3924  $sql .= " WHERE a.fk_soc = " . ((int) $socid);
3925  $sql .= " ORDER BY a.label ASC";
3926 
3927  dol_syslog(get_class($this) . "::select_address", LOG_DEBUG);
3928  $resql = $this->db->query($sql);
3929  if ($resql) {
3930  print '<select class="flat" id="select_' . $htmlname . '" name="' . $htmlname . '">';
3931  if ($showempty) {
3932  print '<option value="0">&nbsp;</option>';
3933  }
3934  $num = $this->db->num_rows($resql);
3935  $i = 0;
3936  if ($num) {
3937  while ($i < $num) {
3938  $obj = $this->db->fetch_object($resql);
3939 
3940  if ($selected && $selected == $obj->rowid) {
3941  print '<option value="' . $obj->rowid . '" selected>' . $obj->label . '</option>';
3942  } else {
3943  print '<option value="' . $obj->rowid . '">' . $obj->label . '</option>';
3944  }
3945  $i++;
3946  }
3947  }
3948  print '</select>';
3949  return $num;
3950  } else {
3951  dol_print_error($this->db);
3952  return -1;
3953  }
3954  }
3955 
3956  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3957 
3964  {
3965  // phpcs:enable
3966  global $langs;
3967 
3968  $num = count($this->cache_conditions_paiements);
3969  if ($num > 0) {
3970  return 0; // Cache already loaded
3971  }
3972 
3973  dol_syslog(__METHOD__, LOG_DEBUG);
3974 
3975  $sql = "SELECT rowid, code, libelle as label, deposit_percent";
3976  $sql .= " FROM " . $this->db->prefix() . 'c_payment_term';
3977  $sql .= " WHERE entity IN (" . getEntity('c_payment_term') . ")";
3978  $sql .= " AND active > 0";
3979  $sql .= " ORDER BY sortorder";
3980 
3981  $resql = $this->db->query($sql);
3982  if ($resql) {
3983  $num = $this->db->num_rows($resql);
3984  $i = 0;
3985  while ($i < $num) {
3986  $obj = $this->db->fetch_object($resql);
3987 
3988  // Si traduction existe, on l'utilise, sinon on prend le libelle par defaut
3989  $label = ($langs->trans("PaymentConditionShort" . $obj->code) != ("PaymentConditionShort" . $obj->code) ? $langs->trans("PaymentConditionShort" . $obj->code) : ($obj->label != '-' ? $obj->label : ''));
3990  $this->cache_conditions_paiements[$obj->rowid]['code'] = $obj->code;
3991  $this->cache_conditions_paiements[$obj->rowid]['label'] = $label;
3992  $this->cache_conditions_paiements[$obj->rowid]['deposit_percent'] = $obj->deposit_percent;
3993  $i++;
3994  }
3995 
3996  //$this->cache_conditions_paiements=dol_sort_array($this->cache_conditions_paiements, 'label', 'asc', 0, 0, 1); // We use the field sortorder of table
3997 
3998  return $num;
3999  } else {
4000  dol_print_error($this->db);
4001  return -1;
4002  }
4003  }
4004 
4005  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4006 
4012  public function load_cache_availability()
4013  {
4014  // phpcs:enable
4015  global $langs;
4016 
4017  $num = count($this->cache_availability); // TODO Use $conf->cache['availability'] instead of $this->cache_availability
4018  if ($num > 0) {
4019  return 0; // Cache already loaded
4020  }
4021 
4022  dol_syslog(__METHOD__, LOG_DEBUG);
4023 
4024  $langs->load('propal');
4025 
4026  $sql = "SELECT rowid, code, label, position";
4027  $sql .= " FROM " . $this->db->prefix() . 'c_availability';
4028  $sql .= " WHERE active > 0";
4029 
4030  $resql = $this->db->query($sql);
4031  if ($resql) {
4032  $num = $this->db->num_rows($resql);
4033  $i = 0;
4034  while ($i < $num) {
4035  $obj = $this->db->fetch_object($resql);
4036 
4037  // Si traduction existe, on l'utilise, sinon on prend le libelle par defaut
4038  $label = ($langs->trans("AvailabilityType" . $obj->code) != ("AvailabilityType" . $obj->code) ? $langs->trans("AvailabilityType" . $obj->code) : ($obj->label != '-' ? $obj->label : ''));
4039  $this->cache_availability[$obj->rowid]['code'] = $obj->code;
4040  $this->cache_availability[$obj->rowid]['label'] = $label;
4041  $this->cache_availability[$obj->rowid]['position'] = $obj->position;
4042  $i++;
4043  }
4044 
4045  $this->cache_availability = dol_sort_array($this->cache_availability, 'position', 'asc', 0, 0, 1);
4046 
4047  return $num;
4048  } else {
4049  dol_print_error($this->db);
4050  return -1;
4051  }
4052  }
4053 
4064  public function selectAvailabilityDelay($selected = '', $htmlname = 'availid', $filtertype = '', $addempty = 0, $morecss = '')
4065  {
4066  global $langs, $user;
4067 
4068  $this->load_cache_availability();
4069 
4070  dol_syslog(__METHOD__ . " selected=" . $selected . ", htmlname=" . $htmlname, LOG_DEBUG);
4071 
4072  print '<select id="' . $htmlname . '" class="flat' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '">';
4073  if ($addempty) {
4074  print '<option value="0">&nbsp;</option>';
4075  }
4076  foreach ($this->cache_availability as $id => $arrayavailability) {
4077  if ($selected == $id) {
4078  print '<option value="' . $id . '" selected>';
4079  } else {
4080  print '<option value="' . $id . '">';
4081  }
4082  print dol_escape_htmltag($arrayavailability['label']);
4083  print '</option>';
4084  }
4085  print '</select>';
4086  if ($user->admin) {
4087  print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
4088  }
4089  print ajax_combobox($htmlname);
4090  }
4091 
4097  public function loadCacheInputReason()
4098  {
4099  global $langs;
4100 
4101  $num = count($this->cache_demand_reason); // TODO Use $conf->cache['input_reason'] instead of $this->cache_demand_reason
4102  if ($num > 0) {
4103  return 0; // Cache already loaded
4104  }
4105 
4106  $sql = "SELECT rowid, code, label";
4107  $sql .= " FROM " . $this->db->prefix() . 'c_input_reason';
4108  $sql .= " WHERE active > 0";
4109 
4110  $resql = $this->db->query($sql);
4111  if ($resql) {
4112  $num = $this->db->num_rows($resql);
4113  $i = 0;
4114  $tmparray = array();
4115  while ($i < $num) {
4116  $obj = $this->db->fetch_object($resql);
4117 
4118  // Si traduction existe, on l'utilise, sinon on prend le libelle par defaut
4119  $label = ($obj->label != '-' ? $obj->label : '');
4120  if ($langs->trans("DemandReasonType" . $obj->code) != ("DemandReasonType" . $obj->code)) {
4121  $label = $langs->trans("DemandReasonType" . $obj->code); // So translation key DemandReasonTypeSRC_XXX will work
4122  }
4123  if ($langs->trans($obj->code) != $obj->code) {
4124  $label = $langs->trans($obj->code); // So translation key SRC_XXX will work
4125  }
4126 
4127  $tmparray[$obj->rowid]['id'] = $obj->rowid;
4128  $tmparray[$obj->rowid]['code'] = $obj->code;
4129  $tmparray[$obj->rowid]['label'] = $label;
4130  $i++;
4131  }
4132 
4133  $this->cache_demand_reason = dol_sort_array($tmparray, 'label', 'asc', 0, 0, 1);
4134 
4135  unset($tmparray);
4136  return $num;
4137  } else {
4138  dol_print_error($this->db);
4139  return -1;
4140  }
4141  }
4142 
4155  public function selectInputReason($selected = '', $htmlname = 'demandreasonid', $exclude = '', $addempty = 0, $morecss = '', $notooltip = 0)
4156  {
4157  global $langs, $user;
4158 
4159  $this->loadCacheInputReason();
4160 
4161  print '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" id="select_' . $htmlname . '" name="' . $htmlname . '">';
4162  if ($addempty) {
4163  print '<option value="0"' . (empty($selected) ? ' selected' : '') . '>&nbsp;</option>';
4164  }
4165  foreach ($this->cache_demand_reason as $id => $arraydemandreason) {
4166  if ($arraydemandreason['code'] == $exclude) {
4167  continue;
4168  }
4169 
4170  if ($selected && ($selected == $arraydemandreason['id'] || $selected == $arraydemandreason['code'])) {
4171  print '<option value="' . $arraydemandreason['id'] . '" selected>';
4172  } else {
4173  print '<option value="' . $arraydemandreason['id'] . '">';
4174  }
4175  $label = $arraydemandreason['label']; // Translation of label was already done into the ->loadCacheInputReason
4176  print $langs->trans($label);
4177  print '</option>';
4178  }
4179  print '</select>';
4180  if ($user->admin && empty($notooltip)) {
4181  print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
4182  }
4183  print ajax_combobox('select_' . $htmlname);
4184  }
4185 
4186  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4187 
4193  public function load_cache_types_paiements()
4194  {
4195  // phpcs:enable
4196  global $langs;
4197 
4198  $num = count($this->cache_types_paiements); // TODO Use $conf->cache['payment_mode'] instead of $this->cache_types_paiements
4199  if ($num > 0) {
4200  return $num; // Cache already loaded
4201  }
4202 
4203  dol_syslog(__METHOD__, LOG_DEBUG);
4204 
4205  $this->cache_types_paiements = array();
4206 
4207  $sql = "SELECT id, code, libelle as label, type, active";
4208  $sql .= " FROM " . $this->db->prefix() . "c_paiement";
4209  $sql .= " WHERE entity IN (" . getEntity('c_paiement') . ")";
4210 
4211  $resql = $this->db->query($sql);
4212  if ($resql) {
4213  $num = $this->db->num_rows($resql);
4214  $i = 0;
4215  while ($i < $num) {
4216  $obj = $this->db->fetch_object($resql);
4217 
4218  // Si traduction existe, on l'utilise, sinon on prend le libelle par defaut
4219  $label = ($langs->transnoentitiesnoconv("PaymentTypeShort" . $obj->code) != ("PaymentTypeShort" . $obj->code) ? $langs->transnoentitiesnoconv("PaymentTypeShort" . $obj->code) : ($obj->label != '-' ? $obj->label : ''));
4220  $this->cache_types_paiements[$obj->id]['id'] = $obj->id;
4221  $this->cache_types_paiements[$obj->id]['code'] = $obj->code;
4222  $this->cache_types_paiements[$obj->id]['label'] = $label;
4223  $this->cache_types_paiements[$obj->id]['type'] = $obj->type;
4224  $this->cache_types_paiements[$obj->id]['active'] = $obj->active;
4225  $i++;
4226  }
4227 
4228  $this->cache_types_paiements = dol_sort_array($this->cache_types_paiements, 'label', 'asc', 0, 0, 1);
4229 
4230  return $num;
4231  } else {
4232  dol_print_error($this->db);
4233  return -1;
4234  }
4235  }
4236 
4237 
4238  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4239 
4257  public function select_conditions_paiements($selected = 0, $htmlname = 'condid', $filtertype = -1, $addempty = 0, $noinfoadmin = 0, $morecss = '', $deposit_percent = -1)
4258  {
4259  // phpcs:enable
4260  print $this->getSelectConditionsPaiements($selected, $htmlname, $filtertype, $addempty, $noinfoadmin, $morecss, $deposit_percent);
4261  }
4262 
4263 
4280  public function getSelectConditionsPaiements($selected = 0, $htmlname = 'condid', $filtertype = -1, $addempty = 0, $noinfoadmin = 0, $morecss = '', $deposit_percent = -1)
4281  {
4282  global $langs, $user, $conf;
4283 
4284  $out = '';
4285  dol_syslog(__METHOD__ . " selected=" . $selected . ", htmlname=" . $htmlname, LOG_DEBUG);
4286 
4288 
4289  // Set default value if not already set by caller
4290  if (empty($selected) && !empty($conf->global->MAIN_DEFAULT_PAYMENT_TERM_ID)) {
4291  dol_syslog(__METHOD__ . "Using deprecated option MAIN_DEFAULT_PAYMENT_TERM_ID", LOG_NOTICE);
4292  $selected = $conf->global->MAIN_DEFAULT_PAYMENT_TERM_ID;
4293  }
4294 
4295  $out .= '<select id="' . $htmlname . '" class="flat selectpaymentterms' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '">';
4296  if ($addempty) {
4297  $out .= '<option value="0">&nbsp;</option>';
4298  }
4299 
4300  $selectedDepositPercent = null;
4301 
4302  foreach ($this->cache_conditions_paiements as $id => $arrayconditions) {
4303  if ($filtertype <= 0 && !empty($arrayconditions['deposit_percent'])) {
4304  continue;
4305  }
4306 
4307  if ($selected == $id) {
4308  $selectedDepositPercent = $deposit_percent > 0 ? $deposit_percent : $arrayconditions['deposit_percent'];
4309  $out .= '<option value="' . $id . '" data-deposit_percent="' . $arrayconditions['deposit_percent'] . '" selected>';
4310  } else {
4311  $out .= '<option value="' . $id . '" data-deposit_percent="' . $arrayconditions['deposit_percent'] . '">';
4312  }
4313  $label = $arrayconditions['label'];
4314 
4315  if (!empty($arrayconditions['deposit_percent'])) {
4316  $label = str_replace('__DEPOSIT_PERCENT__', $deposit_percent > 0 ? $deposit_percent : $arrayconditions['deposit_percent'], $label);
4317  }
4318 
4319  $out .= $label;
4320  $out .= '</option>';
4321  }
4322  $out .= '</select>';
4323  if ($user->admin && empty($noinfoadmin)) {
4324  $out .= info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
4325  }
4326  $out .= ajax_combobox($htmlname);
4327 
4328  if ($deposit_percent >= 0) {
4329  $out .= ' <span id="' . $htmlname . '_deposit_percent_container"' . (empty($selectedDepositPercent) ? ' style="display: none"' : '') . '>';
4330  $out .= $langs->trans('DepositPercent') . ' : ';
4331  $out .= '<input id="' . $htmlname . '_deposit_percent" name="' . $htmlname . '_deposit_percent" class="maxwidth50" value="' . $deposit_percent . '" />';
4332  $out .= '</span>';
4333  $out .= '
4334  <script nonce="' . getNonce() . '">
4335  $(document).ready(function () {
4336  $("#' . $htmlname . '").change(function () {
4337  let $selected = $(this).find("option:selected");
4338  let depositPercent = $selected.attr("data-deposit_percent");
4339 
4340  if (depositPercent.length > 0) {
4341  $("#' . $htmlname . '_deposit_percent_container").show().find("#' . $htmlname . '_deposit_percent").val(depositPercent);
4342  } else {
4343  $("#' . $htmlname . '_deposit_percent_container").hide();
4344  }
4345 
4346  return true;
4347  });
4348  });
4349  </script>';
4350  }
4351 
4352  return $out;
4353  }
4354 
4355 
4356  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4357 
4374  public function select_types_paiements($selected = '', $htmlname = 'paiementtype', $filtertype = '', $format = 0, $empty = 1, $noadmininfo = 0, $maxlength = 0, $active = 1, $morecss = '', $nooutput = 0)
4375  {
4376  // phpcs:enable
4377  global $langs, $user, $conf;
4378 
4379  $out = '';
4380 
4381  dol_syslog(__METHOD__ . " " . $selected . ", " . $htmlname . ", " . $filtertype . ", " . $format, LOG_DEBUG);
4382 
4383  $filterarray = array();
4384  if ($filtertype == 'CRDT') {
4385  $filterarray = array(0, 2, 3);
4386  } elseif ($filtertype == 'DBIT') {
4387  $filterarray = array(1, 2, 3);
4388  } elseif ($filtertype != '' && $filtertype != '-1') {
4389  $filterarray = explode(',', $filtertype);
4390  }
4391 
4392  $this->load_cache_types_paiements();
4393 
4394  // Set default value if not already set by caller
4395  if (empty($selected) && !empty($conf->global->MAIN_DEFAULT_PAYMENT_TYPE_ID)) {
4396  dol_syslog(__METHOD__ . "Using deprecated option MAIN_DEFAULT_PAYMENT_TYPE_ID", LOG_NOTICE);
4397  $selected = $conf->global->MAIN_DEFAULT_PAYMENT_TYPE_ID;
4398  }
4399 
4400  $out .= '<select id="select' . $htmlname . '" class="flat selectpaymenttypes' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '">';
4401  if ($empty) {
4402  $out .= '<option value="">&nbsp;</option>';
4403  }
4404  foreach ($this->cache_types_paiements as $id => $arraytypes) {
4405  // If not good status
4406  if ($active >= 0 && $arraytypes['active'] != $active) {
4407  continue;
4408  }
4409 
4410  // On passe si on a demande de filtrer sur des modes de paiments particuliers
4411  if (count($filterarray) && !in_array($arraytypes['type'], $filterarray)) {
4412  continue;
4413  }
4414 
4415  // We discard empty line if showempty is on because an empty line has already been output.
4416  if ($empty && empty($arraytypes['code'])) {
4417  continue;
4418  }
4419 
4420  if ($format == 0) {
4421  $out .= '<option value="' . $id . '"';
4422  } elseif ($format == 1) {
4423  $out .= '<option value="' . $arraytypes['code'] . '"';
4424  } elseif ($format == 2) {
4425  $out .= '<option value="' . $arraytypes['code'] . '"';
4426  } elseif ($format == 3) {
4427  $out .= '<option value="' . $id . '"';
4428  }
4429  // Print attribute selected or not
4430  if ($format == 1 || $format == 2) {
4431  if ($selected == $arraytypes['code']) {
4432  $out .= ' selected';
4433  }
4434  } else {
4435  if ($selected == $id) {
4436  $out .= ' selected';
4437  }
4438  }
4439  $out .= '>';
4440  $value = '';
4441  if ($format == 0) {
4442  $value = ($maxlength ? dol_trunc($arraytypes['label'], $maxlength) : $arraytypes['label']);
4443  } elseif ($format == 1) {
4444  $value = $arraytypes['code'];
4445  } elseif ($format == 2) {
4446  $value = ($maxlength ? dol_trunc($arraytypes['label'], $maxlength) : $arraytypes['label']);
4447  } elseif ($format == 3) {
4448  $value = $arraytypes['code'];
4449  }
4450  $out .= $value ? $value : '&nbsp;';
4451  $out .= '</option>';
4452  }
4453  $out .= '</select>';
4454  if ($user->admin && !$noadmininfo) {
4455  $out .= info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
4456  }
4457  $out .= ajax_combobox('select' . $htmlname);
4458 
4459  if (empty($nooutput)) {
4460  print $out;
4461  } else {
4462  return $out;
4463  }
4464  }
4465 
4466 
4475  public function selectPriceBaseType($selected = '', $htmlname = 'price_base_type', $addjscombo = 0)
4476  {
4477  global $langs;
4478 
4479  $return = '<select class="flat maxwidth100" id="select_' . $htmlname . '" name="' . $htmlname . '">';
4480  $options = array(
4481  'HT' => $langs->trans("HT"),
4482  'TTC' => $langs->trans("TTC")
4483  );
4484  foreach ($options as $id => $value) {
4485  if ($selected == $id) {
4486  $return .= '<option value="' . $id . '" selected>' . $value;
4487  } else {
4488  $return .= '<option value="' . $id . '">' . $value;
4489  }
4490  $return .= '</option>';
4491  }
4492  $return .= '</select>';
4493  if ($addjscombo) {
4494  $return .= ajax_combobox('select_' . $htmlname);
4495  }
4496 
4497  return $return;
4498  }
4499 
4500  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4501 
4507  public function load_cache_transport_mode()
4508  {
4509  // phpcs:enable
4510  global $langs;
4511 
4512  $num = count($this->cache_transport_mode); // TODO Use $conf->cache['payment_mode'] instead of $this->cache_transport_mode
4513  if ($num > 0) {
4514  return $num; // Cache already loaded
4515  }
4516 
4517  dol_syslog(__METHOD__, LOG_DEBUG);
4518 
4519  $this->cache_transport_mode = array();
4520 
4521  $sql = "SELECT rowid, code, label, active";
4522  $sql .= " FROM " . $this->db->prefix() . "c_transport_mode";
4523  $sql .= " WHERE entity IN (" . getEntity('c_transport_mode') . ")";
4524 
4525  $resql = $this->db->query($sql);
4526  if ($resql) {
4527  $num = $this->db->num_rows($resql);
4528  $i = 0;
4529  while ($i < $num) {
4530  $obj = $this->db->fetch_object($resql);
4531 
4532  // If traduction exist, we use it else we take the default label
4533  $label = ($langs->transnoentitiesnoconv("PaymentTypeShort" . $obj->code) != ("PaymentTypeShort" . $obj->code) ? $langs->transnoentitiesnoconv("PaymentTypeShort" . $obj->code) : ($obj->label != '-' ? $obj->label : ''));
4534  $this->cache_transport_mode[$obj->rowid]['rowid'] = $obj->rowid;
4535  $this->cache_transport_mode[$obj->rowid]['code'] = $obj->code;
4536  $this->cache_transport_mode[$obj->rowid]['label'] = $label;
4537  $this->cache_transport_mode[$obj->rowid]['active'] = $obj->active;
4538  $i++;
4539  }
4540 
4541  $this->cache_transport_mode = dol_sort_array($this->cache_transport_mode, 'label', 'asc', 0, 0, 1);
4542 
4543  return $num;
4544  } else {
4545  dol_print_error($this->db);
4546  return -1;
4547  }
4548  }
4549 
4563  public function selectTransportMode($selected = '', $htmlname = 'transportmode', $format = 0, $empty = 1, $noadmininfo = 0, $maxlength = 0, $active = 1, $morecss = '')
4564  {
4565  global $langs, $user;
4566 
4567  dol_syslog(__METHOD__ . " " . $selected . ", " . $htmlname . ", " . $format, LOG_DEBUG);
4568 
4569  $this->load_cache_transport_mode();
4570 
4571  print '<select id="select' . $htmlname . '" class="flat selectmodetransport' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '">';
4572  if ($empty) {
4573  print '<option value="">&nbsp;</option>';
4574  }
4575  foreach ($this->cache_transport_mode as $id => $arraytypes) {
4576  // If not good status
4577  if ($active >= 0 && $arraytypes['active'] != $active) {
4578  continue;
4579  }
4580 
4581  // We discard empty line if showempty is on because an empty line has already been output.
4582  if ($empty && empty($arraytypes['code'])) {
4583  continue;
4584  }
4585 
4586  if ($format == 0) {
4587  print '<option value="' . $id . '"';
4588  } elseif ($format == 1) {
4589  print '<option value="' . $arraytypes['code'] . '"';
4590  } elseif ($format == 2) {
4591  print '<option value="' . $arraytypes['code'] . '"';
4592  } elseif ($format == 3) {
4593  print '<option value="' . $id . '"';
4594  }
4595  // If text is selected, we compare with code, else with id
4596  if (preg_match('/[a-z]/i', $selected) && $selected == $arraytypes['code']) {
4597  print ' selected';
4598  } elseif ($selected == $id) {
4599  print ' selected';
4600  }
4601  print '>';
4602  $value = '';
4603  if ($format == 0) {
4604  $value = ($maxlength ? dol_trunc($arraytypes['label'], $maxlength) : $arraytypes['label']);
4605  } elseif ($format == 1) {
4606  $value = $arraytypes['code'];
4607  } elseif ($format == 2) {
4608  $value = ($maxlength ? dol_trunc($arraytypes['label'], $maxlength) : $arraytypes['label']);
4609  } elseif ($format == 3) {
4610  $value = $arraytypes['code'];
4611  }
4612  print $value ? $value : '&nbsp;';
4613  print '</option>';
4614  }
4615  print '</select>';
4616  if ($user->admin && !$noadmininfo) {
4617  print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
4618  }
4619  }
4620 
4633  public function selectShippingMethod($selected = '', $htmlname = 'shipping_method_id', $filtre = '', $useempty = 0, $moreattrib = '', $noinfoadmin = 0, $morecss = '')
4634  {
4635  global $langs, $conf, $user;
4636 
4637  $langs->load("admin");
4638  $langs->load("deliveries");
4639 
4640  $sql = "SELECT rowid, code, libelle as label";
4641  $sql .= " FROM " . $this->db->prefix() . "c_shipment_mode";
4642  $sql .= " WHERE active > 0";
4643  if ($filtre) {
4644  $sql .= " AND " . $filtre;
4645  }
4646  $sql .= " ORDER BY libelle ASC";
4647 
4648  dol_syslog(get_class($this) . "::selectShippingMode", LOG_DEBUG);
4649  $result = $this->db->query($sql);
4650  if ($result) {
4651  $num = $this->db->num_rows($result);
4652  $i = 0;
4653  if ($num) {
4654  print '<select id="select' . $htmlname . '" class="flat selectshippingmethod' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '"' . ($moreattrib ? ' ' . $moreattrib : '') . '>';
4655  if ($useempty == 1 || ($useempty == 2 && $num > 1)) {
4656  print '<option value="-1">&nbsp;</option>';
4657  }
4658  while ($i < $num) {
4659  $obj = $this->db->fetch_object($result);
4660  if ($selected == $obj->rowid) {
4661  print '<option value="' . $obj->rowid . '" selected>';
4662  } else {
4663  print '<option value="' . $obj->rowid . '">';
4664  }
4665  print ($langs->trans("SendingMethod" . strtoupper($obj->code)) != "SendingMethod" . strtoupper($obj->code)) ? $langs->trans("SendingMethod" . strtoupper($obj->code)) : $obj->label;
4666  print '</option>';
4667  $i++;
4668  }
4669  print "</select>";
4670  if ($user->admin && empty($noinfoadmin)) {
4671  print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
4672  }
4673 
4674  print ajax_combobox('select' . $htmlname);
4675  } else {
4676  print $langs->trans("NoShippingMethodDefined");
4677  }
4678  } else {
4679  dol_print_error($this->db);
4680  }
4681  }
4682 
4692  public function formSelectShippingMethod($page, $selected = '', $htmlname = 'shipping_method_id', $addempty = 0)
4693  {
4694  global $langs;
4695 
4696  $langs->load("deliveries");
4697 
4698  if ($htmlname != "none") {
4699  print '<form method="POST" action="' . $page . '">';
4700  print '<input type="hidden" name="action" value="setshippingmethod">';
4701  print '<input type="hidden" name="token" value="' . newToken() . '">';
4702  $this->selectShippingMethod($selected, $htmlname, '', $addempty);
4703  print '<input type="submit" class="button valignmiddle" value="' . $langs->trans("Modify") . '">';
4704  print '</form>';
4705  } else {
4706  if ($selected) {
4707  $code = $langs->getLabelFromKey($this->db, $selected, 'c_shipment_mode', 'rowid', 'code');
4708  print $langs->trans("SendingMethod" . strtoupper($code));
4709  } else {
4710  print "&nbsp;";
4711  }
4712  }
4713  }
4714 
4723  public function selectSituationInvoices($selected = '', $socid = 0)
4724  {
4725  global $langs;
4726 
4727  $langs->load('bills');
4728 
4729  $opt = '<option value="" selected></option>';
4730  $sql = "SELECT rowid, ref, situation_cycle_ref, situation_counter, situation_final, fk_soc";
4731  $sql .= ' FROM ' . $this->db->prefix() . 'facture';
4732  $sql .= ' WHERE entity IN (' . getEntity('invoice') . ')';
4733  $sql .= ' AND situation_counter >= 1';
4734  $sql .= ' AND fk_soc = ' . (int) $socid;
4735  $sql .= ' AND type <> 2';
4736  $sql .= ' ORDER by situation_cycle_ref, situation_counter desc';
4737  $resql = $this->db->query($sql);
4738 
4739  if ($resql && $this->db->num_rows($resql) > 0) {
4740  // Last seen cycle
4741  $ref = 0;
4742  while ($obj = $this->db->fetch_object($resql)) {
4743  //Same cycle ?
4744  if ($obj->situation_cycle_ref != $ref) {
4745  // Just seen this cycle
4746  $ref = $obj->situation_cycle_ref;
4747  //not final ?
4748  if ($obj->situation_final != 1) {
4749  //Not prov?
4750  if (substr($obj->ref, 1, 4) != 'PROV') {
4751  if ($selected == $obj->rowid) {
4752  $opt .= '<option value="' . $obj->rowid . '" selected>' . $obj->ref . '</option>';
4753  } else {
4754  $opt .= '<option value="' . $obj->rowid . '">' . $obj->ref . '</option>';
4755  }
4756  }
4757  }
4758  }
4759  }
4760  } else {
4761  dol_syslog("Error sql=" . $sql . ", error=" . $this->error, LOG_ERR);
4762  }
4763  if ($opt == '<option value ="" selected></option>') {
4764  $opt = '<option value ="0" selected>' . $langs->trans('NoSituations') . '</option>';
4765  }
4766  return $opt;
4767  }
4768 
4778  public function selectUnits($selected = '', $htmlname = 'units', $showempty = 0, $unit_type = '')
4779  {
4780  global $langs;
4781 
4782  $langs->load('products');
4783 
4784  $return = '<select class="flat" id="' . $htmlname . '" name="' . $htmlname . '">';
4785 
4786  $sql = "SELECT rowid, label, code FROM " . $this->db->prefix() . "c_units";
4787  $sql .= ' WHERE active > 0';
4788  if (!empty($unit_type)) {
4789  $sql .= " AND unit_type = '" . $this->db->escape($unit_type) . "'";
4790  }
4791  $sql .= " ORDER BY sortorder";
4792 
4793  $resql = $this->db->query($sql);
4794  if ($resql && $this->db->num_rows($resql) > 0) {
4795  if ($showempty) {
4796  $return .= '<option value="none"></option>';
4797  }
4798 
4799  while ($res = $this->db->fetch_object($resql)) {
4800  $unitLabel = $res->label;
4801  if (!empty($langs->tab_translate['unit' . $res->code])) { // check if Translation is available before
4802  $unitLabel = $langs->trans('unit' . $res->code) != $res->label ? $langs->trans('unit' . $res->code) : $res->label;
4803  }
4804 
4805  if ($selected == $res->rowid) {
4806  $return .= '<option value="' . $res->rowid . '" selected>' . $unitLabel . '</option>';
4807  } else {
4808  $return .= '<option value="' . $res->rowid . '">' . $unitLabel . '</option>';
4809  }
4810  }
4811  $return .= '</select>';
4812  }
4813  return $return;
4814  }
4815 
4816  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4817 
4832  public function select_comptes($selected = '', $htmlname = 'accountid', $status = 0, $filtre = '', $useempty = 0, $moreattrib = '', $showcurrency = 0, $morecss = '', $nooutput = 0)
4833  {
4834  // phpcs:enable
4835  global $langs, $conf;
4836 
4837  $out = '';
4838 
4839  $langs->load("admin");
4840  $num = 0;
4841 
4842  $sql = "SELECT rowid, label, bank, clos as status, currency_code";
4843  $sql .= " FROM " . $this->db->prefix() . "bank_account";
4844  $sql .= " WHERE entity IN (" . getEntity('bank_account') . ")";
4845  if ($status != 2) {
4846  $sql .= " AND clos = " . (int) $status;
4847  }
4848  if ($filtre) {
4849  $sql .= " AND " . $filtre;
4850  }
4851  $sql .= " ORDER BY label";
4852 
4853  dol_syslog(get_class($this) . "::select_comptes", LOG_DEBUG);
4854  $result = $this->db->query($sql);
4855  if ($result) {
4856  $num = $this->db->num_rows($result);
4857  $i = 0;
4858  if ($num) {
4859  $out .= '<select id="select' . $htmlname . '" class="flat selectbankaccount' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '"' . ($moreattrib ? ' ' . $moreattrib : '') . '>';
4860  if ($useempty == 1 || ($useempty == 2 && $num > 1)) {
4861  $out .= '<option value="-1">&nbsp;</option>';
4862  }
4863 
4864  while ($i < $num) {
4865  $obj = $this->db->fetch_object($result);
4866  if ($selected == $obj->rowid || ($useempty == 2 && $num == 1 && empty($selected))) {
4867  $out .= '<option value="' . $obj->rowid . '" data-currency-code="' . $obj->currency_code . '" selected>';
4868  } else {
4869  $out .= '<option value="' . $obj->rowid . '" data-currency-code="' . $obj->currency_code . '">';
4870  }
4871  $out .= trim($obj->label);
4872  if ($showcurrency) {
4873  $out .= ' (' . $obj->currency_code . ')';
4874  }
4875  if ($status == 2 && $obj->status == 1) {
4876  $out .= ' (' . $langs->trans("Closed") . ')';
4877  }
4878  $out .= '</option>';
4879  $i++;
4880  }
4881  $out .= "</select>";
4882  $out .= ajax_combobox('select' . $htmlname);
4883  } else {
4884  if ($status == 0) {
4885  $out .= '<span class="opacitymedium">' . $langs->trans("NoActiveBankAccountDefined") . '</span>';
4886  } else {
4887  $out .= '<span class="opacitymedium">' . $langs->trans("NoBankAccountFound") . '</span>';
4888  }
4889  }
4890  } else {
4891  dol_print_error($this->db);
4892  }
4893 
4894  // Output or return
4895  if (empty($nooutput)) {
4896  print $out;
4897  } else {
4898  return $out;
4899  }
4900 
4901  return $num;
4902  }
4903 
4915  public function selectEstablishments($selected = '', $htmlname = 'entity', $status = 0, $filtre = '', $useempty = 0, $moreattrib = '')
4916  {
4917  global $langs, $conf;
4918 
4919  $langs->load("admin");
4920  $num = 0;
4921 
4922  $sql = "SELECT rowid, name, fk_country, status, entity";
4923  $sql .= " FROM " . $this->db->prefix() . "establishment";
4924  $sql .= " WHERE 1=1";
4925  if ($status != 2) {
4926  $sql .= " AND status = " . (int) $status;
4927  }
4928  if ($filtre) {
4929  $sql .= " AND " . $filtre;
4930  }
4931  $sql .= " ORDER BY name";
4932 
4933  dol_syslog(get_class($this) . "::select_establishment", LOG_DEBUG);
4934  $result = $this->db->query($sql);
4935  if ($result) {
4936  $num = $this->db->num_rows($result);
4937  $i = 0;
4938  if ($num) {
4939  print '<select id="select' . $htmlname . '" class="flat selectestablishment" name="' . $htmlname . '"' . ($moreattrib ? ' ' . $moreattrib : '') . '>';
4940  if ($useempty == 1 || ($useempty == 2 && $num > 1)) {
4941  print '<option value="-1">&nbsp;</option>';
4942  }
4943 
4944  while ($i < $num) {
4945  $obj = $this->db->fetch_object($result);
4946  if ($selected == $obj->rowid) {
4947  print '<option value="' . $obj->rowid . '" selected>';
4948  } else {
4949  print '<option value="' . $obj->rowid . '">';
4950  }
4951  print trim($obj->name);
4952  if ($status == 2 && $obj->status == 1) {
4953  print ' (' . $langs->trans("Closed") . ')';
4954  }
4955  print '</option>';
4956  $i++;
4957  }
4958  print "</select>";
4959  } else {
4960  if ($status == 0) {
4961  print '<span class="opacitymedium">' . $langs->trans("NoActiveEstablishmentDefined") . '</span>';
4962  } else {
4963  print '<span class="opacitymedium">' . $langs->trans("NoEstablishmentFound") . '</span>';
4964  }
4965  }
4966 
4967  return $num;
4968  } else {
4969  dol_print_error($this->db);
4970  return -1;
4971  }
4972  }
4973 
4983  public function formSelectAccount($page, $selected = '', $htmlname = 'fk_account', $addempty = 0)
4984  {
4985  global $langs;
4986  if ($htmlname != "none") {
4987  print '<form method="POST" action="' . $page . '">';
4988  print '<input type="hidden" name="action" value="setbankaccount">';
4989  print '<input type="hidden" name="token" value="' . newToken() . '">';
4990  print img_picto('', 'bank_account', 'class="pictofixedwidth"');
4991  $nbaccountfound = $this->select_comptes($selected, $htmlname, 0, '', $addempty);
4992  if ($nbaccountfound > 0) {
4993  print '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
4994  }
4995  print '</form>';
4996  } else {
4997  $langs->load('banks');
4998 
4999  if ($selected) {
5000  require_once DOL_DOCUMENT_ROOT . '/compta/bank/class/account.class.php';
5001  $bankstatic = new Account($this->db);
5002  $result = $bankstatic->fetch($selected);
5003  if ($result) {
5004  print $bankstatic->getNomUrl(1);
5005  }
5006  } else {
5007  print "&nbsp;";
5008  }
5009  }
5010  }
5011 
5012  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5013 
5032  public function select_all_categories($type, $selected = '', $htmlname = "parent", $maxlength = 64, $markafterid = 0, $outputmode = 0, $include = 0, $morecss = '')
5033  {
5034  // phpcs:enable
5035  global $conf, $langs;
5036  $langs->load("categories");
5037 
5038  include_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
5039 
5040  // For backward compatibility
5041  if (is_numeric($type)) {
5042  dol_syslog(__METHOD__ . ': using numeric value for parameter type is deprecated. Use string code instead.', LOG_WARNING);
5043  }
5044 
5045  if ($type === Categorie::TYPE_BANK_LINE) {
5046  // TODO Move this into common category feature
5047  $cate_arbo = array();
5048  $sql = "SELECT c.label, c.rowid";
5049  $sql .= " FROM " . $this->db->prefix() . "bank_categ as c";
5050  $sql .= " WHERE entity = " . $conf->entity;
5051  $sql .= " ORDER BY c.label";
5052  $result = $this->db->query($sql);
5053  if ($result) {
5054  $num = $this->db->num_rows($result);
5055  $i = 0;
5056  while ($i < $num) {
5057  $objp = $this->db->fetch_object($result);
5058  if ($objp) {
5059  $cate_arbo[$objp->rowid] = array('id' => $objp->rowid, 'fulllabel' => $objp->label, 'color' => '', 'picto' => 'category');
5060  }
5061  $i++;
5062  }
5063  $this->db->free($result);
5064  } else {
5065  dol_print_error($this->db);
5066  }
5067  } else {
5068  $cat = new Categorie($this->db);
5069  $cate_arbo = $cat->get_full_arbo($type, $markafterid, $include);
5070  }
5071 
5072  $outarray = array();
5073 
5074  $output = '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" id="' . $htmlname . '">';
5075  if (is_array($cate_arbo)) {
5076  if (!count($cate_arbo)) {
5077  $output .= '<option value="-1" disabled>' . $langs->trans("NoCategoriesDefined") . '</option>';
5078  } else {
5079  $output .= '<option value="-1">&nbsp;</option>';
5080  foreach ($cate_arbo as $key => $value) {
5081  if ($cate_arbo[$key]['id'] == $selected || ($selected === 'auto' && count($cate_arbo) == 1)) {
5082  $add = 'selected ';
5083  } else {
5084  $add = '';
5085  }
5086  $output .= '<option ' . $add . 'value="' . $cate_arbo[$key]['id'] . '"';
5087  $output .= ' data-html="' . dol_escape_htmltag(img_picto('', 'category', 'class="pictofixedwidth" style="color: #' . $cate_arbo[$key]['color'] . '"') . dol_trunc($cate_arbo[$key]['fulllabel'], $maxlength, 'middle')) . '"';
5088  $output .= '>';
5089  $output .= dol_trunc($cate_arbo[$key]['fulllabel'], $maxlength, 'middle');
5090  $output .= '</option>';
5091 
5092  $outarray[$cate_arbo[$key]['id']] = $cate_arbo[$key]['fulllabel'];
5093  }
5094  }
5095  }
5096  $output .= '</select>';
5097  $output .= "\n";
5098 
5099  if ($outputmode == 2) {
5100  return $cate_arbo;
5101  } elseif ($outputmode) {
5102  return $outarray;
5103  }
5104  return $output;
5105  }
5106 
5107  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5108 
5125  public function form_confirm($page, $title, $question, $action, $formquestion = '', $selectedchoice = "", $useajax = 0, $height = 170, $width = 500)
5126  {
5127  // phpcs:enable
5128  dol_syslog(__METHOD__ . ': using form_confirm is deprecated. Use formconfim instead.', LOG_WARNING);
5129  print $this->formconfirm($page, $title, $question, $action, $formquestion, $selectedchoice, $useajax, $height, $width);
5130  }
5131 
5159  public function formconfirm($page, $title, $question, $action, $formquestion = '', $selectedchoice = '', $useajax = 0, $height = 0, $width = 500, $disableformtag = 0, $labelbuttonyes = 'Yes', $labelbuttonno = 'No')
5160  {
5161  global $langs, $conf;
5162 
5163  $more = '<!-- formconfirm - before call, page=' . dol_escape_htmltag($page) . ' -->';
5164  $formconfirm = '';
5165  $inputok = array();
5166  $inputko = array();
5167 
5168  // Clean parameters
5169  $newselectedchoice = empty($selectedchoice) ? "no" : $selectedchoice;
5170  if ($conf->browser->layout == 'phone') {
5171  $width = '95%';
5172  }
5173 
5174  // Set height automatically if not defined
5175  if (empty($height)) {
5176  $height = 220;
5177  if (is_array($formquestion) && count($formquestion) > 2) {
5178  $height += ((count($formquestion) - 2) * 24);
5179  }
5180  }
5181 
5182  if (is_array($formquestion) && !empty($formquestion)) {
5183  // First add hidden fields and value
5184  foreach ($formquestion as $key => $input) {
5185  if (is_array($input) && !empty($input)) {
5186  if ($input['type'] == 'hidden') {
5187  $moreattr = (!empty($input['moreattr']) ? ' ' . $input['moreattr'] : '');
5188  $morecss = (!empty($input['morecss']) ? ' ' . $input['morecss'] : '');
5189 
5190  $more .= '<input type="hidden" id="' . dol_escape_htmltag($input['name']) . '" name="' . dol_escape_htmltag($input['name']) . '" value="' . dol_escape_htmltag($input['value']) . '" class="' . $morecss . '"' . $moreattr . '>' . "\n";
5191  }
5192  }
5193  }
5194 
5195  // Now add questions
5196  $moreonecolumn = '';
5197  $more .= '<div class="tagtable paddingtopbottomonly centpercent noborderspacing">' . "\n";
5198  foreach ($formquestion as $key => $input) {
5199  if (is_array($input) && !empty($input)) {
5200  $size = (!empty($input['size']) ? ' size="' . $input['size'] . '"' : ''); // deprecated. Use morecss instead.
5201  $moreattr = (!empty($input['moreattr']) ? ' ' . $input['moreattr'] : '');
5202  $morecss = (!empty($input['morecss']) ? ' ' . $input['morecss'] : '');
5203 
5204  if ($input['type'] == 'text') {
5205  $more .= '<div class="tagtr"><div class="tagtd' . (empty($input['tdclass']) ? '' : (' ' . $input['tdclass'])) . '">' . $input['label'] . '</div><div class="tagtd"><input type="text" class="flat' . $morecss . '" id="' . dol_escape_htmltag($input['name']) . '" name="' . dol_escape_htmltag($input['name']) . '"' . $size . ' value="' . (empty($input['value']) ? '' : $input['value']) . '"' . $moreattr . ' /></div></div>' . "\n";
5206  } elseif ($input['type'] == 'password') {
5207  $more .= '<div class="tagtr"><div class="tagtd' . (empty($input['tdclass']) ? '' : (' ' . $input['tdclass'])) . '">' . $input['label'] . '</div><div class="tagtd"><input type="password" class="flat' . $morecss . '" id="' . dol_escape_htmltag($input['name']) . '" name="' . dol_escape_htmltag($input['name']) . '"' . $size . ' value="' . (empty($input['value']) ? '' : $input['value']) . '"' . $moreattr . ' /></div></div>' . "\n";
5208  } elseif ($input['type'] == 'textarea') {
5209  /*$more .= '<div class="tagtr"><div class="tagtd'.(empty($input['tdclass']) ? '' : (' '.$input['tdclass'])).'">'.$input['label'].'</div><div class="tagtd">';
5210  $more .= '<textarea name="'.$input['name'].'" class="'.$morecss.'"'.$moreattr.'>';
5211  $more .= $input['value'];
5212  $more .= '</textarea>';
5213  $more .= '</div></div>'."\n";*/
5214  $moreonecolumn .= '<div class="margintoponly">';
5215  $moreonecolumn .= $input['label'] . '<br>';
5216  $moreonecolumn .= '<textarea name="' . dol_escape_htmltag($input['name']) . '" id="' . dol_escape_htmltag($input['name']) . '" class="' . $morecss . '"' . $moreattr . '>';
5217  $moreonecolumn .= $input['value'];
5218  $moreonecolumn .= '</textarea>';
5219  $moreonecolumn .= '</div>';
5220  } elseif (in_array($input['type'], ['select', 'multiselect'])) {
5221  if (empty($morecss)) {
5222  $morecss = 'minwidth100';
5223  }
5224 
5225  $show_empty = isset($input['select_show_empty']) ? $input['select_show_empty'] : 1;
5226  $key_in_label = isset($input['select_key_in_label']) ? $input['select_key_in_label'] : 0;
5227  $value_as_key = isset($input['select_value_as_key']) ? $input['select_value_as_key'] : 0;
5228  $translate = isset($input['select_translate']) ? $input['select_translate'] : 0;
5229  $maxlen = isset($input['select_maxlen']) ? $input['select_maxlen'] : 0;
5230  $disabled = isset($input['select_disabled']) ? $input['select_disabled'] : 0;
5231  $sort = isset($input['select_sort']) ? $input['select_sort'] : '';
5232 
5233  $more .= '<div class="tagtr"><div class="tagtd' . (empty($input['tdclass']) ? '' : (' ' . $input['tdclass'])) . '">';
5234  if (!empty($input['label'])) {
5235  $more .= $input['label'] . '</div><div class="tagtd left">';
5236  }
5237  if ($input['type'] == 'select') {
5238  $more .= $this->selectarray($input['name'], $input['values'], isset($input['default']) ? $input['default'] : '-1', $show_empty, $key_in_label, $value_as_key, $moreattr, $translate, $maxlen, $disabled, $sort, $morecss);
5239  } else {
5240  $more .= $this->multiselectarray($input['name'], $input['values'], is_array($input['default']) ? $input['default'] : [$input['default']], $key_in_label, $value_as_key, $morecss, $translate, $maxlen, $moreattr);
5241  }
5242  $more .= '</div></div>' . "\n";
5243  } elseif ($input['type'] == 'checkbox') {
5244  $more .= '<div class="tagtr">';
5245  $more .= '<div class="tagtd' . (empty($input['tdclass']) ? '' : (' ' . $input['tdclass'])) . '"><label for="' . dol_escape_htmltag($input['name']) . '">' . $input['label'] . '</label></div><div class="tagtd">';
5246  $more .= '<input type="checkbox" class="flat' . ($morecss ? ' ' . $morecss : '') . '" id="' . dol_escape_htmltag($input['name']) . '" name="' . dol_escape_htmltag($input['name']) . '"' . $moreattr;
5247  if (!is_bool($input['value']) && $input['value'] != 'false' && $input['value'] != '0' && $input['value'] != '') {
5248  $more .= ' checked';
5249  }
5250  if (is_bool($input['value']) && $input['value']) {
5251  $more .= ' checked';
5252  }
5253  if (isset($input['disabled'])) {
5254  $more .= ' disabled';
5255  }
5256  $more .= ' /></div>';
5257  $more .= '</div>' . "\n";
5258  } elseif ($input['type'] == 'radio') {
5259  $i = 0;
5260  foreach ($input['values'] as $selkey => $selval) {
5261  $more .= '<div class="tagtr">';
5262  if (isset($input['label'])) {
5263  if ($i == 0) {
5264  $more .= '<div class="tagtd' . (empty($input['tdclass']) ? ' tdtop' : (' tdtop ' . $input['tdclass'])) . '">' . $input['label'] . '</div>';
5265  } else {
5266  $more .= '<div clas="tagtd' . (empty($input['tdclass']) ? '' : (' "' . $input['tdclass'])) . '">&nbsp;</div>';
5267  }
5268  }
5269  $more .= '<div class="tagtd' . ($i == 0 ? ' tdtop' : '') . '"><input type="radio" class="flat' . $morecss . '" id="' . dol_escape_htmltag($input['name'] . $selkey) . '" name="' . dol_escape_htmltag($input['name']) . '" value="' . $selkey . '"' . $moreattr;
5270  if (!empty($input['disabled'])) {
5271  $more .= ' disabled';
5272  }
5273  if (isset($input['default']) && $input['default'] === $selkey) {
5274  $more .= ' checked="checked"';
5275  }
5276  $more .= ' /> ';
5277  $more .= '<label for="' . dol_escape_htmltag($input['name'] . $selkey) . '" class="valignmiddle">' . $selval . '</label>';
5278  $more .= '</div></div>' . "\n";
5279  $i++;
5280  }
5281  } elseif ($input['type'] == 'date' || $input['type'] == 'datetime') {
5282  $more .= '<div class="tagtr"><div class="tagtd' . (empty($input['tdclass']) ? '' : (' ' . $input['tdclass'])) . '">' . $input['label'] . '</div>';
5283  $more .= '<div class="tagtd">';
5284  $addnowlink = (empty($input['datenow']) ? 0 : 1);
5285  $h = $m = 0;
5286  if ($input['type'] == 'datetime') {
5287  $h = isset($input['hours']) ? $input['hours'] : 1;
5288  $m = isset($input['minutes']) ? $input['minutes'] : 1;
5289  }
5290  $more .= $this->selectDate($input['value'], $input['name'], $h, $m, 0, '', 1, $addnowlink);
5291  $more .= '</div></div>'."\n";
5292  $formquestion[] = array('name'=>$input['name'].'day');
5293  $formquestion[] = array('name'=>$input['name'].'month');
5294  $formquestion[] = array('name'=>$input['name'].'year');
5295  $formquestion[] = array('name'=>$input['name'].'hour');
5296  $formquestion[] = array('name'=>$input['name'].'min');
5297  } elseif ($input['type'] == 'other') { // can be 1 column or 2 depending if label is set or not
5298  $more .= '<div class="tagtr"><div class="tagtd'.(empty($input['tdclass']) ? '' : (' '.$input['tdclass'])).'">';
5299  if (!empty($input['label'])) {
5300  $more .= $input['label'] . '</div><div class="tagtd">';
5301  }
5302  $more .= $input['value'];
5303  $more .= '</div></div>' . "\n";
5304  } elseif ($input['type'] == 'onecolumn') {
5305  $moreonecolumn .= '<div class="margintoponly">';
5306  $moreonecolumn .= $input['value'];
5307  $moreonecolumn .= '</div>' . "\n";
5308  } elseif ($input['type'] == 'hidden') {
5309  // Do nothing more, already added by a previous loop
5310  } elseif ($input['type'] == 'separator') {
5311  $more .= '<br>';
5312  } else {
5313  $more .= 'Error type ' . $input['type'] . ' for the confirm box is not a supported type';
5314  }
5315  }
5316  }
5317  $more .= '</div>' . "\n";
5318  $more .= $moreonecolumn;
5319  }
5320 
5321  // JQUERY method dialog is broken with smartphone, we use standard HTML.
5322  // Note: When using dol_use_jmobile or no js, you must also check code for button use a GET url with action=xxx and check that you also output the confirm code when action=xxx
5323  // See page product/card.php for example
5324  if (!empty($conf->dol_use_jmobile)) {
5325  $useajax = 0;
5326  }
5327  if (empty($conf->use_javascript_ajax)) {
5328  $useajax = 0;
5329  }
5330 
5331  if ($useajax) {
5332  $autoOpen = true;
5333  $dialogconfirm = 'dialog-confirm';
5334  $button = '';
5335  if (!is_numeric($useajax)) {
5336  $button = $useajax;
5337  $useajax = 1;
5338  $autoOpen = false;
5339  $dialogconfirm .= '-' . $button;
5340  }
5341  $pageyes = $page . (preg_match('/\?/', $page) ? '&' : '?') . 'action=' . urlencode($action) . '&confirm=yes';
5342  $pageno = ($useajax == 2 ? $page . (preg_match('/\?/', $page) ? '&' : '?') . 'action=' . urlencode($action) . '&confirm=no' : '');
5343 
5344  // Add input fields into list of fields to read during submit (inputok and inputko)
5345  if (is_array($formquestion)) {
5346  foreach ($formquestion as $key => $input) {
5347  //print "xx ".$key." rr ".is_array($input)."<br>\n";
5348  // Add name of fields to propagate with the GET when submitting the form with button OK.
5349  if (is_array($input) && isset($input['name'])) {
5350  if (strpos($input['name'], ',') > 0) {
5351  $inputok = array_merge($inputok, explode(',', $input['name']));
5352  } else {
5353  array_push($inputok, $input['name']);
5354  }
5355  }
5356  // Add name of fields to propagate with the GET when submitting the form with button KO.
5357  if (isset($input['inputko']) && $input['inputko'] == 1) {
5358  array_push($inputko, $input['name']);
5359  }
5360  }
5361  }
5362 
5363  // Show JQuery confirm box.
5364  $formconfirm .= '<div id="' . $dialogconfirm . '" title="' . dol_escape_htmltag($title) . '" style="display: none;">';
5365  if (is_array($formquestion) && !empty($formquestion['text'])) {
5366  $formconfirm .= '<div class="confirmtext">' . $formquestion['text'] . '</div>' . "\n";
5367  }
5368  if (!empty($more)) {
5369  $formconfirm .= '<div class="confirmquestions">' . $more . '</div>' . "\n";
5370  }
5371  $formconfirm .= ($question ? '<div class="confirmmessage">' . img_help('', '') . ' ' . $question . '</div>' : '');
5372  $formconfirm .= '</div>' . "\n";
5373 
5374  $formconfirm .= "\n<!-- begin code of popup for formconfirm page=" . $page . " -->\n";
5375  $formconfirm .= '<script nonce="' . getNonce() . '" type="text/javascript">' . "\n";
5376  $formconfirm .= "/* Code for the jQuery('#dialogforpopup').dialog() */\n";
5377  $formconfirm .= 'jQuery(document).ready(function() {
5378  $(function() {
5379  $( "#' . $dialogconfirm . '" ).dialog(
5380  {
5381  autoOpen: ' . ($autoOpen ? "true" : "false") . ',';
5382  if ($newselectedchoice == 'no') {
5383  $formconfirm .= '
5384  open: function() {
5385  $(this).parent().find("button.ui-button:eq(2)").focus();
5386  },';
5387  }
5388 
5389  $jsforcursor = '';
5390  if ($useajax == 1) {
5391  $jsforcursor = '// The call to urljump can be slow, so we set the wait cursor' . "\n";
5392  $jsforcursor .= 'jQuery("html,body,#id-container").addClass("cursorwait");' . "\n";
5393  }
5394 
5395  $postconfirmas = 'GET';
5396 
5397  $formconfirm .= '
5398  resizable: false,
5399  height: "' . $height . '",
5400  width: "' . $width . '",
5401  modal: true,
5402  closeOnEscape: false,
5403  buttons: {
5404  "' . dol_escape_js($langs->transnoentities($labelbuttonyes)) . '": function() {
5405  var options = "token=' . urlencode(newToken()) . '";
5406  var inputok = ' . json_encode($inputok) . '; /* List of fields into form */
5407  var page = "' . dol_escape_js(!empty($page) ? $page : '') . '";
5408  var pageyes = "' . dol_escape_js(!empty($pageyes) ? $pageyes : '') . '";
5409 
5410  if (inputok.length > 0) {
5411  $.each(inputok, function(i, inputname) {
5412  var more = "";
5413  var inputvalue;
5414  if ($("input[name=\'" + inputname + "\']").attr("type") == "radio") {
5415  inputvalue = $("input[name=\'" + inputname + "\']:checked").val();
5416  } else {
5417  if ($("#" + inputname).attr("type") == "checkbox") { more = ":checked"; }
5418  inputvalue = $("#" + inputname + more).val();
5419  }
5420  if (typeof inputvalue == "undefined") { inputvalue=""; }
5421  console.log("formconfirm check inputname="+inputname+" inputvalue="+inputvalue);
5422  options += "&" + inputname + "=" + encodeURIComponent(inputvalue);
5423  });
5424  }
5425  var urljump = pageyes + (pageyes.indexOf("?") < 0 ? "?" : "&") + options;
5426  if (pageyes.length > 0) {';
5427  if ($postconfirmas == 'GET') {
5428  $formconfirm .= 'location.href = urljump;';
5429  } else {
5430  $formconfirm .= $jsforcursor;
5431  $formconfirm .= 'var post = $.post(
5432  pageyes,
5433  options,
5434  function(data) { $("body").html(data); jQuery("html,body,#id-container").removeClass("cursorwait"); }
5435  );';
5436  }
5437  $formconfirm .= '
5438  console.log("after post ok");
5439  }
5440  $(this).dialog("close");
5441  },
5442  "' . dol_escape_js($langs->transnoentities($labelbuttonno)) . '": function() {
5443  var options = "token=' . urlencode(newToken()) . '";
5444  var inputko = ' . json_encode($inputko) . '; /* List of fields into form */
5445  var page = "' . dol_escape_js(!empty($page) ? $page : '') . '";
5446  var pageno="' . dol_escape_js(!empty($pageno) ? $pageno : '') . '";
5447  if (inputko.length > 0) {
5448  $.each(inputko, function(i, inputname) {
5449  var more = "";
5450  if ($("#" + inputname).attr("type") == "checkbox") { more = ":checked"; }
5451  var inputvalue = $("#" + inputname + more).val();
5452  if (typeof inputvalue == "undefined") { inputvalue=""; }
5453  options += "&" + inputname + "=" + encodeURIComponent(inputvalue);
5454  });
5455  }
5456  var urljump=pageno + (pageno.indexOf("?") < 0 ? "?" : "&") + options;
5457  //alert(urljump);
5458  if (pageno.length > 0) {';
5459  if ($postconfirmas == 'GET') {
5460  $formconfirm .= 'location.href = urljump;';
5461  } else {
5462  $formconfirm .= $jsforcursor;
5463  $formconfirm .= 'var post = $.post(
5464  pageno,
5465  options,
5466  function(data) { $("body").html(data); jQuery("html,body,#id-container").removeClass("cursorwait"); }
5467  );';
5468  }
5469  $formconfirm .= '
5470  console.log("after post ko");
5471  }
5472  $(this).dialog("close");
5473  }
5474  }
5475  }
5476  );
5477 
5478  var button = "' . $button . '";
5479  if (button.length > 0) {
5480  $( "#" + button ).click(function() {
5481  $("#' . $dialogconfirm . '").dialog("open");
5482  });
5483  }
5484  });
5485  });
5486  </script>';
5487  $formconfirm .= "<!-- end ajax formconfirm -->\n";
5488  } else {
5489  $formconfirm .= "\n<!-- begin formconfirm page=" . dol_escape_htmltag($page) . " -->\n";
5490 
5491  if (empty($disableformtag)) {
5492  $formconfirm .= '<form method="POST" action="' . $page . '" class="notoptoleftroright">' . "\n";
5493  }
5494 
5495  $formconfirm .= '<input type="hidden" name="action" value="' . $action . '">' . "\n";
5496  $formconfirm .= '<input type="hidden" name="token" value="' . newToken() . '">' . "\n";
5497 
5498  $formconfirm .= '<table class="valid centpercent">' . "\n";
5499 
5500  // Line title
5501  $formconfirm .= '<tr class="validtitre"><td class="validtitre" colspan="2">';
5502  $formconfirm .= img_picto('', 'pictoconfirm') . ' ' . $title;
5503  $formconfirm .= '</td></tr>' . "\n";
5504 
5505  // Line text
5506  if (is_array($formquestion) && !empty($formquestion['text'])) {
5507  $formconfirm .= '<tr class="valid"><td class="valid" colspan="2">' . $formquestion['text'] . '</td></tr>' . "\n";
5508  }
5509 
5510  // Line form fields
5511  if ($more) {
5512  $formconfirm .= '<tr class="valid"><td class="valid" colspan="2">' . "\n";
5513  $formconfirm .= $more;
5514  $formconfirm .= '</td></tr>' . "\n";
5515  }
5516 
5517  // Line with question
5518  $formconfirm .= '<tr class="valid">';
5519  $formconfirm .= '<td class="valid">' . $question . '</td>';
5520  $formconfirm .= '<td class="valid center">';
5521  $formconfirm .= $this->selectyesno("confirm", $newselectedchoice, 0, false, 0, 0, 'marginleftonly marginrightonly', $labelbuttonyes, $labelbuttonno);
5522  $formconfirm .= '<input class="button valignmiddle confirmvalidatebutton small" type="submit" value="' . $langs->trans("Validate") . '">';
5523  $formconfirm .= '</td>';
5524  $formconfirm .= '</tr>' . "\n";
5525 
5526  $formconfirm .= '</table>' . "\n";
5527 
5528  if (empty($disableformtag)) {
5529  $formconfirm .= "</form>\n";
5530  }
5531  $formconfirm .= '<br>';
5532 
5533  if (!empty($conf->use_javascript_ajax)) {
5534  $formconfirm .= '<!-- code to disable button to avoid double clic -->';
5535  $formconfirm .= '<script nonce="' . getNonce() . '" type="text/javascript">' . "\n";
5536  $formconfirm .= '
5537  $(document).ready(function () {
5538  $(".confirmvalidatebutton").on("click", function() {
5539  console.log("We click on button");
5540  $(this).attr("disabled", "disabled");
5541  setTimeout(\'$(".confirmvalidatebutton").removeAttr("disabled")\', 3000);
5542  //console.log($(this).closest("form"));
5543  $(this).closest("form").submit();
5544  });
5545  });
5546  ';
5547  $formconfirm .= '</script>' . "\n";
5548  }
5549 
5550  $formconfirm .= "<!-- end formconfirm -->\n";
5551  }
5552 
5553  return $formconfirm;
5554  }
5555 
5556 
5557  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5558 
5574  public function form_project($page, $socid, $selected = '', $htmlname = 'projectid', $discard_closed = 0, $maxlength = 20, $forcefocus = 0, $nooutput = 0, $textifnoproject = '', $morecss = '')
5575  {
5576  // phpcs:enable
5577  global $langs;
5578 
5579  require_once DOL_DOCUMENT_ROOT . '/core/lib/project.lib.php';
5580  require_once DOL_DOCUMENT_ROOT . '/core/class/html.formprojet.class.php';
5581 
5582  $out = '';
5583 
5584  $formproject = new FormProjets($this->db);
5585 
5586  $langs->load("project");
5587  if ($htmlname != "none") {
5588  $out .= '<form method="post" action="' . $page . '">';
5589  $out .= '<input type="hidden" name="action" value="classin">';
5590  $out .= '<input type="hidden" name="token" value="' . newToken() . '">';
5591  $out .= $formproject->select_projects($socid, $selected, $htmlname, $maxlength, 0, 1, $discard_closed, $forcefocus, 0, 0, '', 1, 0, $morecss);
5592  $out .= '<input type="submit" class="button smallpaddingimp" value="' . $langs->trans("Modify") . '">';
5593  $out .= '</form>';
5594  } else {
5595  $out .= '<span class="project_head_block">';
5596  if ($selected) {
5597  $projet = new Project($this->db);
5598  $projet->fetch($selected);
5599  $out .= $projet->getNomUrl(0, '', 1);
5600  } else {
5601  $out .= '<span class="opacitymedium">' . $textifnoproject . '</span>';
5602  }
5603  $out .= '</span>';
5604  }
5605 
5606  if (empty($nooutput)) {
5607  print $out;
5608  return '';
5609  }
5610  return $out;
5611  }
5612 
5613  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5614 
5630  public function form_conditions_reglement($page, $selected = '', $htmlname = 'cond_reglement_id', $addempty = 0, $type = '', $filtertype = -1, $deposit_percent = -1, $nooutput = 0)
5631  {
5632  // phpcs:enable
5633  global $langs;
5634 
5635  $out = '';
5636 
5637  if ($htmlname != "none") {
5638  $out .= '<form method="POST" action="' . $page . '">';
5639  $out .= '<input type="hidden" name="action" value="setconditions">';
5640  $out .= '<input type="hidden" name="token" value="' . newToken() . '">';
5641  if ($type) {
5642  $out .= '<input type="hidden" name="type" value="' . dol_escape_htmltag($type) . '">';
5643  }
5644  $out .= $this->getSelectConditionsPaiements($selected, $htmlname, $filtertype, $addempty, 0, '', $deposit_percent);
5645  $out .= '<input type="submit" class="button valignmiddle smallpaddingimp" value="' . $langs->trans("Modify") . '">';
5646  $out .= '</form>';
5647  } else {
5648  if ($selected) {
5650  if (isset($this->cache_conditions_paiements[$selected])) {
5651  $label = $this->cache_conditions_paiements[$selected]['label'];
5652 
5653  if (!empty($this->cache_conditions_paiements[$selected]['deposit_percent'])) {
5654  $label = str_replace('__DEPOSIT_PERCENT__', $deposit_percent > 0 ? $deposit_percent : $this->cache_conditions_paiements[$selected]['deposit_percent'], $label);
5655  }
5656 
5657  $out .= $label;
5658  } else {
5659  $langs->load('errors');
5660  $out .= $langs->trans('ErrorNotInDictionaryPaymentConditions');
5661  }
5662  } else {
5663  $out .= '&nbsp;';
5664  }
5665  }
5666 
5667  if (empty($nooutput)) {
5668  print $out;
5669  return '';
5670  }
5671  return $out;
5672  }
5673 
5674  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5675 
5685  public function form_availability($page, $selected = '', $htmlname = 'availability', $addempty = 0)
5686  {
5687  // phpcs:enable
5688  global $langs;
5689  if ($htmlname != "none") {
5690  print '<form method="post" action="' . $page . '">';
5691  print '<input type="hidden" name="action" value="setavailability">';
5692  print '<input type="hidden" name="token" value="' . newToken() . '">';
5693  $this->selectAvailabilityDelay($selected, $htmlname, -1, $addempty);
5694  print '<input type="submit" name="modify" class="button smallpaddingimp" value="' . $langs->trans("Modify") . '">';
5695  print '<input type="submit" name="cancel" class="button smallpaddingimp" value="' . $langs->trans("Cancel") . '">';
5696  print '</form>';
5697  } else {
5698  if ($selected) {
5699  $this->load_cache_availability();
5700  print $this->cache_availability[$selected]['label'];
5701  } else {
5702  print "&nbsp;";
5703  }
5704  }
5705  }
5706 
5717  public function formInputReason($page, $selected = '', $htmlname = 'demandreason', $addempty = 0)
5718  {
5719  global $langs;
5720  if ($htmlname != "none") {
5721  print '<form method="post" action="' . $page . '">';
5722  print '<input type="hidden" name="action" value="setdemandreason">';
5723  print '<input type="hidden" name="token" value="' . newToken() . '">';
5724  $this->selectInputReason($selected, $htmlname, -1, $addempty);
5725  print '<input type="submit" class="button smallpaddingimp" value="' . $langs->trans("Modify") . '">';
5726  print '</form>';
5727  } else {
5728  if ($selected) {
5729  $this->loadCacheInputReason();
5730  foreach ($this->cache_demand_reason as $key => $val) {
5731  if ($val['id'] == $selected) {
5732  print $val['label'];
5733  break;
5734  }
5735  }
5736  } else {
5737  print "&nbsp;";
5738  }
5739  }
5740  }
5741 
5742  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5743 
5757  public function form_date($page, $selected, $htmlname, $displayhour = 0, $displaymin = 0, $nooutput = 0, $type = '')
5758  {
5759  // phpcs:enable
5760  global $langs;
5761 
5762  $ret = '';
5763 
5764  if ($htmlname != "none") {
5765  $ret .= '<form method="POST" action="' . $page . '" name="form' . $htmlname . '">';
5766  $ret .= '<input type="hidden" name="action" value="set' . $htmlname . '">';
5767  $ret .= '<input type="hidden" name="token" value="' . newToken() . '">';
5768  if ($type) {
5769  $ret .= '<input type="hidden" name="type" value="' . dol_escape_htmltag($type) . '">';
5770  }
5771  $ret .= '<table class="nobordernopadding">';
5772  $ret .= '<tr><td>';
5773  $ret .= $this->selectDate($selected, $htmlname, $displayhour, $displaymin, 1, 'form' . $htmlname, 1, 0);
5774  $ret .= '</td>';
5775  $ret .= '<td class="left"><input type="submit" class="button smallpaddingimp" value="' . $langs->trans("Modify") . '"></td>';
5776  $ret .= '</tr></table></form>';
5777  } else {
5778  if ($displayhour) {
5779  $ret .= dol_print_date($selected, 'dayhour');
5780  } else {
5781  $ret .= dol_print_date($selected, 'day');
5782  }
5783  }
5784 
5785  if (empty($nooutput)) {
5786  print $ret;
5787  }
5788  return $ret;
5789  }
5790 
5791 
5792  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5793 
5804  public function form_users($page, $selected = '', $htmlname = 'userid', $exclude = '', $include = '')
5805  {
5806  // phpcs:enable
5807  global $langs;
5808 
5809  if ($htmlname != "none") {
5810  print '<form method="POST" action="' . $page . '" name="form' . $htmlname . '">';
5811  print '<input type="hidden" name="action" value="set' . $htmlname . '">';
5812  print '<input type="hidden" name="token" value="' . newToken() . '">';
5813  print $this->select_dolusers($selected, $htmlname, 1, $exclude, 0, $include);
5814  print '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
5815  print '</form>';
5816  } else {
5817  if ($selected) {
5818  require_once DOL_DOCUMENT_ROOT . '/user/class/user.class.php';
5819  $theuser = new User($this->db);
5820  $theuser->fetch($selected);
5821  print $theuser->getNomUrl(1);
5822  } else {
5823  print "&nbsp;";
5824  }
5825  }
5826  }
5827 
5828 
5829  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5830 
5844  public function form_modes_reglement($page, $selected = '', $htmlname = 'mode_reglement_id', $filtertype = '', $active = 1, $addempty = 0, $type = '', $nooutput = 0)
5845  {
5846  // phpcs:enable
5847  global $langs;
5848 
5849  $out = '';
5850  if ($htmlname != "none") {
5851  $out .= '<form method="POST" action="' . $page . '">';
5852  $out .= '<input type="hidden" name="action" value="setmode">';
5853  $out .= '<input type="hidden" name="token" value="' . newToken() . '">';
5854  if ($type) {
5855  $out .= '<input type="hidden" name="type" value="' . dol_escape_htmltag($type) . '">';
5856  }
5857  $out .= $this->select_types_paiements($selected, $htmlname, $filtertype, 0, $addempty, 0, 0, $active, '', 1);
5858  $out .= '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
5859  $out .= '</form>';
5860  } else {
5861  if ($selected) {
5862  $this->load_cache_types_paiements();
5863  $out .= $this->cache_types_paiements[$selected]['label'];
5864  } else {
5865  $out .= "&nbsp;";
5866  }
5867  }
5868 
5869  if ($nooutput) {
5870  return $out;
5871  } else {
5872  print $out;
5873  }
5874  return '';
5875  }
5876 
5887  public function formSelectTransportMode($page, $selected = '', $htmlname = 'transport_mode_id', $active = 1, $addempty = 0)
5888  {
5889  global $langs;
5890  if ($htmlname != "none") {
5891  print '<form method="POST" action="' . $page . '">';
5892  print '<input type="hidden" name="action" value="settransportmode">';
5893  print '<input type="hidden" name="token" value="' . newToken() . '">';
5894  $this->selectTransportMode($selected, $htmlname, 0, $addempty, 0, 0, $active);
5895  print '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
5896  print '</form>';
5897  } else {
5898  if ($selected) {
5899  $this->load_cache_transport_mode();
5900  print $this->cache_transport_mode[$selected]['label'];
5901  } else {
5902  print "&nbsp;";
5903  }
5904  }
5905  }
5906 
5907  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5908 
5917  public function form_multicurrency_code($page, $selected = '', $htmlname = 'multicurrency_code')
5918  {
5919  // phpcs:enable
5920  global $langs;
5921  if ($htmlname != "none") {
5922  print '<form method="POST" action="' . $page . '">';
5923  print '<input type="hidden" name="action" value="setmulticurrencycode">';
5924  print '<input type="hidden" name="token" value="' . newToken() . '">';
5925  print $this->selectMultiCurrency($selected, $htmlname, 0);
5926  print '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
5927  print '</form>';
5928  } else {
5929  dol_include_once('/core/lib/company.lib.php');
5930  print !empty($selected) ? currency_name($selected, 1) : '&nbsp;';
5931  }
5932  }
5933 
5934  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5935 
5945  public function form_multicurrency_rate($page, $rate = '', $htmlname = 'multicurrency_tx', $currency = '')
5946  {
5947  // phpcs:enable
5948  global $langs, $mysoc, $conf;
5949 
5950  if ($htmlname != "none") {
5951  print '<form method="POST" action="' . $page . '">';
5952  print '<input type="hidden" name="action" value="setmulticurrencyrate">';
5953  print '<input type="hidden" name="token" value="' . newToken() . '">';
5954  print '<input type="text" class="maxwidth100" name="' . $htmlname . '" value="' . (!empty($rate) ? price(price2num($rate, 'CU')) : 1) . '" /> ';
5955  print '<select name="calculation_mode">';
5956  print '<option value="1">Change ' . $langs->trans("PriceUHT") . ' of lines</option>';
5957  print '<option value="2">Change ' . $langs->trans("PriceUHTCurrency") . ' of lines</option>';
5958  print '</select> ';
5959  print '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
5960  print '</form>';
5961  } else {
5962  if (!empty($rate)) {
5963  print price($rate, 1, $langs, 1, 0);
5964  if ($currency && $rate != 1) {
5965  print ' &nbsp; (' . price($rate, 1, $langs, 1, 0) . ' ' . $currency . ' = 1 ' . $conf->currency . ')';
5966  }
5967  } else {
5968  print 1;
5969  }
5970  }
5971  }
5972 
5973 
5974  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5975 
5991  public function form_remise_dispo($page, $selected, $htmlname, $socid, $amount, $filter = '', $maxvalue = 0, $more = '', $hidelist = 0, $discount_type = 0)
5992  {
5993  // phpcs:enable
5994  global $conf, $langs;
5995  if ($htmlname != "none") {
5996  print '<form method="post" action="' . $page . '">';
5997  print '<input type="hidden" name="action" value="setabsolutediscount">';
5998  print '<input type="hidden" name="token" value="' . newToken() . '">';
5999  print '<div class="inline-block">';
6000  if (!empty($discount_type)) {
6001  if (!empty($conf->global->FACTURE_SUPPLIER_DEPOSITS_ARE_JUST_PAYMENTS)) {
6002  if (!$filter || $filter == "fk_invoice_supplier_source IS NULL") {
6003  $translationKey = 'HasAbsoluteDiscountFromSupplier'; // If we want deposit to be substracted to payments only and not to total of final invoice
6004  } else {
6005  $translationKey = 'HasCreditNoteFromSupplier';
6006  }
6007  } else {
6008  if (!$filter || $filter == "fk_invoice_supplier_source IS NULL OR (description LIKE '(DEPOSIT)%' AND description NOT LIKE '(EXCESS PAID)%')") {
6009  $translationKey = 'HasAbsoluteDiscountFromSupplier';
6010  } else {
6011  $translationKey = 'HasCreditNoteFromSupplier';
6012  }
6013  }
6014  } else {
6015  if (!empty($conf->global->FACTURE_DEPOSITS_ARE_JUST_PAYMENTS)) {
6016  if (!$filter || $filter == "fk_facture_source IS NULL") {
6017  $translationKey = 'CompanyHasAbsoluteDiscount'; // If we want deposit to be substracted to payments only and not to total of final invoice
6018  } else {
6019  $translationKey = 'CompanyHasCreditNote';
6020  }
6021  } else {
6022  if (!$filter || $filter == "fk_facture_source IS NULL OR (description LIKE '(DEPOSIT)%' AND description NOT LIKE '(EXCESS RECEIVED)%')") {
6023  $translationKey = 'CompanyHasAbsoluteDiscount';
6024  } else {
6025  $translationKey = 'CompanyHasCreditNote';
6026  }
6027  }
6028  }
6029  print $langs->trans($translationKey, price($amount, 0, $langs, 0, 0, -1, $conf->currency));
6030  if (empty($hidelist)) {
6031  print ' ';
6032  }
6033  print '</div>';
6034  if (empty($hidelist)) {
6035  print '<div class="inline-block" style="padding-right: 10px">';
6036  $newfilter = 'discount_type=' . intval($discount_type);
6037  if (!empty($discount_type)) {
6038  $newfilter .= ' AND fk_invoice_supplier IS NULL AND fk_invoice_supplier_line IS NULL'; // Supplier discounts available
6039  } else {
6040  $newfilter .= ' AND fk_facture IS NULL AND fk_facture_line IS NULL'; // Customer discounts available
6041  }
6042  if ($filter) {
6043  $newfilter .= ' AND (' . $filter . ')';
6044  }
6045  // output the combo of discounts
6046  $nbqualifiedlines = $this->select_remises($selected, $htmlname, $newfilter, $socid, $maxvalue);
6047  if ($nbqualifiedlines > 0) {
6048  print ' &nbsp; <input type="submit" class="button smallpaddingimp" value="' . dol_escape_htmltag($langs->trans("UseLine")) . '"';
6049  if (!empty($discount_type) && $filter && $filter != "fk_invoice_supplier_source IS NULL OR (description LIKE '(DEPOSIT)%' AND description NOT LIKE '(EXCESS PAID)%')") {
6050  print ' title="' . $langs->trans("UseCreditNoteInInvoicePayment") . '"';
6051  }
6052  if (empty($discount_type) && $filter && $filter != "fk_facture_source IS NULL OR (description LIKE '(DEPOSIT)%' AND description NOT LIKE '(EXCESS RECEIVED)%')") {
6053  print ' title="' . $langs->trans("UseCreditNoteInInvoicePayment") . '"';
6054  }
6055 
6056  print '>';
6057  }
6058  print '</div>';
6059  }
6060  if ($more) {
6061  print '<div class="inline-block">';
6062  print $more;
6063  print '</div>';
6064  }
6065  print '</form>';
6066  } else {
6067  if ($selected) {
6068  print $selected;
6069  } else {
6070  print "0";
6071  }
6072  }
6073  }
6074 
6075 
6076  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6077 
6087  public function form_contacts($page, $societe, $selected = '', $htmlname = 'contactid')
6088  {
6089  // phpcs:enable
6090  global $langs, $conf;
6091 
6092  if ($htmlname != "none") {
6093  print '<form method="post" action="' . $page . '">';
6094  print '<input type="hidden" name="action" value="set_contact">';
6095  print '<input type="hidden" name="token" value="' . newToken() . '">';
6096  print '<table class="nobordernopadding">';
6097  print '<tr><td>';
6098  print $this->selectcontacts($societe->id, $selected, $htmlname);
6099  $num = $this->num;
6100  if ($num == 0) {
6101  $addcontact = (!empty($conf->global->SOCIETE_ADDRESSES_MANAGEMENT) ? $langs->trans("AddContact") : $langs->trans("AddContactAddress"));
6102  print '<a href="' . DOL_URL_ROOT . '/contact/card.php?socid=' . $societe->id . '&amp;action=create&amp;backtoreferer=1">' . $addcontact . '</a>';
6103  }
6104  print '</td>';
6105  print '<td class="left"><input type="submit" class="button smallpaddingimp" value="' . $langs->trans("Modify") . '"></td>';
6106  print '</tr></table></form>';
6107  } else {
6108  if ($selected) {
6109  require_once DOL_DOCUMENT_ROOT . '/contact/class/contact.class.php';
6110  $contact = new Contact($this->db);
6111  $contact->fetch($selected);
6112  print $contact->getFullName($langs);
6113  } else {
6114  print "&nbsp;";
6115  }
6116  }
6117  }
6118 
6119  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6120 
6137  public function form_thirdparty($page, $selected = '', $htmlname = 'socid', $filter = '', $showempty = 0, $showtype = 0, $forcecombo = 0, $events = array(), $nooutput = 0, $excludeids = array(), $textifnothirdparty = '')
6138  {
6139  // phpcs:enable
6140  global $langs;
6141 
6142  $out = '';
6143  if ($htmlname != "none") {
6144  $out .= '<form method="post" action="' . $page . '">';
6145  $out .= '<input type="hidden" name="action" value="set_thirdparty">';
6146  $out .= '<input type="hidden" name="token" value="' . newToken() . '">';
6147  $out .= $this->select_company($selected, $htmlname, $filter, $showempty, $showtype, $forcecombo, $events, 0, 'minwidth100', '', '', 1, array(), false, $excludeids);
6148  $out .= '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
6149  $out .= '</form>';
6150  } else {
6151  if ($selected) {
6152  require_once DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php';
6153  $soc = new Societe($this->db);
6154  $soc->fetch($selected);
6155  $out .= $soc->getNomUrl(0, '');
6156  } else {
6157  $out .= '<span class="opacitymedium">' . $textifnothirdparty . '</span>';
6158  }
6159  }
6160 
6161  if ($nooutput) {
6162  return $out;
6163  } else {
6164  print $out;
6165  }
6166 
6167  return '';
6168  }
6169 
6170  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6171 
6180  public function select_currency($selected = '', $htmlname = 'currency_id')
6181  {
6182  // phpcs:enable
6183  print $this->selectCurrency($selected, $htmlname);
6184  }
6185 
6195  public function selectCurrency($selected = '', $htmlname = 'currency_id', $mode = 0, $useempty = '')
6196  {
6197  global $conf, $langs, $user;
6198 
6199  $langs->loadCacheCurrencies('');
6200 
6201  $out = '';
6202 
6203  if ($selected == 'euro' || $selected == 'euros') {
6204  $selected = 'EUR'; // Pour compatibilite
6205  }
6206 
6207  $out .= '<select class="flat maxwidth200onsmartphone minwidth300" name="' . $htmlname . '" id="' . $htmlname . '">';
6208  if ($useempty) {
6209  $out .= '<option value="-1" selected></option>';
6210  }
6211  foreach ($langs->cache_currencies as $code_iso => $currency) {
6212  $labeltoshow = $currency['label'];
6213  if ($mode == 1) {
6214  $labeltoshow .= ' <span class="opacitymedium">(' . $code_iso . ')</span>';
6215  } else {
6216  $labeltoshow .= ' <span class="opacitymedium">(' . $langs->getCurrencySymbol($code_iso) . ')</span>';
6217  }
6218 
6219  if ($selected && $selected == $code_iso) {
6220  $out .= '<option value="' . $code_iso . '" selected data-html="' . dol_escape_htmltag($labeltoshow) . '">';
6221  } else {
6222  $out .= '<option value="' . $code_iso . '" data-html="' . dol_escape_htmltag($labeltoshow) . '">';
6223  }
6224  $out .= $labeltoshow;
6225  $out .= '</option>';
6226  }
6227  $out .= '</select>';
6228  if ($user->admin) {
6229  $out .= info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
6230  }
6231 
6232  // Make select dynamic
6233  include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
6234  $out .= ajax_combobox($htmlname);
6235 
6236  return $out;
6237  }
6238 
6251  public function selectMultiCurrency($selected = '', $htmlname = 'multicurrency_code', $useempty = 0, $filter = '', $excludeConfCurrency = false, $morecss = '')
6252  {
6253  global $conf, $langs;
6254 
6255  $langs->loadCacheCurrencies(''); // Load ->cache_currencies
6256 
6257  $TCurrency = array();
6258 
6259  $sql = "SELECT code FROM " . $this->db->prefix() . "multicurrency";
6260  $sql .= " WHERE entity IN ('" . getEntity('mutlicurrency') . "')";
6261  if ($filter) {
6262  $sql .= " AND " . $filter;
6263  }
6264  $resql = $this->db->query($sql);
6265  if ($resql) {
6266  while ($obj = $this->db->fetch_object($resql)) {
6267  $TCurrency[$obj->code] = $obj->code;
6268  }
6269  }
6270 
6271  $out = '';
6272  $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" id="' . $htmlname . '">';
6273  if ($useempty) {
6274  $out .= '<option value="">&nbsp;</option>';
6275  }
6276  // If company current currency not in table, we add it into list. Should always be available.
6277  if (!in_array($conf->currency, $TCurrency) && !$excludeConfCurrency) {
6278  $TCurrency[$conf->currency] = $conf->currency;
6279  }
6280  if (count($TCurrency) > 0) {
6281  foreach ($langs->cache_currencies as $code_iso => $currency) {
6282  if (isset($TCurrency[$code_iso])) {
6283  if (!empty($selected) && $selected == $code_iso) {
6284  $out .= '<option value="' . $code_iso . '" selected="selected">';
6285  } else {
6286  $out .= '<option value="' . $code_iso . '">';
6287  }
6288 
6289  $out .= $currency['label'];
6290  $out .= ' (' . $langs->getCurrencySymbol($code_iso) . ')';
6291  $out .= '</option>';
6292  }
6293  }
6294  }
6295 
6296  $out .= '</select>';
6297 
6298  // Make select dynamic
6299  include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
6300  $out .= ajax_combobox($htmlname);
6301 
6302  return $out;
6303  }
6304 
6305  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6306 
6313  public function load_cache_vatrates($country_code)
6314  {
6315  // phpcs:enable
6316  global $langs, $user;
6317 
6318  $num = count($this->cache_vatrates);
6319  if ($num > 0) {
6320  return $num; // Cache already loaded
6321  }
6322 
6323  dol_syslog(__METHOD__, LOG_DEBUG);
6324 
6325  $sql = "SELECT DISTINCT t.rowid, t.code, t.taux, t.localtax1, t.localtax1_type, t.localtax2, t.localtax2_type, t.recuperableonly";
6326  $sql .= " FROM " . $this->db->prefix() . "c_tva as t, " . $this->db->prefix() . "c_country as c";
6327  $sql .= " WHERE t.fk_pays = c.rowid";
6328  $sql .= " AND t.active > 0";
6329  $sql .= " AND c.code IN (" . $this->db->sanitize($country_code, 1) . ")";
6330  $sql .= " ORDER BY t.code ASC, t.taux ASC, t.recuperableonly ASC";
6331 
6332  $resql = $this->db->query($sql);
6333  if ($resql) {
6334  $num = $this->db->num_rows($resql);
6335  if ($num) {
6336  for ($i = 0; $i < $num; $i++) {
6337  $obj = $this->db->fetch_object($resql);
6338  $this->cache_vatrates[$i]['rowid'] = $obj->rowid;
6339  $this->cache_vatrates[$i]['code'] = $obj->code;
6340  $this->cache_vatrates[$i]['txtva'] = $obj->taux;
6341  $this->cache_vatrates[$i]['nprtva'] = $obj->recuperableonly;
6342  $this->cache_vatrates[$i]['localtax1'] = $obj->localtax1;
6343  $this->cache_vatrates[$i]['localtax1_type'] = $obj->localtax1_type;
6344  $this->cache_vatrates[$i]['localtax2'] = $obj->localtax2;
6345  $this->cache_vatrates[$i]['localtax2_type'] = $obj->localtax1_type;
6346 
6347  $this->cache_vatrates[$i]['label'] = $obj->taux . '%' . ($obj->code ? ' (' . $obj->code . ')' : ''); // Label must contains only 0-9 , . % or *
6348  $this->cache_vatrates[$i]['labelallrates'] = $obj->taux . '/' . ($obj->localtax1 ? $obj->localtax1 : '0') . '/' . ($obj->localtax2 ? $obj->localtax2 : '0') . ($obj->code ? ' (' . $obj->code . ')' : ''); // Must never be used as key, only label
6349  $positiverates = '';
6350  if ($obj->taux) {
6351  $positiverates .= ($positiverates ? '/' : '') . $obj->taux;
6352  }
6353  if ($obj->localtax1) {
6354  $positiverates .= ($positiverates ? '/' : '') . $obj->localtax1;
6355  }
6356  if ($obj->localtax2) {
6357  $positiverates .= ($positiverates ? '/' : '') . $obj->localtax2;
6358  }
6359  if (empty($positiverates)) {
6360  $positiverates = '0';
6361  }
6362  $this->cache_vatrates[$i]['labelpositiverates'] = $positiverates . ($obj->code ? ' (' . $obj->code . ')' : ''); // Must never be used as key, only label
6363  }
6364 
6365  return $num;
6366  } else {
6367  $this->error = '<span class="error">';
6368  $this->error .= $langs->trans("ErrorNoVATRateDefinedForSellerCountry", $country_code);
6369  $reg = array();
6370  if (!empty($user) && $user->admin && preg_match('/\'(..)\'/', $country_code, $reg)) {
6371  $langs->load("errors");
6372  $new_country_code = $reg[1];
6373  $country_id = dol_getIdFromCode($this->db, $new_country_code, 'c_pays', 'code', 'rowid');
6374  $this->error .= '<br>'.$langs->trans("ErrorFixThisHere", DOL_URL_ROOT.'/admin/dict.php?id=10'.($country_id > 0 ? '&countryidforinsert='.$country_id : ''));
6375  }
6376  $this->error .= '</span>';
6377  return -1;
6378  }
6379  } else {
6380  $this->error = '<span class="error">' . $this->db->error() . '</span>';
6381  return -2;
6382  }
6383  }
6384 
6385  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6386 
6408  public function load_tva($htmlname = 'tauxtva', $selectedrate = '', $societe_vendeuse = '', $societe_acheteuse = '', $idprod = 0, $info_bits = 0, $type = '', $options_only = false, $mode = 0)
6409  {
6410  // phpcs:enable
6411  global $langs, $conf, $mysoc;
6412 
6413  $langs->load('errors');
6414 
6415  $return = '';
6416 
6417  // Define defaultnpr, defaultttx and defaultcode
6418  $defaultnpr = ($info_bits & 0x01);
6419  $defaultnpr = (preg_match('/\*/', $selectedrate) ? 1 : $defaultnpr);
6420  $defaulttx = str_replace('*', '', $selectedrate);
6421  $defaultcode = '';
6422  $reg = array();
6423  if (preg_match('/\((.*)\)/', $defaulttx, $reg)) {
6424  $defaultcode = $reg[1];
6425  $defaulttx = preg_replace('/\s*\(.*\)/', '', $defaulttx);
6426  }
6427  //var_dump($selectedrate.'-'.$defaulttx.'-'.$defaultnpr.'-'.$defaultcode);
6428 
6429  // Check parameters
6430  if (is_object($societe_vendeuse) && !$societe_vendeuse->country_code) {
6431  if ($societe_vendeuse->id == $mysoc->id) {
6432  $return .= '<span class="error">' . $langs->trans("ErrorYourCountryIsNotDefined") . '</span>';
6433  } else {
6434  $return .= '<span class="error">' . $langs->trans("ErrorSupplierCountryIsNotDefined") . '</span>';
6435  }
6436  return $return;
6437  }
6438 
6439  //var_dump($societe_acheteuse);
6440  //print "name=$name, selectedrate=$selectedrate, seller=".$societe_vendeuse->country_code." buyer=".$societe_acheteuse->country_code." buyer is company=".$societe_acheteuse->isACompany()." idprod=$idprod, info_bits=$info_bits type=$type";
6441  //exit;
6442 
6443  // Define list of countries to use to search VAT rates to show
6444  // First we defined code_country to use to find list
6445  if (is_object($societe_vendeuse)) {
6446  $code_country = "'" . $societe_vendeuse->country_code . "'";
6447  } else {
6448  $code_country = "'" . $mysoc->country_code . "'"; // Pour compatibilite ascendente
6449  }
6450  if (!empty($conf->global->SERVICE_ARE_ECOMMERCE_200238EC)) { // If option to have vat for end customer for services is on
6451  require_once DOL_DOCUMENT_ROOT . '/core/lib/company.lib.php';
6452  if (!isInEEC($societe_vendeuse) && (!is_object($societe_acheteuse) || (isInEEC($societe_acheteuse) && !$societe_acheteuse->isACompany()))) {
6453  // We also add the buyer country code
6454  if (is_numeric($type)) {
6455  if ($type == 1) { // We know product is a service
6456  $code_country .= ",'" . $societe_acheteuse->country_code . "'";
6457  }
6458  } elseif (!$idprod) { // We don't know type of product
6459  $code_country .= ",'" . $societe_acheteuse->country_code . "'";
6460  } else {
6461  $prodstatic = new Product($this->db);
6462  $prodstatic->fetch($idprod);
6463  if ($prodstatic->type == Product::TYPE_SERVICE) { // We know product is a service
6464  $code_country .= ",'" . $societe_acheteuse->country_code . "'";
6465  }
6466  }
6467  }
6468  }
6469 
6470  // Now we get list
6471  $num = $this->load_cache_vatrates($code_country); // If no vat defined, return -1 with message into this->error
6472 
6473  if ($num > 0) {
6474  // Definition du taux a pre-selectionner (si defaulttx non force et donc vaut -1 ou '')</