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-2019  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-2021  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 *
25 * This program is free software; you can redistribute it and/or modify
26 * it under the terms of the GNU General Public License as published by
27 * the Free Software Foundation; either version 3 of the License, or
28 * (at your option) any later version.
29 *
30 * This program is distributed in the hope that it will be useful,
31 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
33 * GNU General Public License for more details.
34 *
35 * You should have received a copy of the GNU General Public License
36 * along with this program. If not, see <https://www.gnu.org/licenses/>.
37 */
38
39/**
40 *	\file       htdocs/core/class/html.form.class.php
41 *  \ingroup    core
42 *	\brief      File of class with all html predefined components
43 */
44
45
46/**
47 *	Class to manage generation of HTML components
48 *	Only common components must be here.
49 *
50 *  TODO Merge all function load_cache_* and loadCache* (except load_cache_vatrates) into one generic function loadCacheTable
51 */
52class Form
53{
54	/**
55	 * @var DoliDB Database handler.
56	 */
57	public $db;
58
59	/**
60	 * @var string Error code (or message)
61	 */
62	public $error = '';
63
64	/**
65	 * @var string[]    Array of error strings
66	 */
67	public $errors = array();
68
69	public $num;
70
71	// Cache arrays
72	public $cache_types_paiements = array();
73	public $cache_conditions_paiements = array();
74	public $cache_transport_mode = array();
75	public $cache_availability = array();
76	public $cache_demand_reason = array();
77	public $cache_types_fees = array();
78	public $cache_vatrates = array();
79
80
81	/**
82	 * Constructor
83	 *
84	 * @param		DoliDB		$db      Database handler
85	 */
86	public function __construct($db)
87	{
88		$this->db = $db;
89	}
90
91	/**
92	 * Output key field for an editable field
93	 *
94	 * @param   string	$text			Text of label or key to translate
95	 * @param   string	$htmlname		Name of select field ('edit' prefix will be added)
96	 * @param   string	$preselected    Value to show/edit (not used in this function)
97	 * @param	object	$object			Object
98	 * @param	boolean	$perm			Permission to allow button to edit parameter. Set it to 0 to have a not edited field.
99	 * @param	string	$typeofdata		Type of data ('string' by default, 'email', 'amount:99', 'numeric:99', 'text' or 'textarea:rows:cols', 'datepicker' ('day' do not work, don't know why), 'checkbox:ckeditor:dolibarr_zzz:width:height:savemethod:1:rows:cols', 'select;xxx[:class]'...)
100	 * @param	string	$moreparam		More param to add on a href URL.
101	 * @param   int     $fieldrequired  1 if we want to show field as mandatory using the "fieldrequired" CSS.
102	 * @param   int     $notabletag     1=Do not output table tags but output a ':', 2=Do not output table tags and no ':', 3=Do not output table tags but output a ' '
103	 * @param	string	$paramid		Key of parameter for id ('id', 'socid')
104	 * @param	string	$help			Tooltip help
105	 * @return	string					HTML edit field
106	 */
107	public function editfieldkey($text, $htmlname, $preselected, $object, $perm, $typeofdata = 'string', $moreparam = '', $fieldrequired = 0, $notabletag = 0, $paramid = 'id', $help = '')
108	{
109		global $conf, $langs;
110
111		$ret = '';
112
113		// TODO change for compatibility
114		if (!empty($conf->global->MAIN_USE_JQUERY_JEDITABLE) && !preg_match('/^select;/', $typeofdata)) {
115			if (!empty($perm)) {
116				$tmp = explode(':', $typeofdata);
117				$ret .= '<div class="editkey_'.$tmp[0].(!empty($tmp[1]) ? ' '.$tmp[1] : '').'" id="'.$htmlname.'">';
118				if ($fieldrequired) {
119					$ret .= '<span class="fieldrequired">';
120				}
121				if ($help) {
122					$ret .= $this->textwithpicto($langs->trans($text), $help);
123				} else {
124					$ret .= $langs->trans($text);
125				}
126				if ($fieldrequired) {
127					$ret .= '</span>';
128				}
129				$ret .= '</div>'."\n";
130			} else {
131				if ($fieldrequired) {
132					$ret .= '<span class="fieldrequired">';
133				}
134				if ($help) {
135					$ret .= $this->textwithpicto($langs->trans($text), $help);
136				} else {
137					$ret .= $langs->trans($text);
138				}
139				if ($fieldrequired) {
140					$ret .= '</span>';
141				}
142			}
143		} else {
144			if (empty($notabletag) && GETPOST('action', 'aZ09') != 'edit'.$htmlname && $perm) {
145				$ret .= '<table class="nobordernopadding centpercent"><tr><td class="nowrap">';
146			}
147			if ($fieldrequired) {
148				$ret .= '<span class="fieldrequired">';
149			}
150			if ($help) {
151				$ret .= $this->textwithpicto($langs->trans($text), $help);
152			} else {
153				$ret .= $langs->trans($text);
154			}
155			if ($fieldrequired) {
156				$ret .= '</span>';
157			}
158			if (!empty($notabletag)) {
159				$ret .= ' ';
160			}
161			if (empty($notabletag) && GETPOST('action', 'aZ09') != 'edit'.$htmlname && $perm) {
162				$ret .= '</td>';
163			}
164			if (empty($notabletag) && GETPOST('action', 'aZ09') != 'edit'.$htmlname && $perm) {
165				$ret .= '<td class="right">';
166			}
167			if ($htmlname && GETPOST('action', 'aZ09') != 'edit'.$htmlname && $perm) {
168				$ret .= '<a class="editfielda" href="'.$_SERVER["PHP_SELF"].'?action=edit'.$htmlname.'&amp;'.$paramid.'='.$object->id.$moreparam.'">'.img_edit($langs->trans('Edit'), ($notabletag ? 0 : 1)).'</a>';
169			}
170			if (!empty($notabletag) && $notabletag == 1) {
171				$ret .= ' : ';
172			}
173			if (!empty($notabletag) && $notabletag == 3) {
174				$ret .= ' ';
175			}
176			if (empty($notabletag) && GETPOST('action', 'aZ09') != 'edit'.$htmlname && $perm) {
177				$ret .= '</td>';
178			}
179			if (empty($notabletag) && GETPOST('action', 'aZ09') != 'edit'.$htmlname && $perm) {
180				$ret .= '</tr></table>';
181			}
182		}
183
184		return $ret;
185	}
186
187	/**
188	 * Output value of a field for an editable field
189	 *
190	 * @param	string	$text			Text of label (not used in this function)
191	 * @param	string	$htmlname		Name of select field
192	 * @param	string	$value			Value to show/edit
193	 * @param	object	$object			Object
194	 * @param	boolean	$perm			Permission to allow button to edit parameter
195	 * @param	string	$typeofdata		Type of data ('string' by default, 'email', 'amount:99', 'numeric:99', 'text' or 'textarea:rows:cols%', 'datepicker' ('day' do not work, don't know why), 'dayhour' or 'datepickerhour', 'ckeditor:dolibarr_zzz:width:height:savemethod:toolbarstartexpanded:rows:cols', 'select;xkey:xval,ykey:yval,...')
196	 * @param	string	$editvalue		When in edit mode, use this value as $value instead of value (for example, you can provide here a formated price instead of value). Use '' to use same than $value
197	 * @param	object	$extObject		External object
198	 * @param	mixed	$custommsg		String or Array of custom messages : eg array('success' => 'MyMessage', 'error' => 'MyMessage')
199	 * @param	string	$moreparam		More param to add on the form action href URL
200	 * @param   int     $notabletag     Do no output table tags
201	 * @param	string	$formatfunc		Call a specific function to output field
202	 * @param	string	$paramid		Key of parameter for id ('id', 'socid')
203	 * @return  string					HTML edit field
204	 */
205	public function editfieldval($text, $htmlname, $value, $object, $perm, $typeofdata = 'string', $editvalue = '', $extObject = null, $custommsg = null, $moreparam = '', $notabletag = 0, $formatfunc = '', $paramid = 'id')
206	{
207		global $conf, $langs, $db;
208
209		$ret = '';
210
211		// Check parameters
212		if (empty($typeofdata)) {
213			return 'ErrorBadParameter';
214		}
215
216		// When option to edit inline is activated
217		if (!empty($conf->global->MAIN_USE_JQUERY_JEDITABLE) && !preg_match('/^select;|datehourpicker/', $typeofdata)) { // TODO add jquery timepicker and support select
218			$ret .= $this->editInPlace($object, $value, $htmlname, $perm, $typeofdata, $editvalue, $extObject, $custommsg);
219		} else {
220			$editmode = (GETPOST('action', 'aZ09') == 'edit'.$htmlname);
221			if ($editmode) {
222				$ret .= "\n";
223				$ret .= '<form method="post" action="'.$_SERVER["PHP_SELF"].($moreparam ? '?'.$moreparam : '').'">';
224				$ret .= '<input type="hidden" name="action" value="set'.$htmlname.'">';
225				$ret .= '<input type="hidden" name="token" value="'.newToken().'">';
226				$ret .= '<input type="hidden" name="'.$paramid.'" value="'.$object->id.'">';
227				if (empty($notabletag)) {
228					$ret .= '<table class="nobordernopadding centpercent">';
229				}
230				if (empty($notabletag)) {
231					$ret .= '<tr><td>';
232				}
233				if (preg_match('/^(string|safehtmlstring|email)/', $typeofdata)) {
234					$tmp = explode(':', $typeofdata);
235					$ret .= '<input type="text" id="'.$htmlname.'" name="'.$htmlname.'" value="'.($editvalue ? $editvalue : $value).'"'.($tmp[1] ? ' size="'.$tmp[1].'"' : '').' autofocus>';
236				} elseif (preg_match('/^(numeric|amount)/', $typeofdata)) {
237					$tmp = explode(':', $typeofdata);
238					$valuetoshow = price2num($editvalue ? $editvalue : $value);
239					$ret .= '<input type="text" id="'.$htmlname.'" name="'.$htmlname.'" value="'.($valuetoshow != '' ?price($valuetoshow) : '').'"'.($tmp[1] ? ' size="'.$tmp[1].'"' : '').' autofocus>';
240				} elseif (preg_match('/^(checkbox)/', $typeofdata)) {
241					$tmp = explode(':', $typeofdata);
242					$ret .= '<input type="checkbox" id="' . $htmlname . '" name="' . $htmlname . '" value="' . $value . '"' . ($tmp[1] ? $tmp[1] : '') . '/>';
243				} elseif (preg_match('/^text/', $typeofdata) || preg_match('/^note/', $typeofdata)) {	// if wysiwyg is enabled $typeofdata = 'ckeditor'
244					$tmp = explode(':', $typeofdata);
245					$cols = $tmp[2];
246					$morealt = '';
247					if (preg_match('/%/', $cols)) {
248						$morealt = ' style="width: '.$cols.'"';
249						$cols = '';
250					}
251
252					$valuetoshow = ($editvalue ? $editvalue : $value);
253					$ret .= '<textarea id="'.$htmlname.'" name="'.$htmlname.'" wrap="soft" rows="'.($tmp[1] ? $tmp[1] : '20').'"'.($cols ? ' cols="'.$cols.'"' : 'class="quatrevingtpercent"').$morealt.'" autofocus>';
254					// textarea convert automatically entities chars into simple chars.
255					// 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.
256					$valuetoshow = str_replace('&', '&amp;', $valuetoshow);
257					$ret .= dol_string_neverthesehtmltags($valuetoshow, array('textarea'));
258					$ret .= '</textarea>';
259				} elseif ($typeofdata == 'day' || $typeofdata == 'datepicker') {
260					$ret .= $this->selectDate($value, $htmlname, 0, 0, 1, 'form'.$htmlname, 1, 0);
261				} elseif ($typeofdata == 'dayhour' || $typeofdata == 'datehourpicker') {
262					$ret .= $this->selectDate($value, $htmlname, 1, 1, 1, 'form'.$htmlname, 1, 0);
263				} elseif (preg_match('/^select;/', $typeofdata)) {
264					$arraydata = explode(',', preg_replace('/^select;/', '', $typeofdata));
265					$arraylist = array();
266					foreach ($arraydata as $val) {
267						$tmp = explode(':', $val);
268						$tmpkey = str_replace('|', ':', $tmp[0]);
269						$arraylist[$tmpkey] = $tmp[1];
270					}
271					$ret .= $this->selectarray($htmlname, $arraylist, $value);
272				} elseif (preg_match('/^ckeditor/', $typeofdata)) {
273					$tmp = explode(':', $typeofdata); // Example: ckeditor:dolibarr_zzz:width:height:savemethod:toolbarstartexpanded:rows:cols:uselocalbrowser
274					require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
275					$doleditor = new DolEditor($htmlname, ($editvalue ? $editvalue : $value), ($tmp[2] ? $tmp[2] : ''), ($tmp[3] ? $tmp[3] : '100'), ($tmp[1] ? $tmp[1] : 'dolibarr_notes'), 'In', ($tmp[5] ? $tmp[5] : 0), (isset($tmp[8]) ? ($tmp[8] ?true:false) : true), true, ($tmp[6] ? $tmp[6] : '20'), ($tmp[7] ? $tmp[7] : '100'));
276					$ret .= $doleditor->Create(1);
277				}
278				if (empty($notabletag)) {
279					$ret .= '</td>';
280				}
281
282				if (empty($notabletag)) {
283					$ret .= '<td class="left">';
284				}
285				//else $ret.='<div class="clearboth"></div>';
286				$ret .= '<input type="submit" class="smallpaddingimp button'.(empty($notabletag) ? '' : ' ').'" name="modify" value="'.$langs->trans("Modify").'">';
287				if (preg_match('/ckeditor|textarea/', $typeofdata) && empty($notabletag)) {
288					$ret .= '<br>'."\n";
289				}
290				$ret .= '<input type="submit" class="smallpaddingimp button button-cancel'.(empty($notabletag) ? '' : ' ').'" name="cancel" value="'.$langs->trans("Cancel").'">';
291				if (empty($notabletag)) {
292					$ret .= '</td>';
293				}
294
295				if (empty($notabletag)) {
296					$ret .= '</tr></table>'."\n";
297				}
298				$ret .= '</form>'."\n";
299			} else {
300				if (preg_match('/^(email)/', $typeofdata)) {
301					$ret .= dol_print_email($value, 0, 0, 0, 0, 1);
302				} elseif (preg_match('/^(amount|numeric)/', $typeofdata)) {
303					$ret .= ($value != '' ? price($value, '', $langs, 0, -1, -1, $conf->currency) : '');
304				} elseif (preg_match('/^(checkbox)/', $typeofdata)) {
305					$tmp = explode(':', $typeofdata);
306					$ret .= '<input type="checkbox" disabled id="' . $htmlname . '" name="' . $htmlname . '" value="' . $value . '"' . ($tmp[1] ? $tmp[1] : '') . '/>';
307				} elseif (preg_match('/^text/', $typeofdata) || preg_match('/^note/', $typeofdata)) {
308					$ret .= dol_htmlentitiesbr($value);
309				} elseif (preg_match('/^safehtmlstring/', $typeofdata)) {
310					$ret .= dol_string_onlythesehtmltags($value);
311				} elseif (preg_match('/^restricthtml/', $typeofdata)) {
312					$ret .= dol_string_onlythesehtmltags($value);
313				} elseif ($typeofdata == 'day' || $typeofdata == 'datepicker') {
314					$ret .= '<span class="valuedate">'.dol_print_date($value, 'day').'</span>';
315				} elseif ($typeofdata == 'dayhour' || $typeofdata == 'datehourpicker') {
316					$ret .= '<span class="valuedate">'.dol_print_date($value, 'dayhour').'</span>';
317				} elseif (preg_match('/^select;/', $typeofdata)) {
318					$arraydata = explode(',', preg_replace('/^select;/', '', $typeofdata));
319					$arraylist = array();
320					foreach ($arraydata as $val) {
321						$tmp = explode(':', $val);
322						$arraylist[$tmp[0]] = $tmp[1];
323					}
324					$ret .= $arraylist[$value];
325					if ($htmlname == 'fk_product_type') {
326						if ($value == 0) {
327							$ret = img_picto($langs->trans("Product"), 'product', 'class="paddingleftonly paddingrightonly colorgrey"').$ret;
328						} else {
329							$ret = img_picto($langs->trans("Service"), 'service', 'class="paddingleftonly paddingrightonly colorgrey"').$ret;
330						}
331					}
332				} elseif (preg_match('/^ckeditor/', $typeofdata)) {
333					$tmpcontent = dol_htmlentitiesbr($value);
334					if (!empty($conf->global->MAIN_DISABLE_NOTES_TAB)) {
335						$firstline = preg_replace('/<br>.*/', '', $tmpcontent);
336						$firstline = preg_replace('/[\n\r].*/', '', $firstline);
337						$tmpcontent = $firstline.((strlen($firstline) != strlen($tmpcontent)) ? '...' : '');
338					}
339					// We dont use dol_escape_htmltag to get the html formating active, but this need we must also
340					// clean data from some dangerous html
341					$ret .= dol_string_onlythesehtmltags(dol_htmlentitiesbr($tmpcontent));
342				} else {
343					$ret .= dol_escape_htmltag($value);
344				}
345
346				if ($formatfunc && method_exists($object, $formatfunc)) {
347					$ret = $object->$formatfunc($ret);
348				}
349			}
350		}
351		return $ret;
352	}
353
354	/**
355	 * Output edit in place form
356	 *
357	 * @param   string	$fieldname		Name of the field
358	 * @param	object	$object			Object
359	 * @param	boolean	$perm			Permission to allow button to edit parameter. Set it to 0 to have a not edited field.
360	 * @param	string	$typeofdata		Type of data ('string' by default, 'email', 'amount:99', 'numeric:99', 'text' or 'textarea:rows:cols', 'datepicker' ('day' do not work, don't know why), 'ckeditor:dolibarr_zzz:width:height:savemethod:1:rows:cols', 'select;xxx[:class]'...)
361	 * @param	string	$check			Same coe than $check parameter of GETPOST()
362	 * @param	string	$morecss		More CSS
363	 * @return	string   		      	HTML code for the edit of alternative language
364	 */
365	public function widgetForTranslation($fieldname, $object, $perm, $typeofdata = 'string', $check = '', $morecss = '')
366	{
367		global $conf, $langs, $extralanguages;
368
369		$result = '';
370
371		// List of extra languages
372		$arrayoflangcode = array();
373		if (!empty($conf->global->PDF_USE_ALSO_LANGUAGE_CODE)) {
374			$arrayoflangcode[] = $conf->global->PDF_USE_ALSO_LANGUAGE_CODE;
375		}
376
377		if (is_array($arrayoflangcode) && count($arrayoflangcode)) {
378			if (!is_object($extralanguages)) {
379				include_once DOL_DOCUMENT_ROOT.'/core/class/extralanguages.class.php';
380				$extralanguages = new ExtraLanguages($this->db);
381			}
382			$extralanguages->fetch_name_extralanguages('societe');
383
384			if (!is_array($extralanguages->attributes[$object->element]) || empty($extralanguages->attributes[$object->element][$fieldname])) {
385				return ''; // No extralang field to show
386			}
387
388			$result .= '<!-- Widget for translation -->'."\n";
389			$result .= '<div class="inline-block paddingleft image-'.$object->element.'-'.$fieldname.'">';
390			$s = img_picto($langs->trans("ShowOtherLanguages"), 'language', '', false, 0, 0, '', 'fa-15 editfieldlang');
391			$result .= $s;
392			$result .= '</div>';
393
394			$result .= '<div class="inline-block hidden field-'.$object->element.'-'.$fieldname.'">';
395
396			$resultforextrlang = '';
397			foreach ($arrayoflangcode as $langcode) {
398				$valuetoshow = GETPOSTISSET('field-'.$object->element."-".$fieldname."-".$langcode) ? GETPOST('field-'.$object->element.'-'.$fieldname."-".$langcode, $check) : '';
399				if (empty($valuetoshow)) {
400					$object->fetchValuesForExtraLanguages();
401					//var_dump($object->array_languages);
402					$valuetoshow = $object->array_languages[$fieldname][$langcode];
403				}
404
405				$s = picto_from_langcode($langcode, 'class="pictoforlang paddingright"');
406				$resultforextrlang .= $s;
407
408				// TODO Use the showInputField() method of ExtraLanguages object
409				if ($typeofdata == 'textarea') {
410					$resultforextrlang .= '<textarea name="field-'.$object->element."-".$fieldname."-".$langcode.'" id="'.$fieldname."-".$langcode.'" class="'.$morecss.'" rows="'.ROWS_2.'" wrap="soft">';
411					$resultforextrlang .= $valuetoshow;
412					$resultforextrlang .= '</textarea>';
413				} else {
414					$resultforextrlang .= '<input type="text" class="inputfieldforlang '.($morecss ? ' '.$morecss : '').'" name="field-'.$object->element.'-'.$fieldname.'-'.$langcode.'" value="'.$valuetoshow.'">';
415				}
416			}
417			$result .= $resultforextrlang;
418
419			$result .= '</div>';
420			$result .= '<script>$(".image-'.$object->element.'-'.$fieldname.'").click(function() { console.log("Toggle lang widget"); jQuery(".field-'.$object->element.'-'.$fieldname.'").toggle(); });</script>';
421		}
422
423		return $result;
424	}
425
426	/**
427	 * Output edit in place form
428	 *
429	 * @param	object	$object			Object
430	 * @param	string	$value			Value to show/edit
431	 * @param	string	$htmlname		DIV ID (field name)
432	 * @param	int		$condition		Condition to edit
433	 * @param	string	$inputType		Type of input ('string', 'numeric', 'datepicker' ('day' do not work, don't know why), 'textarea:rows:cols', 'ckeditor:dolibarr_zzz:width:height:?:1:rows:cols', 'select:loadmethod:savemethod:buttononly')
434	 * @param	string	$editvalue		When in edit mode, use this value as $value instead of value
435	 * @param	object	$extObject		External object
436	 * @param	mixed	$custommsg		String or Array of custom messages : eg array('success' => 'MyMessage', 'error' => 'MyMessage')
437	 * @return	string   		      	HTML edit in place
438	 */
439	protected function editInPlace($object, $value, $htmlname, $condition, $inputType = 'textarea', $editvalue = null, $extObject = null, $custommsg = null)
440	{
441		global $conf;
442
443		$out = '';
444
445		// Check parameters
446		if (preg_match('/^text/', $inputType)) {
447			$value = dol_nl2br($value);
448		} elseif (preg_match('/^numeric/', $inputType)) {
449			$value = price($value);
450		} elseif ($inputType == 'day' || $inputType == 'datepicker') {
451			$value = dol_print_date($value, 'day');
452		}
453
454		if ($condition) {
455			$element = false;
456			$table_element = false;
457			$fk_element		= false;
458			$loadmethod		= false;
459			$savemethod		= false;
460			$ext_element	= false;
461			$button_only	= false;
462			$inputOption = '';
463
464			if (is_object($object)) {
465				$element = $object->element;
466				$table_element = $object->table_element;
467				$fk_element = $object->id;
468			}
469
470			if (is_object($extObject)) {
471				$ext_element = $extObject->element;
472			}
473
474			if (preg_match('/^(string|email|numeric)/', $inputType)) {
475				$tmp = explode(':', $inputType);
476				$inputType = $tmp[0];
477				if (!empty($tmp[1])) {
478					$inputOption = $tmp[1];
479				}
480				if (!empty($tmp[2])) {
481					$savemethod = $tmp[2];
482				}
483				$out .= '<input id="width_'.$htmlname.'" value="'.$inputOption.'" type="hidden"/>'."\n";
484			} elseif ((preg_match('/^day$/', $inputType)) || (preg_match('/^datepicker/', $inputType)) || (preg_match('/^datehourpicker/', $inputType))) {
485				$tmp = explode(':', $inputType);
486				$inputType = $tmp[0];
487				if (!empty($tmp[1])) {
488					$inputOption = $tmp[1];
489				}
490				if (!empty($tmp[2])) {
491					$savemethod = $tmp[2];
492				}
493
494				$out .= '<input id="timestamp" type="hidden"/>'."\n"; // Use for timestamp format
495			} elseif (preg_match('/^(select|autocomplete)/', $inputType)) {
496				$tmp = explode(':', $inputType);
497				$inputType = $tmp[0];
498				$loadmethod = $tmp[1];
499				if (!empty($tmp[2])) {
500					$savemethod = $tmp[2];
501				}
502				if (!empty($tmp[3])) {
503					$button_only = true;
504				}
505			} elseif (preg_match('/^textarea/', $inputType)) {
506				$tmp = explode(':', $inputType);
507				$inputType = $tmp[0];
508				$rows = (empty($tmp[1]) ? '8' : $tmp[1]);
509				$cols = (empty($tmp[2]) ? '80' : $tmp[2]);
510			} elseif (preg_match('/^ckeditor/', $inputType)) {
511				$tmp = explode(':', $inputType);
512				$inputType = $tmp[0];
513				$toolbar = $tmp[1];
514				if (!empty($tmp[2])) {
515					$width = $tmp[2];
516				}
517				if (!empty($tmp[3])) {
518					$heigth = $tmp[3];
519				}
520				if (!empty($tmp[4])) {
521					$savemethod = $tmp[4];
522				}
523
524				if (!empty($conf->fckeditor->enabled)) {
525					$out .= '<input id="ckeditor_toolbar" value="'.$toolbar.'" type="hidden"/>'."\n";
526				} else {
527					$inputType = 'textarea';
528				}
529			}
530
531			$out .= '<input id="element_'.$htmlname.'" value="'.$element.'" type="hidden"/>'."\n";
532			$out .= '<input id="table_element_'.$htmlname.'" value="'.$table_element.'" type="hidden"/>'."\n";
533			$out .= '<input id="fk_element_'.$htmlname.'" value="'.$fk_element.'" type="hidden"/>'."\n";
534			$out .= '<input id="loadmethod_'.$htmlname.'" value="'.$loadmethod.'" type="hidden"/>'."\n";
535			if (!empty($savemethod)) {
536				$out .= '<input id="savemethod_'.$htmlname.'" value="'.$savemethod.'" type="hidden"/>'."\n";
537			}
538			if (!empty($ext_element)) {
539				$out .= '<input id="ext_element_'.$htmlname.'" value="'.$ext_element.'" type="hidden"/>'."\n";
540			}
541			if (!empty($custommsg)) {
542				if (is_array($custommsg)) {
543					if (!empty($custommsg['success'])) {
544						$out .= '<input id="successmsg_'.$htmlname.'" value="'.$custommsg['success'].'" type="hidden"/>'."\n";
545					}
546					if (!empty($custommsg['error'])) {
547						$out .= '<input id="errormsg_'.$htmlname.'" value="'.$custommsg['error'].'" type="hidden"/>'."\n";
548					}
549				} else {
550					$out .= '<input id="successmsg_'.$htmlname.'" value="'.$custommsg.'" type="hidden"/>'."\n";
551				}
552			}
553			if ($inputType == 'textarea') {
554				$out .= '<input id="textarea_'.$htmlname.'_rows" value="'.$rows.'" type="hidden"/>'."\n";
555				$out .= '<input id="textarea_'.$htmlname.'_cols" value="'.$cols.'" type="hidden"/>'."\n";
556			}
557			$out .= '<span id="viewval_'.$htmlname.'" class="viewval_'.$inputType.($button_only ? ' inactive' : ' active').'">'.$value.'</span>'."\n";
558			$out .= '<span id="editval_'.$htmlname.'" class="editval_'.$inputType.($button_only ? ' inactive' : ' active').' hideobject">'.(!empty($editvalue) ? $editvalue : $value).'</span>'."\n";
559		} else {
560			$out = $value;
561		}
562
563		return $out;
564	}
565
566	/**
567	 *	Show a text and picto with tooltip on text or picto.
568	 *  Can be called by an instancied $form->textwithtooltip or by a static call Form::textwithtooltip
569	 *
570	 *	@param	string		$text				Text to show
571	 *	@param	string		$htmltext			HTML content of tooltip. Must be HTML/UTF8 encoded.
572	 *	@param	int			$tooltipon			1=tooltip on text, 2=tooltip on image, 3=tooltip sur les 2
573	 *	@param	int			$direction			-1=image is before, 0=no image, 1=image is after
574	 *	@param	string		$img				Html code for image (use img_xxx() function to get it)
575	 *	@param	string		$extracss			Add a CSS style to td tags
576	 *	@param	int			$notabs				0=Include table and tr tags, 1=Do not include table and tr tags, 2=use div, 3=use span
577	 *	@param	string		$incbefore			Include code before the text
578	 *	@param	int			$noencodehtmltext	Do not encode into html entity the htmltext
579	 *  @param  string      $tooltiptrigger		''=Tooltip on hover, 'abc'=Tooltip on click (abc is a unique key)
580	 *  @param	int			$forcenowrap		Force no wrap between text and picto (works with notabs=2 only)
581	 *	@return	string							Code html du tooltip (texte+picto)
582	 *	@see	textwithpicto() Use thisfunction if you can.
583	 */
584	public function textwithtooltip($text, $htmltext, $tooltipon = 1, $direction = 0, $img = '', $extracss = '', $notabs = 3, $incbefore = '', $noencodehtmltext = 0, $tooltiptrigger = '', $forcenowrap = 0)
585	{
586		if ($incbefore) {
587			$text = $incbefore.$text;
588		}
589		if (!$htmltext) {
590			return $text;
591		}
592		$direction = (int) $direction;	// For backward compatibility when $direction was set to '' instead of 0
593
594		$tag = 'td';
595		if ($notabs == 2) {
596			$tag = 'div';
597		}
598		if ($notabs == 3) {
599			$tag = 'span';
600		}
601		// Sanitize tooltip
602		$htmltext = str_replace(array("\r", "\n"), '', $htmltext);
603
604		$extrastyle = '';
605		if ($direction < 0) {
606			$extracss = ($extracss ? $extracss.' ' : '').($notabs != 3 ? 'inline-block' : '');
607			$extrastyle = 'padding: 0px; padding-left: 3px !important;';
608		}
609		if ($direction > 0) {
610			$extracss = ($extracss ? $extracss.' ' : '').($notabs != 3 ? 'inline-block' : '');
611			$extrastyle = 'padding: 0px; padding-right: 3px !important;';
612		}
613
614		$classfortooltip = 'classfortooltip';
615
616		$s = '';
617		$textfordialog = '';
618
619		if ($tooltiptrigger == '') {
620			$htmltext = str_replace('"', '&quot;', $htmltext);
621		} else {
622			$classfortooltip = 'classfortooltiponclick';
623			$textfordialog .= '<div style="display: none;" id="idfortooltiponclick_'.$tooltiptrigger.'" class="classfortooltiponclicktext">'.$htmltext.'</div>';
624		}
625		if ($tooltipon == 2 || $tooltipon == 3) {
626			$paramfortooltipimg = ' class="'.$classfortooltip.($notabs != 3 ? ' inline-block' : '').($extracss ? ' '.$extracss : '').'" style="padding: 0px;'.($extrastyle ? ' '.$extrastyle : '').'"';
627			if ($tooltiptrigger == '') {
628				$paramfortooltipimg .= ' title="'.($noencodehtmltext ? $htmltext : dol_escape_htmltag($htmltext, 1)).'"'; // Attribut to put on img tag to store tooltip
629			} else {
630				$paramfortooltipimg .= ' dolid="'.$tooltiptrigger.'"';
631			}
632		} else {
633			$paramfortooltipimg = ($extracss ? ' class="'.$extracss.'"' : '').($extrastyle ? ' style="'.$extrastyle.'"' : ''); // Attribut to put on td text tag
634		}
635		if ($tooltipon == 1 || $tooltipon == 3) {
636			$paramfortooltiptd = ' class="'.($tooltipon == 3 ? 'cursorpointer ' : '').$classfortooltip.' inline-block'.($extracss ? ' '.$extracss : '').'" style="padding: 0px;'.($extrastyle ? ' '.$extrastyle : '').'" ';
637			if ($tooltiptrigger == '') {
638				$paramfortooltiptd .= ' title="'.($noencodehtmltext ? $htmltext : dol_escape_htmltag($htmltext, 1)).'"'; // Attribut to put on td tag to store tooltip
639			} else {
640				$paramfortooltiptd .= ' dolid="'.$tooltiptrigger.'"';
641			}
642		} else {
643			$paramfortooltiptd = ($extracss ? ' class="'.$extracss.'"' : '').($extrastyle ? ' style="'.$extrastyle.'"' : ''); // Attribut to put on td text tag
644		}
645		if (empty($notabs)) {
646			$s .= '<table class="nobordernopadding"><tr style="height: auto;">';
647		} elseif ($notabs == 2) {
648			$s .= '<div class="inline-block'.($forcenowrap ? ' nowrap' : '').'">';
649		}
650		// Define value if value is before
651		if ($direction < 0) {
652			$s .= '<'.$tag.$paramfortooltipimg;
653			if ($tag == 'td') {
654				$s .= ' class=valigntop" width="14"';
655			}
656			$s .= '>'.$textfordialog.$img.'</'.$tag.'>';
657		}
658		// Use another method to help avoid having a space in value in order to use this value with jquery
659		// Define label
660		if ((string) $text != '') {
661			$s .= '<'.$tag.$paramfortooltiptd.'>'.$text.'</'.$tag.'>';
662		}
663		// Define value if value is after
664		if ($direction > 0) {
665			$s .= '<'.$tag.$paramfortooltipimg;
666			if ($tag == 'td') {
667				$s .= ' class="valignmiddle" width="14"';
668			}
669			$s .= '>'.$textfordialog.$img.'</'.$tag.'>';
670		}
671		if (empty($notabs)) {
672			$s .= '</tr></table>';
673		} elseif ($notabs == 2) {
674			$s .= '</div>';
675		}
676
677		return $s;
678	}
679
680	/**
681	 *	Show a text with a picto and a tooltip on picto
682	 *
683	 *	@param	string	$text				Text to show
684	 *	@param  string	$htmltext	     	Content of tooltip
685	 *	@param	int		$direction			1=Icon is after text, -1=Icon is before text, 0=no icon
686	 * 	@param	string	$type				Type of picto ('info', 'infoclickable', 'help', 'helpclickable', 'warning', 'superadmin', 'mypicto@mymodule', ...) or image filepath or 'none'
687	 *  @param  string	$extracss           Add a CSS style to td, div or span tag
688	 *  @param  int		$noencodehtmltext   Do not encode into html entity the htmltext
689	 *  @param	int		$notabs				0=Include table and tr tags, 1=Do not include table and tr tags, 2=use div, 3=use span
690	 *  @param  string  $tooltiptrigger     ''=Tooltip on hover, 'abc'=Tooltip on click (abc is a unique key, clickable link is on image or on link if param $type='none' or on both if $type='xxxclickable')
691	 *  @param	int		$forcenowrap		Force no wrap between text and picto (works with notabs=2 only)
692	 * 	@return	string						HTML code of text, picto, tooltip
693	 */
694	public function textwithpicto($text, $htmltext, $direction = 1, $type = 'help', $extracss = '', $noencodehtmltext = 0, $notabs = 3, $tooltiptrigger = '', $forcenowrap = 0)
695	{
696		global $conf, $langs;
697
698		$alt = '';
699		if ($tooltiptrigger) {
700			$alt = $langs->transnoentitiesnoconv("ClickToShowHelp");
701		}
702
703		//For backwards compatibility
704		if ($type == '0') {
705			$type = 'info';
706		} elseif ($type == '1') {
707			$type = 'help';
708		}
709
710		// If info or help with no javascript, show only text
711		if (empty($conf->use_javascript_ajax)) {
712			if ($type == 'info' || $type == 'infoclickable' || $type == 'help' || $type == 'helpclickable') {
713				return $text;
714			} else {
715				$alt = $htmltext;
716				$htmltext = '';
717			}
718		}
719
720		// If info or help with smartphone, show only text (tooltip hover can't works)
721		if (!empty($conf->dol_no_mouse_hover) && empty($tooltiptrigger)) {
722			if ($type == 'info' || $type == 'infoclickable' || $type == 'help' || $type == 'helpclickable') {
723				return $text;
724			}
725		}
726		// If info or help with smartphone, show only text (tooltip on click does not works with dialog on smaprtphone)
727		//if (! empty($conf->dol_no_mouse_hover) && ! empty($tooltiptrigger))
728		//{
729		//if ($type == 'info' || $type == 'help') return '<a href="'..'">'.$text.''</a>';
730		//}
731
732		$img = '';
733		if ($type == 'info') {
734			$img = img_help(0, $alt);
735		} elseif ($type == 'help') {
736			$img = img_help(($tooltiptrigger != '' ? 2 : 1), $alt);
737		} elseif ($type == 'helpclickable') {
738			$img = img_help(($tooltiptrigger != '' ? 2 : 1), $alt);
739		} elseif ($type == 'superadmin') {
740			$img = img_picto($alt, 'redstar');
741		} elseif ($type == 'admin') {
742			$img = img_picto($alt, 'star');
743		} elseif ($type == 'warning') {
744			$img = img_warning($alt);
745		} elseif ($type != 'none') {
746			$img = img_picto($alt, $type); // $type can be an image path
747		}
748
749		return $this->textwithtooltip($text, $htmltext, ((($tooltiptrigger && !$img) || strpos($type, 'clickable')) ? 3 : 2), $direction, $img, $extracss, $notabs, '', $noencodehtmltext, $tooltiptrigger, $forcenowrap);
750	}
751
752	/**
753	 * Generate select HTML to choose massaction
754	 *
755	 * @param	string	$selected		Value auto selected when at least one record is selected. Not a preselected value. Use '0' by default.
756	 * @param	array	$arrayofaction	array('code'=>'label', ...). The code is the key stored into the GETPOST('massaction') when submitting action.
757	 * @param   int     $alwaysvisible  1=select button always visible
758	 * @param   string  $name     		Name for massaction
759	 * @param   string  $cssclass 		CSS class used to check for select
760	 * @return	string|void				Select list
761	 */
762	public function selectMassAction($selected, $arrayofaction, $alwaysvisible = 0, $name = 'massaction', $cssclass = 'checkforselect')
763	{
764		global $conf, $langs, $hookmanager;
765
766
767		$disabled = 0;
768		$ret = '<div class="centpercent center">';
769		$ret .= '<select class="flat'.(empty($conf->use_javascript_ajax) ? '' : ' hideobject').' '.$name.' '.$name.'select valignmiddle alignstart" id="'.$name.'" name="'.$name.'"'.($disabled ? ' disabled="disabled"' : '').'>';
770
771		// 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.
772		$parameters = array();
773		$reshook = $hookmanager->executeHooks('addMoreMassActions', $parameters); // Note that $action and $object may have been modified by hook
774		// check if there is a mass action
775		if (count($arrayofaction) == 0 && empty($hookmanager->resPrint)) {
776			return;
777		}
778		if (empty($reshook)) {
779			$ret .= '<option value="0"'.($disabled ? ' disabled="disabled"' : '').'>-- '.$langs->trans("SelectAction").' --</option>';
780			foreach ($arrayofaction as $code => $label) {
781				$ret .= '<option value="'.$code.'"'.($disabled ? ' disabled="disabled"' : '').' data-html="'.dol_escape_htmltag($label).'">'.$label.'</option>';
782			}
783		}
784		$ret .= $hookmanager->resPrint;
785
786		$ret .= '</select>';
787
788		if (empty($conf->dol_optimize_smallscreen)) {
789			$ret .= ajax_combobox('.'.$name.'select');
790		}
791
792		// 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
793		$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.
794		$ret .= '<input type="submit" disabled name="confirmmassaction"'.(empty($conf->use_javascript_ajax) ? '' : ' style="display: none"').' class="button'.(empty($conf->use_javascript_ajax) ? '' : ' hideobject').' '.$name.' '.$name.'confirmed" value="'.dol_escape_htmltag($langs->trans("Confirm")).'">';
795		$ret .= '</div>';
796
797		if (!empty($conf->use_javascript_ajax)) {
798			$ret .= '<!-- JS CODE TO ENABLE mass action select -->
799    		<script>
800                        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 */
801        		{
802        			atleastoneselected=0;
803                                jQuery("."+cssclass).each(function( index ) {
804    	  				/* console.log( index + ": " + $( this ).text() ); */
805    	  				if ($(this).is(\':checked\')) atleastoneselected++;
806    	  			});
807
808					console.log("initCheckForSelect mode="+mode+" name="+name+" cssclass="+cssclass+" atleastoneselected="+atleastoneselected);
809
810    	  			if (atleastoneselected || '.$alwaysvisible.')
811    	  			{
812                                    jQuery("."+name).show();
813        			    '.($selected ? 'if (atleastoneselected) { jQuery("."+name+"select").val("'.$selected.'").trigger(\'change\'); jQuery("."+name+"confirmed").prop(\'disabled\', false); }' : '').'
814        			    '.($selected ? 'if (! atleastoneselected) { jQuery("."+name+"select").val("0").trigger(\'change\'); jQuery("."+name+"confirmed").prop(\'disabled\', true); } ' : '').'
815    	  			}
816    	  			else
817    	  			{
818                                    jQuery("."+name).hide();
819                                    jQuery("."+name+"other").hide();
820    	            }
821        		}
822
823        	jQuery(document).ready(function () {
824                    initCheckForSelect(0, "' . $name.'", "'.$cssclass.'");
825                    jQuery(".' . $cssclass.'").click(function() {
826                        initCheckForSelect(1, "'.$name.'", "'.$cssclass.'");
827                    });
828                        jQuery(".' . $name.'select").change(function() {
829        			var massaction = $( this ).val();
830        			var urlform = $( this ).closest("form").attr("action").replace("#show_files","");
831        			if (massaction == "builddoc")
832                    {
833                        urlform = urlform + "#show_files";
834    	            }
835        			$( this ).closest("form").attr("action", urlform);
836                    console.log("we select a mass action name='.$name.' massaction="+massaction+" - "+urlform);
837        	        /* Warning: if you set submit button to disabled, post using Enter will no more work if there is no other button */
838        			if ($(this).val() != \'0\')
839    	  			{
840                                        jQuery(".' . $name.'confirmed").prop(\'disabled\', false);
841										jQuery(".' . $name.'other").hide();	/* To disable if another div was open */
842                                        jQuery(".' . $name.'"+massaction).show();
843    	  			}
844    	  			else
845    	  			{
846                                        jQuery(".' . $name.'confirmed").prop(\'disabled\', true);
847										jQuery(".' . $name.'other").hide();	/* To disable any div open */
848    	  			}
849    	        });
850        	});
851    		</script>
852        	';
853		}
854
855		return $ret;
856	}
857
858	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
859	/**
860	 *  Return combo list of activated countries, into language of user
861	 *
862	 *  @param	string	$selected       		Id or Code or Label of preselected country
863	 *  @param  string	$htmlname       		Name of html select object
864	 *  @param  string	$htmloption     		More html options on select object
865	 *  @param	integer	$maxlength				Max length for labels (0=no limit)
866	 *  @param	string	$morecss				More css class
867	 *  @param	string	$usecodeaskey			''=Use id as key (default), 'code3'=Use code on 3 alpha as key, 'code2"=Use code on 2 alpha as key
868	 *  @param	int		$showempty				Show empty choice
869	 *  @param	int		$disablefavorites		1=Disable favorites,
870	 *  @param	int		$addspecialentries		1=Add dedicated entries for group of countries (like 'European Economic Community', ...)
871	 *  @param	array	$exclude_country_code	Array of country code (iso2) to exclude
872	 *  @param	int		$hideflags				Hide flags
873	 *  @return string           				HTML string with select
874	 */
875	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)
876	{
877		// phpcs:enable
878		global $conf, $langs, $mysoc;
879
880		$langs->load("dict");
881
882		$out = '';
883		$countryArray = array();
884		$favorite = array();
885		$label = array();
886		$atleastonefavorite = 0;
887
888		$sql = "SELECT rowid, code as code_iso, code_iso as code_iso3, label, favorite, eec";
889		$sql .= " FROM ".MAIN_DB_PREFIX."c_country";
890		$sql .= " WHERE active > 0";
891		//$sql.= " ORDER BY code ASC";
892
893		dol_syslog(get_class($this)."::select_country", LOG_DEBUG);
894		$resql = $this->db->query($sql);
895		if ($resql) {
896			$out .= '<select id="select'.$htmlname.'" class="flat maxwidth200onsmartphone selectcountry'.($morecss ? ' '.$morecss : '').'" name="'.$htmlname.'" '.$htmloption.'>';
897			$num = $this->db->num_rows($resql);
898			$i = 0;
899			if ($num) {
900				while ($i < $num) {
901					$obj = $this->db->fetch_object($resql);
902
903					$countryArray[$i]['rowid'] = $obj->rowid;
904					$countryArray[$i]['code_iso'] = $obj->code_iso;
905					$countryArray[$i]['code_iso3'] 	= $obj->code_iso3;
906					$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 : ''));
907					$countryArray[$i]['favorite'] = $obj->favorite;
908					$countryArray[$i]['eec'] = $obj->eec;
909					$favorite[$i] = $obj->favorite;
910					$label[$i] = dol_string_unaccent($countryArray[$i]['label']);
911					$i++;
912				}
913
914				if (empty($disablefavorites)) {
915					array_multisort($favorite, SORT_DESC, $label, SORT_ASC, $countryArray);
916				} else {
917					$countryArray = dol_sort_array($countryArray, 'label');
918				}
919
920				if ($showempty) {
921					$out .= '<option value="">&nbsp;</option>'."\n";
922				}
923
924				if ($addspecialentries) {	// Add dedicated entries for groups of countries
925					//if ($showempty) $out.= '<option value="" disabled class="selectoptiondisabledwhite">--------------</option>';
926					$out .= '<option value="special_allnotme"'.($selected == 'special_allnotme' ? ' selected' : '').'>'.$langs->trans("CountriesExceptMe", $langs->transnoentitiesnoconv("Country".$mysoc->country_code)).'</option>';
927					$out .= '<option value="special_eec"'.($selected == 'special_eec' ? ' selected' : '').'>'.$langs->trans("CountriesInEEC").'</option>';
928					if ($mysoc->isInEEC()) {
929						$out .= '<option value="special_eecnotme"'.($selected == 'special_eecnotme' ? ' selected' : '').'>'.$langs->trans("CountriesInEECExceptMe", $langs->transnoentitiesnoconv("Country".$mysoc->country_code)).'</option>';
930					}
931					$out .= '<option value="special_noteec"'.($selected == 'special_noteec' ? ' selected' : '').'>'.$langs->trans("CountriesNotInEEC").'</option>';
932					$out .= '<option value="" disabled class="selectoptiondisabledwhite">------------</option>';
933				}
934
935				foreach ($countryArray as $row) {
936					//if (empty($showempty) && empty($row['rowid'])) continue;
937					if (empty($row['rowid'])) {
938						continue;
939					}
940					if (is_array($exclude_country_code) && count($exclude_country_code) && in_array($row['code_iso'], $exclude_country_code)) {
941						continue; // exclude some countries
942					}
943
944					if (empty($disablefavorites) && $row['favorite'] && $row['code_iso']) {
945						$atleastonefavorite++;
946					}
947					if (empty($row['favorite']) && $atleastonefavorite) {
948						$atleastonefavorite = 0;
949						$out .= '<option value="" disabled class="selectoptiondisabledwhite">------------</option>';
950					}
951
952					$labeltoshow = '';
953					if ($row['label']) {
954						$labeltoshow .= dol_trunc($row['label'], $maxlength, 'middle');
955					} else {
956						$labeltoshow .= '&nbsp;';
957					}
958					if ($row['code_iso']) {
959						$labeltoshow .= ' <span class="opacitymedium">('.$row['code_iso'].')</span>';
960						if (empty($hideflags)) {
961							$tmpflag = picto_from_langcode($row['code_iso'], 'class="saturatemedium paddingrightonly"');
962							$labeltoshow = $tmpflag.' '.$labeltoshow;
963						}
964					}
965
966					if ($selected && $selected != '-1' && ($selected == $row['rowid'] || $selected == $row['code_iso'] || $selected == $row['code_iso3'] || $selected == $row['label'])) {
967						$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']).'">';
968					} else {
969						$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']).'">';
970					}
971					$out .= $labeltoshow;
972					$out .= '</option>'."\n";
973				}
974			}
975			$out .= '</select>';
976		} else {
977			dol_print_error($this->db);
978		}
979
980		// Make select dynamic
981		include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
982		$out .= ajax_combobox('select'.$htmlname, array(), 0, 0, 'resolve');
983
984		return $out;
985	}
986
987	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
988	/**
989	 *  Return select list of incoterms
990	 *
991	 *  @param	string	$selected       		Id or Code of preselected incoterm
992	 *  @param	string	$location_incoterms     Value of input location
993	 *  @param	string	$page       			Defined the form action
994	 *  @param  string	$htmlname       		Name of html select object
995	 *  @param  string	$htmloption     		Options html on select object
996	 * 	@param	int		$forcecombo				Force to load all values and output a standard combobox (with no beautification)
997	 *  @param	array	$events					Event options to run on change. Example: array(array('method'=>'getContacts', 'url'=>dol_buildpath('/core/ajax/contacts.php',1), 'htmlname'=>'contactid', 'params'=>array('add-customer-contact'=>'disabled')))
998	 *  @return string           				HTML string with select and input
999	 */
1000	public function select_incoterms($selected = '', $location_incoterms = '', $page = '', $htmlname = 'incoterm_id', $htmloption = '', $forcecombo = 1, $events = array())
1001	{
1002		// phpcs:enable
1003		global $conf, $langs;
1004
1005		$langs->load("dict");
1006
1007		$out = '';
1008		$incotermArray = array();
1009
1010		$sql = "SELECT rowid, code";
1011		$sql .= " FROM ".MAIN_DB_PREFIX."c_incoterms";
1012		$sql .= " WHERE active > 0";
1013		$sql .= " ORDER BY code ASC";
1014
1015		dol_syslog(get_class($this)."::select_incoterm", LOG_DEBUG);
1016		$resql = $this->db->query($sql);
1017		if ($resql) {
1018			if ($conf->use_javascript_ajax && !$forcecombo) {
1019				include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
1020				$out .= ajax_combobox($htmlname, $events);
1021			}
1022
1023			if (!empty($page)) {
1024				$out .= '<form method="post" action="'.$page.'">';
1025				$out .= '<input type="hidden" name="action" value="set_incoterms">';
1026				$out .= '<input type="hidden" name="token" value="'.newToken().'">';
1027			}
1028
1029			$out .= '<select id="'.$htmlname.'" class="flat selectincoterm width75" name="'.$htmlname.'" '.$htmloption.'>';
1030			$out .= '<option value="0">&nbsp;</option>';
1031			$num = $this->db->num_rows($resql);
1032			$i = 0;
1033			if ($num) {
1034				$foundselected = false;
1035
1036				while ($i < $num) {
1037					$obj = $this->db->fetch_object($resql);
1038					$incotermArray[$i]['rowid'] = $obj->rowid;
1039					$incotermArray[$i]['code'] = $obj->code;
1040					$i++;
1041				}
1042
1043				foreach ($incotermArray as $row) {
1044					if ($selected && ($selected == $row['rowid'] || $selected == $row['code'])) {
1045						$out .= '<option value="'.$row['rowid'].'" selected>';
1046					} else {
1047						$out .= '<option value="'.$row['rowid'].'">';
1048					}
1049
1050					if ($row['code']) {
1051						$out .= $row['code'];
1052					}
1053
1054					$out .= '</option>';
1055				}
1056			}
1057			$out .= '</select>';
1058
1059			$out .= '<input id="location_incoterms" class="maxwidth100onsmartphone nomargintop nomarginbottom" name="location_incoterms" value="'.$location_incoterms.'">';
1060
1061			if (!empty($page)) {
1062				$out .= '<input type="submit" class="button valignmiddle smallpaddingimp nomargintop nomarginbottom" value="'.$langs->trans("Modify").'"></form>';
1063			}
1064		} else {
1065			dol_print_error($this->db);
1066		}
1067
1068		return $out;
1069	}
1070
1071	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1072	/**
1073	 *	Return list of types of lines (product or service)
1074	 * 	Example: 0=product, 1=service, 9=other (for external module)
1075	 *
1076	 *	@param  string	$selected       Preselected type
1077	 *	@param  string	$htmlname       Name of field in html form
1078	 * 	@param	int		$showempty		Add an empty field
1079	 * 	@param	int		$hidetext		Do not show label 'Type' before combo box (used only if there is at least 2 choices to select)
1080	 * 	@param	integer	$forceall		1=Force to show products and services in combo list, whatever are activated modules, 0=No force, 2=Force to show only Products, 3=Force to show only services, -1=Force none (and set hidden field to 'service')
1081	 *  @return	void
1082	 */
1083	public function select_type_of_lines($selected = '', $htmlname = 'type', $showempty = 0, $hidetext = 0, $forceall = 0)
1084	{
1085		// phpcs:enable
1086		global $db, $langs, $user, $conf;
1087
1088		// If product & services are enabled or both disabled.
1089		if ($forceall == 1 || (empty($forceall) && !empty($conf->product->enabled) && !empty($conf->service->enabled))
1090			|| (empty($forceall) && empty($conf->product->enabled) && empty($conf->service->enabled))) {
1091			if (empty($hidetext)) {
1092				print $langs->trans("Type").': ';
1093			}
1094			print '<select class="flat" id="select_'.$htmlname.'" name="'.$htmlname.'">';
1095			if ($showempty) {
1096				print '<option value="-1"';
1097				if ($selected == -1) {
1098					print ' selected';
1099				}
1100				print '>&nbsp;</option>';
1101			}
1102
1103			print '<option value="0"';
1104			if (0 == $selected) {
1105				print ' selected';
1106			}
1107			print '>'.$langs->trans("Product");
1108
1109			print '<option value="1"';
1110			if (1 == $selected) {
1111				print ' selected';
1112			}
1113			print '>'.$langs->trans("Service");
1114
1115			print '</select>';
1116			print ajax_combobox('select_'.$htmlname);
1117			//if ($user->admin) print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"),1);
1118		}
1119		if ((empty($forceall) && empty($conf->product->enabled) && !empty($conf->service->enabled)) || $forceall == 3) {
1120			print $langs->trans("Service");
1121			print '<input type="hidden" name="'.$htmlname.'" value="1">';
1122		}
1123		if ((empty($forceall) && !empty($conf->product->enabled) && empty($conf->service->enabled)) || $forceall == 2) {
1124			print $langs->trans("Product");
1125			print '<input type="hidden" name="'.$htmlname.'" value="0">';
1126		}
1127		if ($forceall < 0) {	// This should happened only for contracts when both predefined product and service are disabled.
1128			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
1129		}
1130	}
1131
1132	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1133	/**
1134	 *	Load into cache cache_types_fees, array of types of fees
1135	 *
1136	 *	@return     int             Nb of lines loaded, <0 if KO
1137	 */
1138	public function load_cache_types_fees()
1139	{
1140		// phpcs:enable
1141		global $langs;
1142
1143		$num = count($this->cache_types_fees);
1144		if ($num > 0) {
1145			return 0; // Cache already loaded
1146		}
1147
1148		dol_syslog(__METHOD__, LOG_DEBUG);
1149
1150		$langs->load("trips");
1151
1152		$sql = "SELECT c.code, c.label";
1153		$sql .= " FROM ".MAIN_DB_PREFIX."c_type_fees as c";
1154		$sql .= " WHERE active > 0";
1155
1156		$resql = $this->db->query($sql);
1157		if ($resql) {
1158			$num = $this->db->num_rows($resql);
1159			$i = 0;
1160
1161			while ($i < $num) {
1162				$obj = $this->db->fetch_object($resql);
1163
1164				// Si traduction existe, on l'utilise, sinon on prend le libelle par defaut
1165				$label = ($obj->code != $langs->trans($obj->code) ? $langs->trans($obj->code) : $langs->trans($obj->label));
1166				$this->cache_types_fees[$obj->code] = $label;
1167				$i++;
1168			}
1169
1170			asort($this->cache_types_fees);
1171
1172			return $num;
1173		} else {
1174			dol_print_error($this->db);
1175			return -1;
1176		}
1177	}
1178
1179	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1180	/**
1181	 *	Return list of types of notes
1182	 *
1183	 *	@param	string		$selected		Preselected type
1184	 *	@param  string		$htmlname		Name of field in form
1185	 * 	@param	int			$showempty		Add an empty field
1186	 * 	@return	void
1187	 */
1188	public function select_type_fees($selected = '', $htmlname = 'type', $showempty = 0)
1189	{
1190		// phpcs:enable
1191		global $user, $langs;
1192
1193		dol_syslog(__METHOD__." selected=".$selected.", htmlname=".$htmlname, LOG_DEBUG);
1194
1195		$this->load_cache_types_fees();
1196
1197		print '<select id="select_'.$htmlname.'" class="flat" name="'.$htmlname.'">';
1198		if ($showempty) {
1199			print '<option value="-1"';
1200			if ($selected == -1) {
1201				print ' selected';
1202			}
1203			print '>&nbsp;</option>';
1204		}
1205
1206		foreach ($this->cache_types_fees as $key => $value) {
1207			print '<option value="'.$key.'"';
1208			if ($key == $selected) {
1209				print ' selected';
1210			}
1211			print '>';
1212			print $value;
1213			print '</option>';
1214		}
1215
1216		print '</select>';
1217		if ($user->admin) {
1218			print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
1219		}
1220	}
1221
1222
1223	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1224	/**
1225	 *  Output html form to select a third party
1226	 *
1227	 *	@param	string	$selected       		Preselected type
1228	 *	@param  string	$htmlname       		Name of field in form
1229	 *  @param  string	$filter         		Optional filters criteras. WARNING: To avoid SQL injection, only few chars [.a-z0-9 =<>] are allowed here (example: 's.rowid <> x', 's.client IN (1,3)')
1230	 *	@param	string	$showempty				Add an empty field (Can be '1' or text key to use on empty line like 'SelectThirdParty')
1231	 * 	@param	int		$showtype				Show third party type in combolist (customer, prospect or supplier)
1232	 * 	@param	int		$forcecombo				Force to load all values and output a standard combobox (with no beautification)
1233	 *  @param	array	$events					Ajax event options to run on change. Example: array(array('method'=>'getContacts', 'url'=>dol_buildpath('/core/ajax/contacts.php',1), 'htmlname'=>'contactid', 'params'=>array('add-customer-contact'=>'disabled')))
1234	 *	@param	int		$limit					Maximum number of elements
1235	 *  @param	string	$morecss				Add more css styles to the SELECT component
1236	 *	@param  string	$moreparam      		Add more parameters onto the select tag. For example 'style="width: 95%"' to avoid select2 component to go over parent container
1237	 *	@param	string	$selected_input_value	Value of preselected input text (for use with ajax)
1238	 *  @param	int		$hidelabel				Hide label (0=no, 1=yes, 2=show search icon (before) and placeholder, 3 search icon after)
1239	 *  @param	array	$ajaxoptions			Options for ajax_autocompleter
1240	 * 	@param  bool	$multiple				add [] in the name of element and add 'multiple' attribut (not working with ajax_autocompleter)
1241	 *  @param	array	$excludeids				Exclude IDs from the select combo
1242	 * 	@return	string							HTML string with select box for thirdparty.
1243	 */
1244	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())
1245	{
1246		// phpcs:enable
1247		global $conf, $user, $langs;
1248
1249		$out = '';
1250
1251		if (!empty($conf->use_javascript_ajax) && !empty($conf->global->COMPANY_USE_SEARCH_TO_SELECT) && !$forcecombo) {
1252			if (is_null($ajaxoptions)) {
1253				$ajaxoptions = array();
1254			}
1255
1256			require_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
1257
1258			// No immediate load of all database
1259			$placeholder = '';
1260			if ($selected && empty($selected_input_value)) {
1261				require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
1262				$societetmp = new Societe($this->db);
1263				$societetmp->fetch($selected);
1264				$selected_input_value = $societetmp->name;
1265				unset($societetmp);
1266			}
1267			// mode 1
1268			$urloption = 'htmlname='.urlencode($htmlname).'&outjson=1&filter='.urlencode($filter).(empty($excludeids) ? '' : '&excludeids='.join(',', $excludeids)).($showtype ? '&showtype='.urlencode($showtype) : '');
1269			$out .= ajax_autocompleter($selected, $htmlname, DOL_URL_ROOT.'/societe/ajax/company.php', $urloption, $conf->global->COMPANY_USE_SEARCH_TO_SELECT, 0, $ajaxoptions);
1270
1271			$out .= '<style type="text/css">.ui-autocomplete { z-index: 1003; }</style>';
1272			if (empty($hidelabel)) {
1273				print $langs->trans("RefOrLabel").' : ';
1274			} elseif ($hidelabel > 1) {
1275				$placeholder = $langs->trans("RefOrLabel");
1276				if ($hidelabel == 2) {
1277					$out .= img_picto($langs->trans("Search"), 'search');
1278				}
1279			}
1280			$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' : '').' />';
1281			if ($hidelabel == 3) {
1282				$out .= img_picto($langs->trans("Search"), 'search');
1283			}
1284		} else {
1285			// Immediate load of all database
1286			$out .= $this->select_thirdparty_list($selected, $htmlname, $filter, $showempty, $showtype, $forcecombo, $events, '', 0, $limit, $morecss, $moreparam, $multiple, $excludeids);
1287		}
1288
1289		return $out;
1290	}
1291
1292	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1293	/**
1294	 *  Output html form to select a third party.
1295	 *  Note, you must use the select_company to get the component to select a third party. This function must only be called by select_company.
1296	 *
1297	 *	@param	string	$selected       Preselected type
1298	 *	@param  string	$htmlname       Name of field in form
1299	 *  @param  string	$filter         Optional filters criteras (example: 's.rowid NOT IN (x)', 's.client IN (1,3)'). Do not use a filter coming from input of users.
1300	 *	@param	string	$showempty		Add an empty field (Can be '1' or text to use on empty line like 'SelectThirdParty')
1301	 * 	@param	int		$showtype		Show third party type in combolist (customer, prospect or supplier)
1302	 * 	@param	int		$forcecombo		Force to use standard HTML select component without beautification
1303	 *  @param	array	$events			Event options. Example: array(array('method'=>'getContacts', 'url'=>dol_buildpath('/core/ajax/contacts.php',1), 'htmlname'=>'contactid', 'params'=>array('add-customer-contact'=>'disabled')))
1304	 *  @param	string	$filterkey		Filter on key value
1305	 *  @param	int		$outputmode		0=HTML select string, 1=Array
1306	 *  @param	int		$limit			Limit number of answers
1307	 *  @param	string	$morecss		Add more css styles to the SELECT component
1308	 *	@param  string	$moreparam      Add more parameters onto the select tag. For example 'style="width: 95%"' to avoid select2 component to go over parent container
1309	 *	@param  bool	$multiple       add [] in the name of element and add 'multiple' attribut
1310	 *  @param	array	$excludeids		Exclude IDs from the select combo
1311	 * 	@return	string					HTML string with
1312	 */
1313	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())
1314	{
1315		// phpcs:enable
1316		global $conf, $user, $langs;
1317
1318		$out = '';
1319		$num = 0;
1320		$outarray = array();
1321
1322		if ($selected === '') {
1323			$selected = array();
1324		} elseif (!is_array($selected)) {
1325			$selected = array($selected);
1326		}
1327
1328		// Clean $filter that may contains sql conditions so sql code
1329		if (function_exists('testSqlAndScriptInject')) {
1330			if (testSqlAndScriptInject($filter, 3) > 0) {
1331				$filter = '';
1332			}
1333		}
1334
1335		// We search companies
1336		$sql = "SELECT s.rowid, s.nom as name, s.name_alias, s.client, s.fournisseur, s.code_client, s.code_fournisseur";
1337		if (!empty($conf->global->COMPANY_SHOW_ADDRESS_SELECTLIST)) {
1338			$sql .= ", s.address, s.zip, s.town";
1339			$sql .= ", dictp.code as country_code";
1340		}
1341		$sql .= " FROM ".MAIN_DB_PREFIX."societe as s";
1342		if (!empty($conf->global->COMPANY_SHOW_ADDRESS_SELECTLIST)) {
1343			$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_country as dictp ON dictp.rowid = s.fk_pays";
1344		}
1345		if (!$user->rights->societe->client->voir && !$user->socid) {
1346			$sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
1347		}
1348		$sql .= " WHERE s.entity IN (".getEntity('societe').")";
1349		if (!empty($user->socid)) {
1350			$sql .= " AND s.rowid = ".((int) $user->socid);
1351		}
1352		if ($filter) {
1353			$sql .= " AND (".$filter.")";
1354		}
1355		if (!$user->rights->societe->client->voir && !$user->socid) {
1356			$sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
1357		}
1358		if (!empty($conf->global->COMPANY_HIDE_INACTIVE_IN_COMBOBOX)) {
1359			$sql .= " AND s.status <> 0";
1360		}
1361		if (!empty($excludeids)) {
1362			$sql .= " AND s.rowid NOT IN (".$this->db->sanitize(join(',', $excludeids)).")";
1363		}
1364		// Add criteria
1365		if ($filterkey && $filterkey != '') {
1366			$sql .= " AND (";
1367			$prefix = empty($conf->global->COMPANY_DONOTSEARCH_ANYWHERE) ? '%' : ''; // Can use index if COMPANY_DONOTSEARCH_ANYWHERE is on
1368			// For natural search
1369			$scrit = explode(' ', $filterkey);
1370			$i = 0;
1371			if (count($scrit) > 1) {
1372				$sql .= "(";
1373			}
1374			foreach ($scrit as $crit) {
1375				if ($i > 0) {
1376					$sql .= " AND ";
1377				}
1378				$sql .= "(s.nom LIKE '".$this->db->escape($prefix.$crit)."%')";
1379				$i++;
1380			}
1381			if (count($scrit) > 1) {
1382				$sql .= ")";
1383			}
1384			if (!empty($conf->barcode->enabled)) {
1385				$sql .= " OR s.barcode LIKE '".$this->db->escape($prefix.$filterkey)."%'";
1386			}
1387			$sql .= " OR s.code_client LIKE '".$this->db->escape($prefix.$filterkey)."%' OR s.code_fournisseur LIKE '".$this->db->escape($prefix.$filterkey)."%'";
1388			$sql .= ")";
1389		}
1390		$sql .= $this->db->order("nom", "ASC");
1391		$sql .= $this->db->plimit($limit, 0);
1392
1393		// Build output string
1394		dol_syslog(get_class($this)."::select_thirdparty_list", LOG_DEBUG);
1395		$resql = $this->db->query($sql);
1396		if ($resql) {
1397			if (!$forcecombo) {
1398				include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
1399				$out .= ajax_combobox($htmlname, $events, $conf->global->COMPANY_USE_SEARCH_TO_SELECT);
1400			}
1401
1402			// Construct $out and $outarray
1403			$out .= '<select id="'.$htmlname.'" class="flat'.($morecss ? ' '.$morecss : '').'"'.($moreparam ? ' '.$moreparam : '').' name="'.$htmlname.($multiple ? '[]' : '').'" '.($multiple ? 'multiple' : '').'>'."\n";
1404
1405			$textifempty = (($showempty && !is_numeric($showempty)) ? $langs->trans($showempty) : '');
1406			if (!empty($conf->global->COMPANY_USE_SEARCH_TO_SELECT)) {
1407				// Do not use textifempty = ' ' or '&nbsp;' here, or search on key will search on ' key'.
1408				//if (! empty($conf->use_javascript_ajax) || $forcecombo) $textifempty='';
1409				if ($showempty && !is_numeric($showempty)) {
1410					$textifempty = $langs->trans($showempty);
1411				} else {
1412					$textifempty .= $langs->trans("All");
1413				}
1414			}
1415			if ($showempty) {
1416				$out .= '<option value="-1" data-html="'.dol_escape_htmltag('<span class="opacitymedium">'.($textifempty ? $textifempty : '&nbsp;').'</span>').'">'.$textifempty.'</option>'."\n";
1417			}
1418
1419			$num = $this->db->num_rows($resql);
1420			$i = 0;
1421			if ($num) {
1422				while ($i < $num) {
1423					$obj = $this->db->fetch_object($resql);
1424					$label = '';
1425					if ($conf->global->SOCIETE_ADD_REF_IN_LIST) {
1426						if (($obj->client) && (!empty($obj->code_client))) {
1427							$label = $obj->code_client.' - ';
1428						}
1429						if (($obj->fournisseur) && (!empty($obj->code_fournisseur))) {
1430							$label .= $obj->code_fournisseur.' - ';
1431						}
1432						$label .= ' '.$obj->name;
1433					} else {
1434						$label = $obj->name;
1435					}
1436
1437					if (!empty($obj->name_alias)) {
1438						$label .= ' ('.$obj->name_alias.')';
1439					}
1440
1441					if ($showtype) {
1442						if ($obj->client || $obj->fournisseur) {
1443							$label .= ' (';
1444						}
1445						if ($obj->client == 1 || $obj->client == 3) {
1446							$label .= $langs->trans("Customer");
1447						}
1448						if ($obj->client == 2 || $obj->client == 3) {
1449							$label .= ($obj->client == 3 ? ', ' : '').$langs->trans("Prospect");
1450						}
1451						if ($obj->fournisseur) {
1452							$label .= ($obj->client ? ', ' : '').$langs->trans("Supplier");
1453						}
1454						if ($obj->client || $obj->fournisseur) {
1455							$label .= ')';
1456						}
1457					}
1458
1459					if (!empty($conf->global->COMPANY_SHOW_ADDRESS_SELECTLIST)) {
1460						$label .= ($obj->address ? ' - '.$obj->address : '').($obj->zip ? ' - '.$obj->zip : '').($obj->town ? ' '.$obj->town : '');
1461						if (!empty($obj->country_code)) {
1462							$label .= ', '.$langs->trans('Country'.$obj->country_code);
1463						}
1464					}
1465
1466					if (empty($outputmode)) {
1467						if (in_array($obj->rowid, $selected)) {
1468							$out .= '<option value="'.$obj->rowid.'" selected>'.$label.'</option>';
1469						} else {
1470							$out .= '<option value="'.$obj->rowid.'">'.$label.'</option>';
1471						}
1472					} else {
1473						array_push($outarray, array('key'=>$obj->rowid, 'value'=>$label, 'label'=>$label));
1474					}
1475
1476					$i++;
1477					if (($i % 10) == 0) {
1478						$out .= "\n";
1479					}
1480				}
1481			}
1482			$out .= '</select>'."\n";
1483		} else {
1484			dol_print_error($this->db);
1485		}
1486
1487		$this->result = array('nbofthirdparties'=>$num);
1488
1489		if ($outputmode) {
1490			return $outarray;
1491		}
1492		return $out;
1493	}
1494
1495
1496	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1497	/**
1498	 *  Return HTML combo list of absolute discounts
1499	 *
1500	 *  @param	string	$selected       Id remise fixe pre-selectionnee
1501	 *  @param  string	$htmlname       Nom champ formulaire
1502	 *  @param  string	$filter         Criteres optionnels de filtre
1503	 *  @param	int		$socid			Id of thirdparty
1504	 *  @param	int		$maxvalue		Max value for lines that can be selected
1505	 *  @return	int						Return number of qualifed lines in list
1506	 */
1507	public function select_remises($selected, $htmlname, $filter, $socid, $maxvalue = 0)
1508	{
1509		// phpcs:enable
1510		global $langs, $conf;
1511
1512		// On recherche les remises
1513		$sql = "SELECT re.rowid, re.amount_ht, re.amount_tva, re.amount_ttc,";
1514		$sql .= " re.description, re.fk_facture_source";
1515		$sql .= " FROM ".MAIN_DB_PREFIX."societe_remise_except as re";
1516		$sql .= " WHERE re.fk_soc = ".(int) $socid;
1517		$sql .= " AND re.entity = ".$conf->entity;
1518		if ($filter) {
1519			$sql .= " AND ".$filter;
1520		}
1521		$sql .= " ORDER BY re.description ASC";
1522
1523		dol_syslog(get_class($this)."::select_remises", LOG_DEBUG);
1524		$resql = $this->db->query($sql);
1525		if ($resql) {
1526			print '<select id="select_'.$htmlname.'" class="flat maxwidthonsmartphone" name="'.$htmlname.'">';
1527			$num = $this->db->num_rows($resql);
1528
1529			$qualifiedlines = $num;
1530
1531			$i = 0;
1532			if ($num) {
1533				print '<option value="0">&nbsp;</option>';
1534				while ($i < $num) {
1535					$obj = $this->db->fetch_object($resql);
1536					$desc = dol_trunc($obj->description, 40);
1537					if (preg_match('/\(CREDIT_NOTE\)/', $desc)) {
1538						$desc = preg_replace('/\(CREDIT_NOTE\)/', $langs->trans("CreditNote"), $desc);
1539					}
1540					if (preg_match('/\(DEPOSIT\)/', $desc)) {
1541						$desc = preg_replace('/\(DEPOSIT\)/', $langs->trans("Deposit"), $desc);
1542					}
1543					if (preg_match('/\(EXCESS RECEIVED\)/', $desc)) {
1544						$desc = preg_replace('/\(EXCESS RECEIVED\)/', $langs->trans("ExcessReceived"), $desc);
1545					}
1546					if (preg_match('/\(EXCESS PAID\)/', $desc)) {
1547						$desc = preg_replace('/\(EXCESS PAID\)/', $langs->trans("ExcessPaid"), $desc);
1548					}
1549
1550					$selectstring = '';
1551					if ($selected > 0 && $selected == $obj->rowid) {
1552						$selectstring = ' selected';
1553					}
1554
1555					$disabled = '';
1556					if ($maxvalue > 0 && $obj->amount_ttc > $maxvalue) {
1557						$qualifiedlines--;
1558						$disabled = ' disabled';
1559					}
1560
1561					if (!empty($conf->global->MAIN_SHOW_FACNUMBER_IN_DISCOUNT_LIST) && !empty($obj->fk_facture_source)) {
1562						$tmpfac = new Facture($this->db);
1563						if ($tmpfac->fetch($obj->fk_facture_source) > 0) {
1564							$desc = $desc.' - '.$tmpfac->ref;
1565						}
1566					}
1567
1568					print '<option value="'.$obj->rowid.'"'.$selectstring.$disabled.'>'.$desc.' ('.price($obj->amount_ht).' '.$langs->trans("HT").' - '.price($obj->amount_ttc).' '.$langs->trans("TTC").')</option>';
1569					$i++;
1570				}
1571			}
1572			print '</select>';
1573			return $qualifiedlines;
1574		} else {
1575			dol_print_error($this->db);
1576			return -1;
1577		}
1578	}
1579
1580	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1581	/**
1582	 *  Return list of all contacts (for a third party or all)
1583	 *
1584	 *  @param	int		$socid      	Id ot third party or 0 for all
1585	 *  @param  string	$selected   	Id contact pre-selectionne
1586	 *  @param  string	$htmlname  	    Name of HTML field ('none' for a not editable field)
1587	 *  @param  int		$showempty      0=no empty value, 1=add an empty value, 2=add line 'Internal' (used by user edit), 3=add an empty value only if more than one record into list
1588	 *  @param  string	$exclude        List of contacts id to exclude
1589	 *  @param	string	$limitto		Disable answers that are not id in this array list
1590	 *  @param	integer	$showfunction   Add function into label
1591	 *  @param	string	$moreclass		Add more class to class style
1592	 *  @param	integer	$showsoc	    Add company into label
1593	 *  @param	int		$forcecombo		Force to use combo box
1594	 *  @param	array	$events			Event options. Example: array(array('method'=>'getContacts', 'url'=>dol_buildpath('/core/ajax/contacts.php',1), 'htmlname'=>'contactid', 'params'=>array('add-customer-contact'=>'disabled')))
1595	 *  @param	bool	$options_only	Return options only (for ajax treatment)
1596	 *  @param	string	$moreparam		Add more parameters onto the select tag. For example 'style="width: 95%"' to avoid select2 component to go over parent container
1597	 *  @param	string	$htmlid			Html id to use instead of htmlname
1598	 *  @return	int						<0 if KO, Nb of contact in list if OK
1599	 *  @deprecated						You can use selectcontacts directly (warning order of param was changed)
1600	 */
1601	public function select_contacts($socid, $selected = '', $htmlname = 'contactid', $showempty = 0, $exclude = '', $limitto = '', $showfunction = 0, $moreclass = '', $showsoc = 0, $forcecombo = 0, $events = array(), $options_only = false, $moreparam = '', $htmlid = '')
1602	{
1603		// phpcs:enable
1604		print $this->selectcontacts($socid, $selected, $htmlname, $showempty, $exclude, $limitto, $showfunction, $moreclass, $options_only, $showsoc, $forcecombo, $events, $moreparam, $htmlid);
1605		return $this->num;
1606	}
1607
1608	/**
1609	 *	Return HTML code of the SELECT of list of all contacts (for a third party or all).
1610	 *  This also set the number of contacts found into $this->num
1611	 *
1612	 * @since 9.0 Add afterSelectContactOptions hook
1613	 *
1614	 *	@param	int			$socid      	Id ot third party or 0 for all or -1 for empty list
1615	 *	@param  array|int	$selected   	Array of ID of pre-selected contact id
1616	 *	@param  string		$htmlname  	    Name of HTML field ('none' for a not editable field)
1617	 *	@param  int			$showempty     	0=no empty value, 1=add an empty value, 2=add line 'Internal' (used by user edit), 3=add an empty value only if more than one record into list
1618	 *	@param  string		$exclude        List of contacts id to exclude
1619	 *	@param	string		$limitto		Disable answers that are not id in this array list
1620	 *	@param	integer		$showfunction   Add function into label
1621	 *	@param	string		$moreclass		Add more class to class style
1622	 *	@param	bool		$options_only	Return options only (for ajax treatment)
1623	 *	@param	integer		$showsoc	    Add company into label
1624	 * 	@param	int			$forcecombo		Force to use combo box (so no ajax beautify effect)
1625	 *  @param	array		$events			Event options. Example: array(array('method'=>'getContacts', 'url'=>dol_buildpath('/core/ajax/contacts.php',1), 'htmlname'=>'contactid', 'params'=>array('add-customer-contact'=>'disabled')))
1626	 *  @param	string		$moreparam		Add more parameters onto the select tag. For example 'style="width: 95%"' to avoid select2 component to go over parent container
1627	 *  @param	string		$htmlid			Html id to use instead of htmlname
1628	 *  @param	bool		$multiple		add [] in the name of element and add 'multiple' attribut
1629	 *  @param	integer		$disableifempty Set tag 'disabled' on select if there is no choice
1630	 *	@return	 int|string					<0 if KO, HTML with select string if OK.
1631	 */
1632	public function selectcontacts($socid, $selected = '', $htmlname = 'contactid', $showempty = 0, $exclude = '', $limitto = '', $showfunction = 0, $moreclass = '', $options_only = false, $showsoc = 0, $forcecombo = 0, $events = array(), $moreparam = '', $htmlid = '', $multiple = false, $disableifempty = 0)
1633	{
1634		global $conf, $langs, $hookmanager, $action;
1635
1636		$langs->load('companies');
1637
1638		if (empty($htmlid)) {
1639			$htmlid = $htmlname;
1640		}
1641		$num = 0;
1642
1643		if ($selected === '') {
1644			$selected = array();
1645		} elseif (!is_array($selected)) {
1646			$selected = array($selected);
1647		}
1648		$out = '';
1649
1650		if (!is_object($hookmanager)) {
1651			include_once DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php';
1652			$hookmanager = new HookManager($this->db);
1653		}
1654
1655		// We search third parties
1656		$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";
1657		if ($showsoc > 0 || !empty($conf->global->CONTACT_SHOW_EMAIL_PHONE_TOWN_SELECTLIST)) {
1658			$sql .= ", s.nom as company, s.town AS company_town";
1659		}
1660		$sql .= " FROM ".MAIN_DB_PREFIX."socpeople as sp";
1661		if ($showsoc > 0 || !empty($conf->global->CONTACT_SHOW_EMAIL_PHONE_TOWN_SELECTLIST)) {
1662			$sql .= " LEFT OUTER JOIN  ".MAIN_DB_PREFIX."societe as s ON s.rowid=sp.fk_soc";
1663		}
1664		$sql .= " WHERE sp.entity IN (".getEntity('socpeople').")";
1665		if ($socid > 0 || $socid == -1) {
1666			$sql .= " AND sp.fk_soc = ".((int) $socid);
1667		}
1668		if (!empty($conf->global->CONTACT_HIDE_INACTIVE_IN_COMBOBOX)) {
1669			$sql .= " AND sp.statut <> 0";
1670		}
1671		$sql .= " ORDER BY sp.lastname ASC";
1672
1673		dol_syslog(get_class($this)."::selectcontacts", LOG_DEBUG);
1674		$resql = $this->db->query($sql);
1675		if ($resql) {
1676			$num = $this->db->num_rows($resql);
1677
1678			if ($htmlname != 'none' && !$options_only) {
1679				$out .= '<select class="flat'.($moreclass ? ' '.$moreclass : '').'" id="'.$htmlid.'" name="'.$htmlname.(($num || empty($disableifempty)) ? '' : ' disabled').($multiple ? '[]' : '').'" '.($multiple ? 'multiple' : '').' '.(!empty($moreparam) ? $moreparam : '').'>';
1680			}
1681
1682			if (($showempty == 1 || ($showempty == 3 && $num > 1)) && !$multiple) {
1683				$out .= '<option value="0"'.(in_array(0, $selected) ? ' selected' : '').'>&nbsp;</option>';
1684			}
1685			if ($showempty == 2) {
1686				$out .= '<option value="0"'.(in_array(0, $selected) ? ' selected' : '').'>-- '.$langs->trans("Internal").' --</option>';
1687			}
1688
1689			$i = 0;
1690			if ($num) {
1691				include_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
1692				$contactstatic = new Contact($this->db);
1693
1694				while ($i < $num) {
1695					$obj = $this->db->fetch_object($resql);
1696
1697					// Set email (or phones) and town extended infos
1698					$extendedInfos = '';
1699					if (!empty($conf->global->CONTACT_SHOW_EMAIL_PHONE_TOWN_SELECTLIST)) {
1700						$extendedInfos = array();
1701						$email = trim($obj->email);
1702						if (!empty($email)) {
1703							$extendedInfos[] = $email;
1704						} else {
1705							$phone = trim($obj->phone);
1706							$phone_perso = trim($obj->phone_perso);
1707							$phone_mobile = trim($obj->phone_mobile);
1708							if (!empty($phone)) {
1709								$extendedInfos[] = $phone;
1710							}
1711							if (!empty($phone_perso)) {
1712								$extendedInfos[] = $phone_perso;
1713							}
1714							if (!empty($phone_mobile)) {
1715								$extendedInfos[] = $phone_mobile;
1716							}
1717						}
1718						$contact_town = trim($obj->contact_town);
1719						$company_town = trim($obj->company_town);
1720						if (!empty($contact_town)) {
1721							$extendedInfos[] = $contact_town;
1722						} elseif (!empty($company_town)) {
1723							$extendedInfos[] = $company_town;
1724						}
1725						$extendedInfos = implode(' - ', $extendedInfos);
1726						if (!empty($extendedInfos)) {
1727							$extendedInfos = ' - '.$extendedInfos;
1728						}
1729					}
1730
1731					$contactstatic->id = $obj->rowid;
1732					$contactstatic->lastname = $obj->lastname;
1733					$contactstatic->firstname = $obj->firstname;
1734					if ($obj->statut == 1) {
1735						if ($htmlname != 'none') {
1736							$disabled = 0;
1737							if (is_array($exclude) && count($exclude) && in_array($obj->rowid, $exclude)) {
1738								$disabled = 1;
1739							}
1740							if (is_array($limitto) && count($limitto) && !in_array($obj->rowid, $limitto)) {
1741								$disabled = 1;
1742							}
1743							if (!empty($selected) && in_array($obj->rowid, $selected)) {
1744								$out .= '<option value="'.$obj->rowid.'"';
1745								if ($disabled) {
1746									$out .= ' disabled';
1747								}
1748								$out .= ' selected>';
1749								$out .= $contactstatic->getFullName($langs).$extendedInfos;
1750								if ($showfunction && $obj->poste) {
1751									$out .= ' ('.$obj->poste.')';
1752								}
1753								if (($showsoc > 0) && $obj->company) {
1754									$out .= ' - ('.$obj->company.')';
1755								}
1756								$out .= '</option>';
1757							} else {
1758								$out .= '<option value="'.$obj->rowid.'"';
1759								if ($disabled) {
1760									$out .= ' disabled';
1761								}
1762								$out .= '>';
1763								$out .= $contactstatic->getFullName($langs).$extendedInfos;
1764								if ($showfunction && $obj->poste) {
1765									$out .= ' ('.$obj->poste.')';
1766								}
1767								if (($showsoc > 0) && $obj->company) {
1768									$out .= ' - ('.$obj->company.')';
1769								}
1770								$out .= '</option>';
1771							}
1772						} else {
1773							if (in_array($obj->rowid, $selected)) {
1774								$out .= $contactstatic->getFullName($langs).$extendedInfos;
1775								if ($showfunction && $obj->poste) {
1776									$out .= ' ('.$obj->poste.')';
1777								}
1778								if (($showsoc > 0) && $obj->company) {
1779									$out .= ' - ('.$obj->company.')';
1780								}
1781							}
1782						}
1783					}
1784					$i++;
1785				}
1786			} else {
1787				$labeltoshow = ($socid != -1) ? ($langs->trans($socid ? "NoContactDefinedForThirdParty" : "NoContactDefined")) : $langs->trans('SelectAThirdPartyFirst');
1788				$out .= '<option class="disabled" value="-1"'.(($showempty == 2 || $multiple) ? '' : ' selected').' disabled="disabled">';
1789				$out .= $labeltoshow;
1790				$out .= '</option>';
1791			}
1792
1793			$parameters = array(
1794				'socid'=>$socid,
1795				'htmlname'=>$htmlname,
1796				'resql'=>$resql,
1797				'out'=>&$out,
1798				'showfunction'=>$showfunction,
1799				'showsoc'=>$showsoc,
1800			);
1801
1802			$reshook = $hookmanager->executeHooks('afterSelectContactOptions', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1803
1804			if ($htmlname != 'none' && !$options_only) {
1805				$out .= '</select>';
1806			}
1807
1808			if ($conf->use_javascript_ajax && !$forcecombo && !$options_only) {
1809				include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
1810				$out .= ajax_combobox($htmlid, $events, $conf->global->CONTACT_USE_SEARCH_TO_SELECT);
1811			}
1812
1813			$this->num = $num;
1814			return $out;
1815		} else {
1816			dol_print_error($this->db);
1817			return -1;
1818		}
1819	}
1820
1821	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1822	/**
1823	 *	Return the HTML select list of users
1824	 *
1825	 *  @param	string			$selected       Id user preselected
1826	 *  @param  string			$htmlname       Field name in form
1827	 *  @param  int				$show_empty     0=liste sans valeur nulle, 1=ajoute valeur inconnue
1828	 *  @param  array			$exclude        Array list of users id to exclude
1829	 * 	@param	int				$disabled		If select list must be disabled
1830	 *  @param  array|string	$include        Array list of users id to include. User '' for all users or 'hierarchy' to have only supervised users or 'hierarchyme' to have supervised + me
1831	 * 	@param	int				$enableonly		Array list of users id to be enabled. All other must be disabled
1832	 *  @param	string			$force_entity	'0' or Ids of environment to force
1833	 * 	@return	void
1834	 *  @deprecated		Use select_dolusers instead
1835	 *  @see select_dolusers()
1836	 */
1837	public function select_users($selected = '', $htmlname = 'userid', $show_empty = 0, $exclude = null, $disabled = 0, $include = '', $enableonly = '', $force_entity = '0')
1838	{
1839		// phpcs:enable
1840		print $this->select_dolusers($selected, $htmlname, $show_empty, $exclude, $disabled, $include, $enableonly, $force_entity);
1841	}
1842
1843	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1844	/**
1845	 *	Return select list of users
1846	 *
1847	 *  @param	string			$selected       User id or user object of user preselected. If 0 or < -2, we use id of current user. If -1, keep unselected (if empty is allowed)
1848	 *  @param  string			$htmlname       Field name in form
1849	 *  @param  int|string		$show_empty     0=list with no empty value, 1=add also an empty value into list
1850	 *  @param  array			$exclude        Array list of users id to exclude
1851	 * 	@param	int				$disabled		If select list must be disabled
1852	 *  @param  array|string	$include        Array list of users id to include. User '' for all users or 'hierarchy' to have only supervised users or 'hierarchyme' to have supervised + me
1853	 * 	@param	array			$enableonly		Array list of users id to be enabled. If defined, it means that others will be disabled
1854	 *  @param	string			$force_entity	'0' or Ids of environment to force
1855	 *  @param	int				$maxlength		Maximum length of string into list (0=no limit)
1856	 *  @param	int				$showstatus		0=show user status only if status is disabled, 1=always show user status into label, -1=never show user status
1857	 *  @param	string			$morefilter		Add more filters into sql request (Example: 'employee = 1'). This value must not come from user input.
1858	 *  @param	integer			$show_every		0=default list, 1=add also a value "Everybody" at beginning of list
1859	 *  @param	string			$enableonlytext	If option $enableonlytext is set, we use this text to explain into label why record is disabled. Not used if enableonly is empty.
1860	 *  @param	string			$morecss		More css
1861	 *  @param  int     		$noactive       Show only active users (this will also happened whatever is this option if USER_HIDE_INACTIVE_IN_COMBOBOX is on).
1862	 *  @param  int				$outputmode     0=HTML select string, 1=Array
1863	 *  @param  bool			$multiple       add [] in the name of element and add 'multiple' attribut
1864	 * 	@return	string							HTML select string
1865	 *  @see select_dolgroups()
1866	 */
1867	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 = '', $noactive = 0, $outputmode = 0, $multiple = false)
1868	{
1869		// phpcs:enable
1870		global $conf, $user, $langs, $hookmanager;
1871
1872		// If no preselected user defined, we take current user
1873		if ((is_numeric($selected) && ($selected < -2 || empty($selected))) && empty($conf->global->SOCIETE_DISABLE_DEFAULT_SALESREPRESENTATIVE)) {
1874			$selected = $user->id;
1875		}
1876
1877		if ($selected === '') {
1878			$selected = array();
1879		} elseif (!is_array($selected)) {
1880			$selected = array($selected);
1881		}
1882
1883		$excludeUsers = null;
1884		$includeUsers = null;
1885
1886		// Permettre l'exclusion d'utilisateurs
1887		if (is_array($exclude)) {
1888			$excludeUsers = implode(",", $exclude);
1889		}
1890		// Permettre l'inclusion d'utilisateurs
1891		if (is_array($include)) {
1892			$includeUsers = implode(",", $include);
1893		} elseif ($include == 'hierarchy') {
1894			// Build list includeUsers to have only hierarchy
1895			$includeUsers = implode(",", $user->getAllChildIds(0));
1896		} elseif ($include == 'hierarchyme') {
1897			// Build list includeUsers to have only hierarchy and current user
1898			$includeUsers = implode(",", $user->getAllChildIds(1));
1899		}
1900
1901		$out = '';
1902		$outarray = array();
1903
1904		// Forge request to select users
1905		$sql = "SELECT DISTINCT u.rowid, u.lastname as lastname, u.firstname, u.statut as status, u.login, u.admin, u.entity, u.photo";
1906		if (!empty($conf->multicompany->enabled) && $conf->entity == 1 && $user->admin && !$user->entity) {
1907			$sql .= ", e.label";
1908		}
1909		$sql .= " FROM ".MAIN_DB_PREFIX."user as u";
1910		if (!empty($conf->multicompany->enabled) && $conf->entity == 1 && $user->admin && !$user->entity) {
1911			$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."entity as e ON e.rowid = u.entity";
1912			if ($force_entity) {
1913				$sql .= " WHERE u.entity IN (0, ".$this->db->sanitize($force_entity).")";
1914			} else {
1915				$sql .= " WHERE u.entity IS NOT NULL";
1916			}
1917		} else {
1918			if (!empty($conf->global->MULTICOMPANY_TRANSVERSE_MODE)) {
1919				$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."usergroup_user as ug";
1920				$sql .= " ON ug.fk_user = u.rowid";
1921				$sql .= " WHERE ug.entity = ".$conf->entity;
1922			} else {
1923				$sql .= " WHERE u.entity IN (0, ".$conf->entity.")";
1924			}
1925		}
1926		if (!empty($user->socid)) {
1927			$sql .= " AND u.fk_soc = ".((int) $user->socid);
1928		}
1929		if (is_array($exclude) && $excludeUsers) {
1930			$sql .= " AND u.rowid NOT IN (".$this->db->sanitize($excludeUsers).")";
1931		}
1932		if ($includeUsers) {
1933			$sql .= " AND u.rowid IN (".$this->db->sanitize($includeUsers).")";
1934		}
1935		if (!empty($conf->global->USER_HIDE_INACTIVE_IN_COMBOBOX) || $noactive) {
1936			$sql .= " AND u.statut <> 0";
1937		}
1938		if (!empty($morefilter)) {
1939			$sql .= " ".$morefilter;
1940		}
1941
1942		//Add hook to filter on user (for exemple on usergroup define in custom modules)
1943		$reshook = $hookmanager->executeHooks('addSQLWhereFilterOnSelectUsers', array(), $this, $action);
1944		if (!empty($reshook)) {
1945			$sql .= $hookmanager->resPrint;
1946		}
1947
1948		if (empty($conf->global->MAIN_FIRSTNAME_NAME_POSITION)) {	// MAIN_FIRSTNAME_NAME_POSITION is 0 means firstname+lastname
1949			$sql .= " ORDER BY u.statut DESC, u.firstname ASC, u.lastname ASC";
1950		} else {
1951			$sql .= " ORDER BY u.statut DESC, u.lastname ASC, u.firstname ASC";
1952		}
1953
1954		dol_syslog(get_class($this)."::select_dolusers", LOG_DEBUG);
1955
1956		$resql = $this->db->query($sql);
1957		if ($resql) {
1958			$num = $this->db->num_rows($resql);
1959			$i = 0;
1960			if ($num) {
1961				// do not use maxwidthonsmartphone by default. Set it by caller so auto size to 100% will work when not defined
1962				$out .= '<select class="flat'.($morecss ? ' '.$morecss : ' minwidth200').'" id="'.$htmlname.'" name="'.$htmlname.($multiple ? '[]' : '').'" '.($multiple ? 'multiple' : '').' '.($disabled ? ' disabled' : '').'>';
1963				if ($show_empty && !$multiple) {
1964					$textforempty = ' ';
1965					if (!empty($conf->use_javascript_ajax)) {
1966						$textforempty = '&nbsp;'; // If we use ajaxcombo, we need &nbsp; here to avoid to have an empty element that is too small.
1967					}
1968					if (!is_numeric($show_empty)) {
1969						$textforempty = $show_empty;
1970					}
1971					$out .= '<option class="optiongrey" value="'.($show_empty < 0 ? $show_empty : -1).'"'.((empty($selected) || in_array(-1, $selected)) ? ' selected' : '').'>'.$textforempty.'</option>'."\n";
1972				}
1973				if ($show_every) {
1974					$out .= '<option value="-2"'.((in_array(-2, $selected)) ? ' selected' : '').'>-- '.$langs->trans("Everybody").' --</option>'."\n";
1975				}
1976
1977				$userstatic = new User($this->db);
1978
1979				while ($i < $num) {
1980					$obj = $this->db->fetch_object($resql);
1981
1982					$userstatic->id = $obj->rowid;
1983					$userstatic->lastname = $obj->lastname;
1984					$userstatic->firstname = $obj->firstname;
1985					$userstatic->photo = $obj->photo;
1986					$userstatic->statut = $obj->status;
1987					$userstatic->entity = $obj->entity;
1988					$userstatic->admin = $obj->admin;
1989
1990					$disableline = '';
1991					if (is_array($enableonly) && count($enableonly) && !in_array($obj->rowid, $enableonly)) {
1992						$disableline = ($enableonlytext ? $enableonlytext : '1');
1993					}
1994
1995					$labeltoshow = '';
1996
1997					// $fullNameMode is 0=Lastname+Firstname (MAIN_FIRSTNAME_NAME_POSITION=1), 1=Firstname+Lastname (MAIN_FIRSTNAME_NAME_POSITION=0)
1998					$fullNameMode = 0;
1999					if (empty($conf->global->MAIN_FIRSTNAME_NAME_POSITION)) {
2000						$fullNameMode = 1; //Firstname+lastname
2001					}
2002					$labeltoshow .= $userstatic->getFullName($langs, $fullNameMode, -1, $maxlength);
2003					if (empty($obj->firstname) && empty($obj->lastname)) {
2004						$labeltoshow .= $obj->login;
2005					}
2006
2007					// Complete name with more info
2008					$moreinfo = '';
2009					if (!empty($conf->global->MAIN_SHOW_LOGIN)) {
2010						$moreinfo .= ($moreinfo ? ' - ' : ' (').$obj->login;
2011					}
2012					if ($showstatus >= 0) {
2013						if ($obj->status == 1 && $showstatus == 1) {
2014							$moreinfo .= ($moreinfo ? ' - ' : ' (').$langs->trans('Enabled');
2015						}
2016						if ($obj->status == 0 && $showstatus == 1) {
2017							$moreinfo .= ($moreinfo ? ' - ' : ' (').$langs->trans('Disabled');
2018						}
2019					}
2020					if (!empty($conf->multicompany->enabled) && empty($conf->global->MULTICOMPANY_TRANSVERSE_MODE) && $conf->entity == 1 && $user->admin && !$user->entity) {
2021						if (!$obj->entity) {
2022							$moreinfo .= ($moreinfo ? ' - ' : ' (').$langs->trans("AllEntities");
2023						} else {
2024							if ($obj->entity != $conf->entity) {
2025								$moreinfo .= ($moreinfo ? ' - ' : ' (').($obj->label ? $obj->label : $langs->trans("EntityNameNotDefined"));
2026							}
2027						}
2028					}
2029					$moreinfo .= ($moreinfo ? ')' : '');
2030					if ($disableline && $disableline != '1') {
2031						$moreinfo .= ' - '.$disableline; // This is text from $enableonlytext parameter
2032					}
2033					$labeltoshow .= $moreinfo;
2034
2035					$out .= '<option value="'.$obj->rowid.'"';
2036					if ($disableline) {
2037						$out .= ' disabled';
2038					}
2039					if ((is_object($selected) && $selected->id == $obj->rowid) || (!is_object($selected) && in_array($obj->rowid, $selected))) {
2040						$out .= ' selected';
2041					}
2042					$out .= ' data-html="';
2043					$outhtml = '';
2044					// if (!empty($obj->photo)) {
2045					$outhtml .= $userstatic->getNomUrl(-3, '', 0, 1, 24, 1, 'login', '', 1).' ';
2046					// }
2047					if ($showstatus >= 0 && $obj->status == 0) {
2048						$outhtml .= '<strike class="opacitymediumxxx">';
2049					}
2050					$outhtml .= $labeltoshow;
2051					if ($showstatus >= 0 && $obj->status == 0) {
2052						$outhtml .= '</strike>';
2053					}
2054					$out .= dol_escape_htmltag($outhtml);
2055					$out .= '">';
2056					$out .= $labeltoshow;
2057					$out .= '</option>';
2058
2059					$outarray[$userstatic->id] = $userstatic->getFullName($langs, $fullNameMode, -1, $maxlength).$moreinfo;
2060
2061					$i++;
2062				}
2063			} else {
2064				$out .= '<select class="flat" id="'.$htmlname.'" name="'.$htmlname.'" disabled>';
2065				$out .= '<option value="">'.$langs->trans("None").'</option>';
2066			}
2067			$out .= '</select>';
2068
2069			if ($num) {
2070				// Enhance with select2
2071				include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
2072				$out .= ajax_combobox($htmlname);
2073			}
2074		} else {
2075			dol_print_error($this->db);
2076		}
2077
2078		if ($outputmode) {
2079			return $outarray;
2080		}
2081		return $out;
2082	}
2083
2084
2085	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2086	/**
2087	 *	Return select list of users. Selected users are stored into session.
2088	 *  List of users are provided into $_SESSION['assignedtouser'].
2089	 *
2090	 *  @param  string	$action         Value for $action
2091	 *  @param  string	$htmlname       Field name in form
2092	 *  @param  int		$show_empty     0=list without the empty value, 1=add empty value
2093	 *  @param  array	$exclude        Array list of users id to exclude
2094	 * 	@param	int		$disabled		If select list must be disabled
2095	 *  @param  array	$include        Array list of users id to include or 'hierarchy' to have only supervised users
2096	 * 	@param	array	$enableonly		Array list of users id to be enabled. All other must be disabled
2097	 *  @param	int		$force_entity	'0' or Ids of environment to force
2098	 *  @param	int		$maxlength		Maximum length of string into list (0=no limit)
2099	 *  @param	int		$showstatus		0=show user status only if status is disabled, 1=always show user status into label, -1=never show user status
2100	 *  @param	string	$morefilter		Add more filters into sql request
2101	 *  @param	int		$showproperties		Show properties of each attendees
2102	 *  @param	array	$listofuserid		Array with properties of each user
2103	 *  @param	array	$listofcontactid	Array with properties of each contact
2104	 *  @param	array	$listofotherid		Array with properties of each other contact
2105	 * 	@return	string					HTML select string
2106	 *  @see select_dolgroups()
2107	 */
2108	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())
2109	{
2110		// phpcs:enable
2111		global $conf, $user, $langs;
2112
2113		$userstatic = new User($this->db);
2114		$out = '';
2115
2116
2117		$assignedtouser = array();
2118		if (!empty($_SESSION['assignedtouser'])) {
2119			$assignedtouser = json_decode($_SESSION['assignedtouser'], true);
2120		}
2121		$nbassignetouser = count($assignedtouser);
2122
2123		//if ($nbassignetouser && $action != 'view') $out .= '<br>';
2124		if ($nbassignetouser) {
2125			$out .= '<ul class="attendees">';
2126		}
2127		$i = 0;
2128		$ownerid = 0;
2129		foreach ($assignedtouser as $key => $value) {
2130			if ($value['id'] == $ownerid) {
2131				continue;
2132			}
2133
2134			$out .= '<li>';
2135			$userstatic->fetch($value['id']);
2136			$out .= $userstatic->getNomUrl(-1);
2137			if ($i == 0) {
2138				$ownerid = $value['id'];
2139				$out .= ' ('.$langs->trans("Owner").')';
2140			}
2141			if ($nbassignetouser > 1 && $action != 'view') {
2142				$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.'">';
2143			}
2144			// Show my availability
2145			if ($showproperties) {
2146				if ($ownerid == $value['id'] && is_array($listofuserid) && count($listofuserid) && in_array($ownerid, array_keys($listofuserid))) {
2147					$out .= '<div class="myavailability inline-block">';
2148					$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>';
2149					$out .= '</div>';
2150				}
2151			}
2152			//$out.=' '.($value['mandatory']?$langs->trans("Mandatory"):$langs->trans("Optional"));
2153			//$out.=' '.($value['transparency']?$langs->trans("Busy"):$langs->trans("NotBusy"));
2154
2155			$out .= '</li>';
2156			$i++;
2157		}
2158		if ($nbassignetouser) {
2159			$out .= '</ul>';
2160		}
2161
2162		// Method with no ajax
2163		if ($action != 'view') {
2164			$out .= '<input type="hidden" class="removedassignedhidden" name="removedassigned" value="">';
2165			$out .= '<script type="text/javascript" language="javascript">jQuery(document).ready(function () {';
2166			$out .= 'jQuery(".removedassigned").click(function() { jQuery(".removedassignedhidden").val(jQuery(this).val()); });';
2167			$out .= 'jQuery(".assignedtouser").change(function() { console.log(jQuery(".assignedtouser option:selected").val());';
2168			$out .= ' if (jQuery(".assignedtouser option:selected").val() > 0) { jQuery("#'.$action.'assignedtouser").attr("disabled", false); }';
2169			$out .= ' else { jQuery("#'.$action.'assignedtouser").attr("disabled", true); }';
2170			$out .= '});';
2171			$out .= '})</script>';
2172			$out .= $this->select_dolusers('', $htmlname, $show_empty, $exclude, $disabled, $include, $enableonly, $force_entity, $maxlength, $showstatus, $morefilter);
2173			$out .= ' <input type="submit" disabled class="button valignmiddle smallpaddingimp reposition" id="'.$action.'assignedtouser" name="'.$action.'assignedtouser" value="'.dol_escape_htmltag($langs->trans("Add")).'">';
2174			$out .= '<br>';
2175		}
2176
2177		return $out;
2178	}
2179
2180
2181	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2182	/**
2183	 *  Return list of products for customer in Ajax if Ajax activated or go to select_produits_list
2184	 *
2185	 *  @param		int			$selected				Preselected products
2186	 *  @param		string		$htmlname				Name of HTML select field (must be unique in page).
2187	 *  @param		int|string	$filtertype				Filter on product type (''=nofilter, 0=product, 1=service)
2188	 *  @param		int			$limit					Limit on number of returned lines
2189	 *  @param		int			$price_level			Level of price to show
2190	 *  @param		int			$status					Sell status -1=Return all products, 0=Products not on sell, 1=Products on sell
2191	 *  @param		int			$finished				2=all, 1=finished, 0=raw material
2192	 *  @param		string		$selected_input_value	Value of preselected input text (for use with ajax)
2193	 *  @param		int			$hidelabel				Hide label (0=no, 1=yes, 2=show search icon (before) and placeholder, 3 search icon after)
2194	 *  @param		array		$ajaxoptions			Options for ajax_autocompleter
2195	 *  @param      int			$socid					Thirdparty Id (to get also price dedicated to this customer)
2196	 *  @param		string		$showempty				'' to not show empty line. Translation key to show an empty line. '1' show empty line with no text.
2197	 * 	@param		int			$forcecombo				Force to use combo box
2198	 *  @param      string      $morecss                Add more css on select
2199	 *  @param      int         $hidepriceinlabel       1=Hide prices in label
2200	 *  @param      string      $warehouseStatus        Warehouse status filter to count the quantity in stock. Following comma separated filter options can be used
2201	 *										            'warehouseopen' = count products from open warehouses,
2202	 *										            'warehouseclosed' = count products from closed warehouses,
2203	 *										            'warehouseinternal' = count products from warehouses for internal correct/transfer only
2204	 *  @param 		array 		$selected_combinations 	Selected combinations. Format: array([attrid] => attrval, [...])
2205	 *  @param		string		$nooutput				No print, return the output into a string
2206	 *  @return		void|string
2207	 */
2208	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)
2209	{
2210		// phpcs:enable
2211		global $langs, $conf;
2212
2213		$out = '';
2214
2215		// check parameters
2216		$price_level = (!empty($price_level) ? $price_level : 0);
2217		if (is_null($ajaxoptions)) {
2218			$ajaxoptions = array();
2219		}
2220
2221		if (strval($filtertype) === '' && (!empty($conf->product->enabled) || !empty($conf->service->enabled))) {
2222			if (!empty($conf->product->enabled) && empty($conf->service->enabled)) {
2223				$filtertype = '0';
2224			} elseif (empty($conf->product->enabled) && !empty($conf->service->enabled)) {
2225				$filtertype = '1';
2226			}
2227		}
2228
2229		if (!empty($conf->use_javascript_ajax) && !empty($conf->global->PRODUIT_USE_SEARCH_TO_SELECT)) {
2230			$placeholder = '';
2231
2232			if ($selected && empty($selected_input_value)) {
2233				require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
2234				$producttmpselect = new Product($this->db);
2235				$producttmpselect->fetch($selected);
2236				$selected_input_value = $producttmpselect->ref;
2237				unset($producttmpselect);
2238			}
2239			// handle case where product or service module is disabled + no filter specified
2240			if ($filtertype == '') {
2241				if (empty($conf->product->enabled)) { // when product module is disabled, show services only
2242					$filtertype = 1;
2243				} elseif (empty($conf->service->enabled)) { // when service module is disabled, show products only
2244					$filtertype = 0;
2245				}
2246			}
2247			// mode=1 means customers products
2248			$urloption = 'htmlname='.$htmlname.'&outjson=1&price_level='.$price_level.'&type='.$filtertype.'&mode=1&status='.$status.'&finished='.$finished.'&hidepriceinlabel='.$hidepriceinlabel.'&warehousestatus='.$warehouseStatus;
2249			//Price by customer
2250			if (!empty($conf->global->PRODUIT_CUSTOMER_PRICES) && !empty($socid)) {
2251				$urloption .= '&socid='.$socid;
2252			}
2253			$out .= ajax_autocompleter($selected, $htmlname, DOL_URL_ROOT.'/product/ajax/products.php', $urloption, $conf->global->PRODUIT_USE_SEARCH_TO_SELECT, 1, $ajaxoptions);
2254
2255			if (!empty($conf->variants->enabled) && is_array($selected_combinations)) {
2256				// Code to automatically insert with javascript the select of attributes under the select of product
2257				// when a parent of variant has been selected.
2258				$out .= '
2259				<!-- script to auto show attributes select tags if a variant was selected -->
2260				<script>
2261					// auto show attributes fields
2262					selected = '.json_encode($selected_combinations).';
2263					combvalues = {};
2264
2265					jQuery(document).ready(function () {
2266
2267						jQuery("input[name=\'prod_entry_mode\']").change(function () {
2268							if (jQuery(this).val() == \'free\') {
2269								jQuery(\'div#attributes_box\').empty();
2270							}
2271						});
2272
2273						jQuery("input#'.$htmlname.'").change(function () {
2274
2275							if (!jQuery(this).val()) {
2276								jQuery(\'div#attributes_box\').empty();
2277								return;
2278							}
2279
2280							console.log("A change has started. We get variants fields to inject html select");
2281
2282							jQuery.getJSON("'.DOL_URL_ROOT.'/variants/ajax/getCombinations.php", {
2283								id: jQuery(this).val()
2284							}, function (data) {
2285								jQuery(\'div#attributes_box\').empty();
2286
2287								jQuery.each(data, function (key, val) {
2288
2289									combvalues[val.id] = val.values;
2290
2291									var span = jQuery(document.createElement(\'div\')).css({
2292										\'display\': \'table-row\'
2293									});
2294
2295									span.append(
2296										jQuery(document.createElement(\'div\')).text(val.label).css({
2297											\'font-weight\': \'bold\',
2298											\'display\': \'table-cell\'
2299										})
2300									);
2301
2302									var html = jQuery(document.createElement(\'select\')).attr(\'name\', \'combinations[\' + val.id + \']\').css({
2303										\'margin-left\': \'15px\',
2304										\'white-space\': \'pre\'
2305									}).append(
2306										jQuery(document.createElement(\'option\')).val(\'\')
2307									);
2308
2309									jQuery.each(combvalues[val.id], function (key, val) {
2310										var tag = jQuery(document.createElement(\'option\')).val(val.id).html(val.value);
2311
2312										if (selected[val.fk_product_attribute] == val.id) {
2313											tag.attr(\'selected\', \'selected\');
2314										}
2315
2316										html.append(tag);
2317									});
2318
2319									span.append(html);
2320									jQuery(\'div#attributes_box\').append(span);
2321								});
2322							})
2323						});
2324
2325						'.($selected ? 'jQuery("input#'.$htmlname.'").change();' : '').'
2326					});
2327				</script>
2328                ';
2329			}
2330
2331			if (empty($hidelabel)) {
2332				$out .= $langs->trans("RefOrLabel").' : ';
2333			} elseif ($hidelabel > 1) {
2334				$placeholder = ' placeholder="'.$langs->trans("RefOrLabel").'"';
2335				if ($hidelabel == 2) {
2336					$out .= img_picto($langs->trans("Search"), 'search');
2337				}
2338			}
2339			$out .= '<input type="text" class="minwidth100" name="search_'.$htmlname.'" id="search_'.$htmlname.'" value="'.$selected_input_value.'"'.$placeholder.' '.(!empty($conf->global->PRODUCT_SEARCH_AUTOFOCUS) ? 'autofocus' : '').' />';
2340			if ($hidelabel == 3) {
2341				$out .= img_picto($langs->trans("Search"), 'search');
2342			}
2343		} else {
2344			$out .= $this->select_produits_list($selected, $htmlname, $filtertype, $limit, $price_level, '', $status, $finished, 0, $socid, $showempty, $forcecombo, $morecss, $hidepriceinlabel, $warehouseStatus);
2345		}
2346
2347		if (empty($nooutput)) {
2348			print $out;
2349		} else {
2350			return $out;
2351		}
2352	}
2353
2354	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2355	/**
2356	 *	Return list of products for a customer.
2357	 *  Called by select_produits.
2358	 *
2359	 *	@param      int		$selected           Preselected product
2360	 *	@param      string	$htmlname           Name of select html
2361	 *  @param		string	$filtertype         Filter on product type (''=nofilter, 0=product, 1=service)
2362	 *	@param      int		$limit              Limit on number of returned lines
2363	 *	@param      int		$price_level        Level of price to show
2364	 * 	@param      string	$filterkey          Filter on product
2365	 *	@param		int		$status             -1=Return all products, 0=Products not on sell, 1=Products on sell
2366	 *  @param      int		$finished           Filter on finished field: 2=No filter
2367	 *  @param      int		$outputmode         0=HTML select string, 1=Array
2368	 *  @param      int		$socid     		    Thirdparty Id (to get also price dedicated to this customer)
2369	 *  @param		string	$showempty		    '' to not show empty line. Translation key to show an empty line. '1' show empty line with no text.
2370	 * 	@param		int		$forcecombo		    Force to use combo box
2371	 *  @param      string  $morecss            Add more css on select
2372	 *  @param      int     $hidepriceinlabel   1=Hide prices in label
2373	 *  @param      string  $warehouseStatus    Warehouse status filter to group/count stock. Following comma separated filter options can be used.
2374	 *										    'warehouseopen' = count products from open warehouses,
2375	 *										    'warehouseclosed' = count products from closed warehouses,
2376	 *										    'warehouseinternal' = count products from warehouses for internal correct/transfer only
2377	 *  @return     array    				    Array of keys for json
2378	 */
2379	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 = '')
2380	{
2381		// phpcs:enable
2382		global $langs, $conf, $user, $db;
2383
2384		$out = '';
2385		$outarray = array();
2386
2387		// Units
2388		if (!empty($conf->global->PRODUCT_USE_UNITS)) {
2389			$langs->load('other');
2390		}
2391
2392		$warehouseStatusArray = array();
2393		if (!empty($warehouseStatus)) {
2394			require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
2395			if (preg_match('/warehouseclosed/', $warehouseStatus)) {
2396				$warehouseStatusArray[] = Entrepot::STATUS_CLOSED;
2397			}
2398			if (preg_match('/warehouseopen/', $warehouseStatus)) {
2399				$warehouseStatusArray[] = Entrepot::STATUS_OPEN_ALL;
2400			}
2401			if (preg_match('/warehouseinternal/', $warehouseStatus)) {
2402				$warehouseStatusArray[] = Entrepot::STATUS_OPEN_INTERNAL;
2403			}
2404		}
2405
2406		$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.duration, p.fk_price_expression";
2407		if (count($warehouseStatusArray)) {
2408			$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
2409		} else {
2410			$selectFieldsGrouped = ", ".$this->db->ifsql("p.stock IS NULL", 0, "p.stock")." AS stock";
2411		}
2412
2413		$sql = "SELECT ";
2414		$sql .= $selectFields.$selectFieldsGrouped;
2415
2416		if (!empty($conf->global->PRODUCT_SORT_BY_CATEGORY)) {
2417			//Product category
2418			$sql .= ", (SELECT ".MAIN_DB_PREFIX."categorie_product.fk_categorie
2419						FROM ".MAIN_DB_PREFIX."categorie_product
2420						WHERE ".MAIN_DB_PREFIX."categorie_product.fk_product=p.rowid
2421						LIMIT 1
2422				) AS categorie_product_id ";
2423		}
2424
2425		//Price by customer
2426		if (!empty($conf->global->PRODUIT_CUSTOMER_PRICES) && !empty($socid)) {
2427			$sql .= ', pcp.rowid as idprodcustprice, pcp.price as custprice, pcp.price_ttc as custprice_ttc,';
2428			$sql .= ' pcp.price_base_type as custprice_base_type, pcp.tva_tx as custtva_tx, pcp.ref_customer as custref';
2429			$selectFields .= ", idprodcustprice, custprice, custprice_ttc, custprice_base_type, custtva_tx, custref";
2430		}
2431		// Units
2432		if (!empty($conf->global->PRODUCT_USE_UNITS)) {
2433			$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";
2434			$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';
2435		}
2436
2437		// Multilang : we add translation
2438		if (!empty($conf->global->MAIN_MULTILANGS)) {
2439			$sql .= ", pl.label as label_translated";
2440			$sql .= ", pl.description as description_translated";
2441			$selectFields .= ", label_translated";
2442			$selectFields .= ", description_translated";
2443		}
2444		// Price by quantity
2445		if (!empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY) || !empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES)) {
2446			$sql .= ", (SELECT pp.rowid FROM ".MAIN_DB_PREFIX."product_price as pp WHERE pp.fk_product = p.rowid";
2447			if ($price_level >= 1 && !empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES)) {
2448				$sql .= " AND price_level = ".((int) $price_level);
2449			}
2450			$sql .= " ORDER BY date_price";
2451			$sql .= " DESC LIMIT 1) as price_rowid";
2452			$sql .= ", (SELECT pp.price_by_qty FROM ".MAIN_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
2453			if ($price_level >= 1 && !empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES)) {
2454				$sql .= " AND price_level = ".((int) $price_level);
2455			}
2456			$sql .= " ORDER BY date_price";
2457			$sql .= " DESC LIMIT 1) as price_by_qty";
2458			$selectFields .= ", price_rowid, price_by_qty";
2459		}
2460		$sql .= " FROM ".MAIN_DB_PREFIX."product as p";
2461		if (count($warehouseStatusArray)) {
2462			$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product_stock as ps on ps.fk_product = p.rowid";
2463			$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."entrepot as e on ps.fk_entrepot = e.rowid AND e.entity IN (".getEntity('stock').")";
2464			$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.
2465		}
2466
2467		// include search in supplier ref
2468		if (!empty($conf->global->MAIN_SEARCH_PRODUCT_BY_FOURN_REF)) {
2469			$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product_fournisseur_price as pfp ON p.rowid = pfp.fk_product";
2470		}
2471
2472		//Price by customer
2473		if (!empty($conf->global->PRODUIT_CUSTOMER_PRICES) && !empty($socid)) {
2474			$sql .= " LEFT JOIN  ".MAIN_DB_PREFIX."product_customer_price as pcp ON pcp.fk_soc=".((int) $socid)." AND pcp.fk_product=p.rowid";
2475		}
2476		// Units
2477		if (!empty($conf->global->PRODUCT_USE_UNITS)) {
2478			$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_units u ON u.rowid = p.fk_unit";
2479		}
2480		// Multilang : we add translation
2481		if (!empty($conf->global->MAIN_MULTILANGS)) {
2482			$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product_lang as pl ON pl.fk_product = p.rowid ";
2483			if (!empty($conf->global->PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE) && !empty($socid)) {
2484				require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
2485				$soc = new Societe($db);
2486				$result = $soc->fetch($socid);
2487				if ($result > 0 && !empty($soc->default_lang)) {
2488					$sql .= " AND pl.lang='" . $this->db->escape($soc->default_lang) . "'";
2489				} else {
2490					$sql .= " AND pl.lang='".$this->db->escape($langs->getDefaultLang())."'";
2491				}
2492			} else {
2493				$sql .= " AND pl.lang='".$this->db->escape($langs->getDefaultLang())."'";
2494			}
2495		}
2496
2497		if (!empty($conf->global->PRODUIT_ATTRIBUTES_HIDECHILD)) {
2498			$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product_attribute_combination pac ON pac.fk_product_child = p.rowid";
2499		}
2500
2501		$sql .= ' WHERE p.entity IN ('.getEntity('product').')';
2502
2503		if (!empty($conf->global->PRODUIT_ATTRIBUTES_HIDECHILD)) {
2504			$sql .= " AND pac.rowid IS NULL";
2505		}
2506
2507		if ($finished == 0) {
2508			$sql .= " AND p.finished = ".((int) $finished);
2509		} elseif ($finished == 1) {
2510			$sql .= " AND p.finished = ".((int) $finished);
2511			if ($status >= 0) {
2512				$sql .= " AND p.tosell = ".((int) $status);
2513			}
2514		} elseif ($status >= 0) {
2515			$sql .= " AND p.tosell = ".((int) $status);
2516		}
2517		// Filter by product type
2518		if (strval($filtertype) != '') {
2519			$sql .= " AND p.fk_product_type = ".((int) $filtertype);
2520		} elseif (empty($conf->product->enabled)) { // when product module is disabled, show services only
2521			$sql .= " AND p.fk_product_type = 1";
2522		} elseif (empty($conf->service->enabled)) { // when service module is disabled, show products only
2523			$sql .= " AND p.fk_product_type = 0";
2524		}
2525		// Add criteria on ref/label
2526		if ($filterkey != '') {
2527			$sql .= ' AND (';
2528			$prefix = empty($conf->global->PRODUCT_DONOTSEARCH_ANYWHERE) ? '%' : ''; // Can use index if PRODUCT_DONOTSEARCH_ANYWHERE is on
2529			// For natural search
2530			$scrit = explode(' ', $filterkey);
2531			$i = 0;
2532			if (count($scrit) > 1) {
2533				$sql .= "(";
2534			}
2535			foreach ($scrit as $crit) {
2536				if ($i > 0) {
2537					$sql .= " AND ";
2538				}
2539				$sql .= "(p.ref LIKE '".$this->db->escape($prefix.$crit)."%' OR p.label LIKE '".$this->db->escape($prefix.$crit)."%'";
2540				if (!empty($conf->global->MAIN_MULTILANGS)) {
2541					$sql .= " OR pl.label LIKE '".$this->db->escape($prefix.$crit)."%'";
2542				}
2543				if (!empty($conf->global->PRODUIT_CUSTOMER_PRICES) && ! empty($socid)) {
2544					$sql .= " OR pcp.ref_customer LIKE '".$this->db->escape($prefix.$crit)."%'";
2545				}
2546				if (!empty($conf->global->PRODUCT_AJAX_SEARCH_ON_DESCRIPTION)) {
2547					$sql .= " OR p.description LIKE '".$this->db->escape($prefix.$crit)."%'";
2548					if (!empty($conf->global->MAIN_MULTILANGS)) {
2549						$sql .= " OR pl.description LIKE '".$this->db->escape($prefix.$crit)."%'";
2550					}
2551				}
2552				if (!empty($conf->global->MAIN_SEARCH_PRODUCT_BY_FOURN_REF)) {
2553					$sql .= " OR pfp.ref_fourn LIKE '".$this->db->escape($prefix.$crit)."%'";
2554				}
2555				$sql .= ")";
2556				$i++;
2557			}
2558			if (count($scrit) > 1) {
2559				$sql .= ")";
2560			}
2561			if (!empty($conf->barcode->enabled)) {
2562				$sql .= " OR p.barcode LIKE '".$this->db->escape($prefix.$filterkey)."%'";
2563			}
2564			$sql .= ')';
2565		}
2566		if (count($warehouseStatusArray)) {
2567			$sql .= ' GROUP BY'.$selectFields;
2568		}
2569
2570		//Sort by category
2571		if (!empty($conf->global->PRODUCT_SORT_BY_CATEGORY)) {
2572			$sql .= " ORDER BY categorie_product_id ";
2573			//ASC OR DESC order
2574			($conf->global->PRODUCT_SORT_BY_CATEGORY == 1) ? $sql .= "ASC" : $sql .= "DESC";
2575		} else {
2576			$sql .= $this->db->order("p.ref");
2577		}
2578
2579		$sql .= $this->db->plimit($limit, 0);
2580
2581		// Build output string
2582		dol_syslog(get_class($this)."::select_produits_list search products", LOG_DEBUG);
2583		$result = $this->db->query($sql);
2584		if ($result) {
2585			require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
2586			require_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2587			require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
2588
2589			$num = $this->db->num_rows($result);
2590
2591			$events = null;
2592
2593			if (!$forcecombo) {
2594				include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
2595				$out .= ajax_combobox($htmlname, $events, $conf->global->PRODUIT_USE_SEARCH_TO_SELECT);
2596			}
2597
2598			$out .= '<select class="flat'.($morecss ? ' '.$morecss : '').'" name="'.$htmlname.'" id="'.$htmlname.'">';
2599
2600			$textifempty = '';
2601			// Do not use textifempty = ' ' or '&nbsp;' here, or search on key will search on ' key'.
2602			//if (! empty($conf->use_javascript_ajax) || $forcecombo) $textifempty='';
2603			if (!empty($conf->global->PRODUIT_USE_SEARCH_TO_SELECT)) {
2604				if ($showempty && !is_numeric($showempty)) {
2605					$textifempty = $langs->trans($showempty);
2606				} else {
2607					$textifempty .= $langs->trans("All");
2608				}
2609			} else {
2610				if ($showempty && !is_numeric($showempty)) {
2611					$textifempty = $langs->trans($showempty);
2612				}
2613			}
2614			if ($showempty) {
2615				$out .= '<option value="-1" selected>'.($textifempty ? $textifempty : '&nbsp;').'</option>';
2616			}
2617
2618			$i = 0;
2619			while ($num && $i < $num) {
2620				$opt = '';
2621				$optJson = array();
2622				$objp = $this->db->fetch_object($result);
2623
2624				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
2625					$sql = "SELECT rowid, quantity, price, unitprice, remise_percent, remise, price_base_type";
2626					$sql .= " FROM ".MAIN_DB_PREFIX."product_price_by_qty";
2627					$sql .= " WHERE fk_product_price=".$objp->price_rowid;
2628					$sql .= " ORDER BY quantity ASC";
2629
2630					dol_syslog(get_class($this)."::select_produits_list search prices by qty", LOG_DEBUG);
2631					$result2 = $this->db->query($sql);
2632					if ($result2) {
2633						$nb_prices = $this->db->num_rows($result2);
2634						$j = 0;
2635						while ($nb_prices && $j < $nb_prices) {
2636							$objp2 = $this->db->fetch_object($result2);
2637
2638							$objp->price_by_qty_rowid = $objp2->rowid;
2639							$objp->price_by_qty_price_base_type = $objp2->price_base_type;
2640							$objp->price_by_qty_quantity = $objp2->quantity;
2641							$objp->price_by_qty_unitprice = $objp2->unitprice;
2642							$objp->price_by_qty_remise_percent = $objp2->remise_percent;
2643							// For backward compatibility
2644							$objp->quantity = $objp2->quantity;
2645							$objp->price = $objp2->price;
2646							$objp->unitprice = $objp2->unitprice;
2647							$objp->remise_percent = $objp2->remise_percent;
2648							$objp->remise = $objp2->remise;
2649
2650							$this->constructProductListOption($objp, $opt, $optJson, 0, $selected, $hidepriceinlabel, $filterkey);
2651
2652							$j++;
2653
2654							// Add new entry
2655							// "key" value of json key array is used by jQuery automatically as selected value
2656							// "label" value of json key array is used by jQuery automatically as text for combo box
2657							$out .= $opt;
2658							array_push($outarray, $optJson);
2659						}
2660					}
2661				} else {
2662					if (!empty($conf->dynamicprices->enabled) && !empty($objp->fk_price_expression)) {
2663						$price_product = new Product($this->db);
2664						$price_product->fetch($objp->rowid, '', '', 1);
2665						$priceparser = new PriceParser($this->db);
2666						$price_result = $priceparser->parseProduct($price_product);
2667						if ($price_result >= 0) {
2668							$objp->price = $price_result;
2669							$objp->unitprice = $price_result;
2670							//Calculate the VAT
2671							$objp->price_ttc = price2num($objp->price) * (1 + ($objp->tva_tx / 100));
2672							$objp->price_ttc = price2num($objp->price_ttc, 'MU');
2673						}
2674					}
2675
2676					$this->constructProductListOption($objp, $opt, $optJson, $price_level, $selected, $hidepriceinlabel, $filterkey);
2677					// Add new entry
2678					// "key" value of json key array is used by jQuery automatically as selected value
2679					// "label" value of json key array is used by jQuery automatically as text for combo box
2680					$out .= $opt;
2681					array_push($outarray, $optJson);
2682				}
2683
2684				$i++;
2685			}
2686
2687			$out .= '</select>';
2688
2689			$this->db->free($result);
2690
2691			if (empty($outputmode)) {
2692				return $out;
2693			}
2694			return $outarray;
2695		} else {
2696			dol_print_error($db);
2697		}
2698	}
2699
2700	/**
2701	 * constructProductListOption.
2702	 * This define value for &$opt and &$optJson.
2703	 *
2704	 * @param 	resource	$objp			    Resultset of fetch
2705	 * @param 	string		$opt			    Option (var used for returned value in string option format)
2706	 * @param 	string		$optJson		    Option (var used for returned value in json format)
2707	 * @param 	int			$price_level	    Price level
2708	 * @param 	string		$selected		    Preselected value
2709	 * @param   int         $hidepriceinlabel   Hide price in label
2710	 * @param   string      $filterkey          Filter key to highlight
2711	 * @param	int			$novirtualstock 	Do not load virtual stock, even if slow option STOCK_SHOW_VIRTUAL_STOCK_IN_PRODUCTS_COMBO is on.
2712	 * @return	void
2713	 */
2714	protected function constructProductListOption(&$objp, &$opt, &$optJson, $price_level, $selected, $hidepriceinlabel = 0, $filterkey = '', $novirtualstock = 0)
2715	{
2716		global $langs, $conf, $user, $db;
2717
2718		$outkey = '';
2719		$outval = '';
2720		$outref = '';
2721		$outlabel = '';
2722		$outlabel_translated = '';
2723		$outdesc = '';
2724		$outdesc_translated = '';
2725		$outbarcode = '';
2726		$outorigin = '';
2727		$outtype = '';
2728		$outprice_ht = '';
2729		$outprice_ttc = '';
2730		$outpricebasetype = '';
2731		$outtva_tx = '';
2732		$outqty = 1;
2733		$outdiscount = 0;
2734
2735		$maxlengtharticle = (empty($conf->global->PRODUCT_MAX_LENGTH_COMBO) ? 48 : $conf->global->PRODUCT_MAX_LENGTH_COMBO);
2736
2737		$label = $objp->label;
2738		if (!empty($objp->label_translated)) {
2739			$label = $objp->label_translated;
2740		}
2741		if (!empty($filterkey) && $filterkey != '') {
2742			$label = preg_replace('/('.preg_quote($filterkey, '/').')/i', '<strong>$1</strong>', $label, 1);
2743		}
2744
2745		$outkey = $objp->rowid;
2746		$outref = $objp->ref;
2747		$outrefcust = empty($objp->custref) ? '' : $objp->custref;
2748		$outlabel = $objp->label;
2749		$outdesc = $objp->description;
2750		if (!empty($conf->global->MAIN_MULTILANGS)) {
2751			$outlabel_translated = $objp->label_translated;
2752			$outdesc_translated = $objp->description_translated;
2753		}
2754		$outbarcode = $objp->barcode;
2755		$outorigin = $objp->fk_country;
2756		$outpbq = empty($objp->price_by_qty_rowid) ? '' : $objp->price_by_qty_rowid;
2757
2758		$outtype = $objp->fk_product_type;
2759		$outdurationvalue = $outtype == Product::TYPE_SERVICE ?substr($objp->duration, 0, dol_strlen($objp->duration) - 1) : '';
2760		$outdurationunit = $outtype == Product::TYPE_SERVICE ?substr($objp->duration, -1) : '';
2761
2762		if ($outorigin && !empty($conf->global->PRODUCT_SHOW_ORIGIN_IN_COMBO)) {
2763			require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
2764		}
2765
2766		// Units
2767		$outvalUnits = '';
2768		if (!empty($conf->global->PRODUCT_USE_UNITS)) {
2769			if (!empty($objp->unit_short)) {
2770				$outvalUnits .= ' - '.$objp->unit_short;
2771			}
2772		}
2773		if (!empty($conf->global->PRODUCT_SHOW_DIMENSIONS_IN_COMBO)) {
2774			if (!empty($objp->weight) && $objp->weight_units !== null) {
2775				$unitToShow = showDimensionInBestUnit($objp->weight, $objp->weight_units, 'weight', $langs);
2776				$outvalUnits .= ' - '.$unitToShow;
2777			}
2778			if ((!empty($objp->length) || !empty($objp->width) || !empty($objp->height)) && $objp->length_units !== null) {
2779				$unitToShow = $objp->length.' x '.$objp->width.' x '.$objp->height.' '.measuringUnitString(0, 'size', $objp->length_units);
2780				$outvalUnits .= ' - '.$unitToShow;
2781			}
2782			if (!empty($objp->surface) && $objp->surface_units !== null) {
2783				$unitToShow = showDimensionInBestUnit($objp->surface, $objp->surface_units, 'surface', $langs);
2784				$outvalUnits .= ' - '.$unitToShow;
2785			}
2786			if (!empty($objp->volume) && $objp->volume_units !== null) {
2787				$unitToShow = showDimensionInBestUnit($objp->volume, $objp->volume_units, 'volume', $langs);
2788				$outvalUnits .= ' - '.$unitToShow;
2789			}
2790		}
2791		if ($outdurationvalue && $outdurationunit) {
2792			$da = array(
2793				'h' => $langs->trans('Hour'),
2794				'd' => $langs->trans('Day'),
2795				'w' => $langs->trans('Week'),
2796				'm' => $langs->trans('Month'),
2797				'y' => $langs->trans('Year')
2798			);
2799			if (isset($da[$outdurationunit])) {
2800				$outvalUnits .= ' - '.$outdurationvalue.' '.$langs->transnoentities($da[$outdurationunit].($outdurationvalue > 1 ? 's' : ''));
2801			}
2802		}
2803
2804		$opt = '<option value="'.$objp->rowid.'"';
2805		$opt .= ($objp->rowid == $selected) ? ' selected' : '';
2806		if (!empty($objp->price_by_qty_rowid) && $objp->price_by_qty_rowid > 0) {
2807			$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.'"';
2808		}
2809		if (!empty($conf->stock->enabled) && isset($objp->stock) && ($objp->fk_product_type == Product::TYPE_PRODUCT || !empty($conf->global->STOCK_SUPPORTS_SERVICES))) {
2810			if (!empty($user->rights->stock->lire)) {
2811				if ($objp->stock > 0) {
2812					$opt .= ' class="product_line_stock_ok"';
2813				} elseif ($objp->stock <= 0) {
2814					$opt .= ' class="product_line_stock_too_low"';
2815				}
2816			}
2817		}
2818		if (!empty($conf->global->PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE)) {
2819			$opt .= ' data-labeltrans="'.$outlabel_translated.'"';
2820			$opt .= ' data-desctrans="'.dol_escape_htmltag($outdesc_translated).'"';
2821		}
2822		$opt .= '>';
2823		$opt .= $objp->ref;
2824		if (! empty($objp->custref)) {
2825			$opt.= ' (' . $objp->custref . ')';
2826		}
2827		if ($outbarcode) {
2828			$opt .= ' ('.$outbarcode.')';
2829		}
2830		$opt .= ' - '.dol_trunc($label, $maxlengtharticle);
2831		if ($outorigin && !empty($conf->global->PRODUCT_SHOW_ORIGIN_IN_COMBO)) {
2832			$opt .= ' ('.getCountry($outorigin, 1).')';
2833		}
2834
2835		$objRef = $objp->ref;
2836		if (! empty($objp->custref)) {
2837			$objRef .= ' (' . $objp->custref . ')';
2838		}
2839		if (!empty($filterkey) && $filterkey != '') {
2840			$objRef = preg_replace('/('.preg_quote($filterkey, '/').')/i', '<strong>$1</strong>', $objRef, 1);
2841		}
2842		$outval .= $objRef;
2843		if ($outbarcode) {
2844			$outval .= ' ('.$outbarcode.')';
2845		}
2846		$outval .= ' - '.dol_trunc($label, $maxlengtharticle);
2847		if ($outorigin && !empty($conf->global->PRODUCT_SHOW_ORIGIN_IN_COMBO)) {
2848			$outval .= ' ('.getCountry($outorigin, 1).')';
2849		}
2850
2851		// Units
2852		$opt .= $outvalUnits;
2853		$outval .= $outvalUnits;
2854
2855		$found = 0;
2856
2857		// Multiprice
2858		// If we need a particular price level (from 1 to 6)
2859		if (empty($hidepriceinlabel) && $price_level >= 1 && (!empty($conf->global->PRODUIT_MULTIPRICES) || !empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES))) {
2860			$sql = "SELECT price, price_ttc, price_base_type, tva_tx";
2861			$sql .= " FROM ".MAIN_DB_PREFIX."product_price";
2862			$sql .= " WHERE fk_product = ".((int) $objp->rowid);
2863			$sql .= " AND entity IN (".getEntity('productprice').")";
2864			$sql .= " AND price_level = ".((int) $price_level);
2865			$sql .= " ORDER BY date_price DESC, rowid DESC"; // Warning DESC must be both on date_price and rowid.
2866			$sql .= " LIMIT 1";
2867
2868			dol_syslog(get_class($this).'::constructProductListOption search price for product '.$objp->rowid.' AND level '.$price_level.'', LOG_DEBUG);
2869			$result2 = $this->db->query($sql);
2870			if ($result2) {
2871				$objp2 = $this->db->fetch_object($result2);
2872				if ($objp2) {
2873					$found = 1;
2874					if ($objp2->price_base_type == 'HT') {
2875						$opt .= ' - '.price($objp2->price, 1, $langs, 0, 0, -1, $conf->currency).' '.$langs->trans("HT");
2876						$outval .= ' - '.price($objp2->price, 0, $langs, 0, 0, -1, $conf->currency).' '.$langs->transnoentities("HT");
2877					} else {
2878						$opt .= ' - '.price($objp2->price_ttc, 1, $langs, 0, 0, -1, $conf->currency).' '.$langs->trans("TTC");
2879						$outval .= ' - '.price($objp2->price_ttc, 0, $langs, 0, 0, -1, $conf->currency).' '.$langs->transnoentities("TTC");
2880					}
2881					$outprice_ht = price($objp2->price);
2882					$outprice_ttc = price($objp2->price_ttc);
2883					$outpricebasetype = $objp2->price_base_type;
2884					$outtva_tx = $objp2->tva_tx;
2885				}
2886			} else {
2887				dol_print_error($this->db);
2888			}
2889		}
2890
2891		// Price by quantity
2892		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))) {
2893			$found = 1;
2894			$outqty = $objp->quantity;
2895			$outdiscount = $objp->remise_percent;
2896			if ($objp->quantity == 1) {
2897				$opt .= ' - '.price($objp->unitprice, 1, $langs, 0, 0, -1, $conf->currency)."/";
2898				$outval .= ' - '.price($objp->unitprice, 0, $langs, 0, 0, -1, $conf->currency)."/";
2899				$opt .= $langs->trans("Unit"); // Do not use strtolower because it breaks utf8 encoding
2900				$outval .= $langs->transnoentities("Unit");
2901			} else {
2902				$opt .= ' - '.price($objp->price, 1, $langs, 0, 0, -1, $conf->currency)."/".$objp->quantity;
2903				$outval .= ' - '.price($objp->price, 0, $langs, 0, 0, -1, $conf->currency)."/".$objp->quantity;
2904				$opt .= $langs->trans("Units"); // Do not use strtolower because it breaks utf8 encoding
2905				$outval .= $langs->transnoentities("Units");
2906			}
2907
2908			$outprice_ht = price($objp->unitprice);
2909			$outprice_ttc = price($objp->unitprice * (1 + ($objp->tva_tx / 100)));
2910			$outpricebasetype = $objp->price_base_type;
2911			$outtva_tx = $objp->tva_tx;
2912		}
2913		if (empty($hidepriceinlabel) && !empty($objp->quantity) && $objp->quantity >= 1) {
2914			$opt .= " (".price($objp->unitprice, 1, $langs, 0, 0, -1, $conf->currency)."/".$langs->trans("Unit").")"; // Do not use strtolower because it breaks utf8 encoding
2915			$outval .= " (".price($objp->unitprice, 0, $langs, 0, 0, -1, $conf->currency)."/".$langs->transnoentities("Unit").")"; // Do not use strtolower because it breaks utf8 encoding
2916		}
2917		if (empty($hidepriceinlabel) && !empty($objp->remise_percent) && $objp->remise_percent >= 1) {
2918			$opt .= " - ".$langs->trans("Discount")." : ".vatrate($objp->remise_percent).' %';
2919			$outval .= " - ".$langs->transnoentities("Discount")." : ".vatrate($objp->remise_percent).' %';
2920		}
2921
2922		// Price by customer
2923		if (empty($hidepriceinlabel) && !empty($conf->global->PRODUIT_CUSTOMER_PRICES)) {
2924			if (!empty($objp->idprodcustprice)) {
2925				$found = 1;
2926
2927				if ($objp->custprice_base_type == 'HT') {
2928					$opt .= ' - '.price($objp->custprice, 1, $langs, 0, 0, -1, $conf->currency).' '.$langs->trans("HT");
2929					$outval .= ' - '.price($objp->custprice, 0, $langs, 0, 0, -1, $conf->currency).' '.$langs->transnoentities("HT");
2930				} else {
2931					$opt .= ' - '.price($objp->custprice_ttc, 1, $langs, 0, 0, -1, $conf->currency).' '.$langs->trans("TTC");
2932					$outval .= ' - '.price($objp->custprice_ttc, 0, $langs, 0, 0, -1, $conf->currency).' '.$langs->transnoentities("TTC");
2933				}
2934
2935				$outprice_ht = price($objp->custprice);
2936				$outprice_ttc = price($objp->custprice_ttc);
2937				$outpricebasetype = $objp->custprice_base_type;
2938				$outtva_tx = $objp->custtva_tx;
2939			}
2940		}
2941
2942		// If level no defined or multiprice not found, we used the default price
2943		if (empty($hidepriceinlabel) && !$found) {
2944			if ($objp->price_base_type == 'HT') {
2945				$opt .= ' - '.price($objp->price, 1, $langs, 0, 0, -1, $conf->currency).' '.$langs->trans("HT");
2946				$outval .= ' - '.price($objp->price, 0, $langs, 0, 0, -1, $conf->currency).' '.$langs->transnoentities("HT");
2947			} else {
2948				$opt .= ' - '.price($objp->price_ttc, 1, $langs, 0, 0, -1, $conf->currency).' '.$langs->trans("TTC");
2949				$outval .= ' - '.price($objp->price_ttc, 0, $langs, 0, 0, -1, $conf->currency).' '.$langs->transnoentities("TTC");
2950			}
2951			$outprice_ht = price($objp->price);
2952			$outprice_ttc = price($objp->price_ttc);
2953			$outpricebasetype = $objp->price_base_type;
2954			$outtva_tx = $objp->tva_tx;
2955		}
2956
2957		if (!empty($conf->stock->enabled) && isset($objp->stock) && ($objp->fk_product_type == Product::TYPE_PRODUCT || !empty($conf->global->STOCK_SUPPORTS_SERVICES))) {
2958			if (!empty($user->rights->stock->lire)) {
2959				$opt .= ' - '.$langs->trans("Stock").': '.price(price2num($objp->stock, 'MS'));
2960
2961				if ($objp->stock > 0) {
2962					$outval .= ' - <span class="product_line_stock_ok">';
2963				} elseif ($objp->stock <= 0) {
2964					$outval .= ' - <span class="product_line_stock_too_low">';
2965				}
2966				$outval .= $langs->transnoentities("Stock").': '.price(price2num($objp->stock, 'MS'));
2967				$outval .= '</span>';
2968				if (empty($novirtualstock) && !empty($conf->global->STOCK_SHOW_VIRTUAL_STOCK_IN_PRODUCTS_COMBO)) {  // Warning, this option may slow down combo list generation
2969					$langs->load("stocks");
2970
2971					$tmpproduct = new Product($this->db);
2972					$tmpproduct->fetch($objp->rowid, '', '', '', 1, 1, 1); // Load product without lang and prices arrays (we just need to make ->virtual_stock() after)
2973					$tmpproduct->load_virtual_stock();
2974					$virtualstock = $tmpproduct->stock_theorique;
2975
2976					$opt .= ' - '.$langs->trans("VirtualStock").':'.$virtualstock;
2977
2978					$outval .= ' - '.$langs->transnoentities("VirtualStock").':';
2979					if ($virtualstock > 0) {
2980						$outval .= '<span class="product_line_stock_ok">';
2981					} elseif ($virtualstock <= 0) {
2982						$outval .= '<span class="product_line_stock_too_low">';
2983					}
2984					$outval .= $virtualstock;
2985					$outval .= '</span>';
2986
2987					unset($tmpproduct);
2988				}
2989			}
2990		}
2991
2992		$opt .= "</option>\n";
2993		$optJson = array(
2994			'key'=>$outkey,
2995			'value'=>$outref,
2996			'label'=>$outval,
2997			'label2'=>$outlabel,
2998			'desc'=>$outdesc,
2999			'type'=>$outtype,
3000			'price_ht'=>price2num($outprice_ht),
3001			'price_ttc'=>price2num($outprice_ttc),
3002			'pricebasetype'=>$outpricebasetype,
3003			'tva_tx'=>$outtva_tx, 'qty'=>$outqty,
3004			'discount'=>$outdiscount,
3005			'duration_value'=>$outdurationvalue,
3006			'duration_unit'=>$outdurationunit,
3007			'pbq'=>$outpbq,
3008			'labeltrans'=>$outlabel_translated,
3009			'desctrans'=>$outdesc_translated,
3010			'ref_customer'=>$outrefcust
3011		);
3012	}
3013
3014	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3015	/**
3016	 *	Return list of products for customer (in Ajax if Ajax activated or go to select_produits_fournisseurs_list)
3017	 *
3018	 *	@param	int		$socid			Id third party
3019	 *	@param  string	$selected       Preselected product
3020	 *	@param  string	$htmlname       Name of HTML Select
3021	 *  @param	string	$filtertype     Filter on product type (''=nofilter, 0=product, 1=service)
3022	 *	@param  string	$filtre			For a SQL filter
3023	 *	@param	array	$ajaxoptions	Options for ajax_autocompleter
3024	 *  @param	int		$hidelabel		Hide label (0=no, 1=yes)
3025	 *  @param  int     $alsoproductwithnosupplierprice    1=Add also product without supplier prices
3026	 *  @param	string	$morecss		More CSS
3027	 *  @param	string	$placeholder	Placeholder
3028	 *	@return	void
3029	 */
3030	public function select_produits_fournisseurs($socid, $selected = '', $htmlname = 'productid', $filtertype = '', $filtre = '', $ajaxoptions = array(), $hidelabel = 0, $alsoproductwithnosupplierprice = 0, $morecss = '', $placeholder = '')
3031	{
3032		// phpcs:enable
3033		global $langs, $conf;
3034		global $price_level, $status, $finished;
3035
3036		if (!isset($status)) {
3037			$status = 1;
3038		}
3039
3040		$selected_input_value = '';
3041		if (!empty($conf->use_javascript_ajax) && !empty($conf->global->PRODUIT_USE_SEARCH_TO_SELECT)) {
3042			if ($selected > 0) {
3043				require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
3044				$producttmpselect = new Product($this->db);
3045				$producttmpselect->fetch($selected);
3046				$selected_input_value = $producttmpselect->ref;
3047				unset($producttmpselect);
3048			}
3049
3050			// mode=2 means suppliers products
3051			$urloption = ($socid > 0 ? 'socid='.$socid.'&' : '').'htmlname='.$htmlname.'&outjson=1&price_level='.$price_level.'&type='.$filtertype.'&mode=2&status='.$status.'&finished='.$finished.'&alsoproductwithnosupplierprice='.$alsoproductwithnosupplierprice;
3052			print ajax_autocompleter($selected, $htmlname, DOL_URL_ROOT.'/product/ajax/products.php', $urloption, $conf->global->PRODUIT_USE_SEARCH_TO_SELECT, 0, $ajaxoptions);
3053			print ($hidelabel ? '' : $langs->trans("RefOrLabel").' : ').'<input type="text" class="minwidth300" name="search_'.$htmlname.'" id="search_'.$htmlname.'" value="'.$selected_input_value.'"'.($placeholder ? ' placeholder="'.$placeholder.'"' : '').'>';
3054		} else {
3055			print $this->select_produits_fournisseurs_list($socid, $selected, $htmlname, $filtertype, $filtre, '', $status, 0, 0, $alsoproductwithnosupplierprice, $morecss, 0, $placeholder);
3056		}
3057	}
3058
3059	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3060	/**
3061	 *	Return list of suppliers products
3062	 *
3063	 *	@param	int		$socid   			Id of supplier thirdparty (0 = no filter)
3064	 *	@param  int		$selected       	Product price pre-selected (must be 'id' in product_fournisseur_price or 'idprod_IDPROD')
3065	 *	@param  string	$htmlname       	Name of HTML select
3066	 *  @param	string	$filtertype     	Filter on product type (''=nofilter, 0=product, 1=service)
3067	 *	@param  string	$filtre         	Generic filter. Data must not come from user input.
3068	 *	@param  string	$filterkey      	Filter of produdts
3069	 *  @param  int		$statut         	-1=Return all products, 0=Products not on buy, 1=Products on buy
3070	 *  @param  int		$outputmode     	0=HTML select string, 1=Array
3071	 *  @param  int     $limit          	Limit of line number
3072	 *  @param  int     $alsoproductwithnosupplierprice    1=Add also product without supplier prices
3073	 *  @param	string	$morecss			Add more CSS
3074	 *  @param	int		$showstockinlist	Show stock information (slower).
3075	 *  @param	string	$placeholder		Placeholder
3076	 *  @return array           			Array of keys for json
3077	 */
3078	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 = '')
3079	{
3080		// phpcs:enable
3081		global $langs, $conf, $db, $user;
3082
3083		$out = '';
3084		$outarray = array();
3085
3086		$maxlengtharticle = (empty($conf->global->PRODUCT_MAX_LENGTH_COMBO) ? 48 : $conf->global->PRODUCT_MAX_LENGTH_COMBO);
3087
3088		$langs->load('stocks');
3089		// Units
3090		if (!empty($conf->global->PRODUCT_USE_UNITS)) {
3091			$langs->load('other');
3092		}
3093
3094		$sql = "SELECT p.rowid, p.ref, p.label, p.price, p.duration, p.fk_product_type, p.stock,";
3095		$sql .= " pfp.ref_fourn, pfp.rowid as idprodfournprice, pfp.price as fprice, pfp.quantity, pfp.remise_percent, pfp.remise, pfp.unitprice,";
3096		$sql .= " pfp.fk_supplier_price_expression, pfp.fk_product, pfp.tva_tx, pfp.fk_soc, s.nom as name,";
3097		$sql .= " pfp.supplier_reputation";
3098		// if we use supplier description of the products
3099		if (!empty($conf->global->PRODUIT_FOURN_TEXTS)) {
3100			$sql .= " ,pfp.desc_fourn as description";
3101		} else {
3102			$sql .= " ,p.description";
3103		}
3104		// Units
3105		if (!empty($conf->global->PRODUCT_USE_UNITS)) {
3106			$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";
3107		}
3108		if (!empty($conf->barcode->enabled)) {
3109			$sql .= ", pfp.barcode";
3110		}
3111		$sql .= " FROM ".MAIN_DB_PREFIX."product as p";
3112		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product_fournisseur_price as pfp ON ( p.rowid = pfp.fk_product AND pfp.entity IN (".getEntity('product').") )";
3113		if ($socid > 0) {
3114			$sql .= " AND pfp.fk_soc = ".((int) $socid);
3115		}
3116		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON pfp.fk_soc = s.rowid";
3117		// Units
3118		if (!empty($conf->global->PRODUCT_USE_UNITS)) {
3119			$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_units u ON u.rowid = p.fk_unit";
3120		}
3121		$sql .= " WHERE p.entity IN (".getEntity('product').")";
3122		if ($statut != -1) {
3123			$sql .= " AND p.tobuy = ".((int) $statut);
3124		}
3125		if (strval($filtertype) != '') {
3126			$sql .= " AND p.fk_product_type = ".((int) $filtertype);
3127		}
3128		if (!empty($filtre)) {
3129			$sql .= " ".$filtre;
3130		}
3131		// Add criteria on ref/label
3132		if ($filterkey != '') {
3133			$sql .= ' AND (';
3134			$prefix = empty($conf->global->PRODUCT_DONOTSEARCH_ANYWHERE) ? '%' : ''; // Can use index if PRODUCT_DONOTSEARCH_ANYWHERE is on
3135			// For natural search
3136			$scrit = explode(' ', $filterkey);
3137			$i = 0;
3138			if (count($scrit) > 1) {
3139				$sql .= "(";
3140			}
3141			foreach ($scrit as $crit) {
3142				if ($i > 0) {
3143					$sql .= " AND ";
3144				}
3145				$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)."%'";
3146				if (!empty($conf->global->PRODUIT_FOURN_TEXTS)) {
3147					$sql .= " OR pfp.desc_fourn LIKE '".$this->db->escape($prefix.$crit)."%'";
3148				}
3149				$sql .= ")";
3150				$i++;
3151			}
3152			if (count($scrit) > 1) {
3153				$sql .= ")";
3154			}
3155			if (!empty($conf->barcode->enabled)) {
3156				$sql .= " OR p.barcode LIKE '".$this->db->escape($prefix.$filterkey)."%'";
3157				$sql .= " OR pfp.barcode LIKE '".$this->db->escape($prefix.$filterkey)."%'";
3158			}
3159			$sql .= ')';
3160		}
3161		$sql .= " ORDER BY pfp.ref_fourn DESC, pfp.quantity ASC";
3162		$sql .= $this->db->plimit($limit, 0);
3163
3164		// Build output string
3165
3166		dol_syslog(get_class($this)."::select_produits_fournisseurs_list", LOG_DEBUG);
3167		$result = $this->db->query($sql);
3168		if ($result) {
3169			require_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
3170			require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
3171
3172			$num = $this->db->num_rows($result);
3173
3174			//$out.='<select class="flat" id="select'.$htmlname.'" name="'.$htmlname.'">';	// remove select to have id same with combo and ajax
3175			$out .= '<select class="flat '.($morecss ? ' '.$morecss : '').'" id="'.$htmlname.'" name="'.$htmlname.'">';
3176			if (!$selected) {
3177				$out .= '<option value="-1" selected>'.($placeholder ? $placeholder : '&nbsp;').'</option>';
3178			} else {
3179				$out .= '<option value="-1">'.($placeholder ? $placeholder : '&nbsp;').'</option>';
3180			}
3181
3182			$i = 0;
3183			while ($i < $num) {
3184				$objp = $this->db->fetch_object($result);
3185
3186				$outkey = $objp->idprodfournprice; // id in table of price
3187				if (!$outkey && $alsoproductwithnosupplierprice) {
3188					$outkey = 'idprod_'.$objp->rowid; // id of product
3189				}
3190
3191				$outref = $objp->ref;
3192				$outval = '';
3193				$outbarcode = $objp->barcode;
3194				$outqty = 1;
3195				$outdiscount = 0;
3196				$outtype = $objp->fk_product_type;
3197				$outdurationvalue = $outtype == Product::TYPE_SERVICE ?substr($objp->duration, 0, dol_strlen($objp->duration) - 1) : '';
3198				$outdurationunit = $outtype == Product::TYPE_SERVICE ?substr($objp->duration, -1) : '';
3199
3200				// Units
3201				$outvalUnits = '';
3202				if (!empty($conf->global->PRODUCT_USE_UNITS)) {
3203					if (!empty($objp->unit_short)) {
3204						$outvalUnits .= ' - '.$objp->unit_short;
3205					}
3206					if (!empty($objp->weight) && $objp->weight_units !== null) {
3207						$unitToShow = showDimensionInBestUnit($objp->weight, $objp->weight_units, 'weight', $langs);
3208						$outvalUnits .= ' - '.$unitToShow;
3209					}
3210					if ((!empty($objp->length) || !empty($objp->width) || !empty($objp->height)) && $objp->length_units !== null) {
3211						$unitToShow = $objp->length.' x '.$objp->width.' x '.$objp->height.' '.measuringUnitString(0, 'size', $objp->length_units);
3212						$outvalUnits .= ' - '.$unitToShow;
3213					}
3214					if (!empty($objp->surface) && $objp->surface_units !== null) {
3215						$unitToShow = showDimensionInBestUnit($objp->surface, $objp->surface_units, 'surface', $langs);
3216						$outvalUnits .= ' - '.$unitToShow;
3217					}
3218					if (!empty($objp->volume) && $objp->volume_units !== null) {
3219						$unitToShow = showDimensionInBestUnit($objp->volume, $objp->volume_units, 'volume', $langs);
3220						$outvalUnits .= ' - '.$unitToShow;
3221					}
3222					if ($outdurationvalue && $outdurationunit) {
3223						$da = array(
3224							'h' => $langs->trans('Hour'),
3225							'd' => $langs->trans('Day'),
3226							'w' => $langs->trans('Week'),
3227							'm' => $langs->trans('Month'),
3228							'y' => $langs->trans('Year')
3229						);
3230						if (isset($da[$outdurationunit])) {
3231							$outvalUnits .= ' - '.$outdurationvalue.' '.$langs->transnoentities($da[$outdurationunit].($outdurationvalue > 1 ? 's' : ''));
3232						}
3233					}
3234				}
3235
3236				$objRef = $objp->ref;
3237				if ($filterkey && $filterkey != '') {
3238					$objRef = preg_replace('/('.preg_quote($filterkey, '/').')/i', '<strong>$1</strong>', $objRef, 1);
3239				}
3240				$objRefFourn = $objp->ref_fourn;
3241				if ($filterkey && $filterkey != '') {
3242					$objRefFourn = preg_replace('/('.preg_quote($filterkey, '/').')/i', '<strong>$1</strong>', $objRefFourn, 1);
3243				}
3244				$label = $objp->label;
3245				if ($filterkey && $filterkey != '') {
3246					$label = preg_replace('/('.preg_quote($filterkey, '/').')/i', '<strong>$1</strong>', $label, 1);
3247				}
3248
3249				$optlabel = $objp->ref;
3250				if (!empty($objp->idprodfournprice) && ($objp->ref != $objp->ref_fourn)) {
3251					$optlabel .= ' <span class=\'opacitymedium\'>('.$objp->ref_fourn.')</span>';
3252				}
3253				if (!empty($conf->barcode->enabled) && !empty($objp->barcode)) {
3254					$optlabel .= ' ('.$outbarcode.')';
3255				}
3256				$optlabel .= ' - '.dol_trunc($label, $maxlengtharticle);
3257
3258				$outvallabel = $objRef;
3259				if (!empty($objp->idprodfournprice) && ($objp->ref != $objp->ref_fourn)) {
3260					$outvallabel .= ' ('.$objRefFourn.')';
3261				}
3262				if (!empty($conf->barcode->enabled) && !empty($objp->barcode)) {
3263					$outvallabel .= ' ('.$outbarcode.')';
3264				}
3265				$outvallabel .= ' - '.dol_trunc($label, $maxlengtharticle);
3266
3267				// Units
3268				$optlabel .= $outvalUnits;
3269				$outvallabel .= $outvalUnits;
3270
3271				if (!empty($objp->idprodfournprice)) {
3272					$outqty = $objp->quantity;
3273					$outdiscount = $objp->remise_percent;
3274					if (!empty($conf->dynamicprices->enabled) && !empty($objp->fk_supplier_price_expression)) {
3275						$prod_supplier = new ProductFournisseur($this->db);
3276						$prod_supplier->product_fourn_price_id = $objp->idprodfournprice;
3277						$prod_supplier->id = $objp->fk_product;
3278						$prod_supplier->fourn_qty = $objp->quantity;
3279						$prod_supplier->fourn_tva_tx = $objp->tva_tx;
3280						$prod_supplier->fk_supplier_price_expression = $objp->fk_supplier_price_expression;
3281						$priceparser = new PriceParser($this->db);
3282						$price_result = $priceparser->parseProductSupplier($prod_supplier);
3283						if ($price_result >= 0) {
3284							$objp->fprice = $price_result;
3285							if ($objp->quantity >= 1) {
3286								$objp->unitprice = $objp->fprice / $objp->quantity; // Replace dynamically unitprice
3287							}
3288						}
3289					}
3290					if ($objp->quantity == 1) {
3291						$optlabel .= ' - '.price($objp->fprice * (!empty($conf->global->DISPLAY_DISCOUNTED_SUPPLIER_PRICE) ? (1 - $objp->remise_percent / 100) : 1), 1, $langs, 0, 0, -1, $conf->currency)."/";
3292						$outvallabel .= ' - '.price($objp->fprice * (!empty($conf->global->DISPLAY_DISCOUNTED_SUPPLIER_PRICE) ? (1 - $objp->remise_percent / 100) : 1), 0, $langs, 0, 0, -1, $conf->currency)."/";
3293						$optlabel .= $langs->trans("Unit"); // Do not use strtolower because it breaks utf8 encoding
3294						$outvallabel .= $langs->transnoentities("Unit");
3295					} else {
3296						$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;
3297						$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;
3298						$optlabel .= ' '.$langs->trans("Units"); // Do not use strtolower because it breaks utf8 encoding
3299						$outvallabel .= ' '.$langs->transnoentities("Units");
3300					}
3301
3302					if ($objp->quantity > 1) {
3303						$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
3304						$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
3305					}
3306					if ($objp->remise_percent >= 1) {
3307						$optlabel .= " - ".$langs->trans("Discount")." : ".vatrate($objp->remise_percent).' %';
3308						$outvallabel .= " - ".$langs->transnoentities("Discount")." : ".vatrate($objp->remise_percent).' %';
3309					}
3310					if ($objp->duration) {
3311						$optlabel .= " - ".$objp->duration;
3312						$outvallabel .= " - ".$objp->duration;
3313					}
3314					if (!$socid) {
3315						$optlabel .= " - ".dol_trunc($objp->name, 8);
3316						$outvallabel .= " - ".dol_trunc($objp->name, 8);
3317					}
3318					if ($objp->supplier_reputation) {
3319						//TODO dictionary
3320						$reputations = array(''=>$langs->trans('Standard'), 'FAVORITE'=>$langs->trans('Favorite'), 'NOTTHGOOD'=>$langs->trans('NotTheGoodQualitySupplier'), 'DONOTORDER'=>$langs->trans('DoNotOrderThisProductToThisSupplier'));
3321
3322						$optlabel .= " - ".$reputations[$objp->supplier_reputation];
3323						$outvallabel .= " - ".$reputations[$objp->supplier_reputation];
3324					}
3325				} else {
3326					if (empty($alsoproductwithnosupplierprice)) {     // No supplier price defined for couple product/supplier
3327						$optlabel .= " - <span class='opacitymedium'>".$langs->trans("NoPriceDefinedForThisSupplier").'</span>';
3328						$outvallabel .= ' - '.$langs->transnoentities("NoPriceDefinedForThisSupplier");
3329					} else // No supplier price defined for product, even on other suppliers
3330					{
3331						$optlabel .= " - <span class='opacitymedium'>".$langs->trans("NoPriceDefinedForThisSupplier").'</span>';
3332						$outvallabel .= ' - '.$langs->transnoentities("NoPriceDefinedForThisSupplier");
3333					}
3334				}
3335
3336				if (!empty($conf->stock->enabled) && $showstockinlist && isset($objp->stock) && ($objp->fk_product_type == Product::TYPE_PRODUCT || !empty($conf->global->STOCK_SUPPORTS_SERVICES))) {
3337					$novirtualstock = ($showstockinlist == 2);
3338
3339					if (!empty($user->rights->stock->lire)) {
3340						$outvallabel .= ' - '.$langs->trans("Stock").': '.price(price2num($objp->stock, 'MS'));
3341
3342						if ($objp->stock > 0) {
3343							$optlabel .= ' - <span class="product_line_stock_ok">';
3344						} elseif ($objp->stock <= 0) {
3345							$optlabel .= ' - <span class="product_line_stock_too_low">';
3346						}
3347						$optlabel .= $langs->transnoentities("Stock").':'.price(price2num($objp->stock, 'MS'));
3348						$optlabel .= '</span>';
3349						if (empty($novirtualstock) && !empty($conf->global->STOCK_SHOW_VIRTUAL_STOCK_IN_PRODUCTS_COMBO)) {  // Warning, this option may slow down combo list generation
3350							$langs->load("stocks");
3351
3352							$tmpproduct = new Product($this->db);
3353							$tmpproduct->fetch($objp->rowid, '', '', '', 1, 1, 1); // Load product without lang and prices arrays (we just need to make ->virtual_stock() after)
3354							$tmpproduct->load_virtual_stock();
3355							$virtualstock = $tmpproduct->stock_theorique;
3356
3357							$outvallabel .= ' - '.$langs->trans("VirtualStock").':'.$virtualstock;
3358
3359							$optlabel .= ' - '.$langs->transnoentities("VirtualStock").':';
3360							if ($virtualstock > 0) {
3361								$optlabel .= '<span class="product_line_stock_ok">';
3362							} elseif ($virtualstock <= 0) {
3363								$optlabel .= '<span class="product_line_stock_too_low">';
3364							}
3365							$optlabel .= $virtualstock;
3366							$optlabel .= '</span>';
3367
3368							unset($tmpproduct);
3369						}
3370					}
3371				}
3372
3373				$opt = '<option value="'.$outkey.'"';
3374				if ($selected && $selected == $objp->idprodfournprice) {
3375					$opt .= ' selected';
3376				}
3377				if (empty($objp->idprodfournprice) && empty($alsoproductwithnosupplierprice)) {
3378					$opt .= ' disabled';
3379				}
3380				if (!empty($objp->idprodfournprice) && $objp->idprodfournprice > 0) {
3381					$opt .= ' data-qty="'.$objp->quantity.'" data-up="'.$objp->unitprice.'" data-discount="'.$outdiscount.'"';
3382				}
3383				$opt .= ' data-description="'.dol_escape_htmltag($objp->description, 0, 1).'"';
3384				$opt .= ' data-html="'.dol_escape_htmltag($optlabel).'"';
3385				$opt .= '>';
3386
3387				$opt .= $optlabel;
3388				$outval .= $outvallabel;
3389
3390				$opt .= "</option>\n";
3391
3392
3393				// Add new entry
3394				// "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
3395				// "label" value of json key array is used by jQuery automatically as text for combo box
3396				$out .= $opt;
3397				array_push(
3398					$outarray,
3399					array('key'=>$outkey,
3400						'value'=>$outref,
3401						'label'=>$outval,
3402						'qty'=>$outqty,
3403						'price_ht'=>price2num($objp->unitprice, 'MT'),
3404						'discount'=>$outdiscount,
3405						'type'=>$outtype,
3406						'duration_value'=>$outdurationvalue,
3407						'duration_unit'=>$outdurationunit,
3408						'disabled'=>(empty($objp->idprodfournprice) ? true : false),
3409						'description'=>$objp->description
3410					)
3411				);
3412				// Exemple of var_dump $outarray
3413				// array(1) {[0]=>array(6) {[key"]=>string(1) "2" ["value"]=>string(3) "ppp"
3414				//           ["label"]=>string(76) "ppp (<strong>f</strong>ff2) - ppp - 20,00 Euros/1unité (20,00 Euros/unité)"
3415				//      	 ["qty"]=>string(1) "1" ["discount"]=>string(1) "0" ["disabled"]=>bool(false)
3416				//}
3417				//var_dump($outval); var_dump(utf8_check($outval)); var_dump(json_encode($outval));
3418				//$outval=array('label'=>'ppp (<strong>f</strong>ff2) - ppp - 20,00 Euros/ Unité (20,00 Euros/unité)');
3419				//var_dump($outval); var_dump(utf8_check($outval)); var_dump(json_encode($outval));
3420
3421				$i++;
3422			}
3423			$out .= '</select>';
3424
3425			$this->db->free($result);
3426
3427			include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
3428			$out .= ajax_combobox($htmlname);
3429
3430			if (empty($outputmode)) {
3431				return $out;
3432			}
3433			return $outarray;
3434		} else {
3435			dol_print_error($this->db);
3436		}
3437	}
3438
3439	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3440	/**
3441	 *	Return list of suppliers prices for a product
3442	 *
3443	 *  @param	    int		$productid       	Id of product
3444	 *  @param      string	$htmlname        	Name of HTML field
3445	 *  @param      int		$selected_supplier  Pre-selected supplier if more than 1 result
3446	 *  @return	    string
3447	 */
3448	public function select_product_fourn_price($productid, $htmlname = 'productfournpriceid', $selected_supplier = '')
3449	{
3450		// phpcs:enable
3451		global $langs, $conf;
3452
3453		$langs->load('stocks');
3454
3455		$sql = "SELECT p.rowid, p.ref, p.label, p.price, p.duration, pfp.fk_soc,";
3456		$sql .= " pfp.ref_fourn, pfp.rowid as idprodfournprice, pfp.price as fprice, pfp.remise_percent, pfp.quantity, pfp.unitprice,";
3457		$sql .= " pfp.fk_supplier_price_expression, pfp.fk_product, pfp.tva_tx, s.nom as name";
3458		$sql .= " FROM ".MAIN_DB_PREFIX."product as p";
3459		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product_fournisseur_price as pfp ON p.rowid = pfp.fk_product";
3460		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON pfp.fk_soc = s.rowid";
3461		$sql .= " WHERE pfp.entity IN (".getEntity('productsupplierprice').")";
3462		$sql .= " AND p.tobuy = 1";
3463		$sql .= " AND s.fournisseur = 1";
3464		$sql .= " AND p.rowid = ".((int) $productid);
3465		$sql .= " ORDER BY s.nom, pfp.ref_fourn DESC";
3466
3467		dol_syslog(get_class($this)."::select_product_fourn_price", LOG_DEBUG);
3468		$result = $this->db->query($sql);
3469
3470		if ($result) {
3471			$num = $this->db->num_rows($result);
3472
3473			$form = '<select class="flat" id="select_'.$htmlname.'" name="'.$htmlname.'">';
3474
3475			if (!$num) {
3476				$form .= '<option value="0">-- '.$langs->trans("NoSupplierPriceDefinedForThisProduct").' --</option>';
3477			} else {
3478				require_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
3479				$form .= '<option value="0">&nbsp;</option>';
3480
3481				$i = 0;
3482				while ($i < $num) {
3483					$objp = $this->db->fetch_object($result);
3484
3485					$opt = '<option value="'.$objp->idprodfournprice.'"';
3486					//if there is only one supplier, preselect it
3487					if ($num == 1 || ($selected_supplier > 0 && $objp->fk_soc == $selected_supplier)) {
3488						$opt .= ' selected';
3489					}
3490					$opt .= '>'.$objp->name.' - '.$objp->ref_fourn.' - ';
3491
3492					if (!empty($conf->dynamicprices->enabled) && !empty($objp->fk_supplier_price_expression)) {
3493						$prod_supplier = new ProductFournisseur($this->db);
3494						$prod_supplier->product_fourn_price_id = $objp->idprodfournprice;
3495						$prod_supplier->id = $productid;
3496						$prod_supplier->fourn_qty = $objp->quantity;
3497						$prod_supplier->fourn_tva_tx = $objp->tva_tx;
3498						$prod_supplier->fk_supplier_price_expression = $objp->fk_supplier_price_expression;
3499						$priceparser = new PriceParser($this->db);
3500						$price_result = $priceparser->parseProductSupplier($prod_supplier);
3501						if ($price_result >= 0) {
3502							$objp->fprice = $price_result;
3503							if ($objp->quantity >= 1) {
3504								$objp->unitprice = $objp->fprice / $objp->quantity;
3505							}
3506						}
3507					}
3508					if ($objp->quantity == 1) {
3509						$opt .= price($objp->fprice * (!empty($conf->global->DISPLAY_DISCOUNTED_SUPPLIER_PRICE) ? (1 - $objp->remise_percent / 100) : 1), 1, $langs, 0, 0, -1, $conf->currency)."/";
3510					}
3511
3512					$opt .= $objp->quantity.' ';
3513
3514					if ($objp->quantity == 1) {
3515						$opt .= $langs->trans("Unit");
3516					} else {
3517						$opt .= $langs->trans("Units");
3518					}
3519					if ($objp->quantity > 1) {
3520						$opt .= " - ";
3521						$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");
3522					}
3523					if ($objp->duration) {
3524						$opt .= " - ".$objp->duration;
3525					}
3526					$opt .= "</option>\n";
3527
3528					$form .= $opt;
3529					$i++;
3530				}
3531			}
3532
3533			$form .= '</select>';
3534			$this->db->free($result);
3535			return $form;
3536		} else {
3537			dol_print_error($this->db);
3538		}
3539	}
3540
3541	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3542	/**
3543	 *    Return list of delivery address
3544	 *
3545	 *    @param    string	$selected          	Id contact pre-selectionn
3546	 *    @param    int		$socid				Id of company
3547	 *    @param    string	$htmlname          	Name of HTML field
3548	 *    @param    int		$showempty         	Add an empty field
3549	 *    @return	integer|null
3550	 */
3551	public function select_address($selected, $socid, $htmlname = 'address_id', $showempty = 0)
3552	{
3553		// phpcs:enable
3554		// looking for users
3555		$sql = "SELECT a.rowid, a.label";
3556		$sql .= " FROM ".MAIN_DB_PREFIX."societe_address as a";
3557		$sql .= " WHERE a.fk_soc = ".((int) $socid);
3558		$sql .= " ORDER BY a.label ASC";
3559
3560		dol_syslog(get_class($this)."::select_address", LOG_DEBUG);
3561		$resql = $this->db->query($sql);
3562		if ($resql) {
3563			print '<select class="flat" id="select_'.$htmlname.'" name="'.$htmlname.'">';
3564			if ($showempty) {
3565				print '<option value="0">&nbsp;</option>';
3566			}
3567			$num = $this->db->num_rows($resql);
3568			$i = 0;
3569			if ($num) {
3570				while ($i < $num) {
3571					$obj = $this->db->fetch_object($resql);
3572
3573					if ($selected && $selected == $obj->rowid) {
3574						print '<option value="'.$obj->rowid.'" selected>'.$obj->label.'</option>';
3575					} else {
3576						print '<option value="'.$obj->rowid.'">'.$obj->label.'</option>';
3577					}
3578					$i++;
3579				}
3580			}
3581			print '</select>';
3582			return $num;
3583		} else {
3584			dol_print_error($this->db);
3585		}
3586	}
3587
3588
3589	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3590	/**
3591	 *      Load into cache list of payment terms
3592	 *
3593	 *      @return     int             Nb of lines loaded, <0 if KO
3594	 */
3595	public function load_cache_conditions_paiements()
3596	{
3597		// phpcs:enable
3598		global $langs;
3599
3600		$num = count($this->cache_conditions_paiements);
3601		if ($num > 0) {
3602			return 0; // Cache already loaded
3603		}
3604
3605		dol_syslog(__METHOD__, LOG_DEBUG);
3606
3607		$sql = "SELECT rowid, code, libelle as label";
3608		$sql .= " FROM ".MAIN_DB_PREFIX.'c_payment_term';
3609		$sql .= " WHERE entity IN (".getEntity('c_payment_term').")";
3610		$sql .= " AND active > 0";
3611		$sql .= " ORDER BY sortorder";
3612
3613		$resql = $this->db->query($sql);
3614		if ($resql) {
3615			$num = $this->db->num_rows($resql);
3616			$i = 0;
3617			while ($i < $num) {
3618				$obj = $this->db->fetch_object($resql);
3619
3620				// Si traduction existe, on l'utilise, sinon on prend le libelle par defaut
3621				$label = ($langs->trans("PaymentConditionShort".$obj->code) != ("PaymentConditionShort".$obj->code) ? $langs->trans("PaymentConditionShort".$obj->code) : ($obj->label != '-' ? $obj->label : ''));
3622				$this->cache_conditions_paiements[$obj->rowid]['code'] = $obj->code;
3623				$this->cache_conditions_paiements[$obj->rowid]['label'] = $label;
3624				$i++;
3625			}
3626
3627			//$this->cache_conditions_paiements=dol_sort_array($this->cache_conditions_paiements, 'label', 'asc', 0, 0, 1);		// We use the field sortorder of table
3628
3629			return $num;
3630		} else {
3631			dol_print_error($this->db);
3632			return -1;
3633		}
3634	}
3635
3636	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3637	/**
3638	 *      Charge dans cache la liste des délais de livraison possibles
3639	 *
3640	 *      @return     int             Nb of lines loaded, <0 if KO
3641	 */
3642	public function load_cache_availability()
3643	{
3644		// phpcs:enable
3645		global $langs;
3646
3647		$num = count($this->cache_availability);
3648		if ($num > 0) {
3649			return 0; // Cache already loaded
3650		}
3651
3652		dol_syslog(__METHOD__, LOG_DEBUG);
3653
3654		$langs->load('propal');
3655
3656		$sql = "SELECT rowid, code, label, position";
3657		$sql .= " FROM ".MAIN_DB_PREFIX.'c_availability';
3658		$sql .= " WHERE active > 0";
3659
3660		$resql = $this->db->query($sql);
3661		if ($resql) {
3662			$num = $this->db->num_rows($resql);
3663			$i = 0;
3664			while ($i < $num) {
3665				$obj = $this->db->fetch_object($resql);
3666
3667				// Si traduction existe, on l'utilise, sinon on prend le libelle par defaut
3668				$label = ($langs->trans("AvailabilityType".$obj->code) != ("AvailabilityType".$obj->code) ? $langs->trans("AvailabilityType".$obj->code) : ($obj->label != '-' ? $obj->label : ''));
3669				$this->cache_availability[$obj->rowid]['code'] = $obj->code;
3670				$this->cache_availability[$obj->rowid]['label'] = $label;
3671				$this->cache_availability[$obj->rowid]['position'] = $obj->position;
3672				$i++;
3673			}
3674
3675			$this->cache_availability = dol_sort_array($this->cache_availability, 'position', 'asc', 0, 0, 1);
3676
3677			return $num;
3678		} else {
3679			dol_print_error($this->db);
3680			return -1;
3681		}
3682	}
3683
3684	/**
3685	 *      Retourne la liste des types de delais de livraison possibles
3686	 *
3687	 *      @param	int		$selected       Id du type de delais pre-selectionne
3688	 *      @param  string	$htmlname       Nom de la zone select
3689	 *      @param  string	$filtertype     To add a filter
3690	 *		@param	int		$addempty		Add empty entry
3691	 * 		@param	string	$morecss		More CSS
3692	 *		@return	void
3693	 */
3694	public function selectAvailabilityDelay($selected = '', $htmlname = 'availid', $filtertype = '', $addempty = 0, $morecss = '')
3695	{
3696		global $langs, $user;
3697
3698		$this->load_cache_availability();
3699
3700		dol_syslog(__METHOD__." selected=".$selected.", htmlname=".$htmlname, LOG_DEBUG);
3701
3702		print '<select id="'.$htmlname.'" class="flat'.($morecss ? ' '.$morecss : '').'" name="'.$htmlname.'">';
3703		if ($addempty) {
3704			print '<option value="0">&nbsp;</option>';
3705		}
3706		foreach ($this->cache_availability as $id => $arrayavailability) {
3707			if ($selected == $id) {
3708				print '<option value="'.$id.'" selected>';
3709			} else {
3710				print '<option value="'.$id.'">';
3711			}
3712			print dol_escape_htmltag($arrayavailability['label']);
3713			print '</option>';
3714		}
3715		print '</select>';
3716		if ($user->admin) {
3717			print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
3718		}
3719		print ajax_combobox($htmlname);
3720	}
3721
3722	/**
3723	 *      Load into cache cache_demand_reason, array of input reasons
3724	 *
3725	 *      @return     int             Nb of lines loaded, <0 if KO
3726	 */
3727	public function loadCacheInputReason()
3728	{
3729		global $langs;
3730
3731		$num = count($this->cache_demand_reason);
3732		if ($num > 0) {
3733			return 0; // Cache already loaded
3734		}
3735
3736		$sql = "SELECT rowid, code, label";
3737		$sql .= " FROM ".MAIN_DB_PREFIX.'c_input_reason';
3738		$sql .= " WHERE active > 0";
3739
3740		$resql = $this->db->query($sql);
3741		if ($resql) {
3742			$num = $this->db->num_rows($resql);
3743			$i = 0;
3744			$tmparray = array();
3745			while ($i < $num) {
3746				$obj = $this->db->fetch_object($resql);
3747
3748				// Si traduction existe, on l'utilise, sinon on prend le libelle par defaut
3749				$label = ($obj->label != '-' ? $obj->label : '');
3750				if ($langs->trans("DemandReasonType".$obj->code) != ("DemandReasonType".$obj->code)) {
3751					$label = $langs->trans("DemandReasonType".$obj->code); // So translation key DemandReasonTypeSRC_XXX will work
3752				}
3753				if ($langs->trans($obj->code) != $obj->code) {
3754					$label = $langs->trans($obj->code); // So translation key SRC_XXX will work
3755				}
3756
3757				$tmparray[$obj->rowid]['id']   = $obj->rowid;
3758				$tmparray[$obj->rowid]['code'] = $obj->code;
3759				$tmparray[$obj->rowid]['label'] = $label;
3760				$i++;
3761			}
3762
3763			$this->cache_demand_reason = dol_sort_array($tmparray, 'label', 'asc', 0, 0, 1);
3764
3765			unset($tmparray);
3766			return $num;
3767		} else {
3768			dol_print_error($this->db);
3769			return -1;
3770		}
3771	}
3772
3773	/**
3774	 *	Return list of input reason (events that triggered an object creation, like after sending an emailing, making an advert, ...)
3775	 *  List found into table c_input_reason loaded by loadCacheInputReason
3776	 *
3777	 *  @param	int		$selected        Id or code of type origin to select by default
3778	 *  @param  string	$htmlname        Nom de la zone select
3779	 *  @param  string	$exclude         To exclude a code value (Example: SRC_PROP)
3780	 *	@param	int		$addempty		 Add an empty entry
3781	 *  @param  string	$morecss		 Add more css to the HTML select component
3782	 *  @param	int		$notooltip		 Do not show the tooltip for admin
3783	 *	@return	void
3784	 */
3785	public function selectInputReason($selected = '', $htmlname = 'demandreasonid', $exclude = '', $addempty = 0, $morecss = '', $notooltip = 0)
3786	{
3787		global $langs, $user;
3788
3789		$this->loadCacheInputReason();
3790
3791		print '<select class="flat'.($morecss ? ' '.$morecss : '').'" id="select_'.$htmlname.'" name="'.$htmlname.'">';
3792		if ($addempty) {
3793			print '<option value="0"'.(empty($selected) ? ' selected' : '').'>&nbsp;</option>';
3794		}
3795		foreach ($this->cache_demand_reason as $id => $arraydemandreason) {
3796			if ($arraydemandreason['code'] == $exclude) {
3797				continue;
3798			}
3799
3800			if ($selected && ($selected == $arraydemandreason['id'] || $selected == $arraydemandreason['code'])) {
3801				print '<option value="'.$arraydemandreason['id'].'" selected>';
3802			} else {
3803				print '<option value="'.$arraydemandreason['id'].'">';
3804			}
3805			$label = $arraydemandreason['label']; // Translation of label was already done into the ->loadCacheInputReason
3806			print $langs->trans($label);
3807			print '</option>';
3808		}
3809		print '</select>';
3810		if ($user->admin && empty($notooltip)) {
3811			print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
3812		}
3813		print ajax_combobox('select_'.$htmlname);
3814	}
3815
3816	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3817	/**
3818	 *      Charge dans cache la liste des types de paiements possibles
3819	 *
3820	 *      @return     int                 Nb of lines loaded, <0 if KO
3821	 */
3822	public function load_cache_types_paiements()
3823	{
3824		// phpcs:enable
3825		global $langs;
3826
3827		$num = count($this->cache_types_paiements);
3828		if ($num > 0) {
3829			return $num; // Cache already loaded
3830		}
3831
3832		dol_syslog(__METHOD__, LOG_DEBUG);
3833
3834		$this->cache_types_paiements = array();
3835
3836		$sql = "SELECT id, code, libelle as label, type, active";
3837		$sql .= " FROM ".MAIN_DB_PREFIX."c_paiement";
3838		$sql .= " WHERE entity IN (".getEntity('c_paiement').")";
3839
3840		$resql = $this->db->query($sql);
3841		if ($resql) {
3842			$num = $this->db->num_rows($resql);
3843			$i = 0;
3844			while ($i < $num) {
3845				$obj = $this->db->fetch_object($resql);
3846
3847				// Si traduction existe, on l'utilise, sinon on prend le libelle par defaut
3848				$label = ($langs->transnoentitiesnoconv("PaymentTypeShort".$obj->code) != ("PaymentTypeShort".$obj->code) ? $langs->transnoentitiesnoconv("PaymentTypeShort".$obj->code) : ($obj->label != '-' ? $obj->label : ''));
3849				$this->cache_types_paiements[$obj->id]['id'] = $obj->id;
3850				$this->cache_types_paiements[$obj->id]['code'] = $obj->code;
3851				$this->cache_types_paiements[$obj->id]['label'] = $label;
3852				$this->cache_types_paiements[$obj->id]['type'] = $obj->type;
3853				$this->cache_types_paiements[$obj->id]['active'] = $obj->active;
3854				$i++;
3855			}
3856
3857			$this->cache_types_paiements = dol_sort_array($this->cache_types_paiements, 'label', 'asc', 0, 0, 1);
3858
3859			return $num;
3860		} else {
3861			dol_print_error($this->db);
3862			return -1;
3863		}
3864	}
3865
3866
3867	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3868	/**
3869	 *      Return list of payment modes.
3870	 *      Constant MAIN_DEFAULT_PAYMENT_TERM_ID can used to set default value but scope is all application, probably not what you want.
3871	 *      See instead to force the default value by the caller.
3872	 *
3873	 *      @param	int		$selected		Id of payment term to preselect by default
3874	 *      @param	string	$htmlname		Nom de la zone select
3875	 *      @param	int		$filtertype		Not used
3876	 *		@param	int		$addempty		Add an empty entry
3877	 * 		@param	int		$noinfoadmin		0=Add admin info, 1=Disable admin info
3878	 * 		@param	string	$morecss			Add more CSS on select tag
3879	 *		@return	void
3880	 */
3881	public function select_conditions_paiements($selected = 0, $htmlname = 'condid', $filtertype = -1, $addempty = 0, $noinfoadmin = 0, $morecss = '')
3882	{
3883		// phpcs:enable
3884		global $langs, $user, $conf;
3885
3886		dol_syslog(__METHOD__." selected=".$selected.", htmlname=".$htmlname, LOG_DEBUG);
3887
3888		$this->load_cache_conditions_paiements();
3889
3890		// Set default value if not already set by caller
3891		if (empty($selected) && !empty($conf->global->MAIN_DEFAULT_PAYMENT_TERM_ID)) {
3892			$selected = $conf->global->MAIN_DEFAULT_PAYMENT_TERM_ID;
3893		}
3894
3895		print '<select id="'.$htmlname.'" class="flat selectpaymentterms'.($morecss ? ' '.$morecss : '').'" name="'.$htmlname.'">';
3896		if ($addempty) {
3897			print '<option value="0">&nbsp;</option>';
3898		}
3899		foreach ($this->cache_conditions_paiements as $id => $arrayconditions) {
3900			if ($selected == $id) {
3901				print '<option value="'.$id.'" selected>';
3902			} else {
3903				print '<option value="'.$id.'">';
3904			}
3905			print $arrayconditions['label'];
3906			print '</option>';
3907		}
3908		print '</select>';
3909		if ($user->admin && empty($noinfoadmin)) {
3910			print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
3911		}
3912		print ajax_combobox($htmlname);
3913	}
3914
3915
3916	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3917	/**
3918	 *      Return list of payment methods
3919	 *      Constant MAIN_DEFAULT_PAYMENT_TYPE_ID can used to set default value but scope is all application, probably not what you want.
3920	 *
3921	 *      @param	string	$selected       Id or code or preselected payment mode
3922	 *      @param  string	$htmlname       Name of select field
3923	 *      @param  string	$filtertype     To filter on field type in llx_c_paiement ('CRDT' or 'DBIT' or array('code'=>xx,'label'=>zz))
3924	 *      @param  int		$format         0=id+label, 1=code+code, 2=code+label, 3=id+code
3925	 *      @param  int		$empty			1=can be empty, 0 otherwise
3926	 * 		@param	int		$noadmininfo	0=Add admin info, 1=Disable admin info
3927	 *      @param  int		$maxlength      Max length of label
3928	 *      @param  int     $active         Active or not, -1 = all
3929	 *      @param  string  $morecss        Add more CSS on select tag
3930	 * 		@return	void
3931	 */
3932	public function select_types_paiements($selected = '', $htmlname = 'paiementtype', $filtertype = '', $format = 0, $empty = 1, $noadmininfo = 0, $maxlength = 0, $active = 1, $morecss = '')
3933	{
3934		// phpcs:enable
3935		global $langs, $user, $conf;
3936
3937		dol_syslog(__METHOD__." ".$selected.", ".$htmlname.", ".$filtertype.", ".$format, LOG_DEBUG);
3938
3939		$filterarray = array();
3940		if ($filtertype == 'CRDT') {
3941			$filterarray = array(0, 2, 3);
3942		} elseif ($filtertype == 'DBIT') {
3943			$filterarray = array(1, 2, 3);
3944		} elseif ($filtertype != '' && $filtertype != '-1') {
3945			$filterarray = explode(',', $filtertype);
3946		}
3947
3948		$this->load_cache_types_paiements();
3949
3950		// Set default value if not already set by caller
3951		if (empty($selected) && !empty($conf->global->MAIN_DEFAULT_PAYMENT_TYPE_ID)) {
3952			$selected = $conf->global->MAIN_DEFAULT_PAYMENT_TYPE_ID;
3953		}
3954
3955		print '<select id="select'.$htmlname.'" class="flat selectpaymenttypes'.($morecss ? ' '.$morecss : '').'" name="'.$htmlname.'">';
3956		if ($empty) {
3957			print '<option value="">&nbsp;</option>';
3958		}
3959		foreach ($this->cache_types_paiements as $id => $arraytypes) {
3960			// If not good status
3961			if ($active >= 0 && $arraytypes['active'] != $active) {
3962				continue;
3963			}
3964
3965			// On passe si on a demande de filtrer sur des modes de paiments particuliers
3966			if (count($filterarray) && !in_array($arraytypes['type'], $filterarray)) {
3967				continue;
3968			}
3969
3970			// We discard empty line if showempty is on because an empty line has already been output.
3971			if ($empty && empty($arraytypes['code'])) {
3972				continue;
3973			}
3974
3975			if ($format == 0) {
3976				print '<option value="'.$id.'"';
3977			} elseif ($format == 1) {
3978				print '<option value="'.$arraytypes['code'].'"';
3979			} elseif ($format == 2) {
3980				print '<option value="'.$arraytypes['code'].'"';
3981			} elseif ($format == 3) {
3982				print '<option value="'.$id.'"';
3983			}
3984			// Print attribute selected or not
3985			if ($format == 1 || $format == 2) {
3986				if ($selected == $arraytypes['code']) {
3987					print ' selected';
3988				}
3989			} else {
3990				if ($selected == $id) {
3991					print ' selected';
3992				}
3993			}
3994			print '>';
3995			if ($format == 0) {
3996				$value = ($maxlength ?dol_trunc($arraytypes['label'], $maxlength) : $arraytypes['label']);
3997			} elseif ($format == 1) {
3998				$value = $arraytypes['code'];
3999			} elseif ($format == 2) {
4000				$value = ($maxlength ?dol_trunc($arraytypes['label'], $maxlength) : $arraytypes['label']);
4001			} elseif ($format == 3) {
4002				$value = $arraytypes['code'];
4003			}
4004			print $value ? $value : '&nbsp;';
4005			print '</option>';
4006		}
4007		print '</select>';
4008		if ($user->admin && !$noadmininfo) {
4009			print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
4010		}
4011		print ajax_combobox('select'.$htmlname);
4012	}
4013
4014
4015	/**
4016	 *  Selection HT or TTC
4017	 *
4018	 *  @param	string	$selected       Id pre-selectionne
4019	 *  @param  string	$htmlname       Nom de la zone select
4020	 *  @param	string	$addjscombo		Add js combo
4021	 * 	@return	string					Code of HTML select to chose tax or not
4022	 */
4023	public function selectPriceBaseType($selected = '', $htmlname = 'price_base_type', $addjscombo = 0)
4024	{
4025		global $langs;
4026
4027		$return = '<select class="flat maxwidth100" id="select_'.$htmlname.'" name="'.$htmlname.'">';
4028		$options = array(
4029			'HT'=>$langs->trans("HT"),
4030			'TTC'=>$langs->trans("TTC")
4031		);
4032		foreach ($options as $id => $value) {
4033			if ($selected == $id) {
4034				$return .= '<option value="'.$id.'" selected>'.$value;
4035			} else {
4036				$return .= '<option value="'.$id.'">'.$value;
4037			}
4038			$return .= '</option>';
4039		}
4040		$return .= '</select>';
4041		if ($addjscombo) {
4042			$return .= ajax_combobox('select_'.$htmlname);
4043		}
4044
4045		return $return;
4046	}
4047
4048	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4049	/**
4050	 *      Load in cache list of transport mode
4051	 *
4052	 *      @return     int                 Nb of lines loaded, <0 if KO
4053	 */
4054	public function load_cache_transport_mode()
4055	{
4056		// phpcs:enable
4057		global $langs;
4058
4059		$num = count($this->cache_transport_mode);
4060		if ($num > 0) {
4061			return $num; // Cache already loaded
4062		}
4063
4064		dol_syslog(__METHOD__, LOG_DEBUG);
4065
4066		$this->cache_transport_mode = array();
4067
4068		$sql = "SELECT rowid, code, label, active";
4069		$sql .= " FROM ".MAIN_DB_PREFIX."c_transport_mode";
4070		$sql .= " WHERE entity IN (".getEntity('c_transport_mode').")";
4071
4072		$resql = $this->db->query($sql);
4073		if ($resql) {
4074			$num = $this->db->num_rows($resql);
4075			$i = 0;
4076			while ($i < $num) {
4077				$obj = $this->db->fetch_object($resql);
4078
4079				// If traduction exist, we use it else we take the default label
4080				$label = ($langs->transnoentitiesnoconv("PaymentTypeShort".$obj->code) != ("PaymentTypeShort".$obj->code) ? $langs->transnoentitiesnoconv("PaymentTypeShort".$obj->code) : ($obj->label != '-' ? $obj->label : ''));
4081				$this->cache_transport_mode[$obj->rowid]['rowid'] = $obj->rowid;
4082				$this->cache_transport_mode[$obj->rowid]['code'] = $obj->code;
4083				$this->cache_transport_mode[$obj->rowid]['label'] = $label;
4084				$this->cache_transport_mode[$obj->rowid]['active'] = $obj->active;
4085				$i++;
4086			}
4087
4088			$this->cache_transport_mode = dol_sort_array($this->cache_transport_mode, 'label', 'asc', 0, 0, 1);
4089
4090			return $num;
4091		} else {
4092			dol_print_error($this->db);
4093			return -1;
4094		}
4095	}
4096
4097	/**
4098	 *      Return list of transport mode for intracomm report
4099	 *
4100	 *      @param	string	$selected       Id of the transport mode pre-selected
4101	 *      @param  string	$htmlname       Name of the select field
4102	 *      @param  int		$format         0=id+label, 1=code+code, 2=code+label, 3=id+code
4103	 *      @param  int		$empty			1=can be empty, 0 else
4104	 *      @param	int		$noadmininfo	0=Add admin info, 1=Disable admin info
4105	 *      @param  int		$maxlength      Max length of label
4106	 *      @param  int     $active         Active or not, -1 = all
4107	 *      @param  string  $morecss        Add more CSS on select tag
4108	 * 		@return	void
4109	 */
4110	public function selectTransportMode($selected = '', $htmlname = 'transportmode', $format = 0, $empty = 1, $noadmininfo = 0, $maxlength = 0, $active = 1, $morecss = '')
4111	{
4112		global $langs, $user;
4113
4114		dol_syslog(__METHOD__." ".$selected.", ".$htmlname.", ".$format, LOG_DEBUG);
4115
4116		$this->load_cache_transport_mode();
4117
4118		print '<select id="select'.$htmlname.'" class="flat selectmodetransport'.($morecss ? ' '.$morecss : '').'" name="'.$htmlname.'">';
4119		if ($empty) {
4120			print '<option value="">&nbsp;</option>';
4121		}
4122		foreach ($this->cache_transport_mode as $id => $arraytypes) {
4123			// If not good status
4124			if ($active >= 0 && $arraytypes['active'] != $active) {
4125				continue;
4126			}
4127
4128			// We discard empty line if showempty is on because an empty line has already been output.
4129			if ($empty && empty($arraytypes['code'])) {
4130				continue;
4131			}
4132
4133			if ($format == 0) {
4134				print '<option value="'.$id.'"';
4135			} elseif ($format == 1) {
4136				print '<option value="'.$arraytypes['code'].'"';
4137			} elseif ($format == 2) {
4138				print '<option value="'.$arraytypes['code'].'"';
4139			} elseif ($format == 3) {
4140				print '<option value="'.$id.'"';
4141			}
4142			// If text is selected, we compare with code, else with id
4143			if (preg_match('/[a-z]/i', $selected) && $selected == $arraytypes['code']) {
4144				print ' selected';
4145			} elseif ($selected == $id) {
4146				print ' selected';
4147			}
4148			print '>';
4149			if ($format == 0) {
4150				$value = ($maxlength ?dol_trunc($arraytypes['label'], $maxlength) : $arraytypes['label']);
4151			} elseif ($format == 1) {
4152				$value = $arraytypes['code'];
4153			} elseif ($format == 2) {
4154				$value = ($maxlength ?dol_trunc($arraytypes['label'], $maxlength) : $arraytypes['label']);
4155			} elseif ($format == 3) {
4156				$value = $arraytypes['code'];
4157			}
4158			print $value ? $value : '&nbsp;';
4159			print '</option>';
4160		}
4161		print '</select>';
4162		if ($user->admin && !$noadmininfo) {
4163			print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
4164		}
4165	}
4166
4167	/**
4168	 *  Return a HTML select list of shipping mode
4169	 *
4170	 *  @param	string	$selected           Id shipping mode pre-selected
4171	 *  @param  string	$htmlname           Name of select zone
4172	 *  @param  string	$filtre             To filter list. This parameter must not come from input of users
4173	 *  @param  int		$useempty           1=Add an empty value in list, 2=Add an empty value in list only if there is more than 2 entries.
4174	 *  @param  string	$moreattrib         To add more attribute on select
4175	 *	@param	int		$noinfoadmin		0=Add admin info, 1=Disable admin info
4176	 *  @param	string	$morecss			More CSS
4177	 * 	@return	void
4178	 */
4179	public function selectShippingMethod($selected = '', $htmlname = 'shipping_method_id', $filtre = '', $useempty = 0, $moreattrib = '', $noinfoadmin = 0, $morecss = '')
4180	{
4181		global $langs, $conf, $user;
4182
4183		$langs->load("admin");
4184		$langs->load("deliveries");
4185
4186		$sql = "SELECT rowid, code, libelle as label";
4187		$sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode";
4188		$sql .= " WHERE active > 0";
4189		if ($filtre) {
4190			$sql .= " AND ".$filtre;
4191		}
4192		$sql .= " ORDER BY libelle ASC";
4193
4194		dol_syslog(get_class($this)."::selectShippingMode", LOG_DEBUG);
4195		$result = $this->db->query($sql);
4196		if ($result) {
4197			$num = $this->db->num_rows($result);
4198			$i = 0;
4199			if ($num) {
4200				print '<select id="select'.$htmlname.'" class="flat selectshippingmethod'.($morecss ? ' '.$morecss : '').'" name="'.$htmlname.'"'.($moreattrib ? ' '.$moreattrib : '').'>';
4201				if ($useempty == 1 || ($useempty == 2 && $num > 1)) {
4202					print '<option value="-1">&nbsp;</option>';
4203				}
4204				while ($i < $num) {
4205					$obj = $this->db->fetch_object($result);
4206					if ($selected == $obj->rowid) {
4207						print '<option value="'.$obj->rowid.'" selected>';
4208					} else {
4209						print '<option value="'.$obj->rowid.'">';
4210					}
4211					print ($langs->trans("SendingMethod".strtoupper($obj->code)) != "SendingMethod".strtoupper($obj->code)) ? $langs->trans("SendingMethod".strtoupper($obj->code)) : $obj->label;
4212					print '</option>';
4213					$i++;
4214				}
4215				print "</select>";
4216				if ($user->admin  && empty($noinfoadmin)) {
4217					print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
4218				}
4219
4220				print ajax_combobox('select'.$htmlname);
4221			} else {
4222				print $langs->trans("NoShippingMethodDefined");
4223			}
4224		} else {
4225			dol_print_error($this->db);
4226		}
4227	}
4228
4229	/**
4230	 *    Display form to select shipping mode
4231	 *
4232	 *    @param	string	$page        Page
4233	 *    @param    int		$selected    Id of shipping mode
4234	 *    @param    string	$htmlname    Name of select html field
4235	 *    @param    int		$addempty    1=Add an empty value in list, 2=Add an empty value in list only if there is more than 2 entries.
4236	 *    @return	void
4237	 */
4238	public function formSelectShippingMethod($page, $selected = '', $htmlname = 'shipping_method_id', $addempty = 0)
4239	{
4240		global $langs, $db;
4241
4242		$langs->load("deliveries");
4243
4244		if ($htmlname != "none") {
4245			print '<form method="POST" action="'.$page.'">';
4246			print '<input type="hidden" name="action" value="setshippingmethod">';
4247			print '<input type="hidden" name="token" value="'.newToken().'">';
4248			$this->selectShippingMethod($selected, $htmlname, '', $addempty);
4249			print '<input type="submit" class="button valignmiddle" value="'.$langs->trans("Modify").'">';
4250			print '</form>';
4251		} else {
4252			if ($selected) {
4253				$code = $langs->getLabelFromKey($db, $selected, 'c_shipment_mode', 'rowid', 'code');
4254				print $langs->trans("SendingMethod".strtoupper($code));
4255			} else {
4256				print "&nbsp;";
4257			}
4258		}
4259	}
4260
4261	/**
4262	 * Creates HTML last in cycle situation invoices selector
4263	 *
4264	 * @param     string  $selected   		Preselected ID
4265	 * @param     int     $socid      		Company ID
4266	 *
4267	 * @return    string                     HTML select
4268	 */
4269	public function selectSituationInvoices($selected = '', $socid = 0)
4270	{
4271		global $langs;
4272
4273		$langs->load('bills');
4274
4275		$opt = '<option value ="" selected></option>';
4276		$sql = 'SELECT rowid, ref, situation_cycle_ref, situation_counter, situation_final, fk_soc';
4277		$sql .= ' FROM '.MAIN_DB_PREFIX.'facture';
4278		$sql .= ' WHERE entity IN ('.getEntity('invoice').')';
4279		$sql .= ' AND situation_counter >= 1';
4280		$sql .= ' AND fk_soc = '.(int) $socid;
4281		$sql .= ' AND type <> 2';
4282		$sql .= ' ORDER by situation_cycle_ref, situation_counter desc';
4283		$resql = $this->db->query($sql);
4284
4285		if ($resql && $this->db->num_rows($resql) > 0) {
4286			// Last seen cycle
4287			$ref = 0;
4288			while ($obj = $this->db->fetch_object($resql)) {
4289				//Same cycle ?
4290				if ($obj->situation_cycle_ref != $ref) {
4291					// Just seen this cycle
4292					$ref = $obj->situation_cycle_ref;
4293					//not final ?
4294					if ($obj->situation_final != 1) {
4295						//Not prov?
4296						if (substr($obj->ref, 1, 4) != 'PROV') {
4297							if ($selected == $obj->rowid) {
4298								$opt .= '<option value="'.$obj->rowid.'" selected>'.$obj->ref.'</option>';
4299							} else {
4300								$opt .= '<option value="'.$obj->rowid.'">'.$obj->ref.'</option>';
4301							}
4302						}
4303					}
4304				}
4305			}
4306		} else {
4307				dol_syslog("Error sql=".$sql.", error=".$this->error, LOG_ERR);
4308		}
4309		if ($opt == '<option value ="" selected></option>') {
4310			$opt = '<option value ="0" selected>'.$langs->trans('NoSituations').'</option>';
4311		}
4312		return $opt;
4313	}
4314
4315	/**
4316	 *      Creates HTML units selector (code => label)
4317	 *
4318	 *      @param	string	$selected       Preselected Unit ID
4319	 *      @param  string	$htmlname       Select name
4320	 *      @param	int		$showempty		Add a nempty line
4321	 *      @param  string  $unit_type      Restrict to one given unit type
4322	 * 		@return	string                  HTML select
4323	 */
4324	public function selectUnits($selected = '', $htmlname = 'units', $showempty = 0, $unit_type = '')
4325	{
4326		global $langs;
4327
4328		$langs->load('products');
4329
4330		$return = '<select class="flat" id="'.$htmlname.'" name="'.$htmlname.'">';
4331
4332		$sql = 'SELECT rowid, label, code from '.MAIN_DB_PREFIX.'c_units';
4333		$sql .= ' WHERE active > 0';
4334		if (!empty($unit_type)) {
4335			$sql .= " AND unit_type = '".$this->db->escape($unit_type)."'";
4336		}
4337
4338		$resql = $this->db->query($sql);
4339		if ($resql && $this->db->num_rows($resql) > 0) {
4340			if ($showempty) {
4341				$return .= '<option value="none"></option>';
4342			}
4343
4344			while ($res = $this->db->fetch_object($resql)) {
4345				$unitLabel = $res->label;
4346				if (!empty($langs->tab_translate['unit'.$res->code])) {	// check if Translation is available before
4347					$unitLabel = $langs->trans('unit'.$res->code) != $res->label ? $langs->trans('unit'.$res->code) : $res->label;
4348				}
4349
4350				if ($selected == $res->rowid) {
4351					$return .= '<option value="'.$res->rowid.'" selected>'.$unitLabel.'</option>';
4352				} else {
4353					$return .= '<option value="'.$res->rowid.'">'.$unitLabel.'</option>';
4354				}
4355			}
4356			$return .= '</select>';
4357		}
4358		return $return;
4359	}
4360
4361	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4362	/**
4363	 *  Return a HTML select list of bank accounts
4364	 *
4365	 *  @param	string	$selected           Id account pre-selected
4366	 *  @param  string	$htmlname           Name of select zone
4367	 *  @param  int		$status             Status of searched accounts (0=open, 1=closed, 2=both)
4368	 *  @param  string	$filtre             To filter list. This parameter must not come from input of users
4369	 *  @param  int		$useempty           1=Add an empty value in list, 2=Add an empty value in list only if there is more than 2 entries.
4370	 *  @param  string	$moreattrib         To add more attribute on select
4371	 *  @param	int		$showcurrency		Show currency in label
4372	 *  @param	string	$morecss			More CSS
4373	 *  @param	int		$nooutput			1=Return string, do not send to output
4374	 * 	@return	int							<0 if error, Num of bank account found if OK (0, 1, 2, ...)
4375	 */
4376	public function select_comptes($selected = '', $htmlname = 'accountid', $status = 0, $filtre = '', $useempty = 0, $moreattrib = '', $showcurrency = 0, $morecss = '', $nooutput = 0)
4377	{
4378		// phpcs:enable
4379		global $langs, $conf;
4380
4381		$out = '';
4382
4383		$langs->load("admin");
4384		$num = 0;
4385
4386		$sql = "SELECT rowid, label, bank, clos as status, currency_code";
4387		$sql .= " FROM ".MAIN_DB_PREFIX."bank_account";
4388		$sql .= " WHERE entity IN (".getEntity('bank_account').")";
4389		if ($status != 2) {
4390			$sql .= " AND clos = ".(int) $status;
4391		}
4392		if ($filtre) {
4393			$sql .= " AND ".$filtre;
4394		}
4395		$sql .= " ORDER BY label";
4396
4397		dol_syslog(get_class($this)."::select_comptes", LOG_DEBUG);
4398		$result = $this->db->query($sql);
4399		if ($result) {
4400			$num = $this->db->num_rows($result);
4401			$i = 0;
4402			if ($num) {
4403				$out .= '<select id="select'.$htmlname.'" class="flat selectbankaccount'.($morecss ? ' '.$morecss : '').'" name="'.$htmlname.'"'.($moreattrib ? ' '.$moreattrib : '').'>';
4404				if ($useempty == 1 || ($useempty == 2 && $num > 1)) {
4405					$out .= '<option value="-1">&nbsp;</option>';
4406				}
4407
4408				while ($i < $num) {
4409					$obj = $this->db->fetch_object($result);
4410					if ($selected == $obj->rowid || ($useempty == 2 && $num == 1 && empty($selected))) {
4411						$out .= '<option value="'.$obj->rowid.'" data-currency-code="'.$obj->currency_code.'" selected>';
4412					} else {
4413						$out .= '<option value="'.$obj->rowid.'" data-currency-code="'.$obj->currency_code.'">';
4414					}
4415					$out .= trim($obj->label);
4416					if ($showcurrency) {
4417						$out .= ' ('.$obj->currency_code.')';
4418					}
4419					if ($status == 2 && $obj->status == 1) {
4420						$out .= ' ('.$langs->trans("Closed").')';
4421					}
4422					$out .= '</option>';
4423					$i++;
4424				}
4425				$out .= "</select>";
4426				$out .= ajax_combobox('select'.$htmlname);
4427			} else {
4428				if ($status == 0) {
4429					$out .= '<span class="opacitymedium">'.$langs->trans("NoActiveBankAccountDefined").'</span>';
4430				} else {
4431					$out .= '<span class="opacitymedium">'.$langs->trans("NoBankAccountFound").'</span>';
4432				}
4433			}
4434		} else {
4435			dol_print_error($this->db);
4436		}
4437
4438		// Output or return
4439		if (empty($nooutput)) {
4440			print $out;
4441		} else {
4442			return $out;
4443		}
4444
4445		return $num;
4446	}
4447
4448	/**
4449	 *  Return a HTML select list of establishment
4450	 *
4451	 *  @param	string	$selected           Id establishment pre-selected
4452	 *  @param  string	$htmlname           Name of select zone
4453	 *  @param  int		$status             Status of searched establishment (0=open, 1=closed, 2=both)
4454	 *  @param  string	$filtre             To filter list. This parameter must not come from input of users
4455	 *  @param  int		$useempty           1=Add an empty value in list, 2=Add an empty value in list only if there is more than 2 entries.
4456	 *  @param  string	$moreattrib         To add more attribute on select
4457	 * 	@return	int							<0 if error, Num of establishment found if OK (0, 1, 2, ...)
4458	 */
4459	public function selectEstablishments($selected = '', $htmlname = 'entity', $status = 0, $filtre = '', $useempty = 0, $moreattrib = '')
4460	{
4461		// phpcs:enable
4462		global $langs, $conf;
4463
4464		$langs->load("admin");
4465		$num = 0;
4466
4467		$sql = "SELECT rowid, name, fk_country, status, entity";
4468		$sql .= " FROM ".MAIN_DB_PREFIX."establishment";
4469		$sql .= " WHERE 1=1";
4470		if ($status != 2) {
4471			$sql .= " AND status = ".(int) $status;
4472		}
4473		if ($filtre) {
4474			$sql .= " AND ".$filtre;
4475		}
4476		$sql .= " ORDER BY name";
4477
4478		dol_syslog(get_class($this)."::select_establishment", LOG_DEBUG);
4479		$result = $this->db->query($sql);
4480		if ($result) {
4481			$num = $this->db->num_rows($result);
4482			$i = 0;
4483			if ($num) {
4484				print '<select id="select'.$htmlname.'" class="flat selectestablishment" name="'.$htmlname.'"'.($moreattrib ? ' '.$moreattrib : '').'>';
4485				if ($useempty == 1 || ($useempty == 2 && $num > 1)) {
4486					print '<option value="-1">&nbsp;</option>';
4487				}
4488
4489				while ($i < $num) {
4490					$obj = $this->db->fetch_object($result);
4491					if ($selected == $obj->rowid) {
4492						print '<option value="'.$obj->rowid.'" selected>';
4493					} else {
4494						print '<option value="'.$obj->rowid.'">';
4495					}
4496					print trim($obj->name);
4497					if ($status == 2 && $obj->status == 1) {
4498						print ' ('.$langs->trans("Closed").')';
4499					}
4500					print '</option>';
4501					$i++;
4502				}
4503				print "</select>";
4504			} else {
4505				if ($status == 0) {
4506					print '<span class="opacitymedium">'.$langs->trans("NoActiveEstablishmentDefined").'</span>';
4507				} else {
4508					print '<span class="opacitymedium">'.$langs->trans("NoEstablishmentFound").'</span>';
4509				}
4510			}
4511		} else {
4512			dol_print_error($this->db);
4513		}
4514	}
4515
4516	/**
4517	 *    Display form to select bank account
4518	 *
4519	 *    @param	string	$page        Page
4520	 *    @param    int		$selected    Id of bank account
4521	 *    @param    string	$htmlname    Name of select html field
4522	 *    @param    int		$addempty    1=Add an empty value in list, 2=Add an empty value in list only if there is more than 2 entries.
4523	 *    @return	void
4524	 */
4525	public function formSelectAccount($page, $selected = '', $htmlname = 'fk_account', $addempty = 0)
4526	{
4527		global $langs;
4528		if ($htmlname != "none") {
4529			print '<form method="POST" action="'.$page.'">';
4530			print '<input type="hidden" name="action" value="setbankaccount">';
4531			print '<input type="hidden" name="token" value="'.newToken().'">';
4532			print img_picto('', 'bank_account', 'class="pictofixedwidth"');
4533			$nbaccountfound = $this->select_comptes($selected, $htmlname, 0, '', $addempty);
4534			if ($nbaccountfound > 0) {
4535				print '<input type="submit" class="button valignmiddle" value="'.$langs->trans("Modify").'">';
4536			}
4537			print '</form>';
4538		} else {
4539			$langs->load('banks');
4540
4541			if ($selected) {
4542				require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
4543				$bankstatic = new Account($this->db);
4544				$result = $bankstatic->fetch($selected);
4545				if ($result) {
4546					print $bankstatic->getNomUrl(1);
4547				}
4548			} else {
4549				print "&nbsp;";
4550			}
4551		}
4552	}
4553
4554	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4555	/**
4556	 *    Return list of categories having choosed type
4557	 *
4558	 *    @param	string|int	            $type				Type of category ('customer', 'supplier', 'contact', 'product', 'member'). Old mode (0, 1, 2, ...) is deprecated.
4559	 *    @param    string		            $selected    		Id of category preselected or 'auto' (autoselect category if there is only one element). Not used if $outputmode = 1.
4560	 *    @param    string		            $htmlname			HTML field name
4561	 *    @param    int			            $maxlength      	Maximum length for labels
4562	 *    @param    int|string|array    	$markafterid        Keep only or removed all categories including the leaf $markafterid in category tree (exclude) or Keep only of category is inside the leaf starting with this id.
4563	 *                                                          $markafterid can be an :
4564	 *                                                          - int (id of category)
4565	 *                                                          - string (categories ids seprated by comma)
4566	 *                                                          - array (list of categories ids)
4567	 *    @param	int			            $outputmode			0=HTML select string, 1=Array
4568	 *    @param	int			            $include			[=0] Removed or 1=Keep only
4569	 *    @param	string					$morecss			More CSS
4570	 *    @return	string
4571	 *    @see select_categories()
4572	 */
4573	public function select_all_categories($type, $selected = '', $htmlname = "parent", $maxlength = 64, $markafterid = 0, $outputmode = 0, $include = 0, $morecss = '')
4574	{
4575		// phpcs:enable
4576		global $conf, $langs;
4577		$langs->load("categories");
4578
4579		include_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
4580
4581		// For backward compatibility
4582		if (is_numeric($type)) {
4583			dol_syslog(__METHOD__.': using numeric value for parameter type is deprecated. Use string code instead.', LOG_WARNING);
4584		}
4585
4586		if ($type === Categorie::TYPE_BANK_LINE) {
4587			// TODO Move this into common category feature
4588			$cate_arbo = array();
4589			$sql = "SELECT c.label, c.rowid";
4590			$sql .= " FROM ".MAIN_DB_PREFIX."bank_categ as c";
4591			$sql .= " WHERE entity = ".$conf->entity;
4592			$sql .= " ORDER BY c.label";
4593			$result = $this->db->query($sql);
4594			if ($result) {
4595				$num = $this->db->num_rows($result);
4596				$i = 0;
4597				while ($i < $num) {
4598					$objp = $this->db->fetch_object($result);
4599					if ($objp) {
4600						$cate_arbo[$objp->rowid] = array('id'=>$objp->rowid, 'fulllabel'=>$objp->label);
4601					}
4602					$i++;
4603				}
4604				$this->db->free($result);
4605			} else {
4606				dol_print_error($this->db);
4607			}
4608		} else {
4609			$cat = new Categorie($this->db);
4610			$cate_arbo = $cat->get_full_arbo($type, $markafterid, $include);
4611		}
4612
4613		$output = '<select class="flat'.($morecss ? ' '.$morecss : '').'" name="'.$htmlname.'" id="'.$htmlname.'">';
4614		$outarray = array();
4615		if (is_array($cate_arbo)) {
4616			if (!count($cate_arbo)) {
4617				$output .= '<option value="-1" disabled>'.$langs->trans("NoCategoriesDefined").'</option>';
4618			} else {
4619				$output .= '<option value="-1">&nbsp;</option>';
4620				foreach ($cate_arbo as $key => $value) {
4621					if ($cate_arbo[$key]['id'] == $selected || ($selected === 'auto' && count($cate_arbo) == 1)) {
4622						$add = 'selected ';
4623					} else {
4624						$add = '';
4625					}
4626					$output .= '<option '.$add.'value="'.$cate_arbo[$key]['id'].'">'.dol_trunc($cate_arbo[$key]['fulllabel'], $maxlength, 'middle').'</option>';
4627
4628					$outarray[$cate_arbo[$key]['id']] = $cate_arbo[$key]['fulllabel'];
4629				}
4630			}
4631		}
4632		$output .= '</select>';
4633		$output .= "\n";
4634
4635		if ($outputmode) {
4636			return $outarray;
4637		}
4638		return $output;
4639	}
4640
4641	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4642	/**
4643	 *     Show a confirmation HTML form or AJAX popup
4644	 *
4645	 *     @param	string		$page        	   	Url of page to call if confirmation is OK
4646	 *     @param	string		$title       	   	Title
4647	 *     @param	string		$question    	   	Question
4648	 *     @param 	string		$action      	   	Action
4649	 *	   @param	array		$formquestion	   	An array with forms complementary inputs
4650	 * 	   @param	string		$selectedchoice		"" or "no" or "yes"
4651	 * 	   @param	int			$useajax		   	0=No, 1=Yes, 2=Yes but submit page with &confirm=no if choice is No, 'xxx'=preoutput confirm box with div id=dialog-confirm-xxx
4652	 *     @param	int			$height          	Force height of box
4653	 *     @param	int			$width				Force width of box
4654	 *     @return 	void
4655	 *     @deprecated
4656	 *     @see formconfirm()
4657	 */
4658	public function form_confirm($page, $title, $question, $action, $formquestion = '', $selectedchoice = "", $useajax = 0, $height = 170, $width = 500)
4659	{
4660		// phpcs:enable
4661		dol_syslog(__METHOD__.': using form_confirm is deprecated. Use formconfim instead.', LOG_WARNING);
4662		print $this->formconfirm($page, $title, $question, $action, $formquestion, $selectedchoice, $useajax, $height, $width);
4663	}
4664
4665	/**
4666	 *     Show a confirmation HTML form or AJAX popup.
4667	 *     Easiest way to use this is with useajax=1.
4668	 *     If you use useajax='xxx', you must also add jquery code to trigger opening of box (with correct parameters)
4669	 *     just after calling this method. For example:
4670	 *       print '<script type="text/javascript">'."\n";
4671	 *       print 'jQuery(document).ready(function() {'."\n";
4672	 *       print 'jQuery(".xxxlink").click(function(e) { jQuery("#aparamid").val(jQuery(this).attr("rel")); jQuery("#dialog-confirm-xxx").dialog("open"); return false; });'."\n";
4673	 *       print '});'."\n";
4674	 *       print '</script>'."\n";
4675	 *
4676	 *     @param  	string			$page        	   	Url of page to call if confirmation is OK. Can contains parameters (param 'action' and 'confirm' will be reformated)
4677	 *     @param	string			$title       	   	Title
4678	 *     @param	string			$question    	   	Question
4679	 *     @param 	string			$action      	   	Action
4680	 *	   @param  	array|string	$formquestion	   	An array with complementary inputs to add into forms: array(array('label'=> ,'type'=> , 'size'=>, 'morecss'=>, 'moreattr'=>))
4681	 *													type can be 'hidden', 'text', 'password', 'checkbox', 'radio', 'date', 'morecss', 'other' or 'onecolumn'...
4682	 * 	   @param  	string			$selectedchoice  	'' or 'no', or 'yes' or '1' or '0'
4683	 * 	   @param  	int|string		$useajax		   	0=No, 1=Yes, 2=Yes but submit page with &confirm=no if choice is No, 'xxx'=Yes and preoutput confirm box with div id=dialog-confirm-xxx
4684	 *     @param  	int|string		$height          	Force height of box (0 = auto)
4685	 *     @param	int				$width				Force width of box ('999' or '90%'). Ignored and forced to 90% on smartphones.
4686	 *     @param	int				$disableformtag		1=Disable form tag. Can be used if we are already inside a <form> section.
4687	 *     @return 	string      		    			HTML ajax code if a confirm ajax popup is required, Pure HTML code if it's an html form
4688	 */
4689	public function formconfirm($page, $title, $question, $action, $formquestion = '', $selectedchoice = '', $useajax = 0, $height = 0, $width = 500, $disableformtag = 0)
4690	{
4691		global $langs, $conf;
4692
4693		$more = '<!-- formconfirm before calling page='.dol_escape_htmltag($page).' -->';
4694		$formconfirm = '';
4695		$inputok = array();
4696		$inputko = array();
4697
4698		// Clean parameters
4699		$newselectedchoice = empty($selectedchoice) ? "no" : $selectedchoice;
4700		if ($conf->browser->layout == 'phone') {
4701			$width = '95%';
4702		}
4703
4704		// Set height automatically if not defined
4705		if (empty($height)) {
4706			$height = 220;
4707			if (is_array($formquestion) && count($formquestion) > 2) {
4708				$height += ((count($formquestion) - 2) * 24);
4709			}
4710		}
4711
4712		if (is_array($formquestion) && !empty($formquestion)) {
4713			// First add hidden fields and value
4714			foreach ($formquestion as $key => $input) {
4715				if (is_array($input) && !empty($input)) {
4716					if ($input['type'] == 'hidden') {
4717						$more .= '<input type="hidden" id="'.$input['name'].'" name="'.$input['name'].'" value="'.dol_escape_htmltag($input['value']).'">'."\n";
4718					}
4719				}
4720			}
4721
4722			// Now add questions
4723			$moreonecolumn = '';
4724			$more .= '<div class="tagtable paddingtopbottomonly centpercent noborderspacing">'."\n";
4725			foreach ($formquestion as $key => $input) {
4726				if (is_array($input) && !empty($input)) {
4727					$size = (!empty($input['size']) ? ' size="'.$input['size'].'"' : '');	// deprecated. Use morecss instead.
4728					$moreattr = (!empty($input['moreattr']) ? ' '.$input['moreattr'] : '');
4729					$morecss = (!empty($input['morecss']) ? ' '.$input['morecss'] : '');
4730
4731					if ($input['type'] == 'text') {
4732						$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="'.$input['name'].'" name="'.$input['name'].'"'.$size.' value="'.$input['value'].'"'.$moreattr.' /></div></div>'."\n";
4733					} elseif ($input['type'] == 'password')	{
4734						$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="'.$input['name'].'" name="'.$input['name'].'"'.$size.' value="'.$input['value'].'"'.$moreattr.' /></div></div>'."\n";
4735					} elseif ($input['type'] == 'select') {
4736						if (empty($morecss)) {
4737							$morecss = 'minwidth100';
4738						}
4739
4740						$show_empty = isset($input['select_show_empty']) ? $input['select_show_empty'] : 1;
4741						$key_in_label = isset($input['select_key_in_label']) ? $input['select_key_in_label'] : 0;
4742						$value_as_key = isset($input['select_value_as_key']) ? $input['select_value_as_key'] : 0;
4743						$translate = isset($input['select_translate']) ? $input['select_translate'] : 0;
4744						$maxlen = isset($input['select_maxlen']) ? $input['select_maxlen'] : 0;
4745						$disabled = isset($input['select_disabled']) ? $input['select_disabled'] : 0;
4746						$sort = isset($input['select_sort']) ? $input['select_sort'] : '';
4747
4748						$more .= '<div class="tagtr"><div class="tagtd'.(empty($input['tdclass']) ? '' : (' '.$input['tdclass'])).'">';
4749						if (!empty($input['label'])) {
4750							$more .= $input['label'].'</div><div class="tagtd left">';
4751						}
4752						$more .= $this->selectarray($input['name'], $input['values'], $input['default'], $show_empty, $key_in_label, $value_as_key, $moreattr, $translate, $maxlen, $disabled, $sort, $morecss);
4753						$more .= '</div></div>'."\n";
4754					} elseif ($input['type'] == 'checkbox') {
4755						$more .= '<div class="tagtr">';
4756						$more .= '<div class="tagtd'.(empty($input['tdclass']) ? '' : (' '.$input['tdclass'])).'">'.$input['label'].' </div><div class="tagtd">';
4757						$more .= '<input type="checkbox" class="flat'.$morecss.'" id="'.$input['name'].'" name="'.$input['name'].'"'.$moreattr;
4758						if (!is_bool($input['value']) && $input['value'] != 'false' && $input['value'] != '0') {
4759							$more .= ' checked';
4760						}
4761						if (is_bool($input['value']) && $input['value']) {
4762							$more .= ' checked';
4763						}
4764						if (isset($input['disabled'])) {
4765							$more .= ' disabled';
4766						}
4767						$more .= ' /></div>';
4768						$more .= '</div>'."\n";
4769					} elseif ($input['type'] == 'radio') {
4770						$i = 0;
4771						foreach ($input['values'] as $selkey => $selval) {
4772							$more .= '<div class="tagtr">';
4773							if ($i == 0) {
4774								$more .= '<div class="tagtd'.(empty($input['tdclass']) ? ' tdtop' : (' tdtop '.$input['tdclass'])).'">'.$input['label'].'</div>';
4775							} else {
4776								$more .= '<div clas="tagtd'.(empty($input['tdclass']) ? '' : (' "'.$input['tdclass'])).'">&nbsp;</div>';
4777							}
4778							$more .= '<div class="tagtd'.($i == 0 ? ' tdtop' : '').'"><input type="radio" class="flat'.$morecss.'" id="'.$input['name'].$selkey.'" name="'.$input['name'].'" value="'.$selkey.'"'.$moreattr;
4779							if ($input['disabled']) {
4780								$more .= ' disabled';
4781							}
4782							if (isset($input['default']) && $input['default'] === $selkey) {
4783								$more .= ' checked="checked"';
4784							}
4785							$more .= ' /> ';
4786							$more .= '<label for="'.$input['name'].$selkey.'">'.$selval.'</label>';
4787							$more .= '</div></div>'."\n";
4788							$i++;
4789						}
4790					} elseif ($input['type'] == 'date') {
4791						$more .= '<div class="tagtr"><div class="tagtd'.(empty($input['tdclass']) ? '' : (' '.$input['tdclass'])).'">'.$input['label'].'</div>';
4792						$more .= '<div class="tagtd">';
4793						$more .= $this->selectDate($input['value'], $input['name'], 0, 0, 0, '', 1, 0);
4794						$more .= '</div></div>'."\n";
4795						$formquestion[] = array('name'=>$input['name'].'day');
4796						$formquestion[] = array('name'=>$input['name'].'month');
4797						$formquestion[] = array('name'=>$input['name'].'year');
4798						$formquestion[] = array('name'=>$input['name'].'hour');
4799						$formquestion[] = array('name'=>$input['name'].'min');
4800					} elseif ($input['type'] == 'other') {
4801						$more .= '<div class="tagtr"><div class="tagtd'.(empty($input['tdclass']) ? '' : (' '.$input['tdclass'])).'">';
4802						if (!empty($input['label'])) {
4803							$more .= $input['label'].'</div><div class="tagtd">';
4804						}
4805						$more .= $input['value'];
4806						$more .= '</div></div>'."\n";
4807					} elseif ($input['type'] == 'onecolumn') {
4808						$moreonecolumn .= '<div class="margintoponly">';
4809						$moreonecolumn .= $input['value'];
4810						$moreonecolumn .= '</div>'."\n";
4811					} elseif ($input['type'] == 'hidden') {
4812						// Do nothing more, already added by a previous loop
4813					} else {
4814						$more .= 'Error type '.$input['type'].' for the confirm box is not a supported type';
4815					}
4816				}
4817			}
4818			$more .= '</div>'."\n";
4819			$more .= $moreonecolumn;
4820		}
4821
4822		// JQUI method dialog is broken with jmobile, we use standard HTML.
4823		// 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
4824		// See page product/card.php for example
4825		if (!empty($conf->dol_use_jmobile)) {
4826			$useajax = 0;
4827		}
4828		if (empty($conf->use_javascript_ajax)) {
4829			$useajax = 0;
4830		}
4831
4832		if ($useajax) {
4833			$autoOpen = true;
4834			$dialogconfirm = 'dialog-confirm';
4835			$button = '';
4836			if (!is_numeric($useajax)) {
4837				$button = $useajax;
4838				$useajax = 1;
4839				$autoOpen = false;
4840				$dialogconfirm .= '-'.$button;
4841			}
4842			$pageyes = $page.(preg_match('/\?/', $page) ? '&' : '?').'action='.$action.'&confirm=yes';
4843			$pageno = ($useajax == 2 ? $page.(preg_match('/\?/', $page) ? '&' : '?').'confirm=no' : '');
4844
4845			// Add input fields into list of fields to read during submit (inputok and inputko)
4846			if (is_array($formquestion)) {
4847				foreach ($formquestion as $key => $input) {
4848					//print "xx ".$key." rr ".is_array($input)."<br>\n";
4849					// Add name of fields to propagate with the GET when submitting the form with button OK.
4850					if (is_array($input) && isset($input['name'])) {
4851						if (strpos($input['name'], ',') > 0) {
4852							$inputok = array_merge($inputok, explode(',', $input['name']));
4853						} else {
4854							array_push($inputok, $input['name']);
4855						}
4856					}
4857					// Add name of fields to propagate with the GET when submitting the form with button KO.
4858					if (isset($input['inputko']) && $input['inputko'] == 1) {
4859						array_push($inputko, $input['name']);
4860					}
4861				}
4862			}
4863
4864			// Show JQuery confirm box.
4865			$formconfirm .= '<div id="'.$dialogconfirm.'" title="'.dol_escape_htmltag($title).'" style="display: none;">';
4866			if (is_array($formquestion) && !empty($formquestion['text'])) {
4867				$formconfirm .= '<div class="confirmtext">'.$formquestion['text'].'</div>'."\n";
4868			}
4869			if (!empty($more)) {
4870				$formconfirm .= '<div class="confirmquestions">'.$more.'</div>'."\n";
4871			}
4872			$formconfirm .= ($question ? '<div class="confirmmessage">'.img_help('', '').' '.$question.'</div>' : '');
4873			$formconfirm .= '</div>'."\n";
4874
4875			$formconfirm .= "\n<!-- begin ajax formconfirm page=".$page." -->\n";
4876			$formconfirm .= '<script type="text/javascript">'."\n";
4877			$formconfirm .= 'jQuery(document).ready(function() {
4878            $(function() {
4879            	$( "#'.$dialogconfirm.'" ).dialog(
4880            	{
4881                    autoOpen: '.($autoOpen ? "true" : "false").',';
4882			if ($newselectedchoice == 'no') {
4883				$formconfirm .= '
4884						open: function() {
4885            				$(this).parent().find("button.ui-button:eq(2)").focus();
4886						},';
4887			}
4888			$formconfirm .= '
4889                    resizable: false,
4890                    height: "'.$height.'",
4891                    width: "'.$width.'",
4892                    modal: true,
4893                    closeOnEscape: false,
4894                    buttons: {
4895                        "'.dol_escape_js($langs->transnoentities("Yes")).'": function() {
4896                        	var options = "&token='.urlencode(newToken()).'";
4897                        	var inputok = '.json_encode($inputok).';	/* List of fields into form */
4898                         	var pageyes = "'.dol_escape_js(!empty($pageyes) ? $pageyes : '').'";
4899                         	if (inputok.length>0) {
4900                         		$.each(inputok, function(i, inputname) {
4901                         			var more = "";
4902									var inputvalue;
4903                         			if ($("input[name=\'" + inputname + "\']").attr("type") == "radio") {
4904										inputvalue = $("input[name=\'" + inputname + "\']:checked").val();
4905									} else {
4906                         		    	if ($("#" + inputname).attr("type") == "checkbox") { more = ":checked"; }
4907                         				inputvalue = $("#" + inputname + more).val();
4908									}
4909                         			if (typeof inputvalue == "undefined") { inputvalue=""; }
4910									console.log("check inputname="+inputname+" inputvalue="+inputvalue);
4911                         			options += "&" + inputname + "=" + encodeURIComponent(inputvalue);
4912                         		});
4913                         	}
4914                         	var urljump = pageyes + (pageyes.indexOf("?") < 0 ? "?" : "") + options;
4915            				if (pageyes.length > 0) { location.href = urljump; }
4916                            $(this).dialog("close");
4917                        },
4918                        "'.dol_escape_js($langs->transnoentities("No")).'": function() {
4919                        	var options = "&token='.urlencode(newToken()).'";
4920                         	var inputko = '.json_encode($inputko).';	/* List of fields into form */
4921                         	var pageno="'.dol_escape_js(!empty($pageno) ? $pageno : '').'";
4922                         	if (inputko.length>0) {
4923                         		$.each(inputko, function(i, inputname) {
4924                         			var more = "";
4925                         			if ($("#" + inputname).attr("type") == "checkbox") { more = ":checked"; }
4926                         			var inputvalue = $("#" + inputname + more).val();
4927                         			if (typeof inputvalue == "undefined") { inputvalue=""; }
4928                         			options += "&" + inputname + "=" + encodeURIComponent(inputvalue);
4929                         		});
4930                         	}
4931                         	var urljump=pageno + (pageno.indexOf("?") < 0 ? "?" : "") + options;
4932                         	//alert(urljump);
4933            				if (pageno.length > 0) { location.href = urljump; }
4934                            $(this).dialog("close");
4935                        }
4936                    }
4937                }
4938                );
4939
4940            	var button = "'.$button.'";
4941            	if (button.length > 0) {
4942                	$( "#" + button ).click(function() {
4943                		$("#'.$dialogconfirm.'").dialog("open");
4944        			});
4945                }
4946            });
4947            });
4948            </script>';
4949			$formconfirm .= "<!-- end ajax formconfirm -->\n";
4950		} else {
4951			$formconfirm .= "\n<!-- begin formconfirm page=".dol_escape_htmltag($page)." -->\n";
4952
4953			if (empty($disableformtag)) {
4954				$formconfirm .= '<form method="POST" action="'.$page.'" class="notoptoleftroright">'."\n";
4955			}
4956
4957			$formconfirm .= '<input type="hidden" name="action" value="'.$action.'">'."\n";
4958			$formconfirm .= '<input type="hidden" name="token" value="'.newToken().'">'."\n";
4959
4960			$formconfirm .= '<table class="valid centpercent">'."\n";
4961
4962			// Line title
4963			$formconfirm .= '<tr class="validtitre"><td class="validtitre" colspan="2">';
4964			$formconfirm .= img_picto('', 'recent').' '.$title;
4965			$formconfirm .= '</td></tr>'."\n";
4966
4967			// Line text
4968			if (is_array($formquestion) && !empty($formquestion['text'])) {
4969				$formconfirm .= '<tr class="valid"><td class="valid" colspan="2">'.$formquestion['text'].'</td></tr>'."\n";
4970			}
4971
4972			// Line form fields
4973			if ($more) {
4974				$formconfirm .= '<tr class="valid"><td class="valid" colspan="2">'."\n";
4975				$formconfirm .= $more;
4976				$formconfirm .= '</td></tr>'."\n";
4977			}
4978
4979			// Line with question
4980			$formconfirm .= '<tr class="valid">';
4981			$formconfirm .= '<td class="valid">'.$question.'</td>';
4982			$formconfirm .= '<td class="valid center">';
4983			$formconfirm .= $this->selectyesno("confirm", $newselectedchoice, 0, false, 0, 0, 'marginleftonly marginrightonly');
4984			$formconfirm .= '<input class="button valignmiddle confirmvalidatebutton" type="submit" value="'.$langs->trans("Validate").'">';
4985			$formconfirm .= '</td>';
4986			$formconfirm .= '</tr>'."\n";
4987
4988			$formconfirm .= '</table>'."\n";
4989
4990			if (empty($disableformtag)) {
4991				$formconfirm .= "</form>\n";
4992			}
4993			$formconfirm .= '<br>';
4994
4995			if (empty($conf->use_javascript_ajax)) {
4996				$formconfirm .= '<!-- code to disable button to avoid double clic -->';
4997				$formconfirm .= '<script type="text/javascript">'."\n";
4998				$formconfirm .= '
4999				$(document).ready(function () {
5000					$(".confirmvalidatebutton").on("click", function() {
5001						console.log("We click on button");
5002						$(this).attr("disabled", "disabled");
5003						setTimeout(\'$(".confirmvalidatebutton").removeAttr("disabled")\', 3000);
5004						//console.log($(this).closest("form"));
5005						$(this).closest("form").submit();
5006					});
5007				});
5008				';
5009				$formconfirm .= '</script>'."\n";
5010			}
5011
5012			$formconfirm .= "<!-- end formconfirm -->\n";
5013		}
5014
5015		return $formconfirm;
5016	}
5017
5018
5019	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5020	/**
5021	 *    Show a form to select a project
5022	 *
5023	 *    @param	int		$page        		Page
5024	 *    @param	int		$socid       		Id third party (-1=all, 0=only projects not linked to a third party, id=projects not linked or linked to third party id)
5025	 *    @param    int		$selected    		Id pre-selected project
5026	 *    @param    string	$htmlname    		Name of select field
5027	 *    @param	int		$discard_closed		Discard closed projects (0=Keep,1=hide completely except $selected,2=Disable)
5028	 *    @param	int		$maxlength			Max length
5029	 *    @param	int		$forcefocus			Force focus on field (works with javascript only)
5030	 *    @param    int     $nooutput           No print is done. String is returned.
5031	 *    @return	string                      Return html content
5032	 */
5033	public function form_project($page, $socid, $selected = '', $htmlname = 'projectid', $discard_closed = 0, $maxlength = 20, $forcefocus = 0, $nooutput = 0)
5034	{
5035		// phpcs:enable
5036		global $langs;
5037
5038		require_once DOL_DOCUMENT_ROOT.'/core/lib/project.lib.php';
5039		require_once DOL_DOCUMENT_ROOT.'/core/class/html.formprojet.class.php';
5040
5041		$out = '';
5042
5043		$formproject = new FormProjets($this->db);
5044
5045		$langs->load("project");
5046		if ($htmlname != "none") {
5047			$out .= "\n";
5048			$out .= '<form method="post" action="'.$page.'">';
5049			$out .= '<input type="hidden" name="action" value="classin">';
5050			$out .= '<input type="hidden" name="token" value="'.newToken().'">';
5051			$out .= $formproject->select_projects($socid, $selected, $htmlname, $maxlength, 0, 1, $discard_closed, $forcefocus, 0, 0, '', 1);
5052			$out .= '<input type="submit" class="button smallpaddingimp" value="'.$langs->trans("Modify").'">';
5053			$out .= '</form>';
5054		} else {
5055			if ($selected) {
5056				$projet = new Project($this->db);
5057				$projet->fetch($selected);
5058				//print '<a href="'.DOL_URL_ROOT.'/projet/card.php?id='.$selected.'">'.$projet->title.'</a>';
5059				$out .= $projet->getNomUrl(0, '', 1);
5060			} else {
5061				$out .= "&nbsp;";
5062			}
5063		}
5064
5065		if (empty($nooutput)) {
5066			print $out;
5067			return '';
5068		}
5069		return $out;
5070	}
5071
5072	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5073	/**
5074	 *	Show a form to select payment conditions
5075	 *
5076	 *  @param	int		$page        	Page
5077	 *  @param  string	$selected    	Id condition pre-selectionne
5078	 *  @param  string	$htmlname    	Name of select html field
5079	 *	@param	int		$addempty		Add empty entry
5080	 *  @return	void
5081	 */
5082	public function form_conditions_reglement($page, $selected = '', $htmlname = 'cond_reglement_id', $addempty = 0)
5083	{
5084		// phpcs:enable
5085		global $langs;
5086		if ($htmlname != "none") {
5087			print '<form method="post" action="'.$page.'">';
5088			print '<input type="hidden" name="action" value="setconditions">';
5089			print '<input type="hidden" name="token" value="'.newToken().'">';
5090			$this->select_conditions_paiements($selected, $htmlname, -1, $addempty);
5091			print '<input type="submit" class="button valignmiddle smallpaddingimp" value="'.$langs->trans("Modify").'">';
5092			print '</form>';
5093		} else {
5094			if ($selected) {
5095				$this->load_cache_conditions_paiements();
5096				if (isset($this->cache_conditions_paiements[$selected])) {
5097					print $this->cache_conditions_paiements[$selected]['label'];
5098				} else {
5099					$langs->load('errors');
5100					print $langs->trans('ErrorNotInDictionaryPaymentConditions');
5101				}
5102			} else {
5103				print "&nbsp;";
5104			}
5105		}
5106	}
5107
5108	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5109	/**
5110	 *  Show a form to select a delivery delay
5111	 *
5112	 *  @param  int		$page        	Page
5113	 *  @param  string	$selected    	Id condition pre-selectionne
5114	 *  @param  string	$htmlname    	Name of select html field
5115	 *	@param	int		$addempty		Ajoute entree vide
5116	 *  @return	void
5117	 */
5118	public function form_availability($page, $selected = '', $htmlname = 'availability', $addempty = 0)
5119	{
5120		// phpcs:enable
5121		global $langs;
5122		if ($htmlname != "none") {
5123			print '<form method="post" action="'.$page.'">';
5124			print '<input type="hidden" name="action" value="setavailability">';
5125			print '<input type="hidden" name="token" value="'.newToken().'">';
5126			$this->selectAvailabilityDelay($selected, $htmlname, -1, $addempty);
5127			print '<input type="submit" class="button smallpaddingimp" value="'.$langs->trans("Modify").'">';
5128			print '</form>';
5129		} else {
5130			if ($selected) {
5131				$this->load_cache_availability();
5132				print $this->cache_availability[$selected]['label'];
5133			} else {
5134				print "&nbsp;";
5135			}
5136		}
5137	}
5138
5139	/**
5140	 *  Output HTML form to select list of input reason (events that triggered an object creation, like after sending an emailing, making an advert, ...)
5141	 *  List found into table c_input_reason loaded by loadCacheInputReason
5142	 *
5143	 *  @param  string	$page        	Page
5144	 *  @param  string	$selected    	Id condition pre-selectionne
5145	 *  @param  string	$htmlname    	Name of select html field
5146	 *  @param	int		$addempty		Add empty entry
5147	 *  @return	void
5148	 */
5149	public function formInputReason($page, $selected = '', $htmlname = 'demandreason', $addempty = 0)
5150	{
5151		global $langs;
5152		if ($htmlname != "none") {
5153			print '<form method="post" action="'.$page.'">';
5154			print '<input type="hidden" name="action" value="setdemandreason">';
5155			print '<input type="hidden" name="token" value="'.newToken().'">';
5156			$this->selectInputReason($selected, $htmlname, -1, $addempty);
5157			print '<input type="submit" class="button smallpaddingimp" value="'.$langs->trans("Modify").'">';
5158			print '</form>';
5159		} else {
5160			if ($selected) {
5161				$this->loadCacheInputReason();
5162				foreach ($this->cache_demand_reason as $key => $val) {
5163					if ($val['id'] == $selected) {
5164						print $val['label'];
5165						break;
5166					}
5167				}
5168			} else {
5169				print "&nbsp;";
5170			}
5171		}
5172	}
5173
5174	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5175	/**
5176	 *    Show a form + html select a date
5177	 *
5178	 *    @param	string		$page        	Page
5179	 *    @param	string		$selected    	Date preselected
5180	 *    @param    string		$htmlname    	Html name of date input fields or 'none'
5181	 *    @param    int			$displayhour 	Display hour selector
5182	 *    @param    int			$displaymin		Display minutes selector
5183	 *    @param	int			$nooutput		1=No print output, return string
5184	 *    @return	string
5185	 *    @see		selectDate()
5186	 */
5187	public function form_date($page, $selected, $htmlname, $displayhour = 0, $displaymin = 0, $nooutput = 0)
5188	{
5189		// phpcs:enable
5190		global $langs;
5191
5192		$ret = '';
5193
5194		if ($htmlname != "none") {
5195			$ret .= '<form method="post" action="'.$page.'" name="form'.$htmlname.'">';
5196			$ret .= '<input type="hidden" name="action" value="set'.$htmlname.'">';
5197			$ret .= '<input type="hidden" name="token" value="'.newToken().'">';
5198			$ret .= '<table class="nobordernopadding">';
5199			$ret .= '<tr><td>';
5200			$ret .= $this->selectDate($selected, $htmlname, $displayhour, $displaymin, 1, 'form'.$htmlname, 1, 0);
5201			$ret .= '</td>';
5202			$ret .= '<td class="left"><input type="submit" class="button smallpaddingimp" value="'.$langs->trans("Modify").'"></td>';
5203			$ret .= '</tr></table></form>';
5204		} else {
5205			if ($displayhour) {
5206				$ret .= dol_print_date($selected, 'dayhour');
5207			} else {
5208				$ret .= dol_print_date($selected, 'day');
5209			}
5210		}
5211
5212		if (empty($nooutput)) {
5213			print $ret;
5214		}
5215		return $ret;
5216	}
5217
5218
5219	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5220	/**
5221	 *  Show a select form to choose a user
5222	 *
5223	 *  @param	string	$page        	Page
5224	 *  @param  string	$selected    	Id of user preselected
5225	 *  @param  string	$htmlname    	Name of input html field. If 'none', we just output the user link.
5226	 *  @param  array	$exclude		List of users id to exclude
5227	 *  @param  array	$include        List of users id to include
5228	 *  @return	void
5229	 */
5230	public function form_users($page, $selected = '', $htmlname = 'userid', $exclude = '', $include = '')
5231	{
5232		// phpcs:enable
5233		global $langs;
5234
5235		if ($htmlname != "none") {
5236			print '<form method="POST" action="'.$page.'" name="form'.$htmlname.'">';
5237			print '<input type="hidden" name="action" value="set'.$htmlname.'">';
5238			print '<input type="hidden" name="token" value="'.newToken().'">';
5239			print $this->select_dolusers($selected, $htmlname, 1, $exclude, 0, $include);
5240			print '<input type="submit" class="button smallpaddingimp valignmiddle" value="'.$langs->trans("Modify").'">';
5241			print '</form>';
5242		} else {
5243			if ($selected) {
5244				require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php';
5245				$theuser = new User($this->db);
5246				$theuser->fetch($selected);
5247				print $theuser->getNomUrl(1);
5248			} else {
5249				print "&nbsp;";
5250			}
5251		}
5252	}
5253
5254
5255	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5256	/**
5257	 *    Show form with payment mode
5258	 *
5259	 *    @param	string	$page        	Page
5260	 *    @param    int		$selected    	Id mode pre-selectionne
5261	 *    @param    string	$htmlname    	Name of select html field
5262	 *    @param  	string	$filtertype		To filter on field type in llx_c_paiement (array('code'=>xx,'label'=>zz))
5263	 *    @param    int     $active         Active or not, -1 = all
5264	 *    @param   int     $addempty       1=Add empty entry
5265	 *    @return	void
5266	 */
5267	public function form_modes_reglement($page, $selected = '', $htmlname = 'mode_reglement_id', $filtertype = '', $active = 1, $addempty = 0)
5268	{
5269		// phpcs:enable
5270		global $langs;
5271		if ($htmlname != "none") {
5272			print '<form method="POST" action="'.$page.'">';
5273			print '<input type="hidden" name="action" value="setmode">';
5274			print '<input type="hidden" name="token" value="'.newToken().'">';
5275			$this->select_types_paiements($selected, $htmlname, $filtertype, 0, $addempty, 0, 0, $active);
5276			print '<input type="submit" class="button smallpaddingimp valignmiddle" value="'.$langs->trans("Modify").'">';
5277			print '</form>';
5278		} else {
5279			if ($selected) {
5280				$this->load_cache_types_paiements();
5281				print $this->cache_types_paiements[$selected]['label'];
5282			} else {
5283				print "&nbsp;";
5284			}
5285		}
5286	}
5287
5288	/**
5289	 *    Show form with transport mode
5290	 *
5291	 *    @param	string	$page        	Page
5292	 *    @param    int		$selected    	Id mode pre-select
5293	 *    @param    string	$htmlname    	Name of select html field
5294	 *    @param    int     $active         Active or not, -1 = all
5295	 *    @param    int     $addempty       1=Add empty entry
5296	 *    @return	void
5297	 */
5298	public function formSelectTransportMode($page, $selected = '', $htmlname = 'transport_mode_id', $active = 1, $addempty = 0)
5299	{
5300		global $langs;
5301		if ($htmlname != "none") {
5302			print '<form method="POST" action="'.$page.'">';
5303			print '<input type="hidden" name="action" value="settransportmode">';
5304			print '<input type="hidden" name="token" value="'.newToken().'">';
5305			$this->selectTransportMode($selected, $htmlname, 0, $addempty, 0, 0, $active);
5306			print '<input type="submit" class="button smallpaddingimp valignmiddle" value="'.$langs->trans("Modify").'">';
5307			print '</form>';
5308		} else {
5309			if ($selected) {
5310				$this->load_cache_transport_mode();
5311				print $this->cache_transport_mode[$selected]['label'];
5312			} else {
5313				print "&nbsp;";
5314			}
5315		}
5316	}
5317
5318	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5319	/**
5320	 *    Show form with multicurrency code
5321	 *
5322	 *    @param	string	$page        	Page
5323	 *    @param    string	$selected    	code pre-selectionne
5324	 *    @param    string	$htmlname    	Name of select html field
5325	 *    @return	void
5326	 */
5327	public function form_multicurrency_code($page, $selected = '', $htmlname = 'multicurrency_code')
5328	{
5329		// phpcs:enable
5330		global $langs;
5331		if ($htmlname != "none") {
5332			print '<form method="POST" action="'.$page.'">';
5333			print '<input type="hidden" name="action" value="setmulticurrencycode">';
5334			print '<input type="hidden" name="token" value="'.newToken().'">';
5335			print $this->selectMultiCurrency($selected, $htmlname, 0);
5336			print '<input type="submit" class="button smallpaddingimp valignmiddle" value="'.$langs->trans("Modify").'">';
5337			print '</form>';
5338		} else {
5339			dol_include_once('/core/lib/company.lib.php');
5340			print !empty($selected) ? currency_name($selected, 1) : '&nbsp;';
5341		}
5342	}
5343
5344	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5345	/**
5346	 *    Show form with multicurrency rate
5347	 *
5348	 *    @param	string	$page        	Page
5349	 *    @param    double	$rate	    	Current rate
5350	 *    @param    string	$htmlname    	Name of select html field
5351	 *    @param    string  $currency       Currency code to explain the rate
5352	 *    @return	void
5353	 */
5354	public function form_multicurrency_rate($page, $rate = '', $htmlname = 'multicurrency_tx', $currency = '')
5355	{
5356		// phpcs:enable
5357		global $langs, $mysoc, $conf;
5358
5359		if ($htmlname != "none") {
5360			print '<form method="POST" action="'.$page.'">';
5361			print '<input type="hidden" name="action" value="setmulticurrencyrate">';
5362			print '<input type="hidden" name="token" value="'.newToken().'">';
5363			print '<input type="text" class="maxwidth100" name="'.$htmlname.'" value="'.(!empty($rate) ? price(price2num($rate, 'CU')) : 1).'" /> ';
5364			print '<select name="calculation_mode">';
5365			print '<option value="1">Change '.$langs->trans("PriceUHT").' of lines</option>';
5366			print '<option value="2">Change '.$langs->trans("PriceUHTCurrency").' of lines</option>';
5367			print '</select> ';
5368			print '<input type="submit" class="button smallpaddingimp valignmiddle" value="'.$langs->trans("Modify").'">';
5369			print '</form>';
5370		} else {
5371			if (!empty($rate)) {
5372				print price($rate, 1, $langs, 1, 0);
5373				if ($currency && $rate != 1) {
5374					print ' &nbsp; ('.price($rate, 1, $langs, 1, 0).' '.$currency.' = 1 '.$conf->currency.')';
5375				}
5376			} else {
5377				print 1;
5378			}
5379		}
5380	}
5381
5382
5383	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5384	/**
5385	 *	Show a select box with available absolute discounts
5386	 *
5387	 *  @param  string	$page        	Page URL where form is shown
5388	 *  @param  int		$selected    	Value pre-selected
5389	 *	@param  string	$htmlname    	Name of SELECT component. If 'none', not changeable. Example 'remise_id'.
5390	 *	@param	int		$socid			Third party id
5391	 * 	@param	float	$amount			Total amount available
5392	 * 	@param	string	$filter			SQL filter on discounts
5393	 * 	@param	int		$maxvalue		Max value for lines that can be selected
5394	 *  @param  string	$more           More string to add
5395	 *  @param  int     $hidelist       1=Hide list
5396	 *  @param	int		$discount_type	0 => customer discount, 1 => supplier discount
5397	 *  @return	void
5398	 */
5399	public function form_remise_dispo($page, $selected, $htmlname, $socid, $amount, $filter = '', $maxvalue = 0, $more = '', $hidelist = 0, $discount_type = 0)
5400	{
5401		// phpcs:enable
5402		global $conf, $langs;
5403		if ($htmlname != "none") {
5404			print '<form method="post" action="'.$page.'">';
5405			print '<input type="hidden" name="action" value="setabsolutediscount">';
5406			print '<input type="hidden" name="token" value="'.newToken().'">';
5407			print '<div class="inline-block">';
5408			if (!empty($discount_type)) {
5409				if (!empty($conf->global->FACTURE_DEPOSITS_ARE_JUST_PAYMENTS)) {
5410					if (!$filter || $filter == "fk_invoice_supplier_source IS NULL") {
5411						$translationKey = 'HasAbsoluteDiscountFromSupplier'; // If we want deposit to be substracted to payments only and not to total of final invoice
5412					} else {
5413						$translationKey = 'HasCreditNoteFromSupplier';
5414					}
5415				} else {
5416					if (!$filter || $filter == "fk_invoice_supplier_source IS NULL OR (description LIKE '(DEPOSIT)%' AND description NOT LIKE '(EXCESS PAID)%')") {
5417						$translationKey = 'HasAbsoluteDiscountFromSupplier';
5418					} else {
5419						$translationKey = 'HasCreditNoteFromSupplier';
5420					}
5421				}
5422			} else {
5423				if (!empty($conf->global->FACTURE_DEPOSITS_ARE_JUST_PAYMENTS)) {
5424					if (!$filter || $filter == "fk_facture_source IS NULL") {
5425						$translationKey = 'CompanyHasAbsoluteDiscount'; // If we want deposit to be substracted to payments only and not to total of final invoice
5426					} else {
5427						$translationKey = 'CompanyHasCreditNote';
5428					}
5429				} else {
5430					if (!$filter || $filter == "fk_facture_source IS NULL OR (description LIKE '(DEPOSIT)%' AND description NOT LIKE '(EXCESS RECEIVED)%')") {
5431						$translationKey = 'CompanyHasAbsoluteDiscount';
5432					} else {
5433						$translationKey = 'CompanyHasCreditNote';
5434					}
5435				}
5436			}
5437			print $langs->trans($translationKey, price($amount, 0, $langs, 0, 0, -1, $conf->currency));
5438			if (empty($hidelist)) {
5439				print ' ';
5440			}
5441			print '</div>';
5442			if (empty($hidelist)) {
5443				print '<div class="inline-block" style="padding-right: 10px">';
5444				$newfilter = 'discount_type='.intval($discount_type);
5445				if (!empty($discount_type)) {
5446					$newfilter .= ' AND fk_invoice_supplier IS NULL AND fk_invoice_supplier_line IS NULL'; // Supplier discounts available
5447				} else {
5448					$newfilter .= ' AND fk_facture IS NULL AND fk_facture_line IS NULL'; // Customer discounts available
5449				}
5450				if ($filter) {
5451					$newfilter .= ' AND ('.$filter.')';
5452				}
5453				$nbqualifiedlines = $this->select_remises($selected, $htmlname, $newfilter, $socid, $maxvalue);
5454				if ($nbqualifiedlines > 0) {
5455					print ' &nbsp; <input type="submit" class="button smallpaddingimp" value="'.dol_escape_htmltag($langs->trans("UseLine")).'"';
5456					if (!empty($discount_type) && $filter && $filter != "fk_invoice_supplier_source IS NULL OR (description LIKE '(DEPOSIT)%' AND description NOT LIKE '(EXCESS PAID)%')") {
5457						print ' title="'.$langs->trans("UseCreditNoteInInvoicePayment").'"';
5458					}
5459					if (empty($discount_type) && $filter && $filter != "fk_facture_source IS NULL OR (description LIKE '(DEPOSIT)%' AND description NOT LIKE '(EXCESS RECEIVED)%')") {
5460						print ' title="'.$langs->trans("UseCreditNoteInInvoicePayment").'"';
5461					}
5462
5463					print '>';
5464				}
5465				print '</div>';
5466			}
5467			if ($more) {
5468				print '<div class="inline-block">';
5469				print $more;
5470				print '</div>';
5471			}
5472			print '</form>';
5473		} else {
5474			if ($selected) {
5475				print $selected;
5476			} else {
5477				print "0";
5478			}
5479		}
5480	}
5481
5482
5483	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5484	/**
5485	 *  Show forms to select a contact
5486	 *
5487	 *  @param	string		$page        	Page
5488	 *  @param	Societe		$societe		Filter on third party
5489	 *  @param    int			$selected    	Id contact pre-selectionne
5490	 *  @param    string		$htmlname    	Name of HTML select. If 'none', we just show contact link.
5491	 *  @return	void
5492	 */
5493	public function form_contacts($page, $societe, $selected = '', $htmlname = 'contactid')
5494	{
5495		// phpcs:enable
5496		global $langs, $conf;
5497
5498		if ($htmlname != "none") {
5499			print '<form method="post" action="'.$page.'">';
5500			print '<input type="hidden" name="action" value="set_contact">';
5501			print '<input type="hidden" name="token" value="'.newToken().'">';
5502			print '<table class="nobordernopadding">';
5503			print '<tr><td>';
5504			print $this->selectcontacts($societe->id, $selected, $htmlname);
5505			$num = $this->num;
5506			if ($num == 0) {
5507				$addcontact = (!empty($conf->global->SOCIETE_ADDRESSES_MANAGEMENT) ? $langs->trans("AddContact") : $langs->trans("AddContactAddress"));
5508				print '<a href="'.DOL_URL_ROOT.'/contact/card.php?socid='.$societe->id.'&amp;action=create&amp;backtoreferer=1">'.$addcontact.'</a>';
5509			}
5510			print '</td>';
5511			print '<td class="left"><input type="submit" class="button smallpaddingimp" value="'.$langs->trans("Modify").'"></td>';
5512			print '</tr></table></form>';
5513		} else {
5514			if ($selected) {
5515				require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
5516				$contact = new Contact($this->db);
5517				$contact->fetch($selected);
5518				print $contact->getFullName($langs);
5519			} else {
5520				print "&nbsp;";
5521			}
5522		}
5523	}
5524
5525	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5526	/**
5527	 *  Output html select to select thirdparty
5528	 *
5529	 *  @param	string	$page       	Page
5530	 *  @param  string	$selected   	Id preselected
5531	 *  @param  string	$htmlname		Name of HTML select
5532	 *  @param  string	$filter         Optional filters criteras. Do not use a filter coming from input of users.
5533	 *	@param	int		$showempty		Add an empty field
5534	 * 	@param	int		$showtype		Show third party type in combolist (customer, prospect or supplier)
5535	 * 	@param	int		$forcecombo		Force to use combo box
5536	 *  @param	array	$events			Event options. Example: array(array('method'=>'getContacts', 'url'=>dol_buildpath('/core/ajax/contacts.php',1), 'htmlname'=>'contactid', 'params'=>array('add-customer-contact'=>'disabled')))
5537	 *  @param  int     $nooutput       No print output. Return it only.
5538	 *  @param	array	$excludeids		Exclude IDs from the select combo
5539	 *  @return	void|string
5540	 */
5541	public function form_thirdparty($page, $selected = '', $htmlname = 'socid', $filter = '', $showempty = 0, $showtype = 0, $forcecombo = 0, $events = array(), $nooutput = 0, $excludeids = array())
5542	{
5543		// phpcs:enable
5544		global $langs;
5545
5546		$out = '';
5547		if ($htmlname != "none") {
5548			$out .= '<form method="post" action="'.$page.'">';
5549			$out .= '<input type="hidden" name="action" value="set_thirdparty">';
5550			$out .= '<input type="hidden" name="token" value="'.newToken().'">';
5551			$out .= $this->select_company($selected, $htmlname, $filter, $showempty, $showtype, $forcecombo, $events, 0, 'minwidth100', '', '', 1, array(), false, $excludeids);
5552			$out .= '<input type="submit" class="button smallpaddingimp valignmiddle" value="'.$langs->trans("Modify").'">';
5553			$out .= '</form>';
5554		} else {
5555			if ($selected) {
5556				require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
5557				$soc = new Societe($this->db);
5558				$soc->fetch($selected);
5559				$out .= $soc->getNomUrl($langs);
5560			} else {
5561				$out .= "&nbsp;";
5562			}
5563		}
5564
5565		if ($nooutput) {
5566			return $out;
5567		} else {
5568			print $out;
5569		}
5570	}
5571
5572	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5573	/**
5574	 *    Retourne la liste des devises, dans la langue de l'utilisateur
5575	 *
5576	 *    @param	string	$selected    preselected currency code
5577	 *    @param    string	$htmlname    name of HTML select list
5578	 *    @deprecated
5579	 *    @return	void
5580	 */
5581	public function select_currency($selected = '', $htmlname = 'currency_id')
5582	{
5583		// phpcs:enable
5584		print $this->selectCurrency($selected, $htmlname);
5585	}
5586
5587	/**
5588	 *  Retourne la liste des devises, dans la langue de l'utilisateur
5589	 *
5590	 *  @param	string	$selected    preselected currency code
5591	 *  @param  string	$htmlname    name of HTML select list
5592	 *  @param  string  $mode        0 = Add currency symbol into label, 1 = Add 3 letter iso code
5593	 * 	@return	string
5594	 */
5595	public function selectCurrency($selected = '', $htmlname = 'currency_id', $mode = 0)
5596	{
5597		global $conf, $langs, $user;
5598
5599		$langs->loadCacheCurrencies('');
5600
5601		$out = '';
5602
5603		if ($selected == 'euro' || $selected == 'euros') {
5604			$selected = 'EUR'; // Pour compatibilite
5605		}
5606
5607		$out .= '<select class="flat maxwidth200onsmartphone minwidth300" name="'.$htmlname.'" id="'.$htmlname.'">';
5608		foreach ($langs->cache_currencies as $code_iso => $currency) {
5609			$labeltoshow = $currency['label'];
5610			if ($mode == 1) {
5611				$labeltoshow .= ' <span class="opacitymedium">('.$code_iso.')</span>';
5612			} else {
5613				$labeltoshow .= ' <span class="opacitymedium">('.$langs->getCurrencySymbol($code_iso).')</span>';
5614			}
5615
5616			if ($selected && $selected == $code_iso) {
5617				$out .= '<option value="'.$code_iso.'" selected data-html="'.dol_escape_htmltag($labeltoshow).'">';
5618			} else {
5619				$out .= '<option value="'.$code_iso.'" data-html="'.dol_escape_htmltag($labeltoshow).'">';
5620			}
5621			$out .= $labeltoshow;
5622			$out .= '</option>';
5623		}
5624		$out .= '</select>';
5625		if ($user->admin) {
5626			$out .= info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
5627		}
5628
5629		// Make select dynamic
5630		include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
5631		$out .= ajax_combobox($htmlname);
5632
5633		return $out;
5634	}
5635
5636	/**
5637	 *	Return array of currencies in user language
5638	 *
5639	 *  @param	string	$selected    preselected currency code
5640	 *  @param  string	$htmlname    name of HTML select list
5641	 *  @param  integer	$useempty    1=Add empty line
5642	 *  @param string $filter Optional filters criteras (example: 'code <> x', ' in (1,3)')
5643	 *  @param bool $excludeConfCurrency false  = If company current currency not in table, we add it into list. Should always be available.  true = we are in currency_rate update , we don't want to see conf->currency in select
5644	 * 	@return	string
5645	 */
5646	public function selectMultiCurrency($selected = '', $htmlname = 'multicurrency_code', $useempty = 0, $filter = '', $excludeConfCurrency = false)
5647	{
5648		global $db, $conf, $langs, $user;
5649
5650		$langs->loadCacheCurrencies(''); // Load ->cache_currencies
5651
5652		$TCurrency = array();
5653
5654		$sql = 'SELECT code FROM '.MAIN_DB_PREFIX.'multicurrency';
5655		$sql .= " WHERE entity IN ('".getEntity('mutlicurrency')."')";
5656		if ($filter) {
5657			$sql .= " AND ".$filter;
5658		}
5659		$resql = $this->db->query($sql);
5660		if ($resql) {
5661			while ($obj = $this->db->fetch_object($resql)) {
5662				$TCurrency[$obj->code] = $obj->code;
5663			}
5664		}
5665
5666		$out = '';
5667		$out .= '<select class="flat" name="'.$htmlname.'" id="'.$htmlname.'">';
5668		if ($useempty) {
5669			$out .= '<option value="">&nbsp;</option>';
5670		}
5671		// If company current currency not in table, we add it into list. Should always be available.
5672		if (!in_array($conf->currency, $TCurrency) && !$excludeConfCurrency) {
5673			$TCurrency[$conf->currency] = $conf->currency;
5674		}
5675		if (count($TCurrency) > 0) {
5676			foreach ($langs->cache_currencies as $code_iso => $currency) {
5677				if (isset($TCurrency[$code_iso])) {
5678					if (!empty($selected) && $selected == $code_iso) {
5679						$out .= '<option value="'.$code_iso.'" selected="selected">';
5680					} else {
5681						$out .= '<option value="'.$code_iso.'">';
5682					}
5683
5684					$out .= $currency['label'];
5685					$out .= ' ('.$langs->getCurrencySymbol($code_iso).')';
5686					$out .= '</option>';
5687				}
5688			}
5689		}
5690
5691		$out .= '</select>';
5692		// Make select dynamic
5693		include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
5694		$out .= ajax_combobox($htmlname);
5695
5696		return $out;
5697	}
5698
5699	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5700	/**
5701	 *  Load into the cache vat rates of a country
5702	 *
5703	 *  @param	string	$country_code		Country code with quotes ("'CA'", or "'CA,IN,...'")
5704	 *  @return	int							Nb of loaded lines, 0 if already loaded, <0 if KO
5705	 */
5706	public function load_cache_vatrates($country_code)
5707	{
5708		// phpcs:enable
5709		global $langs;
5710
5711		$num = count($this->cache_vatrates);
5712		if ($num > 0) {
5713			return $num; // Cache already loaded
5714		}
5715
5716		dol_syslog(__METHOD__, LOG_DEBUG);
5717
5718		$sql = "SELECT DISTINCT t.rowid, t.code, t.taux, t.localtax1, t.localtax1_type, t.localtax2, t.localtax2_type, t.recuperableonly";
5719		$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
5720		$sql .= " WHERE t.fk_pays = c.rowid";
5721		$sql .= " AND t.active > 0";
5722		$sql .= " AND c.code IN (".$this->db->sanitize($country_code, 1).")";
5723		$sql .= " ORDER BY t.code ASC, t.taux ASC, t.recuperableonly ASC";
5724
5725		$resql = $this->db->query($sql);
5726		if ($resql) {
5727			$num = $this->db->num_rows($resql);
5728			if ($num) {
5729				for ($i = 0; $i < $num; $i++) {
5730					$obj = $this->db->fetch_object($resql);
5731					$this->cache_vatrates[$i]['rowid']	= $obj->rowid;
5732					$this->cache_vatrates[$i]['code'] = $obj->code;
5733					$this->cache_vatrates[$i]['txtva']	= $obj->taux;
5734					$this->cache_vatrates[$i]['nprtva'] = $obj->recuperableonly;
5735					$this->cache_vatrates[$i]['localtax1']	    = $obj->localtax1;
5736					$this->cache_vatrates[$i]['localtax1_type']	= $obj->localtax1_type;
5737					$this->cache_vatrates[$i]['localtax2']	    = $obj->localtax2;
5738					$this->cache_vatrates[$i]['localtax2_type']	= $obj->localtax1_type;
5739
5740					$this->cache_vatrates[$i]['label'] = $obj->taux.'%'.($obj->code ? ' ('.$obj->code.')' : ''); // Label must contains only 0-9 , . % or *
5741					$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
5742					$positiverates = '';
5743					if ($obj->taux) {
5744						$positiverates .= ($positiverates ? '/' : '').$obj->taux;
5745					}
5746					if ($obj->localtax1) {
5747						$positiverates .= ($positiverates ? '/' : '').$obj->localtax1;
5748					}
5749					if ($obj->localtax2) {
5750						$positiverates .= ($positiverates ? '/' : '').$obj->localtax2;
5751					}
5752					if (empty($positiverates)) {
5753						$positiverates = '0';
5754					}
5755					$this->cache_vatrates[$i]['labelpositiverates'] = $positiverates.($obj->code ? ' ('.$obj->code.')' : ''); // Must never be used as key, only label
5756				}
5757
5758				return $num;
5759			} else {
5760				$this->error = '<font class="error">'.$langs->trans("ErrorNoVATRateDefinedForSellerCountry", $country_code).'</font>';
5761				return -1;
5762			}
5763		} else {
5764			$this->error = '<font class="error">'.$this->db->error().'</font>';
5765			return -2;
5766		}
5767	}
5768
5769	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5770	/**
5771	 *  Output an HTML select vat rate.
5772	 *  The name of this function should be selectVat. We keep bad name for compatibility purpose.
5773	 *
5774	 *  @param	string	      $htmlname           Name of HTML select field
5775	 *  @param  float|string  $selectedrate       Force preselected vat rate. Can be '8.5' or '8.5 (NOO)' for example. Use '' for no forcing.
5776	 *  @param  Societe	      $societe_vendeuse   Thirdparty seller
5777	 *  @param  Societe	      $societe_acheteuse  Thirdparty buyer
5778	 *  @param  int		      $idprod             Id product. O if unknown of NA.
5779	 *  @param  int		      $info_bits          Miscellaneous information on line (1 for NPR)
5780	 *  @param  int|string    $type               ''=Unknown, 0=Product, 1=Service (Used if idprod not defined)
5781	 *                  		                  Si vendeur non assujeti a TVA, TVA par defaut=0. Fin de regle.
5782	 *                  					      Si le (pays vendeur = pays acheteur) alors la TVA par defaut=TVA du produit vendu. Fin de regle.
5783	 *                  					      Si (vendeur et acheteur dans Communaute europeenne) et bien vendu = moyen de transports neuf (auto, bateau, avion), TVA par defaut=0 (La TVA doit etre paye par l'acheteur au centre d'impots de son pays et non au vendeur). Fin de regle.
5784	 *                                            Si vendeur et acheteur dans Communauté européenne et acheteur= particulier alors TVA par défaut=TVA du produit vendu. Fin de règle.
5785	 *                                            Si vendeur et acheteur dans Communauté européenne et acheteur= entreprise alors TVA par défaut=0. Fin de règle.
5786	 *                  					      Sinon la TVA proposee par defaut=0. Fin de regle.
5787	 *  @param	bool	     $options_only		  Return HTML options lines only (for ajax treatment)
5788	 *  @param  int          $mode                0=Use vat rate as key in combo list, 1=Add VAT code after vat rate into key, -1=Use id of vat line as key
5789	 *  @return	string
5790	 */
5791	public function load_tva($htmlname = 'tauxtva', $selectedrate = '', $societe_vendeuse = '', $societe_acheteuse = '', $idprod = 0, $info_bits = 0, $type = '', $options_only = false, $mode = 0)
5792	{
5793		// phpcs:enable
5794		global $langs, $conf, $mysoc;
5795
5796		$langs->load('errors');
5797
5798		$return = '';
5799
5800		// Define defaultnpr, defaultttx and defaultcode
5801		$defaultnpr = ($info_bits & 0x01);
5802		$defaultnpr = (preg_match('/\*/', $selectedrate) ? 1 : $defaultnpr);
5803		$defaulttx = str_replace('*', '', $selectedrate);
5804		$defaultcode = '';
5805		$reg = array();
5806		if (preg_match('/\((.*)\)/', $defaulttx, $reg)) {
5807			$defaultcode = $reg[1];
5808			$defaulttx = preg_replace('/\s*\(.*\)/', '', $defaulttx);
5809		}
5810		//var_dump($selectedrate.'-'.$defaulttx.'-'.$defaultnpr.'-'.$defaultcode);
5811
5812		// Check parameters
5813		if (is_object($societe_vendeuse) && !$societe_vendeuse->country_code) {
5814			if ($societe_vendeuse->id == $mysoc->id) {
5815				$return .= '<font class="error">'.$langs->trans("ErrorYourCountryIsNotDefined").'</font>';
5816			} else {
5817				$return .= '<font class="error">'.$langs->trans("ErrorSupplierCountryIsNotDefined").'</font>';
5818			}
5819			return $return;
5820		}
5821
5822		//var_dump($societe_acheteuse);
5823		//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";
5824		//exit;
5825
5826		// Define list of countries to use to search VAT rates to show
5827		// First we defined code_country to use to find list
5828		if (is_object($societe_vendeuse)) {
5829			$code_country = "'".$societe_vendeuse->country_code."'";
5830		} else {
5831			$code_country = "'".$mysoc->country_code."'"; // Pour compatibilite ascendente
5832		}
5833		if (!empty($conf->global->SERVICE_ARE_ECOMMERCE_200238EC)) {    // If option to have vat for end customer for services is on
5834			require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
5835			if (!isInEEC($societe_vendeuse) && (!is_object($societe_acheteuse) || (isInEEC($societe_acheteuse) && !$societe_acheteuse->isACompany()))) {
5836				// We also add the buyer
5837				if (is_numeric($type)) {
5838					if ($type == 1) { // We know product is a service
5839						$code_country .= ",'".$societe_acheteuse->country_code."'";
5840					}
5841				} elseif (!$idprod) {  // We don't know type of product
5842					$code_country .= ",'".$societe_acheteuse->country_code."'";
5843				} else {
5844					$prodstatic = new Product($this->db);
5845					$prodstatic->fetch($idprod);
5846					if ($prodstatic->type == Product::TYPE_SERVICE) {   // We know product is a service
5847						$code_country .= ",'".$societe_acheteuse->country_code."'";
5848					}
5849				}
5850			}
5851		}
5852
5853		// Now we get list
5854		$num = $this->load_cache_vatrates($code_country); // If no vat defined, return -1 with message into this->error
5855
5856		if ($num > 0) {
5857			// Definition du taux a pre-selectionner (si defaulttx non force et donc vaut -1 ou '')
5858			if ($defaulttx < 0 || dol_strlen($defaulttx) == 0) {
5859				$tmpthirdparty = new Societe($this->db);
5860				$defaulttx = get_default_tva($societe_vendeuse, (is_object($societe_acheteuse) ? $societe_acheteuse : $tmpthirdparty), $idprod);
5861				$defaultnpr = get_default_npr($societe_vendeuse, (is_object($societe_acheteuse) ? $societe_acheteuse : $tmpthirdparty), $idprod);
5862				if (preg_match('/\((.*)\)/', $defaulttx, $reg)) {
5863					$defaultcode = $reg[1];
5864					$defaulttx = preg_replace('/\s*\(.*\)/', '', $defaulttx);
5865				}
5866				if (empty($defaulttx)) {
5867					$defaultnpr = 0;
5868				}
5869			}
5870
5871			// Si taux par defaut n'a pu etre determine, on prend dernier de la liste.
5872			// Comme ils sont tries par ordre croissant, dernier = plus eleve = taux courant
5873			if ($defaulttx < 0 || dol_strlen($defaulttx) == 0) {
5874				if (empty($conf->global->MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS)) {
5875					$defaulttx = $this->cache_vatrates[$num - 1]['txtva'];
5876				} else {
5877					$defaulttx = ($conf->global->MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS == 'none' ? '' : $conf->global->MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS);
5878				}
5879			}
5880
5881			// Disabled if seller is not subject to VAT
5882			$disabled = false;
5883			$title = '';
5884			if (is_object($societe_vendeuse) && $societe_vendeuse->id == $mysoc->id && $societe_vendeuse->tva_assuj == "0") {
5885				// Override/enable VAT for expense report regardless of global setting - needed if expense report used for business expenses instead
5886				// of using supplier invoices (this is a very bad idea !)
5887				if (empty($conf->global->EXPENSEREPORT_OVERRIDE_VAT)) {
5888					$title = ' title="'.$langs->trans('VATIsNotUsed').'"';
5889					$disabled = true;
5890				}
5891			}
5892
5893			if (!$options_only) {
5894				$return .= '<select class="flat minwidth75imp" id="'.$htmlname.'" name="'.$htmlname.'"'.($disabled ? ' disabled' : '').$title.'>';
5895			}
5896
5897			$selectedfound = false;
5898			foreach ($this->cache_vatrates as $rate) {
5899				// Keep only 0 if seller is not subject to VAT
5900				if ($disabled && $rate['txtva'] != 0) {
5901					continue;
5902				}
5903
5904				// Define key to use into select list
5905				$key = $rate['txtva'];
5906				$key .= $rate['nprtva'] ? '*' : '';
5907				if ($mode > 0 && $rate['code']) {
5908					$key .= ' ('.$rate['code'].')';
5909				}
5910				if ($mode < 0) {
5911					$key = $rate['rowid'];
5912				}
5913
5914				$return .= '<option value="'.$key.'"';
5915				if (!$selectedfound) {
5916					if ($defaultcode) { // If defaultcode is defined, we used it in priority to select combo option instead of using rate+npr flag
5917						if ($defaultcode == $rate['code']) {
5918							$return .= ' selected';
5919							$selectedfound = true;
5920						}
5921					} elseif ($rate['txtva'] == $defaulttx && $rate['nprtva'] == $defaultnpr) {
5922						$return .= ' selected';
5923						$selectedfound = true;
5924					}
5925				}
5926				$return .= '>';
5927				//if (! empty($conf->global->MAIN_VAT_SHOW_POSITIVE_RATES))
5928				if ($mysoc->country_code == 'IN' || !empty($conf->global->MAIN_VAT_LABEL_IS_POSITIVE_RATES)) {
5929					$return .= $rate['labelpositiverates'];
5930				} else {
5931					$return .= vatrate($rate['label']);
5932				}
5933				//$return.=($rate['code']?' '.$rate['code']:'');
5934				$return .= (empty($rate['code']) && $rate['nprtva']) ? ' *' : ''; // We show the *  (old behaviour only if new vat code is not used)
5935
5936				$return .= '</option>';
5937			}
5938
5939			if (!$options_only) {
5940				$return .= '</select>';
5941			}
5942		} else {
5943			$return .= $this->error;
5944		}
5945
5946		$this->num = $num;
5947		return $return;
5948	}
5949
5950
5951	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5952	/**
5953	 *  Show a HTML widget to input a date or combo list for day, month, years and optionaly hours and minutes.
5954	 *  Fields are preselected with :
5955	 *            	- set_time date (must be a local PHP server timestamp or string date with format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM')
5956	 *            	- local date in user area, if set_time is '' (so if set_time is '', output may differs when done from two different location)
5957	 *            	- Empty (fields empty), if set_time is -1 (in this case, parameter empty must also have value 1)
5958	 *
5959	 *	@param	integer	    $set_time 		Pre-selected date (must be a local PHP server timestamp), -1 to keep date not preselected, '' to use current date with 00:00 hour (Parameter 'empty' must be 0 or 2).
5960	 *	@param	string		$prefix			Prefix for fields name
5961	 *	@param	int			$h				1 or 2=Show also hours (2=hours on a new line), -1 has same effect but hour and minutes are prefilled with 23:59 if date is empty, 3 show hour always empty
5962	 *	@param	int			$m				1=Show also minutes, -1 has same effect but hour and minutes are prefilled with 23:59 if date is empty, 3 show minutes always empty
5963	 *	@param	int			$empty			0=Fields required, 1=Empty inputs are allowed, 2=Empty inputs are allowed for hours only
5964	 *	@param	string		$form_name 		Not used
5965	 *	@param	int			$d				1=Show days, month, years
5966	 * 	@param	int			$addnowlink		Add a link "Now"
5967	 * 	@param	int			$nooutput		Do not output html string but return it
5968	 * 	@param 	int			$disabled		Disable input fields
5969	 *  @param  int			$fullday        When a checkbox with this html name is on, hour and day are set with 00:00 or 23:59
5970	 *  @param	string		$addplusone		Add a link "+1 hour". Value must be name of another select_date field.
5971	 *  @param  datetime    $adddateof      Add a link "Date of invoice" using the following date.
5972	 *  @return	string|void					Nothing or string if nooutput is 1
5973	 *  @deprecated
5974	 *  @see    selectDate(), form_date(), select_month(), select_year(), select_dayofweek()
5975	 */
5976	public function select_date($set_time = '', $prefix = 're', $h = 0, $m = 0, $empty = 0, $form_name = "", $d = 1, $addnowlink = 0, $nooutput = 0, $disabled = 0, $fullday = '', $addplusone = '', $adddateof = '')
5977	{
5978		// phpcs:enable
5979		$retstring = $this->selectDate($set_time, $prefix, $h, $m, $empty, $form_name, $d, $addnowlink, $disabled, $fullday, $addplusone, $adddateof);
5980		if (!empty($nooutput)) {
5981			return $retstring;
5982		}
5983		print $retstring;
5984		return;
5985	}
5986
5987	/**
5988	 *  Show 2 HTML widget to input a date or combo list for day, month, years and optionaly hours and minutes.
5989	 *  Fields are preselected with :
5990	 *              - set_time date (must be a local PHP server timestamp or string date with format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM')
5991	 *              - local date in user area, if set_time is '' (so if set_time is '', output may differs when done from two different location)
5992	 *              - Empty (fields empty), if set_time is -1 (in this case, parameter empty must also have value 1)
5993	 *
5994	 *  @param  integer     $set_time       Pre-selected date (must be a local PHP server timestamp), -1 to keep date not preselected, '' to use current date with 00:00 hour (Parameter 'empty' must be 0 or 2).
5995	 *  @param  integer     $set_time_end       Pre-selected date (must be a local PHP server timestamp), -1 to keep date not preselected, '' to use current date with 00:00 hour (Parameter 'empty' must be 0 or 2).
5996	 *  @param	string		$prefix			Prefix for fields name
5997	 *  @param	string		$empty			0=Fields required, 1=Empty inputs are allowed, 2=Empty inputs are allowed for hours only
5998	 * 	@return string                      Html for selectDate
5999	 *  @see    form_date(), select_month(), select_year(), select_dayofweek()
6000	 */
6001	public function selectDateToDate($set_time = '', $set_time_end = '', $prefix = 're', $empty = 0)
6002	{
6003		global $langs;
6004
6005		$ret = $this->selectDate($set_time, $prefix.'_start', 0, 0, $empty, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("from"), 'tzuserrel');
6006		$ret .= '<br>';
6007		$ret .= $this->selectDate($set_time_end, $prefix.'_end', 0, 0, $empty, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("to"), 'tzuserrel');
6008		return $ret;
6009	}
6010
6011	/**
6012	 *  Show a HTML widget to input a date or combo list for day, month, years and optionaly hours and minutes.
6013	 *  Fields are preselected with :
6014	 *              - set_time date (must be a local PHP server timestamp or string date with format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM')
6015	 *              - local date in user area, if set_time is '' (so if set_time is '', output may differs when done from two different location)
6016	 *              - Empty (fields empty), if set_time is -1 (in this case, parameter empty must also have value 1)
6017	 *
6018	 *  @param  integer     $set_time       Pre-selected date (must be a local PHP server timestamp), -1 to keep date not preselected, '' to use current date with 00:00 hour (Parameter 'empty' must be 0 or 2).
6019	 *  @param	string		$prefix			Prefix for fields name
6020	 *  @param	int			$h				1 or 2=Show also hours (2=hours on a new line), -1 has same effect but hour and minutes are prefilled with 23:59 if date is empty, 3 show hour always empty
6021	 *	@param	int			$m				1=Show also minutes, -1 has same effect but hour and minutes are prefilled with 23:59 if date is empty, 3 show minutes always empty
6022	 *	@param	int			$empty			0=Fields required, 1=Empty inputs are allowed, 2=Empty inputs are allowed for hours only
6023	 *	@param	string		$form_name 		Not used
6024	 *	@param	int			$d				1=Show days, month, years
6025	 * 	@param	int			$addnowlink		Add a link "Now", 1 with server time, 2 with local computer time
6026	 * 	@param 	int			$disabled		Disable input fields
6027	 *  @param  int			$fullday        When a checkbox with id #fullday is checked, hours are set with 00:00 (if value if 'fulldaystart') or 23:59 (if value is 'fulldayend')
6028	 *  @param	string		$addplusone		Add a link "+1 hour". Value must be name of another selectDate field.
6029	 *  @param  datetime    $adddateof      Add a link "Date of ..." using the following date. See also $labeladddateof for the label used.
6030	 *  @param  string      $openinghours   Specify hour start and hour end for the select ex 8,20
6031	 *  @param  int         $stepminutes    Specify step for minutes between 1 and 30
6032	 *  @param	string		$labeladddateof Label to use for the $adddateof parameter.
6033	 *  @param	string 		$placeholder    Placeholder
6034	 *  @param	mixed		$gm				'auto' (for backward compatibility, avoid this), 'gmt' or 'tzserver' or 'tzuserrel'
6035	 * 	@return string                      Html for selectDate
6036	 *  @see    form_date(), select_month(), select_year(), select_dayofweek()
6037	 */
6038	public function selectDate($set_time = '', $prefix = 're', $h = 0, $m = 0, $empty = 0, $form_name = "", $d = 1, $addnowlink = 0, $disabled = 0, $fullday = '', $addplusone = '', $adddateof = '', $openinghours = '', $stepminutes = 1, $labeladddateof = '', $placeholder = '', $gm = 'auto')
6039	{
6040		global $conf, $langs;
6041
6042		if ($gm === 'auto') {
6043			$gm = (empty($conf) ? 'tzserver' : $conf->tzuserinputkey);
6044		}
6045
6046		$retstring = '';
6047
6048		if ($prefix == '') {
6049			$prefix = 're';
6050		}
6051		if ($h == '') {
6052			$h = 0;
6053		}
6054		if ($m == '') {
6055			$m = 0;
6056		}
6057		$emptydate = 0;
6058		$emptyhours = 0;
6059		if ($stepminutes <= 0 || $stepminutes > 30) {
6060			$stepminutes = 1;
6061		}
6062		if ($empty == 1) {
6063			$emptydate = 1;
6064			$emptyhours = 1;
6065		}
6066		if ($empty == 2) {
6067			$emptydate = 0;
6068			$emptyhours = 1;
6069		}
6070		$orig_set_time = $set_time;
6071
6072		if ($set_time === '' && $emptydate == 0) {
6073			include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
6074			if ($gm == 'tzuser' || $gm == 'tzuserrel') {
6075				$set_time = dol_now($gm);
6076			} else {
6077				$set_time = dol_now('tzuser') - (getServerTimeZoneInt('now') * 3600); // set_time must be relative to PHP server timezone
6078			}
6079		}
6080
6081		// Analysis of the pre-selection date
6082		$reg = array();
6083		if (preg_match('/^([0-9]+)\-([0-9]+)\-([0-9]+)\s?([0-9]+)?:?([0-9]+)?/', $set_time, $reg)) {	// deprecated usage
6084			// Date format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'
6085			$syear	= (!empty($reg[1]) ? $reg[1] : '');
6086			$smonth = (!empty($reg[2]) ? $reg[2] : '');
6087			$sday	= (!empty($reg[3]) ? $reg[3] : '');
6088			$shour	= (!empty($reg[4]) ? $reg[4] : '');
6089			$smin	= (!empty($reg[5]) ? $reg[5] : '');
6090		} elseif (strval($set_time) != '' && $set_time != -1) {
6091			// set_time est un timestamps (0 possible)
6092			$syear = dol_print_date($set_time, "%Y", $gm);
6093			$smonth = dol_print_date($set_time, "%m", $gm);
6094			$sday = dol_print_date($set_time, "%d", $gm);
6095			if ($orig_set_time != '') {
6096				$shour = dol_print_date($set_time, "%H", $gm);
6097				$smin = dol_print_date($set_time, "%M", $gm);
6098				$ssec = dol_print_date($set_time, "%S", $gm);
6099			} else {
6100				$shour = '';
6101				$smin = '';
6102				$ssec = '';
6103			}
6104		} else {
6105			// Date est '' ou vaut -1
6106			$syear = '';
6107			$smonth = '';
6108			$sday = '';
6109			$shour = !isset($conf->global->MAIN_DEFAULT_DATE_HOUR) ? ($h == -1 ? '23' : '') : $conf->global->MAIN_DEFAULT_DATE_HOUR;
6110			$smin = !isset($conf->global->MAIN_DEFAULT_DATE_MIN) ? ($h == -1 ? '59' : '') : $conf->global->MAIN_DEFAULT_DATE_MIN;
6111			$ssec = !isset($conf->global->MAIN_DEFAULT_DATE_SEC) ? ($h == -1 ? '59' : '') : $conf->global->MAIN_DEFAULT_DATE_SEC;
6112		}
6113		if ($h == 3) {
6114			$shour = '';
6115		}
6116		if ($m == 3) {
6117			$smin = '';
6118		}
6119
6120		$nowgmt = dol_now('gmt');
6121		//var_dump(dol_print_date($nowgmt, 'dayhourinputnoreduce', 'tzuserrel'));
6122
6123		// You can set MAIN_POPUP_CALENDAR to 'eldy' or 'jquery'
6124		$usecalendar = 'combo';
6125		if (!empty($conf->use_javascript_ajax) && (empty($conf->global->MAIN_POPUP_CALENDAR) || $conf->global->MAIN_POPUP_CALENDAR != "none")) {
6126			$usecalendar = ((empty($conf->global->MAIN_POPUP_CALENDAR) || $conf->global->MAIN_POPUP_CALENDAR == 'eldy') ? 'jquery' : $conf->global->MAIN_POPUP_CALENDAR);
6127		}
6128
6129		if ($d) {
6130			// Show date with popup
6131			if ($usecalendar != 'combo') {
6132				$formated_date = '';
6133				//print "e".$set_time." t ".$conf->format_date_short;
6134				if (strval($set_time) != '' && $set_time != -1) {
6135					//$formated_date=dol_print_date($set_time,$conf->format_date_short);
6136					$formated_date = dol_print_date($set_time, $langs->trans("FormatDateShortInput"), $gm); // FormatDateShortInput for dol_print_date / FormatDateShortJavaInput that is same for javascript
6137				}
6138
6139				// Calendrier popup version eldy
6140				if ($usecalendar == "eldy") {
6141					// Input area to enter date manually
6142					$retstring .= '<input id="'.$prefix.'" name="'.$prefix.'" type="text" class="maxwidthdate" maxlength="11" value="'.$formated_date.'"';
6143					$retstring .= ($disabled ? ' disabled' : '');
6144					$retstring .= ' onChange="dpChangeDay(\''.$prefix.'\',\''.$langs->trans("FormatDateShortJavaInput").'\'); "'; // FormatDateShortInput for dol_print_date / FormatDateShortJavaInput that is same for javascript
6145					$retstring .= '>';
6146
6147					// Icon calendar
6148					$retstringbuttom = '';
6149					if (!$disabled) {
6150						$retstringbuttom = '<button id="'.$prefix.'Button" type="button" class="dpInvisibleButtons"';
6151						$base = DOL_URL_ROOT.'/core/';
6152						$retstringbuttom .= ' onClick="showDP(\''.$base.'\',\''.$prefix.'\',\''.$langs->trans("FormatDateShortJavaInput").'\',\''.$langs->defaultlang.'\');"';
6153						$retstringbuttom .= '>'.img_object($langs->trans("SelectDate"), 'calendarday', 'class="datecallink"').'</button>';
6154					} else {
6155						$retstringbuttom = '<button id="'.$prefix.'Button" type="button" class="dpInvisibleButtons">'.img_object($langs->trans("Disabled"), 'calendarday', 'class="datecallink"').'</button>';
6156					}
6157					$retstring = $retstringbuttom.$retstring;
6158
6159					$retstring .= '<input type="hidden" id="'.$prefix.'day"   name="'.$prefix.'day"   value="'.$sday.'">'."\n";
6160					$retstring .= '<input type="hidden" id="'.$prefix.'month" name="'.$prefix.'month" value="'.$smonth.'">'."\n";
6161					$retstring .= '<input type="hidden" id="'.$prefix.'year"  name="'.$prefix.'year"  value="'.$syear.'">'."\n";
6162				} elseif ($usecalendar == 'jquery') {
6163					if (!$disabled) {
6164						// Output javascript for datepicker
6165						$retstring .= "<script type='text/javascript'>";
6166						$retstring .= "$(function(){ $('#".$prefix."').datepicker({
6167							dateFormat: '".$langs->trans("FormatDateShortJQueryInput")."',
6168							autoclose: true,
6169							todayHighlight: true,";
6170						if (!empty($conf->dol_use_jmobile)) {
6171							$retstring .= "
6172								beforeShow: function (input, datePicker) {
6173									input.disabled = true;
6174								},
6175								onClose: function (dateText, datePicker) {
6176									this.disabled = false;
6177								},
6178								";
6179						}
6180						// Note: We don't need monthNames, monthNamesShort, dayNames, dayNamesShort, dayNamesMin, they are set globally on datepicker component in lib_head.js.php
6181						if (empty($conf->global->MAIN_POPUP_CALENDAR_ON_FOCUS)) {
6182							$retstring .= "
6183								showOn: 'button',	/* both has problem with autocompletion */
6184								buttonImage: '".DOL_URL_ROOT."/theme/".$conf->theme."/img/object_calendarday.png',
6185								buttonImageOnly: true";
6186						}
6187						$retstring .= "
6188							}) });";
6189						$retstring .= "</script>";
6190					}
6191
6192					// Zone de saisie manuelle de la date
6193					$retstring .= '<div class="nowrap inline-block divfordateinput">';
6194					$retstring .= '<input id="'.$prefix.'" name="'.$prefix.'" type="text" class="maxwidthdate" maxlength="11" value="'.$formated_date.'"';
6195					$retstring .= ($disabled ? ' disabled' : '');
6196					$retstring .= ($placeholder ? ' placeholder="'.dol_escape_htmltag($placeholder).'"' : '');
6197					$retstring .= ' onChange="dpChangeDay(\''.$prefix.'\',\''.$langs->trans("FormatDateShortJavaInput").'\'); "'; // FormatDateShortInput for dol_print_date / FormatDateShortJavaInput that is same for javascript
6198					$retstring .= '>';
6199
6200					// Icone calendrier
6201					if (!$disabled) {
6202						/* Not required. Managed by option buttonImage of jquery
6203						$retstring.=img_object($langs->trans("SelectDate"),'calendarday','id="'.$prefix.'id" class="datecallink"');
6204						$retstring.="<script type='text/javascript'>";
6205						$retstring.="jQuery(document).ready(function() {";
6206						$retstring.='	jQuery("#'.$prefix.'id").click(function() {';
6207						$retstring.="    	jQuery('#".$prefix."').focus();";
6208						$retstring.='    });';
6209						$retstring.='});';
6210						$retstring.="</script>";*/
6211					} else {
6212						$retstringbutton = '<button id="'.$prefix.'Button" type="button" class="dpInvisibleButtons">'.img_object($langs->trans("Disabled"), 'calendarday', 'class="datecallink"').'</button>';
6213						$retsring = $retstringbutton.$retstring;
6214					}
6215
6216					$retstring .= '</div>';
6217					$retstring .= '<input type="hidden" id="'.$prefix.'day"   name="'.$prefix.'day"   value="'.$sday.'">'."\n";
6218					$retstring .= '<input type="hidden" id="'.$prefix.'month" name="'.$prefix.'month" value="'.$smonth.'">'."\n";
6219					$retstring .= '<input type="hidden" id="'.$prefix.'year"  name="'.$prefix.'year"  value="'.$syear.'">'."\n";
6220				} else {
6221					$retstring .= "Bad value of MAIN_POPUP_CALENDAR";
6222				}
6223			} else {
6224				// Show date with combo selects
6225				// Day
6226				$retstring .= '<select'.($disabled ? ' disabled' : '').' class="flat valignmiddle maxwidth50imp" id="'.$prefix.'day" name="'.$prefix.'day">';
6227
6228				if ($emptydate || $set_time == -1) {
6229					$retstring .= '<option value="0" selected>&nbsp;</option>';
6230				}
6231
6232				for ($day = 1; $day <= 31; $day++) {
6233					$retstring .= '<option value="'.$day.'"'.($day == $sday ? ' selected' : '').'>'.$day.'</option>';
6234				}
6235
6236				$retstring .= "</select>";
6237
6238				$retstring .= '<select'.($disabled ? ' disabled' : '').' class="flat valignmiddle maxwidth75imp" id="'.$prefix.'month" name="'.$prefix.'month">';
6239				if ($emptydate || $set_time == -1) {
6240					$retstring .= '<option value="0" selected>&nbsp;</option>';
6241				}
6242
6243				// Month
6244				for ($month = 1; $month <= 12; $month++) {
6245					$retstring .= '<option value="'.$month.'"'.($month == $smonth ? ' selected' : '').'>';
6246					$retstring .= dol_print_date(mktime(12, 0, 0, $month, 1, 2000), "%b");
6247					$retstring .= "</option>";
6248				}
6249				$retstring .= "</select>";
6250
6251				// Year
6252				if ($emptydate || $set_time == -1) {
6253					$retstring .= '<input'.($disabled ? ' disabled' : '').' placeholder="'.dol_escape_htmltag($langs->trans("Year")).'" class="flat maxwidth50imp valignmiddle" type="number" min="0" max="3000" maxlength="4" id="'.$prefix.'year" name="'.$prefix.'year" value="'.$syear.'">';
6254				} else {
6255					$retstring .= '<select'.($disabled ? ' disabled' : '').' class="flat valignmiddle maxwidth75imp" id="'.$prefix.'year" name="'.$prefix.'year">';
6256
6257					for ($year = $syear - 10; $year < $syear + 10; $year++) {
6258						$retstring .= '<option value="'.$year.'"'.($year == $syear ? ' selected' : '').'>'.$year.'</option>';
6259					}
6260					$retstring .= "</select>\n";
6261				}
6262			}
6263		}
6264
6265		if ($d && $h) {
6266			$retstring .= ($h == 2 ? '<br>' : ' ');
6267			$retstring .= '<span class="nowraponall">';
6268		}
6269
6270		if ($h) {
6271			$hourstart = 0;
6272			$hourend = 24;
6273			if ($openinghours != '') {
6274				$openinghours = explode(',', $openinghours);
6275				$hourstart = $openinghours[0];
6276				$hourend = $openinghours[1];
6277				if ($hourend < $hourstart) {
6278					$hourend = $hourstart;
6279				}
6280			}
6281			// Show hour
6282			$retstring .= '<select'.($disabled ? ' disabled' : '').' class="flat valignmiddle maxwidth50 '.($fullday ? $fullday.'hour' : '').'" id="'.$prefix.'hour" name="'.$prefix.'hour">';
6283			if ($emptyhours) {
6284				$retstring .= '<option value="-1">&nbsp;</option>';
6285			}
6286			for ($hour = $hourstart; $hour < $hourend; $hour++) {
6287				if (strlen($hour) < 2) {
6288					$hour = "0".$hour;
6289				}
6290				$retstring .= '<option value="'.$hour.'"'.(($hour == $shour) ? ' selected' : '').'>'.$hour;
6291				//$retstring .= (empty($conf->dol_optimize_smallscreen) ? '' : 'H');
6292				$retstring .= '</option>';
6293			}
6294			$retstring .= '</select>';
6295			//if ($m && empty($conf->dol_optimize_smallscreen)) $retstring .= ":";
6296			if ($m) {
6297				$retstring .= ":";
6298			}
6299		}
6300
6301		if ($m) {
6302			// Show minutes
6303			$retstring .= '<select'.($disabled ? ' disabled' : '').' class="flat valignmiddle maxwidth50 '.($fullday ? $fullday.'min' : '').'" id="'.$prefix.'min" name="'.$prefix.'min">';
6304			if ($emptyhours) {
6305				$retstring .= '<option value="-1">&nbsp;</option>';
6306			}
6307			for ($min = 0; $min < 60; $min += $stepminutes) {
6308				if (strlen($min) < 2) {
6309					$min = "0".$min;
6310				}
6311				$retstring .= '<option value="'.$min.'"'.(($min == $smin) ? ' selected' : '').'>'.$min.(empty($conf->dol_optimize_smallscreen) ? '' : '').'</option>';
6312			}
6313			$retstring .= '</select>';
6314
6315			$retstring .= '<input type="hidden" name="'.$prefix.'sec" value="'.$ssec.'">';
6316		}
6317
6318		if ($d && $h) {
6319			$retstring .= '</span>';
6320		}
6321
6322		// Add a "Now" link
6323		if ($conf->use_javascript_ajax && $addnowlink) {
6324			// Script which will be inserted in the onClick of the "Now" link
6325			$reset_scripts = "";
6326			if ($addnowlink == 2) { // local computer time
6327				// pad add leading 0 on numbers
6328				$reset_scripts .= "Number.prototype.pad = function(size) {
6329                        var s = String(this);
6330                        while (s.length < (size || 2)) {s = '0' + s;}
6331                        return s;
6332                    };
6333                    var d = new Date();";
6334			}
6335
6336			// Generate the date part, depending on the use or not of the javascript calendar
6337			if ($addnowlink == 1) { // server time expressed in user time setup
6338				$reset_scripts .= 'jQuery(\'#'.$prefix.'\').val(\''.dol_print_date($nowgmt, 'day', 'tzuserrel').'\');';
6339				$reset_scripts .= 'jQuery(\'#'.$prefix.'day\').val(\''.dol_print_date($nowgmt, '%d', 'tzuserrel').'\');';
6340				$reset_scripts .= 'jQuery(\'#'.$prefix.'month\').val(\''.dol_print_date($nowgmt, '%m', 'tzuserrel').'\');';
6341				$reset_scripts .= 'jQuery(\'#'.$prefix.'year\').val(\''.dol_print_date($nowgmt, '%Y', 'tzuserrel').'\');';
6342			} elseif ($addnowlink == 2) {
6343				/* Disabled because the output does not use the string format defined by FormatDateShort key to forge the value into #prefix.
6344				 * This break application for foreign languages.
6345				$reset_scripts .= 'jQuery(\'#'.$prefix.'\').val(d.toLocaleDateString(\''.str_replace('_', '-', $langs->defaultlang).'\'));';
6346				$reset_scripts .= 'jQuery(\'#'.$prefix.'day\').val(d.getDate().pad());';
6347				$reset_scripts .= 'jQuery(\'#'.$prefix.'month\').val(parseInt(d.getMonth().pad()) + 1);';
6348				$reset_scripts .= 'jQuery(\'#'.$prefix.'year\').val(d.getFullYear());';
6349				*/
6350				$reset_scripts .= 'jQuery(\'#'.$prefix.'\').val(\''.dol_print_date($nowgmt, 'day', 'tzuserrel').'\');';
6351				$reset_scripts .= 'jQuery(\'#'.$prefix.'day\').val(\''.dol_print_date($nowgmt, '%d', 'tzuserrel').'\');';
6352				$reset_scripts .= 'jQuery(\'#'.$prefix.'month\').val(\''.dol_print_date($nowgmt, '%m', 'tzuserrel').'\');';
6353				$reset_scripts .= 'jQuery(\'#'.$prefix.'year\').val(\''.dol_print_date($nowgmt, '%Y', 'tzuserrel').'\');';
6354			}
6355			/*if ($usecalendar == "eldy")
6356			{
6357				$base=DOL_URL_ROOT.'/core/';
6358				$reset_scripts .= 'resetDP(\''.$base.'\',\''.$prefix.'\',\''.$langs->trans("FormatDateShortJavaInput").'\',\''.$langs->defaultlang.'\');';
6359			}
6360			else
6361			{
6362				$reset_scripts .= 'this.form.elements[\''.$prefix.'day\'].value=formatDate(new Date(), \'d\'); ';
6363				$reset_scripts .= 'this.form.elements[\''.$prefix.'month\'].value=formatDate(new Date(), \'M\'); ';
6364				$reset_scripts .= 'this.form.elements[\''.$prefix.'year\'].value=formatDate(new Date(), \'yyyy\'); ';
6365			}*/
6366			// Update the hour part
6367			if ($h) {
6368				if ($fullday) {
6369					$reset_scripts .= " if (jQuery('#fullday:checked').val() == null) {";
6370				}
6371				//$reset_scripts .= 'this.form.elements[\''.$prefix.'hour\'].value=formatDate(new Date(), \'HH\'); ';
6372				if ($addnowlink == 1) {
6373					$reset_scripts .= 'jQuery(\'#'.$prefix.'hour\').val(\''.dol_print_date($nowgmt, '%H', 'tzuserrel').'\');';
6374					$reset_scripts .= 'jQuery(\'#'.$prefix.'hour\').change();';
6375				} elseif ($addnowlink == 2) {
6376					$reset_scripts .= 'jQuery(\'#'.$prefix.'hour\').val(d.getHours().pad());';
6377					$reset_scripts .= 'jQuery(\'#'.$prefix.'hour\').change();';
6378				}
6379
6380				if ($fullday) {
6381					$reset_scripts .= ' } ';
6382				}
6383			}
6384			// Update the minute part
6385			if ($m) {
6386				if ($fullday) {
6387					$reset_scripts .= " if (jQuery('#fullday:checked').val() == null) {";
6388				}
6389				//$reset_scripts .= 'this.form.elements[\''.$prefix.'min\'].value=formatDate(new Date(), \'mm\'); ';
6390				if ($addnowlink == 1) {
6391					$reset_scripts .= 'jQuery(\'#'.$prefix.'min\').val(\''.dol_print_date($nowgmt, '%M', 'tzuserrel').'\');';
6392					$reset_scripts .= 'jQuery(\'#'.$prefix.'min\').change();';
6393				} elseif ($addnowlink == 2) {
6394					$reset_scripts .= 'jQuery(\'#'.$prefix.'min\').val(d.getMinutes().pad());';
6395					$reset_scripts .= 'jQuery(\'#'.$prefix.'min\').change();';
6396				}
6397				if ($fullday) {
6398					$reset_scripts .= ' } ';
6399				}
6400			}
6401			// If reset_scripts is not empty, print the link with the reset_scripts in the onClick
6402			if ($reset_scripts && empty($conf->dol_optimize_smallscreen)) {
6403				$retstring .= ' <button class="dpInvisibleButtons datenowlink" id="'.$prefix.'ButtonNow" type="button" name="_useless" value="now" onClick="'.$reset_scripts.'">';
6404				$retstring .= $langs->trans("Now");
6405				$retstring .= '</button> ';
6406			}
6407		}
6408
6409		// Add a "Plus one hour" link
6410		if ($conf->use_javascript_ajax && $addplusone) {
6411			// Script which will be inserted in the onClick of the "Add plusone" link
6412			$reset_scripts = "";
6413
6414			// Generate the date part, depending on the use or not of the javascript calendar
6415			$reset_scripts .= 'jQuery(\'#'.$prefix.'\').val(\''.dol_print_date($nowgmt, 'dayinputnoreduce', 'tzuserrel').'\');';
6416			$reset_scripts .= 'jQuery(\'#'.$prefix.'day\').val(\''.dol_print_date($nowgmt, '%d', 'tzuserrel').'\');';
6417			$reset_scripts .= 'jQuery(\'#'.$prefix.'month\').val(\''.dol_print_date($nowgmt, '%m', 'tzuserrel').'\');';
6418			$reset_scripts .= 'jQuery(\'#'.$prefix.'year\').val(\''.dol_print_date($nowgmt, '%Y', 'tzuserrel').'\');';
6419			// Update the hour part
6420			if ($h) {
6421				if ($fullday) {
6422					$reset_scripts .= " if (jQuery('#fullday:checked').val() == null) {";
6423				}
6424				$reset_scripts .= 'jQuery(\'#'.$prefix.'hour\').val(\''.dol_print_date($nowgmt, '%H', 'tzuserrel').'\');';
6425				if ($fullday) {
6426					$reset_scripts .= ' } ';
6427				}
6428			}
6429			// Update the minute part
6430			if ($m) {
6431				if ($fullday) {
6432					$reset_scripts .= " if (jQuery('#fullday:checked').val() == null) {";
6433				}
6434				$reset_scripts .= 'jQuery(\'#'.$prefix.'min\').val(\''.dol_print_date($nowgmt, '%M', 'tzuserrel').'\');';
6435				if ($fullday) {
6436					$reset_scripts .= ' } ';
6437				}
6438			}
6439			// If reset_scripts is not empty, print the link with the reset_scripts in the onClick
6440			if ($reset_scripts && empty($conf->dol_optimize_smallscreen)) {
6441				$retstring .= ' <button class="dpInvisibleButtons datenowlink" id="'.$prefix.'ButtonPlusOne" type="button" name="_useless2" value="plusone" onClick="'.$reset_scripts.'">';
6442				$retstring .= $langs->trans("DateStartPlusOne");
6443				$retstring .= '</button> ';
6444			}
6445		}
6446
6447		// Add a link to set data
6448		if ($conf->use_javascript_ajax && $adddateof) {
6449			$tmparray = dol_getdate($adddateof);
6450			if (empty($labeladddateof)) {
6451				$labeladddateof = $langs->trans("DateInvoice");
6452			}
6453			$retstring .= ' - <button class="dpInvisibleButtons datenowlink" id="dateofinvoice" type="button" name="_dateofinvoice" value="now" onclick="console.log(\'Click on now link\'); jQuery(\'#re\').val(\''.dol_print_date($adddateof, 'dayinputnoreduce').'\');jQuery(\'#reday\').val(\''.$tmparray['mday'].'\');jQuery(\'#remonth\').val(\''.$tmparray['mon'].'\');jQuery(\'#reyear\').val(\''.$tmparray['year'].'\');">'.$labeladddateof.'</a>';
6454		}
6455
6456		return $retstring;
6457	}
6458
6459	/**
6460	 * selectTypeDuration
6461	 *
6462	 * @param   string   	$prefix     	Prefix
6463	 * @param   string   	$selected   	Selected duration type
6464	 * @param	array		$excludetypes	Array of duration types to exclude. Example array('y', 'm')
6465	 * @return  string      	         	HTML select string
6466	 */
6467	public function selectTypeDuration($prefix, $selected = 'i', $excludetypes = array())
6468	{
6469		global $langs;
6470
6471		$TDurationTypes = array(
6472			'y'=>$langs->trans('Years'),
6473			'm'=>$langs->trans('Month'),
6474			'w'=>$langs->trans('Weeks'),
6475			'd'=>$langs->trans('Days'),
6476			'h'=>$langs->trans('Hours'),
6477			'i'=>$langs->trans('Minutes')
6478		);
6479
6480		// Removed undesired duration types
6481		foreach ($excludetypes as $value) {
6482			unset($TDurationTypes[$value]);
6483		}
6484
6485		$retstring = '<select class="flat" id="select_'.$prefix.'type_duration" name="'.$prefix.'type_duration">';
6486		foreach ($TDurationTypes as $key => $typeduration) {
6487			$retstring .= '<option value="'.$key.'"';
6488			if ($key == $selected) {
6489				$retstring .= " selected";
6490			}
6491			$retstring .= ">".$typeduration."</option>";
6492		}
6493		$retstring .= "</select>";
6494
6495		$retstring .= ajax_combobox('select_'.$prefix.'type_duration');
6496
6497		return $retstring;
6498	}
6499
6500	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6501	/**
6502	 *  Function to show a form to select a duration on a page
6503	 *
6504	 *	@param	string		$prefix   		Prefix for input fields
6505	 *	@param  int			$iSecond  		Default preselected duration (number of seconds or '')
6506	 * 	@param	int			$disabled       Disable the combo box
6507	 * 	@param	string		$typehour		If 'select' then input hour and input min is a combo,
6508	 *						            	If 'text' input hour is in text and input min is a text,
6509	 *						            	If 'textselect' input hour is in text and input min is a combo
6510	 *  @param	integer		$minunderhours	If 1, show minutes selection under the hours
6511	 * 	@param	int			$nooutput		Do not output html string but return it
6512	 *  @return	string|void
6513	 */
6514	public function select_duration($prefix, $iSecond = '', $disabled = 0, $typehour = 'select', $minunderhours = 0, $nooutput = 0)
6515	{
6516		// phpcs:enable
6517		global $langs;
6518
6519		$retstring = '';
6520
6521		$hourSelected = 0;
6522		$minSelected = 0;
6523
6524		// Hours
6525		if ($iSecond != '') {
6526			require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
6527
6528			$hourSelected = convertSecondToTime($iSecond, 'allhour');
6529			$minSelected = convertSecondToTime($iSecond, 'min');
6530		}
6531
6532		if ($typehour == 'select') {
6533			$retstring .= '<select class="flat" id="select_'.$prefix.'hour" name="'.$prefix.'hour"'.($disabled ? ' disabled' : '').'>';
6534			for ($hour = 0; $hour < 25; $hour++) {	// For a duration, we allow 24 hours
6535				$retstring .= '<option value="'.$hour.'"';
6536				if ($hourSelected == $hour) {
6537					$retstring .= " selected";
6538				}
6539				$retstring .= ">".$hour."</option>";
6540			}
6541			$retstring .= "</select>";
6542		} elseif ($typehour == 'text' || $typehour == 'textselect') {
6543			$retstring .= '<input placeholder="'.$langs->trans('HourShort').'" type="number" min="0" name="'.$prefix.'hour"'.($disabled ? ' disabled' : '').' class="flat maxwidth50 inputhour" value="'.(($hourSelected != '') ? ((int) $hourSelected) : '').'">';
6544		} else {
6545			return 'BadValueForParameterTypeHour';
6546		}
6547
6548		if ($typehour != 'text') {
6549			$retstring .= ' '.$langs->trans('HourShort');
6550		} else {
6551			$retstring .= '<span class="hideonsmartphone">:</span>';
6552		}
6553
6554		// Minutes
6555		if ($minunderhours) {
6556			$retstring .= '<br>';
6557		} else {
6558			$retstring .= '<span class="hideonsmartphone">&nbsp;</span>';
6559		}
6560
6561		if ($typehour == 'select' || $typehour == 'textselect') {
6562			$retstring .= '<select class="flat" id="select_'.$prefix.'min" name="'.$prefix.'min"'.($disabled ? ' disabled' : '').'>';
6563			for ($min = 0; $min <= 55; $min = $min + 5) {
6564				$retstring .= '<option value="'.$min.'"';
6565				if ($minSelected == $min) {
6566					$retstring .= ' selected';
6567				}
6568				$retstring .= '>'.$min.'</option>';
6569			}
6570			$retstring .= "</select>";
6571		} elseif ($typehour == 'text') {
6572			$retstring .= '<input placeholder="'.$langs->trans('MinuteShort').'" type="number" min="0" name="'.$prefix.'min"'.($disabled ? ' disabled' : '').' class="flat maxwidth50 inputminute" value="'.(($minSelected != '') ? ((int) $minSelected) : '').'">';
6573		}
6574
6575		if ($typehour != 'text') {
6576			$retstring .= ' '.$langs->trans('MinuteShort');
6577		}
6578
6579		//$retstring.="&nbsp;";
6580
6581		if (!empty($nooutput)) {
6582			return $retstring;
6583		}
6584
6585		print $retstring;
6586		return;
6587	}
6588
6589
6590	/**
6591	 * Generic method to select a component from a combo list.
6592	 * Can use autocomplete with ajax after x key pressed or a full combo, depending on setup.
6593	 * This is the generic method that will replace all specific existing methods.
6594	 *
6595	 * @param 	string			$objectdesc			ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter]]
6596	 * @param	string			$htmlname			Name of HTML select component
6597	 * @param	int				$preselectedvalue	Preselected value (ID of element)
6598	 * @param	string			$showempty			''=empty values not allowed, 'string'=value show if we allow empty values (for example 'All', ...)
6599	 * @param	string			$searchkey			Search criteria
6600	 * @param	string			$placeholder		Place holder
6601	 * @param	string			$morecss			More CSS
6602	 * @param	string			$moreparams			More params provided to ajax call
6603	 * @param	int				$forcecombo			Force to load all values and output a standard combobox (with no beautification)
6604	 * @param	int				$disabled			1=Html component is disabled
6605	 * @param	string	        $selected_input_value	Value of preselected input text (for use with ajax)
6606	 * @return	string								Return HTML string
6607	 * @see selectForFormsList() select_thirdparty_list()
6608	 */
6609	public function selectForForms($objectdesc, $htmlname, $preselectedvalue, $showempty = '', $searchkey = '', $placeholder = '', $morecss = '', $moreparams = '', $forcecombo = 0, $disabled = 0, $selected_input_value = '')
6610	{
6611		global $conf, $user;
6612
6613		$objecttmp = null;
6614
6615		$InfoFieldList = explode(":", $objectdesc);
6616		$classname = $InfoFieldList[0];
6617		$classpath = $InfoFieldList[1];
6618		$addcreatebuttonornot = empty($InfoFieldList[2]) ? 0 : $InfoFieldList[2];
6619		$filter = empty($InfoFieldList[3]) ? '' : $InfoFieldList[3];
6620
6621		if (!empty($classpath)) {
6622			dol_include_once($classpath);
6623
6624			if ($classname && class_exists($classname)) {
6625				$objecttmp = new $classname($this->db);
6626				// Make some replacement
6627				$sharedentities = getEntity(strtolower($classname));
6628				$objecttmp->filter = str_replace(
6629					array('__ENTITY__', '__SHARED_ENTITIES__', '__USER_ID__'),
6630					array($conf->entity, $sharedentities, $user->id),
6631					$filter
6632				);
6633			}
6634		}
6635		if (!is_object($objecttmp)) {
6636			dol_syslog('Error bad setup of type for field '.$InfoFieldList, LOG_WARNING);
6637			return 'Error bad setup of type for field '.join(',', $InfoFieldList);
6638		}
6639
6640		//var_dump($objecttmp->filter);
6641		$prefixforautocompletemode = $objecttmp->element;
6642		if ($prefixforautocompletemode == 'societe') {
6643			$prefixforautocompletemode = 'company';
6644		}
6645		if ($prefixforautocompletemode == 'product') {
6646			$prefixforautocompletemode = 'produit';
6647		}
6648		$confkeyforautocompletemode = strtoupper($prefixforautocompletemode).'_USE_SEARCH_TO_SELECT'; // For example COMPANY_USE_SEARCH_TO_SELECT
6649
6650		dol_syslog(get_class($this)."::selectForForms object->filter=".$objecttmp->filter, LOG_DEBUG);
6651		$out = '';
6652		if (!empty($conf->use_javascript_ajax) && !empty($conf->global->$confkeyforautocompletemode) && !$forcecombo) {
6653			// No immediate load of all database
6654			$placeholder = '';
6655			if ($preselectedvalue && empty($selected_input_value)) {
6656				$objecttmp->fetch($preselectedvalue);
6657				$selected_input_value = ($prefixforautocompletemode == 'company' ? $objecttmp->name : $objecttmp->ref);
6658				//unset($objecttmp);
6659			}
6660
6661			$objectdesc = $classname.':'.$classpath.':'.$addcreatebuttonornot.':'.$filter;
6662			$urlforajaxcall = DOL_URL_ROOT.'/core/ajax/selectobject.php';
6663
6664			// No immediate load of all database
6665			$urloption = 'htmlname='.$htmlname.'&outjson=1&objectdesc='.$objectdesc.'&filter='.urlencode($objecttmp->filter);
6666			// Activate the auto complete using ajax call.
6667			$out .= ajax_autocompleter($preselectedvalue, $htmlname, $urlforajaxcall, $urloption, $conf->global->$confkeyforautocompletemode, 0, array());
6668			$out .= '<style type="text/css">.ui-autocomplete { z-index: 1003; }</style>';
6669			$out .= '<input type="text" class="'.$morecss.'"'.($disabled ? ' disabled="disabled"' : '').' name="search_'.$htmlname.'" id="search_'.$htmlname.'" value="'.$selected_input_value.'"'.($placeholder ? ' placeholder="'.dol_escape_htmltag($placeholder).'"' : '') .' />';
6670		} else {
6671			// Immediate load of table record. Note: filter is inside $objecttmp->filter
6672			$out .= $this->selectForFormsList($objecttmp, $htmlname, $preselectedvalue, $showempty, $searchkey, $placeholder, $morecss, $moreparams, $forcecombo, 0, $disabled);
6673		}
6674
6675		return $out;
6676	}
6677
6678	/**
6679	 * Function to forge a SQL criteria
6680	 *
6681	 * @param  array    $matches       Array of found string by regex search. Example: "t.ref:like:'SO-%'" or "t.date_creation:<:'20160101'" or "t.nature:is:NULL"
6682	 * @return string                  Forged criteria. Example: "t.field like 'abc%'"
6683	 */
6684	protected static function forgeCriteriaCallback($matches)
6685	{
6686		global $db;
6687
6688		//dol_syslog("Convert matches ".$matches[1]);
6689		if (empty($matches[1])) {
6690			return '';
6691		}
6692		$tmp = explode(':', $matches[1]);
6693		if (count($tmp) < 3) {
6694			return '';
6695		}
6696
6697		$tmpescaped = $tmp[2];
6698		$regbis = array();
6699		if (preg_match('/^\'(.*)\'$/', $tmpescaped, $regbis)) {
6700			$tmpescaped = "'".$db->escape($regbis[1])."'";
6701		} else {
6702			$tmpescaped = $db->escape($tmpescaped);
6703		}
6704		return $db->escape($tmp[0]).' '.strtoupper($db->escape($tmp[1]))." ".$tmpescaped;
6705	}
6706
6707	/**
6708	 * Output html form to select an object.
6709	 * Note, this function is called by selectForForms or by ajax selectobject.php
6710	 *
6711	 * @param 	Object			$objecttmp			Object to knwo the table to scan for combo.
6712	 * @param	string			$htmlname			Name of HTML select component
6713	 * @param	int				$preselectedvalue	Preselected value (ID of element)
6714	 * @param	string			$showempty			''=empty values not allowed, 'string'=value show if we allow empty values (for example 'All', ...)
6715	 * @param	string			$searchkey			Search value
6716	 * @param	string			$placeholder		Place holder
6717	 * @param	string			$morecss			More CSS
6718	 * @param	string			$moreparams			More params provided to ajax call
6719	 * @param	int				$forcecombo			Force to load all values and output a standard combobox (with no beautification)
6720	 * @param	int				$outputmode			0=HTML select string, 1=Array
6721	 * @param	int				$disabled			1=Html component is disabled
6722	 * @return	string|array						Return HTML string
6723	 * @see selectForForms()
6724	 */
6725	public function selectForFormsList($objecttmp, $htmlname, $preselectedvalue, $showempty = '', $searchkey = '', $placeholder = '', $morecss = '', $moreparams = '', $forcecombo = 0, $outputmode = 0, $disabled = 0)
6726	{
6727		global $conf, $langs, $user, $hookmanager;
6728
6729		//print "$objecttmp->filter, $htmlname, $preselectedvalue, $showempty = '', $searchkey = '', $placeholder = '', $morecss = '', $moreparams = '', $forcecombo = 0, $outputmode = 0, $disabled";
6730
6731		$prefixforautocompletemode = $objecttmp->element;
6732		if ($prefixforautocompletemode == 'societe') {
6733			$prefixforautocompletemode = 'company';
6734		}
6735		$confkeyforautocompletemode = strtoupper($prefixforautocompletemode).'_USE_SEARCH_TO_SELECT'; // For example COMPANY_USE_SEARCH_TO_SELECT
6736
6737		if (!empty($objecttmp->fields)) {	// For object that declare it, it is better to use declared fields (like societe, contact, ...)
6738			$tmpfieldstoshow = '';
6739			foreach ($objecttmp->fields as $key => $val) {
6740				if (!dol_eval($val['enabled'], 1, 1)) {
6741					continue;
6742				}
6743				if (!empty($val['showoncombobox'])) {
6744					$tmpfieldstoshow .= ($tmpfieldstoshow ? ',' : '').'t.'.$key;
6745				}
6746			}
6747			if ($tmpfieldstoshow) {
6748				$fieldstoshow = $tmpfieldstoshow;
6749			}
6750		} else {
6751			// For backward compatibility
6752			$objecttmp->fields['ref'] = array('type'=>'varchar(30)', 'label'=>'Ref', 'showoncombobox'=>1);
6753		}
6754
6755		if (empty($fieldstoshow)) {
6756			if (isset($objecttmp->fields['ref'])) {
6757				$fieldstoshow = 't.ref';
6758			} else {
6759				$langs->load("errors");
6760				$this->error = $langs->trans("ErrorNoFieldWithAttributeShowoncombobox");
6761				return $langs->trans('ErrorNoFieldWithAttributeShowoncombobox');
6762			}
6763		}
6764
6765		$out = '';
6766		$outarray = array();
6767
6768		$num = 0;
6769
6770		// Search data
6771		$sql = "SELECT t.rowid, ".$fieldstoshow." FROM ".MAIN_DB_PREFIX.$objecttmp->table_element." as t";
6772		if (isset($objecttmp->ismultientitymanaged)) {
6773			if (!is_numeric($objecttmp->ismultientitymanaged)) {
6774				$tmparray = explode('@', $objecttmp->ismultientitymanaged);
6775				$sql .= " INNER JOIN ".MAIN_DB_PREFIX.$tmparray[1]." as parenttable ON parenttable.rowid = t.".$tmparray[0];
6776			}
6777			if ($objecttmp->ismultientitymanaged === 'fk_soc@societe') {
6778				if (!$user->rights->societe->client->voir && !$user->socid) {
6779					$sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
6780				}
6781			}
6782		}
6783
6784		// Add where from hooks
6785		$parameters = array();
6786		$reshook = $hookmanager->executeHooks('selectForFormsListWhere', $parameters); // Note that $action and $object may have been modified by hook
6787		if (!empty($hookmanager->resPrint)) {
6788			$sql .= $hookmanager->resPrint;
6789		} else {
6790			$sql .= " WHERE 1=1";
6791			if (isset($objecttmp->ismultientitymanaged)) {
6792				if ($objecttmp->ismultientitymanaged == 1) {
6793					$sql .= " AND t.entity IN (".getEntity($objecttmp->table_element).")";
6794				}
6795				if (!is_numeric($objecttmp->ismultientitymanaged)) {
6796					$sql .= " AND parenttable.entity = t.".$tmparray[0];
6797				}
6798				if ($objecttmp->ismultientitymanaged == 1 && !empty($user->socid)) {
6799					if ($objecttmp->element == 'societe') {
6800						$sql .= " AND t.rowid = ".((int) $user->socid);
6801					} else {
6802						$sql .= " AND t.fk_soc = ".((int) $user->socid);
6803					}
6804				}
6805				if ($objecttmp->ismultientitymanaged === 'fk_soc@societe') {
6806					if (!$user->rights->societe->client->voir && !$user->socid) {
6807						$sql .= " AND t.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
6808					}
6809				}
6810			}
6811			if ($searchkey != '') {
6812				$sql .= natural_search(explode(',', $fieldstoshow), $searchkey);
6813			}
6814			if ($objecttmp->ismultientitymanaged == 'fk_soc@societe') {
6815				if (!$user->rights->societe->client->voir && !$user->socid) {
6816					$sql .= " AND t.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
6817				}
6818			}
6819			if ($objecttmp->filter) {	 // Syntax example "(t.ref:like:'SO-%') and (t.date_creation:<:'20160101')"
6820				/*if (! DolibarrApi::_checkFilters($objecttmp->filter))
6821				{
6822					throw new RestException(503, 'Error when validating parameter sqlfilters '.$objecttmp->filter);
6823				}*/
6824				$regexstring = '\(([^:\'\(\)]+:[^:\'\(\)]+:[^\(\)]+)\)';
6825				$sql .= " AND (".preg_replace_callback('/'.$regexstring.'/', 'Form::forgeCriteriaCallback', $objecttmp->filter).")";
6826			}
6827		}
6828		$sql .= $this->db->order($fieldstoshow, "ASC");
6829		//$sql.=$this->db->plimit($limit, 0);
6830		//print $sql;
6831
6832		// Build output string
6833		$resql = $this->db->query($sql);
6834		if ($resql) {
6835			// Construct $out and $outarray
6836			$out .= '<select id="'.$htmlname.'" class="flat'.($morecss ? ' '.$morecss : '').'"'.($disabled ? ' disabled="disabled"' : '').($moreparams ? ' '.$moreparams : '').' name="'.$htmlname.'">'."\n";
6837
6838			// Warning: Do not use textifempty = ' ' or '&nbsp;' here, or search on key will search on ' key'. Seems it is no more true with selec2 v4
6839			$textifempty = '&nbsp;';
6840
6841			//if (! empty($conf->use_javascript_ajax) || $forcecombo) $textifempty='';
6842			if (!empty($conf->global->$confkeyforautocompletemode)) {
6843				if ($showempty && !is_numeric($showempty)) {
6844					$textifempty = $langs->trans($showempty);
6845				} else {
6846					$textifempty .= $langs->trans("All");
6847				}
6848			}
6849			if ($showempty) {
6850				$out .= '<option value="-1">'.$textifempty.'</option>'."\n";
6851			}
6852
6853			$num = $this->db->num_rows($resql);
6854			$i = 0;
6855			if ($num) {
6856				while ($i < $num) {
6857					$obj = $this->db->fetch_object($resql);
6858					$label = '';
6859					$tmparray = explode(',', $fieldstoshow);
6860					$oldvalueforshowoncombobox = 0;
6861					foreach ($tmparray as $key => $val) {
6862						$val = preg_replace('/t\./', '', $val);
6863						$label .= (($label && $obj->$val) ? ($oldvalueforshowoncombobox != $objecttmp->fields[$val]['showoncombobox'] ? ' - ' : ' ') : '');
6864						$label .= $obj->$val;
6865						$oldvalueforshowoncombobox = $objecttmp->fields[$val]['showoncombobox'];
6866					}
6867					if (empty($outputmode)) {
6868						if ($preselectedvalue > 0 && $preselectedvalue == $obj->rowid) {
6869							$out .= '<option value="'.$obj->rowid.'" selected>'.$label.'</option>';
6870						} else {
6871							$out .= '<option value="'.$obj->rowid.'">'.$label.'</option>';
6872						}
6873					} else {
6874						array_push($outarray, array('key'=>$obj->rowid, 'value'=>$label, 'label'=>$label));
6875					}
6876
6877					$i++;
6878					if (($i % 10) == 0) {
6879						$out .= "\n";
6880					}
6881				}
6882			}
6883
6884			$out .= '</select>'."\n";
6885
6886			if (!$forcecombo) {
6887				include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
6888				$out .= ajax_combobox($htmlname, null, (!empty($conf->global->$confkeyforautocompletemode) ? $conf->global->$confkeyforautocompletemode : 0));
6889			}
6890		} else {
6891			dol_print_error($this->db);
6892		}
6893
6894		$this->result = array('nbofelement'=>$num);
6895
6896		if ($outputmode) {
6897			return $outarray;
6898		}
6899		return $out;
6900	}
6901
6902
6903	/**
6904	 *	Return a HTML select string, built from an array of key+value.
6905	 *  Note: Do not apply langs->trans function on returned content, content may be entity encoded twice.
6906	 *
6907	 *	@param	string			$htmlname			Name of html select area. Must start with "multi" if this is a multiselect
6908	 *	@param	array			$array				Array like array(key => value) or array(key=>array('label'=>..., 'data-...'=>..., 'disabled'=>..., 'css'=>...))
6909	 *	@param	string|string[]	$id					Preselected key or preselected keys for multiselect
6910	 *	@param	int|string		$show_empty			0 no empty value allowed, 1 or string to add an empty value into list (If 1: key is -1 and value is '' or '&nbsp;', If placeholder string: key is -1 and value is the string), <0 to add an empty value with key that is this value.
6911	 *	@param	int				$key_in_label		1 to show key into label with format "[key] value"
6912	 *	@param	int				$value_as_key		1 to use value as key
6913	 *	@param  string			$moreparam			Add more parameters onto the select tag. For example 'style="width: 95%"' to avoid select2 component to go over parent container
6914	 *	@param  int				$translate			1=Translate and encode value
6915	 * 	@param	int				$maxlen				Length maximum for labels
6916	 * 	@param	int				$disabled			Html select box is disabled
6917	 *  @param	string			$sort				'ASC' or 'DESC' = Sort on label, '' or 'NONE' or 'POS' = Do not sort, we keep original order
6918	 *  @param	string			$morecss			Add more class to css styles
6919	 *  @param	int				$addjscombo			Add js combo
6920	 *  @param  string          $moreparamonempty	Add more param on the empty option line. Not used if show_empty not set
6921	 *  @param  int             $disablebademail	1=Check if a not valid email, 2=Check string '---', and if found into value, disable and colorize entry
6922	 *  @param  int             $nohtmlescape		No html escaping.
6923	 * 	@return	string								HTML select string.
6924	 *  @see multiselectarray(), selectArrayAjax(), selectArrayFilter()
6925	 */
6926	public static function selectarray($htmlname, $array, $id = '', $show_empty = 0, $key_in_label = 0, $value_as_key = 0, $moreparam = '', $translate = 0, $maxlen = 0, $disabled = 0, $sort = '', $morecss = '', $addjscombo = 1, $moreparamonempty = '', $disablebademail = 0, $nohtmlescape = 0)
6927	{
6928		global $conf, $langs;
6929
6930		// Do we want a multiselect ?
6931		//$jsbeautify = 0;
6932		//if (preg_match('/^multi/',$htmlname)) $jsbeautify = 1;
6933		$jsbeautify = 1;
6934
6935		if ($value_as_key) {
6936			$array = array_combine($array, $array);
6937		}
6938
6939		$out = '';
6940
6941		if ($addjscombo < 0) {
6942			if (empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
6943				$addjscombo = 1;
6944			} else {
6945				$addjscombo = 0;
6946			}
6947		}
6948
6949		// Add code for jquery to use multiselect
6950		if ($addjscombo && $jsbeautify) {
6951			// Enhance with select2
6952			include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
6953			$out .= ajax_combobox($htmlname, array(), 0, 0, 'resolve', $show_empty < 0 ? (string) $show_empty : '-1');
6954		}
6955
6956		$out .= '<select id="'.preg_replace('/^\./', '', $htmlname).'" '.($disabled ? 'disabled="disabled" ' : '').'class="flat '.(preg_replace('/^\./', '', $htmlname)).($morecss ? ' '.$morecss : '').'"';
6957		$out .= ' name="'.preg_replace('/^\./', '', $htmlname).'" '.($moreparam ? $moreparam : '');
6958		$out .= '>';
6959
6960		if ($show_empty) {
6961			$textforempty = ' ';
6962			if (!empty($conf->use_javascript_ajax)) {
6963				$textforempty = '&nbsp;'; // If we use ajaxcombo, we need &nbsp; here to avoid to have an empty element that is too small.
6964			}
6965			if (!is_numeric($show_empty)) {
6966				$textforempty = $show_empty;
6967			}
6968			$out .= '<option class="optiongrey" '.($moreparamonempty ? $moreparamonempty.' ' : '').'value="'.($show_empty < 0 ? $show_empty : -1).'"'.($id == $show_empty ? ' selected' : '').'>'.$textforempty.'</option>'."\n";
6969		}
6970
6971		if (is_array($array)) {
6972			// Translate
6973			if ($translate) {
6974				foreach ($array as $key => $value) {
6975					if (!is_array($value)) {
6976						$array[$key] = $langs->trans($value);
6977					} else {
6978						$array[$key]['label'] = $langs->trans($value['label']);
6979					}
6980				}
6981			}
6982
6983			// Sort
6984			if ($sort == 'ASC') {
6985				asort($array);
6986			} elseif ($sort == 'DESC') {
6987				arsort($array);
6988			}
6989
6990			foreach ($array as $key => $tmpvalue) {
6991				if (is_array($tmpvalue)) {
6992					$value = $tmpvalue['label'];
6993					$disabled = empty($tmpvalue['disabled']) ? '' : ' disabled';
6994					$style = empty($tmpvalue['css']) ? ' class="'.$tmpvalue['css'].'"' : '';
6995				} else {
6996					$value = $tmpvalue;
6997					$disabled = '';
6998					$style = '';
6999				}
7000				if (!empty($disablebademail)) {
7001					if (($disablebademail == 1 && !preg_match('/&lt;.+@.+&gt;/', $value))
7002						|| ($disablebademail == 2 && preg_match('/---/', $value))) {
7003						$disabled = ' disabled';
7004						$style = ' class="warning"';
7005					}
7006				}
7007
7008				if ($key_in_label) {
7009					if (empty($nohtmlescape)) {
7010						$selectOptionValue = dol_escape_htmltag($key.' - '.($maxlen ?dol_trunc($value, $maxlen) : $value));
7011					} else {
7012						$selectOptionValue = $key.' - '.($maxlen ?dol_trunc($value, $maxlen) : $value);
7013					}
7014				} else {
7015					if (empty($nohtmlescape)) {
7016						$selectOptionValue = dol_escape_htmltag($maxlen ?dol_trunc($value, $maxlen) : $value);
7017					} else {
7018						$selectOptionValue = $maxlen ?dol_trunc($value, $maxlen) : $value;
7019					}
7020					if ($value == '' || $value == '-') {
7021						$selectOptionValue = '&nbsp;';
7022					}
7023				}
7024
7025				$out .= '<option value="'.$key.'"';
7026				$out .= $style.$disabled;
7027				if (is_array($id)) {
7028					if (in_array($key, $id) && !$disabled) {
7029						$out .= ' selected'; // To preselect a value
7030					}
7031				} else {
7032					$id = (string) $id; // if $id = 0, then $id = '0'
7033					if ($id != '' && $id == $key && !$disabled) {
7034						$out .= ' selected'; // To preselect a value
7035					}
7036				}
7037				if ($nohtmlescape) {
7038					$out .= ' data-html="'.dol_escape_htmltag($selectOptionValue).'"';
7039				}
7040				if (is_array($tmpvalue)) {
7041					foreach ($tmpvalue as $keyforvalue => $valueforvalue) {
7042						if (preg_match('/^data-/', $keyforvalue)) {
7043							$out .= ' '.$keyforvalue.'="'.$valueforvalue.'"';
7044						}
7045					}
7046				}
7047				$out .= '>';
7048				//var_dump($selectOptionValue);
7049				$out .= $selectOptionValue;
7050				$out .= "</option>\n";
7051			}
7052		}
7053
7054		$out .= "</select>";
7055		return $out;
7056	}
7057
7058
7059	/**
7060	 *	Return a HTML select string, built from an array of key+value, but content returned into select come from an Ajax call of an URL.
7061	 *  Note: Do not apply langs->trans function on returned content of Ajax service, content may be entity encoded twice.
7062	 *
7063	 *	@param	string	$htmlname       		Name of html select area
7064	 *	@param	string	$url					Url. Must return a json_encode of array(key=>array('text'=>'A text', 'url'=>'An url'), ...)
7065	 *	@param	string	$id             		Preselected key
7066	 *	@param  string	$moreparam      		Add more parameters onto the select tag
7067	 *	@param  string	$moreparamtourl 		Add more parameters onto the Ajax called URL
7068	 * 	@param	int		$disabled				Html select box is disabled
7069	 *  @param	int		$minimumInputLength		Minimum Input Length
7070	 *  @param	string	$morecss				Add more class to css styles
7071	 *  @param  int     $callurlonselect        If set to 1, some code is added so an url return by the ajax is called when value is selected.
7072	 *  @param  string  $placeholder            String to use as placeholder
7073	 *  @param  integer $acceptdelayedhtml      1 = caller is requesting to have html js content not returned but saved into global $delayedhtmlcontent (so caller can show it at end of page to avoid flash FOUC effect)
7074	 * 	@return	string   						HTML select string
7075	 *  @see selectArrayFilter(), ajax_combobox() in ajax.lib.php
7076	 */
7077	public static function selectArrayAjax($htmlname, $url, $id = '', $moreparam = '', $moreparamtourl = '', $disabled = 0, $minimumInputLength = 1, $morecss = '', $callurlonselect = 0, $placeholder = '', $acceptdelayedhtml = 0)
7078	{
7079		global $conf, $langs;
7080		global $delayedhtmlcontent;	// Will be used later outside of this function
7081
7082		// TODO Use an internal dolibarr component instead of select2
7083		if (empty($conf->global->MAIN_USE_JQUERY_MULTISELECT) && !defined('REQUIRE_JQUERY_MULTISELECT')) {
7084			return '';
7085		}
7086
7087		$out = '<select type="text" class="'.$htmlname.($morecss ? ' '.$morecss : '').'" '.($moreparam ? $moreparam.' ' : '').'name="'.$htmlname.'"></select>';
7088
7089		$outdelayed = '';
7090		if (!empty($conf->use_javascript_ajax)) {
7091			$tmpplugin = 'select2';
7092			$outdelayed = "\n".'<!-- JS CODE TO ENABLE '.$tmpplugin.' for id '.$htmlname.' -->
7093		    	<script>
7094		    	$(document).ready(function () {
7095
7096	    	        '.($callurlonselect ? 'var saveRemoteData = [];' : '').'
7097
7098	                $(".'.$htmlname.'").select2({
7099				    	ajax: {
7100					    	dir: "ltr",
7101					    	url: "'.$url.'",
7102					    	dataType: \'json\',
7103					    	delay: 250,
7104					    	data: function (params) {
7105					    		return {
7106							    	q: params.term, 	// search term
7107					    			page: params.page
7108					    		};
7109				    		},
7110				    		processResults: function (data) {
7111				    			// parse the results into the format expected by Select2.
7112				    			// since we are using custom formatting functions we do not need to alter the remote JSON data
7113				    			//console.log(data);
7114								saveRemoteData = data;
7115					    	    /* format json result for select2 */
7116					    	    result = []
7117					    	    $.each( data, function( key, value ) {
7118					    	       result.push({id: key, text: value.text});
7119	                            });
7120				    			//return {results:[{id:\'none\', text:\'aa\'}, {id:\'rrr\', text:\'Red\'},{id:\'bbb\', text:\'Search a into projects\'}], more:false}
7121				    			//console.log(result);
7122				    			return {results: result, more: false}
7123				    		},
7124				    		cache: true
7125				    	},
7126		 				language: select2arrayoflanguage,
7127						containerCssClass: \':all:\',					/* Line to add class of origin SELECT propagated to the new <span class="select2-selection...> tag */
7128					    placeholder: "'.dol_escape_js($placeholder).'",
7129				    	escapeMarkup: function (markup) { return markup; }, 	// let our custom formatter work
7130				    	minimumInputLength: '.$minimumInputLength.',
7131				        formatResult: function(result, container, query, escapeMarkup) {
7132	                        return escapeMarkup(result.text);
7133	                    },
7134				    });
7135
7136	                '.($callurlonselect ? '
7137	                /* Code to execute a GET when we select a value */
7138	                $(".'.$htmlname.'").change(function() {
7139				    	var selected = $(".'.$htmlname.'").val();
7140	                	console.log("We select in selectArrayAjax the entry "+selected)
7141				        $(".'.$htmlname.'").val("");  /* reset visible combo value */
7142	    			    $.each( saveRemoteData, function( key, value ) {
7143	    				        if (key == selected)
7144	    			            {
7145	    			                 console.log("selectArrayAjax - Do a redirect to "+value.url)
7146	    			                 location.assign(value.url);
7147	    			            }
7148	                    });
7149	    			});' : '').'
7150
7151	    	   });
7152		       </script>';
7153		}
7154
7155		if ($acceptdelayedhtml) {
7156			$delayedhtmlcontent .= $outdelayed;
7157		} else {
7158			$out .= $outdelayed;
7159		}
7160		return $out;
7161	}
7162
7163	/**
7164	 *  Return a HTML select string, built from an array of key+value, but content returned into select is defined into $array parameter.
7165	 *  Note: Do not apply langs->trans function on returned content of Ajax service, content may be entity encoded twice.
7166	 *
7167	 *  @param  string	$htmlname               Name of html select area
7168	 *	@param	array	$array					Array (key=>array('text'=>'A text', 'url'=>'An url'), ...)
7169	 *	@param	string	$id             		Preselected key
7170	 *	@param  string	$moreparam      		Add more parameters onto the select tag
7171	 *	@param	int		$disableFiltering		If set to 1, results are not filtered with searched string
7172	 * 	@param	int		$disabled				Html select box is disabled
7173	 *  @param	int		$minimumInputLength		Minimum Input Length
7174	 *  @param	string	$morecss				Add more class to css styles
7175	 *  @param  int     $callurlonselect        If set to 1, some code is added so an url return by the ajax is called when value is selected.
7176	 *  @param  string  $placeholder            String to use as placeholder
7177	 *  @param  integer $acceptdelayedhtml      1 = caller is requesting to have html js content not returned but saved into global $delayedhtmlcontent (so caller can show it at end of page to avoid flash FOUC effect)
7178	 *  @return	string   						HTML select string
7179	 *  @see selectArrayAjax(), ajax_combobox() in ajax.lib.php
7180	 */
7181	public static function selectArrayFilter($htmlname, $array, $id = '', $moreparam = '', $disableFiltering = 0, $disabled = 0, $minimumInputLength = 1, $morecss = '', $callurlonselect = 0, $placeholder = '', $acceptdelayedhtml = 0)
7182	{
7183		global $conf, $langs;
7184		global $delayedhtmlcontent;	// Will be used later outside of this function
7185
7186		// TODO Use an internal dolibarr component instead of select2
7187		if (empty($conf->global->MAIN_USE_JQUERY_MULTISELECT) && !defined('REQUIRE_JQUERY_MULTISELECT')) {
7188			return '';
7189		}
7190
7191		$out = '<select type="text" class="'.$htmlname.($morecss ? ' '.$morecss : '').'" '.($moreparam ? $moreparam.' ' : '').'name="'.$htmlname.'"><option></option></select>';
7192
7193		$formattedarrayresult = array();
7194
7195		foreach ($array as $key => $value) {
7196			$o = new stdClass();
7197			$o->id = $key;
7198			$o->text = $value['text'];
7199			$o->url = $value['url'];
7200			$formattedarrayresult[] = $o;
7201		}
7202
7203		$outdelayed = '';
7204		if (!empty($conf->use_javascript_ajax)) {
7205			$tmpplugin = 'select2';
7206			$outdelayed = "\n".'<!-- JS CODE TO ENABLE '.$tmpplugin.' for id '.$htmlname.' -->
7207				<script>
7208				$(document).ready(function () {
7209					var data = '.json_encode($formattedarrayresult).';
7210
7211					'.($callurlonselect ? 'var saveRemoteData = '.json_encode($array).';' : '').'
7212
7213					$(".'.$htmlname.'").select2({
7214						data: data,
7215						language: select2arrayoflanguage,
7216						containerCssClass: \':all:\',					/* Line to add class of origin SELECT propagated to the new <span class="select2-selection...> tag */
7217						placeholder: "'.dol_escape_js($placeholder).'",
7218						escapeMarkup: function (markup) { return markup; }, 	// let our custom formatter work
7219						minimumInputLength: '.$minimumInputLength.',
7220						formatResult: function(result, container, query, escapeMarkup) {
7221							return escapeMarkup(result.text);
7222						},
7223						matcher: function (params, data) {
7224
7225							if(! data.id) return null;';
7226
7227			if ($callurlonselect) {
7228				$outdelayed .= '
7229
7230							var urlBase = data.url;
7231							var separ = urlBase.indexOf("?") >= 0 ? "&" : "?";
7232							/* console.log("params.term="+params.term); */
7233							/* console.log("params.term encoded="+encodeURIComponent(params.term)); */
7234							saveRemoteData[data.id].url = urlBase + separ + "sall=" + encodeURIComponent(params.term.replace(/\"/g, ""));';
7235			}
7236
7237			if (!$disableFiltering) {
7238				$outdelayed .= '
7239
7240							if(data.text.match(new RegExp(params.term))) {
7241								return data;
7242							}
7243
7244							return null;';
7245			} else {
7246				$outdelayed .= '
7247
7248							return data;';
7249			}
7250
7251			$outdelayed .= '
7252						}
7253					});
7254
7255					'.($callurlonselect ? '
7256					/* Code to execute a GET when we select a value */
7257					$(".'.$htmlname.'").change(function() {
7258						var selected = $(".'.$htmlname.'").val();
7259						console.log("We select "+selected)
7260
7261						$(".'.$htmlname.'").val("");  /* reset visible combo value */
7262						$.each( saveRemoteData, function( key, value ) {
7263							if (key == selected)
7264							{
7265								console.log("selectArrayFilter - Do a redirect to "+value.url)
7266								location.assign(value.url);
7267							}
7268						});
7269					});' : '').'
7270
7271				});
7272				</script>';
7273		}
7274
7275		if ($acceptdelayedhtml) {
7276			$delayedhtmlcontent .= $outdelayed;
7277		} else {
7278			$out .= $outdelayed;
7279		}
7280		return $out;
7281	}
7282
7283	/**
7284	 *	Show a multiselect form from an array. WARNING: Use this only for short lists.
7285	 *
7286	 *	@param	string	$htmlname		Name of select
7287	 *	@param	array	$array			Array with key+value
7288	 *	@param	array	$selected		Array with key+value preselected
7289	 *	@param	int		$key_in_label   1 to show key like in "[key] value"
7290	 *	@param	int		$value_as_key   1 to use value as key
7291	 *	@param  string	$morecss        Add more css style
7292	 *	@param  int		$translate		Translate and encode value
7293	 *  @param	int		$width			Force width of select box. May be used only when using jquery couch. Example: 250, 95%
7294	 *  @param	string	$moreattrib		Add more options on select component. Example: 'disabled'
7295	 *  @param	string	$elemtype		Type of element we show ('category', ...). Will execute a formating function on it. To use in readonly mode if js component support HTML formatting.
7296	 *  @param	string	$placeholder	String to use as placeholder
7297	 *  @param	int		$addjscombo		Add js combo
7298	 *	@return	string					HTML multiselect string
7299	 *  @see selectarray(), selectArrayAjax(), selectArrayFilter()
7300	 */
7301	public static function multiselectarray($htmlname, $array, $selected = array(), $key_in_label = 0, $value_as_key = 0, $morecss = '', $translate = 0, $width = 0, $moreattrib = '', $elemtype = '', $placeholder = '', $addjscombo = -1)
7302	{
7303		global $conf, $langs;
7304
7305		$out = '';
7306
7307		if ($addjscombo < 0) {
7308			if (empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
7309				$addjscombo = 1;
7310			} else {
7311				$addjscombo = 0;
7312			}
7313		}
7314
7315		// Add code for jquery to use multiselect
7316		if (!empty($conf->use_javascript_ajax) && !empty($conf->global->MAIN_USE_JQUERY_MULTISELECT) || defined('REQUIRE_JQUERY_MULTISELECT')) {
7317			$out .= "\n".'<!-- JS CODE TO ENABLE select for id '.$htmlname.', addjscombo='.$addjscombo.' -->';
7318			$out .= "\n".'<script>'."\n";
7319			if ($addjscombo == 1) {
7320				$tmpplugin = empty($conf->global->MAIN_USE_JQUERY_MULTISELECT) ?constant('REQUIRE_JQUERY_MULTISELECT') : $conf->global->MAIN_USE_JQUERY_MULTISELECT;
7321				$out .= 'function formatResult(record) {'."\n";
7322				if ($elemtype == 'category') {
7323					$out .= 'return \'<span><img src="'.DOL_URL_ROOT.'/theme/eldy/img/object_category.png"> \'+record.text+\'</span>\';';
7324				} else {
7325					$out .= 'return record.text;';
7326				}
7327				$out .= '};'."\n";
7328				$out .= 'function formatSelection(record) {'."\n";
7329				if ($elemtype == 'category') {
7330					$out .= 'return \'<span><img src="'.DOL_URL_ROOT.'/theme/eldy/img/object_category.png"> \'+record.text+\'</span>\';';
7331				} else {
7332					$out .= 'return record.text;';
7333				}
7334				$out .= '};'."\n";
7335				$out .= '$(document).ready(function () {
7336							$(\'#'.$htmlname.'\').'.$tmpplugin.'({
7337								dir: \'ltr\',
7338								// Specify format function for dropdown item
7339								formatResult: formatResult,
7340							 	templateResult: formatResult,		/* For 4.0 */
7341								// Specify format function for selected item
7342								formatSelection: formatSelection,
7343							 	templateSelection: formatSelection		/* For 4.0 */
7344							});
7345
7346							/* Add also morecss to the css .select2 that is after the #htmlname, for component that are show dynamically after load, because select2 set
7347								 the size only if component is not hidden by default on load */
7348							$(\'#'.$htmlname.' + .select2\').addClass(\''.$morecss.'\');
7349						});'."\n";
7350			} elseif ($addjscombo == 2 && !defined('DISABLE_MULTISELECT')) {
7351				// Add other js lib
7352				// TODO external lib multiselect/jquery.multi-select.js must have been loaded to use this multiselect plugin
7353				// ...
7354				$out .= 'console.log(\'addjscombo=2 for htmlname='.$htmlname.'\');';
7355				$out .= '$(document).ready(function () {
7356							$(\'#'.$htmlname.'\').multiSelect({
7357								containerHTML: \'<div class="multi-select-container">\',
7358								menuHTML: \'<div class="multi-select-menu">\',
7359								buttonHTML: \'<span class="multi-select-button '.$morecss.'">\',
7360								menuItemHTML: \'<label class="multi-select-menuitem">\',
7361								activeClass: \'multi-select-container--open\',
7362								noneText: \''.$placeholder.'\'
7363							});
7364						})';
7365			}
7366			$out .= '</script>';
7367		}
7368
7369		// Try also magic suggest
7370		$out .= '<select id="'.$htmlname.'" class="multiselect'.($morecss ? ' '.$morecss : '').'" multiple name="'.$htmlname.'[]"'.($moreattrib ? ' '.$moreattrib : '').($width ? ' style="width: '.(preg_match('/%/', $width) ? $width : $width.'px').'"' : '').'>'."\n";
7371		if (is_array($array) && !empty($array)) {
7372			if ($value_as_key) {
7373				$array = array_combine($array, $array);
7374			}
7375
7376			if (!empty($array)) {
7377				foreach ($array as $key => $value) {
7378					$newval = ($translate ? $langs->trans($value) : $value);
7379					$newval = ($key_in_label ? $key.' - '.$newval : $newval);
7380
7381					$out .= '<option value="'.$key.'"';
7382					if (is_array($selected) && !empty($selected) && in_array((string) $key, $selected) && ((string) $key != '')) {
7383						$out .= ' selected';
7384					}
7385					$out .= ' data-html="'.dol_escape_htmltag($newval).'"';
7386					$out .= '>';
7387					$out .= dol_htmlentitiesbr($newval);
7388					$out .= '</option>'."\n";
7389				}
7390			}
7391		}
7392		$out .= '</select>'."\n";
7393
7394		return $out;
7395	}
7396
7397
7398	/**
7399	 *	Show a multiselect dropbox from an array. If a saved selection of fields exists for user (into $user->conf->MAIN_SELECTEDFIELDS_contextofpage), we use this one instead of default.
7400	 *
7401	 *	@param	string	$htmlname		Name of HTML field
7402	 *	@param	array	$array			Array with array of fields we could show. This array may be modified according to setup of user.
7403	 *  @param  string  $varpage        Id of context for page. Can be set by caller with $varpage=(empty($contextpage)?$_SERVER["PHP_SELF"]:$contextpage);
7404	 *	@return	string					HTML multiselect string
7405	 *  @see selectarray()
7406	 */
7407	public static function multiSelectArrayWithCheckbox($htmlname, &$array, $varpage)
7408	{
7409		global $conf, $langs, $user, $extrafields;
7410
7411		if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
7412			return '';
7413		}
7414
7415		$tmpvar = "MAIN_SELECTEDFIELDS_".$varpage; // To get list of saved selected fields to show
7416
7417		if (!empty($user->conf->$tmpvar)) {		// A list of fields was already customized for user
7418			$tmparray = explode(',', $user->conf->$tmpvar);
7419			foreach ($array as $key => $val) {
7420				//var_dump($key);
7421				//var_dump($tmparray);
7422				if (in_array($key, $tmparray)) {
7423					$array[$key]['checked'] = 1;
7424				} else {
7425					$array[$key]['checked'] = 0;
7426				}
7427			}
7428		} else {								// There is no list of fields already customized for user
7429			foreach ($array as $key => $val) {
7430				if ($array[$key]['checked'] < 0) {
7431					$array[$key]['checked'] = 0;
7432				}
7433			}
7434		}
7435
7436		$listoffieldsforselection = '';
7437		$listcheckedstring = '';
7438
7439		foreach ($array as $key => $val) {
7440			/* var_dump($val);
7441			var_dump(array_key_exists('enabled', $val));
7442			var_dump(!$val['enabled']);*/
7443			if (array_key_exists('enabled', $val) && isset($val['enabled']) && !$val['enabled']) {
7444				unset($array[$key]); // We don't want this field
7445				continue;
7446			}
7447			if (!empty($val['type']) && $val['type'] == 'separate') {
7448				// Field remains in array but we don't add it into $listoffieldsforselection
7449				//$listoffieldsforselection .= '<li>-----</li>';
7450				continue;
7451			}
7452			if ($val['label']) {
7453				if (!empty($val['langfile']) && is_object($langs)) {
7454					$langs->load($val['langfile']);
7455				}
7456
7457				// Note: $val['checked'] <> 0 means we must show the field into the combo list
7458				$listoffieldsforselection .= '<li><input type="checkbox" id="checkbox'.$key.'" value="'.$key.'"'.((empty($val['checked']) && $val['checked'] != '-1') ? '' : ' checked="checked"').'/><label for="checkbox'.$key.'">'.dol_escape_htmltag($langs->trans($val['label'])).'</label></li>';
7459				$listcheckedstring .= (empty($val['checked']) ? '' : $key.',');
7460			}
7461		}
7462
7463		$out = '<!-- Component multiSelectArrayWithCheckbox '.$htmlname.' -->
7464
7465        <dl class="dropdown">
7466            <dt>
7467            <a href="#'.$htmlname.'">
7468              '.img_picto('', 'list').'
7469            </a>
7470            <input type="hidden" class="'.$htmlname.'" name="'.$htmlname.'" value="'.$listcheckedstring.'">
7471            </dt>
7472            <dd class="dropdowndd">
7473                <div class="multiselectcheckbox'.$htmlname.'">
7474                    <ul class="ul'.$htmlname.'">
7475                    '.$listoffieldsforselection.'
7476                    </ul>
7477                </div>
7478            </dd>
7479        </dl>
7480
7481        <script type="text/javascript">
7482          jQuery(document).ready(function () {
7483              $(\'.multiselectcheckbox'.$htmlname.' input[type="checkbox"]\').on(\'click\', function () {
7484                  console.log("A new field was added/removed, we edit field input[name=formfilteraction]");
7485
7486                  $("input:hidden[name=formfilteraction]").val(\'listafterchangingselectedfields\');	// Update field so we know we changed something on selected fields after POST
7487
7488                  var title = $(this).val() + ",";
7489                  if ($(this).is(\':checked\')) {
7490                      $(\'.'.$htmlname.'\').val(title + $(\'.'.$htmlname.'\').val());
7491                  }
7492                  else {
7493                      $(\'.'.$htmlname.'\').val( $(\'.'.$htmlname.'\').val().replace(title, \'\') )
7494                  }
7495                  // Now, we submit page
7496                  //$(this).parents(\'form:first\').submit();
7497              });
7498
7499
7500           });
7501        </script>
7502
7503        ';
7504		return $out;
7505	}
7506
7507	/**
7508	 * 	Render list of categories linked to object with id $id and type $type
7509	 *
7510	 * 	@param		int		$id				Id of object
7511	 * 	@param		string	$type			Type of category ('member', 'customer', 'supplier', 'product', 'contact'). Old mode (0, 1, 2, ...) is deprecated.
7512	 *  @param		int		$rendermode		0=Default, use multiselect. 1=Emulate multiselect (recommended)
7513	 *  @param		int		$nolink			1=Do not add html links
7514	 * 	@return		string					String with categories
7515	 */
7516	public function showCategories($id, $type, $rendermode = 0, $nolink = 0)
7517	{
7518		global $db;
7519
7520		include_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
7521
7522		$cat = new Categorie($db);
7523		$categories = $cat->containing($id, $type);
7524
7525		if ($rendermode == 1) {
7526			$toprint = array();
7527			foreach ($categories as $c) {
7528				$ways = $c->print_all_ways(' &gt;&gt; ', ($nolink ? 'none' : ''), 0, 1); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formated text
7529				foreach ($ways as $way) {
7530					$toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"'.($c->color ? ' style="background: #'.$c->color.';"' : ' style="background: #bbb"').'>'.$way.'</li>';
7531				}
7532			}
7533			return '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
7534		}
7535
7536		if ($rendermode == 0) {
7537			$arrayselected = array();
7538			$cate_arbo = $this->select_all_categories($type, '', 'parent', 64, 0, 1);
7539			foreach ($categories as $c) {
7540				$arrayselected[] = $c->id;
7541			}
7542
7543			return $this->multiselectarray('categories', $cate_arbo, $arrayselected, '', 0, '', 0, '100%', 'disabled', 'category');
7544		}
7545
7546		return 'ErrorBadValueForParameterRenderMode'; // Should not happened
7547	}
7548
7549	/**
7550	 *  Show linked object block.
7551	 *
7552	 *  @param	CommonObject	$object		      Object we want to show links to
7553	 *  @param  string          $morehtmlright    More html to show on right of title
7554	 *  @param  array           $compatibleImportElementsList  Array of compatibles elements object for "import from" action
7555	 *  @return	int							      <0 if KO, >=0 if OK
7556	 */
7557	public function showLinkedObjectBlock($object, $morehtmlright = '', $compatibleImportElementsList = false)
7558	{
7559		global $conf, $langs, $hookmanager;
7560		global $bc, $action;
7561
7562		$object->fetchObjectLinked();
7563
7564		// Bypass the default method
7565		$hookmanager->initHooks(array('commonobject'));
7566		$parameters = array(
7567			'morehtmlright' => $morehtmlright,
7568			'compatibleImportElementsList' => &$compatibleImportElementsList,
7569		);
7570		$reshook = $hookmanager->executeHooks('showLinkedObjectBlock', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
7571
7572		if (empty($reshook)) {
7573			$nbofdifferenttypes = count($object->linkedObjects);
7574
7575			print '<!-- showLinkedObjectBlock -->';
7576			print load_fiche_titre($langs->trans('RelatedObjects'), $morehtmlright, '', 0, 0, 'showlinkedobjectblock');
7577
7578
7579			print '<div class="div-table-responsive-no-min">';
7580			print '<table class="noborder allwidth" data-block="showLinkedObject" data-element="'.$object->element.'"  data-elementid="'.$object->id.'"   >';
7581
7582			print '<tr class="liste_titre">';
7583			print '<td>'.$langs->trans("Type").'</td>';
7584			print '<td>'.$langs->trans("Ref").'</td>';
7585			print '<td class="center"></td>';
7586			print '<td class="center">'.$langs->trans("Date").'</td>';
7587			print '<td class="right">'.$langs->trans("AmountHTShort").'</td>';
7588			print '<td class="right">'.$langs->trans("Status").'</td>';
7589			print '<td></td>';
7590			print '</tr>';
7591
7592			$nboftypesoutput = 0;
7593
7594			foreach ($object->linkedObjects as $objecttype => $objects) {
7595				$tplpath = $element = $subelement = $objecttype;
7596
7597				// to display inport button on tpl
7598				$showImportButton = false;
7599				if (!empty($compatibleImportElementsList) && in_array($element, $compatibleImportElementsList)) {
7600					$showImportButton = true;
7601				}
7602
7603				$regs = array();
7604				if ($objecttype != 'supplier_proposal' && preg_match('/^([^_]+)_([^_]+)/i', $objecttype, $regs)) {
7605					$element = $regs[1];
7606					$subelement = $regs[2];
7607					$tplpath = $element.'/'.$subelement;
7608				}
7609				$tplname = 'linkedobjectblock';
7610
7611				// To work with non standard path
7612				if ($objecttype == 'facture') {
7613					$tplpath = 'compta/'.$element;
7614					if (empty($conf->facture->enabled)) {
7615						continue; // Do not show if module disabled
7616					}
7617				} elseif ($objecttype == 'facturerec') {
7618					$tplpath = 'compta/facture';
7619					$tplname = 'linkedobjectblockForRec';
7620					if (empty($conf->facture->enabled)) {
7621						continue; // Do not show if module disabled
7622					}
7623				} elseif ($objecttype == 'propal') {
7624					$tplpath = 'comm/'.$element;
7625					if (empty($conf->propal->enabled)) {
7626						continue; // Do not show if module disabled
7627					}
7628				} elseif ($objecttype == 'supplier_proposal') {
7629					if (empty($conf->supplier_proposal->enabled)) {
7630						continue; // Do not show if module disabled
7631					}
7632				} elseif ($objecttype == 'shipping' || $objecttype == 'shipment') {
7633					$tplpath = 'expedition';
7634					if (empty($conf->expedition->enabled)) {
7635						continue; // Do not show if module disabled
7636					}
7637				} elseif ($objecttype == 'reception') {
7638					$tplpath = 'reception';
7639					if (empty($conf->reception->enabled)) {
7640						continue; // Do not show if module disabled
7641					}
7642				} elseif ($objecttype == 'delivery') {
7643					$tplpath = 'delivery';
7644					if (empty($conf->expedition->enabled)) {
7645						continue; // Do not show if module disabled
7646					}
7647				} elseif ($objecttype == 'invoice_supplier') {
7648					$tplpath = 'fourn/facture';
7649				} elseif ($objecttype == 'order_supplier') {
7650					$tplpath = 'fourn/commande';
7651				} elseif ($objecttype == 'expensereport') {
7652					$tplpath = 'expensereport';
7653				} elseif ($objecttype == 'subscription') {
7654					$tplpath = 'adherents';
7655				} elseif ($objecttype == 'conferenceorbooth') {
7656					$tplpath = 'eventorganization';
7657				} elseif ($objecttype == 'conferenceorboothattendee') {
7658					$tplpath = 'eventorganization';
7659				}
7660
7661				global $linkedObjectBlock;
7662				$linkedObjectBlock = $objects;
7663
7664
7665				// Output template part (modules that overwrite templates must declare this into descriptor)
7666				$dirtpls = array_merge($conf->modules_parts['tpl'], array('/'.$tplpath.'/tpl'));
7667				foreach ($dirtpls as $reldir) {
7668					if ($nboftypesoutput == ($nbofdifferenttypes - 1)) {    // No more type to show after
7669						global $noMoreLinkedObjectBlockAfter;
7670						$noMoreLinkedObjectBlockAfter = 1;
7671					}
7672
7673					$res = @include dol_buildpath($reldir.'/'.$tplname.'.tpl.php');
7674					if ($res) {
7675						$nboftypesoutput++;
7676						break;
7677					}
7678				}
7679			}
7680
7681			if (!$nboftypesoutput) {
7682				print '<tr><td class="impair opacitymedium" colspan="7">'.$langs->trans("None").'</td></tr>';
7683			}
7684
7685			print '</table>';
7686
7687			if (!empty($compatibleImportElementsList)) {
7688				$res = @include dol_buildpath('core/tpl/ajax/objectlinked_lineimport.tpl.php');
7689			}
7690
7691
7692			print '</div>';
7693
7694			return $nbofdifferenttypes;
7695		}
7696	}
7697
7698	/**
7699	 *  Show block with links to link to other objects.
7700	 *
7701	 *  @param	CommonObject	$object				Object we want to show links to
7702	 *  @param	array			$restrictlinksto	Restrict links to some elements, for exemple array('order') or array('supplier_order'). null or array() if no restriction.
7703	 *  @param	array			$excludelinksto		Do not show links of this type, for exemple array('order') or array('supplier_order'). null or array() if no exclusion.
7704	 *  @return	string								<0 if KO, >0 if OK
7705	 */
7706	public function showLinkToObjectBlock($object, $restrictlinksto = array(), $excludelinksto = array())
7707	{
7708		global $conf, $langs, $hookmanager;
7709		global $bc, $action;
7710
7711		$linktoelem = '';
7712		$linktoelemlist = '';
7713		$listofidcompanytoscan = '';
7714
7715		if (!is_object($object->thirdparty)) {
7716			$object->fetch_thirdparty();
7717		}
7718
7719		$possiblelinks = array();
7720		if (is_object($object->thirdparty) && !empty($object->thirdparty->id) && $object->thirdparty->id > 0) {
7721			$listofidcompanytoscan = $object->thirdparty->id;
7722			if (($object->thirdparty->parent > 0) && !empty($conf->global->THIRDPARTY_INCLUDE_PARENT_IN_LINKTO)) {
7723				$listofidcompanytoscan .= ','.$object->thirdparty->parent;
7724			}
7725			if (($object->fk_project > 0) && !empty($conf->global->THIRDPARTY_INCLUDE_PROJECT_THIRDPARY_IN_LINKTO)) {
7726				include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
7727				$tmpproject = new Project($this->db);
7728				$tmpproject->fetch($object->fk_project);
7729				if ($tmpproject->socid > 0 && ($tmpproject->socid != $object->thirdparty->id)) {
7730					$listofidcompanytoscan .= ','.$tmpproject->socid;
7731				}
7732				unset($tmpproject);
7733			}
7734
7735			$possiblelinks = array(
7736				'propal'=>array('enabled'=>$conf->propal->enabled, 'perms'=>1, 'label'=>'LinkToProposal', 'sql'=>"SELECT s.rowid as socid, s.nom as name, s.client, t.rowid, t.ref, t.ref_client, t.total_ht FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."propal as t WHERE t.fk_soc = s.rowid AND t.fk_soc IN (".$this->db->sanitize($listofidcompanytoscan).') AND t.entity IN ('.getEntity('propal').')'),
7737				'order'=>array('enabled'=>$conf->commande->enabled, 'perms'=>1, 'label'=>'LinkToOrder', 'sql'=>"SELECT s.rowid as socid, s.nom as name, s.client, t.rowid, t.ref, t.ref_client, t.total_ht FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."commande as t WHERE t.fk_soc = s.rowid AND t.fk_soc IN (".$this->db->sanitize($listofidcompanytoscan).') AND t.entity IN ('.getEntity('commande').')'),
7738				'invoice'=>array('enabled'=>$conf->facture->enabled, 'perms'=>1, 'label'=>'LinkToInvoice', 'sql'=>"SELECT s.rowid as socid, s.nom as name, s.client, t.rowid, t.ref, t.ref_client, t.total_ht FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."facture as t WHERE t.fk_soc = s.rowid AND t.fk_soc IN (".$this->db->sanitize($listofidcompanytoscan).') AND t.entity IN ('.getEntity('invoice').')'),
7739				'invoice_template'=>array('enabled'=>$conf->facture->enabled, 'perms'=>1, 'label'=>'LinkToTemplateInvoice', 'sql'=>"SELECT s.rowid as socid, s.nom as name, s.client, t.rowid, t.titre as ref, t.total_ht FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."facture_rec as t WHERE t.fk_soc = s.rowid AND t.fk_soc IN (".$this->db->sanitize($listofidcompanytoscan).') AND t.entity IN ('.getEntity('invoice').')'),
7740				'contrat'=>array(
7741					'enabled'=>$conf->contrat->enabled,
7742					'perms'=>1,
7743					'label'=>'LinkToContract',
7744					'sql'=>"SELECT s.rowid as socid, s.nom as name, s.client, t.rowid, t.ref, t.ref_customer as ref_client, t.ref_supplier, SUM(td.total_ht) as total_ht FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."contrat as t, ".MAIN_DB_PREFIX."contratdet as td WHERE t.fk_soc = s.rowid AND td.fk_contrat = t.rowid AND t.fk_soc IN (".$this->db->sanitize($listofidcompanytoscan).') AND t.entity IN ('.getEntity('contract').') GROUP BY s.rowid, s.nom, s.client, t.rowid, t.ref, t.ref_customer, t.ref_supplier'
7745				),
7746				'fichinter'=>array('enabled'=>$conf->ficheinter->enabled, 'perms'=>1, 'label'=>'LinkToIntervention', 'sql'=>"SELECT s.rowid as socid, s.nom as name, s.client, t.rowid, t.ref FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."fichinter as t WHERE t.fk_soc = s.rowid AND t.fk_soc IN (".$this->db->sanitize($listofidcompanytoscan).') AND t.entity IN ('.getEntity('intervention').')'),
7747				'supplier_proposal'=>array('enabled'=>$conf->supplier_proposal->enabled, 'perms'=>1, 'label'=>'LinkToSupplierProposal', 'sql'=>"SELECT s.rowid as socid, s.nom as name, s.client, t.rowid, t.ref, '' as ref_supplier, t.total_ht FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."supplier_proposal as t WHERE t.fk_soc = s.rowid AND t.fk_soc IN (".$this->db->sanitize($listofidcompanytoscan).') AND t.entity IN ('.getEntity('supplier_proposal').')'),
7748				'order_supplier'=>array('enabled'=>$conf->supplier_order->enabled, 'perms'=>1, 'label'=>'LinkToSupplierOrder', 'sql'=>"SELECT s.rowid as socid, s.nom as name, s.client, t.rowid, t.ref, t.ref_supplier, t.total_ht FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."commande_fournisseur as t WHERE t.fk_soc = s.rowid AND t.fk_soc IN (".$this->db->sanitize($listofidcompanytoscan).') AND t.entity IN ('.getEntity('commande_fournisseur').')'),
7749				'invoice_supplier'=>array('enabled'=>$conf->supplier_invoice->enabled, 'perms'=>1, 'label'=>'LinkToSupplierInvoice', 'sql'=>"SELECT s.rowid as socid, s.nom as name, s.client, t.rowid, t.ref, t.ref_supplier, t.total_ht FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."facture_fourn as t WHERE t.fk_soc = s.rowid AND t.fk_soc IN (".$this->db->sanitize($listofidcompanytoscan).') AND t.entity IN ('.getEntity('facture_fourn').')'),
7750				'ticket'=>array('enabled'=>$conf->ticket->enabled, 'perms'=>1, 'label'=>'LinkToTicket', 'sql'=>"SELECT s.rowid as socid, s.nom as name, s.client, t.rowid, t.ref, t.track_id, '0' as total_ht FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."ticket as t WHERE t.fk_soc = s.rowid AND t.fk_soc IN (".$this->db->sanitize($listofidcompanytoscan).') AND t.entity IN ('.getEntity('ticket').')')
7751			);
7752		}
7753
7754		// Can complete the possiblelink array
7755		$hookmanager->initHooks(array('commonobject'));
7756		$parameters = array('listofidcompanytoscan' => $listofidcompanytoscan);
7757
7758		if (!empty($listofidcompanytoscan)) {  // If empty, we don't have criteria to scan the object we can link to
7759			$reshook = $hookmanager->executeHooks('showLinkToObjectBlock', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
7760		}
7761
7762		if (empty($reshook)) {
7763			if (is_array($hookmanager->resArray) && count($hookmanager->resArray)) {
7764				$possiblelinks = array_merge($possiblelinks, $hookmanager->resArray);
7765			}
7766		} elseif ($reshook > 0) {
7767			if (is_array($hookmanager->resArray) && count($hookmanager->resArray)) {
7768				$possiblelinks = $hookmanager->resArray;
7769			}
7770		}
7771
7772		foreach ($possiblelinks as $key => $possiblelink) {
7773			$num = 0;
7774
7775			if (empty($possiblelink['enabled'])) {
7776				continue;
7777			}
7778
7779			if (!empty($possiblelink['perms']) && (empty($restrictlinksto) || in_array($key, $restrictlinksto)) && (empty($excludelinksto) || !in_array($key, $excludelinksto))) {
7780				print '<div id="'.$key.'list"'.(empty($conf->use_javascript_ajax) ? '' : ' style="display:none"').'>';
7781				$sql = $possiblelink['sql'];
7782
7783				$resqllist = $this->db->query($sql);
7784				if ($resqllist) {
7785					$num = $this->db->num_rows($resqllist);
7786					$i = 0;
7787
7788					print '<br>';
7789					print '<form action="'.$_SERVER["PHP_SELF"].'" method="POST" name="formlinked'.$key.'">';
7790					print '<input type="hidden" name="action" value="addlink">';
7791					print '<input type="hidden" name="token" value="'.newToken().'">';
7792					print '<input type="hidden" name="id" value="'.$object->id.'">';
7793					print '<input type="hidden" name="addlink" value="'.$key.'">';
7794					print '<table class="noborder">';
7795					print '<tr class="liste_titre">';
7796					print '<td class="nowrap"></td>';
7797					print '<td class="center">'.$langs->trans("Ref").'</td>';
7798					print '<td class="left">'.$langs->trans("RefCustomer").'</td>';
7799					print '<td class="right">'.$langs->trans("AmountHTShort").'</td>';
7800					print '<td class="left">'.$langs->trans("Company").'</td>';
7801					print '</tr>';
7802					while ($i < $num) {
7803						$objp = $this->db->fetch_object($resqllist);
7804
7805						print '<tr class="oddeven">';
7806						print '<td class="left">';
7807						print '<input type="radio" name="idtolinkto" id="'.$key.'_'.$objp->rowid.'" value="'.$objp->rowid.'">';
7808						print '</td>';
7809						print '<td class="center"><label for="'.$key.'_'.$objp->rowid.'">'.$objp->ref.'</label></td>';
7810						print '<td>'.(!empty($objp->ref_client) ? $objp->ref_client : (!empty($objp->ref_supplier) ? $objp->ref_supplier : '')).'</td>';
7811						print '<td class="right">';
7812						if ($possiblelink['label'] == 'LinkToContract') {
7813							$form = new Form($this->db);
7814							print $form->textwithpicto('', $langs->trans("InformationOnLinkToContract")).' ';
7815						}
7816						print '<span class="amount">'.price($objp->total_ht).'</span>';
7817						print '</td>';
7818						print '<td>'.$objp->name.'</td>';
7819						print '</tr>';
7820						$i++;
7821					}
7822					print '</table>';
7823					print '<div class="center">';
7824					print '<input type="submit" class="button valignmiddle marginleftonly marginrightonly" value="'.$langs->trans('ToLink').'">';
7825					if (empty($conf->use_javascript_ajax)) {
7826						print '<input type="submit" class="button button-cancel marginleftonly marginrightonly" name="cancel" value="'.$langs->trans("Cancel").'"></div>';
7827					} else {
7828						print '<input type="submit"; onclick="javascript:jQuery(\'#'.$key.'list\').toggle(); return false;" class="button button-cancel marginleftonly marginrightonly" name="cancel" value="'.$langs->trans("Cancel").'"></div>';
7829					}
7830					print '</form>';
7831					$this->db->free($resqllist);
7832				} else {
7833					dol_print_error($this->db);
7834				}
7835				print '</div>';
7836
7837				//$linktoelem.=($linktoelem?' &nbsp; ':'');
7838				if ($num > 0) {
7839					$linktoelemlist .= '<li><a href="#linkto'.$key.'" class="linkto dropdowncloseonclick" rel="'.$key.'">'.$langs->trans($possiblelink['label']).' ('.$num.')</a></li>';
7840					// } else $linktoelem.=$langs->trans($possiblelink['label']);
7841				} else {
7842					$linktoelemlist .= '<li><span class="linktodisabled">'.$langs->trans($possiblelink['label']).' (0)</span></li>';
7843				}
7844			}
7845		}
7846
7847		if ($linktoelemlist) {
7848			$linktoelem = '
7849    		<dl class="dropdown" id="linktoobjectname">
7850    		';
7851			if (!empty($conf->use_javascript_ajax)) {
7852				$linktoelem .= '<dt><a href="#linktoobjectname"><span class="fas fa-link paddingrightonly"></span>'.$langs->trans("LinkTo").'...</a></dt>';
7853			}
7854			$linktoelem .= '<dd>
7855    		<div class="multiselectlinkto">
7856    		<ul class="ulselectedfields">'.$linktoelemlist.'
7857    		</ul>
7858    		</div>
7859    		</dd>
7860    		</dl>';
7861		} else {
7862			$linktoelem = '';
7863		}
7864
7865		if (!empty($conf->use_javascript_ajax)) {
7866			print '<!-- Add js to show linkto box -->
7867				<script>
7868				jQuery(document).ready(function() {
7869					jQuery(".linkto").click(function() {
7870						console.log("We choose to show/hide links for rel="+jQuery(this).attr(\'rel\')+" so #"+jQuery(this).attr(\'rel\')+"list");
7871					    jQuery("#"+jQuery(this).attr(\'rel\')+"list").toggle();
7872					});
7873				});
7874				</script>
7875		    ';
7876		}
7877
7878		return $linktoelem;
7879	}
7880
7881	/**
7882	 *	Return an html string with a select combo box to choose yes or no
7883	 *
7884	 *	@param	string		$htmlname		Name of html select field
7885	 *	@param	string		$value			Pre-selected value
7886	 *	@param	int			$option			0 return yes/no, 1 return 1/0
7887	 *	@param	bool		$disabled		true or false
7888	 *  @param	int      	$useempty		1=Add empty line
7889	 *  @param	int			$addjscombo		1=Add js beautifier on combo box
7890	 *  @param	string		$morecss		More CSS
7891	 *	@return	string						See option
7892	 */
7893	public function selectyesno($htmlname, $value = '', $option = 0, $disabled = false, $useempty = 0, $addjscombo = 0, $morecss = '')
7894	{
7895		global $langs;
7896
7897		$yes = "yes";
7898		$no = "no";
7899		if ($option) {
7900			$yes = "1";
7901			$no = "0";
7902		}
7903
7904		$disabled = ($disabled ? ' disabled' : '');
7905
7906		$resultyesno = '<select class="flat width75'.($morecss ? ' '.$morecss : '').'" id="'.$htmlname.'" name="'.$htmlname.'"'.$disabled.'>'."\n";
7907		if ($useempty) {
7908			$resultyesno .= '<option value="-1"'.(($value < 0) ? ' selected' : '').'>&nbsp;</option>'."\n";
7909		}
7910		if (("$value" == 'yes') || ($value == 1)) {
7911			$resultyesno .= '<option value="'.$yes.'" selected>'.$langs->trans("Yes").'</option>'."\n";
7912			$resultyesno .= '<option value="'.$no.'">'.$langs->trans("No").'</option>'."\n";
7913		} else {
7914			$selected = (($useempty && $value != '0' && $value != 'no') ? '' : ' selected');
7915			$resultyesno .= '<option value="'.$yes.'">'.$langs->trans("Yes").'</option>'."\n";
7916			$resultyesno .= '<option value="'.$no.'"'.$selected.'>'.$langs->trans("No").'</option>'."\n";
7917		}
7918		$resultyesno .= '</select>'."\n";
7919
7920		if ($addjscombo) {
7921			$resultyesno .= ajax_combobox($htmlname);
7922		}
7923
7924		return $resultyesno;
7925	}
7926
7927	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
7928	/**
7929	 *  Return list of export templates
7930	 *
7931	 *  @param	string	$selected          Id modele pre-selectionne
7932	 *  @param  string	$htmlname          Name of HTML select
7933	 *  @param  string	$type              Type of searched templates
7934	 *  @param  int		$useempty          Affiche valeur vide dans liste
7935	 *  @return	void
7936	 */
7937	public function select_export_model($selected = '', $htmlname = 'exportmodelid', $type = '', $useempty = 0)
7938	{
7939		// phpcs:enable
7940		$sql = "SELECT rowid, label";
7941		$sql .= " FROM ".MAIN_DB_PREFIX."export_model";
7942		$sql .= " WHERE type = '".$this->db->escape($type)."'";
7943		$sql .= " ORDER BY rowid";
7944		$result = $this->db->query($sql);
7945		if ($result) {
7946			print '<select class="flat" id="select_'.$htmlname.'" name="'.$htmlname.'">';
7947			if ($useempty) {
7948				print '<option value="-1">&nbsp;</option>';
7949			}
7950
7951			$num = $this->db->num_rows($result);
7952			$i = 0;
7953			while ($i < $num) {
7954				$obj = $this->db->fetch_object($result);
7955				if ($selected == $obj->rowid) {
7956					print '<option value="'.$obj->rowid.'" selected>';
7957				} else {
7958					print '<option value="'.$obj->rowid.'">';
7959				}
7960				print $obj->label;
7961				print '</option>';
7962				$i++;
7963			}
7964			print "</select>";
7965		} else {
7966			dol_print_error($this->db);
7967		}
7968	}
7969
7970	/**
7971	 *    Return a HTML area with the reference of object and a navigation bar for a business object
7972	 *    Note: To complete search with a particular filter on select, you can set $object->next_prev_filter set to define SQL criterias.
7973	 *
7974	 *    @param	object	$object			Object to show.
7975	 *    @param	string	$paramid   		Name of parameter to use to name the id into the URL next/previous link.
7976	 *    @param	string	$morehtml  		More html content to output just before the nav bar.
7977	 *    @param	int		$shownav	  	Show Condition (navigation is shown if value is 1).
7978	 *    @param	string	$fieldid   		Name of field id into database to use for select next and previous (we make the select max and min on this field compared to $object->ref). Use 'none' to disable next/prev.
7979	 *    @param	string	$fieldref   	Name of field ref of object (object->ref) to show or 'none' to not show ref.
7980	 *    @param	string	$morehtmlref  	More html to show after ref.
7981	 *    @param	string	$moreparam  	More param to add in nav link url. Must start with '&...'.
7982	 *	  @param	int		$nodbprefix		Do not include DB prefix to forge table name.
7983	 *	  @param	string	$morehtmlleft	More html code to show before ref.
7984	 *	  @param	string	$morehtmlstatus	More html code to show under navigation arrows (status place).
7985	 *	  @param	string	$morehtmlright	More html code to show after ref.
7986	 * 	  @return	string    				Portion HTML with ref + navigation buttons
7987	 */
7988	public function showrefnav($object, $paramid, $morehtml = '', $shownav = 1, $fieldid = 'rowid', $fieldref = 'ref', $morehtmlref = '', $moreparam = '', $nodbprefix = 0, $morehtmlleft = '', $morehtmlstatus = '', $morehtmlright = '')
7989	{
7990		global $langs, $conf, $hookmanager, $extralanguages;
7991
7992		$ret = '';
7993		if (empty($fieldid)) {
7994			$fieldid = 'rowid';
7995		}
7996		if (empty($fieldref)) {
7997			$fieldref = 'ref';
7998		}
7999
8000		// Preparing gender's display if there is one
8001		$addgendertxt = '';
8002		if (!empty($object->gender)) {
8003			$addgendertxt = ' ';
8004			switch ($object->gender) {
8005				case 'man':
8006					$addgendertxt .= '<i class="fas fa-mars"></i>';
8007					break;
8008				case 'woman':
8009					$addgendertxt .= '<i class="fas fa-venus"></i>';
8010					break;
8011				case 'other':
8012					$addgendertxt .= '<i class="fas fa-genderless"></i>';
8013					break;
8014			}
8015		}
8016
8017		// Add where from hooks
8018		if (is_object($hookmanager)) {
8019			$parameters = array();
8020			$reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $object); // Note that $action and $object may have been modified by hook
8021			$object->next_prev_filter .= $hookmanager->resPrint;
8022		}
8023		$previous_ref = $next_ref = '';
8024		if ($shownav) {
8025			//print "paramid=$paramid,morehtml=$morehtml,shownav=$shownav,$fieldid,$fieldref,$morehtmlref,$moreparam";
8026			$object->load_previous_next_ref((isset($object->next_prev_filter) ? $object->next_prev_filter : ''), $fieldid, $nodbprefix);
8027
8028			$navurl = $_SERVER["PHP_SELF"];
8029			// Special case for project/task page
8030			if ($paramid == 'project_ref') {
8031				if (preg_match('/\/tasks\/(task|contact|note|document)\.php/', $navurl)) {     // TODO Remove this when nav with project_ref on task pages are ok
8032					$navurl = preg_replace('/\/tasks\/(task|contact|time|note|document)\.php/', '/tasks.php', $navurl);
8033					$paramid = 'ref';
8034				}
8035			}
8036
8037			// accesskey is for Windows or Linux:  ALT + key for chrome, ALT + SHIFT + KEY for firefox
8038			// accesskey is for Mac:               CTRL + key for all browsers
8039			$stringforfirstkey = $langs->trans("KeyboardShortcut");
8040			if ($conf->browser->name == 'chrome') {
8041				$stringforfirstkey .= ' ALT +';
8042			} elseif ($conf->browser->name == 'firefox') {
8043				$stringforfirstkey .= ' ALT + SHIFT +';
8044			} else {
8045				$stringforfirstkey .= ' CTL +';
8046			}
8047
8048			$previous_ref = $object->ref_previous ? '<a accesskey="p" title="'.$stringforfirstkey.' p" class="classfortooltip" href="'.$navurl.'?'.$paramid.'='.urlencode($object->ref_previous).$moreparam.'"><i class="fa fa-chevron-left"></i></a>' : '<span class="inactive"><i class="fa fa-chevron-left opacitymedium"></i></span>';
8049			$next_ref     = $object->ref_next ? '<a accesskey="n" title="'.$stringforfirstkey.' n" class="classfortooltip" href="'.$navurl.'?'.$paramid.'='.urlencode($object->ref_next).$moreparam.'"><i class="fa fa-chevron-right"></i></a>' : '<span class="inactive"><i class="fa fa-chevron-right opacitymedium"></i></span>';
8050		}
8051
8052		//print "xx".$previous_ref."x".$next_ref;
8053		$ret .= '<!-- Start banner content --><div style="vertical-align: middle">';
8054
8055		// Right part of banner
8056		if ($morehtmlright) {
8057			$ret .= '<div class="inline-block floatleft">'.$morehtmlright.'</div>';
8058		}
8059
8060		if ($previous_ref || $next_ref || $morehtml) {
8061			$ret .= '<div class="pagination paginationref"><ul class="right">';
8062		}
8063		if ($morehtml) {
8064			$ret .= '<li class="noborder litext'.(($shownav && $previous_ref && $next_ref) ? ' clearbothonsmartphone' : '').'">'.$morehtml.'</li>';
8065		}
8066		if ($shownav && ($previous_ref || $next_ref)) {
8067			$ret .= '<li class="pagination">'.$previous_ref.'</li>';
8068			$ret .= '<li class="pagination">'.$next_ref.'</li>';
8069		}
8070		if ($previous_ref || $next_ref || $morehtml) {
8071			$ret .= '</ul></div>';
8072		}
8073
8074		$parameters = array();
8075		$reshook = $hookmanager->executeHooks('moreHtmlStatus', $parameters, $object); // Note that $action and $object may have been modified by hook
8076		if (empty($reshook)) {
8077			$morehtmlstatus .= $hookmanager->resPrint;
8078		} else {
8079			$morehtmlstatus = $hookmanager->resPrint;
8080		}
8081		if ($morehtmlstatus) {
8082			$ret .= '<div class="statusref">'.$morehtmlstatus.'</div>';
8083		}
8084
8085		$parameters = array();
8086		$reshook = $hookmanager->executeHooks('moreHtmlRef', $parameters, $object); // Note that $action and $object may have been modified by hook
8087		if (empty($reshook)) {
8088			$morehtmlref .= $hookmanager->resPrint;
8089		} elseif ($reshook > 0) {
8090			$morehtmlref = $hookmanager->resPrint;
8091		}
8092
8093		// Left part of banner
8094		if ($morehtmlleft) {
8095			if ($conf->browser->layout == 'phone') {
8096				$ret .= '<!-- morehtmlleft --><div class="floatleft">'.$morehtmlleft.'</div>'; // class="center" to have photo in middle
8097			} else {
8098				$ret .= '<!-- morehtmlleft --><div class="inline-block floatleft">'.$morehtmlleft.'</div>';
8099			}
8100		}
8101
8102		//if ($conf->browser->layout == 'phone') $ret.='<div class="clearboth"></div>';
8103		$ret .= '<div class="inline-block floatleft valignmiddle maxwidth750 marginbottomonly refid'.(($shownav && ($previous_ref || $next_ref)) ? ' refidpadding' : '').'">';
8104
8105		// For thirdparty, contact, user, member, the ref is the id, so we show something else
8106		if ($object->element == 'societe') {
8107			$ret .= dol_htmlentities($object->name);
8108
8109			// List of extra languages
8110			$arrayoflangcode = array();
8111			if (!empty($conf->global->PDF_USE_ALSO_LANGUAGE_CODE)) {
8112				$arrayoflangcode[] = $conf->global->PDF_USE_ALSO_LANGUAGE_CODE;
8113			}
8114
8115			if (is_array($arrayoflangcode) && count($arrayoflangcode)) {
8116				if (!is_object($extralanguages)) {
8117					include_once DOL_DOCUMENT_ROOT.'/core/class/extralanguages.class.php';
8118					$extralanguages = new ExtraLanguages($this->db);
8119				}
8120				$extralanguages->fetch_name_extralanguages('societe');
8121
8122				if (!empty($extralanguages->attributes['societe']['name'])) {
8123					$object->fetchValuesForExtraLanguages();
8124
8125					$htmltext = '';
8126					// If there is extra languages
8127					foreach ($arrayoflangcode as $extralangcode) {
8128						$htmltext .= picto_from_langcode($extralangcode, 'class="pictoforlang paddingright"');
8129						if ($object->array_languages['name'][$extralangcode]) {
8130							$htmltext .= $object->array_languages['name'][$extralangcode];
8131						} else {
8132							$htmltext .= '<span class="opacitymedium">'.$langs->trans("SwitchInEditModeToAddTranslation").'</span>';
8133						}
8134					}
8135					$ret .= '<!-- Show translations of name -->'."\n";
8136					$ret .= $this->textwithpicto('', $htmltext, -1, 'language', 'opacitymedium paddingleft');
8137				}
8138			}
8139		} elseif ($object->element == 'member') {
8140			$ret .= $object->ref.'<br>';
8141			$fullname = $object->getFullName($langs);
8142			if ($object->morphy == 'mor' && $object->societe) {
8143				$ret .= dol_htmlentities($object->societe).((!empty($fullname) && $object->societe != $fullname) ? ' ('.dol_htmlentities($fullname).$addgendertxt.')' : '');
8144			} else {
8145				$ret .= dol_htmlentities($fullname).$addgendertxt.((!empty($object->societe) && $object->societe != $fullname) ? ' ('.dol_htmlentities($object->societe).')' : '');
8146			}
8147		} elseif (in_array($object->element, array('contact', 'user', 'usergroup'))) {
8148			$ret .= dol_htmlentities($object->getFullName($langs)).$addgendertxt;
8149		} elseif (in_array($object->element, array('action', 'agenda'))) {
8150			$ret .= $object->ref.'<br>'.$object->label;
8151		} elseif (in_array($object->element, array('adherent_type'))) {
8152			$ret .= $object->label;
8153		} elseif ($object->element == 'ecm_directories') {
8154			$ret .= '';
8155		} elseif ($fieldref != 'none') {
8156			$ret .= dol_htmlentities($object->$fieldref);
8157		}
8158
8159		if ($morehtmlref) {
8160			// don't add a additional space, when "$morehtmlref" starts with a HTML div tag
8161			if (substr($morehtmlref, 0, 4) != '<div') {
8162				$ret .= ' ';
8163			}
8164
8165			$ret .= $morehtmlref;
8166		}
8167
8168		$ret .= '</div>';
8169
8170		$ret .= '</div><!-- End banner content -->';
8171
8172		return $ret;
8173	}
8174
8175
8176	/**
8177	 *  Return HTML code to output a barcode
8178	 *
8179	 *  @param	Object	$object			Object containing data to retrieve file name
8180	 * 	@param	int		$width			Width of photo
8181	 * 	@param	string	$morecss		More CSS on img of barcode
8182	 * 	@return string    				HTML code to output barcode
8183	 */
8184	public function showbarcode(&$object, $width = 100, $morecss = '')
8185	{
8186		global $conf;
8187
8188		//Check if barcode is filled in the card
8189		if (empty($object->barcode)) {
8190			return '';
8191		}
8192
8193		// Complete object if not complete
8194		if (empty($object->barcode_type_code) || empty($object->barcode_type_coder)) {
8195			$result = $object->fetch_barcode();
8196			//Check if fetch_barcode() failed
8197			if ($result < 1) {
8198				return '<!-- ErrorFetchBarcode -->';
8199			}
8200		}
8201
8202		// Barcode image
8203		$url = DOL_URL_ROOT.'/viewimage.php?modulepart=barcode&generator='.urlencode($object->barcode_type_coder).'&code='.urlencode($object->barcode).'&encoding='.urlencode($object->barcode_type_code);
8204		$out = '<!-- url barcode = '.$url.' -->';
8205		$out .= '<img src="'.$url.'"'.($morecss ? ' class="'.$morecss.'"' : '').'>';
8206		return $out;
8207	}
8208
8209	/**
8210	 *    	Return HTML code to output a photo
8211	 *
8212	 *    	@param	string		$modulepart			Key to define module concerned ('societe', 'userphoto', 'memberphoto')
8213	 *     	@param  object		$object				Object containing data to retrieve file name
8214	 * 		@param	int			$width				Width of photo
8215	 * 		@param	int			$height				Height of photo (auto if 0)
8216	 * 		@param	int			$caneditfield		Add edit fields
8217	 * 		@param	string		$cssclass			CSS name to use on img for photo
8218	 * 		@param	string		$imagesize		    'mini', 'small' or '' (original)
8219	 *      @param  int         $addlinktofullsize  Add link to fullsize image
8220	 *      @param  int         $cache              1=Accept to use image in cache
8221	 *      @param	string		$forcecapture		'', 'user' or 'environment'. Force parameter capture on HTML input file element to ask a smartphone to allow to open camera to take photo. Auto if ''.
8222	 *      @param	int			$noexternsourceoverwrite	No overwrite image with extern source (like 'gravatar' or other module)
8223	 * 	  	@return string    						HTML code to output photo
8224	 */
8225	public static function showphoto($modulepart, $object, $width = 100, $height = 0, $caneditfield = 0, $cssclass = 'photowithmargin', $imagesize = '', $addlinktofullsize = 1, $cache = 0, $forcecapture = '', $noexternsourceoverwrite = 0)
8226	{
8227		global $conf, $langs;
8228
8229		$entity = (!empty($object->entity) ? $object->entity : $conf->entity);
8230		$id = (!empty($object->id) ? $object->id : $object->rowid);
8231
8232		$ret = '';
8233		$dir = '';
8234		$file = '';
8235		$originalfile = '';
8236		$altfile = '';
8237		$email = '';
8238		$capture = '';
8239		if ($modulepart == 'societe') {
8240			$dir = $conf->societe->multidir_output[$entity];
8241			if (!empty($object->logo)) {
8242				if (dolIsAllowedForPreview($object->logo)) {
8243					if ((string) $imagesize == 'mini') {
8244						$file = get_exdir(0, 0, 0, 0, $object, 'thirdparty').'logos/'.getImageFileNameForSize($object->logo, '_mini'); // getImageFileNameForSize include the thumbs
8245					} elseif ((string) $imagesize == 'small') {
8246						$file = get_exdir(0, 0, 0, 0, $object, 'thirdparty').'logos/'.getImageFileNameForSize($object->logo, '_small');
8247					} else {
8248						$file = get_exdir(0, 0, 0, 0, $object, 'thirdparty').'logos/'.$object->logo;
8249					}
8250					$originalfile = get_exdir(0, 0, 0, 0, $object, 'thirdparty').'logos/'.$object->logo;
8251				}
8252			}
8253			$email = $object->email;
8254		} elseif ($modulepart == 'contact')	{
8255			$dir = $conf->societe->multidir_output[$entity].'/contact';
8256			if (!empty($object->photo)) {
8257				if (dolIsAllowedForPreview($object->photo)) {
8258					if ((string) $imagesize == 'mini') {
8259						$file = get_exdir(0, 0, 0, 0, $object, 'contact').'photos/'.getImageFileNameForSize($object->photo, '_mini');
8260					} elseif ((string) $imagesize == 'small') {
8261						$file = get_exdir(0, 0, 0, 0, $object, 'contact').'photos/'.getImageFileNameForSize($object->photo, '_small');
8262					} else {
8263						$file = get_exdir(0, 0, 0, 0, $object, 'contact').'photos/'.$object->photo;
8264					}
8265					$originalfile = get_exdir(0, 0, 0, 0, $object, 'contact').'photos/'.$object->photo;
8266				}
8267			}
8268			$email = $object->email;
8269			$capture = 'user';
8270		} elseif ($modulepart == 'userphoto') {
8271			$dir = $conf->user->dir_output;
8272			if (!empty($object->photo)) {
8273				if (dolIsAllowedForPreview($object->photo)) {
8274					if ((string) $imagesize == 'mini') {
8275						$file = get_exdir(0, 0, 0, 0, $object, 'user').getImageFileNameForSize($object->photo, '_mini');
8276					} elseif ((string) $imagesize == 'small') {
8277						$file = get_exdir(0, 0, 0, 0, $object, 'user').getImageFileNameForSize($object->photo, '_small');
8278					} else {
8279						$file = get_exdir(0, 0, 0, 0, $object, 'user').$object->photo;
8280					}
8281					$originalfile = get_exdir(0, 0, 0, 0, $object, 'user').$object->photo;
8282				}
8283			}
8284			if (!empty($conf->global->MAIN_OLD_IMAGE_LINKS)) {
8285				$altfile = $object->id.".jpg"; // For backward compatibility
8286			}
8287			$email = $object->email;
8288			$capture = 'user';
8289		} elseif ($modulepart == 'memberphoto')	{
8290			$dir = $conf->adherent->dir_output;
8291			if (!empty($object->photo)) {
8292				if (dolIsAllowedForPreview($object->photo)) {
8293					if ((string) $imagesize == 'mini') {
8294						$file = get_exdir(0, 0, 0, 0, $object, 'member').'photos/'.getImageFileNameForSize($object->photo, '_mini');
8295					} elseif ((string) $imagesize == 'small') {
8296						$file = get_exdir(0, 0, 0, 0, $object, 'member').'photos/'.getImageFileNameForSize($object->photo, '_small');
8297					} else {
8298						$file = get_exdir(0, 0, 0, 0, $object, 'member').'photos/'.$object->photo;
8299					}
8300					$originalfile = get_exdir(0, 0, 0, 0, $object, 'member').'photos/'.$object->photo;
8301				}
8302			}
8303			if (!empty($conf->global->MAIN_OLD_IMAGE_LINKS)) {
8304				$altfile = $object->id.".jpg"; // For backward compatibility
8305			}
8306			$email = $object->email;
8307			$capture = 'user';
8308		} else {
8309			// Generic case to show photos
8310			$dir = $conf->$modulepart->dir_output;
8311			if (!empty($object->photo)) {
8312				if (dolIsAllowedForPreview($object->photo)) {
8313					if ((string) $imagesize == 'mini') {
8314						$file = get_exdir($id, 2, 0, 0, $object, $modulepart).'photos/'.getImageFileNameForSize($object->photo, '_mini');
8315					} elseif ((string) $imagesize == 'small') {
8316						$file = get_exdir($id, 2, 0, 0, $object, $modulepart).'photos/'.getImageFileNameForSize($object->photo, '_small');
8317					} else {
8318						$file = get_exdir($id, 2, 0, 0, $object, $modulepart).'photos/'.$object->photo;
8319					}
8320					$originalfile = get_exdir($id, 2, 0, 0, $object, $modulepart).'photos/'.$object->photo;
8321				}
8322			}
8323			if (!empty($conf->global->MAIN_OLD_IMAGE_LINKS)) {
8324				$altfile = $object->id.".jpg"; // For backward compatibility
8325			}
8326			$email = $object->email;
8327		}
8328
8329		if ($forcecapture) {
8330			$capture = $forcecapture;
8331		}
8332
8333		if ($dir) {
8334			if ($file && file_exists($dir."/".$file)) {
8335				if ($addlinktofullsize) {
8336					$urladvanced = getAdvancedPreviewUrl($modulepart, $originalfile, 0, '&entity='.$entity);
8337					if ($urladvanced) {
8338						$ret .= '<a href="'.$urladvanced.'">';
8339					} else {
8340						$ret .= '<a href="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$entity.'&file='.urlencode($originalfile).'&cache='.$cache.'">';
8341					}
8342				}
8343				$ret .= '<img alt="Photo" class="photo'.$modulepart.($cssclass ? ' '.$cssclass : '').' photologo'.(preg_replace('/[^a-z]/i', '_', $file)).'" '.($width ? ' width="'.$width.'"' : '').($height ? ' height="'.$height.'"' : '').' src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$entity.'&file='.urlencode($file).'&cache='.$cache.'">';
8344				if ($addlinktofullsize) {
8345					$ret .= '</a>';
8346				}
8347			} elseif ($altfile && file_exists($dir."/".$altfile)) {
8348				if ($addlinktofullsize) {
8349					$urladvanced = getAdvancedPreviewUrl($modulepart, $originalfile, 0, '&entity='.$entity);
8350					if ($urladvanced) {
8351						$ret .= '<a href="'.$urladvanced.'">';
8352					} else {
8353						$ret .= '<a href="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$entity.'&file='.urlencode($originalfile).'&cache='.$cache.'">';
8354					}
8355				}
8356				$ret .= '<img class="photo'.$modulepart.($cssclass ? ' '.$cssclass : '').'" alt="Photo alt" id="photologo'.(preg_replace('/[^a-z]/i', '_', $file)).'" class="'.$cssclass.'" '.($width ? ' width="'.$width.'"' : '').($height ? ' height="'.$height.'"' : '').' src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$entity.'&file='.urlencode($altfile).'&cache='.$cache.'">';
8357				if ($addlinktofullsize) {
8358					$ret .= '</a>';
8359				}
8360			} else {
8361				$nophoto = '/public/theme/common/nophoto.png';
8362				$defaultimg = 'identicon';		// For gravatar
8363				if (in_array($modulepart, array('societe', 'userphoto', 'contact', 'memberphoto'))) {	// For modules that need a special image when photo not found
8364					if ($modulepart == 'societe' || ($modulepart == 'memberphoto' && strpos($object->morphy, 'mor')) !== false) {
8365						$nophoto = 'company';
8366					} else {
8367						$nophoto = '/public/theme/common/user_anonymous.png';
8368						if (!empty($object->gender) && $object->gender == 'man') {
8369							$nophoto = '/public/theme/common/user_man.png';
8370						}
8371						if (!empty($object->gender) && $object->gender == 'woman') {
8372							$nophoto = '/public/theme/common/user_woman.png';
8373						}
8374					}
8375				}
8376
8377				if (!empty($conf->gravatar->enabled) && $email && empty($noexternsourceoverwrite)) {
8378					// see https://gravatar.com/site/implement/images/php/
8379					$ret .= '<!-- Put link to gravatar -->';
8380					$ret .= '<img class="photo'.$modulepart.($cssclass ? ' '.$cssclass : '').'" alt="Gravatar avatar" title="'.$email.' Gravatar avatar" '.($width ? ' width="'.$width.'"' : '').($height ? ' height="'.$height.'"' : '').' src="https://www.gravatar.com/avatar/'.md5(strtolower(trim($email))).'?s='.$width.'&d='.$defaultimg.'">'; // gravatar need md5 hash
8381				} else {
8382					if ($nophoto == 'company') {
8383						$ret .= '<div class="photo'.$modulepart.($cssclass ? ' '.$cssclass : '').'" alt="No photo" '.($width ? ' width="'.$width.'"' : '').($height ? ' height="'.$height.'"' : '').'">'.img_picto('', 'company').'</div>';
8384					} else {
8385						$ret .= '<img class="photo'.$modulepart.($cssclass ? ' '.$cssclass : '').'" alt="No photo" '.($width ? ' width="'.$width.'"' : '').($height ? ' height="'.$height.'"' : '').' src="'.DOL_URL_ROOT.$nophoto.'">';
8386					}
8387				}
8388			}
8389
8390			if ($caneditfield) {
8391				if ($object->photo) {
8392					$ret .= "<br>\n";
8393				}
8394				$ret .= '<table class="nobordernopadding centpercent">';
8395				if ($object->photo) {
8396					$ret .= '<tr><td><input type="checkbox" class="flat photodelete" name="deletephoto" id="photodelete"> '.$langs->trans("Delete").'<br><br></td></tr>';
8397				}
8398				$ret .= '<tr><td class="tdoverflow"><input type="file" class="flat maxwidth200onsmartphone" name="photo" id="photoinput" accept="image/*"'.($capture ? ' capture="'.$capture.'"' : '').'></td></tr>';
8399				$ret .= '</table>';
8400			}
8401		} else {
8402			dol_print_error('', 'Call of showphoto with wrong parameters modulepart='.$modulepart);
8403		}
8404
8405		return $ret;
8406	}
8407
8408	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
8409	/**
8410	 *	Return select list of groups
8411	 *
8412	 *  @param	string	$selected       Id group preselected
8413	 *  @param  string	$htmlname       Field name in form
8414	 *  @param  int		$show_empty     0=liste sans valeur nulle, 1=ajoute valeur inconnue
8415	 *  @param  string	$exclude        Array list of groups id to exclude
8416	 * 	@param	int		$disabled		If select list must be disabled
8417	 *  @param  string	$include        Array list of groups id to include
8418	 * 	@param	int		$enableonly		Array list of groups id to be enabled. All other must be disabled
8419	 * 	@param	string	$force_entity	'0' or Ids of environment to force
8420	 * 	@param	bool	$multiple		add [] in the name of element and add 'multiple' attribut (not working with ajax_autocompleter)
8421	 *  @param  string	$morecss		More css to add to html component
8422	 *  @return	string
8423	 *  @see select_dolusers()
8424	 */
8425	public function select_dolgroups($selected = '', $htmlname = 'groupid', $show_empty = 0, $exclude = '', $disabled = 0, $include = '', $enableonly = '', $force_entity = '0', $multiple = false, $morecss = '')
8426	{
8427		// phpcs:enable
8428		global $conf, $user, $langs;
8429
8430		// Permettre l'exclusion de groupes
8431		if (is_array($exclude)) {
8432			$excludeGroups = implode(",", $exclude);
8433		}
8434		// Permettre l'inclusion de groupes
8435		if (is_array($include)) {
8436			$includeGroups = implode(",", $include);
8437		}
8438
8439		if (!is_array($selected)) {
8440			$selected = array($selected);
8441		}
8442
8443		$out = '';
8444
8445		// On recherche les groupes
8446		$sql = "SELECT ug.rowid, ug.nom as name";
8447		if (!empty($conf->multicompany->enabled) && $conf->entity == 1 && $user->admin && !$user->entity) {
8448			$sql .= ", e.label";
8449		}
8450		$sql .= " FROM ".MAIN_DB_PREFIX."usergroup as ug ";
8451		if (!empty($conf->multicompany->enabled) && $conf->entity == 1 && $user->admin && !$user->entity) {
8452			$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."entity as e ON e.rowid=ug.entity";
8453			if ($force_entity) {
8454				$sql .= " WHERE ug.entity IN (0, ".$force_entity.")";
8455			} else {
8456				$sql .= " WHERE ug.entity IS NOT NULL";
8457			}
8458		} else {
8459			$sql .= " WHERE ug.entity IN (0, ".$conf->entity.")";
8460		}
8461		if (is_array($exclude) && $excludeGroups) {
8462			$sql .= " AND ug.rowid NOT IN (".$this->db->sanitize($excludeGroups).")";
8463		}
8464		if (is_array($include) && $includeGroups) {
8465			$sql .= " AND ug.rowid IN (".$this->db->sanitize($includeGroups).")";
8466		}
8467		$sql .= " ORDER BY ug.nom ASC";
8468
8469		dol_syslog(get_class($this)."::select_dolgroups", LOG_DEBUG);
8470		$resql = $this->db->query($sql);
8471		if ($resql) {
8472			// Enhance with select2
8473			include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
8474			$out .= ajax_combobox($htmlname);
8475
8476			$out .= '<select class="flat minwidth200'.($morecss ? ' '.$morecss : '').'" id="'.$htmlname.'" name="'.$htmlname.($multiple ? '[]' : '').'" '.($multiple ? 'multiple' : '').' '.($disabled ? ' disabled' : '').'>';
8477
8478			$num = $this->db->num_rows($resql);
8479			$i = 0;
8480			if ($num) {
8481				if ($show_empty && !$multiple) {
8482					$out .= '<option value="-1"'.(in_array(-1, $selected) ? ' selected' : '').'>&nbsp;</option>'."\n";
8483				}
8484
8485				while ($i < $num) {
8486					$obj = $this->db->fetch_object($resql);
8487					$disableline = 0;
8488					if (is_array($enableonly) && count($enableonly) && !in_array($obj->rowid, $enableonly)) {
8489						$disableline = 1;
8490					}
8491
8492					$out .= '<option value="'.$obj->rowid.'"';
8493					if ($disableline) {
8494						$out .= ' disabled';
8495					}
8496					if ((is_object($selected[0]) && $selected[0]->id == $obj->rowid) || (!is_object($selected[0]) && in_array($obj->rowid, $selected))) {
8497						$out .= ' selected';
8498					}
8499					$out .= '>';
8500
8501					$out .= $obj->name;
8502					if (!empty($conf->multicompany->enabled) && empty($conf->global->MULTICOMPANY_TRANSVERSE_MODE) && $conf->entity == 1) {
8503						$out .= " (".$obj->label.")";
8504					}
8505
8506					$out .= '</option>';
8507					$i++;
8508				}
8509			} else {
8510				if ($show_empty) {
8511					$out .= '<option value="-1"'.(in_array(-1, $selected) ? ' selected' : '').'></option>'."\n";
8512				}
8513				$out .= '<option value="" disabled>'.$langs->trans("NoUserGroupDefined").'</option>';
8514			}
8515			$out .= '</select>';
8516		} else {
8517			dol_print_error($this->db);
8518		}
8519
8520		return $out;
8521	}
8522
8523
8524	/**
8525	 *	Return HTML to show the search and clear seach button
8526	 *
8527	 *  @return	string
8528	 */
8529	public function showFilterButtons()
8530	{
8531		$out = '<div class="nowraponall">';
8532		$out .= '<button type="submit" class="liste_titre button_search reposition" name="button_search_x" value="x"><span class="fa fa-search"></span></button>';
8533		$out .= '<button type="submit" class="liste_titre button_removefilter reposition" name="button_removefilter_x" value="x"><span class="fa fa-remove"></span></button>';
8534		$out .= '</div>';
8535
8536		return $out;
8537	}
8538
8539	/**
8540	 *	Return HTML to show the search and clear search button
8541	 *
8542	 *  @param  string  $cssclass                  CSS class
8543	 *  @param  int     $calljsfunction            0=default. 1=call function initCheckForSelect() after changing status of checkboxes
8544	 *  @param  string  $massactionname            Mass action button name that will launch an action on the selected items
8545	 *  @return	string
8546	 */
8547	public function showCheckAddButtons($cssclass = 'checkforaction', $calljsfunction = 0, $massactionname = "massaction")
8548	{
8549		global $conf, $langs;
8550
8551		$out = '';
8552
8553		if (!empty($conf->use_javascript_ajax)) {
8554			$out .= '<div class="inline-block checkallactions"><input type="checkbox" id="'.$cssclass.'s" name="'.$cssclass.'s" class="checkallactions"></div>';
8555		}
8556		$out .= '<script>
8557            $(document).ready(function() {
8558                $("#' . $cssclass.'s").click(function() {
8559                    if($(this).is(\':checked\')){
8560                        console.log("We check all '.$cssclass.' and trigger the change method");
8561                		$(".'.$cssclass.'").prop(\'checked\', true).trigger(\'change\');
8562                    }
8563                    else
8564                    {
8565                        console.log("We uncheck all");
8566                		$(".'.$cssclass.'").prop(\'checked\', false).trigger(\'change\');
8567                    }'."\n";
8568		if ($calljsfunction) {
8569			$out .= 'if (typeof initCheckForSelect == \'function\') { initCheckForSelect(0, "'.$massactionname.'", "'.$cssclass.'"); } else { console.log("No function initCheckForSelect found. Call won\'t be done."); }';
8570		}
8571		$out .= '         });
8572        	        $(".' . $cssclass.'").change(function() {
8573					$(this).closest("tr").toggleClass("highlight", this.checked);
8574				});
8575		 	});
8576    	</script>';
8577
8578		return $out;
8579	}
8580
8581	/**
8582	 *	Return HTML to show the search and clear seach button
8583	 *
8584	 *  @param	int  	$addcheckuncheckall        Add the check all/uncheck all checkbox (use javascript) and code to manage this
8585	 *  @param  string  $cssclass                  CSS class
8586	 *  @param  int     $calljsfunction            0=default. 1=call function initCheckForSelect() after changing status of checkboxes
8587	 *  @param  string  $massactionname            Mass action name
8588	 *  @return	string
8589	 */
8590	public function showFilterAndCheckAddButtons($addcheckuncheckall = 0, $cssclass = 'checkforaction', $calljsfunction = 0, $massactionname = "massaction")
8591	{
8592		$out = $this->showFilterButtons();
8593		if ($addcheckuncheckall) {
8594			$out .= $this->showCheckAddButtons($cssclass, $calljsfunction, $massactionname);
8595		}
8596		return $out;
8597	}
8598
8599	/**
8600	 * Return HTML to show the select of expense categories
8601	 *
8602	 * @param	string	$selected              preselected category
8603	 * @param	string	$htmlname              name of HTML select list
8604	 * @param	integer	$useempty              1=Add empty line
8605	 * @param	array	$excludeid             id to exclude
8606	 * @param	string	$target                htmlname of target select to bind event
8607	 * @param	int		$default_selected      default category to select if fk_c_type_fees change = EX_KME
8608	 * @param	array	$params                param to give
8609	 * @param	int		$info_admin			   Show the tooltip help picto to setup list
8610	 * @return	string
8611	 */
8612	public function selectExpenseCategories($selected = '', $htmlname = 'fk_c_exp_tax_cat', $useempty = 0, $excludeid = array(), $target = '', $default_selected = 0, $params = array(), $info_admin = 1)
8613	{
8614		global $db, $langs, $user;
8615
8616		$out = '';
8617		$sql = 'SELECT rowid, label FROM '.MAIN_DB_PREFIX.'c_exp_tax_cat WHERE active = 1';
8618		$sql .= ' AND entity IN (0,'.getEntity('exp_tax_cat').')';
8619		if (!empty($excludeid)) {
8620			$sql .= ' AND rowid NOT IN ('.$this->db->sanitize(implode(',', $excludeid)).')';
8621		}
8622		$sql .= ' ORDER BY label';
8623
8624		$resql = $db->query($sql);
8625		if ($resql) {
8626			$out = '<select id="select_'.$htmlname.'" name="'.$htmlname.'" class="'.$htmlname.' flat minwidth75imp maxwidth200">';
8627			if ($useempty) {
8628				$out .= '<option value="0">&nbsp;</option>';
8629			}
8630
8631			while ($obj = $db->fetch_object($resql)) {
8632				$out .= '<option '.($selected == $obj->rowid ? 'selected="selected"' : '').' value="'.$obj->rowid.'">'.$langs->trans($obj->label).'</option>';
8633			}
8634			$out .= '</select>';
8635			$out .= ajax_combobox('select_'.$htmlname);
8636
8637			if (!empty($htmlname) && $user->admin && $info_admin) {
8638				$out .= ' '.info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
8639			}
8640
8641			if (!empty($target)) {
8642				$sql = "SELECT c.id FROM ".MAIN_DB_PREFIX."c_type_fees as c WHERE c.code = 'EX_KME' AND c.active = 1";
8643				$resql = $db->query($sql);
8644				if ($resql) {
8645					if ($db->num_rows($resql) > 0) {
8646						$obj = $db->fetch_object($resql);
8647						$out .= '<script>
8648							$(function() {
8649								$("select[name='.$target.']").on("change", function() {
8650									var current_val = $(this).val();
8651									if (current_val == '.$obj->id.') {';
8652						if (!empty($default_selected) || !empty($selected)) {
8653							$out .= '$("select[name='.$htmlname.']").val("'.($default_selected > 0 ? $default_selected : $selected).'");';
8654						}
8655
8656						$out .= '
8657										$("select[name='.$htmlname.']").change();
8658									}
8659								});
8660
8661								$("select[name='.$htmlname.']").change(function() {
8662
8663									if ($("select[name='.$target.']").val() == '.$obj->id.') {
8664										// get price of kilometer to fill the unit price
8665										$.ajax({
8666											method: "POST",
8667											dataType: "json",
8668											data: { fk_c_exp_tax_cat: $(this).val(), token: \''.currentToken().'\' },
8669											url: "'.(DOL_URL_ROOT.'/expensereport/ajax/ajaxik.php?'.$params).'",
8670										}).done(function( data, textStatus, jqXHR ) {
8671											console.log(data);
8672											if (typeof data.up != "undefined") {
8673												$("input[name=value_unit]").val(data.up);
8674												$("select[name='.$htmlname.']").attr("title", data.title);
8675											} else {
8676												$("input[name=value_unit]").val("");
8677												$("select[name='.$htmlname.']").attr("title", "");
8678											}
8679										});
8680									}
8681								});
8682							});
8683						</script>';
8684					}
8685				}
8686			}
8687		} else {
8688			dol_print_error($db);
8689		}
8690
8691		return $out;
8692	}
8693
8694	/**
8695	 * Return HTML to show the select ranges of expense range
8696	 *
8697	 * @param	string	$selected    preselected category
8698	 * @param	string	$htmlname    name of HTML select list
8699	 * @param	integer	$useempty    1=Add empty line
8700	 * @return	string
8701	 */
8702	public function selectExpenseRanges($selected = '', $htmlname = 'fk_range', $useempty = 0)
8703	{
8704		global $db, $conf, $langs;
8705
8706		$out = '';
8707		$sql = 'SELECT rowid, range_ik FROM '.MAIN_DB_PREFIX.'c_exp_tax_range';
8708		$sql .= ' WHERE entity = '.$conf->entity.' AND active = 1';
8709
8710		$resql = $db->query($sql);
8711		if ($resql) {
8712			$out = '<select id="select_'.$htmlname.'" name="'.$htmlname.'" class="'.$htmlname.' flat minwidth75imp">';
8713			if ($useempty) {
8714				$out .= '<option value="0"></option>';
8715			}
8716
8717			while ($obj = $db->fetch_object($resql)) {
8718				$out .= '<option '.($selected == $obj->rowid ? 'selected="selected"' : '').' value="'.$obj->rowid.'">'.price($obj->range_ik, 0, $langs, 1, 0).'</option>';
8719			}
8720			$out .= '</select>';
8721		} else {
8722			dol_print_error($db);
8723		}
8724
8725		return $out;
8726	}
8727
8728	/**
8729	 * Return HTML to show a select of expense
8730	 *
8731	 * @param	string	$selected    preselected category
8732	 * @param	string	$htmlname    name of HTML select list
8733	 * @param	integer	$useempty    1=Add empty choice
8734	 * @param	integer	$allchoice   1=Add all choice
8735	 * @param	integer	$useid       0=use 'code' as key, 1=use 'id' as key
8736	 * @return	string
8737	 */
8738	public function selectExpense($selected = '', $htmlname = 'fk_c_type_fees', $useempty = 0, $allchoice = 1, $useid = 0)
8739	{
8740		global $db, $langs;
8741
8742		$out = '';
8743		$sql = 'SELECT id, code, label FROM '.MAIN_DB_PREFIX.'c_type_fees';
8744		$sql .= ' WHERE active = 1';
8745
8746		$resql = $db->query($sql);
8747		if ($resql) {
8748			$out = '<select id="select_'.$htmlname.'" name="'.$htmlname.'" class="'.$htmlname.' flat minwidth75imp">';
8749			if ($useempty) {
8750				$out .= '<option value="0"></option>';
8751			}
8752			if ($allchoice) {
8753				$out .= '<option value="-1">'.$langs->trans('AllExpenseReport').'</option>';
8754			}
8755
8756			$field = 'code';
8757			if ($useid) {
8758				$field = 'id';
8759			}
8760
8761			while ($obj = $db->fetch_object($resql)) {
8762				$key = $langs->trans($obj->code);
8763				$out .= '<option '.($selected == $obj->{$field} ? 'selected="selected"' : '').' value="'.$obj->{$field}.'">'.($key != $obj->code ? $key : $obj->label).'</option>';
8764			}
8765			$out .= '</select>';
8766		} else {
8767			dol_print_error($db);
8768		}
8769
8770		return $out;
8771	}
8772
8773	/**
8774	 *  Output a combo list with invoices qualified for a third party
8775	 *
8776	 *  @param	int		$socid      	Id third party (-1=all, 0=only projects not linked to a third party, id=projects not linked or linked to third party id)
8777	 *  @param  int		$selected   	Id invoice preselected
8778	 *  @param  string	$htmlname   	Name of HTML select
8779	 *	@param	int		$maxlength		Maximum length of label
8780	 *	@param	int		$option_only	Return only html options lines without the select tag
8781	 *	@param	string	$show_empty		Add an empty line ('1' or string to show for empty line)
8782	 *  @param	int		$discard_closed Discard closed projects (0=Keep,1=hide completely,2=Disable)
8783	 *  @param	int		$forcefocus		Force focus on field (works with javascript only)
8784	 *  @param	int		$disabled		Disabled
8785	 *  @param	string	$morecss        More css added to the select component
8786	 *  @param	string	$projectsListId ''=Automatic filter on project allowed. List of id=Filter on project ids.
8787	 *  @param	string	$showproject	'all' = Show project info, ''=Hide project info
8788	 *  @param	User	$usertofilter	User object to use for filtering
8789	 *	@return int         			Nbr of project if OK, <0 if KO
8790	 */
8791	public function selectInvoice($socid = -1, $selected = '', $htmlname = 'invoiceid', $maxlength = 24, $option_only = 0, $show_empty = '1', $discard_closed = 0, $forcefocus = 0, $disabled = 0, $morecss = 'maxwidth500', $projectsListId = '', $showproject = 'all', $usertofilter = null)
8792	{
8793		global $user, $conf, $langs;
8794
8795		require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
8796
8797		if (is_null($usertofilter)) {
8798			$usertofilter = $user;
8799		}
8800
8801		$out = '';
8802
8803		$hideunselectables = false;
8804		if (!empty($conf->global->PROJECT_HIDE_UNSELECTABLES)) {
8805			$hideunselectables = true;
8806		}
8807
8808		if (empty($projectsListId)) {
8809			if (empty($usertofilter->rights->projet->all->lire)) {
8810				$projectstatic = new Project($this->db);
8811				$projectsListId = $projectstatic->getProjectsAuthorizedForUser($usertofilter, 0, 1);
8812			}
8813		}
8814
8815		// Search all projects
8816		$sql = "SELECT f.rowid, f.ref as fref, 'nolabel' as flabel, p.rowid as pid, f.ref,
8817            p.title, p.fk_soc, p.fk_statut, p.public,";
8818		$sql .= ' s.nom as name';
8819		$sql .= ' FROM '.MAIN_DB_PREFIX.'projet as p';
8820		$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'societe as s ON s.rowid = p.fk_soc,';
8821		$sql .= ' '.MAIN_DB_PREFIX.'facture as f';
8822		$sql .= " WHERE p.entity IN (".getEntity('project').")";
8823		$sql .= " AND f.fk_projet = p.rowid AND f.fk_statut=0"; //Brouillons seulement
8824		//if ($projectsListId) $sql.= " AND p.rowid IN (".$this->db->sanitize($projectsListId).")";
8825		//if ($socid == 0) $sql.= " AND (p.fk_soc=0 OR p.fk_soc IS NULL)";
8826		//if ($socid > 0)  $sql.= " AND (p.fk_soc=".((int) $socid)." OR p.fk_soc IS NULL)";
8827		$sql .= " ORDER BY p.ref, f.ref ASC";
8828
8829		$resql = $this->db->query($sql);
8830		if ($resql) {
8831			// Use select2 selector
8832			if (!empty($conf->use_javascript_ajax)) {
8833				include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
8834				$comboenhancement = ajax_combobox($htmlname, '', 0, $forcefocus);
8835				$out .= $comboenhancement;
8836				$morecss = 'minwidth200imp maxwidth500';
8837			}
8838
8839			if (empty($option_only)) {
8840				$out .= '<select class="valignmiddle flat'.($morecss ? ' '.$morecss : '').'"'.($disabled ? ' disabled="disabled"' : '').' id="'.$htmlname.'" name="'.$htmlname.'">';
8841			}
8842			if (!empty($show_empty)) {
8843				$out .= '<option value="0" class="optiongrey">';
8844				if (!is_numeric($show_empty)) {
8845					$out .= $show_empty;
8846				} else {
8847					$out .= '&nbsp;';
8848				}
8849				$out .= '</option>';
8850			}
8851			$num = $this->db->num_rows($resql);
8852			$i = 0;
8853			if ($num) {
8854				while ($i < $num) {
8855					$obj = $this->db->fetch_object($resql);
8856					// If we ask to filter on a company and user has no permission to see all companies and project is linked to another company, we hide project.
8857					if ($socid > 0 && (empty($obj->fk_soc) || $obj->fk_soc == $socid) && empty($usertofilter->rights->societe->lire)) {
8858						// Do nothing
8859					} else {
8860						if ($discard_closed == 1 && $obj->fk_statut == Project::STATUS_CLOSED) {
8861							$i++;
8862							continue;
8863						}
8864
8865						$labeltoshow = '';
8866
8867						if ($showproject == 'all') {
8868							$labeltoshow .= dol_trunc($obj->ref, 18); // Invoice ref
8869							if ($obj->name) {
8870								$labeltoshow .= ' - '.$obj->name; // Soc name
8871							}
8872
8873							$disabled = 0;
8874							if ($obj->fk_statut == Project::STATUS_DRAFT) {
8875								$disabled = 1;
8876								$labeltoshow .= ' - '.$langs->trans("Draft");
8877							} elseif ($obj->fk_statut == Project::STATUS_CLOSED) {
8878								if ($discard_closed == 2) {
8879									$disabled = 1;
8880								}
8881								$labeltoshow .= ' - '.$langs->trans("Closed");
8882							} elseif ($socid > 0 && (!empty($obj->fk_soc) && $obj->fk_soc != $socid)) {
8883								$disabled = 1;
8884								$labeltoshow .= ' - '.$langs->trans("LinkedToAnotherCompany");
8885							}
8886						}
8887
8888						if (!empty($selected) && $selected == $obj->rowid) {
8889							$out .= '<option value="'.$obj->rowid.'" selected';
8890							//if ($disabled) $out.=' disabled';						// with select2, field can't be preselected if disabled
8891							$out .= '>'.$labeltoshow.'</option>';
8892						} else {
8893							if ($hideunselectables && $disabled && ($selected != $obj->rowid)) {
8894								$resultat = '';
8895							} else {
8896								$resultat = '<option value="'.$obj->rowid.'"';
8897								if ($disabled) {
8898									$resultat .= ' disabled';
8899								}
8900								//if ($obj->public) $labeltoshow.=' ('.$langs->trans("Public").')';
8901								//else $labeltoshow.=' ('.$langs->trans("Private").')';
8902								$resultat .= '>';
8903								$resultat .= $labeltoshow;
8904								$resultat .= '</option>';
8905							}
8906							$out .= $resultat;
8907						}
8908					}
8909					$i++;
8910				}
8911			}
8912			if (empty($option_only)) {
8913				$out .= '</select>';
8914			}
8915
8916			print $out;
8917
8918			$this->db->free($resql);
8919			return $num;
8920		} else {
8921			dol_print_error($this->db);
8922			return -1;
8923		}
8924	}
8925
8926	/**
8927	 * Output the component to make advanced search criteries
8928	 *
8929	 * @param	array		$arrayofcriterias			          Array of available search criterias. Example: array($object->element => $object->fields, 'otherfamily' => otherarrayoffields, ...)
8930	 * @param	array		$search_component_params	          Array of selected search criterias
8931	 * @param   array       $arrayofinputfieldsalreadyoutput      Array of input fields already inform. The component will not generate a hidden input field if it is in this list.
8932	 * @return	string									          HTML component for advanced search
8933	 */
8934	public function searchComponent($arrayofcriterias, $search_component_params, $arrayofinputfieldsalreadyoutput = array())
8935	{
8936		global $langs;
8937
8938		$ret = '';
8939
8940		$ret .= '<div class="nowrap centpercent">';
8941		//$ret .= '<button type="submit" class="liste_titre button_removefilter" name="button_removefilter_x" value="x"><span class="fa fa-remove"></span></button>';
8942		$ret .= '<a href="#" class="dropdownsearch-toggle unsetcolor paddingright">';
8943		$ret .= '<span class="fas fa-filter linkobject boxfilter" title="Filter" id="idsubimgproductdistribution"></span>';
8944		$ret .= $langs->trans("Filters");
8945		$ret .= '</a>';
8946		//$ret .= '<button type="submit" class="liste_titre button_search paddingleftonly" name="button_search_x" value="x"><span class="fa fa-search"></span></button>';
8947		$ret .= '<div name="search_component_params" class="search_component_params inline-block minwidth500 maxwidth300onsmartphone valignmiddle">';
8948		$texttoshow = '<div class="opacitymedium inline-block search_component_searchtext">'.$langs->trans("Search").'</div>';
8949
8950		$ret .= '<div class="search_component inline-block valignmiddle">'.$texttoshow.'</div>';
8951		$ret .= '</div>';
8952		$ret .= '<input type="hidden" name="search_component_params_hidden" class="search_component_params_hidden" value="'.GETPOST("search_component_params_hidden").'">';
8953		// For compatibility with forms that show themself the search criteria in addition of this component, we output the fields
8954		foreach ($arrayofcriterias as $criterias) {
8955			foreach ($criterias as $criteriafamilykey => $criteriafamilyval) {
8956				if (in_array('search_'.$criteriafamilykey, $arrayofinputfieldsalreadyoutput)) {
8957					continue;
8958				}
8959				if (in_array($criteriafamilykey, array('rowid', 'ref_ext', 'entity', 'extraparams'))) {
8960					continue;
8961				}
8962				if (in_array($criteriafamilyval['type'], array('date', 'datetime', 'timestamp'))) {
8963					$ret .= '<input type="hidden" name="search_'.$criteriafamilykey.'_start">';
8964					$ret .= '<input type="hidden" name="search_'.$criteriafamilykey.'_startyear">';
8965					$ret .= '<input type="hidden" name="search_'.$criteriafamilykey.'_startmonth">';
8966					$ret .= '<input type="hidden" name="search_'.$criteriafamilykey.'_startday">';
8967					$ret .= '<input type="hidden" name="search_'.$criteriafamilykey.'_end">';
8968					$ret .= '<input type="hidden" name="search_'.$criteriafamilykey.'_endyear">';
8969					$ret .= '<input type="hidden" name="search_'.$criteriafamilykey.'_endmonth">';
8970					$ret .= '<input type="hidden" name="search_'.$criteriafamilykey.'_endday">';
8971				} else {
8972					$ret .= '<input type="hidden" name="search_'.$criteriafamilykey.'">';
8973				}
8974			}
8975		}
8976		$ret .= '</div>';
8977
8978
8979		return $ret;
8980	}
8981
8982	/**
8983	 * selectModelMail
8984	 *
8985	 * @param   string   $prefix     	Prefix
8986	 * @param   string   $modelType  	Model type
8987	 * @param	int		 $default	 	1=Show also Default mail template
8988	 * @param	int		 $addjscombo	Add js combobox
8989	 * @return  string               	HTML select string
8990	 */
8991	public function selectModelMail($prefix, $modelType = '', $default = 0, $addjscombo = 0)
8992	{
8993		global $langs, $db, $user;
8994
8995		$retstring = '';
8996
8997		$TModels = array();
8998
8999		include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php';
9000		$formmail = new FormMail($db);
9001		$result = $formmail->fetchAllEMailTemplate($modelType, $user, $langs);
9002
9003		if ($default) {
9004			$TModels[0] = $langs->trans('DefaultMailModel');
9005		}
9006		if ($result > 0) {
9007			foreach ($formmail->lines_model as $model) {
9008				$TModels[$model->id] = $model->label;
9009			}
9010		}
9011
9012		$retstring .= '<select class="flat" id="select_'.$prefix.'model_mail" name="'.$prefix.'model_mail">';
9013
9014		foreach ($TModels as $id_model => $label_model) {
9015			$retstring .= '<option value="'.$id_model.'"';
9016			$retstring .= ">".$label_model."</option>";
9017		}
9018
9019		$retstring .= "</select>";
9020
9021		if ($addjscombo) {
9022			$retstring .= ajax_combobox('select_'.$prefix.'model_mail');
9023		}
9024
9025		return $retstring;
9026	}
9027}
9028