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