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