dolibarr 25.0.0-alpha
html.form.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (c) 2002-2007 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2004-2012 Laurent Destailleur <eldy@users.sourceforge.net>
4 * Copyright (C) 2004 Benoit Mortier <benoit.mortier@opensides.be>
5 * Copyright (C) 2004 Sebastien Di Cintio <sdicintio@ressource-toi.org>
6 * Copyright (C) 2004 Eric Seigne <eric.seigne@ryxeo.com>
7 * Copyright (C) 2005-2017 Regis Houssin <regis.houssin@inodbox.com>
8 * Copyright (C) 2006 Andre Cianfarani <acianfa@free.fr>
9 * Copyright (C) 2006 Marc Barilley/Ocebo <marc@ocebo.com>
10 * Copyright (C) 2007 Franky Van Liedekerke <franky.van.liedekerker@telenet.be>
11 * Copyright (C) 2007 Patrick Raguin <patrick.raguin@gmail.com>
12 * Copyright (C) 2010 Juanjo Menent <jmenent@2byte.es>
13 * Copyright (C) 2010-2021 Philippe Grand <philippe.grand@atoo-net.com>
14 * Copyright (C) 2011 Herve Prot <herve.prot@symeos.com>
15 * Copyright (C) 2012-2016 Marcos García <marcosgdf@gmail.com>
16 * Copyright (C) 2012 Cedric Salvador <csalvador@gpcsolutions.fr>
17 * Copyright (C) 2012-2015 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr>
18 * Copyright (C) 2014-2026 Alexandre Spangaro <alexandre@inovea-conseil.com>
19 * Copyright (C) 2018-2022 Ferran Marcet <fmarcet@2byte.es>
20 * Copyright (C) 2018-2026 Frédéric France <frederic.france@free.fr>
21 * Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
22 * Copyright (C) 2018 Christophe Battarel <christophe@altairis.fr>
23 * Copyright (C) 2018 Josep Lluis Amador <joseplluis@lliuretic.cat>
24 * Copyright (C) 2023 Joachim Kueter <git-jk@bloxera.com>
25 * Copyright (C) 2023 Nick Fragoulis
26 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
27 * Copyright (C) 2024 William Mead <william.mead@manchenumerique.fr>
28 * Copyright (C) 2026 Lenin Rivas <lenin.rivas777@gmail.com>
29 * Copyright (C) 2026 Open-Dsi <support@open-dsi.fr>
30 * Copyright (C) 2026 Jose MARTINEZ <jose.martinez@pichinov.com>
31 *
32 * This program is free software; you can redistribute it and/or modify
33 * it under the terms of the GNU General Public License as published by
34 * the Free Software Foundation; either version 3 of the License, or
35 * (at your option) any later version.
36 *
37 * This program is distributed in the hope that it will be useful,
38 * but WITHOUT ANY WARRANTY; without even the implied warranty of
39 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
40 * GNU General Public License for more details.
41 *
42 * You should have received a copy of the GNU General Public License
43 * along with this program. If not, see <https://www.gnu.org/licenses/>.
44 */
45
59class Form
60{
64 public $db;
65
69 public $error = '';
70
74 public $errors = array();
75
76 // Some properties used to return data by some methods
78 public $result;
79
81 public $num;
82
83 // Cache arrays
85 public $cache_types_paiements = array();
87 public $cache_conditions_paiements = array();
89 public $cache_transport_mode = array();
91 public $cache_availability = array();
93 public $cache_demand_reason = array();
95 public $cache_types_fees = array();
97 public $cache_vatrates = array();
99 public $cache_invoice_subtype = array();
101 public $cache_rule_for_lines_dates = array();
102
106 private $phoneInputSharedJsLoaded = false;
107
108
114 public function __construct($db)
115 {
116 $this->db = $db;
117 }
118
127 public function getDurationTypes(Translate $langs, $plurial = true, $reverse = false)
128 {
129 if ($plurial) {
130 $arrayoftypes = [
131 'y' => $langs->trans('Years'),
132 'm' => $langs->trans('Month'),
133 'w' => $langs->trans('Weeks'),
134 'd' => $langs->trans('Days'),
135 'h' => $langs->trans('Hours'),
136 'i' => $langs->trans('Minutes'),
137 's' => $langs->trans('Seconds'),
138 ];
139 } else {
140 $arrayoftypes = [
141 "y" => $langs->trans("Year"),
142 "m" => $langs->trans("Month"),
143 "w" => $langs->trans("Week"),
144 "d" => $langs->trans("Day"),
145 "h" => $langs->trans("Hour"),
146 "i" => $langs->trans("Minute"),
147 's' => $langs->trans('Second'),
148 ];
149 }
150 if ($reverse) {
151 return array_reverse($arrayoftypes);
152 } else {
153 return $arrayoftypes;
154 }
155 }
156
173 public function editfieldkey($text, $htmlname, $preselected, $object, $perm, $typeofdata = 'string', $moreparam = '', $fieldrequired = 0, $notabletag = 0, $paramid = 'id', $help = '')
174 {
175 global $langs;
176
177 $ret = '';
178
179 // TODO change for compatibility
180 if (getDolGlobalString('MAIN_USE_EDIT_IN_PLACE') && !preg_match('/^select;/', $typeofdata)) {
181 if ($perm) {
182 $tmp = explode(':', $typeofdata);
183 $ret .= '<div class="editkey_' . $tmp[0] . (!empty($tmp[1]) ? ' ' . $tmp[1] : '') . '" id="' . $htmlname . '">';
184 if ($fieldrequired) {
185 $ret .= '<span class="fieldrequired">';
186 }
187 if ($help) {
188 $ret .= $this->textwithpicto($langs->trans($text), $help);
189 } else {
190 $ret .= $langs->trans($text);
191 }
192 if ($fieldrequired) {
193 $ret .= '</span>';
194 }
195 $ret .= '</div>' . "\n";
196 } else {
197 if ($fieldrequired) {
198 $ret .= '<span class="fieldrequired">';
199 }
200 if ($help) {
201 $ret .= $this->textwithpicto($langs->trans($text), $help);
202 } else {
203 $ret .= $langs->trans($text);
204 }
205 if ($fieldrequired) {
206 $ret .= '</span>';
207 }
208 }
209 } else {
210 if (empty($notabletag) && $perm) {
211 $ret .= '<table class="nobordernopadding centpercent"><tr><td class="nowrap">';
212 }
213 if ($fieldrequired) {
214 $ret .= '<span class="fieldrequired">';
215 }
216 if ($help) {
217 $ret .= $this->textwithpicto($langs->trans($text), $help);
218 } else {
219 $ret .= $langs->trans($text);
220 }
221 if ($fieldrequired) {
222 $ret .= '</span>';
223 }
224 if (!empty($notabletag)) {
225 $ret .= ' ';
226 }
227 if (empty($notabletag) && $perm) {
228 $ret .= '</td>';
229 }
230 if (empty($notabletag) && $perm) {
231 $ret .= '<td class="right">';
232 }
233 if ($htmlname && GETPOST('action', 'aZ09') != 'edit' . $htmlname && $perm && is_object($object)) {
234 $ret .= '<a class="editfielda reposition" href="' . dolBuildUrl($_SERVER["PHP_SELF"], ['action' => 'edit' . $htmlname, $paramid => $object->id], true) . $moreparam . '">';
235 $ret .= img_edit($langs->trans('Edit'), ($notabletag ? 0 : 1));
236 $ret .= '</a>';
237 }
238 if (!empty($notabletag) && $notabletag == 1) {
239 if ($text) {
240 $ret .= ' : ';
241 } else {
242 $ret .= ' ';
243 }
244 }
245 if (!empty($notabletag) && $notabletag == 3) {
246 $ret .= ' ';
247 }
248 if (empty($notabletag) && $perm) {
249 $ret .= '</td>';
250 }
251 if (empty($notabletag) && $perm) {
252 $ret .= '</tr></table>';
253 }
254 }
255
256 return $ret;
257 }
258
282 public function editfieldval($text, $htmlname, $value, $object, $perm, $typeofdata = 'string', $editvalue = '', $extObject = null, $custommsg = null, $moreparam = '', $notabletag = 1, $formatfunc = '', $paramid = 'id', $gm = 'auto', $moreoptions = array(), $editaction = '')
283 {
284 global $conf, $langs;
285
286 $ret = '';
287
288 // Check parameters
289 if (empty($typeofdata)) {
290 return 'ErrorBadParameter typeofdata is empty';
291 }
292 // Clean parameter $typeofdata
293 if ($typeofdata == 'datetime') {
294 $typeofdata = 'dayhour';
295 }
296 if ($typeofdata == 'date') {
297 $typeofdata = 'day';
298 }
299 $reg = array();
300 if (preg_match('/^(\w+)\‍((\d+)\‍)$/', $typeofdata, $reg)) {
301 if ($reg[1] == 'varchar') {
302 $typeofdata = 'string';
303 } elseif ($reg[1] == 'int') {
304 $typeofdata = 'numeric';
305 } else {
306 return 'ErrorBadParameter ' . $typeofdata;
307 }
308 }
309
310 // When option to edit inline is activated
311 if (getDolGlobalString('MAIN_USE_EDIT_IN_PLACE') && !preg_match('/^select;|day|datepicker|dayhour|datehourpicker/', $typeofdata)) { // TODO add jquery timepicker and support select
312 $ret .= $this->editInPlace($object, $value, $htmlname, ($perm ? 1 : 0), $typeofdata, $editvalue, $extObject, $custommsg);
313 } else {
314 if ($editaction == '') {
315 $editaction = GETPOST('action', 'aZ09');
316 }
317 $editmode = ($editaction == 'edit' . $htmlname);
318 if ($editmode) { // edit mode
319 $ret .= "<!-- formeditfieldval -->\n";
320 $ret .= '<form method="post" action="' . $_SERVER["PHP_SELF"] . ($moreparam ? '?' . $moreparam : '') . '">';
321 $ret .= '<input type="hidden" name="action" value="set' . $htmlname . '">';
322 $ret .= '<input type="hidden" name="token" value="' . newToken() . '">';
323 $ret .= '<input type="hidden" name="' . $paramid . '" value="' . $object->id . '">';
324 if (empty($notabletag)) {
325 $ret .= '<table class="nobordernopadding centpercent">';
326 }
327 if (empty($notabletag)) {
328 $ret .= '<tr><td>';
329 }
330 if (preg_match('/^(string|safehtmlstring|email|phone|url)/', $typeofdata)) {
331 $tmp = explode(':', $typeofdata);
332 $ret .= '<input type="text" id="' . $htmlname . '" name="' . $htmlname . '" value="' . ($editvalue ? $editvalue : $value) . '"' . (empty($tmp[1]) ? '' : ' size="' . $tmp[1] . '"') . ' autofocus spellcheck="false">';
333 } elseif (preg_match('/^(integer)/', $typeofdata)) {
334 $tmp = explode(':', $typeofdata);
335 $valuetoshow = price2num($editvalue ? $editvalue : $value, 0);
336 $ret .= '<input type="text" id="' . $htmlname . '" name="' . $htmlname . '" value="' . $valuetoshow . '"' . (empty($tmp[1]) ? '' : ' size="' . $tmp[1] . '"') . ' autofocus>';
337 } elseif (preg_match('/^(numeric|amount)/', $typeofdata)) {
338 $tmp = explode(':', $typeofdata);
339 $valuetoshow = price2num($editvalue ? $editvalue : $value);
340 $ret .= '<input type="text" id="' . $htmlname . '" name="' . $htmlname . '" value="' . ($valuetoshow != '' ? price($valuetoshow) : '') . '"' . (empty($tmp[1]) ? '' : ' size="' . $tmp[1] . '"') . ' autofocus>';
341 } elseif (preg_match('/^(checkbox)/', $typeofdata)) {
342 $tmp = explode(':', $typeofdata);
343 $ret .= '<input type="checkbox" id="' . $htmlname . '" name="' . $htmlname . '" value="' . ($value ? $value : 'on') . '"' . ($value ? ' checked' : '') . (empty($tmp[1]) ? '' : $tmp[1]) . '/>';
344 } elseif (preg_match('/^text/', $typeofdata) || preg_match('/^note/', $typeofdata)) { // if wysiwyg is enabled $typeofdata = 'ckeditor'
345 $tmp = explode(':', $typeofdata);
346 $cols = (empty($tmp[2]) ? '' : $tmp[2]);
347 $morealt = '';
348 if (preg_match('/%/', $cols)) {
349 $morealt = ' style="width: ' . $cols . '"';
350 $cols = '';
351 }
352 $valuetoshow = ($editvalue ? $editvalue : $value);
353 $ret .= '<textarea id="' . $htmlname . '" name="' . $htmlname . '" wrap="soft" rows="' . (empty($tmp[1]) ? '20' : $tmp[1]) . '"' . ($cols ? ' cols="' . $cols . '"' : 'class="quatrevingtpercent"') . $morealt . '" autofocus>';
354 // textarea convert automatically entities chars into simple chars.
355 // 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 wysiwyg is off.
356 $valuetoshow = str_replace('&', '&amp;', $valuetoshow);
357 $ret .= dol_htmlwithnojs(dol_string_neverthesehtmltags($valuetoshow, array('textarea')));
358 $ret .= '</textarea><div class="clearboth"></div>';
359 } elseif ($typeofdata == 'day' || $typeofdata == 'datepicker') {
360 $addnowlink = empty($moreoptions['addnowlink']) ? 0 : $moreoptions['addnowlink'];
361 $adddateof = empty($moreoptions['adddateof']) ? '' : $moreoptions['adddateof'];
362 $labeladddateof = empty($moreoptions['labeladddateof']) ? '' : $moreoptions['labeladddateof'];
363 $ret .= $this->selectDate($value, $htmlname, 0, 0, 1, 'form' . $htmlname, 1, $addnowlink, 0, '', '', $adddateof, '', 1, $labeladddateof, '', $gm);
364 } elseif ($typeofdata == 'dayhour' || $typeofdata == 'datehourpicker') {
365 $addnowlink = empty($moreoptions['addnowlink']) ? 0 : $moreoptions['addnowlink'];
366 $adddateof = empty($moreoptions['adddateof']) ? '' : $moreoptions['adddateof'];
367 $labeladddateof = empty($moreoptions['labeladddateof']) ? '' : $moreoptions['labeladddateof'];
368 $ret .= $this->selectDate($value, $htmlname, 1, 1, 1, 'form' . $htmlname, 1, $addnowlink, 0, '', '', $adddateof, '', 1, $labeladddateof, '', $gm);
369 } elseif (preg_match('/^select;/', $typeofdata)) {
370 $arraydata = explode(',', preg_replace('/^select;/', '', $typeofdata));
371 $arraylist = array();
372 foreach ($arraydata as $val) {
373 $tmp = explode(':', $val);
374 $tmpkey = str_replace('|', ':', $tmp[0]);
375 $arraylist[$tmpkey] = $tmp[1];
376 }
377 $ret .= $this->selectarray($htmlname, $arraylist, $value);
378 } elseif (preg_match('/^link/', $typeofdata)) {
379 // TODO Not yet implemented. See code for extrafields
380 } elseif (preg_match('/^ckeditor/', $typeofdata)) {
381 $tmp = explode(':', $typeofdata); // Example: ckeditor:dolibarr_zzz:width:height:savemethod:toolbarstartexpanded:rows:cols:uselocalbrowser
382 require_once DOL_DOCUMENT_ROOT . '/core/class/doleditor.class.php';
383 $doleditor = new DolEditor($htmlname, ($editvalue ? $editvalue : $value), (empty($tmp[2]) ? '' : $tmp[2]), (empty($tmp[3]) ? 100 : (int) $tmp[3]), (empty($tmp[1]) ? 'dolibarr_notes' : $tmp[1]), 'In', (empty($tmp[5]) ? false : (bool) $tmp[5]), (isset($tmp[8]) ? ($tmp[8] ? true : false) : true), true, (empty($tmp[6]) ? 20 : (int) $tmp[6]), (empty($tmp[7]) ? '100' : $tmp[7]));
384 $ret .= $doleditor->Create(1);
385 } elseif ($typeofdata == 'asis') {
386 $ret .= ($editvalue ? $editvalue : $value);
387 }
388 if (empty($notabletag)) {
389 $ret .= '</td>';
390 }
391
392 // Button save-cancel
393 if (empty($notabletag)) {
394 $ret .= '<td>';
395 }
396 //else $ret.='<div class="clearboth"></div>';
397 $ret .= '<input type="submit" class="smallpaddingimp nomargingtop nomarginbottom button' . (empty($notabletag) ? '' : ' ') . '" name="modify" value="' . $langs->trans("Save") . '">';
398 if (preg_match('/ckeditor|textarea/', $typeofdata) && empty($notabletag)) {
399 $ret .= '<br>' . "\n";
400 }
401 $ret .= '<input type="submit" class="smallpaddingimp nomargingtop nomarginbottom button button-cancel' . (empty($notabletag) ? '' : ' ') . '" name="cancel" value="' . $langs->trans("Cancel") . '">';
402 if (empty($notabletag)) {
403 $ret .= '</td>';
404 }
405
406 if (empty($notabletag)) {
407 $ret .= '</tr></table>' . "\n";
408 }
409 $ret .= '</form>' . "\n";
410 } else { // view mode
411 if (preg_match('/^email/', $typeofdata)) {
412 $ret .= dol_print_email($value, 0, 0, 0, 0, 1);
413 } elseif (preg_match('/^phone/', $typeofdata)) {
414 $ret .= dol_print_phone($value, '_blank', 32, 1);
415 } elseif (preg_match('/^url/', $typeofdata)) {
416 $ret .= dol_print_url($value, '_blank', 32, 1);
417 } elseif (preg_match('/^(amount|numeric)/', $typeofdata)) {
418 $ret .= ($value != '' ? price($value, 0, $langs, 0, -1, -1, $conf->currency) : '');
419 } elseif (preg_match('/^checkbox/', $typeofdata)) {
420 $tmp = explode(':', $typeofdata);
421 $ret .= '<input type="checkbox" disabled id="' . $htmlname . '" name="' . $htmlname . '" value="' . $value . '"' . ($value ? ' checked' : '') . ($tmp[1] ? $tmp[1] : '') . '/>';
422 } elseif (preg_match('/^text/', $typeofdata) || preg_match('/^note/', $typeofdata)) {
424 } elseif (preg_match('/^(safehtmlstring|restricthtml)/', $typeofdata)) { // 'restricthtml' is not an allowed type for editfieldval. Value is 'safehtmlstring'
426 } elseif ($typeofdata == 'day' || $typeofdata == 'datepicker') {
427 $ret .= '<span class="valuedate">' . dol_print_date($value, 'day', $gm) . '</span>';
428 } elseif ($typeofdata == 'dayhour' || $typeofdata == 'datehourpicker') {
429 $ret .= '<span class="valuedate">' . dol_print_date($value, 'dayhour', $gm) . '</span>';
430 } elseif (preg_match('/^select;/', $typeofdata)) {
431 $arraydata = explode(',', preg_replace('/^select;/', '', $typeofdata));
432 $arraylist = array();
433 foreach ($arraydata as $val) {
434 $tmp = explode(':', $val);
435 $arraylist[$tmp[0]] = $tmp[1];
436 }
437 $ret .= $arraylist[$value];
438 if ($htmlname == 'fk_product_type') {
439 if ($value == 0) {
440 $ret = img_picto($langs->trans("Product"), 'product', 'class="paddingleftonly paddingrightonly colorgrey"') . $ret;
441 } else {
442 $ret = img_picto($langs->trans("Service"), 'service', 'class="paddingleftonly paddingrightonly colorgrey"') . $ret;
443 }
444 }
445 } elseif (preg_match('/^ckeditor/', $typeofdata)) {
446 $tmpcontent = dol_htmlentitiesbr($value);
447 if (getDolGlobalString('MAIN_DISABLE_NOTES_TAB')) {
448 $firstline = preg_replace('/<br>.*/', '', $tmpcontent);
449 $firstline = preg_replace('/[\n\r].*/', '', $firstline);
450 $tmpcontent = $firstline . ((strlen($firstline) != strlen($tmpcontent)) ? '...' : '');
451 }
452 // We don't use dol_escape_htmltag to get the html formatting active, but this need we must also
453 // clean data from some dangerous html
455 } else {
456 if (empty($moreoptions['valuealreadyhtmlescaped'])) {
457 $ret .= dol_escape_htmltag($value);
458 } else {
459 $ret .= $value; // $value must be already html escaped.
460 }
461 }
462
463 // Custom format if parameter $formatfunc has been provided
464 if ($formatfunc && method_exists($object, $formatfunc)) {
465 $ret = $object->$formatfunc($ret);
466 }
467 }
468 }
469 return $ret;
470 }
471
483 public function widgetForTranslation($fieldname, $object, $perm, $typeofdata = 'string', $check = '', $morecss = '')
484 {
485 global $conf, $langs, $extralanguages;
486
487 $result = '';
488
489 // List of extra languages
490 $arrayoflangcode = array();
491 if (getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE')) {
492 $arrayoflangcode[] = getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE');
493 }
494
495 if (is_array($arrayoflangcode) && count($arrayoflangcode)) {
496 if (!is_object($extralanguages)) {
497 include_once DOL_DOCUMENT_ROOT . '/core/class/extralanguages.class.php';
498 $extralanguages = new ExtraLanguages($this->db);
499 }
500 $extralanguages->fetch_name_extralanguages('societe');
501
502 // ExtraLanguages::fetch_name_extralanguages() leaves $this->attributes empty
503 // when MAIN_USE_ALTERNATE_TRANSLATION_FOR is not configured, so PHP 8 raises
504 // 'Undefined array key' on the read below if we do not guard it (issue #34596).
505 if (empty($extralanguages->attributes[$object->element]) || !is_array($extralanguages->attributes[$object->element]) || empty($extralanguages->attributes[$object->element][$fieldname])) {
506 return ''; // No extralang field to show
507 }
508
509 $result .= '<!-- Widget for translation -->' . "\n";
510 $result .= '<div class="inline-block paddingleft image-' . $object->element . '-' . $fieldname . '">';
511 $s = img_picto($langs->trans("ShowOtherLanguages"), 'language', '', 0, 0, 0, '', 'fa-15 editfieldlang');
512 $result .= $s;
513 $result .= '</div>';
514
515 $result .= '<div class="inline-block hidden field-' . $object->element . '-' . $fieldname . '">';
516
517 $resultforextrlang = '';
518 foreach ($arrayoflangcode as $langcode) {
519 $valuetoshow = GETPOSTISSET('field-' . $object->element . "-" . $fieldname . "-" . $langcode) ? GETPOST('field-' . $object->element . '-' . $fieldname . "-" . $langcode, $check) : '';
520 if (empty($valuetoshow)) {
521 $object->fetchValuesForExtraLanguages();
522 //var_dump($object->array_languages);
523 $valuetoshow = $object->array_languages[$fieldname][$langcode];
524 }
525
526 $s = picto_from_langcode($langcode, 'class="pictoforlang paddingright"');
527 $resultforextrlang .= $s;
528
529 // TODO Use the showInputField() method of ExtraLanguages object
530 if ($typeofdata == 'textarea') {
531 $resultforextrlang .= '<textarea name="field-' . $object->element . "-" . $fieldname . "-" . $langcode . '" id="' . $fieldname . "-" . $langcode . '" class="' . $morecss . '" rows="' . ROWS_2 . '" wrap="soft">';
532 $resultforextrlang .= $valuetoshow;
533 $resultforextrlang .= '</textarea>';
534 } else {
535 $resultforextrlang .= '<input type="text" class="inputfieldforlang ' . ($morecss ? ' ' . $morecss : '') . '" name="field-' . $object->element . '-' . $fieldname . '-' . $langcode . '" value="' . $valuetoshow . '">';
536 }
537 }
538 $result .= $resultforextrlang;
539
540 $result .= '</div>';
541 $result .= '<script nonce="' . getNonce() . '">$(".image-' . $object->element . '-' . $fieldname . '").click(function() { console.log("Toggle lang widget"); jQuery(".field-' . $object->element . '-' . $fieldname . '").toggle(); });</script>';
542 }
543
544 return $result;
545 }
546
560 protected function editInPlace($object, $value, $htmlname, $condition, $inputType = 'textarea', $editvalue = null, $extObject = null, $custommsg = null)
561 {
562 $out = '';
563
564 // Check parameters
565 if (preg_match('/^text/', $inputType)) {
566 $value = dol_nl2br($value);
567 } elseif (preg_match('/^numeric/', $inputType)) {
568 $value = price($value);
569 } elseif ($inputType == 'day' || $inputType == 'datepicker') {
570 $value = dol_print_date($value, 'day');
571 }
572
573 if ($condition) {
574 $element = false;
575 $table_element = false;
576 $fk_element = false;
577 $loadmethod = false;
578 $savemethod = false;
579 $ext_element = false;
580 $button_only = false;
581 $inputOption = '';
582 $rows = '';
583 $cols = '';
584
585 if (is_object($object)) {
586 $element = $object->element;
587 $table_element = $object->table_element;
588 $fk_element = $object->id;
589 }
590
591 if (is_object($extObject)) {
592 $ext_element = $extObject->element;
593 }
594
595 if (preg_match('/^(string|email|numeric)/', $inputType)) {
596 $tmp = explode(':', $inputType);
597 $inputType = $tmp[0];
598 if (!empty($tmp[1])) {
599 $inputOption = $tmp[1];
600 }
601 if (!empty($tmp[2])) {
602 $savemethod = $tmp[2];
603 }
604 $out .= '<input id="width_' . $htmlname . '" value="' . $inputOption . '" type="hidden"/>' . "\n";
605 } elseif ((preg_match('/^day$/', $inputType)) || (preg_match('/^datepicker/', $inputType)) || (preg_match('/^datehourpicker/', $inputType))) {
606 $tmp = explode(':', $inputType);
607 $inputType = $tmp[0];
608 if (!empty($tmp[1])) {
609 $inputOption = $tmp[1];
610 }
611 if (!empty($tmp[2])) {
612 $savemethod = $tmp[2];
613 }
614
615 $out .= '<input id="timestamp" type="hidden"/>' . "\n"; // Use for timestamp format
616 } elseif (preg_match('/^(select|autocomplete)/', $inputType)) {
617 $tmp = explode(':', $inputType);
618 $inputType = $tmp[0];
619 $loadmethod = $tmp[1];
620 if (!empty($tmp[2])) {
621 $savemethod = $tmp[2];
622 }
623 if (!empty($tmp[3])) {
624 $button_only = true;
625 }
626 } elseif (preg_match('/^textarea/', $inputType)) {
627 $tmp = explode(':', $inputType);
628 $inputType = $tmp[0];
629 $rows = (empty($tmp[1]) ? '8' : $tmp[1]);
630 $cols = (empty($tmp[2]) ? '80' : $tmp[2]);
631 } elseif (preg_match('/^ckeditor/', $inputType)) {
632 $tmp = explode(':', $inputType);
633 $inputType = $tmp[0];
634 $toolbar = $tmp[1];
635 if (!empty($tmp[2])) {
636 $width = $tmp[2];
637 }
638 if (!empty($tmp[3])) {
639 $height = $tmp[3];
640 }
641 if (!empty($tmp[4])) {
642 $savemethod = $tmp[4];
643 }
644
645 if (isModEnabled('fckeditor')) {
646 $out .= '<input id="ckeditor_toolbar" value="' . $toolbar . '" type="hidden"/>' . "\n";
647 } else {
648 $inputType = 'textarea';
649 }
650 }
651
652 $out .= '<input id="element_' . $htmlname . '" value="' . $element . '" type="hidden"/>' . "\n";
653 $out .= '<input id="table_element_' . $htmlname . '" value="' . $table_element . '" type="hidden"/>' . "\n";
654 $out .= '<input id="fk_element_' . $htmlname . '" value="' . $fk_element . '" type="hidden"/>' . "\n";
655 $out .= '<input id="loadmethod_' . $htmlname . '" value="' . $loadmethod . '" type="hidden"/>' . "\n";
656 if (!empty($savemethod)) {
657 $out .= '<input id="savemethod_' . $htmlname . '" value="' . $savemethod . '" type="hidden"/>' . "\n";
658 }
659 if (!empty($ext_element)) {
660 $out .= '<input id="ext_element_' . $htmlname . '" value="' . $ext_element . '" type="hidden"/>' . "\n";
661 }
662 if (!empty($custommsg)) {
663 if (is_array($custommsg)) {
664 if (!empty($custommsg['success'])) {
665 $out .= '<input id="successmsg_' . $htmlname . '" value="' . $custommsg['success'] . '" type="hidden"/>' . "\n";
666 }
667 if (!empty($custommsg['error'])) {
668 $out .= '<input id="errormsg_' . $htmlname . '" value="' . $custommsg['error'] . '" type="hidden"/>' . "\n";
669 }
670 } else {
671 $out .= '<input id="successmsg_' . $htmlname . '" value="' . $custommsg . '" type="hidden"/>' . "\n";
672 }
673 }
674 if ($inputType == 'textarea') {
675 $out .= '<input id="textarea_' . $htmlname . '_rows" value="' . $rows . '" type="hidden"/>' . "\n";
676 $out .= '<input id="textarea_' . $htmlname . '_cols" value="' . $cols . '" type="hidden"/>' . "\n";
677 }
678 $out .= '<span id="viewval_' . $htmlname . '" class="viewval_' . $inputType . ($button_only ? ' inactive' : ' active') . '">' . $value . '</span>' . "\n";
679 $out .= '<span id="editval_' . $htmlname . '" class="editval_' . $inputType . ($button_only ? ' inactive' : ' active') . ' hideobject">' . (!empty($editvalue) ? $editvalue : $value) . '</span>' . "\n";
680 } else {
681 $out = $value;
682 }
683
684 return $out;
685 }
686
705 public function textwithtooltip($text, $htmltext, $tooltipon = 1, $direction = 0, $img = '', $extracss = '', $notabs = 3, $incbefore = '', $noencodehtmltext = 0, $tooltiptrigger = '', $forcenowrap = 0)
706 {
707 if ($incbefore) {
708 $text = $incbefore . $text;
709 }
710 if (!$htmltext) {
711 return $text;
712 }
713 $direction = (int) $direction; // For backward compatibility when $direction was set to '' instead of 0
714
715 $tag = 'td';
716 if ($notabs == 2) {
717 $tag = 'div';
718 }
719 if ($notabs == 3) {
720 $tag = 'span';
721 }
722 // Sanitize tooltip
723 $htmltext = str_replace(array("\r", "\n"), '', $htmltext);
724
725 $extrastyle = '';
726 if ($direction < 0) {
727 $extracss = ($extracss ? $extracss : '') . ($notabs != 3 ? ' inline-block' : '');
728 $extrastyle = 'padding: 0px; padding-left: 2px;';
729 }
730 if ($direction > 0) {
731 $extracss = ($extracss ? $extracss : '') . ($notabs != 3 ? ' inline-block' : '');
732 $extrastyle = 'padding: 0px; padding-right: 2px;';
733 }
734
735 $classfortooltip = 'classfortooltip';
736
737 $s = '';
738 $textfordialog = '';
739
740 if ($tooltiptrigger == '') {
741 $htmltext = str_replace('"', '&quot;', $htmltext);
742 } else {
743 $classfortooltip = 'classfortooltiponclick';
744 $textfordialog .= '<div style="display: none;" id="idfortooltiponclick_' . $tooltiptrigger . '" class="classfortooltiponclicktext"';
745 // Set default title of dialog
746 global $langs;
747 if ($langs instanceof Translate) {
748 $textfordialog .= ' title="'.$langs->trans("Note").'"';
749 }
750 $textfordialog .= '>' . $htmltext . '</div>';
751 }
752 if ($tooltipon == 2 || $tooltipon == 3) {
753 $paramfortooltipimg = ' class="' . $classfortooltip . ($notabs != 3 ? ' inline-block' : '') . ($extracss ? ' ' . $extracss : '') . '" style="padding: 0px;' . ($extrastyle ? ' ' . $extrastyle : '') . '"';
754 if ($tooltiptrigger == '') {
755 $paramfortooltipimg .= ' title="' . ($noencodehtmltext ? $htmltext : dol_escape_htmltag($htmltext, 1, 0, 'span', 0, 1)) . '"'; // Attribute to put on img tag to store tooltip
756 } else {
757 $paramfortooltipimg .= ' dolid="' . $tooltiptrigger . '"';
758 }
759 } else {
760 $paramfortooltipimg = ($extracss ? ' class="' . $extracss . '"' : '') . ($extrastyle ? ' style="' . $extrastyle . '"' : ''); // Attribute to put on td text tag
761 }
762 if ($tooltipon == 1 || $tooltipon == 3) {
763 $paramfortooltiptd = ' class="' . ($tooltipon == 3 ? 'cursorpointer ' : '') . $classfortooltip . ($tag != 'td' ? ' inline-block' : '') . ($extracss ? ' ' . $extracss : '') . '" style="padding: 0px;' . ($extrastyle ? ' ' . $extrastyle : '') . '" ';
764 if ($tooltiptrigger == '') {
765 $paramfortooltiptd .= ' title="' . ($noencodehtmltext ? $htmltext : dol_escape_htmltag($htmltext, 1, 0, 'span', 0, 1)) . '"'; // Attribute to put on td tag to store tooltip
766 } else {
767 $paramfortooltiptd .= ' dolid="' . $tooltiptrigger . '"';
768 }
769 } else {
770 $paramfortooltiptd = ($extracss ? ' class="' . $extracss . '"' : '') . ($extrastyle ? ' style="' . $extrastyle . '"' : ''); // Attribute to put on td text tag
771 }
772 if (empty($notabs)) {
773 $s .= '<table class="nobordernopadding"><tr style="height: auto;">';
774 } elseif ($notabs == 2) {
775 $s .= '<div class="inline-block' . ($forcenowrap ? ' nowrap' : '') . '">';
776 }
777 // Define value if value is before
778 if ($direction < 0) {
779 $s .= '<' . $tag . $paramfortooltipimg;
780 if ($tag == 'td') {
781 $s .= ' class="valigntop" width="14"';
782 }
783 $s .= '>' . $textfordialog . $img . '</' . $tag . '>';
784 }
785 // Use another method to help avoid having a space in value in order to use this value with jquery
786 // Define label
787 if ((string) $text != '') {
788 $s .= '<' . $tag . $paramfortooltiptd . '>' . $text . '</' . $tag . '>';
789 }
790 // Define value if value is after
791 if ($direction > 0) {
792 $s .= '<' . $tag . $paramfortooltipimg;
793 if ($tag == 'td') {
794 $s .= ' class="valignmiddle" width="14"';
795 }
796 $s .= '>' . $textfordialog . $img . '</' . $tag . '>';
797 }
798 if (empty($notabs)) {
799 $s .= '</tr></table>';
800 } elseif ($notabs == 2) {
801 $s .= '</div>';
802 }
803
804 return $s;
805 }
806
821 public function textwithpicto($text, $htmltooltip, $direction = 1, $type = 'help', $extracss = 'valignmiddle', $noencodehtmltext = 0, $notabs = 3, $tooltiptrigger = '', $forcenowrap = 0)
822 {
823 global $conf, $langs;
824
825 //For backwards compatibility
826 if ($type == '0') {
827 $type = 'info';
828 } elseif ($type == '1') {
829 $type = 'help';
830 }
831 // Clean parameters
832 $tooltiptrigger = preg_replace('/[^a-z0-9]/i', '', $tooltiptrigger);
833
834 if (preg_match('/onsmartphone$/', $tooltiptrigger) && empty($conf->dol_no_mouse_hover)) {
835 $tooltiptrigger = preg_replace('/^.*onsmartphone$/', '', $tooltiptrigger);
836 }
837 $alt = '';
838 if ($tooltiptrigger) {
839 $alt = $langs->transnoentitiesnoconv("ClickToShowHelp");
840 }
841
842 // If info or help with no javascript, show only text
843 if (empty($conf->use_javascript_ajax)) {
844 if ($type == 'info' || $type == 'infoclickable' || $type == 'help' || $type == 'helpclickable') {
845 return $text;
846 } else {
847 $alt = $htmltooltip;
848 $htmltooltip = '';
849 }
850 }
851
852 // If info or help with smartphone, show only text (tooltip hover can't works)
853 if (!empty($conf->dol_no_mouse_hover) && empty($tooltiptrigger)) {
854 if ($type == 'info' || $type == 'infoclickable' || $type == 'help' || $type == 'helpclickable') {
855 return $text;
856 }
857 }
858 // If info or help with smartphone, show only text (tooltip on click does not works with dialog on smaprtphone)
859 //if (!empty($conf->dol_no_mouse_hover) && !empty($tooltiptrigger))
860 //{
861 //if ($type == 'info' || $type == 'help') return '<a href="'..'">'.$text.'</a>';
862 //}
863
864 $img = '';
865 if ($type == 'info') {
866 $img = img_help(($tooltiptrigger != '' ? 2 : 0), $alt);
867 } elseif ($type == 'help') {
868 $img = img_help(($tooltiptrigger != '' ? 2 : 1), $alt);
869 } elseif ($type == 'helpclickable') {
870 $img = img_help(($tooltiptrigger != '' ? 2 : 1), $alt);
871 } elseif ($type == 'warning') {
872 $img = img_warning($alt);
873 } elseif ($type != 'none') {
874 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
875 $img = img_picto($alt, $type); // $type can be an image path
876 }
877
878 $tooltipon = ((($tooltiptrigger && !$img) || strpos($type, 'clickable')) ? 3 : 2);
879
880 return $this->textwithtooltip($text, $htmltooltip, $tooltipon, $direction, $img, $extracss, $notabs, '', $noencodehtmltext, $tooltiptrigger, $forcenowrap);
881 }
882
893 public function selectMassAction($selected, $arrayofaction, $alwaysvisible = 0, $name = 'massaction', $cssclass = 'checkforselect')
894 {
895 global $conf, $langs, $hookmanager;
896
897 $disabled = 0;
898 $ret = '<div class="centpercent center">';
899 $ret .= '<select class="flat' . (empty($conf->use_javascript_ajax) ? '' : ' hideobject') . ' ' . $name . ' ' . $name . 'select valignmiddle alignstart" id="' . $name . '" name="' . $name . '"' . ($disabled ? ' disabled="disabled"' : '') . '>';
900
901 // 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.
902 $parameters = array();
903 $reshook = $hookmanager->executeHooks('addMoreMassActions', $parameters); // Note that $action and $object may have been modified by hook
904 // check if there is a mass action
905
906 if (is_array($arrayofaction) && count($arrayofaction) == 0 && empty($hookmanager->resPrint)) {
907 return;
908 }
909 if (empty($reshook)) {
910 $ret .= '<option value="0"' . ($disabled ? ' disabled="disabled"' : '') . '>-- ' . $langs->trans("SelectAction") . ' --</option>';
911 if (is_array($arrayofaction)) {
912 foreach ($arrayofaction as $code => $label) {
913 $ret .= '<option value="' . $code . '"' . ($disabled ? ' disabled="disabled"' : '') . ' data-html="' . dol_escape_htmltag($label) . '">' . $label . '</option>';
914 }
915 }
916 }
917 $ret .= $hookmanager->resPrint;
918
919 $ret .= '</select>';
920
921 if (empty($conf->dol_optimize_smallscreen)) {
922 $ret .= ajax_combobox('.' . $name . 'select');
923 }
924
925 // 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
926 $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.
927 $ret .= '<input type="submit" disabled name="confirmmassaction"' . (empty($conf->use_javascript_ajax) ? '' : ' style="display: none"') . ' class="reposition button smallpaddingimp' . (empty($conf->use_javascript_ajax) ? '' : ' hideobject') . ' ' . $name . ' ' . $name . 'confirmed" value="' . dol_escape_htmltag($langs->trans("Confirm")) . '">';
928 $ret .= '</div>';
929
930 if (!empty($conf->use_javascript_ajax)) {
931 $ret .= '<!-- JS CODE TO ENABLE mass action select -->
932 <script nonce="' . getNonce() . '">
933 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 check for select boxes */
934 atleastoneselected=0;
935 jQuery("."+cssclass).each(function( index ) {
936 /* console.log( index + ": " + $( this ).text() ); */
937 if ($(this).is(\':checked\')) atleastoneselected++;
938 });
939
940 console.log("initCheckForSelect mode="+mode+" name="+name+" cssclass="+cssclass+" atleastoneselected="+atleastoneselected);
941
942 if (atleastoneselected || ' . ((int) $alwaysvisible) . ') {
943 jQuery("."+name).show();
944 ' . ($selected ? 'if (atleastoneselected) { jQuery("."+name+"select").val("' . $selected . '").trigger(\'change\'); jQuery("."+name+"confirmed").prop(\'disabled\', false); }' : '') . '
945 ' . ($selected ? 'if (! atleastoneselected) { jQuery("."+name+"select").val("0").trigger(\'change\'); jQuery("."+name+"confirmed").prop(\'disabled\', true); } ' : '') . '
946 } else {
947 jQuery("."+name).hide();
948 jQuery("."+name+"other").hide();
949 }
950 }
951
952 jQuery(document).ready(function () {
953 initCheckForSelect(0, "' . $name . '", "' . $cssclass . '");
954 jQuery(".' . $cssclass . '").change(function() {
955 console.log("A change was done on .' . $cssclass . '");
956 initCheckForSelect(1, "' . $name . '", "' . $cssclass . '");
957 });
958 jQuery(".' . $name . 'select").change(function() {
959 var massaction = $( this ).val();
960 var urlform = $( this ).closest("form").attr("action").replace("#show_files","");
961 if (massaction == "builddoc") {
962 urlform = urlform + "#show_files";
963 }
964 $( this ).closest("form").attr("action", urlform);
965 console.log("we select a mass action name=' . $name . ' massaction="+massaction+" - "+urlform);
966 /* Warning: if you set submit button to disabled, post using Enter will no more work if there is no other button */
967 if ($(this).val() != \'0\') {
968 jQuery(".' . $name . 'confirmed").prop(\'disabled\', false);
969 jQuery(".' . $name . 'other").hide(); /* To disable if another div was open */
970 jQuery(".' . $name . '"+massaction).show();
971 } else {
972 jQuery(".' . $name . 'confirmed").prop(\'disabled\', true);
973 jQuery(".' . $name . 'other").hide(); /* To disable any div open */
974 }
975 });
976 });
977 </script>
978 ';
979 }
980
981 return $ret;
982 }
983
984 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
985
1003 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, $forcecombo = 0)
1004 {
1005 // phpcs:enable
1006 global $langs, $mysoc;
1007
1008 $langs->load("dict");
1009
1010 $selected = (string) $selected;
1011
1012 $out = '';
1014 $countryArray = array();
1015 $favorite = array();
1016 $label = array();
1017 $atleastonefavorite = 0;
1018
1019 $sql = "SELECT rowid, code as code_iso, code_iso as code_iso3, label, favorite, eec";
1020 $sql .= " FROM " . $this->db->prefix() . "c_country";
1021 $sql .= " WHERE active > 0";
1022 //$sql.= " ORDER BY code ASC";
1023
1024 dol_syslog(get_class($this) . "::select_country", LOG_DEBUG);
1025
1026 $resql = $this->db->query($sql);
1027 if ($resql) {
1028 $out .= '<select id="select' . $htmlname . '" class="flat maxwidth200onsmartphone selectcountry' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" ' . $htmloption . '>';
1029 $num = $this->db->num_rows($resql);
1030 $i = 0;
1031 if ($num) {
1032 while ($i < $num) {
1033 $obj = $this->db->fetch_object($resql);
1034
1035 $countryArray[$i]
1036 = array(
1037 'rowid' => (int) $obj->rowid,
1038 'code_iso' => (string) $obj->code_iso,
1039 'code_iso3' => (string) $obj->code_iso3,
1040 'label' => (string) ($obj->code_iso && $langs->transnoentitiesnoconv("Country" . $obj->code_iso) != "Country" . $obj->code_iso ? $langs->transnoentitiesnoconv("Country" . $obj->code_iso) : ($obj->label != '-' ? $obj->label : '')),
1041 'favorite' => (string) $obj->favorite,
1042 'eec' => (string) $obj->eec,
1043 );
1044 $favorite[$i] = $obj->favorite;
1045 $label[$i] = dol_string_unaccent($countryArray[$i]['label']);
1046 $i++;
1047 }
1048
1049 if (empty($disablefavorites)) {
1050 $array1_sort_order = SORT_DESC;
1051 $array2_sort_order = SORT_ASC;
1052 array_multisort($favorite, $array1_sort_order, $label, $array2_sort_order, $countryArray);
1053 } else {
1054 $countryArray = dol_sort_array($countryArray, 'label');
1055 }
1056
1057 if ($showempty) {
1058 if (is_numeric($showempty)) {
1059 $out .= '<option value="">&nbsp;</option>' . "\n";
1060 } else {
1061 $out .= '<option value="-1">' . $langs->trans($showempty) . '</option>' . "\n";
1062 }
1063 }
1064
1065 if ($addspecialentries) { // Add dedicated entries for groups of countries
1066 //if ($showempty) $out.= '<option value="" disabled class="selectoptiondisabledwhite">--------------</option>';
1067 $out .= '<option value="special_allnotme"' . ($selected == 'special_allnotme' ? ' selected' : '') . '>' . $langs->trans("CountriesExceptMe", $langs->transnoentitiesnoconv("Country" . $mysoc->country_code)) . '</option>';
1068 $out .= '<option value="special_eec"' . ($selected == 'special_eec' ? ' selected' : '') . '>' . $langs->trans("CountriesInEEC") . '</option>';
1069 if ($mysoc->isInEEC()) {
1070 $out .= '<option value="special_eecnotme"' . ($selected == 'special_eecnotme' ? ' selected' : '') . '>' . $langs->trans("CountriesInEECExceptMe", $langs->transnoentitiesnoconv("Country" . $mysoc->country_code)) . '</option>';
1071 }
1072 $out .= '<option value="special_noteec"' . ($selected == 'special_noteec' ? ' selected' : '') . '>' . $langs->trans("CountriesNotInEEC") . '</option>';
1073 $out .= '<option value="" disabled class="selectoptiondisabledwhite">------------</option>';
1074 }
1075
1076 foreach ($countryArray as $row) {
1077 //if (empty($showempty) && empty($row['rowid'])) continue;
1078 if (empty($row['rowid'])) {
1079 continue;
1080 }
1081 if (is_array($exclude_country_code) && count($exclude_country_code) && in_array($row['code_iso'], $exclude_country_code)) {
1082 continue; // exclude some countries
1083 }
1084
1085 if (empty($disablefavorites) && $row['favorite'] && $row['code_iso']) {
1086 $atleastonefavorite++;
1087 }
1088 if (empty($row['favorite']) && $atleastonefavorite) {
1089 $atleastonefavorite = 0;
1090 $out .= '<option value="" disabled class="selectoptiondisabledwhite">------------</option>';
1091 }
1092
1093 $labeltoshow = '';
1094 if ($row['label']) {
1095 $labeltoshow .= dol_trunc($row['label'], $maxlength, 'middle');
1096 } else {
1097 $labeltoshow .= '&nbsp;';
1098 }
1099 if ($row['code_iso']) {
1100 $labeltoshow .= ' <span class="opacitymedium">(' . $row['code_iso'] . ')</span>';
1101 if (empty($hideflags)) {
1102 $tmpflag = picto_from_langcode($row['code_iso'], 'class="saturatemedium paddingrightonly"', 1);
1103 $labeltoshow = $tmpflag . ' ' . $labeltoshow;
1104 }
1105 }
1106
1107 if ($selected && $selected != '-1' && ($selected == $row['rowid'] || $selected == $row['code_iso'] || $selected == $row['code_iso3'] || $selected == $row['label'])) {
1108 $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']) . '">';
1109 } else {
1110 $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']) . '">';
1111 }
1112 $out .= dol_string_nohtmltag($labeltoshow);
1113 $out .= '</option>' . "\n";
1114 }
1115 }
1116 $out .= '</select>';
1117 } else {
1118 dol_print_error($this->db);
1119 }
1120
1121 // Make select dynamic
1122 if (empty($forcecombo)) {
1123 include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
1124 $out .= ajax_combobox('select' . $htmlname, array(), 0, 0, 'resolve');
1125 }
1126
1127 return $out;
1128 }
1129
1140 public function selectPhoneCode($selected = '', $htmlname = 'phone_code', $morecss = 'maxwidth150', $showempty = 0, $country_id_hint = 0)
1141 {
1142 global $langs;
1143
1144 $langs->load("dict");
1145
1146 $out = '';
1147 $codeArray = array();
1148 $favorite = array();
1149 $label = array();
1150 $atleastonefavorite = 0;
1151
1152 $sql = "SELECT rowid, code, label, phone_code, favorite, trunk_prefix";
1153 $sql .= " FROM ".$this->db->prefix()."c_country";
1154 // phone_code is an integer column, comparing it to an empty string fails on PostgreSQL
1155 $sql .= " WHERE active > 0 AND phone_code IS NOT NULL AND phone_code != 0";
1156
1157 dol_syslog(get_class($this)."::selectPhoneCode", LOG_DEBUG);
1158 $resql = $this->db->query($sql);
1159 if ($resql) {
1160 $num = $this->db->num_rows($resql);
1161 $i = 0;
1162 while ($i < $num) {
1163 $obj = $this->db->fetch_object($resql);
1164
1165 $translabel = ($obj->code && $langs->transnoentitiesnoconv("Country".$obj->code) != "Country".$obj->code) ? $langs->transnoentitiesnoconv("Country".$obj->code) : $obj->label;
1166
1167 $codeArray[$i]['rowid'] = $obj->rowid;
1168 $codeArray[$i]['code'] = $obj->code;
1169 $codeArray[$i]['label'] = $translabel;
1170 $codeArray[$i]['phone_code'] = '+'.$obj->phone_code;
1171 $codeArray[$i]['favorite'] = $obj->favorite;
1172 $codeArray[$i]['trunk_prefix'] = $obj->trunk_prefix;
1173 $favorite[$i] = $obj->favorite;
1174 $label[$i] = dol_string_unaccent($translabel);
1175 $i++;
1176 }
1177
1178 $array1_sort_order = SORT_DESC;
1179 $array2_sort_order = SORT_ASC;
1180 array_multisort($favorite, $array1_sort_order, $label, $array2_sort_order, $codeArray);
1181
1182 $out .= '<select id="select'.$htmlname.'" class="flat selectphonecode'.($morecss ? ' '.$morecss : '').'" name="'.$htmlname.'">';
1183
1184 if ($showempty) {
1185 $out .= '<option value="">&nbsp;</option>'."\n";
1186 }
1187
1188 // Determine which row index to select: prefer country_id_hint match, fallback to first phone_code match
1189 $selectedIdx = -1;
1190 $firstMatchIdx = -1;
1191 if ($selected !== '') {
1192 foreach ($codeArray as $idx => $row) {
1193 if ($row['phone_code'] == $selected) {
1194 if ($firstMatchIdx < 0) {
1195 $firstMatchIdx = $idx;
1196 }
1197 if ($country_id_hint > 0 && $row['rowid'] == $country_id_hint) {
1198 $selectedIdx = $idx;
1199 break;
1200 }
1201 }
1202 }
1203 if ($selectedIdx < 0 && $firstMatchIdx >= 0) {
1204 $selectedIdx = $firstMatchIdx;
1205 }
1206 }
1207
1208 foreach ($codeArray as $idx => $row) {
1209 if (empty($row['code'])) {
1210 continue;
1211 }
1212
1213 if ($row['favorite']) {
1214 $atleastonefavorite++;
1215 }
1216 if (empty($row['favorite']) && $atleastonefavorite) {
1217 $atleastonefavorite = 0;
1218 $out .= '<option value="" disabled class="selectoptiondisabledwhite">------------</option>';
1219 }
1220
1221 $tmpflag = picto_from_langcode($row['code'], 'class="saturatemedium paddingrightonly"', 1);
1222
1223 // Short label for selected display: flag + country code
1224 $selectlabel = ($tmpflag ? $tmpflag.' ' : '').$row['code'];
1225
1226 // Detailed label for dropdown list: flag + country name + phone code
1227 $labeltoshow = ($tmpflag ? $tmpflag.' ' : '').$row['label'].' '.$row['phone_code'];
1228
1229 $out .= '<option value="'.dol_escape_htmltag($row['phone_code']).'"';
1230 if ($idx === $selectedIdx) {
1231 $out .= ' selected';
1232 }
1233 $out .= ' data-html="'.dol_escape_htmltag($labeltoshow).'"';
1234 $out .= ' data-select-html="'.dol_escape_htmltag($selectlabel).'"';
1235 $out .= ' data-country-id="'.((int) $row['rowid']).'"';
1236 $out .= ' data-trunk-prefix="'.dol_escape_htmltag((string) $row['trunk_prefix']).'"';
1237 $out .= '>';
1238 $out .= dol_string_nohtmltag($labeltoshow);
1239 $out .= '</option>'."\n";
1240 }
1241 $out .= '</select>';
1242 } else {
1243 dol_print_error($this->db);
1244 }
1245
1246 // Make select dynamic
1247 include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
1248 $out .= ajax_combobox('select'.$htmlname, array(), 0, 0, 'resolve');
1249
1250 return $out;
1251 }
1252
1269 public function showPhoneInput($phoneValue, $htmlname, $country_id_hint = 0, $picto = 'object_phoning', $morecss = 'maxwidth150', $maxlength = 0, $countrySelectorId = 'selectcountry_id')
1270 {
1271 global $mysoc;
1272
1273 include_once DOL_DOCUMENT_ROOT.'/core/lib/phone.lib.php';
1274
1275 $codename = $htmlname.'_code';
1276
1277 // Fallback country_id: use caller hint, else main company country
1278 if (empty($country_id_hint) && !empty($mysoc->country_id)) {
1279 $country_id_hint = $mysoc->country_id;
1280 }
1281
1282 // On POST re-display, read the hidden field (which contains the full phone string)
1283 if (GETPOSTISSET($htmlname)) {
1284 $fullPhone = (string) GETPOST($htmlname);
1285 } else {
1286 $fullPhone = (string) $phoneValue;
1287 }
1288
1289 // Split into code + number
1290 $parsed = dol_parse_phone($fullPhone);
1291
1292 // Resolve default phone code: parsed code if set, else from country hint
1293 $phonecode = !empty($parsed['code']) ? $parsed['code'] : dol_get_phone_code_from_country($this->db, $country_id_hint);
1294
1295 $selectedCode = $phonecode;
1296 $numberValue = $parsed['number'];
1297
1298 // Remove country code
1299 if (strpos($numberValue, $selectedCode) === 0) {
1300 $numberValue = str_replace($selectedCode, '', $numberValue);
1301 }
1302
1303 // Add back trunk prefix for display (e.g. "644986885" → "0644986885" for France)
1304 if ($numberValue !== '' && $selectedCode !== '') {
1305 $trunkPrefix = dol_get_trunk_prefix($this->db, $selectedCode);
1306 if ($trunkPrefix !== '' && strpos($numberValue, $trunkPrefix) !== 0) {
1307 $numberValue = $trunkPrefix.$numberValue;
1308 }
1309 }
1310
1311 // Build output: hidden field (POSTed value)
1312 $out = '<input type="hidden" name="'.dol_escape_htmltag($htmlname).'" id="'.dol_escape_htmltag($htmlname).'" value="'.dol_escape_htmltag($fullPhone).'">';
1313
1314 // Picto
1315 $out .= img_picto('', $picto, 'class="pictofixedwidth"');
1316
1317 // Phone code select (display-only name, not submitted as separate POST param)
1318 $out .= $this->selectPhoneCode($selectedCode, $codename, 'maxwidth75 phone_code_select', 0, $country_id_hint);
1319
1320 // Visible number input (no name — not POSTed)
1321 $out .= '<input type="tel" inputmode="numeric" pattern="[0-9]*" id="'.dol_escape_htmltag($htmlname).'_input" class="'.dol_escape_htmltag($morecss).'"';
1322 if ($maxlength > 0) {
1323 $out .= ' maxlength="'.$maxlength.'"';
1324 }
1325 $out .= ' value="'.dol_escape_htmltag($numberValue).'">';
1326
1327 // Per-field JS to sync hidden field
1328 $out .= $this->getPhoneInputFieldJs($htmlname, $codename);
1329
1330 // Shared JS for country-sync (output once per page)
1331 $out .= $this->getPhoneInputSharedJs($countrySelectorId);
1332
1333 return $out;
1334 }
1335
1346 private function getPhoneInputFieldJs($htmlname, $codename)
1347 {
1348 $hiddenId = dol_escape_js($htmlname); // TODO: Not the correct method
1349 $inputId = dol_escape_js($htmlname).'_input'; // Correctly quoted with '' below @phan-suppress-current-line FunctionMissingSingleQuoteWrapping
1350 $selectId = 'select'.dol_escape_js($codename); // Correctly quoted with '' below @phan-suppress-current-line FunctionMissingSingleQuoteWrapping
1351
1352 $out = "\n".'<script type="text/javascript">'."\n";
1353 $out .= 'jQuery(document).ready(function() {'."\n";
1354 $out .= ' function syncPhoneField_'.$hiddenId.'() {'."\n";
1355 $out .= ' var selectEl = jQuery(\'#'.$selectId.'\');'."\n";
1356 $out .= ' var code = selectEl.val() || "";'."\n";
1357 $out .= ' var number = (jQuery(\'#'.$inputId.'\').val() || "").replace(/[^0-9]/g, "");'."\n";
1358 $out .= ' if (code && number) {'."\n";
1359 $out .= ' var selOpt = selectEl[0] && selectEl[0].selectedOptions && selectEl[0].selectedOptions[0];'."\n";
1360 $out .= ' var trunkPrefix = selOpt ? (selOpt.getAttribute("data-trunk-prefix") || "") : "";'."\n";
1361 $out .= ' if (trunkPrefix !== "" && number.indexOf(trunkPrefix) === 0) {'."\n";
1362 $out .= ' number = number.substring(trunkPrefix.length);'."\n";
1363 $out .= ' }'."\n";
1364 $out .= ' jQuery("#'.$hiddenId.'").val(code + " " + number);'."\n";
1365 $out .= ' } else if (number) {'."\n";
1366 $out .= ' jQuery("#'.$hiddenId.'").val(number);'."\n";
1367 $out .= ' } else {'."\n";
1368 $out .= ' jQuery("#'.$hiddenId.'").val("");'."\n";
1369 $out .= ' }'."\n";
1370 $out .= ' }'."\n";
1371 $out .= ' jQuery(\'#'.$selectId.'\').on("change", function() { syncPhoneField_'.$hiddenId.'(); });'."\n";
1372 $out .= ' jQuery(\'#'.$inputId.'\').on("input change", function() { syncPhoneField_'.$hiddenId.'(); });'."\n";
1373 $out .= '});'."\n";
1374 $out .= '</script>'."\n";
1375
1376 return $out;
1377 }
1378
1388 private function getPhoneInputSharedJs($countrySelectorId)
1389 {
1390 if ($this->phoneInputSharedJsLoaded) {
1391 return '';
1392 }
1393 $this->phoneInputSharedJsLoaded = true;
1394
1395 $out = "\n".'<script type="text/javascript">'."\n";
1396 $out .= 'jQuery(document).ready(function() {'."\n";
1397 $out .= ' jQuery(\'#'.dol_escape_js($countrySelectorId).'\').on("change", function() {'."\n";
1398 $out .= ' var country_id = jQuery(this).val();'."\n";
1399 $out .= ' if (country_id) {'."\n";
1400 $out .= ' jQuery.getJSON("'.DOL_URL_ROOT.'/core/ajax/getphonecode.php", {country_id: country_id, token: "'.currentToken().'"}, function(data) {'."\n";
1401 $out .= ' if (data.phone_code) {'."\n";
1402 $out .= ' jQuery(".phone_code_select").each(function() {'."\n";
1403 $out .= ' jQuery(this).val(data.phone_code).trigger("change");'."\n";
1404 $out .= ' });'."\n";
1405 $out .= ' }'."\n";
1406 $out .= ' });'."\n";
1407 $out .= ' }'."\n";
1408 $out .= ' });'."\n";
1409 $out .= '});'."\n";
1410 $out .= '</script>'."\n";
1411
1412 return $out;
1413 }
1414
1428 private function makeAddLinkToObject($object, $key, $possiblelink, $num, $resqllist)
1429 {
1430 dol_syslog(__METHOD__, LOG_DEBUG);
1431 global $langs, $form;
1432 if (empty($form)) {
1433 $form = new Form($this->db);
1434 }
1435 $htmltoenteralink = '';
1436 $i = 0;
1437
1438 // headers
1439 $htmltoenteralink .= '<tr class="liste_titre">';
1440 $htmltoenteralink .= '<td class="nowrap"></td>';
1441 $htmltoenteralink .= '<td>' . $langs->trans("Ref") . '</td>';
1442 $htmltoenteralink .= '<td>' . $langs->trans("RefCustomer") . '</td>';
1443 $htmltoenteralink .= '<td class="right">' . $langs->trans("AmountHTShort") . '</td>';
1444 $htmltoenteralink .= '<td>' . $langs->trans("Company") . '</td>';
1445 $htmltoenteralink .= '</tr>';
1446
1447 // rows with data
1448 while ($i < $num) {
1449 $objp = $this->db->fetch_object($resqllist);
1450 $alreadylinked = false;
1451 if (!empty($object->linkedObjectsIds[$possiblelink['linkname'] ?? $key])) {
1452 if (in_array($objp->rowid, array_values($object->linkedObjectsIds[$possiblelink['linkname'] ?? $key]))) {
1453 $alreadylinked = true;
1454 }
1455 }
1456 $htmltoenteralink .= '<tr class="oddeven">';
1457 $htmltoenteralink .= '<td>';
1458 if ($alreadylinked) {
1459 $htmltoenteralink .= img_picto('', 'link');
1460 } else {
1461 $htmltoenteralink .= '<input type="checkbox" name="idtolinkto[' . $key . '_' . $objp->rowid . ']" id="' . $key . '_' . $objp->rowid . '" value="' . $objp->rowid . '">';
1462 }
1463 $htmltoenteralink .= '</td>';
1464 $htmltoenteralink .= '<td>';
1465 if (!$alreadylinked) {
1466 $htmltoenteralink .= '<label for="' . $key . '_' . $objp->rowid . '">';
1467 }
1468 $htmltoenteralink .= $objp->ref;
1469 if (!$alreadylinked) {
1470 $htmltoenteralink .= '</label>';
1471 }
1472 $htmltoenteralink .= '</td>';
1473 $htmltoenteralink .= '<td>' . (!empty($objp->ref_client) ? $objp->ref_client : (!empty($objp->ref_supplier) ? $objp->ref_supplier : '')) . '</td>';
1474 $htmltoenteralink .= '<td class="right">';
1475 if ($possiblelink['label'] == 'LinkToContract') {
1476 $htmltoenteralink .= $form->textwithpicto('', $langs->trans("InformationOnLinkToContract")) . ' ';
1477 }
1478 $htmltoenteralink .= '<span class="amount">' . (isset($objp->total_ht) ? price($objp->total_ht) : '') . '</span>';
1479 $htmltoenteralink .= '</td>';
1480 $htmltoenteralink .= '<td>' . $objp->name . '</td>';
1481 $htmltoenteralink .= '</tr>';
1482 $i++;
1483 }
1484
1485 return $htmltoenteralink;
1486 }
1487
1502 private function makeAddLinkToAttendee($object, $key, $possiblelink, $num, $resqllist)
1503 {
1504 dol_syslog(__METHOD__, LOG_DEBUG);
1505 global $langs, $form;
1506 require_once DOL_DOCUMENT_ROOT . '/eventorganization/class/conferenceorboothattendee.class.php';
1507 require_once DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php';
1508 require_once DOL_DOCUMENT_ROOT . '/projet/class/project.class.php';
1509 $attendeestatic = new ConferenceOrBoothAttendee($this->db);
1510 $companystatic = new Societe($this->db);
1511 $projectstatic = new Project($this->db);
1512 if (empty($form)) {
1513 $form = new Form($this->db);
1514 }
1515 $htmltoenteralink = '';
1516 $i = 0;
1517
1518 // headers
1519 $htmltoenteralink .= '<tr class="liste_titre">';
1520 $htmltoenteralink .= '<td class="nowrap"></td>';
1521 $htmltoenteralink .= '<td>' . $langs->trans("Ref") . '</td>';
1522 $htmltoenteralink .= '<td>' . $langs->trans("Name") . '</td>';
1523 $htmltoenteralink .= '<td>' . $langs->trans("Email") . '</td>';
1524 $htmltoenteralink .= '<td>' . $langs->trans("Company") . '</td>';
1525 $htmltoenteralink .= '<td>' . $langs->trans("Project") . '</td>';
1526 $htmltoenteralink .= '<td>' . $langs->trans("DateOfRegistration") . '</td>';
1527 $htmltoenteralink .= '</tr>';
1528
1529 // rows with data
1530 while ($i < $num) {
1531 $objp = $this->db->fetch_object($resqllist);
1532 $alreadylinked = false;
1533 if (!empty($object->linkedObjectsIds[$possiblelink['linkname'] ?? $key])) {
1534 if (in_array($objp->rowid, array_values($object->linkedObjectsIds[$possiblelink['linkname'] ?? $key]))) {
1535 $alreadylinked = true;
1536 }
1537 }
1538 $htmltoenteralink .= '<tr class="oddeven">';
1539 $htmltoenteralink .= '<td>';
1540 if ($alreadylinked) {
1541 $htmltoenteralink .= img_picto('', 'link');
1542 } else {
1543 $htmltoenteralink .= '<input type="checkbox" name="idtolinkto[' . $key . '_' . $objp->rowid . ']" id="' . $key . '_' . $objp->rowid . '" value="' . $objp->rowid . '">';
1544 }
1545 $htmltoenteralink .= '</td>';
1546 $fetchattendee = $attendeestatic->fetch($objp->rowid);
1547 if ($fetchattendee) {
1548 $htmltoenteralink .= '<td>' . $attendeestatic->getNomUrl(0). '</td>';
1549 } else {
1550 $htmltoenteralink .= '<td><label for="' . $key . '_' . $objp->rowid . '">' . $objp->ref . '</label></td>';
1551 }
1552 $htmltoenteralink .= '<td>' . $objp->name . '</td>';
1553 $htmltoenteralink .= '<td>' . $objp->email . '</td>';
1554 $fetchcompany = $companystatic->fetch($objp->socid);
1555 if ($fetchcompany) {
1556 $htmltoenteralink .= '<td>' . $companystatic->getNomUrl(0). '</td>';
1557 } else {
1558 $htmltoenteralink .= '<td>' . $objp->name . '</td>';
1559 }
1560 $fetchcproject = $projectstatic->fetch($objp->fk_project);
1561 if ($fetchcproject) {
1562 $htmltoenteralink .= '<td>' . $projectstatic->getNomUrl(0). '</td>';
1563 } else {
1564 $htmltoenteralink .= '<td>' . $objp->fk_project . '</td>';
1565 }
1566 $htmltoenteralink .= '<td>' . $objp->date_subscription . '</td>';
1567 $htmltoenteralink .= '</tr>';
1568 $i++;
1569 }
1570
1571 return $htmltoenteralink;
1572 }
1573
1574 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1575
1589 public function select_incoterms($selected = '', $location_incoterms = '', $page = '', $htmlname = 'incoterm_id', $htmloption = '', $forcecombo = 1, $events = array(), $disableautocomplete = 0)
1590 {
1591 // phpcs:enable
1592 global $conf, $langs;
1593
1594 $langs->load("dict");
1595
1596 $out = '';
1597 //$moreattrib = '';
1598 $incotermArray = array();
1599
1600 $sql = "SELECT rowid, code";
1601 $sql .= " FROM " . $this->db->prefix() . "c_incoterms";
1602 $sql .= " WHERE active > 0";
1603 $sql .= " ORDER BY code ASC";
1604
1605 dol_syslog(get_class($this) . "::select_incoterm", LOG_DEBUG);
1606 $resql = $this->db->query($sql);
1607 if ($resql) {
1608 if ($conf->use_javascript_ajax && !$forcecombo) {
1609 include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
1610 $out .= ajax_combobox($htmlname, $events);
1611 }
1612
1613 if (!empty($page)) {
1614 $out .= '<form method="post" action="' . $page . '">';
1615 $out .= '<input type="hidden" name="action" value="set_incoterms">';
1616 $out .= '<input type="hidden" name="token" value="' . newToken() . '">';
1617 }
1618
1619 $out .= '<select id="' . $htmlname . '" class="flat selectincoterm width75" name="' . $htmlname . '" ' . $htmloption . '>';
1620 $out .= '<option value="0">&nbsp;</option>';
1621 $num = $this->db->num_rows($resql);
1622 $i = 0;
1623 if ($num) {
1624 while ($i < $num) {
1625 $obj = $this->db->fetch_object($resql);
1626 $incotermArray[$i]['rowid'] = $obj->rowid;
1627 $incotermArray[$i]['code'] = $obj->code;
1628 $i++;
1629 }
1630
1631 foreach ($incotermArray as $row) {
1632 if ($selected && ($selected == $row['rowid'] || $selected == $row['code'])) {
1633 $out .= '<option value="' . $row['rowid'] . '" selected>';
1634 } else {
1635 $out .= '<option value="' . $row['rowid'] . '">';
1636 }
1637
1638 if ($row['code']) {
1639 $out .= $row['code'];
1640 }
1641
1642 $out .= '</option>';
1643 }
1644 }
1645 $out .= '</select>';
1646 $out .= ajax_combobox($htmlname);
1647
1648 if ($conf->use_javascript_ajax && empty($disableautocomplete)) {
1649 $out .= ajax_multiautocompleter('location_incoterms', array(), DOL_URL_ROOT . '/core/ajax/locationincoterms.php') . "\n";
1650 //$moreattrib .= ' autocomplete="off"';
1651 }
1652 $out .= '<input id="location_incoterms" class="maxwidthonsmartphone heightofcombo" type="text" name="location_incoterms" value="' . $location_incoterms . '">' . "\n";
1653
1654 if (!empty($page)) {
1655 $out .= '<input type="submit" class="button valignmiddle smallpaddingimp nomargintop nomarginbottom" value="' . $langs->trans("Modify") . '"></form>';
1656 }
1657 } else {
1658 dol_print_error($this->db);
1659 }
1660
1661 return $out;
1662 }
1663
1664 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1665
1679 public function select_type_of_lines($selected = '', $htmlname = 'type', $showempty = 0, $hidetext = 0, $forceall = 0, $morecss = "", $useajaxcombo = 1)
1680 {
1681 // phpcs:enable
1682 global $langs;
1683
1684 // If product & services are enabled or both disabled.
1685 if ($forceall == 1 || (empty($forceall) && isModEnabled("product") && isModEnabled("service"))
1686 || (empty($forceall) && !isModEnabled('product') && !isModEnabled('service'))) {
1687 if (empty($hidetext)) {
1688 print $langs->trans("Type").'...';
1689 }
1690
1691 print '<select class="flat'.($morecss ? ' '.$morecss : '').'" id="select_' . $htmlname . '" name="' . $htmlname . '">';
1692 if ($showempty) {
1693 print '<option value="-1" class="opacitymedium"'.($useajaxcombo ? '' : ' disabled="disabled"');
1694 if ($selected == -1) {
1695 print ' selected';
1696 }
1697 print '>';
1698 if (is_numeric($showempty)) {
1699 print '&nbsp;';
1700 } else {
1701 print $showempty;
1702 }
1703 print '</option>';
1704 }
1705
1706 print '<option value="0"';
1707 if (0 == $selected || ($selected == -1 && getDolGlobalString('MAIN_FREE_PRODUCT_CHECKED_BY_DEFAULT') == 'product')) {
1708 print ' selected';
1709 }
1710 print '>' . $langs->trans("Product");
1711 print '</option>';
1712
1713 print '<option value="1"';
1714 if (1 == $selected || ($selected == -1 && getDolGlobalString('MAIN_FREE_PRODUCT_CHECKED_BY_DEFAULT') == 'service')) {
1715 print ' selected';
1716 }
1717 print '>' . $langs->trans("Service");
1718 print '</option>';
1719
1720 print '</select>';
1721
1722 if ($useajaxcombo) {
1723 print ajax_combobox('select_' . $htmlname);
1724 }
1725 //if ($user->admin) print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"),1);
1726 }
1727 if ((empty($forceall) && !isModEnabled('product') && isModEnabled("service")) || $forceall == 3) {
1728 print $langs->trans("Service");
1729 print '<input type="hidden" name="' . $htmlname . '" value="1">';
1730 }
1731 if ((empty($forceall) && isModEnabled("product") && !isModEnabled('service')) || $forceall == 2) {
1732 print $langs->trans("Product");
1733 print '<input type="hidden" name="' . $htmlname . '" value="0">';
1734 }
1735 if ($forceall < 0) { // This should happened only for contracts when both predefined product and service are disabled.
1736 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
1737 }
1738 }
1739
1740 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1741
1747 public function load_cache_types_fees()
1748 {
1749 // phpcs:enable
1750 global $langs;
1751
1752 $num = count($this->cache_types_fees);
1753 if ($num > 0) {
1754 return 0; // Cache already loaded
1755 }
1756
1757 dol_syslog(__METHOD__, LOG_DEBUG);
1758
1759 $langs->load("trips");
1760
1761 $sql = "SELECT c.code, c.label";
1762 $sql .= " FROM " . $this->db->prefix() . "c_type_fees as c";
1763 $sql .= " WHERE active > 0";
1764
1765 $resql = $this->db->query($sql);
1766 if ($resql) {
1767 $num = $this->db->num_rows($resql);
1768 $i = 0;
1769
1770 while ($i < $num) {
1771 $obj = $this->db->fetch_object($resql);
1772
1773 // If a translation exists, we use is, otherwise, we take the label by default
1774 $label = ($obj->code != $langs->trans($obj->code) ? $langs->trans($obj->code) : $langs->trans($obj->label));
1775 $this->cache_types_fees[$obj->code] = $label;
1776 $i++;
1777 }
1778
1779 asort($this->cache_types_fees);
1780
1781 return $num;
1782 } else {
1783 dol_print_error($this->db);
1784 return -1;
1785 }
1786 }
1787
1788 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1789
1801 public function select_type_fees($selected = '', $htmlname = 'type', $showempty = 0, $morecss = '', $nooutput = 0, $noadmin = 0)
1802 {
1803 // phpcs:enable
1804 global $user, $langs;
1805
1806 dol_syslog(__METHOD__ . " selected=" . $selected . ", htmlname=" . $htmlname, LOG_DEBUG);
1807
1808 $this->load_cache_types_fees();
1809
1810 $out = '';
1811
1812 $out .= '<select id="select_' . $htmlname . '" class="flat'.($morecss ? ' '.$morecss : '').'" name="' . $htmlname . '">';
1813 if ($showempty) {
1814 $out .= '<option value="-1"';
1815 if ($selected === -1) {
1816 $out .= ' selected';
1817 }
1818 $out .= '>';
1819 if (!is_numeric($showempty)) {
1820 $out .= $langs->trans($showempty);
1821 }
1822 $out .= '</option>';
1823 }
1824
1825 foreach ($this->cache_types_fees as $key => $value) {
1826 $out .= '<option value="' . $key . '"';
1827 if ($key == (string) $selected) {
1828 $out .= ' selected';
1829 }
1830 $out .= '>';
1831 $out .= $value;
1832 $out .= '</option>';
1833 }
1834
1835 $out .= '</select>';
1836 if ($user->admin && empty($noadmin)) {
1837 $out .= info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
1838 }
1839
1840 $out .= ajax_combobox('select_'.$htmlname);
1841
1842 if (empty($nooutput)) {
1843 print $out;
1844 return '';
1845 }
1846
1847 return $out;
1848 }
1849
1850
1851 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1852
1875 public function select_company($selected = '', $htmlname = 'socid', $filter = '', $showempty = '', $showtype = 0, $forcecombo = 0, $events = array(), $limit = 0, $morecss = 'minwidth100', $moreparam = '', $selected_input_value = '', $hidelabel = 1, $ajaxoptions = array(), $multiple = false, $excludeids = array(), $showcode = 0)
1876 {
1877 // phpcs:enable
1878 global $conf, $langs;
1879
1880 $out = '';
1881
1882 if (!empty($conf->use_javascript_ajax) && getDolGlobalString('COMPANY_USE_SEARCH_TO_SELECT') && !$forcecombo) {
1883 if (is_null($ajaxoptions)) {
1884 $ajaxoptions = array();
1885 }
1886
1887 require_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
1888
1889 // No immediate load of all database
1890 $placeholder = '';
1891 if ($selected && empty($selected_input_value)) {
1892 require_once DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php';
1893 $societetmp = new Societe($this->db);
1894 $societetmp->fetch($selected);
1895 $selected_input_value = $societetmp->name;
1896 unset($societetmp);
1897 }
1898
1899 // mode 1
1900 $urloption = 'htmlname=' . urlencode((string) (str_replace('.', '_', $htmlname))) . '&outjson=1&filter=' . urlencode((string) ($filter)) . (empty($excludeids) ? '' : '&excludeids=' . implode(',', $excludeids)) . ($showtype ? '&showtype=' . urlencode((string) ($showtype)) : '') . ($showcode ? '&showcode=' . urlencode((string) ($showcode)) : '') . ($limit ? '&limit='.$limit : '');
1901
1902 $out .= '<!-- force css to be higher than dialog popup --><style type="text/css">.ui-autocomplete { z-index: 1010; }</style>';
1903 if (empty($hidelabel)) {
1904 $out .= $langs->trans("RefOrLabel") . ' : ';
1905 } elseif ($hidelabel == 1 && !is_numeric($showempty)) {
1906 $placeholder = $langs->trans($showempty);
1907 } elseif ($hidelabel > 1) {
1908 $placeholder = $langs->trans("RefOrLabel");
1909 if ($hidelabel == 2) {
1910 $out .= img_picto($langs->trans("Search"), 'search');
1911 }
1912 }
1913 $out .= '<input type="text" class="' . $morecss . '" name="search_' . $htmlname . '" id="search_' . $htmlname . '" value="' . $selected_input_value . '"' . ($placeholder ? ' placeholder="' . dol_escape_htmltag($placeholder) . '"' : '') . ' ' . (getDolGlobalString('THIRDPARTY_SEARCH_AUTOFOCUS') ? 'autofocus' : '') . ' spellcheck="false" />';
1914 if ($hidelabel == 3) {
1915 $out .= img_picto($langs->trans("Search"), 'search');
1916 }
1917
1918 $out .= ajax_event($htmlname, $events);
1919
1920 $out .= ajax_autocompleter($selected, $htmlname, DOL_URL_ROOT.'/societe/ajax/company.php', $urloption, getDolGlobalInt('COMPANY_USE_SEARCH_TO_SELECT'), 0, $ajaxoptions);
1921 } else {
1922 // Immediate load of all database
1923 $out .= $this->select_thirdparty_list($selected, $htmlname, $filter, $showempty, $showtype, $forcecombo, $events, '', 0, $limit, $morecss, $moreparam, $multiple, $excludeids, $showcode);
1924 }
1925
1926 return $out;
1927 }
1928
1929
1930 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1931
1958 public function select_contact($socid, $selected = '', $htmlname = 'contactid', $showempty = 0, $exclude = '', $limitto = '', $showfunction = 0, $morecss = '', $nokeyifsocid = true, $showsoc = 0, $forcecombo = 0, $events = array(), $moreparam = '', $htmlid = '', $selected_input_value = '', $filter = '', $multiple = false)
1959 {
1960 // phpcs:enable
1961
1962 global $conf, $langs;
1963
1964 $out = '';
1965
1966 if (empty($htmlid)) {
1967 $htmlid = $htmlname;
1968 }
1969
1970 $sav = getDolGlobalString('CONTACT_USE_SEARCH_TO_SELECT');
1971 if ($nokeyifsocid && $socid > 0) {
1972 $conf->global->CONTACT_USE_SEARCH_TO_SELECT = 0;
1973 }
1974
1975 if (!empty($conf->use_javascript_ajax) && getDolGlobalString('CONTACT_USE_SEARCH_TO_SELECT') && !$forcecombo) {
1976 require_once DOL_DOCUMENT_ROOT . '/contact/class/contact.class.php';
1977 require_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
1978
1979 // select2 combo pre-filled with only the currently selected contact(s) as <option selected>.
1980 // select2 keeps those and fetches the rest on demand from contact/ajax/contact.php instead of
1981 // loading the whole contact list into the page.
1982 $selectedids = array();
1983 foreach (($multiple ? (is_array($selected) ? $selected : array()) : ($selected !== '' && $selected !== 0 ? array($selected) : array())) as $tmpid) {
1984 if (is_numeric($tmpid) && (int) $tmpid > 0) {
1985 $selectedids[] = (int) $tmpid;
1986 }
1987 }
1988
1989 // A non-numeric $showempty ('&nbsp;', a label, ...) is used as a select2 placeholder instead of
1990 // an <option> value; select2 needs an empty first <option> in single mode to be able to show it.
1991 $placeholder = is_numeric($showempty) ? '' : (string) $showempty;
1992
1993 $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" id="' . $htmlid . '" name="' . $htmlname . ($multiple ? '[]' : '') . '"' . ($multiple ? ' multiple' : '') . ($moreparam ? ' ' . $moreparam : '') . '>';
1994 if (!$multiple) {
1995 $out .= '<option></option>';
1996 }
1997 if (count($selectedids)) {
1998 $contacttmp = new Contact($this->db);
1999 foreach ($selectedids as $tmpid) {
2000 if ($contacttmp->fetch($tmpid) > 0) {
2001 $out .= '<option value="' . $tmpid . '" selected>' . dol_escape_htmltag($contacttmp->getFullName($langs)) . '</option>';
2002 }
2003 }
2004 unset($contacttmp);
2005 }
2006 $out .= '</select>';
2007
2008 $out .= ajax_event($htmlname, $events);
2009
2010 // Same as the value of 'htmlname=' below: the endpoint reads the typed term from a GET param
2011 // named after this transformed htmlname (see contact/ajax/contact.php).
2012 $htmlnamefortermparam = str_replace('.', '_', $htmlname);
2013 $urloption = 'htmlname=' . urlencode((string) $htmlnamefortermparam) . '&outjson=1&filter=' . urlencode((string) $filter)
2014 . (empty($exclude) ? '' : '&exclude=' . urlencode($exclude))
2015 . ($showsoc ? '&showsoc=' . urlencode((string) $showsoc) : '')
2016 . ($socid > 0 ? '&socid=' . ((int) $socid) : '');
2017
2018 // A non-numeric value ('infinite') means "infinite list": no minimum number of chars, the list
2019 // opens as soon as the field gets the focus (select2 queries the endpoint with an empty term).
2020 $minlengthforajax = getDolGlobalInt('CONTACT_USE_SEARCH_TO_SELECT');
2021 if ($minlengthforajax < 1) {
2022 $minlengthforajax = 0;
2023 }
2024 // Page size of the ajax endpoint (CONTACT_LIMIT_SIZE): select2 keeps asking for the next page
2025 // while the endpoint returns a full page, so the whole list stays browsable by scrolling.
2026 $ajaxpagesize = getDolGlobalInt('CONTACT_LIMIT_SIZE', 20);
2027
2028 $htmlidjs = str_replace('.', '\\\\.', $htmlid);
2029 $out .= '<script nonce="' . getNonce() . '">jQuery(function() {
2030 jQuery("#' . $htmlidjs . '").select2({
2031 theme: "default",
2032 language: (typeof select2arrayoflanguage === "undefined") ? "en" : select2arrayoflanguage,
2033 containerCssClass: ":all:",
2034 placeholder: ' . json_encode($placeholder) . ',
2035 minimumInputLength: ' . ((int) $minlengthforajax) . ',
2036 ajax: {
2037 url: "' . DOL_URL_ROOT . '/contact/ajax/contact.php?' . $urloption . '",
2038 dataType: "json",
2039 delay: 250,
2040 data: function(params) {
2041 var d = {};
2042 d[' . json_encode($htmlnamefortermparam) . '] = params.term;
2043 d.page = params.page || 1;
2044 return d;
2045 },
2046 processResults: function(data) {
2047 var result = [];
2048 jQuery.each(data, function(i, val) {
2049 result.push({ id: val.key, text: val.value });
2050 });
2051 return { results: result, pagination: { more: data.length >= ' . ((int) $ajaxpagesize) . ' } };
2052 },
2053 cache: true
2054 }
2055 });' . (getDolGlobalString('CONTACT_SEARCH_AUTOFOCUS') ? '
2056 jQuery("#' . $htmlidjs . '").select2("open");' : '') . '
2057 });</script>';
2058 $out .= '<!-- force css to be higher than dialog popup --><style type="text/css">.select2-container { z-index: 1010; }</style>';
2059 } else {
2060 // Immediate load of all database
2061 $disableifempty = 0;
2062 $options_only = 0;
2063 $limitto = '';
2064
2065 $out .= $this->selectcontacts($socid, $selected, $htmlname, $showempty, $exclude, $limitto, $showfunction, $morecss, $options_only, $showsoc, $forcecombo, $events, $moreparam, $htmlid, $multiple, $disableifempty, $filter);
2066 }
2067
2068 $conf->global->CONTACT_USE_SEARCH_TO_SELECT = $sav;
2069
2070 return $out;
2071 }
2072
2073
2074 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2075
2099 public function select_thirdparty_list($selected = '', $htmlname = 'socid', $filter = '', $showempty = '', $showtype = 0, $forcecombo = 0, $events = array(), $filterkey = '', $outputmode = 0, $limit = 0, $morecss = 'minwidth100', $moreparam = '', $multiple = false, $excludeids = array(), $showcode = 0)
2100 {
2101 // phpcs:enable
2102 global $user, $langs;
2103 global $hookmanager;
2104
2105 $langs->loadLangs(array("companies", "suppliers"));
2106
2107 $out = '';
2108 $num = 0;
2109 $outarray = array();
2110
2111 if ($selected === '') {
2112 $selected = array();
2113 } elseif (!is_array($selected)) {
2114 $selected = array($selected);
2115 }
2116
2117 // Clean $filter that may contains sql conditions so sql code
2118 if (function_exists('testSqlAndScriptInject')) {
2119 if (testSqlAndScriptInject($filter, 3) > 0) {
2120 $filter = '';
2121 return 'SQLInjectionTryDetected';
2122 }
2123 }
2124
2125 if ($filter != '') { // If a filter was provided
2126 $errormsg = '';
2127 $filter = forgeSQLFromUniversalSearchCriteria($filter, $errormsg, 1);
2128
2129 // Redo clean $filter that may contains sql conditions so sql code
2130 if (function_exists('testSqlAndScriptInject')) {
2131 if (testSqlAndScriptInject($filter, 3) > 0) {
2132 $filter = '';
2133 return 'SQLInjectionTryDetected';
2134 }
2135 }
2136 }
2137
2138 // We search companies
2139 $sql = "SELECT s.rowid, s.nom as name, s.name_alias, s.tva_intra, s.client, s.fournisseur, s.code_client, s.code_fournisseur";
2140 if (getDolGlobalString('COMPANY_SHOW_ADDRESS_SELECTLIST')) {
2141 $sql .= ", s.address, s.zip, s.town";
2142 $sql .= ", dictp.code as country_code";
2143 }
2144 $sql .= " FROM " . $this->db->prefix() . "societe as s";
2145 if (getDolGlobalString('COMPANY_SHOW_ADDRESS_SELECTLIST')) {
2146 $sql .= " LEFT JOIN " . $this->db->prefix() . "c_country as dictp ON dictp.rowid = s.fk_pays";
2147 }
2148 if (!$user->hasRight('societe', 'client', 'voir')) {
2149 $sql .= ", " . $this->db->prefix() . "societe_commerciaux as sc";
2150 }
2151 $sql .= " WHERE s.entity IN (" . getEntity('societe') . ")";
2152 if (!empty($user->socid)) {
2153 $sql .= " AND s.rowid = " . ((int) $user->socid);
2154 }
2155 if ($filter) {
2156 // $filter is safe because, it has been tested by testSqlAndScriptInject() and sanitized by forgeSQLFromUniversalSearchCriteria()
2157 $sqlwhere = $filter; // @phan-suppress-current-line SqlInjection
2158 $sql .= " AND (" . $sqlwhere . ")";
2159 }
2160 if (!$user->hasRight('societe', 'client', 'voir')) {
2161 $sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = " . ((int) $user->id);
2162 }
2163 if (getDolGlobalString('COMPANY_HIDE_INACTIVE_IN_COMBOBOX')) {
2164 $sql .= " AND s.status <> 0";
2165 }
2166 if (!empty($excludeids)) {
2167 $sql .= " AND s.rowid NOT IN (" . $this->db->sanitize(implode(',', $excludeids)) . ")";
2168 }
2169 // Add where from hooks
2170 $parameters = array();
2171 $reshook = $hookmanager->executeHooks('selectThirdpartyListWhere', $parameters); // Note that $action and $object may have been modified by hook
2172 $sql .= $hookmanager->resPrint;
2173 // Add criteria
2174 if ($filterkey && $filterkey != '') {
2175 $sql .= " AND (";
2176 $prefix = !getDolGlobalString('COMPANY_DONOTSEARCH_ANYWHERE') ? '%' : ''; // Can use index if COMPANY_DONOTSEARCH_ANYWHERE is on
2177 // For natural search
2178 $search_crit = explode(' ', $filterkey);
2179 $i = 0;
2180 if (count($search_crit) > 1) {
2181 $sql .= "(";
2182 }
2183 foreach ($search_crit as $crit) {
2184 if ($i > 0) {
2185 $sql .= " AND ";
2186 }
2187 $sql .= "(s.nom LIKE '" . $this->db->escape($prefix . $crit) . "%')";
2188 $i++;
2189 }
2190 if (count($search_crit) > 1) {
2191 $sql .= ")";
2192 }
2193 if (isModEnabled('barcode')) {
2194 $sql .= " OR s.barcode LIKE '" . $this->db->escape($prefix . $filterkey) . "%'";
2195 }
2196 $sql .= " OR s.code_client LIKE '" . $this->db->escape($prefix . $filterkey) . "%' OR s.code_fournisseur LIKE '" . $this->db->escape($prefix . $filterkey) . "%'";
2197 $sql .= " OR s.name_alias LIKE '" . $this->db->escape($prefix . $filterkey) . "%' OR s.tva_intra LIKE '" . $this->db->escape($prefix . $filterkey) . "%'";
2198 $sql .= ")";
2199 }
2200 $sql .= $this->db->order("nom", "ASC");
2201 $sql .= $this->db->plimit($limit, 0);
2202
2203 // Build output string
2204 dol_syslog(get_class($this)."::select_thirdparty_list", LOG_DEBUG);
2205 $resql = $this->db->query($sql);
2206 if ($resql) {
2207 // Construct $out and $outarray
2208 $out .= '<select id="' . $htmlname . '" class="flat' . ($morecss ? ' ' . $morecss : '') . '"' . ($moreparam ? ' ' . $moreparam : '') . ' name="' . $htmlname . ($multiple ? '[]' : '') . '"' . ($multiple ? ' multiple' : '') . '>' . "\n";
2209
2210 $textifempty = (($showempty && !is_numeric($showempty)) ? $langs->trans($showempty) : '');
2211 if (getDolGlobalString('COMPANY_USE_SEARCH_TO_SELECT')) {
2212 // Do not use textifempty = ' ' or '&nbsp;' here, or search on key will search on ' key'.
2213 //if (!empty($conf->use_javascript_ajax) || $forcecombo) $textifempty='';
2214 if ($showempty && !is_numeric($showempty)) {
2215 $textifempty = $langs->trans($showempty);
2216 } else {
2217 $textifempty .= $langs->trans("All");
2218 }
2219 }
2220 if ($showempty) {
2221 $out .= '<option value="-1" data-html="' . dol_escape_htmltag('<span class="opacitymedium">' . ($textifempty ? $textifempty : '&nbsp;') . '</span>') . '">' . $textifempty . '</option>' . "\n";
2222 }
2223
2224 $companytemp = new Societe($this->db);
2225
2226 $num = $this->db->num_rows($resql);
2227 $i = 0;
2228 if ($num) {
2229 while ($i < $num) {
2230 $obj = $this->db->fetch_object($resql);
2231 $label = '';
2232 if ($showcode || getDolGlobalString('SOCIETE_ADD_REF_IN_LIST')) {
2233 if (($obj->client) && (!empty($obj->code_client))) {
2234 $label = $obj->code_client . ' - ';
2235 }
2236 if (($obj->fournisseur) && (!empty($obj->code_fournisseur))) {
2237 $label .= $obj->code_fournisseur . ' - ';
2238 }
2239 $label .= ' ' . $obj->name;
2240 } else {
2241 $label = $obj->name;
2242 }
2243
2244 if (!empty($obj->name_alias)) {
2245 $label .= ' (' . $obj->name_alias . ')';
2246 }
2247
2248 if (getDolGlobalString('SOCIETE_SHOW_VAT_IN_LIST') && !empty($obj->tva_intra)) {
2249 $label .= ' - '.$obj->tva_intra;
2250 }
2251
2252 $labelhtml = $label;
2253
2254 if ($showtype) {
2255 $companytemp->id = $obj->rowid;
2256 $companytemp->client = $obj->client;
2257 $companytemp->fournisseur = $obj->fournisseur;
2258 $tmptype = $companytemp->getTypeUrl(1, '', 0, 'span');
2259 if ($tmptype) {
2260 $labelhtml .= ' ' . $tmptype;
2261 }
2262
2263 if ($obj->client || $obj->fournisseur) {
2264 $label .= ' (';
2265 }
2266 if ($obj->client == 1 || $obj->client == 3) {
2267 $label .= $langs->trans("Customer");
2268 }
2269 if ($obj->client == 2 || $obj->client == 3) {
2270 $label .= ($obj->client == 3 ? ', ' : '') . $langs->trans("Prospect");
2271 }
2272 if ($obj->fournisseur) {
2273 $label .= ($obj->client ? ', ' : '') . $langs->trans("Supplier");
2274 }
2275 if ($obj->client || $obj->fournisseur) {
2276 $label .= ')';
2277 }
2278 }
2279
2280 if (getDolGlobalString('COMPANY_SHOW_ADDRESS_SELECTLIST')) {
2281 $s = ($obj->address ? ' - ' . $obj->address : '') . ($obj->zip ? ' - ' . $obj->zip : '') . ($obj->town ? ' ' . $obj->town : '');
2282 if (!empty($obj->country_code)) {
2283 $s .= ', ' . $langs->trans('Country' . $obj->country_code);
2284 }
2285 $label .= $s;
2286 $labelhtml .= $s;
2287 }
2288
2289 if (empty($outputmode)) {
2290 if (in_array($obj->rowid, $selected)) {
2291 $out .= '<option value="' . $obj->rowid . '" selected data-html="' . dol_escape_htmltag($labelhtml, 0, 0, '', 0, 1) . '">' . dol_escape_htmltag($label, 0, 0, '', 0, 1) . '</option>';
2292 } else {
2293 $out .= '<option value="' . $obj->rowid . '" data-html="' . dol_escape_htmltag($labelhtml, 0, 0, '', 0, 1) . '">' . dol_escape_htmltag($label, 0, 0, '', 0, 1) . '</option>';
2294 }
2295 } else {
2296 array_push($outarray, array('key' => $obj->rowid, 'value' => $label, 'label' => $label, 'labelhtml' => $labelhtml));
2297 }
2298
2299 $i++;
2300 if (($i % 10) == 0) {
2301 $out .= "\n";
2302 }
2303 }
2304 }
2305 $out .= '</select>' . "\n";
2306 if (!$forcecombo) {
2307 include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
2308 $out .= ajax_combobox($htmlname, $events, getDolGlobalInt("COMPANY_USE_SEARCH_TO_SELECT"));
2309 }
2310 } else {
2311 dol_print_error($this->db);
2312 }
2313
2314 $this->result = array('nbofthirdparties' => $num);
2315
2316 if ($outputmode) {
2317 return $outarray;
2318 }
2319 return $out;
2320 }
2321
2322
2350 public function selectcontacts($socid, $selected = array(), $htmlname = 'contactid', $showempty = 0, $exclude = '', $limitto = '', $showfunction = 0, $morecss = '', $options_only = 0, $showsoc = 0, $forcecombo = 0, $events = array(), $moreparam = '', $htmlid = '', $multiple = false, $disableifempty = 0, $filter = '', $limit = 0, $limitoffset = 0)
2351 {
2352 global $conf, $user, $langs, $hookmanager, $action;
2353
2354 $langs->load('companies');
2355
2356 if (empty($htmlid)) {
2357 $htmlid = $htmlname;
2358 }
2359 $num = 0;
2360 $out = '';
2361 $outarray = array();
2362
2363 if ($selected === '') {
2364 $selected = array();
2365 } elseif (!is_array($selected)) {
2366 $selected = array((int) $selected);
2367 }
2368
2369 // Clean $filter that may contains sql conditions so sql code
2370 if (function_exists('testSqlAndScriptInject')) {
2371 if (testSqlAndScriptInject($filter, 3) > 0) {
2372 $filter = '';
2373 return 'SQLInjectionTryDetected';
2374 }
2375 }
2376
2377 if ($filter != '') { // If a filter was provided
2378 if (preg_match('/[\‍(\‍)]/', $filter)) {
2379 // If there is one parenthesis inside the criteria, we assume it is an Universal Filter Syntax.
2380 $errormsg = '';
2381 $filter = forgeSQLFromUniversalSearchCriteria($filter, $errormsg, 1);
2382
2383 // Redo clean $filter that may contains sql conditions so sql code
2384 if (function_exists('testSqlAndScriptInject')) {
2385 if (testSqlAndScriptInject($filter, 3) > 0) {
2386 $filter = '';
2387 return 'SQLInjectionTryDetected';
2388 }
2389 }
2390 } else {
2391 // If not, we do nothing. We already know that there is no parenthesis
2392 // TODO Disallow this case in a future by returning an error here.
2393 dol_syslog("Warning, select_thirdparty_list was called with a filter criteria not using the Universal Search Filter Syntax.", LOG_WARNING);
2394 }
2395 }
2396
2397 if (!is_object($hookmanager)) {
2398 include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
2399 $hookmanager = new HookManager($this->db);
2400 }
2401
2402 // We search third parties
2403 $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";
2404 if ($showsoc > 0 || getDolGlobalString('CONTACT_SHOW_EMAIL_PHONE_TOWN_SELECTLIST')) {
2405 $sql .= ", s.nom as company, s.town AS company_town";
2406 }
2407 $sql .= " FROM " . $this->db->prefix() . "socpeople as sp";
2408 if ($showsoc > 0 || getDolGlobalString('CONTACT_SHOW_EMAIL_PHONE_TOWN_SELECTLIST')) {
2409 $sql .= " LEFT JOIN " . $this->db->prefix() . "societe as s ON s.rowid = sp.fk_soc";
2410 }
2411 $sql .= " WHERE sp.entity IN (" . getEntity('contact') . ")";
2412 $sql .= " AND ((sp.fk_user_creat = ".((int) $user->id)." AND sp.priv = 1) OR sp.priv = 0)"; // check if this is a private contact
2413 if ($socid > 0 || $socid == -1) {
2414 $sql .= " AND sp.fk_soc = " . ((int) $socid);
2415 }
2416 if (getDolGlobalString('CONTACT_HIDE_INACTIVE_IN_COMBOBOX')) {
2417 $sql .= " AND sp.statut <> 0";
2418 }
2419 // filter user access
2420 if (!$user->hasRight('societe', 'client', 'voir') && !$user->socid) {
2421 $sql .= " AND EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = sp.fk_soc AND sc.fk_user = ".(int) $user->id .")";
2422 }
2423 if ($user->socid > 0) {
2424 $sql .= " AND sp.fk_soc = ".((int) $user->socid);
2425 }
2426 if ($filter) {
2427 // $filter is safe because, if it contains '(' or ')', it has been sanitized by testSqlAndScriptInject() and forgeSQLFromUniversalSearchCriteria()
2428 // if not, by testSqlAndScriptInject() only.
2429 $sanitizedfilter = $filter; // @phan-suppress-current-line SqlInjection
2430 $sql .= " AND (" . $sanitizedfilter . ")";
2431 }
2432 // Add where from hooks
2433 $parameters = array();
2434 $reshook = $hookmanager->executeHooks('selectContactListWhere', $parameters); // Note that $action and $object may have been modified by hook
2435 $sql .= $hookmanager->resPrint;
2436 $sql .= " ORDER BY sp.lastname ASC";
2437 $sql .= $this->db->plimit($limit, ((int) $limitoffset > 0 ? (int) $limitoffset : 0));
2438
2439 dol_syslog(get_class($this) . "::selectcontacts", LOG_DEBUG);
2440 $resql = $this->db->query($sql);
2441 if ($resql) {
2442 $num = $this->db->num_rows($resql);
2443
2444 if ($htmlname != 'none' && !$options_only) {
2445 $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" id="' . $htmlid . '" name="' . $htmlname . ($multiple ? '[]' : '') . '" ' . (($num || empty($disableifempty)) ? '' : ' disabled') . ($multiple ? 'multiple' : '') . ' ' . (!empty($moreparam) ? $moreparam : '') . '>';
2446 }
2447
2448 if ($showempty && !is_numeric($showempty)) {
2449 $textforempty = $showempty;
2450 $out .= '<option class="optiongrey" value="-1"' . (in_array(-1, $selected) ? ' selected' : '') . '>' . dol_escape_htmltag($textforempty) . '</option>';
2451 } else {
2452 if (($showempty == 1 || ($showempty == 3 && $num > 1)) && !$multiple) {
2453 $out .= '<option value="0"' . (in_array(0, $selected) ? ' selected' : '') . '>&nbsp;</option>';
2454 }
2455 if ($showempty == 2) {
2456 $out .= '<option value="0"' . (in_array(0, $selected) ? ' selected' : '') . '>-- ' . $langs->trans("Internal") . ' --</option>';
2457 }
2458 }
2459
2460 $i = 0;
2461 if ($num) {
2462 include_once DOL_DOCUMENT_ROOT . '/contact/class/contact.class.php';
2463 $contactstatic = new Contact($this->db);
2464
2465 while ($i < $num) {
2466 $obj = $this->db->fetch_object($resql);
2467
2468 // Set email (or phones) and town extended infos
2469 $extendedInfos = '';
2470 if (getDolGlobalString('CONTACT_SHOW_EMAIL_PHONE_TOWN_SELECTLIST')) {
2471 $extendedInfos = array();
2472 $email = trim($obj->email);
2473 if (!empty($email)) {
2474 $extendedInfos[] = $email;
2475 } else {
2476 $phone = trim($obj->phone);
2477 $phone_perso = trim($obj->phone_perso);
2478 $phone_mobile = trim($obj->phone_mobile);
2479 if (!empty($phone)) {
2480 $extendedInfos[] = $phone;
2481 }
2482 if (!empty($phone_perso)) {
2483 $extendedInfos[] = $phone_perso;
2484 }
2485 if (!empty($phone_mobile)) {
2486 $extendedInfos[] = $phone_mobile;
2487 }
2488 }
2489 $contact_town = trim($obj->contact_town);
2490 $company_town = trim($obj->company_town);
2491 if (!empty($contact_town)) {
2492 $extendedInfos[] = $contact_town;
2493 } elseif (!empty($company_town)) {
2494 $extendedInfos[] = $company_town;
2495 }
2496 $extendedInfos = implode(' - ', $extendedInfos);
2497 if (!empty($extendedInfos)) {
2498 $extendedInfos = ' - ' . $extendedInfos;
2499 }
2500 }
2501
2502 $contactstatic->id = $obj->rowid;
2503 $contactstatic->lastname = $obj->lastname;
2504 $contactstatic->firstname = $obj->firstname;
2505 if ($obj->statut == 1) {
2506 $tmplabel = '';
2507 if ($htmlname != 'none') {
2508 $disabled = 0;
2509 if (is_array($exclude) && count($exclude) && in_array($obj->rowid, $exclude)) {
2510 $disabled = 1;
2511 }
2512 if (is_array($limitto) && count($limitto) && !in_array($obj->rowid, $limitto)) {
2513 $disabled = 1;
2514 }
2515 if (!empty($selected) && in_array($obj->rowid, $selected)) {
2516 $out .= '<option value="' . $obj->rowid . '"';
2517 if ($disabled) {
2518 $out .= ' disabled';
2519 }
2520 $out .= ' selected>';
2521
2522 $tmplabel = $contactstatic->getFullName($langs) . $extendedInfos;
2523 if ($showfunction && $obj->poste) {
2524 $tmplabel .= ' (' . $obj->poste . ')';
2525 }
2526 if (($showsoc > 0) && $obj->company) {
2527 $tmplabel .= ' - (' . $obj->company . ')';
2528 }
2529
2530 $out .= $tmplabel;
2531 $out .= '</option>';
2532 } else {
2533 $out .= '<option value="' . $obj->rowid . '"';
2534 if ($disabled) {
2535 $out .= ' disabled';
2536 }
2537 $out .= '>';
2538
2539 $tmplabel = $contactstatic->getFullName($langs) . $extendedInfos;
2540 if ($showfunction && $obj->poste) {
2541 $tmplabel .= ' (' . $obj->poste . ')';
2542 }
2543 if (($showsoc > 0) && $obj->company) {
2544 $tmplabel .= ' - (' . $obj->company . ')';
2545 }
2546
2547 $out .= $tmplabel;
2548 $out .= '</option>';
2549 }
2550 } else {
2551 if (in_array($obj->rowid, $selected)) {
2552 $tmplabel = $contactstatic->getFullName($langs) . $extendedInfos;
2553 if ($showfunction && $obj->poste) {
2554 $tmplabel .= ' (' . $obj->poste . ')';
2555 }
2556 if (($showsoc > 0) && $obj->company) {
2557 $tmplabel .= ' - (' . $obj->company . ')';
2558 }
2559
2560 $out .= $tmplabel;
2561 }
2562 }
2563
2564 if ($tmplabel != '') {
2565 array_push($outarray, array('key' => $obj->rowid, 'value' => $tmplabel, 'label' => $tmplabel, 'labelhtml' => $tmplabel));
2566 }
2567 }
2568 $i++;
2569 }
2570 } else {
2571 $labeltoshow = ($socid != -1) ? ($langs->trans($socid ? "NoContactDefinedForThirdParty" : "NoContactDefined")) : $langs->trans('SelectAThirdPartyFirst');
2572 $out .= '<option class="disabled" value="-1"' . (($showempty == 2 || $multiple) ? '' : ' selected') . ' disabled="disabled">';
2573 $out .= $labeltoshow;
2574 $out .= '</option>';
2575 }
2576
2577 $parameters = array(
2578 'socid' => $socid,
2579 'htmlname' => $htmlname,
2580 'resql' => $resql,
2581 'out' => &$out,
2582 'showfunction' => $showfunction,
2583 'showsoc' => $showsoc,
2584 );
2585
2586 $reshook = $hookmanager->executeHooks('afterSelectContactOptions', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2587
2588 if ($htmlname != 'none' && !$options_only) {
2589 $out .= '</select>';
2590 }
2591
2592 if ($conf->use_javascript_ajax && !$forcecombo && !$options_only) {
2593 include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
2594 $out .= ajax_combobox($htmlid, $events, getDolGlobalInt("CONTACT_USE_SEARCH_TO_SELECT"));
2595 }
2596
2597 $this->num = $num;
2598
2599 if ($options_only === 2) {
2600 // Return array of options
2601 return $outarray;
2602 } else {
2603 return $out;
2604 }
2605 } else {
2606 dol_print_error($this->db);
2607 return -1;
2608 }
2609 }
2610
2611
2612 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2613
2624 public function select_remises($selected, $htmlname, $filter, $socid, $maxvalue = 0)
2625 {
2626 // phpcs:enable
2627 global $langs, $conf;
2628
2629 $showsourceinvoice = getDolGlobalString('MAIN_SHOW_FACNUMBER_IN_DISCOUNT_LIST');
2630
2631 // Search for the discounts
2632 $sql = "SELECT re.rowid, re.amount_ht, re.amount_tva, re.amount_ttc,";
2633 $sql .= " re.description, re.fk_facture_source, re.fk_invoice_supplier_source";
2634 if ($showsourceinvoice) {
2635 // Resolve the source invoice (customer or supplier) in the main query instead of one fetch per line
2636 $sql .= ", f.ref as src_cust_ref, f.datef as src_cust_date";
2637 $sql .= ", ff.ref as src_supp_ref, ff.datef as src_supp_date";
2638 }
2639 $sql .= " FROM " . $this->db->prefix() . "societe_remise_except as re";
2640 if ($showsourceinvoice) {
2641 $sql .= " LEFT JOIN " . $this->db->prefix() . "facture as f ON f.rowid = re.fk_facture_source";
2642 $sql .= " LEFT JOIN " . $this->db->prefix() . "facture_fourn as ff ON ff.rowid = re.fk_invoice_supplier_source";
2643 }
2644 $sql .= " WHERE re.fk_soc = " . (int) $socid;
2645 $sql .= " AND re.entity = " . ((int) $conf->entity);
2646 if ($filter) {
2647 $sanitizedfilter = $filter; // @phan-suppress-current-line SqlInjection
2648 if ($showsourceinvoice) {
2649 // The joined tables also carry a fk_facture_source column: qualify unprefixed references of the caller filter
2650 $sanitizedfilter = preg_replace('/(?<![a-zA-Z0-9_.])fk_facture_source\b/', 're.fk_facture_source', $sanitizedfilter);
2651 }
2652 $sql .= " AND " . $sanitizedfilter;
2653 }
2654 if ($showsourceinvoice) {
2655 // When the source invoices are shown, their date is the natural order (oldest deposit first)
2656 $sql .= " ORDER BY COALESCE(ff.datef, f.datef, re.datec) ASC, re.rowid ASC";
2657 } else {
2658 $sql .= " ORDER BY re.description ASC";
2659 }
2660
2661 dol_syslog(get_class($this) . "::select_remises", LOG_DEBUG);
2662 $resql = $this->db->query($sql);
2663 if ($resql) {
2664 print '<select id="select_' . $htmlname . '" class="flat maxwidth300 maxwidth200onsmartphone" name="' . $htmlname . '">';
2665 $num = $this->db->num_rows($resql);
2666
2667 $qualifiedlines = $num;
2668
2669 $i = 0;
2670 if ($num) {
2671 print '<option value="0">&nbsp;</option>';
2672 while ($i < $num) {
2673 $obj = $this->db->fetch_object($resql);
2674 $desc = dol_trunc($obj->description, 40);
2675 if (preg_match('/\‍(CREDIT_NOTE\‍)/', $desc)) {
2676 $desc = preg_replace('/\‍(CREDIT_NOTE\‍)/', $langs->trans("CreditNote"), $desc);
2677 }
2678 if (preg_match('/\‍(DEPOSIT\‍)/', $desc)) {
2679 $desc = preg_replace('/\‍(DEPOSIT\‍)/', $langs->trans("Deposit"), $desc);
2680 }
2681 if (preg_match('/\‍(EXCESS RECEIVED\‍)/', $desc)) {
2682 $desc = preg_replace('/\‍(EXCESS RECEIVED\‍)/', $langs->trans("ExcessReceived"), $desc);
2683 }
2684 if (preg_match('/\‍(EXCESS PAID\‍)/', $desc)) {
2685 $desc = preg_replace('/\‍(EXCESS PAID\‍)/', $langs->trans("ExcessPaid"), $desc);
2686 }
2687
2688 $selectstring = '';
2689 if ($selected > 0 && $selected == $obj->rowid) {
2690 $selectstring = ' selected';
2691 }
2692
2693 $disabled = '';
2694 if ($maxvalue > 0 && $obj->amount_ttc > $maxvalue) {
2695 $qualifiedlines--;
2696 $disabled = ' disabled';
2697 }
2698
2699 if ($showsourceinvoice) {
2700 $srcref = !empty($obj->src_supp_ref) ? $obj->src_supp_ref : (!empty($obj->src_cust_ref) ? $obj->src_cust_ref : '');
2701 $srcdate = !empty($obj->src_supp_date) ? $obj->src_supp_date : (!empty($obj->src_cust_date) ? $obj->src_cust_date : '');
2702 if ($srcref) {
2703 $desc = $desc . ' - ' . $srcref;
2704 if ($srcdate) {
2705 $desc .= ' (' . dol_print_date($this->db->jdate($srcdate), 'day') . ')';
2706 }
2707 }
2708 }
2709
2710 print '<option value="' . $obj->rowid . '"' . $selectstring . $disabled . '>' . $desc . ' (' . price($obj->amount_ht) . ' ' . $langs->trans("HT") . ' - ' . price($obj->amount_ttc) . ' ' . $langs->trans("TTC") . ')</option>';
2711 $i++;
2712 }
2713 }
2714 print '</select>';
2715 print ajax_combobox('select_' . $htmlname);
2716
2717 return $qualifiedlines;
2718 } else {
2719 dol_print_error($this->db);
2720 return -1;
2721 }
2722 }
2723
2724
2725 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2726
2742 public function select_users($selected = '', $htmlname = 'userid', $show_empty = 0, $exclude = null, $disabled = 0, $include = '', $enableonly = array(), $force_entity = '0')
2743 {
2744 // phpcs:enable
2745 print $this->select_dolusers($selected, $htmlname, $show_empty, $exclude, $disabled, $include, $enableonly, $force_entity);
2746 }
2747
2759 public static $user_combo_allowed_morefilters = array(
2760 'u.statut:=:1', // active users only
2761 '(statut:=:1)', // active users only
2762 'employee:=:1', // employees only
2763 '(employee:=:1)', // employees only
2764 '(admin:=:1) AND (statut:=:1)', // active administrators only
2765 );
2766
2774 public static function isUserComboMorefilterAllowed($morefilter)
2775 {
2776 return in_array(trim((string) $morefilter), self::$user_combo_allowed_morefilters, true);
2777 }
2778
2794 private function buildSelectDolusersAjaxUrlOption($htmlname, $exclude, $include, $force_entity, $showstatus, $notdisabled, $maxlength, $morefilter = '')
2795 {
2796 $urloption = 'htmlname='.urlencode($htmlname).'&outjson=1';
2797 if (is_array($exclude) && count($exclude)) {
2798 $urloption .= '&exclude='.urlencode(implode(',', $exclude));
2799 }
2800 if (is_array($include) && count($include)) {
2801 $urloption .= '&include='.urlencode(implode(',', $include));
2802 } elseif (is_string($include) && $include !== '') {
2803 $urloption .= '&include='.urlencode($include);
2804 }
2805 if ($force_entity !== '') {
2806 $urloption .= '&force_entity='.urlencode((string) $force_entity);
2807 }
2808 if ($showstatus !== 0) {
2809 $urloption .= '&showstatus='.((int) $showstatus);
2810 }
2811 if (!empty($notdisabled)) {
2812 $urloption .= '&notdisabled=1';
2813 }
2814 if (!empty($maxlength)) {
2815 $urloption .= '&maxlength='.((int) $maxlength);
2816 }
2817 // $morefilter is a raw Universal Search Filter fed to forgeSQLFromUniversalSearchCriteria().
2818 // Only forward it when it is one of the known-safe expressions of self::$user_combo_allowed_morefilters
2819 // (the endpoint re-checks against the same list); any other value would let a client run arbitrary
2820 // WHERE clauses on llx_user and is dropped here - the ajax list is then simply not narrowed by it.
2821 if ((string) $morefilter !== '') {
2822 if (self::isUserComboMorefilterAllowed($morefilter)) {
2823 $urloption .= '&morefilter='.urlencode(trim((string) $morefilter));
2824 } else {
2825 dol_syslog(__METHOD__.": morefilter '".$morefilter."' is not in Form::\$user_combo_allowed_morefilters; the search-to-select user list will not be narrowed by it", LOG_WARNING);
2826 }
2827 }
2828 return $urloption;
2829 }
2830
2842 private function buildSelectDolusersAjaxSelect2Script($htmlname, $urloption, $minlengthforajax, $ajaxpagesize)
2843 {
2844 $htmlnamejs = str_replace('.', '\\\\.', $htmlname);
2845 return '<script nonce="'.getNonce().'">jQuery(function() {
2846 jQuery("#'.$htmlnamejs.'").select2({
2847 theme: "default",
2848 language: (typeof select2arrayoflanguage === "undefined") ? "en" : select2arrayoflanguage,
2849 containerCssClass: ":all:",
2850 placeholder: "",
2851 minimumInputLength: '.((int) $minlengthforajax).',
2852 ajax: {
2853 url: "'.DOL_URL_ROOT.'/user/ajax/users.php?'.$urloption.'",
2854 dataType: "json",
2855 delay: 250,
2856 data: function(params) {
2857 var d = {};
2858 d['.json_encode($htmlname).'] = params.term;
2859 d.page = params.page || 1;
2860 return d;
2861 },
2862 processResults: function(data) {
2863 var result = [];
2864 jQuery.each(data, function(i, val) {
2865 result.push({ id: val.key, text: val.value });
2866 });
2867 return { results: result, pagination: { more: data.length >= '.((int) $ajaxpagesize).' } };
2868 },
2869 cache: true
2870 }
2871 });
2872 });</script>';
2873 }
2874
2875 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2876
2904 public function select_dolusers($userselected = '', $htmlname = 'userid', $show_empty = 0, $exclude = null, $disabled = 0, $include = '', $enableonly = '', $force_entity = '', $maxlength = 0, $showstatus = 0, $morefilter = '', $showalso = 0, $enableonlytext = '', $morecss = '', $notdisabled = 0, $outputmode = 0, $multiple = false, $forcecombo = 0, $filterkey = '', $limit = 0, $limitoffset = 0)
2905 {
2906 // phpcs:enable
2907 global $conf, $user, $langs, $hookmanager;
2908 global $action;
2909
2910 // Convert $selected into an int (in case it is an object)
2911 if (is_object($userselected)) {
2912 $selected = (int) $userselected->id;
2913 } elseif (is_numeric($userselected)) {
2914 $selected = (int) $userselected;
2915 } elseif (is_array($userselected)) {
2916 $selected = $userselected;
2917 } else {
2918 $selected = -1;
2919 }
2920
2921 // If no preselected user defined, we take current user
2922 if ((is_numeric($selected) && ((int) $selected < -4 || empty($selected))) && !getDolGlobalString('SOCIETE_DISABLE_DEFAULT_SALESREPRESENTATIVE')) {
2923 $selected = $user->id;
2924 }
2925
2926 // Convert selected int into an array
2927 if (!is_array($selected)) {
2928 if ($selected === -1 || $selected === '') {
2929 $selected = array();
2930 } else {
2931 $selected = array($selected);
2932 }
2933 }
2934
2935 // Exclude some users in $excludeUsers string
2936 $excludeUsers = null;
2937 if (is_array($exclude)) {
2938 $excludeUsers = implode(",", $exclude);
2939 }
2940
2941 // Include some users in $includeUsers string
2942 $includeUsers = null;
2943 $includeUsersArray = array();
2944 if (is_array($include)) {
2945 $includeUsersArray = $include;
2946 } elseif ($include == 'hierarchy') {
2947 // Build list includeUsersArray to have only hierarchy
2948 $includeUsersArray = $user->getAllChildIds(0);
2949 } elseif ($include == 'hierarchyme') {
2950 // Build list includeUsersArray to have only hierarchy and current user
2951 $includeUsersArray = $user->getAllChildIds(1);
2952 }
2953 // Get list of allowed users
2954 /* We do not limit list of users. Because we should limit this only for combo list into HR features where we may be allowed to
2955 * see all other users and element in other. For example in agenda, we can have permission to read all event of otherusers.
2956 * So we disable this.
2957 if (!$user->hasRight('user', 'user', 'lire')) {
2958 if (empty($includeUsersArray)) {
2959 $includeUsers = implode(",", $user->getAllChildIds(1));
2960 } else {
2961 $includeUsers = implode(",", array_intersect($includeUsersArray, $user->getAllChildIds(1)));
2962 }
2963 } else {
2964 $includeUsers = implode(",", $includeUsersArray);
2965 } */
2966 $includeUsers = implode(",", $includeUsersArray);
2967
2968 $num = 0;
2969
2970 $out = '';
2971 $outarray = array();
2972 $outarray2 = array();
2973
2974 // If the ajax "search to select" mode is enabled for users (constant USER_USE_SEARCH_TO_SELECT), we output a
2975 // select2 combo calling user/ajax/users.php instead of loading the full list of users. This is limited to
2976 // the HTML output: array output, forced combo, pseudo-entries (showalso) and per-line enabling (enableonly)
2977 // keep the full-list behaviour. Both single and multiple select page through the same endpoint (see
2978 // buildSelectDolusersAjaxSelect2Script()); only the <select> markup (single option vs multiple/[]) differs.
2979 $canajaxsearchtoselect = !empty($conf->use_javascript_ajax) && getDolGlobalString('USER_USE_SEARCH_TO_SELECT') && !$forcecombo && empty($outputmode)
2980 && empty($showalso) && !(is_array($enableonly) && count($enableonly)) && $filterkey === '';
2981
2982 if ($canajaxsearchtoselect && !$multiple) {
2983 $preselected = 0;
2984 if (!empty($selected)) {
2985 $firstval = reset($selected);
2986 if (is_numeric($firstval) && (int) $firstval > 0) {
2987 $preselected = (int) $firstval;
2988 }
2989 }
2990
2991 $preselectedlabel = '';
2992 if ($preselected > 0) {
2993 $usertmpselect = new User($this->db);
2994 if ($usertmpselect->fetch($preselected) > 0) {
2995 $fullNameMode = getDolGlobalString('MAIN_FIRSTNAME_NAME_POSITION') ? 0 : 1;
2996 $preselectedlabel = $usertmpselect->getFullName($langs, $fullNameMode, -1, $maxlength);
2997 if ($preselectedlabel === '') {
2998 $preselectedlabel = $usertmpselect->login;
2999 }
3000 }
3001 unset($usertmpselect);
3002 }
3003
3004 $out .= '<select class="flat'.($morecss ? ' '.$morecss : ' minwidth200').'" id="'.$htmlname.'" name="'.$htmlname.'"'.($disabled ? ' disabled' : '').'>';
3005 if ($preselected > 0) {
3006 $out .= '<option value="'.$preselected.'" selected>'.dol_escape_htmltag($preselectedlabel).'</option>';
3007 }
3008 $out .= '</select>';
3009
3010 // Propagate the filters to the ajax endpoint so it returns the same subset as the full combo would
3011 $urloption = $this->buildSelectDolusersAjaxUrlOption($htmlname, $exclude, $include, $force_entity, $showstatus, $notdisabled, $maxlength, $morefilter);
3012
3013 // A non-numeric value ('infinite') means "infinite list": no minimum number of chars, the list
3014 // opens as soon as the field gets the focus (select2 queries the endpoint with an empty term).
3015 $minlengthforajax = getDolGlobalInt('USER_USE_SEARCH_TO_SELECT');
3016 if ($minlengthforajax < 1) {
3017 $minlengthforajax = 0;
3018 }
3019 // Page size of the ajax endpoint (USER_LIMIT_SIZE): select2 keeps asking for the next page
3020 // while the endpoint returns a full page, so the whole list stays browsable by scrolling.
3021 $ajaxpagesize = getDolGlobalInt('USER_LIMIT_SIZE', 20);
3022
3023 $out .= $this->buildSelectDolusersAjaxSelect2Script($htmlname, $urloption, $minlengthforajax, $ajaxpagesize);
3024 $out .= '<!-- force css to be higher than dialog popup --><style type="text/css">.select2-container { z-index: 1010; }</style>';
3025
3026 return $out;
3027 }
3028
3029 if ($canajaxsearchtoselect && $multiple) {
3030 // Multiple select + "search to select": render a select2 combo pre-filled with only the currently
3031 // selected users as <option selected>. select2 keeps those and fetches the rest on demand from
3032 // user/ajax/users.php instead of loading the whole llx_user table into the page.
3033 $preselectedids = array();
3034 foreach ($selected as $tmpid) {
3035 if (is_numeric($tmpid) && (int) $tmpid > 0) {
3036 $preselectedids[] = (int) $tmpid;
3037 }
3038 }
3039
3040 $fullNameMode = getDolGlobalString('MAIN_FIRSTNAME_NAME_POSITION') ? 0 : 1;
3041 $out .= '<select class="flat'.($morecss ? ' '.$morecss : ' minwidth200').'" id="'.$htmlname.'" name="'.$htmlname.'[]" multiple'.($disabled ? ' disabled' : '').'>';
3042 if (count($preselectedids)) {
3043 $usertmpselect = new User($this->db);
3044 foreach ($preselectedids as $tmpid) {
3045 if ($usertmpselect->fetch($tmpid) > 0) {
3046 $labeltoshow = $usertmpselect->getFullName($langs, $fullNameMode, -1, $maxlength);
3047 if ($labeltoshow === '') {
3048 $labeltoshow = $usertmpselect->login;
3049 }
3050 $out .= '<option value="'.$tmpid.'" selected>'.dol_escape_htmltag($labeltoshow).'</option>';
3051 }
3052 }
3053 unset($usertmpselect);
3054 }
3055 $out .= '</select>';
3056
3057 $urloption = $this->buildSelectDolusersAjaxUrlOption($htmlname, $exclude, $include, $force_entity, $showstatus, $notdisabled, $maxlength, $morefilter);
3058
3059 // A non-numeric value ('infinite') means "infinite list": no minimum number of chars, the list
3060 // opens as soon as the field gets the focus (select2 queries the endpoint with an empty term).
3061 $minlengthforajax = getDolGlobalInt('USER_USE_SEARCH_TO_SELECT');
3062 if ($minlengthforajax < 1) {
3063 $minlengthforajax = 0;
3064 }
3065 // Page size of the ajax endpoint (USER_LIMIT_SIZE): select2 keeps asking for the next page
3066 // while the endpoint returns a full page, so the whole list stays browsable by scrolling.
3067 $ajaxpagesize = getDolGlobalInt('USER_LIMIT_SIZE', 20);
3068
3069 $out .= $this->buildSelectDolusersAjaxSelect2Script($htmlname, $urloption, $minlengthforajax, $ajaxpagesize);
3070 $out .= '<!-- force css to be higher than dialog popup --><style type="text/css">.select2-container { z-index: 1010; }</style>';
3071
3072 return $out;
3073 }
3074
3075 // Do we want to show the label of entity into the combo list ?
3076 $showlabelofentity = isModEnabled('multicompany') && !getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE') && $conf->entity == 1 && !empty($user->admin) && empty($user->entity) && !preg_match('/^search_/', $htmlname);
3077 $userissuperadminentityone = isModEnabled('multicompany') && $conf->entity == 1 && $user->admin && empty($user->entity);
3078
3079 // Forge request to select users
3080 $sql = "SELECT DISTINCT u.rowid, u.lastname as lastname, u.firstname, u.statut as status, u.login, u.admin, u.entity, u.gender, u.photo";
3081 if ($showlabelofentity) {
3082 $sql .= ", e.label";
3083 }
3084 $sql .= " FROM " . $this->db->prefix() . "user as u";
3085 if ($showlabelofentity) {
3086 $sql .= " LEFT JOIN " . $this->db->prefix() . "entity as e ON e.rowid = u.entity";
3087 }
3088 // Condition here should be the same than into societe->getSalesRepresentatives().
3089 if ($userissuperadminentityone && $force_entity !== 'default') {
3090 if (!empty($force_entity)) {
3091 $sql .= " WHERE u.entity IN (0, " . $this->db->sanitize($force_entity) . ")";
3092 } else {
3093 $sql .= " WHERE u.entity IS NOT NULL";
3094 }
3095 } else {
3096 if (isModEnabled('multicompany') && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
3097 $sql .= " WHERE u.rowid IN (SELECT ug.fk_user FROM ".$this->db->prefix()."usergroup_user as ug WHERE ug.entity IN (".getEntity('usergroup')."))";
3098 } else {
3099 $sql .= " WHERE u.entity IN (" . getEntity('user') . ")";
3100 }
3101 }
3102
3103 if (!empty($user->socid)) {
3104 $sql .= " AND u.fk_soc = " . ((int) $user->socid);
3105 }
3106 if (is_array($exclude) && $excludeUsers) {
3107 $sql .= " AND u.rowid NOT IN (" . $this->db->sanitize($excludeUsers) . ")";
3108 }
3109 if ($includeUsers) {
3110 $sql .= " AND u.rowid IN (" . $this->db->sanitize($includeUsers) . ")";
3111 }
3112 if (getDolGlobalString('USER_HIDE_INACTIVE_IN_COMBOBOX') || $notdisabled) {
3113 $sql .= " AND (u.statut <> 0";
3114 if (!empty($selected)) {
3115 $sql .= " OR u.rowid IN (".$this->db->sanitize(implode(',', $selected)).")"; // We must always keep the selected users to avoid to loose it/them when updating
3116 }
3117 $sql .= ")";
3118 }
3119 if (getDolGlobalString('USER_HIDE_NONEMPLOYEE_IN_COMBOBOX')) {
3120 $sql .= " AND u.employee <> 0";
3121 }
3122 if (getDolGlobalString('USER_HIDE_EXTERNAL_IN_COMBOBOX')) {
3123 $sql .= " AND u.fk_soc IS NULL";
3124 }
3125 if (!empty($morefilter)) {
3126 $errormessage = '';
3127 $sql .= forgeSQLFromUniversalSearchCriteria($morefilter, $errormessage);
3128 if ($errormessage) {
3129 $this->errors[] = $errormessage;
3130 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
3131 if ($outputmode == 0) {
3132 return 'Error bad param $morefilter';
3133 } else {
3134 return array();
3135 }
3136 }
3137 }
3138
3139 //Add hook to filter on user (for example on usergroup define in custom modules)
3140 $reshook = $hookmanager->executeHooks('addSQLWhereFilterOnSelectUsers', array(), $this, $action);
3141 if (!empty($reshook)) {
3142 $sql .= $hookmanager->resPrint;
3143 }
3144
3145 // Add criteria on the natural search string (used by the user/ajax/users.php autocomplete endpoint)
3146 if ($filterkey !== '') {
3147 $sql .= " AND (";
3148 $prefix = getDolGlobalString('USER_DONOTSEARCH_ANYWHERE') ? '' : '%'; // Can use index if USER_DONOTSEARCH_ANYWHERE is on
3149 // For natural search
3150 $search_crit = explode(' ', $filterkey);
3151 $i = 0;
3152 if (count($search_crit) > 1) {
3153 $sql .= "(";
3154 }
3155 foreach ($search_crit as $crit) {
3156 if ($i > 0) {
3157 $sql .= " AND ";
3158 }
3159 $sql .= "(u.firstname LIKE '".$this->db->escape($prefix.$crit)."%' OR u.lastname LIKE '".$this->db->escape($prefix.$crit)."%' OR u.login LIKE '".$this->db->escape($prefix.$crit)."%')";
3160 $i++;
3161 }
3162 if (count($search_crit) > 1) {
3163 $sql .= ")";
3164 }
3165 $sql .= ")";
3166 }
3167
3168 if (!getDolGlobalString('MAIN_FIRSTNAME_NAME_POSITION')) { // MAIN_FIRSTNAME_NAME_POSITION is 0 means firstname+lastname
3169 $sql .= " ORDER BY u.statut DESC, u.firstname ASC, u.lastname ASC";
3170 } else {
3171 $sql .= " ORDER BY u.statut DESC, u.lastname ASC, u.firstname ASC";
3172 }
3173
3174 if ($limit > 0) {
3175 $sql .= $this->db->plimit($limit, ((int) $limitoffset > 0 ? (int) $limitoffset : 0));
3176 }
3177
3178 dol_syslog(get_class($this) . "::select_dolusers", LOG_DEBUG);
3179
3180 $resql = $this->db->query($sql);
3181 if ($resql) {
3182 $num = $this->db->num_rows($resql);
3183 $i = 0;
3184 if ($num) {
3185 // do not use maxwidthonsmartphone by default. Set it by caller so auto size to 100% will work when not defined
3186 $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : ' minwidth200') . '" id="' . $htmlname . '" name="' . $htmlname . ($multiple ? '[]' : '') . '" ' . ($multiple ? 'multiple' : '') . ' ' . ($disabled ? ' disabled' : '') . '>';
3187 if ($show_empty && !$multiple) {
3188 $textforempty = ' ';
3189 if (!empty($conf->use_javascript_ajax)) {
3190 $textforempty = '&nbsp;'; // If we use ajaxcombo, we need &nbsp; here to avoid to have an empty element that is too small.
3191 }
3192 if (!is_numeric($show_empty)) {
3193 $textforempty = $show_empty;
3194 }
3195 $out .= '<option class="optiongrey" value="' . ($show_empty < 0 ? $show_empty : -1) . '"' . ((empty($selected) || in_array(-1, $selected)) ? ' selected' : '') . '>' . dol_escape_htmltag($textforempty) . '</option>' . "\n";
3196
3197 $outarray[($show_empty < 0 ? $show_empty : -1)] = $textforempty;
3198 $outarray2[($show_empty < 0 ? $show_empty : -1)] = array(
3199 'id' => ($show_empty < 0 ? $show_empty : -1),
3200 'label' => $textforempty,
3201 'labelhtml' => $textforempty,
3202 'color' => '',
3203 'picto' => ''
3204 );
3205 }
3206 if ($showalso == 2 || $showalso == 3) {
3207 $out .= '<option value="-3"' . ((in_array(-3, $selected)) ? ' selected' : '') . '>-- ' . $langs->trans("MyTeam") . ' --</option>' . "\n";
3208
3209 $hasAtLeastOneSubordinate = (count($user->getAllChildIds(1)) > 1);
3210 if ($hasAtLeastOneSubordinate) {
3211 //$sql = "SELECT rowid FROM".MAIN_DB_PREFIX."user "
3212 $outarray[-3] = '-- ' . $langs->trans("MyTeam") . ' --';
3213 $outarray2[-3] = array(
3214 'id' => -3,
3215 'label' => '-- ' . $langs->trans("MyTeam") . ' --',
3216 'labelhtml' => '-- ' . $langs->trans("MyTeam") . ' --',
3217 'color' => '',
3218 'picto' => ''
3219 );
3220 }
3221 }
3222 if ($showalso == 1 || $showalso == 3) {
3223 $out .= '<option value="-2"' . ((in_array(-2, $selected)) ? ' selected' : '') . '>-- ' . $langs->trans("Everybody") . ' --</option>' . "\n";
3224
3225 $outarray[-2] = '-- ' . $langs->trans("Everybody") . ' --';
3226 $outarray2[-2] = array(
3227 'id' => -2,
3228 'label' => '-- ' . $langs->trans("Everybody") . ' --',
3229 'labelhtml' => '-- ' . $langs->trans("Everybody") . ' --',
3230 'color' => '',
3231 'picto' => ''
3232 );
3233 }
3234 if ($showalso == 4) {
3235 $out .= '<option value="-4"' . ((in_array(-4, $selected)) ? ' selected' : '') . '>-- ' . $langs->trans("AllProjectContacts") . ' --</option>' . "\n";
3236
3237 $outarray[-4] = '-- ' . $langs->trans("AllProjectContacts") . ' --';
3238 $outarray2[-4] = array(
3239 'id' => -4,
3240 'label' => '-- ' . $langs->trans("AllProjectContacts") . ' --',
3241 'labelhtml' => '-- ' . $langs->trans("AllProjectContacts") . ' --',
3242 'color' => '',
3243 'picto' => ''
3244 );
3245 }
3246
3247 $userstatic = new User($this->db);
3248
3249 while ($i < $num) {
3250 $obj = $this->db->fetch_object($resql);
3251
3252 $userstatic->id = $obj->rowid;
3253 $userstatic->lastname = $obj->lastname;
3254 $userstatic->firstname = $obj->firstname;
3255 $userstatic->photo = $obj->photo;
3256 $userstatic->status = $obj->status;
3257 $userstatic->entity = $obj->entity;
3258 $userstatic->admin = $obj->admin;
3259 $userstatic->gender = $obj->gender;
3260
3261 $disableline = '';
3262 if (is_array($enableonly) && count($enableonly) && !in_array($obj->rowid, $enableonly)) {
3263 $disableline = ($enableonlytext ? $enableonlytext : '1');
3264 }
3265
3266 $labeltoshow = '';
3267 $labeltoshowhtml = '';
3268
3269 // $fullNameMode is 0=Lastname+Firstname (MAIN_FIRSTNAME_NAME_POSITION=1), 1=Firstname+Lastname (MAIN_FIRSTNAME_NAME_POSITION=0)
3270 $fullNameMode = 0;
3271 if (!getDolGlobalString('MAIN_FIRSTNAME_NAME_POSITION')) {
3272 $fullNameMode = 1; //Firstname+lastname
3273 }
3274 $labeltoshow .= $userstatic->getFullName($langs, $fullNameMode, -1, $maxlength);
3275 $labeltoshowhtml .= $userstatic->getFullName($langs, $fullNameMode, -1, $maxlength);
3276 if (empty($obj->firstname) && empty($obj->lastname)) {
3277 $labeltoshow .= $obj->login;
3278 $labeltoshowhtml .= $obj->login;
3279 }
3280
3281 // Complete name with a more info string like: ' (info1 - info2 - ...)'
3282 $moreinfo = '';
3283 $moreinfohtml = '';
3284 if (getDolGlobalString('MAIN_SHOW_LOGIN')) {
3285 $moreinfo .= ($moreinfo ? ' - ' : ' (');
3286 $moreinfohtml .= ($moreinfohtml ? ' - ' : ' <span class="opacitymedium">(');
3287 $moreinfo .= $obj->login;
3288 $moreinfohtml .= $obj->login;
3289 }
3290 if ($showstatus >= 0) {
3291 if ($obj->status == 1 && $showstatus == 1) {
3292 $moreinfo .= ($moreinfo ? ' - ' : ' (') . $langs->trans('Enabled');
3293 $moreinfohtml .= ($moreinfohtml ? ' - ' : ' <span class="opacitymedium">(') . $langs->trans('Enabled');
3294 }
3295 if ($obj->status == 0 && $showstatus == 1) {
3296 $moreinfo .= ($moreinfo ? ' - ' : ' (') . $langs->trans('Disabled');
3297 $moreinfohtml .= ($moreinfohtml ? ' - ' : ' <span class="opacitymedium">(') . $langs->trans('Disabled');
3298 }
3299 }
3300 if ($showlabelofentity) {
3301 if (empty($obj->entity)) {
3302 $moreinfo .= ($moreinfo ? ' - ' : ' (') . $langs->trans("AllEntities");
3303 $moreinfohtml .= ($moreinfohtml ? ' - ' : ' <span class="opacitymedium">(') . $langs->trans("AllEntities");
3304 } else {
3305 if ($obj->entity != $conf->entity) {
3306 $moreinfo .= ($moreinfo ? ' - ' : ' (') . ($obj->label ? $obj->label : $langs->trans("EntityNameNotDefined"));
3307 $moreinfohtml .= ($moreinfohtml ? ' - ' : ' <span class="opacitymedium">(').($obj->label ? $obj->label : $langs->trans("EntityNameNotDefined"));
3308 }
3309 }
3310 }
3311 $moreinfo .= (!empty($moreinfo) ? ')' : '');
3312 $moreinfohtml .= (!empty($moreinfohtml) ? ')</span>' : '');
3313 if (!empty($disableline) && $disableline != '1') {
3314 // Add text from $enableonlytext parameter
3315 $moreinfo .= ' - ' . $disableline;
3316 $moreinfohtml .= ' - ' . $disableline;
3317 }
3318 $labeltoshow .= $moreinfo;
3319 $labeltoshowhtml .= $moreinfohtml;
3320
3321 $out .= '<option value="' . $obj->rowid . '"';
3322 if (!empty($disableline)) {
3323 $out .= ' disabled';
3324 }
3325 if (in_array($obj->rowid, $selected)) {
3326 $out .= ' selected';
3327 }
3328 $out .= ' data-html="';
3329
3330 $outhtml = $userstatic->getNomUrl(-3, '', 0, 1, 24, 1, 'login', '', 1) . ' ';
3331 if ($showstatus >= 0 && $obj->status == 0) {
3332 $outhtml .= '<strike class="opacitymediumxxx">';
3333 }
3334 $outhtml .= $labeltoshowhtml;
3335 if ($showstatus >= 0 && $obj->status == 0) {
3336 $outhtml .= '</strike>';
3337 }
3338 $labeltoshowhtml = $outhtml;
3339
3340 $out .= dol_escape_htmltag($outhtml);
3341 $out .= '">';
3342 $out .= $labeltoshow;
3343 $out .= '</option>';
3344
3345 $outarray[$userstatic->id] = $userstatic->getFullName($langs, $fullNameMode, -1, $maxlength) . $moreinfo;
3346 $outarray2[$userstatic->id] = array(
3347 'id' => $userstatic->id,
3348 'label' => $labeltoshow,
3349 'labelhtml' => $labeltoshowhtml,
3350 'color' => '',
3351 'picto' => ''
3352 );
3353
3354 $i++;
3355 }
3356 } else {
3357 $out .= '<select class="flat" id="' . $htmlname . '" name="' . $htmlname . '" disabled>';
3358 $out .= '<option value="">' . $langs->trans("None") . '</option>';
3359 }
3360 $out .= '</select>';
3361
3362 if ($num && !$forcecombo) {
3363 // Enhance with select2
3364 include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
3365 $out .= ajax_combobox($htmlname);
3366 }
3367 } else {
3368 dol_print_error($this->db);
3369 }
3370
3371 $this->num = $num;
3372
3373 if ($outputmode == 2) {
3374 return $outarray2;
3375 } elseif ($outputmode) {
3376 return $outarray;
3377 }
3378
3379 return $out;
3380 }
3381
3382
3383 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3407 public function select_dolusers_forevent($action = '', $htmlname = 'userid', $show_empty = 0, $exclude = null, $disabled = 0, $include = array(), $enableonly = array(), $force_entity = '0', $maxlength = 0, $showstatus = 0, $morefilter = '', $showproperties = 0, $listofuserid = array(), $listofcontactid = array(), $listofotherid = array(), $canremoveowner = 1)
3408 {
3409 // phpcs:enable
3410 global $langs, $user;
3411
3412 $userstatic = new User($this->db);
3413 $out = '';
3414
3415 // The list of selected users is provided by the caller through $listofuserid (owner first).
3416 // Fall back to the legacy global $_SESSION['assignedtouser'] only when no list is provided
3417 // (comm/action/card.php now scopes that session bucket per event id and no longer feeds this key).
3418 if (!empty($listofuserid)) {
3419 $assignedtouser = $listofuserid;
3420 } elseif (!empty($_SESSION['assignedtouser'])) {
3421 $assignedtouser = json_decode($_SESSION['assignedtouser'], true);
3422 if (!is_array($assignedtouser)) {
3423 $assignedtouser = array();
3424 }
3425 } else {
3426 $assignedtouser = array();
3427 }
3428 $nbassignetouser = count($assignedtouser);
3429
3430 //if ($nbassignetouser && $action != 'view') $out .= '<br>';
3431 if ($nbassignetouser) {
3432 $out .= '<ul class="attendees">';
3433 }
3434 $i = 0;
3435 $ownerid = 0;
3436 foreach ($assignedtouser as $key => $value) {
3437 if ($value['id'] == $ownerid) {
3438 continue;
3439 }
3440
3441 $out .= '<li>';
3442
3443 $userstatic->fetch($value['id']);
3444 $out .= $userstatic->getNomUrl(-4);
3445
3446 if ($i == 0) {
3447 $ownerid = $value['id'];
3448 $out .= ' (' . $langs->trans("Owner") . ')';
3449 }
3450 // Add picto to delete owner/assignee
3451 if ($nbassignetouser > 1 && $action != 'view') {
3452 $canremoveassignee = 1;
3453 if ($i == 0) {
3454 // We are on the owner of the event
3455 if (!$canremoveowner) {
3456 $canremoveassignee = 0;
3457 }
3458 if (!$user->hasRight('agenda', 'allactions', 'create')) {
3459 $canremoveassignee = 0; // Can't remove the owner
3460 }
3461 } else {
3462 // We are not on the owner of the event but on a secondary assignee
3463 }
3464 if ($canremoveassignee) {
3465 // If user has all permission, he should be ableto remove a assignee.
3466 // If user has not all permission, he can onlyremove assignee of other (he can't remove itself)
3467 $out .= ' <input type="image" style="border: 0px;" src="' . img_picto($langs->trans("Remove"), 'delete', '', 0, 1) . '" value="' . $userstatic->id . '" class="noborderfocus removedassigned reposition" id="removedassigned_' . $userstatic->id . '" name="removedassigned_' . $userstatic->id . '">';
3468 }
3469 }
3470 // Show my availability
3471 if ($showproperties) {
3472 if ($ownerid == $value['id'] && is_array($listofuserid) && count($listofuserid) && in_array($ownerid, array_keys($listofuserid))) {
3473 $out .= '<div class="myavailability inline-block">';
3474 $out .= '<span class="hideonsmartphone">&nbsp;-&nbsp;';
3475 //$out .= '<span class="opacitymedium">' . $langs->trans("Availability") . ':</span>';
3476 $out .= '</span>';
3477 $out .= ' <input title="'.$langs->trans("Availability").'" id="transparency" class="paddingrightonly" ' . ($action == 'view' ? 'disabled' : '') . ' type="checkbox" name="transparency"' . ($listofuserid[$ownerid]['transparency'] ? ' checked' : '') . '><label for="transparency">' . $langs->trans("Busy") . '</label>';
3478 $out .= '</div>';
3479 }
3480 }
3481 //$out.=' '.($value['mandatory']?$langs->trans("Mandatory"):$langs->trans("Optional"));
3482 //$out.=' '.($value['transparency']?$langs->trans("Busy"):$langs->trans("NotBusy"));
3483
3484 $out .= '</li>';
3485 $i++;
3486 }
3487 if ($nbassignetouser) {
3488 $out .= '</ul>';
3489 }
3490
3491 // Method with no ajax
3492 if ($action != 'view') {
3493 // Section to add another user
3494 $out .= '<div class="divadduser'.$htmlname.'">';
3495 $out .= '<input type="hidden" class="removedassignedhidden" name="removedassigned" value="">';
3496 $out .= '<script nonce="' . getNonce() . '" type="text/javascript">jQuery(document).ready(function () {';
3497 $out .= 'jQuery(".removedassigned").click(function() { jQuery(".removedassignedhidden").val(jQuery(this).val()); });';
3498 $out .= 'jQuery(".assignedtouser").change(function() { console.log(jQuery(".assignedtouser option:selected").val());';
3499 $out .= ' if (jQuery(".assignedtouser option:selected").val() > 0) { jQuery("#' . $action . 'assignedtouser").attr("disabled", false); }';
3500 $out .= ' else { jQuery("#' . $action . 'assignedtouser").attr("disabled", true); }';
3501 $out .= '});';
3502 $out .= '})</script>';
3503 $out .= img_picto('', 'user', 'class="pictofixedwidth"');
3504 // Force a real combo (forcecombo=1): this widget drives the "Add" button from the <option> elements of the
3505 // select, so it must not be replaced by the ajax autocomplete of select_dolusers() (USER_USE_SEARCH_TO_SELECT).
3506 $out .= $this->select_dolusers('', $htmlname, $show_empty, $exclude, $disabled, $include, $enableonly, $force_entity, $maxlength, $showstatus, $morefilter, 0, '', 'minwidth200', 0, 0, false, 1);
3507 // select_dolusers() skips the select2 beautification when forcecombo is set, so re-apply it here.
3508 include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
3509 $out .= ajax_combobox($htmlname);
3510 $out .= ' <button type="submit" disabled class="button valignmiddle smallpaddingimp reposition butActionAdd" id="' . $action . 'assignedtouser" name="' . $action . 'assignedtouser" value="' . dol_escape_htmltag($langs->trans("Add")) . '">';
3511 $out .= $langs->trans("Add").'</button>';
3512 $out .= '</div>';
3513 //$out .= '<br>';
3514 }
3515
3516 return $out;
3517 }
3518
3519 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3539 public function select_dolresources_forevent($action = '', $htmlname = 'userid', $show_empty = 0, $exclude = null, $disabled = 0, $include = array(), $enableonly = array(), $force_entity = '0', $maxlength = 0, $showstatus = 0, $morefilter = '', $showproperties = 0, $listofresourceid = array())
3540 {
3541 // phpcs:enable
3542 global $langs;
3543
3544 require_once DOL_DOCUMENT_ROOT.'/resource/class/html.formresource.class.php';
3545 require_once DOL_DOCUMENT_ROOT.'/resource/class/dolresource.class.php';
3546 $formresources = new FormResource($this->db);
3547 $resourcestatic = new Dolresource($this->db);
3548
3549 $out = '';
3550 if (!empty($_SESSION['assignedtoresource'])) {
3551 $assignedtoresource = json_decode($_SESSION['assignedtoresource'], true);
3552 if (!is_array($assignedtoresource)) {
3553 $assignedtoresource = array();
3554 }
3555 } else {
3556 $assignedtoresource = array();
3557 }
3558 $nbassignetoresource = count($assignedtoresource);
3559
3560 //if ($nbassignetoresource && $action != 'view') $out .= '<br>';
3561 if ($nbassignetoresource) {
3562 $out .= '<ul class="attendees">';
3563 }
3564 $i = 0;
3565
3566 foreach ($assignedtoresource as $key => $value) {
3567 $out .= '<li>';
3568 $resourcestatic->fetch($value['id']);
3569 $out .= $resourcestatic->getNomUrl(-1);
3570 if ($nbassignetoresource >= 1 && $action != 'view') {
3571 $out .= ' <input type="image" style="border: 0px;" src="' . img_picto($langs->trans("Remove"), 'delete', '', 0, 1) . '" value="' . $resourcestatic->id . '" class="removedassignedresource reposition" id="removedassignedresource_' . $resourcestatic->id . '" name="removedassignedresource_' . $resourcestatic->id . '">';
3572 }
3573 // Show my availability
3574 if ($showproperties) {
3575 if (is_array($listofresourceid) && count($listofresourceid)) {
3576 $out .= '<div class="myavailability inline-block">';
3577 $out .= '<span class="hideonsmartphone">&nbsp;-&nbsp;';
3578 //$out .= '<span class="opacitymedium">' . $langs->trans("Availability") . ': </span>';
3579 $out .= '</span>';
3580 $out .= ' <input title="'.$langs->trans("Availability").'" id="transparencyresource'.$value['id'].'" class="paddingrightonly" ' . ($action == 'view' ? 'disabled' : '') . ' type="checkbox" name="transparency"' . ($listofresourceid[$value['id']]['transparency'] ? ' checked' : '') . '><label for="transparencyresource'.$value['id'].'">' . $langs->trans("Busy") . '</label>';
3581 $out .= '</div>';
3582 }
3583 }
3584 //$out.=' '.($value['mandatory']?$langs->trans("Mandatory"):$langs->trans("Optional"));
3585 //$out.=' '.($value['transparency']?$langs->trans("Busy"):$langs->trans("NotBusy"));
3586
3587 $out .= '</li>';
3588 $i++;
3589 }
3590 if ($nbassignetoresource) {
3591 $out .= '</ul>';
3592 }
3593
3594 // Method with no ajax
3595 if ($action != 'view') {
3596 $out .= '<input type="hidden" class="removedassignedresourcehidden" name="removedassignedresource" value="">';
3597 $out .= '<script nonce="' . getNonce() . '" type="text/javascript">jQuery(document).ready(function () {';
3598 $out .= 'jQuery(".removedassignedresource").click(function() { jQuery(".removedassignedresourcehidden").val(jQuery(this).val()); });';
3599 $out .= 'jQuery(".assignedtoresource").change(function() { console.log(jQuery(".assignedtoresource option:selected").val());';
3600 $out .= ' if (jQuery(".assignedtoresource option:selected").val() > 0) { jQuery("#' . $action . 'assignedtoresource").attr("disabled", false); }';
3601 $out .= ' else { jQuery("#' . $action . 'assignedtoresource").attr("disabled", true); }';
3602 $out .= '});';
3603 $out .= '})</script>';
3604
3605 $events = array();
3606 if ($nbassignetoresource) {
3607 //$out .= img_picto('', 'add', 'class="pictofixedwidth"');
3608 } else {
3609 $out .= img_picto('', 'resource', 'class="pictofixedwidth"');
3610 }
3611 $out .= $formresources->select_resource_list(0, $htmlname, '', 1, 1, 0, $events, '', 2, 0, 'minwidth200');
3612 //$out .= $this->select_dolusers('', $htmlname, $show_empty, $exclude, $disabled, $include, $enableonly, $force_entity, $maxlength, $showstatus, $morefilter);
3613 $out .= ' <button type="submit" disabled class="button valignmiddle smallpaddingimp reposition butActionAdd" id="' . $action . 'assignedtoresource" name="' . $action . 'assignedtoresource" value="' . dol_escape_htmltag($langs->trans("Add")) . '">';
3614 $out .= $langs->trans("Add");
3615 $out .= '</button>';
3616 $out .= '<br>';
3617 }
3618
3619 return $out;
3620 }
3621
3622 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3623
3653 public function select_produits($selected = 0, $htmlname = 'productid', $filtertype = '', $limit = 0, $price_level = 0, $status = 1, $finished = 2, $selected_input_value = '', $hidelabel = 0, $ajaxoptions = array(), $socid = 0, $showempty = '1', $forcecombo = 0, $morecss = '', $hidepriceinlabel = 0, $warehouseStatus = '', $selected_combinations = null, $nooutput = 0, $status_purchase = -1, $warehouseId = 0)
3654 {
3655 // phpcs:enable
3656 global $langs, $conf;
3657
3658 $out = '';
3659
3660 // check parameters
3661 $price_level = (!empty($price_level) ? $price_level : 0);
3662 if (is_null($ajaxoptions)) {
3663 $ajaxoptions = array();
3664 }
3665
3666 if (strval($filtertype) === '' && (isModEnabled("product") || isModEnabled("service"))) {
3667 if (isModEnabled("product") && !isModEnabled('service')) {
3668 $filtertype = '0';
3669 } elseif (!isModEnabled('product') && isModEnabled("service")) {
3670 $filtertype = '1';
3671 }
3672 }
3673
3674 if (!empty($conf->use_javascript_ajax) && getDolGlobalString('PRODUIT_USE_SEARCH_TO_SELECT')) {
3675 $placeholder = (is_numeric($showempty) ? '' : 'placeholder="'.dolPrintHTML($showempty).'"');
3676
3677 if ($selected && empty($selected_input_value)) {
3678 require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
3679 $producttmpselect = new Product($this->db);
3680 $producttmpselect->fetch($selected);
3681 $selected_input_value = $producttmpselect->ref;
3682 unset($producttmpselect);
3683 }
3684 // handle case where product or service module is disabled + no filter specified
3685 if ($filtertype == '') {
3686 if (!isModEnabled('product')) { // when product module is disabled, show services only
3687 $filtertype = 1;
3688 } elseif (!isModEnabled('service')) { // when service module is disabled, show products only
3689 $filtertype = 0;
3690 }
3691 }
3692 // mode=1 means customers products
3693 $urloption = ($socid > 0 ? 'socid=' . $socid . '&' : '') . 'htmlname=' . $htmlname . '&outjson=1&price_level=' . $price_level . '&type=' . $filtertype . '&mode=1&status=' . $status . '&status_purchase=' . $status_purchase . '&finished=' . $finished . '&hidepriceinlabel=' . $hidepriceinlabel . '&warehousestatus=' . $warehouseStatus;
3694 if ((int) $warehouseId > 0) {
3695 $urloption .= '&warehouseid=' . (int) $warehouseId;
3696 }
3697
3698 if (isModEnabled('variants') && is_array($selected_combinations)) {
3699 // Code to automatically insert with javascript the select of attributes under the select of product
3700 // when a parent of variant has been selected.
3701 // Note: Samecode than for product input using select
3702 $htmltag = 'input';
3703 $out .= '
3704 <!-- script to auto show attributes select tags if a variant was selected -->
3705 <script nonce="' . getNonce() . '">
3706 // auto show attributes fields
3707 selected = ' . json_encode($selected_combinations) . ';
3708 combvalues = {};
3709
3710 jQuery(document).ready(function () {
3711
3712 jQuery("input[name=\'prod_entry_mode\']").change(function () {
3713 if (jQuery(this).val() == \'free\') {
3714 jQuery(\'div#attributes_box\').empty();
3715 }
3716 });
3717
3718 jQuery("'.$htmltag.'#' . $htmlname . '").change(function () {
3719
3720 if (!jQuery(this).val()) {
3721 jQuery(\'div#attributes_box\').empty();
3722 return;
3723 }
3724
3725 console.log("A change has started. We get variants fields to inject html select");
3726
3727 jQuery.getJSON("' . DOL_URL_ROOT . '/variants/ajax/getCombinations.php", {
3728 id: jQuery(this).val()
3729 }, function (data) {
3730 jQuery(\'div#attributes_box\').empty();
3731
3732 jQuery.each(data, function (key, val) {
3733
3734 combvalues[val.id] = val.values;
3735
3736 var span = jQuery(document.createElement(\'div\')).css({
3737 \'display\': \'table-row\'
3738 });
3739
3740 span.append(
3741 jQuery(document.createElement(\'div\')).text(val.label).css({
3742 \'font-weight\': \'bold\',
3743 \'display\': \'table-cell\'
3744 })
3745 );
3746
3747 var html = jQuery(document.createElement(\'select\')).attr(\'name\', \'combinations[\' + val.id + \']\').css({
3748 \'margin-left\': \'15px\',
3749 \'white-space\': \'pre\'
3750 }).append(
3751 jQuery(document.createElement(\'option\')).val(\'\')
3752 );
3753
3754 jQuery.each(combvalues[val.id], function (key, val) {
3755 var tag = jQuery(document.createElement(\'option\')).val(val.id).html(val.value);
3756
3757 if (selected[val.fk_product_attribute] == val.id) {
3758 tag.attr(\'selected\', \'selected\');
3759 }
3760
3761 html.append(tag);
3762 });
3763
3764 span.append(html);
3765 jQuery(\'div#attributes_box\').append(span);
3766 });
3767 })
3768 });
3769
3770 ' . ($selected ? 'jQuery("'.$htmltag.'#' . $htmlname . '").change();' : '') . '
3771 });
3772 </script>
3773 ';
3774 }
3775
3776 if (empty($hidelabel)) {
3777 $placeholder = ' placeholder="' . dolPrintHTMLForAttribute($langs->trans("RefOrLabel")) . '"';
3778 } elseif ($hidelabel > 1) {
3779 $placeholder = ' placeholder="' . dolPrintHTMLForAttribute($langs->trans("RefOrLabel")) . '"';
3780 if ($hidelabel == 2) {
3781 $out .= img_picto($langs->trans("Search"), 'search');
3782 }
3783 }
3784
3785 $out .= '<input type="text" class="minwidth100' . ($morecss ? ' ' . $morecss : '') . '" name="search_' . $htmlname . '" id="search_' . $htmlname . '" value="' . $selected_input_value . '"' . $placeholder . ' ' . (getDolGlobalString('PRODUCT_SEARCH_AUTOFOCUS') ? 'autofocus' : '') . ' spellcheck="false" />';
3786 if ($hidelabel == 3) {
3787 $out .= img_picto($langs->trans("Search"), 'search');
3788 }
3789
3790 $out .= ajax_autocompleter((string) $selected, $htmlname, DOL_URL_ROOT . '/product/ajax/products.php', $urloption, getDolGlobalInt('PRODUIT_USE_SEARCH_TO_SELECT'), getDolGlobalInt('PRODUCT_SEARCH_AUTO_SELECT_IF_ONLY_ONE', 1), $ajaxoptions);
3791 } else {
3792 $out .= $this->select_produits_list($selected, $htmlname, $filtertype, $limit, $price_level, '', $status, $finished, 0, $socid, $showempty, $forcecombo, $morecss, $hidepriceinlabel, $warehouseStatus, $status_purchase, $warehouseId);
3793
3794 if (isModEnabled('variants') && is_array($selected_combinations)) {
3795 // Code to automatically insert with javascript the select of attributes under the select of product
3796 // when a parent of variant has been selected.
3797 // Note: Samecode than for product input using Ajax
3798 $htmltag = 'select';
3799 $out .= '
3800 <!-- script to auto show attributes select tags if a variant was selected -->
3801 <script nonce="' . getNonce() . '">
3802 // auto show attributes fields
3803 selected = ' . json_encode($selected_combinations) . ';
3804 combvalues = {};
3805
3806 jQuery(document).ready(function () {
3807
3808 jQuery("input[name=\'prod_entry_mode\']").change(function () {
3809 if (jQuery(this).val() == \'free\') {
3810 jQuery(\'div#attributes_box\').empty();
3811 }
3812 });
3813
3814 jQuery("'.$htmltag.'#' . $htmlname . '").change(function () {
3815
3816 if (!jQuery(this).val()) {
3817 jQuery(\'div#attributes_box\').empty();
3818 return;
3819 }
3820
3821 console.log("A change has started. We get variants fields to inject html select");
3822
3823 jQuery.getJSON("' . DOL_URL_ROOT . '/variants/ajax/getCombinations.php", {
3824 id: jQuery(this).val()
3825 }, function (data) {
3826 jQuery(\'div#attributes_box\').empty();
3827
3828 jQuery.each(data, function (key, val) {
3829
3830 combvalues[val.id] = val.values;
3831
3832 var span = jQuery(document.createElement(\'div\')).css({
3833 \'display\': \'table-row\'
3834 });
3835
3836 span.append(
3837 jQuery(document.createElement(\'div\')).text(val.label).css({
3838 \'font-weight\': \'bold\',
3839 \'display\': \'table-cell\'
3840 })
3841 );
3842
3843 var html = jQuery(document.createElement(\'select\')).attr(\'name\', \'combinations[\' + val.id + \']\').css({
3844 \'margin-left\': \'15px\',
3845 \'white-space\': \'pre\'
3846 }).append(
3847 jQuery(document.createElement(\'option\')).val(\'\')
3848 );
3849
3850 jQuery.each(combvalues[val.id], function (key, val) {
3851 var tag = jQuery(document.createElement(\'option\')).val(val.id).html(val.value);
3852
3853 if (selected[val.fk_product_attribute] == val.id) {
3854 tag.attr(\'selected\', \'selected\');
3855 }
3856
3857 html.append(tag);
3858 });
3859
3860 span.append(html);
3861 jQuery(\'div#attributes_box\').append(span);
3862 });
3863 })
3864 });
3865
3866 ' . ($selected ? 'jQuery("'.$htmltag.'#' . $htmlname . '").change();' : '') . '
3867 });
3868 </script>
3869 ';
3870 }
3871 }
3872
3873 if (empty($nooutput)) {
3874 print $out;
3875 } else {
3876 return $out;
3877 }
3878 }
3879
3880 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3881
3897 public function select_bom($selected = '', $htmlname = 'bom_id', $limit = 0, $status = 1, $type = 0, $showempty = '1', $morecss = '', $nooutput = '', $forcecombo = 0, $TProducts = [])
3898 {
3899 // phpcs:enable
3900
3901 require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
3902
3903 $error = 0;
3904 $out = '';
3905
3906 if (!$forcecombo) {
3907 include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
3908 $events = array();
3909 $out .= ajax_combobox($htmlname, $events, getDolGlobalInt("BOM_USE_SEARCH_TO_SELECT"));
3910 }
3911
3912 $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" id="' . $htmlname . '">';
3913
3914 $sql = 'SELECT b.rowid, b.ref, b.label as bomLabel, p.label as productLabel';
3915 $sql .= ' FROM ' . $this->db->prefix() . 'bom_bom as b';
3916 $sql .= ' INNER JOIN ' . $this->db->prefix() . 'product as p ON b.fk_product = p.rowid';
3917 $sql .= ' WHERE b.entity IN (' . getEntity('bom') . ')';
3918 if (!empty($status)) {
3919 $sql .= ' AND status = ' . (int) $status;
3920 }
3921 if (!empty($type)) {
3922 $sql .= ' AND bomtype = ' . (int) $type;
3923 }
3924 if (!empty($TProducts)) {
3925 $sql .= ' AND fk_product IN (' . $this->db->sanitize(implode(',', $TProducts)) . ')';
3926 }
3927 if (!empty($limit)) {
3928 $sql .= ' LIMIT ' . (int) $limit;
3929 }
3930 $resql = $this->db->query($sql);
3931 if ($resql) {
3932 if ($showempty) {
3933 $out .= '<option value="-1"';
3934 if (empty($selected)) {
3935 $out .= ' selected';
3936 }
3937 $out .= '>&nbsp;</option>';
3938 }
3939 while ($obj = $this->db->fetch_object($resql)) {
3940 $out .= '<option value="' . $obj->rowid . '"';
3941 if ($obj->rowid == $selected) {
3942 $out .= 'selected';
3943 }
3944 $out .= '>' . $obj->ref . ' - ' . $obj->productLabel . ' - ' . $obj->bomLabel . '</option>';
3945 }
3946 } else {
3947 $error++;
3948 dol_print_error($this->db);
3949 }
3950 $out .= '</select>';
3951 if (empty($nooutput)) {
3952 print $out;
3953 } else {
3954 return $out;
3955 }
3956 }
3957
3958 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3959
3986 public function select_produits_list($selected = 0, $htmlname = 'productid', $filtertype = '', $limit = 1000, $price_level = 0, $filterkey = '', $status = 1, $finished = 2, $outputmode = 0, $socid = 0, $showempty = '1', $forcecombo = 0, $morecss = 'maxwidth500', $hidepriceinlabel = 0, $warehouseStatus = '', $status_purchase = -1, $warehouseId = 0)
3987 {
3988 // phpcs:enable
3989 global $langs;
3990 global $hookmanager;
3991
3992 $out = '';
3993 $outarray = array();
3994
3995 // Units
3996 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
3997 $langs->load('other');
3998 }
3999
4000 $warehouseStatusArray = array();
4001 if (!empty($warehouseStatus)) {
4002 require_once DOL_DOCUMENT_ROOT . '/product/stock/class/entrepot.class.php';
4003 if (preg_match('/warehouseclosed/', $warehouseStatus)) {
4004 $warehouseStatusArray[] = Entrepot::STATUS_CLOSED;
4005 }
4006 if (preg_match('/warehouseopen/', $warehouseStatus)) {
4007 $warehouseStatusArray[] = Entrepot::STATUS_OPEN_ALL;
4008 }
4009 if (preg_match('/warehouseinternal/', $warehouseStatus)) {
4010 $warehouseStatusArray[] = Entrepot::STATUS_OPEN_INTERNAL;
4011 }
4012 }
4013
4014 $selectFields = "p.rowid, p.ref, p.label, p.description, p.barcode, p.fk_country, p.fk_product_type, p.price, p.price_ttc, p.price_base_type, p.tva_tx, p.default_vat_code, p.duration, p.fk_price_expression";
4015 if (count($warehouseStatusArray)) {
4016 $selectFieldsGrouped = ", SUM(" . $this->db->ifsql("e.statut IS NULL", "0", "ps.reel") . ") as stock"; // e.statut is null if there is no record in a qualified stock
4017 } else {
4018 $selectFieldsGrouped = ", " . $this->db->ifsql("p.stock IS NULL", '0', "p.stock") . " AS stock";
4019 }
4020
4021 $sql = "SELECT ";
4022
4023 // Add select from hooks
4024 $parameters = array();
4025 $reshook = $hookmanager->executeHooks('selectProductsListSelect', $parameters); // Note that $action and $object may have been modified by hook
4026 if (empty($reshook)) {
4027 $sql .= $selectFields.$selectFieldsGrouped.$hookmanager->resPrint;
4028 } else {
4029 $sql .= $hookmanager->resPrint;
4030 }
4031
4032 if (getDolGlobalString('PRODUCT_SORT_BY_CATEGORY')) {
4033 // Take randomly the first category of product to allow a sort on it. Bugged feature !
4034 $sql .= ", (SELECT " . $this->db->prefix() . "categorie_product.fk_categorie
4035 FROM " . $this->db->prefix() . "categorie_product
4036 WHERE " . $this->db->prefix() . "categorie_product.fk_product = p.rowid
4037 LIMIT 1
4038 ) AS categorie_product_id";
4039 }
4040
4041 // Price by customer
4042 if ((getDolGlobalString('PRODUIT_CUSTOMER_PRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && !empty($socid)) {
4043 $sql .= ', pcp.rowid as idprodcustprice, pcp.price as custprice, pcp.price_ttc as custprice_ttc,';
4044 $sql .= ' pcp.price_base_type as custprice_base_type, pcp.tva_tx as custtva_tx, pcp.default_vat_code as custdefault_vat_code, pcp.ref_customer as custref, pcp.discount_percent as custdiscount_percent';
4045 $selectFields .= ", idprodcustprice, custprice, custprice_ttc, custprice_base_type, custtva_tx, custdefault_vat_code, custref, custdiscount_percent";
4046 }
4047 // Units
4048 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
4049 $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";
4050 $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';
4051 }
4052
4053 // Multilang : we add translation
4054 if (getDolGlobalInt('MAIN_MULTILANGS')) {
4055 $sql .= ", pl.label as label_translated";
4056 $sql .= ", pl.description as description_translated";
4057 $selectFields .= ", label_translated";
4058 $selectFields .= ", description_translated";
4059 }
4060 // Price by quantity
4061 if (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES')) {
4062 $sql .= ", (SELECT pp.rowid FROM " . $this->db->prefix() . "product_price as pp WHERE pp.fk_product = p.rowid";
4063 if ($price_level >= 1 && getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES')) {
4064 $sql .= " AND price_level = " . ((int) $price_level);
4065 }
4066 $sql .= " ORDER BY date_price";
4067 $sql .= " DESC LIMIT 1) as price_rowid";
4068 $sql .= ", (SELECT pp.price_by_qty FROM " . $this->db->prefix() . "product_price as pp WHERE pp.fk_product = p.rowid"; // price_by_qty is 1 if some prices by qty exists in subtable
4069 if ($price_level >= 1 && getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES')) {
4070 $sql .= " AND price_level = " . ((int) $price_level);
4071 }
4072 $sql .= " ORDER BY date_price";
4073 $sql .= " DESC LIMIT 1) as price_by_qty";
4074 $selectFields .= ", price_rowid, price_by_qty";
4075 }
4076
4077 //$sqlfields = $sql; // $sql fields to remove for count total
4078
4079 $sql .= " FROM ".$this->db->prefix()."product as p";
4080
4081 if (getDolGlobalString('MAIN_SEARCH_PRODUCT_FORCE_INDEX')) {
4082 $sql .= " USE INDEX (" . $this->db->sanitize(getDolGlobalString('MAIN_PRODUCT_FORCE_INDEX')) . ")";
4083 }
4084
4085 // Add from (left join) from hooks
4086 $parameters = array(
4087 'socid' => $socid,
4088 );
4089 $reshook = $hookmanager->executeHooks('selectProductsListFrom', $parameters); // Note that $action and $object may have been modified by hook
4090 $sql .= $hookmanager->resPrint;
4091
4092 if (count($warehouseStatusArray)) {
4093 // Return line if product is inside the selected stock. If not, e.* and p.* will be null so we will count 0.
4094 // Replace this with a AND EXISTS ? Not possible as we need the ps.reel field for the SUM or 0 if no link.
4095 $sql .= " LEFT JOIN " . $this->db->prefix() . "product_stock as ps ON ps.fk_product = p.rowid";
4096 $sql .= " LEFT JOIN " . $this->db->prefix() . "entrepot as e ON ps.fk_entrepot = e.rowid AND e.entity IN (" . getEntity('stock') . ")";
4097 $sql .= ' AND e.statut IN (' . $this->db->sanitize($this->db->escape(implode(',', $warehouseStatusArray))) . ')';
4098 }
4099
4100 // Price by customer (Add field pcp for the older price for couple product/thirdparty.
4101 if ((getDolGlobalString('PRODUIT_CUSTOMER_PRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && !empty($socid)) {
4102 $now = dol_now();
4103 $sql .= " LEFT JOIN (";
4104 $sql .= " SELECT pcp1.*";
4105 $sql .= " FROM " . $this->db->prefix() . "product_customer_price AS pcp1";
4106 $sql .= " LEFT JOIN (";
4107 $sql .= " SELECT fk_soc, fk_product, MIN(date_begin) AS date_begin";
4108 $sql .= " FROM " . $this->db->prefix() . "product_customer_price";
4109 $sql .= " WHERE fk_soc = " . ((int) $socid);
4110 $sql .= " AND date_begin <= '" . $this->db->idate($now) . "'";
4111 $sql .= " AND (date_end IS NULL OR '" . $this->db->idate($now) . "' <= date_end)";
4112 $sql .= " GROUP BY fk_soc, fk_product";
4113 $sql .= " ) AS pcp2 ON pcp1.fk_soc = pcp2.fk_soc AND pcp1.fk_product = pcp2.fk_product AND pcp1.date_begin = pcp2.date_begin";
4114 $sql .= " WHERE pcp2.fk_soc IS NOT NULL";
4115 $sql .= " ) AS pcp ON pcp.fk_soc = " . ((int) $socid) . " AND pcp.fk_product = p.rowid";
4116 }
4117 // Units : we add unit properties with a link on the primary key of unit
4118 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
4119 $sql .= " LEFT JOIN " . $this->db->prefix() . "c_units as u ON u.rowid = p.fk_unit";
4120 }
4121 // Multilang : we add translation fields with a link on unique key fk_product/lang.
4122 if (getDolGlobalInt('MAIN_MULTILANGS')) {
4123 $sql .= " LEFT JOIN " . $this->db->prefix() . "product_lang as pl ON pl.fk_product = p.rowid";
4124 if (getDolGlobalString('PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE') && !empty($socid)) {
4125 require_once DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php';
4126 $soc = new Societe($this->db);
4127 $result = $soc->fetch($socid);
4128 if ($result > 0 && !empty($soc->default_lang)) {
4129 $sql .= " AND pl.lang = '" . $this->db->escape($soc->default_lang) . "'";
4130 } else {
4131 $sql .= " AND pl.lang = '" . $this->db->escape($langs->getDefaultLang()) . "'";
4132 }
4133 } else {
4134 $sql .= " AND pl.lang = '" . $this->db->escape($langs->getDefaultLang()) . "'";
4135 }
4136 }
4137
4138 // Add WHERE conditions
4139 $sql .= ' WHERE p.entity IN (' . getEntity('product') . ')';
4140 if (getDolGlobalString('PRODUIT_ATTRIBUTES_HIDECHILD')) {
4141 if (getDolGlobalString('PRODUIT_ATTRIBUTES_HIDECHILD_BUT_ALLOW_SEARCH_IN_EAN13')) {
4142 if (strlen($filterkey) != 13) {
4143 $sql .= " AND NOT EXISTS (SELECT pac.rowid FROM ".$this->db->prefix()."product_attribute_combination as pac WHERE pac.fk_product_child = p.rowid)";
4144 }
4145 } else {
4146 $sql .= " AND NOT EXISTS (SELECT pac.rowid FROM ".$this->db->prefix()."product_attribute_combination as pac WHERE pac.fk_product_child = p.rowid)";
4147 }
4148 }
4149 if ($finished == 0) {
4150 $sql .= " AND p.finished = " . ((int) $finished);
4151 } elseif ($finished == 1) {
4152 $sql .= " AND p.finished = ".((int) $finished);
4153 }
4154 if ($status >= 0) {
4155 $sql .= " AND p.tosell = ".((int) $status);
4156 }
4157 if ($status_purchase >= 0) {
4158 $sql .= " AND p.tobuy = " . ((int) $status_purchase);
4159 }
4160 // Filter by product type
4161 if (strval($filtertype) != '') {
4162 $sql .= " AND p.fk_product_type = " . ((int) $filtertype);
4163 } elseif (!isModEnabled('product')) { // when product module is disabled, show services only
4164 $sql .= " AND p.fk_product_type = 1";
4165 } elseif (!isModEnabled('service')) { // when service module is disabled, show products only
4166 $sql .= " AND p.fk_product_type = 0";
4167 }
4168
4169 if ((int) $warehouseId > 0) {
4170 $sql .= " AND EXISTS (SELECT psw.fk_product FROM " . $this->db->prefix() . "product_stock as psw WHERE psw.reel > 0 AND psw.fk_entrepot = ".(int) $warehouseId." AND psw.fk_product = p.rowid)";
4171 }
4172
4173 // Add where from hooks
4174 $parameters = array(
4175 'filterkey' => &$filterkey,
4176 'socid' => $socid,
4177 );
4178 $reshook = $hookmanager->executeHooks('selectProductsListWhere', $parameters); // Note that $action and $object may have been modified by hook
4179 $sql .= $hookmanager->resPrint;
4180 // Add criteria on ref/label
4181 if ($filterkey != '') {
4182 $sqlSupplierSearch = '';
4183
4184 $sql .= ' AND (';
4185 $prefix = getDolGlobalString('PRODUCT_DONOTSEARCH_ANYWHERE') ? '' : '%'; // Can use index if PRODUCT_DONOTSEARCH_ANYWHERE is on
4186 // For natural search
4187 $search_crit = explode(' ', $filterkey);
4188 $i = 0;
4189 if (count($search_crit) > 1) {
4190 $sql .= "(";
4191 }
4192 foreach ($search_crit as $crit) {
4193 if ($i > 0) {
4194 $sql .= " AND ";
4195 }
4196 $sql .= "(p.ref LIKE '" . $this->db->escape($prefix . $crit) . "%' OR p.label LIKE '" . $this->db->escape($prefix . $crit) . "%'";
4197 if (getDolGlobalInt('MAIN_MULTILANGS')) {
4198 $sql .= " OR pl.label LIKE '" . $this->db->escape($prefix . $crit) . "%'";
4199 }
4200 if ((getDolGlobalString('PRODUIT_CUSTOMER_PRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && !empty($socid)) {
4201 $sql .= " OR pcp.ref_customer LIKE '" . $this->db->escape($prefix . $crit) . "%'";
4202 }
4203 if (getDolGlobalString('PRODUCT_AJAX_SEARCH_ON_DESCRIPTION')) {
4204 $sql .= " OR p.description LIKE '" . $this->db->escape($prefix . $crit) . "%'";
4205 if (getDolGlobalInt('MAIN_MULTILANGS')) {
4206 $sql .= " OR pl.description LIKE '" . $this->db->escape($prefix . $crit) . "%'";
4207 }
4208 }
4209
4210 // include search in supplier ref
4211 if (getDolGlobalString('MAIN_SEARCH_PRODUCT_BY_FOURN_REF')) {
4212 $sqlSupplierSearch .= !empty($sqlSupplierSearch) ? ' AND ' : '';
4213 $sqlSupplierSearch .= " pfp.ref_fourn LIKE '" . $this->db->escape($prefix . $crit) . "%'";
4214 }
4215 $sql .= ")";
4216 $i++;
4217 }
4218 if (count($search_crit) > 1) {
4219 $sql .= ")";
4220 }
4221 if (isModEnabled('barcode')) {
4222 $sql .= " OR p.barcode LIKE '" . $this->db->escape($prefix . $filterkey) . "%'";
4223 }
4224
4225 // include search in supplier ref
4226 if (getDolGlobalString('MAIN_SEARCH_PRODUCT_BY_FOURN_REF')) {
4227 $sql .= " OR EXISTS (SELECT pfp.fk_product FROM " . $this->db->prefix() . "product_fournisseur_price as pfp WHERE p.rowid = pfp.fk_product";
4228 $sql .= " AND ((";
4229 $sql .= $sqlSupplierSearch;
4230 $sql .= ")";
4231 if (isModEnabled('barcode')) {
4232 $sql .= " OR pfp.barcode LIKE '" . $this->db->escape($prefix . $filterkey) . "%'";
4233 }
4234 $sql .= "))";
4235 }
4236
4237 $sql .= ')';
4238 }
4239 if (count($warehouseStatusArray)) {
4240 $sql .= " GROUP BY " . $this->db->sanitize($selectFields, 0, 0, 1); // To have the SUM on ps.reel working in the select.
4241 }
4242
4243 // Sort by category
4244 if (getDolGlobalString('PRODUCT_SORT_BY_CATEGORY')) {
4245 $sql .= " ORDER BY categorie_product_id ".(getDolGlobalInt('PRODUCT_SORT_BY_CATEGORY') == 1 ? "ASC" : "DESC");
4246 } else {
4247 $sql .= $this->db->order("p.ref");
4248 }
4249
4250 $limit = getDolGlobalInt('SEARCH_LIMIT_AJAX') ?: $limit; // SEARCH_LIMIT_AJAX is a hidden option that has priority on visible option PRODUIT_LIMIT_SIZE if set.
4251 $sql .= $this->db->plimit($limit, 0);
4252
4253 /* The fast and low memory method to get and count full list converts the sql into a sql count */
4254 /*
4255 $nbtotalofrecords = 0;
4256 $sqlforcount = preg_replace('/^'.preg_quote($sqlfields, '/').'/', 'SELECT COUNT(*) as nbtotalofrecords', $sql);
4257 $sqlforcount = preg_replace('/GROUP BY .*$/', '', $sqlforcount);
4258
4259 $resql = $this->db->query($sqlforcount);
4260 if ($resql) {
4261 $objforcount = $this->db->fetch_object($resql);
4262 $nbtotalofrecords = $objforcount->nbtotalofrecords;
4263 } else {
4264 dol_print_error($this->db);
4265 }
4266 */
4267
4268 // Build output string
4269 dol_syslog(get_class($this) . "::select_produits_list search products", LOG_DEBUG);
4270
4271 // If we have no $limit parameter, this request may hang dur to high number of lines returned.
4272 // This should not happen because this method should not be called directly, iIt is called by select_produit() that always add a $limit parameter.
4273 $result = $this->db->query($sql);
4274
4275 if ($result) {
4276 require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
4277 require_once DOL_DOCUMENT_ROOT . '/product/dynamic_price/class/price_parser.class.php';
4278 require_once DOL_DOCUMENT_ROOT . '/core/lib/product.lib.php';
4279
4280 $num = $this->db->num_rows($result);
4281
4282 $events = array();
4283
4284 if (!$forcecombo) {
4285 include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
4286 $out .= ajax_combobox($htmlname, $events, getDolGlobalInt("PRODUIT_USE_SEARCH_TO_SELECT"));
4287 }
4288
4289 $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" id="' . $htmlname . '">';
4290
4291 $textifempty = '';
4292 // Do not use textifempty = ' ' or '&nbsp;' here, or search on key will search on ' key'.
4293 //if (!empty($conf->use_javascript_ajax) || $forcecombo) $textifempty='';
4294 if (getDolGlobalString('PRODUIT_USE_SEARCH_TO_SELECT')) {
4295 if ($showempty && !is_numeric($showempty)) {
4296 $textifempty = $langs->trans($showempty);
4297 } else {
4298 $textifempty .= $langs->trans("All");
4299 }
4300 } else {
4301 if ($showempty && !is_numeric($showempty)) {
4302 $textifempty = $langs->trans($showempty);
4303 }
4304 }
4305 if ($showempty) {
4306 $out .= '<option value="-1" selected>' . ($textifempty ? $textifempty : '&nbsp;') . '</option>';
4307 }
4308
4309 $i = 0;
4310 while ($num && $i < $num) {
4311 $opt = '';
4312 $optJson = array();
4313 $objp = $this->db->fetch_object($result);
4314
4315 if ((getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY') || getDolGlobalString('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
4316 $sql = "SELECT rowid, quantity, price, unitprice, remise_percent, remise, price_base_type";
4317 $sql .= " FROM " . $this->db->prefix() . "product_price_by_qty";
4318 $sql .= " WHERE fk_product_price = " . ((int) $objp->price_rowid);
4319 $sql .= " ORDER BY quantity ASC";
4320
4321 dol_syslog(get_class($this) . "::select_produits_list search prices by qty", LOG_DEBUG);
4322 $result2 = $this->db->query($sql);
4323 if ($result2) {
4324 $nb_prices = $this->db->num_rows($result2);
4325 $j = 0;
4326 while ($nb_prices && $j < $nb_prices) {
4327 $objp2 = $this->db->fetch_object($result2);
4328
4329 $objp->price_by_qty_rowid = $objp2->rowid;
4330 $objp->price_by_qty_price_base_type = $objp2->price_base_type;
4331 $objp->price_by_qty_quantity = $objp2->quantity;
4332 $objp->price_by_qty_unitprice = $objp2->unitprice;
4333 $objp->price_by_qty_remise_percent = $objp2->remise_percent;
4334 // For backward compatibility
4335 $objp->quantity = $objp2->quantity;
4336 $objp->price = $objp2->price;
4337 $objp->unitprice = $objp2->unitprice;
4338 $objp->remise_percent = $objp2->remise_percent;
4339
4340 //$objp->tva_tx is not overwritten by $objp2 value
4341 //$objp->default_vat_code is not overwritten by $objp2 value
4342
4343 $this->constructProductListOption($objp, $opt, $optJson, 0, $selected, $hidepriceinlabel, $filterkey);
4344 '@phan-var-force array{key:string,value:string,label:string,label2:string,desc:string,type:string,price_ht:string,price_ttc:string,price_ht_locale:string,price_ttc_locale:string,pricebasetype:string,tva_tx:string,default_vat_code:string,qty:string,discount:string,duration_value:string,duration_unit:string,pbq:string,labeltrans:string,desctrans:string,ref_customer:string} $optJson';
4345 $j++;
4346
4347 // Add new entry
4348 // "key" value of json key array is used by jQuery automatically as selected value
4349 // "label" value of json key array is used by jQuery automatically as text for combo box
4350 $out .= $opt;
4351 array_push($outarray, $optJson);
4352 }
4353 }
4354 } else {
4355 if (isModEnabled('dynamicprices') && !empty($objp->fk_price_expression)) {
4356 $price_product = new Product($this->db);
4357 $price_product->fetch($objp->rowid, '', '', '1');
4358
4359 require_once DOL_DOCUMENT_ROOT . '/product/dynamic_price/class/price_parser.class.php';
4360 $priceparser = new PriceParser($this->db);
4361 $price_result = $priceparser->parseProduct($price_product);
4362 if ($price_result >= 0) {
4363 $objp->price = $price_result;
4364 $objp->unitprice = $price_result;
4365 //Calculate the VAT
4366 $objp->price_ttc = (float) price2num($objp->price) * (1 + ($objp->tva_tx / 100));
4367 $objp->price_ttc = price2num($objp->price_ttc, 'MU');
4368 }
4369 }
4370 if (getDolGlobalInt('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES') && !empty($objp->custprice)) {
4371 $price_level = '';
4372 }
4373 $this->constructProductListOption($objp, $opt, $optJson, $price_level, $selected, $hidepriceinlabel, $filterkey);
4374 // Add new entry
4375 // "key" value of json key array is used by jQuery automatically as selected value
4376 // "label" value of json key array is used by jQuery automatically as text for combo box
4377 $out .= $opt;
4378 array_push($outarray, $optJson);
4379 }
4380
4381 $i++;
4382 }
4383
4384 $out .= '</select>';
4385
4386 $this->db->free($result);
4387
4388 if (empty($outputmode)) {
4389 return $out;
4390 }
4391
4392 return $outarray;
4393 } else {
4394 dol_print_error($this->db);
4395 }
4396
4397 return '';
4398 }
4399
4415 protected function constructProductListOption(&$objp, &$opt, &$optJson, $price_level, $selected, $hidepriceinlabel = 0, $filterkey = '', $novirtualstock = 0)
4416 {
4417 global $langs, $conf, $user;
4418 global $hookmanager;
4419
4420 $outkey = '';
4421 $outval = '';
4422 $outref = '';
4423 $outlabel = '';
4424 $outlabel_translated = '';
4425 $outdesc = '';
4426 $outdesc_translated = '';
4427 $outbarcode = '';
4428 $outorigin = '';
4429 $outtype = '';
4430 $outprice_ht = '';
4431 $outprice_ttc = '';
4432 $outpricebasetype = '';
4433 $outtva_tx = '';
4434 $outdefault_vat_code = '';
4435 $outqty = 1;
4436 $outdiscount = '0';
4437
4438 $maxlengtharticle = getDolGlobalInt('PRODUCT_MAX_LENGTH_COMBO', 48);
4439
4440 $productlabel = $objp->label;
4441 if (!empty($objp->label_translated)) {
4442 $productlabel = $objp->label_translated;
4443 }
4444 $label = $productlabel;
4445 if (!empty($filterkey) && $filterkey != '') {
4446 $label = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', $label, 1);
4447 }
4448
4449 $outkey = $objp->rowid;
4450 $outref = $objp->ref;
4451 $outrefcust = empty($objp->custref) ? '' : $objp->custref;
4452 $outlabel = $objp->label;
4453 $outdesc = $objp->description;
4454 if (getDolGlobalInt('MAIN_MULTILANGS')) {
4455 $outlabel_translated = $objp->label_translated;
4456 $outdesc_translated = $objp->description_translated;
4457 }
4458 $outbarcode = $objp->barcode;
4459 $outorigin = $objp->fk_country;
4460 $outpbq = empty($objp->price_by_qty_rowid) ? '' : $objp->price_by_qty_rowid;
4461
4462 $outtype = $objp->fk_product_type;
4463 $outdurationvalue = $outtype == Product::TYPE_SERVICE ? substr($objp->duration, 0, dol_strlen($objp->duration) - 1) : '';
4464 $outdurationunit = $outtype == Product::TYPE_SERVICE ? substr($objp->duration, -1) : '';
4465
4466 if ($outorigin && getDolGlobalString('PRODUCT_SHOW_ORIGIN_IN_COMBO')) {
4467 require_once DOL_DOCUMENT_ROOT . '/core/lib/company.lib.php';
4468 }
4469
4470 // Units
4471 $outvalUnits = '';
4472 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
4473 if (!empty($objp->unit_short)) {
4474 $outvalUnits .= ' - ' . $objp->unit_short;
4475 }
4476 }
4477 if (getDolGlobalString('PRODUCT_SHOW_DIMENSIONS_IN_COMBO')) {
4478 if (!empty($objp->weight) && $objp->weight_units !== null) {
4479 $unitToShow = showDimensionInBestUnit($objp->weight, $objp->weight_units, 'weight', $langs);
4480 $outvalUnits .= ' - ' . $unitToShow;
4481 }
4482 if ((!empty($objp->length) || !empty($objp->width) || !empty($objp->height)) && $objp->length_units !== null) {
4483 $unitToShow = $objp->length . ' x ' . $objp->width . ' x ' . $objp->height . ' ' . measuringUnitString(0, 'size', $objp->length_units);
4484 $outvalUnits .= ' - ' . $unitToShow;
4485 }
4486 if (!empty($objp->surface) && $objp->surface_units !== null) {
4487 $unitToShow = showDimensionInBestUnit($objp->surface, $objp->surface_units, 'surface', $langs);
4488 $outvalUnits .= ' - ' . $unitToShow;
4489 }
4490 if (!empty($objp->volume) && $objp->volume_units !== null) {
4491 $unitToShow = showDimensionInBestUnit($objp->volume, $objp->volume_units, 'volume', $langs);
4492 $outvalUnits .= ' - ' . $unitToShow;
4493 }
4494 }
4495 if ($outdurationvalue && $outdurationunit) {
4496 $da = array(
4497 'h' => $langs->trans('Hour'),
4498 'd' => $langs->trans('Day'),
4499 'w' => $langs->trans('Week'),
4500 'm' => $langs->trans('Month'),
4501 'y' => $langs->trans('Year')
4502 );
4503 if (isset($da[$outdurationunit])) {
4504 $outvalUnits .= ' - ' . $outdurationvalue . ' ' . $langs->transnoentities($da[$outdurationunit] . ($outdurationvalue > 1 ? 's' : ''));
4505 }
4506 }
4507
4508 // Set stocktag (stock too low or not or unknown)
4509 $stocktag = 0;
4510 if (isModEnabled('stock') && isset($objp->stock) && ($objp->fk_product_type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) {
4511 if ($user->hasRight('stock', 'lire')) {
4512 if ($objp->stock > 0) {
4513 $stocktag = 1;
4514 } elseif ($objp->stock <= 0) {
4515 $stocktag = -1;
4516 }
4517 }
4518 }
4519
4520 // Set full plain label for the native <option> text. Select2 uses this text
4521 // as its search corpus, while data-html below keeps the visible label short.
4522 $labeltosearch = '';
4523 $labeltosearch .= $objp->ref;
4524 if (!empty($objp->custref)) {
4525 $labeltosearch .= ' (' . $objp->custref . ')';
4526 }
4527 if ($outbarcode) {
4528 $labeltosearch .= ' (' . $outbarcode . ')';
4529 }
4530 $labeltosearch .= ' - ' . $productlabel;
4531 if ($outorigin && getDolGlobalString('PRODUCT_SHOW_ORIGIN_IN_COMBO')) {
4532 $labeltosearch .= ' (' . getCountry($outorigin, '1') . ')';
4533 }
4534
4535 // Set $labltoshowhtml
4536 $labeltoshowhtml = '';
4537 $labeltoshowhtml .= $objp->ref;
4538 if (!empty($objp->custref)) {
4539 $labeltoshowhtml .= ' (' . $objp->custref . ')';
4540 }
4541 if (!empty($filterkey) && $filterkey != '') {
4542 $labeltoshowhtml = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', $labeltoshowhtml, 1);
4543 }
4544 if ($outbarcode) {
4545 $labeltoshowhtml .= ' (' . $outbarcode . ')';
4546 }
4547 $labeltoshowhtml .= ' - ' . dol_trunc($label, $maxlengtharticle);
4548 if ($outorigin && getDolGlobalString('PRODUCT_SHOW_ORIGIN_IN_COMBO')) {
4549 $labeltoshowhtml .= ' (' . getCountry($outorigin, '1') . ')';
4550 }
4551
4552 // Stock
4553 $labeltoshowstock = '';
4554 $labeltoshowhtmlstock = '';
4555 if (isModEnabled('stock') && isset($objp->stock) && ($objp->fk_product_type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) {
4556 if ($user->hasRight('stock', 'lire')) {
4557 $labeltoshowstock .= ' - ' . $langs->trans("Stock") . ': ' . price(price2num($objp->stock, 'MS'), 0, $langs, 0, 0);
4558
4559 if ($objp->stock > 0) {
4560 $labeltoshowhtmlstock .= ' - <span class="product_line_stock_ok">';
4561 } elseif ($objp->stock <= 0) {
4562 $labeltoshowhtmlstock .= ' - <span class="product_line_stock_too_low">';
4563 }
4564 $labeltoshowhtmlstock .= $langs->transnoentities("Stock") . ': ' . price(price2num($objp->stock, 'MS'), 0, $langs, 0, 0);
4565 $labeltoshowhtmlstock .= '</span>';
4566
4567 if (empty($novirtualstock) && getDolGlobalString('STOCK_SHOW_VIRTUAL_STOCK_IN_PRODUCTS_COMBO')) { // Warning, this option may slow down combo list generation
4568 $langs->load("stocks");
4569
4570 $tmpproduct = new Product($this->db);
4571 $tmpproduct->fetch($objp->rowid, '', '', '', 1, 1, 1); // Load product without lang and prices arrays (we just need to make ->virtual_stock() after)
4572 $tmpproduct->load_virtual_stock();
4573 $virtualstock = $tmpproduct->stock_theorique;
4574
4575 $labeltoshowstock .= ' - ' . $langs->trans("VirtualStock") . ':' . $virtualstock;
4576
4577 $labeltoshowhtmlstock .= ' - ' . $langs->transnoentities("VirtualStock") . ':';
4578 if ($virtualstock > 0) {
4579 $labeltoshowhtmlstock .= '<span class="product_line_stock_ok">';
4580 } elseif ($virtualstock <= 0) {
4581 $labeltoshowhtmlstock .= '<span class="product_line_stock_too_low">';
4582 }
4583 $labeltoshowhtmlstock .= $virtualstock;
4584 $labeltoshowhtmlstock .= '</span>';
4585
4586 unset($tmpproduct);
4587 }
4588 }
4589 }
4590
4591 // Price
4592 $found = 0;
4593 $labeltoshowprice = '';
4594 $labeltoshowhtmlprice = '';
4595 // If we need a particular price level (from 1 to n)
4596 if (empty($hidepriceinlabel) && $price_level >= 1 && (getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES'))) {
4597 $sql = "SELECT price, price_ttc, price_base_type, tva_tx, default_vat_code";
4598 $sql .= " FROM " . $this->db->prefix() . "product_price";
4599 $sql .= " WHERE fk_product = " . ((int) $objp->rowid);
4600 $sql .= " AND entity IN (" . getEntity('productprice') . ")";
4601 $sql .= " AND price_level = " . ((int) $price_level);
4602 $sql .= " ORDER BY date_price DESC, rowid DESC"; // Warning DESC must be both on date_price and rowid.
4603 $sql .= " LIMIT 1";
4604
4605 dol_syslog(get_class($this) . '::constructProductListOption search price for product ' . $objp->rowid . ' AND level ' . $price_level, LOG_DEBUG);
4606 $result2 = $this->db->query($sql);
4607 if ($result2) {
4608 $objp2 = $this->db->fetch_object($result2);
4609 if ($objp2) {
4610 $found = 1;
4611 if ($objp2->price_base_type == 'HT') {
4612 $labeltoshowprice .= ' - ' . price($objp2->price, 1, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->trans("HT");
4613 $labeltoshowhtmlprice .= ' - ' . price($objp2->price, 0, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->transnoentities("HT");
4614 } else {
4615 $labeltoshowprice .= ' - ' . price($objp2->price_ttc, 1, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->trans("TTC");
4616 $labeltoshowhtmlprice .= ' - ' . price($objp2->price_ttc, 0, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->transnoentities("TTC");
4617 }
4618 $outprice_ht = price($objp2->price);
4619 $outprice_ttc = price($objp2->price_ttc);
4620 $outpricebasetype = $objp2->price_base_type;
4621 if (getDolGlobalString('PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL')) { // using this option is a bug. kept for backward compatibility
4622 $outtva_tx = $objp2->tva_tx; // We use the vat rate on line of multiprice
4623 $outdefault_vat_code = $objp2->default_vat_code; // We use the vat code on line of multiprice
4624 } else {
4625 $outtva_tx = $objp->tva_tx; // We use the vat rate of product, not the one on line of multiprice
4626 $outdefault_vat_code = $objp->default_vat_code; // We use the vat code or product, not the one on line of multiprice
4627 }
4628 }
4629 } else {
4630 dol_print_error($this->db);
4631 }
4632 }
4633
4634 // Price by quantity
4635 if (empty($hidepriceinlabel) && !empty($objp->quantity) && $objp->quantity >= 1 && (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES'))) {
4636 $found = 1;
4637 $outqty = $objp->quantity;
4638 $outdiscount = $objp->remise_percent;
4639 if ($objp->quantity == 1) {
4640 $labeltoshowprice .= ' - ' . price($objp->unitprice, 1, $langs, 0, 0, -1, $conf->currency) . "/";
4641 $labeltoshowhtmlprice .= ' - ' . price($objp->unitprice, 0, $langs, 0, 0, -1, $conf->currency) . "/";
4642 $labeltoshowprice .= $langs->trans("Unit"); // Do not use strtolower because it breaks utf8 encoding
4643 $labeltoshowhtmlprice .= $langs->transnoentities("Unit");
4644 } else {
4645 $labeltoshowprice .= ' - ' . price($objp->price, 1, $langs, 0, 0, -1, $conf->currency) . "/" . $objp->quantity;
4646 $labeltoshowhtmlprice .= ' - ' . price($objp->price, 0, $langs, 0, 0, -1, $conf->currency) . "/" . $objp->quantity;
4647 $labeltoshowprice .= $langs->trans("Units"); // Do not use strtolower because it breaks utf8 encoding
4648 $labeltoshowhtmlprice .= $langs->transnoentities("Units");
4649 }
4650
4651 $outprice_ht = price($objp->unitprice);
4652 $outprice_ttc = price($objp->unitprice * (1 + ($objp->tva_tx / 100)));
4653 $outpricebasetype = $objp->price_base_type;
4654 $outtva_tx = $objp->tva_tx; // This value is the value on product when constructProductListOption is called by select_produits_list even if other field $objp-> are from table price_by_qty
4655 $outdefault_vat_code = $objp->default_vat_code; // This value is the value on product when constructProductListOption is called by select_produits_list even if other field $objp-> are from table price_by_qty
4656 }
4657 if (empty($hidepriceinlabel) && !empty($objp->quantity) && $objp->quantity >= 1) {
4658 $labeltoshowprice .= " (" . price($objp->unitprice, 1, $langs, 0, 0, -1, $conf->currency) . "/" . $langs->trans("Unit") . ")"; // Do not use strtolower because it breaks utf8 encoding
4659 $labeltoshowhtmlprice .= " (" . price($objp->unitprice, 0, $langs, 0, 0, -1, $conf->currency) . "/" . $langs->transnoentities("Unit") . ")"; // Do not use strtolower because it breaks utf8 encoding
4660 }
4661 if (empty($hidepriceinlabel) && !empty($objp->remise_percent) && $objp->remise_percent >= 1) {
4662 $labeltoshowprice .= " - " . $langs->trans("Discount") . " : " . vatrate($objp->remise_percent) . ' %';
4663 $labeltoshowhtmlprice .= " - " . $langs->transnoentities("Discount") . " : " . vatrate($objp->remise_percent) . ' %';
4664 }
4665
4666 // Price by customer
4667 if (empty($hidepriceinlabel) && (getDolGlobalString('PRODUIT_CUSTOMER_PRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES'))) {
4668 if (!empty($objp->idprodcustprice)) {
4669 $found = 1;
4670
4671 if ($objp->custprice_base_type == 'HT') {
4672 $labeltoshowprice .= ' - ' . price($objp->custprice, 1, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->trans("HT");
4673 $labeltoshowhtmlprice .= ' - ' . price($objp->custprice, 0, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->transnoentities("HT");
4674 } else {
4675 $labeltoshowprice .= ' - ' . price($objp->custprice_ttc, 1, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->trans("TTC");
4676 $labeltoshowhtmlprice .= ' - ' . price($objp->custprice_ttc, 0, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->transnoentities("TTC");
4677 }
4678
4679 $outprice_ht = price($objp->custprice);
4680 $outprice_ttc = price($objp->custprice_ttc);
4681 $outpricebasetype = $objp->custprice_base_type;
4682 $outtva_tx = $objp->custtva_tx;
4683 $outdefault_vat_code = $objp->custdefault_vat_code;
4684 $outdiscount = $objp->custdiscount_percent;
4685 }
4686 }
4687
4688 // If level no defined or multiprice not found, we used the default price
4689 if (empty($hidepriceinlabel) && !$found) {
4690 if ($objp->price_base_type == 'HT') {
4691 $labeltoshowprice .= ' - ' . price($objp->price, 1, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->trans("HT");
4692 $labeltoshowhtmlprice .= ' - ' . price($objp->price, 0, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->transnoentities("HT");
4693 } else {
4694 $labeltoshowprice .= ' - ' . price($objp->price_ttc, 1, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->trans("TTC");
4695 $labeltoshowhtmlprice .= ' - ' . price($objp->price_ttc, 0, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->transnoentities("TTC");
4696 }
4697 $outprice_ht = price($objp->price);
4698 $outprice_ttc = price($objp->price_ttc);
4699 $outpricebasetype = $objp->price_base_type;
4700 $outtva_tx = $objp->tva_tx;
4701 $outdefault_vat_code = $objp->default_vat_code;
4702 }
4703
4704 $optiontext = $labeltosearch.$outvalUnits.$labeltoshowprice.$labeltoshowstock;
4705 $optionhtml = $labeltoshowhtml.$outvalUnits.$labeltoshowhtmlprice.$labeltoshowhtmlstock;
4706 $optionhtmlforattribute = dol_escape_htmltag($optionhtml, 0, 0, '', 0, 1);
4707
4708 // Build options
4709 $opt = '<option value="' . $objp->rowid . '"';
4710 $opt .= ($objp->rowid == $selected) ? ' selected' : '';
4711 if (!empty($objp->price_by_qty_rowid) && $objp->price_by_qty_rowid > 0) {
4712 $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 . '"';
4713 }
4714 if (getDolGlobalString('PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE')) {
4715 $opt .= ' data-labeltrans="' . dol_escape_htmltag($outlabel_translated, 0, 0, '', 0, 1) . '"';
4716 $opt .= ' data-desctrans="' . dol_escape_htmltag($outdesc_translated) . '"';
4717 }
4718
4719 if ($stocktag == 1) {
4720 $opt .= ' class="product_line_stock_ok" data-html="'.dolPrintHTMLForAttribute($labeltoshowhtml, 0, array('strong')).dolPrintHTMLForAttribute($outvalUnits).$labeltoshowhtmlprice.dolPrintHTMLForAttribute($labeltoshowhtmlstock).'"';
4721 //$opt .= ' class="product_line_stock_ok"';
4722 }
4723 if ($stocktag == -1) {
4724 $opt .= ' class="product_line_stock_too_low" data-html="'.dolPrintHTMLForAttribute($labeltoshowhtml, 0, array('strong')).dolPrintHTMLForAttribute($outvalUnits).$labeltoshowhtmlprice.dolPrintHTMLForAttribute($labeltoshowhtmlstock).'"';
4725 //$opt .= ' class="product_line_stock_too_low"';
4726 }
4727 $opt .= ' data-html="'.$optionhtmlforattribute.'" data-select-html="'.$optionhtmlforattribute.'"';
4728
4729 $opt .= '>';
4730
4731 // Ref, barcode, country
4732 $opt .= dol_escape_htmltag($optiontext, 0, 0, '', 0, 1);
4733 $outval .= $labeltoshowhtml;
4734
4735 // Units
4736 $outval .= $outvalUnits;
4737
4738 // Price
4739 $outval .= $labeltoshowhtmlprice;
4740
4741 // Stock
4742 $outval .= $labeltoshowhtmlstock;
4743
4744
4745 $parameters = array('objp' => $objp);
4746 $reshook = $hookmanager->executeHooks('constructProductListOption', $parameters); // Note that $action and $object may have been modified by hook
4747 if (empty($reshook)) {
4748 $opt .= $hookmanager->resPrint;
4749 } else {
4750 $opt = $hookmanager->resPrint;
4751 }
4752
4753 $opt .= "</option>\n";
4754 $optJson = array(
4755 'key' => $outkey,
4756 'value' => $outref,
4757 'label' => $outval,
4758 'label2' => $outlabel,
4759 'desc' => $outdesc,
4760 'type' => $outtype,
4761 'price_ht' => price2num($outprice_ht),
4762 'price_ttc' => price2num($outprice_ttc),
4763 'price_ht_locale' => price(price2num($outprice_ht)),
4764 'price_ttc_locale' => price(price2num($outprice_ttc)),
4765 'pricebasetype' => $outpricebasetype,
4766 'tva_tx' => $outtva_tx,
4767 'default_vat_code' => $outdefault_vat_code,
4768 'qty' => $outqty,
4769 'discount' => $outdiscount,
4770 'duration_value' => $outdurationvalue,
4771 'duration_unit' => $outdurationunit,
4772 'pbq' => $outpbq,
4773 'labeltrans' => $outlabel_translated,
4774 'desctrans' => $outdesc_translated,
4775 'ref_customer' => $outrefcust
4776 );
4777 }
4778
4779 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4780
4797 public function select_produits_fournisseurs($socid, $selected = '', $htmlname = 'productid', $filtertype = '', $notused = '', $ajaxoptions = array(), $hidelabel = 0, $alsoproductwithnosupplierprice = 0, $morecss = '', $placeholder = '', $nooutput = 0)
4798 {
4799 // phpcs:enable
4800 global $langs, $conf;
4801 global $price_level, $status, $finished;
4802
4803 if (!isset($status)) {
4804 $status = 1;
4805 }
4806
4807 $selected_input_value = '';
4808 if (!empty($conf->use_javascript_ajax) && getDolGlobalString('PRODUIT_USE_SEARCH_TO_SELECT')) {
4809 $regtmpsel = array();
4810 if ((int) $selected > 0) {
4811 require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
4812 $producttmpselect = new Product($this->db);
4813 $producttmpselect->fetch((int) $selected);
4814 $selected_input_value = $producttmpselect->ref;
4815 unset($producttmpselect);
4816 } elseif (preg_match('/^idprod_([0-9]+)$/', (string) $selected, $regtmpsel)) {
4817 // Preselect when a product without supplier price was just created ('idprod_ID' value, used by backtopage of creation popup)
4818 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
4819 $producttmpselect = new Product($this->db);
4820 $producttmpselect->fetch((int) $regtmpsel[1]);
4821 $selected_input_value = $producttmpselect->ref;
4822 unset($producttmpselect);
4823 }
4824
4825 // mode=2 means suppliers products
4826 $urloption = ($socid > 0 ? 'socid=' . $socid . '&' : '') . 'htmlname=' . $htmlname . '&outjson=1&price_level=' . $price_level . '&type=' . $filtertype . '&mode=2&status=' . $status . '&finished=' . $finished . '&alsoproductwithnosupplierprice=' . $alsoproductwithnosupplierprice;
4827
4828 $s = ($hidelabel ? '' : $langs->trans("RefOrLabel") . ' : ') . '<input type="text" class="'.$morecss.'" name="search_' . $htmlname . '" id="search_' . $htmlname . '" value="' . $selected_input_value . '"' . ($placeholder ? ' placeholder="' . $placeholder . '"' : '') . ' spellcheck="false">';
4829
4830 $s .= ajax_autocompleter($selected, $htmlname, DOL_URL_ROOT . '/product/ajax/products.php', $urloption, getDolGlobalInt('PRODUIT_USE_SEARCH_TO_SELECT'), 0, $ajaxoptions);
4831 } else {
4832 $s = $this->select_produits_fournisseurs_list($socid, $selected, $htmlname, $filtertype, $notused, '', $status, 0, 0, $alsoproductwithnosupplierprice, $morecss, getDolGlobalInt('SUPPLIER_SHOW_STOCK_IN_PRODUCTS_COMBO'), $placeholder);
4833 }
4834
4835 if ($nooutput) {
4836 return $s;
4837 } else {
4838 print $s;
4839 }
4840 }
4841
4842 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4843
4862 public function select_produits_fournisseurs_list($socid, $selected = '', $htmlname = 'productid', $filtertype = '', $notused = '', $filterkey = '', $statut = -1, $outputmode = 0, $limit = 100, $alsoproductwithnosupplierprice = 0, $morecss = '', $showstockinlist = 0, $placeholder = '')
4863 {
4864 // phpcs:enable
4865 global $langs, $conf, $user;
4866 global $hookmanager;
4867
4868 $out = '';
4869 $outarray = array();
4870
4871 $maxlengtharticle = getDolGlobalInt('PRODUCT_MAX_LENGTH_COMBO', 48);
4872
4873 $langs->load('stocks');
4874 // Units
4875 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
4876 $langs->load('other');
4877 }
4878
4879 $sql = "SELECT p.rowid, p.ref, p.label, p.price, p.duration, p.fk_product_type, p.stock, p.tva_tx as tva_tx_sale, p.default_vat_code as default_vat_code_sale,";
4880 $sql .= " pfp.ref_fourn, pfp.rowid as idprodfournprice, pfp.price as fprice, pfp.quantity, pfp.remise_percent, pfp.remise, pfp.unitprice, pfp.barcode";
4881 $sql .= ", pfp.multicurrency_code, pfp.multicurrency_unitprice";
4882 $sql .= ", pfp.fk_supplier_price_expression, pfp.fk_product, pfp.tva_tx, pfp.default_vat_code, pfp.fk_soc, s.nom as name";
4883 $sql .= ", pfp.supplier_reputation";
4884 // if we use supplier description of the products
4885 if (getDolGlobalString('PRODUIT_FOURN_TEXTS')) {
4886 $sql .= ", pfp.desc_fourn as description";
4887 } else {
4888 $sql .= ", p.description";
4889 }
4890 // Units
4891 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
4892 $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";
4893 }
4894
4895 // Add select from hooks
4896 $parameters = [];
4897 $reshook = $hookmanager->executeHooks('selectSuppliersProductsListSelect', $parameters); // Note that $action and $object may have been modified by hook
4898 $sql .= $hookmanager->resPrint;
4899
4900 $sql .= " FROM " . $this->db->prefix() . "product as p";
4901
4902 // Add join from hooks
4903 $parameters = [];
4904 $reshook = $hookmanager->executeHooks('selectSuppliersProductsListFrom', $parameters); // Note that $action and $object may have been modified by hook
4905 $sql .= $hookmanager->resPrint;
4906
4907 $sql .= " LEFT JOIN " . $this->db->prefix() . "product_fournisseur_price as pfp ON ( p.rowid = pfp.fk_product AND pfp.entity IN (" . getEntity('product') . ") )";
4908 if ($socid > 0) {
4909 $sql .= " AND pfp.fk_soc = " . ((int) $socid);
4910 }
4911 $sql .= " LEFT JOIN " . $this->db->prefix() . "societe as s ON pfp.fk_soc = s.rowid";
4912 // Units
4913 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
4914 $sql .= " LEFT JOIN " . $this->db->prefix() . "c_units u ON u.rowid = p.fk_unit";
4915 }
4916 $sql .= " WHERE p.entity IN (" . getEntity('product') . ")";
4917 if ($statut != -1) {
4918 $sql .= " AND p.tobuy = " . ((int) $statut);
4919 }
4920 if (strval($filtertype) != '') {
4921 $sql .= " AND p.fk_product_type = " . ((int) $filtertype);
4922 }
4923
4924 // Add where from hooks
4925 $parameters = array();
4926 $reshook = $hookmanager->executeHooks('selectSuppliersProductsListWhere', $parameters); // Note that $action and $object may have been modified by hook
4927 $sql .= $hookmanager->resPrint;
4928 // Add criteria on ref/label
4929 if ($filterkey != '') {
4930 $sql .= ' AND (';
4931 $prefix = getDolGlobalString('PRODUCT_DONOTSEARCH_ANYWHERE') ? '' : '%'; // Can use index if PRODUCT_DONOTSEARCH_ANYWHERE is on
4932 // For natural search
4933 $search_crit = explode(' ', $filterkey);
4934 $i = 0;
4935 if (count($search_crit) > 1) {
4936 $sql .= "(";
4937 }
4938 foreach ($search_crit as $crit) {
4939 if ($i > 0) {
4940 $sql .= " AND ";
4941 }
4942 $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) . "%'";
4943 if (getDolGlobalString('PRODUIT_FOURN_TEXTS')) {
4944 $sql .= " OR pfp.desc_fourn LIKE '" . $this->db->escape($prefix . $crit) . "%'";
4945 }
4946 $sql .= ")";
4947 $i++;
4948 }
4949 if (count($search_crit) > 1) {
4950 $sql .= ")";
4951 }
4952 if (isModEnabled('barcode')) {
4953 $sql .= " OR p.barcode LIKE '" . $this->db->escape($prefix . $filterkey) . "%'";
4954 $sql .= " OR pfp.barcode LIKE '" . $this->db->escape($prefix . $filterkey) . "%'";
4955 }
4956 $sql .= ')';
4957 }
4958 $sql .= " ORDER BY pfp.ref_fourn DESC, pfp.quantity ASC";
4959 $sql .= $this->db->plimit($limit, 0);
4960
4961 // Build output string
4962
4963 dol_syslog(get_class($this) . "::select_produits_fournisseurs_list", LOG_DEBUG);
4964 $result = $this->db->query($sql);
4965 if ($result) {
4966 require_once DOL_DOCUMENT_ROOT . '/product/dynamic_price/class/price_parser.class.php';
4967 require_once DOL_DOCUMENT_ROOT . '/core/lib/product.lib.php';
4968
4969 $num = $this->db->num_rows($result);
4970
4971 //$out.='<select class="flat" id="select'.$htmlname.'" name="'.$htmlname.'">'; // remove select to have id same with combo and ajax
4972 $out .= '<select class="flat ' . ($morecss ? ' ' . $morecss : '') . '" id="' . $htmlname . '" name="' . $htmlname . '">';
4973 if (!$selected) {
4974 $out .= '<option value="-1" selected>' . ($placeholder ? $placeholder : '&nbsp;') . '</option>';
4975 } else {
4976 $out .= '<option value="-1">' . ($placeholder ? $placeholder : '&nbsp;') . '</option>';
4977 }
4978
4979 $i = 0;
4980 while ($i < $num) {
4981 $objp = $this->db->fetch_object($result);
4982
4983 if (is_null($objp->idprodfournprice)) {
4984 // There is no supplier price found, we will use the vat rate for sale
4985 $objp->tva_tx = $objp->tva_tx_sale;
4986 $objp->default_vat_code = $objp->default_vat_code_sale;
4987 }
4988
4989 $outkey = $objp->idprodfournprice; // id in table of price
4990 if (!$outkey && $alsoproductwithnosupplierprice) {
4991 $outkey = 'idprod_' . $objp->rowid; // id of product
4992 }
4993
4994 $outref = $objp->ref;
4995 $outbarcode = $objp->barcode;
4996 $outqty = 1;
4997 $outdiscount = 0;
4998 $outtype = $objp->fk_product_type;
4999 $outdurationvalue = $outtype == Product::TYPE_SERVICE ? substr($objp->duration, 0, dol_strlen($objp->duration) - 1) : '';
5000 $outdurationunit = $outtype == Product::TYPE_SERVICE ? substr($objp->duration, -1) : '';
5001
5002 // Units
5003 $outvalUnits = '';
5004 if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
5005 if (!empty($objp->unit_short)) {
5006 $outvalUnits .= ' - ' . $objp->unit_short;
5007 }
5008 if (!empty($objp->weight) && $objp->weight_units !== null) {
5009 $unitToShow = showDimensionInBestUnit($objp->weight, $objp->weight_units, 'weight', $langs);
5010 $outvalUnits .= ' - ' . $unitToShow;
5011 }
5012 if ((!empty($objp->length) || !empty($objp->width) || !empty($objp->height)) && $objp->length_units !== null) {
5013 $unitToShow = $objp->length . ' x ' . $objp->width . ' x ' . $objp->height . ' ' . measuringUnitString(0, 'size', $objp->length_units);
5014 $outvalUnits .= ' - ' . $unitToShow;
5015 }
5016 if (!empty($objp->surface) && $objp->surface_units !== null) {
5017 $unitToShow = showDimensionInBestUnit($objp->surface, $objp->surface_units, 'surface', $langs);
5018 $outvalUnits .= ' - ' . $unitToShow;
5019 }
5020 if (!empty($objp->volume) && $objp->volume_units !== null) {
5021 $unitToShow = showDimensionInBestUnit($objp->volume, $objp->volume_units, 'volume', $langs);
5022 $outvalUnits .= ' - ' . $unitToShow;
5023 }
5024 if ($outdurationvalue && $outdurationunit) {
5025 $da = array(
5026 'h' => $langs->trans('Hour'),
5027 'd' => $langs->trans('Day'),
5028 'w' => $langs->trans('Week'),
5029 'm' => $langs->trans('Month'),
5030 'y' => $langs->trans('Year')
5031 );
5032 if (isset($da[$outdurationunit])) {
5033 $outvalUnits .= ' - ' . $outdurationvalue . ' ' . $langs->transnoentities($da[$outdurationunit] . ($outdurationvalue > 1 ? 's' : ''));
5034 }
5035 }
5036 }
5037
5038 $objRef = $objp->ref;
5039 if ($filterkey && $filterkey != '') {
5040 $objRef = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', $objRef, 1);
5041 }
5042 $objRefFourn = $objp->ref_fourn;
5043 if ($filterkey && $filterkey != '') {
5044 $objRefFourn = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', $objRefFourn, 1);
5045 }
5046 $label = $objp->label;
5047 if ($filterkey && $filterkey != '') {
5048 $label = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', $label, 1);
5049 }
5050
5051 switch ($objp->fk_product_type) {
5053 $picto = 'product';
5054 break;
5056 $picto = 'service';
5057 break;
5058 default:
5059 $picto = '';
5060 break;
5061 }
5062
5063 if (empty($picto)) {
5064 $optlabel = '';
5065 } else {
5066 $optlabel = img_object('', $picto, 'class="paddingright classfortooltip"', 0, 0, 1);
5067 }
5068
5069 $optlabel .= $objp->ref;
5070 if (!empty($objp->idprodfournprice) && ($objp->ref != $objp->ref_fourn)) {
5071 $optlabel .= ' <span class="opacitymedium">(' . $objp->ref_fourn . ')</span>';
5072 }
5073 if (isModEnabled('barcode') && !empty($objp->barcode)) {
5074 $optlabel .= ' (' . $outbarcode . ')';
5075 }
5076 $optlabel .= ' - ' . dol_trunc($label, $maxlengtharticle);
5077
5078 $outvallabel = $objRef;
5079 if (!empty($objp->idprodfournprice) && ($objp->ref != $objp->ref_fourn)) {
5080 $outvallabel .= ' (' . $objRefFourn . ')';
5081 }
5082 if (isModEnabled('barcode') && !empty($objp->barcode)) {
5083 $outvallabel .= ' (' . $outbarcode . ')';
5084 }
5085 $outvallabel .= ' - ' . dol_trunc($label, $maxlengtharticle);
5086
5087 $outsearchlabel = implode(' ', array_filter(array(
5088 (string) $objp->ref,
5089 (string) $objp->ref_fourn,
5090 (string) $objp->barcode,
5091 (string) $objp->label,
5092 dol_string_nohtmltag((string) $objp->description)
5093 ), function (string $value): bool {
5094 return $value !== '';
5095 }));
5096
5097 // Units
5098 $optlabel .= $outvalUnits;
5099 $outvallabel .= $outvalUnits;
5100
5101 if (!empty($objp->idprodfournprice)) {
5102 $outqty = $objp->quantity;
5103 $outdiscount = $objp->remise_percent;
5104 if (isModEnabled('dynamicprices') && !empty($objp->fk_supplier_price_expression)) {
5105 $prod_supplier = new ProductFournisseur($this->db);
5106 $prod_supplier->product_fourn_price_id = $objp->idprodfournprice;
5107 $prod_supplier->id = $objp->fk_product;
5108 $prod_supplier->fourn_qty = $objp->quantity;
5109 $prod_supplier->fourn_tva_tx = $objp->tva_tx;
5110 $prod_supplier->fk_supplier_price_expression = $objp->fk_supplier_price_expression;
5111
5112 require_once DOL_DOCUMENT_ROOT . '/product/dynamic_price/class/price_parser.class.php';
5113 $priceparser = new PriceParser($this->db);
5114 $price_result = $priceparser->parseProductSupplier($prod_supplier);
5115 if ($price_result >= 0) {
5116 $objp->fprice = $price_result;
5117 if ($objp->quantity >= 1) {
5118 $objp->unitprice = $objp->fprice / $objp->quantity; // Replace dynamically unitprice
5119 }
5120 }
5121 }
5122 if ($objp->quantity == 1) {
5123 $optlabel .= ' - ' . price($objp->fprice * (getDolGlobalString('DISPLAY_DISCOUNTED_SUPPLIER_PRICE') ? (1 - $objp->remise_percent / 100) : 1), 1, $langs, 0, 0, -1, $conf->currency) . "/";
5124 $outvallabel .= ' - ' . price($objp->fprice * (getDolGlobalString('DISPLAY_DISCOUNTED_SUPPLIER_PRICE') ? (1 - $objp->remise_percent / 100) : 1), 0, $langs, 0, 0, -1, $conf->currency) . "/";
5125 $optlabel .= $langs->trans("Unit"); // Do not use strtolower because it breaks utf8 encoding
5126 $outvallabel .= $langs->transnoentities("Unit");
5127 } else {
5128 $optlabel .= ' - ' . price($objp->fprice * (getDolGlobalString('DISPLAY_DISCOUNTED_SUPPLIER_PRICE') ? (1 - $objp->remise_percent / 100) : 1), 1, $langs, 0, 0, -1, $conf->currency) . "/" . $objp->quantity;
5129 $outvallabel .= ' - ' . price($objp->fprice * (getDolGlobalString('DISPLAY_DISCOUNTED_SUPPLIER_PRICE') ? (1 - $objp->remise_percent / 100) : 1), 0, $langs, 0, 0, -1, $conf->currency) . "/" . $objp->quantity;
5130 $optlabel .= ' ' . $langs->trans("Units"); // Do not use strtolower because it breaks utf8 encoding
5131 $outvallabel .= ' ' . $langs->transnoentities("Units");
5132 }
5133
5134 if ($objp->quantity != 1) {
5135 $optlabel .= " (" . price($objp->unitprice * (getDolGlobalString('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
5136 $outvallabel .= " (" . price($objp->unitprice * (getDolGlobalString('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
5137 }
5138 if ($objp->remise_percent >= 1) {
5139 $optlabel .= " - " . $langs->trans("Discount") . " : " . vatrate($objp->remise_percent) . ' %';
5140 $outvallabel .= " - " . $langs->transnoentities("Discount") . " : " . vatrate($objp->remise_percent) . ' %';
5141 }
5142 if ($objp->duration) {
5143 $optlabel .= " - " . $objp->duration;
5144 $outvallabel .= " - " . $objp->duration;
5145 }
5146 if (!$socid) {
5147 $optlabel .= " - " . dol_trunc($objp->name, 8);
5148 $outvallabel .= " - " . dol_trunc($objp->name, 8);
5149 }
5150 if ($objp->supplier_reputation) {
5151 //TODO dictionary
5152 $reputations = array('' => $langs->trans('Standard'), 'FAVORITE' => $langs->trans('Favorite'), 'NOTTHGOOD' => $langs->trans('NotTheGoodQualitySupplier'), 'DONOTORDER' => $langs->trans('DoNotOrderThisProductToThisSupplier'));
5153
5154 $optlabel .= " - " . $reputations[$objp->supplier_reputation];
5155 $outvallabel .= " - " . $reputations[$objp->supplier_reputation];
5156 }
5157 } else {
5158 $optlabel .= " - <span class='opacitymedium'>" . $langs->trans("NoPriceDefinedForThisSupplier") . '</span>';
5159 $outvallabel .= ' - ' . $langs->transnoentities("NoPriceDefinedForThisSupplier");
5160 }
5161
5162 if (isModEnabled('stock') && $showstockinlist && isset($objp->stock) && ($objp->fk_product_type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) {
5163 $novirtualstock = ($showstockinlist == 2);
5164
5165 if ($user->hasRight('stock', 'lire')) {
5166 $outvallabel .= ' - ' . $langs->trans("Stock") . ': ' . price(price2num($objp->stock, 'MS'), 0, $langs, 0, 0);
5167
5168 if ($objp->stock > 0) {
5169 $optlabel .= ' - <span class="product_line_stock_ok">';
5170 } elseif ($objp->stock <= 0) {
5171 $optlabel .= ' - <span class="product_line_stock_too_low">';
5172 }
5173 $optlabel .= $langs->transnoentities("Stock") . ':' . price(price2num($objp->stock, 'MS'));
5174 $optlabel .= '</span>';
5175 if (empty($novirtualstock) && getDolGlobalString('STOCK_SHOW_VIRTUAL_STOCK_IN_PRODUCTS_COMBO')) { // Warning, this option may slow down combo list generation
5176 $langs->load("stocks");
5177
5178 $tmpproduct = new Product($this->db);
5179 $tmpproduct->fetch($objp->rowid, '', '', '', 1, 1, 1); // Load product without lang and prices arrays (we just need to make ->virtual_stock() after)
5180 $tmpproduct->load_virtual_stock();
5181 $virtualstock = $tmpproduct->stock_theorique;
5182
5183 $outvallabel .= ' - ' . $langs->trans("VirtualStock") . ':' . $virtualstock;
5184
5185 $optlabel .= ' - ' . $langs->transnoentities("VirtualStock") . ':';
5186 if ($virtualstock > 0) {
5187 $optlabel .= '<span class="product_line_stock_ok">';
5188 } elseif ($virtualstock <= 0) {
5189 $optlabel .= '<span class="product_line_stock_too_low">';
5190 }
5191 $optlabel .= $virtualstock;
5192 $optlabel .= '</span>';
5193
5194 unset($tmpproduct);
5195 }
5196 }
5197 }
5198
5199 $optstart = '<option value="' . $outkey . '"';
5200 if ($selected && preg_match('/^idprod_/', (string) $selected) && (string) $selected == 'idprod_'.$objp->rowid) {
5201 $optstart .= ' selected';
5202 } elseif ($selected && (string) $selected == (string) $objp->idprodfournprice) {
5203 $optstart .= ' selected';
5204 }
5205
5206 if (empty($objp->idprodfournprice) && empty($alsoproductwithnosupplierprice)) {
5207 $optstart .= ' disabled';
5208 }
5209
5210 if (!empty($objp->idprodfournprice) && $objp->idprodfournprice > 0) {
5211 $optstart .= ' data-product-id="' . dol_escape_htmltag($objp->rowid) . '"';
5212 $optstart .= ' data-price-id="' . dol_escape_htmltag($objp->idprodfournprice) . '"';
5213 $optstart .= ' data-qty="' . dol_escape_htmltag($objp->quantity) . '"';
5214 $optstart .= ' data-up="' . dol_escape_htmltag(price2num($objp->unitprice)) . '"'; // the price with numeric international format
5215 $optstart .= ' data-up-locale="' . dol_escape_htmltag(price($objp->unitprice)) . '"'; // the price formatted in user language
5216 $optstart .= ' data-discount="' . dol_escape_htmltag((string) $outdiscount) . '"';
5217 $optstart .= ' data-tvatx="' . dol_escape_htmltag(price2num($objp->tva_tx)) . '"'; // the rate with numeric international format
5218 $optstart .= ' data-tvatx-formated="' . dol_escape_htmltag(price($objp->tva_tx, 0, $langs, 1, -1, 2)) . '"'; // the rate formatted in user language
5219 $optstart .= ' data-default-vat-code="' . dol_escape_htmltag($objp->default_vat_code) . '"';
5220 $optstart .= ' data-supplier-ref="' . dol_escape_htmltag($objp->ref_fourn) . '"';
5221 if (isModEnabled('multicurrency')) {
5222 $optstart .= ' data-multicurrency-code="' . dol_escape_htmltag($objp->multicurrency_code) . '"';
5223 $optstart .= ' data-multicurrency-unitprice="' . dol_escape_htmltag(price2num($objp->multicurrency_unitprice)) . '"'; // the price with numeric international format
5224 }
5225 }
5226 $optstart .= ' data-description="' . dol_escape_htmltag($objp->description, 0, 1) . '"';
5227 $optstart .= ' data-search="' . dol_escape_htmltag($outsearchlabel) . '"';
5228
5229 // set $parameters to call hook
5230 $outarrayentry = array(
5231 'key' => $outkey,
5232 'value' => $outref,
5233 'label' => $outvallabel,
5234 'labelhtml' => $optlabel,
5235 'qty' => $outqty,
5236 'price_qty_ht' => price2num($objp->fprice, 'MU'), // Keep higher resolution for price for the min qty
5237 'price_unit_ht' => price2num($objp->unitprice, 'MU'), // This is used to fill the Unit Price
5238 'price_ht' => price2num($objp->unitprice, 'MU'), // This is used to fill the Unit Price (for compatibility)
5239 'tva_tx_formated' => price($objp->tva_tx, 0, $langs, 1, -1, 2),
5240 'tva_tx' => price2num($objp->tva_tx),
5241 'default_vat_code' => $objp->default_vat_code,
5242 'supplier_ref' => $objp->ref_fourn,
5243 'discount' => $outdiscount,
5244 'type' => $outtype,
5245 'duration_value' => $outdurationvalue,
5246 'duration_unit' => $outdurationunit,
5247 'disabled' => empty($objp->idprodfournprice),
5248 'description' => $objp->description
5249 );
5250 if (isModEnabled('multicurrency')) {
5251 $outarrayentry['multicurrency_code'] = $objp->multicurrency_code;
5252 $outarrayentry['multicurrency_unitprice'] = price2num($objp->multicurrency_unitprice, 'MU');
5253 }
5254 $parameters = array(
5255 'objp' => &$objp,
5256 'optstart' => &$optstart,
5257 'optlabel' => &$optlabel,
5258 'outvallabel' => &$outvallabel,
5259 'outarrayentry' => &$outarrayentry,
5260 'fk_soc' => $socid
5261 );
5262 $reshook = $hookmanager->executeHooks('selectProduitsFournisseurListOption', $parameters, $this);
5263
5264
5265 // Add new entry
5266 // "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
5267 // "label" value of json key array is used by jQuery automatically as text for combo box
5268 $out .= $optstart . ' data-html="' . dol_escape_htmltag($optlabel) . '">' . $optlabel . "</option>\n";
5269 $outarraypush = array(
5270 'key' => $outkey,
5271 'value' => $outref,
5272 'label' => $outvallabel,
5273 'labelhtml' => $optlabel,
5274 'qty' => $outqty,
5275 'price_qty_ht' => price2num($objp->fprice, 'MU'), // Keep higher resolution for price for the min qty
5276 'price_qty_ht_locale' => price($objp->fprice),
5277 'price_unit_ht' => price2num($objp->unitprice, 'MU'), // This is used to fill the Unit Price
5278 'price_unit_ht_locale' => price($objp->unitprice),
5279 'price_ht' => price2num($objp->unitprice, 'MU'), // This is used to fill the Unit Price (for compatibility)
5280 'tva_tx_formated' => price($objp->tva_tx),
5281 'tva_tx' => price2num($objp->tva_tx),
5282 'default_vat_code' => $objp->default_vat_code,
5283 'supplier_ref' => $objp->ref_fourn,
5284 'discount' => $outdiscount,
5285 'type' => $outtype,
5286 'duration_value' => $outdurationvalue,
5287 'duration_unit' => $outdurationunit,
5288 'disabled' => empty($objp->idprodfournprice),
5289 'description' => $objp->description
5290 );
5291 if (isModEnabled('multicurrency')) {
5292 $outarraypush['multicurrency_code'] = $objp->multicurrency_code;
5293 $outarraypush['multicurrency_unitprice'] = price2num($objp->multicurrency_unitprice, 'MU');
5294 }
5295 array_push($outarray, $outarraypush);
5296
5297 // Example of var_dump $outarray
5298 // array(1) {[0]=>array(6) {[key"]=>string(1) "2" ["value"]=>string(3) "ppp"
5299 // ["label"]=>string(76) "ppp (<strong>f</strong>ff2) - ppp - 20,00 Euros/unit (20,00 Euros/unit)"
5300 // ["qty"]=>string(1) "1" ["discount"]=>string(1) "0" ["disabled"]=>bool(false)
5301 //}
5302 //var_dump($outval); var_dump(utf8_check($outval)); var_dump(json_encode($outval));
5303 //$outval=array('label'=>'ppp (<strong>f</strong>ff2) - ppp - 20,00 Euros/ Unit (20,00 Euros/unit)');
5304 //var_dump($outval); var_dump(utf8_check($outval)); var_dump(json_encode($outval));
5305
5306 $i++;
5307 }
5308 $out .= '</select>';
5309
5310 $this->db->free($result);
5311
5312 include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
5313 $out .= ajax_combobox($htmlname);
5314 } else {
5315 dol_print_error($this->db);
5316 }
5317
5318 if (empty($outputmode)) {
5319 return $out;
5320 }
5321 return $outarray;
5322 }
5323
5324 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5325
5334 public function select_product_fourn_price($productid, $htmlname = 'productfournpriceid', $selected_supplier = 0)
5335 {
5336 // phpcs:enable
5337 global $langs, $conf;
5338
5339 $langs->load('stocks');
5340
5341 $sql = "SELECT p.rowid, p.ref, p.label, p.price, p.duration, pfp.fk_soc,";
5342 $sql .= " pfp.ref_fourn, pfp.rowid as idprodfournprice, pfp.price as fprice, pfp.remise_percent, pfp.quantity, pfp.unitprice,";
5343 $sql .= " pfp.fk_supplier_price_expression, pfp.fk_product, pfp.tva_tx, s.nom as name";
5344 $sql .= " FROM " . $this->db->prefix() . "product as p";
5345 $sql .= " LEFT JOIN " . $this->db->prefix() . "product_fournisseur_price as pfp ON p.rowid = pfp.fk_product";
5346 $sql .= " LEFT JOIN " . $this->db->prefix() . "societe as s ON pfp.fk_soc = s.rowid";
5347 $sql .= " WHERE pfp.entity IN (" . getEntity('productsupplierprice') . ")";
5348 $sql .= " AND p.tobuy = 1";
5349 $sql .= " AND s.fournisseur = 1";
5350 $sql .= " AND p.rowid = " . ((int) $productid);
5351 if (!getDolGlobalString('PRODUCT_BEST_SUPPLIER_PRICE_PRESELECTED')) {
5352 $sql .= " ORDER BY s.nom, pfp.ref_fourn DESC";
5353 } else {
5354 $sql .= " ORDER BY pfp.unitprice - pfp.unitprice * pfp.remise_percent / 100 ASC";
5355 }
5356
5357 dol_syslog(get_class($this) . "::select_product_fourn_price", LOG_DEBUG);
5358 $result = $this->db->query($sql);
5359
5360 if ($result) {
5361 $num = $this->db->num_rows($result);
5362
5363 $form = '<select class="flat" id="select_' . $htmlname . '" name="' . $htmlname . '">';
5364
5365 if (!$num) {
5366 $form .= '<option value="0">-- ' . $langs->trans("NoSupplierPriceDefinedForThisProduct") . ' --</option>';
5367 } else {
5368 require_once DOL_DOCUMENT_ROOT . '/product/dynamic_price/class/price_parser.class.php';
5369 $form .= '<option value="0">&nbsp;</option>';
5370
5371 $i = 0;
5372 while ($i < $num) {
5373 $objp = $this->db->fetch_object($result);
5374
5375 $opt = '<option value="' . $objp->idprodfournprice . '"';
5376 //if there is only one supplier, preselect it
5377 if ($num == 1 || ($selected_supplier > 0 && $objp->fk_soc == $selected_supplier) || ($i == 0 && getDolGlobalString('PRODUCT_BEST_SUPPLIER_PRICE_PRESELECTED'))) {
5378 $opt .= ' selected';
5379 }
5380 $opt .= '>' . $objp->name . ' - ' . $objp->ref_fourn . ' - ';
5381
5382 if (isModEnabled('dynamicprices') && !empty($objp->fk_supplier_price_expression)) {
5383 $prod_supplier = new ProductFournisseur($this->db);
5384 $prod_supplier->product_fourn_price_id = $objp->idprodfournprice;
5385 $prod_supplier->id = $productid;
5386 $prod_supplier->fourn_qty = $objp->quantity;
5387 $prod_supplier->fourn_tva_tx = $objp->tva_tx;
5388 $prod_supplier->fk_supplier_price_expression = $objp->fk_supplier_price_expression;
5389
5390 require_once DOL_DOCUMENT_ROOT . '/product/dynamic_price/class/price_parser.class.php';
5391 $priceparser = new PriceParser($this->db);
5392 $price_result = $priceparser->parseProductSupplier($prod_supplier);
5393 if ($price_result >= 0) {
5394 $objp->fprice = $price_result;
5395 if ($objp->quantity >= 1) {
5396 $objp->unitprice = $objp->fprice / $objp->quantity;
5397 }
5398 }
5399 }
5400 if ($objp->quantity == 1) {
5401 $opt .= price($objp->fprice * (getDolGlobalString('DISPLAY_DISCOUNTED_SUPPLIER_PRICE') ? (1 - $objp->remise_percent / 100) : 1), 1, $langs, 0, 0, -1, $conf->currency) . "/";
5402 }
5403
5404 $opt .= $objp->quantity . ' ';
5405
5406 if ($objp->quantity == 1) {
5407 $opt .= $langs->trans("Unit");
5408 } else {
5409 $opt .= $langs->trans("Units");
5410 }
5411 if ($objp->quantity > 1) {
5412 $opt .= " - ";
5413 $opt .= price($objp->unitprice * (getDolGlobalString('DISPLAY_DISCOUNTED_SUPPLIER_PRICE') ? (1 - $objp->remise_percent / 100) : 1), 1, $langs, 0, 0, -1, $conf->currency) . "/" . $langs->trans("Unit");
5414 }
5415 if ($objp->duration) {
5416 $opt .= " - " . $objp->duration;
5417 }
5418 $opt .= "</option>\n";
5419
5420 $form .= $opt;
5421 $i++;
5422 }
5423 }
5424
5425 $form .= '</select>';
5426 $this->db->free($result);
5427 return $form;
5428 } else {
5429 dol_print_error($this->db);
5430 return '';
5431 }
5432 }
5433
5434
5435 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5442 {
5443 // phpcs:enable
5444 global $langs, $hookmanager;
5445
5446 $num = count($this->cache_conditions_paiements);
5447 if ($num > 0) {
5448 return 0; // Cache already loaded
5449 }
5450
5451 dol_syslog(__METHOD__, LOG_DEBUG);
5452
5453 $this->cache_conditions_paiements = array();
5454
5455 $sql = "SELECT rowid, code, libelle as label, deposit_percent, entity";
5456 $sql .= " FROM " . $this->db->prefix() . 'c_payment_term';
5457 $sql .= " WHERE entity IN (" . getEntity('c_payment_term') . ")";
5458 $sql .= " AND active > 0";
5459 $sql .= " ORDER BY sortorder";
5460
5461 $resql = $this->db->query($sql);
5462 if ($resql) {
5463 $num = $this->db->num_rows($resql);
5464 $i = 0;
5465 while ($i < $num) {
5466 $obj = $this->db->fetch_object($resql);
5467
5468 // If a translation exists, we use it, otherwise, we take the label by default
5469 $label = ($langs->trans("PaymentConditionShort" . $obj->code) != "PaymentConditionShort" . $obj->code ? $langs->trans("PaymentConditionShort" . $obj->code) : ($obj->label != '-' ? $obj->label : ''));
5470
5471 $this->cache_conditions_paiements[$obj->rowid]['code'] = (string) $obj->code;
5472 $this->cache_conditions_paiements[$obj->rowid]['label'] = (string) $label;
5473 $this->cache_conditions_paiements[$obj->rowid]['deposit_percent'] = (string) $obj->deposit_percent;
5474 $this->cache_conditions_paiements[$obj->rowid]['entity'] = (int) $obj->entity;
5475
5476 $i++;
5477 }
5478
5479 $parameters = array('dictionary' => 'paymentterm');
5480 $reshook = $hookmanager->executeHooks('loadDictionaryCache', $parameters, $this); // Note that $action and $object may have been modified by hook
5481 if (empty($reshook)) {
5482 if (is_array($hookmanager->resArray) && count($hookmanager->resArray)) {
5483 $this->cache_conditions_paiements = array_merge($this->cache_conditions_paiements, $hookmanager->resArray);
5484 }
5485 } else {
5486 $this->cache_conditions_paiements = $hookmanager->resArray;
5487 }
5488
5489 //$this->cache_conditions_paiements=dol_sort_array($this->cache_conditions_paiements, 'label', 'asc', 0, 0, 1); // We use the field sortorder of table
5490
5491 return $num;
5492 } else {
5493 dol_print_error($this->db);
5494 return -1;
5495 }
5496 }
5497
5498 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5499
5506 {
5507 // phpcs:enable
5508 $factureRec = new FactureRec($this->db);
5509
5510 $this->cache_rule_for_lines_dates = $factureRec->fields['rule_for_lines_dates']['arrayofkeyval'];
5511
5512 if (empty($this->cache_rule_for_lines_dates)) {
5513 return -1;
5514 }
5515
5516 return 1;
5517 }
5518
5519 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5520
5526 public function load_cache_availability()
5527 {
5528 // phpcs:enable
5529 global $langs;
5530
5531 $num = count($this->cache_availability); // TODO Use $conf->cache['availability'] instead of $this->cache_availability
5532 if ($num > 0) {
5533 return 0; // Cache already loaded
5534 }
5535
5536 dol_syslog(__METHOD__, LOG_DEBUG);
5537
5538 $this->cache_availability = array();
5539
5540 $langs->load('propal');
5541
5542 $sql = "SELECT rowid, code, label, position";
5543 $sql .= " FROM " . $this->db->prefix() . 'c_availability';
5544 $sql .= " WHERE active > 0";
5545
5546 $resql = $this->db->query($sql);
5547 if ($resql) {
5548 $num = $this->db->num_rows($resql);
5549 $i = 0;
5550 while ($i < $num) {
5551 $obj = $this->db->fetch_object($resql);
5552
5553 // If a translation exists, we use is, otherwise, we take the label by default
5554 $label = ($langs->trans("AvailabilityType" . $obj->code) != "AvailabilityType" . $obj->code ? $langs->trans("AvailabilityType" . $obj->code) : ($obj->label != '-' ? $obj->label : ''));
5555 $this->cache_availability[$obj->rowid]['code'] = (string) $obj->code;
5556 $this->cache_availability[$obj->rowid]['label'] = (string) $label;
5557 $this->cache_availability[$obj->rowid]['position'] = (int) $obj->position;
5558 $i++;
5559 }
5560
5561 // @phan-suppress-next-line PhanTypeMismatchProperty PhanTypeMismatchDimFetch
5562 $this->cache_availability = dol_sort_array($this->cache_availability, 'position', 'asc', 0, 0, 1);
5563
5564 return $num;
5565 } else {
5566 dol_print_error($this->db);
5567 return -1;
5568 }
5569 }
5570
5582 public function selectAvailabilityDelay($selected = '', $htmlname = 'availid', $filtertype = '', $addempty = 0, $morecss = '', $noouput = 0)
5583 {
5584 global $langs, $user;
5585
5586 $this->load_cache_availability();
5587
5588 dol_syslog(__METHOD__ . " selected=" . $selected . ", htmlname=" . $htmlname, LOG_DEBUG);
5589
5590 $out = '<select id="' . $htmlname . '" class="flat' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '">';
5591 if ($addempty) {
5592 $out .= '<option value="-1">'.(is_numeric($addempty) ? '&nbsp;' : $langs->trans($addempty)).'</option>';
5593 }
5594 foreach ($this->cache_availability as $id => $arrayavailability) {
5595 if ($selected == $id) {
5596 $out .= '<option value="' . $id . '" selected>';
5597 } else {
5598 $out .= '<option value="' . $id . '">';
5599 }
5600 $out .= dol_escape_htmltag($arrayavailability['label']);
5601 $out .= '</option>';
5602 }
5603 $out .= '</select>';
5604 if ($user->admin) {
5605 $out .= info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
5606 }
5607 $out .= ajax_combobox($htmlname);
5608
5609 if ($noouput) {
5610 return $out;
5611 } else {
5612 print $out;
5613 return '';
5614 }
5615 }
5616
5622 public function loadCacheInputReason()
5623 {
5624 global $langs;
5625
5626 $num = count($this->cache_demand_reason); // TODO Use $conf->cache['input_reason'] instead of $this->cache_demand_reason
5627 if ($num > 0) {
5628 return 0; // Cache already loaded
5629 }
5630
5631 $sql = "SELECT rowid, code, label";
5632 $sql .= " FROM " . $this->db->prefix() . 'c_input_reason';
5633 $sql .= " WHERE active > 0";
5634
5635 $resql = $this->db->query($sql);
5636 if ($resql) {
5637 $num = $this->db->num_rows($resql);
5638 $i = 0;
5640 $tmparray = array();
5641 while ($i < $num) {
5642 $obj = $this->db->fetch_object($resql);
5643
5644 // If a translation exists, we use is, otherwise, we take the label by default
5645 $label = ($obj->label != '-' ? (string) $obj->label : '');
5646 if ($langs->trans("DemandReasonType" . $obj->code) != "DemandReasonType" . $obj->code) {
5647 $label = $langs->trans("DemandReasonType" . $obj->code); // So translation key DemandReasonTypeSRC_XXX will work
5648 }
5649 if ($langs->trans($obj->code) != $obj->code) {
5650 $label = $langs->trans($obj->code); // So translation key SRC_XXX will work
5651 }
5652
5653 $tmparray[(int) $obj->rowid]
5654 = array(
5655 'id' => (int) $obj->rowid,
5656 'code' => (string) $obj->code,
5657 'label' => $label,
5658 );
5659 $i++;
5660 }
5661
5662 $this->cache_demand_reason = dol_sort_array($tmparray, 'label', 'asc', 0, 0, 1);
5663
5664 unset($tmparray);
5665 return $num;
5666 } else {
5667 dol_print_error($this->db);
5668 return -1;
5669 }
5670 }
5671
5684 public function selectInputReason($selected = '', $htmlname = 'demandreasonid', $exclude = '', $addempty = 0, $morecss = '', $notooltip = 0)
5685 {
5686 global $langs, $user;
5687
5688 $this->loadCacheInputReason();
5689
5690 print '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" id="select_' . $htmlname . '" name="' . $htmlname . '">';
5691 if ($addempty) {
5692 print '<option value="0"' . (empty($selected) ? ' selected' : '') . '>&nbsp;</option>';
5693 }
5694 foreach ($this->cache_demand_reason as $id => $arraydemandreason) {
5695 if ($arraydemandreason['code'] == $exclude) {
5696 continue;
5697 }
5698
5699 if ($selected && ($selected == $arraydemandreason['id'] || $selected == $arraydemandreason['code'])) {
5700 print '<option value="' . $arraydemandreason['id'] . '" selected>';
5701 } else {
5702 print '<option value="' . $arraydemandreason['id'] . '">';
5703 }
5704 $label = $arraydemandreason['label']; // Translation of label was already done into the ->loadCacheInputReason
5705 print $langs->trans($label);
5706 print '</option>';
5707 }
5708 print '</select>';
5709 if ($user->admin && empty($notooltip)) {
5710 print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
5711 }
5712 print ajax_combobox('select_' . $htmlname);
5713 }
5714
5715 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5716
5723 {
5724 // phpcs:enable
5725 global $langs, $hookmanager;
5726
5727 $num = count($this->cache_types_paiements); // TODO Use $conf->cache['payment_mode'] instead of $this->cache_types_paiements
5728 if ($num > 0) {
5729 return $num; // Cache already loaded
5730 }
5731
5732 dol_syslog(__METHOD__, LOG_DEBUG);
5733
5734 $this->cache_types_paiements = array();
5735
5736 $sql = "SELECT id, code, libelle as label, type, entity, active";
5737 $sql .= " FROM " . $this->db->prefix() . "c_paiement";
5738 $sql .= " WHERE entity IN (" . getEntity('c_paiement') . ")";
5739
5740 $resql = $this->db->query($sql);
5741 if ($resql) {
5742 $num = $this->db->num_rows($resql);
5743 $i = 0;
5744 while ($i < $num) {
5745 $obj = $this->db->fetch_object($resql);
5746
5747 // If a translation exists, we use is, otherwise, we take the label by default
5748 $label = ($langs->transnoentitiesnoconv("PaymentTypeShort" . $obj->code) != "PaymentTypeShort" . $obj->code ? $langs->transnoentitiesnoconv("PaymentTypeShort" . $obj->code) : ($obj->label != '-' ? $obj->label : ''));
5749 $shortlabel = $label; // TODO
5750
5751 $this->cache_types_paiements[(int) $obj->id]['id'] = (int) $obj->id;
5752 $this->cache_types_paiements[(int) $obj->id]['code'] = (string) $obj->code;
5753 $this->cache_types_paiements[(int) $obj->id]['label'] = (string) $label;
5754 $this->cache_types_paiements[(int) $obj->id]['shortlabel'] = (string) $shortlabel;
5755 $this->cache_types_paiements[(int) $obj->id]['type'] = (int) $obj->type;
5756 $this->cache_types_paiements[(int) $obj->id]['entity'] = (int) $obj->entity;
5757 $this->cache_types_paiements[(int) $obj->id]['active'] = (int) $obj->active;
5758 $i++;
5759 }
5760
5761 $parameters = array('dictionary' => 'paymenttype');
5762 $reshook = $hookmanager->executeHooks('loadDictionaryCache', $parameters, $this); // Note that $action and $object may have been modified by hook
5763 if (empty($reshook)) {
5764 if (is_array($hookmanager->resArray) && count($hookmanager->resArray)) {
5765 $this->cache_types_paiements = array_merge($this->cache_types_paiements, $hookmanager->resArray);
5766 }
5767 } else {
5768 $this->cache_types_paiements = $hookmanager->resArray;
5769 }
5770
5771 $this->cache_types_paiements = dol_sort_array($this->cache_types_paiements, 'label', 'asc', 0, 0, 1);
5772
5773 return $num;
5774 } else {
5775 dol_print_error($this->db);
5776 return -1;
5777 }
5778 }
5779
5780
5781 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5782
5801 public function select_conditions_paiements($selected = 0, $htmlname = 'condid', $filtertype = -1, $addempty = 0, $noinfoadmin = 0, $morecss = '', $deposit_percent = -1, $noprint = 0)
5802 {
5803 // phpcs:enable
5804 $out = $this->getSelectConditionsPaiements($selected, $htmlname, $filtertype, $addempty, $noinfoadmin, $morecss, $deposit_percent);
5805 if (empty($noprint)) {
5806 print $out;
5807 } else {
5808 return $out;
5809 }
5810 }
5811
5812
5829 public function getSelectConditionsPaiements($selected = 0, $htmlname = 'condid', $filtertype = -1, $addempty = 0, $noinfoadmin = 0, $morecss = '', $deposit_percent = -1)
5830 {
5831 global $langs, $user;
5832
5833 $out = '';
5834 dol_syslog(__METHOD__ . " selected=" . $selected . ", htmlname=" . $htmlname, LOG_DEBUG);
5835
5836 $this->load_cache_conditions_paiements();
5837
5838 // Set default value if not already set by caller
5839 if (empty($selected) && strpos($htmlname, 'search_') !== 0 && getDolGlobalInt('MAIN_DEFAULT_PAYMENT_TERM_ID')) {
5840 dol_syslog(__METHOD__ . "Using deprecated option MAIN_DEFAULT_PAYMENT_TERM_ID", LOG_NOTICE);
5841 $selected = getDolGlobalInt('MAIN_DEFAULT_PAYMENT_TERM_ID');
5842 }
5843
5844 $out .= '<select id="' . $htmlname . '" class="flat selectpaymentterms' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '">';
5845 if ($addempty) {
5846 $out .= '<option value="0">&nbsp;</option>';
5847 }
5848
5849 $selectedDepositPercent = null;
5850
5851 foreach ($this->cache_conditions_paiements as $id => $arrayconditions) {
5852 if ($filtertype <= 0 && !empty($arrayconditions['deposit_percent'])) {
5853 continue;
5854 }
5855
5856 if ($selected == $id) {
5857 $selectedDepositPercent = $deposit_percent > 0 ? $deposit_percent : $arrayconditions['deposit_percent'];
5858 $out .= '<option value="' . $id . '" data-deposit_percent="' . $arrayconditions['deposit_percent'] . '" selected>';
5859 } else {
5860 $out .= '<option value="' . $id . '" data-deposit_percent="' . $arrayconditions['deposit_percent'] . '">';
5861 }
5862 $label = $arrayconditions['label'];
5863
5864 if (!empty($arrayconditions['deposit_percent'])) {
5865 $label = str_replace('__DEPOSIT_PERCENT__', $deposit_percent > 0 ? $deposit_percent : $arrayconditions['deposit_percent'], $label);
5866 }
5867
5868 $out .= $label;
5869 $out .= '</option>';
5870 }
5871 $out .= '</select>';
5872 if ($user->admin && empty($noinfoadmin)) {
5873 $out .= info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
5874 }
5875 $out .= ajax_combobox($htmlname);
5876
5877 if ($deposit_percent >= 0) {
5878 $out .= ' <span id="' . $htmlname . '_deposit_percent_container"' . (empty($selectedDepositPercent) ? ' style="display: none"' : '') . '>';
5879 $out .= $langs->trans('DepositPercent') . ' : ';
5880 $out .= '<input id="' . $htmlname . '_deposit_percent" name="' . $htmlname . '_deposit_percent" class="maxwidth50" value="' . $deposit_percent . '" />';
5881 $out .= '</span>';
5882 $out .= '
5883 <script nonce="' . getNonce() . '">
5884 $(document).ready(function () {
5885 let $select = $("#' . $htmlname . '");
5886 let $container = $("#' . $htmlname . '_deposit_percent_container");
5887 let $input = $("#' . $htmlname . '_deposit_percent");
5888
5889 function refreshDepositPercent(isInit) {
5890 let depositPercent = $select.find("option:selected").attr("data-deposit_percent") || "";
5891
5892 if (depositPercent.length > 0) {
5893 $container.show();
5894 // On page load, keep an existing (possibly customized) value; on user change use the payment term default
5895 if (!isInit || !parseFloat($input.val())) {
5896 $input.val(depositPercent);
5897 }
5898 } else {
5899 $container.hide();
5900 }
5901 }
5902
5903 $select.change(function () {
5904 refreshDepositPercent(false);
5905 return true;
5906 });
5907
5908 // Initialize on load so a default payment term with a deposit is reflected without a manual change
5909 refreshDepositPercent(true);
5910 });
5911 </script>';
5912 }
5913
5914 return $out;
5915 }
5916
5917
5926 public function getSelectRuleForLinesDates($selected = '', $htmlname = 'rule_for_lines_dates', $addempty = 0)
5927 {
5928 global $langs;
5929
5930 $out = '';
5931
5932 $this->load_cache_rule_for_lines_dates();
5933
5934 $out .= '<select id="' . $htmlname . '" class="flat selectbillingterm" name="' . $htmlname . '">';
5935 if ($addempty) {
5936 $out .= '<option value="-1">&nbsp;</option>';
5937 }
5938
5939
5940 foreach ($this->cache_rule_for_lines_dates as $rule_for_lines_dates_key => $rule_for_lines_dates_name) {
5941 if ($selected == $rule_for_lines_dates_key) {
5942 $out .= '<option value="' . $rule_for_lines_dates_key . '" selected>';
5943 } else {
5944 $out .= '<option value="' . $rule_for_lines_dates_key . '">';
5945 }
5946
5947 $out .= $langs->trans($rule_for_lines_dates_name);
5948 $out .= '</option>';
5949 }
5950 $out .= '</select>';
5951
5952 $out .= ajax_combobox($htmlname);
5953
5954 return $out;
5955 }
5956
5957
5958 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5959
5976 public function select_types_paiements($selected = '', $htmlname = 'paiementtype', $filtertype = '', $format = 0, $empty = 1, $noadmininfo = 0, $maxlength = 0, $active = 1, $morecss = '', $nooutput = 0)
5977 {
5978 // phpcs:enable
5979 global $langs, $user;
5980
5981 $out = '';
5982
5983 dol_syslog(__METHOD__ . " " . $selected . ", " . $htmlname . ", " . $filtertype . ", " . $format, LOG_DEBUG);
5984
5985 $filterarray = array();
5986 if ($filtertype == 'CRDT') {
5987 $filterarray = array(0, 2, 3);
5988 } elseif ($filtertype == 'DBIT') {
5989 $filterarray = array(1, 2, 3);
5990 } elseif ($filtertype != '' && $filtertype != '-1') {
5991 $filterarray = explode(',', $filtertype);
5992 }
5993
5994 $this->load_cache_types_paiements();
5995
5996 // Set default value if not already set by caller
5997 if (empty($selected) && strpos($htmlname, 'search_') !== 0 && getDolGlobalString('MAIN_DEFAULT_PAYMENT_TYPE_ID')) {
5998 dol_syslog(__METHOD__ . "Using deprecated option MAIN_DEFAULT_PAYMENT_TYPE_ID", LOG_NOTICE);
5999 $selected = getDolGlobalString('MAIN_DEFAULT_PAYMENT_TYPE_ID');
6000 }
6001
6002 $out .= '<select id="select' . $htmlname . '" class="flat selectpaymenttypes' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '">';
6003 if ($empty) {
6004 $out .= '<option value="">&nbsp;</option>';
6005 }
6006 foreach ($this->cache_types_paiements as $id => $arraytypes) {
6007 // If not good status
6008 if ($active >= 0 && $arraytypes['active'] != $active) {
6009 continue;
6010 }
6011
6012 // We skip of the user requested to filter on specific payment methods
6013 if (count($filterarray) && !in_array($arraytypes['type'], $filterarray)) {
6014 continue;
6015 }
6016
6017 // We discard empty lines if showempty is on because an empty line has already been output.
6018 if ($empty && empty($arraytypes['code'])) {
6019 continue;
6020 }
6021
6022 if ($format == 0) {
6023 $out .= '<option value="' . $id . '" data-code="'.$arraytypes['code'].'"';
6024 } elseif ($format == 1) {
6025 $out .= '<option value="' . $arraytypes['code'] . '"';
6026 } elseif ($format == 2) {
6027 $out .= '<option value="' . $arraytypes['code'] . '"';
6028 } elseif ($format == 3) {
6029 $out .= '<option value="' . $id . '"';
6030 }
6031 // Print attribute selected or not
6032 if ($format == 1 || $format == 2) {
6033 if ($selected == $arraytypes['code']) {
6034 $out .= ' selected';
6035 }
6036 } else {
6037 if ($selected == $id) {
6038 $out .= ' selected';
6039 }
6040 }
6041 $out .= '>';
6042 $value = '';
6043 if ($format == 0) {
6044 $value = ($maxlength ? dol_trunc($arraytypes['label'], $maxlength) : $arraytypes['label']);
6045 } elseif ($format == 1) {
6046 $value = $arraytypes['code'];
6047 } elseif ($format == 2) {
6048 $value = ($maxlength ? dol_trunc($arraytypes['label'], $maxlength) : $arraytypes['label']);
6049 } elseif ($format == 3) {
6050 $value = $arraytypes['code'];
6051 }
6052 $out .= $value ? $value : '&nbsp;';
6053 $out .= '</option>';
6054 }
6055 $out .= '</select>';
6056 if ($user->admin && !$noadmininfo) {
6057 $out .= info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
6058 }
6059 $out .= ajax_combobox('select' . $htmlname);
6060
6061 if (empty($nooutput)) {
6062 print $out;
6063 } else {
6064 return $out;
6065 }
6066 }
6067
6068
6077 public function selectPriceBaseType($selected = '', $htmlname = 'price_base_type', $addjscombo = 0)
6078 {
6079 global $langs;
6080
6081 $return = '<select class="flat maxwidth100" id="select_' . $htmlname . '" name="' . $htmlname . '">';
6082 $options = array(
6083 'HT' => $langs->trans("HT"),
6084 'TTC' => $langs->trans("TTC")
6085 );
6086 foreach ($options as $id => $value) {
6087 if ($selected == $id) {
6088 $return .= '<option value="' . $id . '" selected>' . $value;
6089 } else {
6090 $return .= '<option value="' . $id . '">' . $value;
6091 }
6092 $return .= '</option>';
6093 }
6094 $return .= '</select>';
6095 if ($addjscombo) {
6096 $return .= ajax_combobox('select_' . $htmlname);
6097 }
6098
6099 return $return;
6100 }
6101
6102 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6103
6110 {
6111 // phpcs:enable
6112 global $langs;
6113
6114 $num = count($this->cache_transport_mode); // TODO Use $conf->cache['payment_mode'] instead of $this->cache_transport_mode
6115 if ($num > 0) {
6116 return $num; // Cache already loaded
6117 }
6118
6119 dol_syslog(__METHOD__, LOG_DEBUG);
6120
6121 $this->cache_transport_mode = array();
6122
6123 $sql = "SELECT rowid, code, label, active";
6124 $sql .= " FROM " . $this->db->prefix() . "c_transport_mode";
6125 $sql .= " WHERE entity IN (" . getEntity('c_transport_mode') . ")";
6126
6127 $resql = $this->db->query($sql);
6128 if ($resql) {
6129 $num = $this->db->num_rows($resql);
6130 $i = 0;
6131 while ($i < $num) {
6132 $obj = $this->db->fetch_object($resql);
6133
6134 // If traduction exist, we use it else we take the default label
6135 $label = ($langs->transnoentitiesnoconv("PaymentTypeShort" . $obj->code) != "PaymentTypeShort" . $obj->code ? $langs->transnoentitiesnoconv("PaymentTypeShort" . $obj->code) : ($obj->label != '-' ? $obj->label : ''));
6136 $this->cache_transport_mode[(int) $obj->rowid]
6137 = array(
6138 'rowid' => (int) $obj->rowid,
6139 'code' => (string) $obj->code,
6140 'label' => (string) $label,
6141 'active' => (int) $obj->active,
6142 );
6143 $i++;
6144 }
6145
6146 $this->cache_transport_mode = dol_sort_array($this->cache_transport_mode, 'label', 'asc', 0, 0, 1);
6147
6148 return $num;
6149 } else {
6150 dol_print_error($this->db);
6151 return -1;
6152 }
6153 }
6154
6168 public function selectTransportMode($selected = '', $htmlname = 'transportmode', $format = 0, $empty = 1, $noadmininfo = 0, $maxlength = 0, $active = 1, $morecss = '')
6169 {
6170 global $langs, $user;
6171
6172 dol_syslog(__METHOD__ . " " . $selected . ", " . $htmlname . ", " . $format, LOG_DEBUG);
6173
6174 $this->load_cache_transport_mode();
6175
6176 print '<select id="select' . $htmlname . '" class="flat selectmodetransport' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '">';
6177 if ($empty) {
6178 print '<option value="">&nbsp;</option>';
6179 }
6180 foreach ($this->cache_transport_mode as $id => $arraytypes) {
6181 // If not good status
6182 if ($active >= 0 && $arraytypes['active'] != $active) {
6183 continue;
6184 }
6185
6186 // We discard empty line if showempty is on because an empty line has already been output.
6187 if ($empty && empty($arraytypes['code'])) {
6188 continue;
6189 }
6190
6191 if ($format == 0) {
6192 print '<option value="' . $id . '"';
6193 } elseif ($format == 1) {
6194 print '<option value="' . $arraytypes['code'] . '"';
6195 } elseif ($format == 2) {
6196 print '<option value="' . $arraytypes['code'] . '"';
6197 } elseif ($format == 3) {
6198 print '<option value="' . $id . '"';
6199 }
6200 // If text is selected, we compare with code, else with id
6201 if (preg_match('/[a-z]/i', $selected) && $selected == $arraytypes['code']) {
6202 print ' selected';
6203 } elseif ($selected == $id) {
6204 print ' selected';
6205 }
6206 print '>';
6207 $value = '';
6208 if ($format == 0) {
6209 $value = ($maxlength ? dol_trunc($arraytypes['label'], $maxlength) : $arraytypes['label']);
6210 } elseif ($format == 1) {
6211 $value = $arraytypes['code'];
6212 } elseif ($format == 2) {
6213 $value = ($maxlength ? dol_trunc($arraytypes['label'], $maxlength) : $arraytypes['label']);
6214 } elseif ($format == 3) {
6215 $value = $arraytypes['code'];
6216 }
6217 print $value ? $value : '&nbsp;';
6218 print '</option>';
6219 }
6220 print '</select>';
6221
6222 print ajax_combobox("select".$htmlname);
6223
6224 if ($user->admin && !$noadmininfo) {
6225 print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
6226 }
6227 }
6228
6241 public function selectShippingMethod($selected = '', $htmlname = 'shipping_method_id', $filtre = '', $useempty = 0, $moreattrib = '', $noinfoadmin = 0, $morecss = '')
6242 {
6243 global $langs, $user;
6244
6245 $langs->loadLangs(array("admin", "sendings"));
6246
6247 $sql = "SELECT rowid, code, libelle as label";
6248 $sql .= " FROM " . $this->db->prefix() . "c_shipment_mode";
6249 $sql .= " WHERE active > 0";
6250 if ($filtre) {
6251 $sql .= forgeSQLFromUniversalSearchCriteria($filtre);
6252 }
6253 $sql .= " ORDER BY libelle ASC";
6254
6255 dol_syslog(get_class($this) . "::selectShippingMode", LOG_DEBUG);
6256
6257 $result = $this->db->query($sql);
6258 if ($result) {
6259 $num = $this->db->num_rows($result);
6260 $i = 0;
6261 if ($num) {
6262 print '<select id="select' . $htmlname . '" class="flat selectshippingmethod' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '"' . ($moreattrib ? ' ' . $moreattrib : '') . '>';
6263 if ($useempty == 1 || ($useempty == 2 && $num > 1)) {
6264 print '<option value="-1">&nbsp;</option>';
6265 }
6266 while ($i < $num) {
6267 $obj = $this->db->fetch_object($result);
6268 if ($selected == $obj->rowid) {
6269 print '<option value="' . $obj->rowid . '" selected>';
6270 } else {
6271 print '<option value="' . $obj->rowid . '">';
6272 }
6273 print ($langs->trans("SendingMethod" . strtoupper($obj->code)) != "SendingMethod" . strtoupper($obj->code)) ? $langs->trans("SendingMethod" . strtoupper($obj->code)) : $obj->label;
6274 print '</option>';
6275 $i++;
6276 }
6277 print "</select>";
6278 if ($user->admin && empty($noinfoadmin)) {
6279 print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
6280 }
6281
6282 print ajax_combobox('select' . $htmlname);
6283 } else {
6284 print $langs->trans("NoShippingMethodDefined");
6285 }
6286 } else {
6287 dol_print_error($this->db);
6288 }
6289 }
6290
6300 public function formSelectShippingMethod($page, $selected = '', $htmlname = 'shipping_method_id', $addempty = 0)
6301 {
6302 global $langs;
6303
6304 $langs->load("sendings");
6305
6306 if ($htmlname != "none") {
6307 print '<form method="POST" action="' . $page . '">';
6308 print '<input type="hidden" name="action" value="setshippingmethod">';
6309 print '<input type="hidden" name="token" value="' . newToken() . '">';
6310 $this->selectShippingMethod($selected, $htmlname, '', $addempty);
6311 print '<input type="submit" class="button valignmiddle" value="' . $langs->trans("Modify") . '">';
6312 print '</form>';
6313 } else {
6314 if ($selected) {
6315 $code = $langs->getLabelFromKey($this->db, $selected, 'c_shipment_mode', 'rowid', 'code');
6316 print $langs->trans("SendingMethod" . strtoupper($code));
6317 } else {
6318 print "&nbsp;";
6319 }
6320 }
6321 }
6322
6331 public function selectSituationInvoices($selected = '', $socid = 0)
6332 {
6333 global $langs;
6334
6335 $langs->load('bills');
6336
6337 $opt = '';
6338
6339 $sql = "SELECT rowid, ref, situation_cycle_ref, situation_counter, situation_final, fk_soc";
6340 $sql .= ' FROM ' . $this->db->prefix() . 'facture';
6341 $sql .= ' WHERE entity IN (' . getEntity('invoice') . ')';
6342 $sql .= ' AND situation_counter >= 1';
6343 $sql .= ' AND fk_soc = ' . (int) $socid;
6344 $sql .= ' AND type <> 2';
6345 $sql .= ' ORDER by situation_cycle_ref, situation_counter desc';
6346 $resql = $this->db->query($sql);
6347
6348 $nbSituationInvoiceForThirdparty = 0;
6349
6350 if ($resql && $this->db->num_rows($resql) > 0) {
6351 // Last seen cycle
6352 $ref = 0;
6353 while ($obj = $this->db->fetch_object($resql)) {
6354 //Same cycle ?
6355 if ($obj->situation_cycle_ref != $ref) {
6356 // Just seen this cycle
6357 $ref = $obj->situation_cycle_ref;
6358 //not final ?
6359 if ($obj->situation_final != 1) {
6360 //Not prov?
6361 if (substr($obj->ref, 1, 4) != 'PROV') {
6362 $nbSituationInvoiceForThirdparty++;
6363
6364 if ($selected == $obj->rowid) {
6365 $opt .= '<option value="' . $obj->rowid . '" selected>' . $obj->ref . '</option>';
6366 } else {
6367 $opt .= '<option value="' . $obj->rowid . '">' . $obj->ref . '</option>';
6368 }
6369 }
6370 }
6371 }
6372 }
6373 } else {
6374 dol_syslog("Error sql=" . $sql . ", error=" . $this->error, LOG_ERR);
6375 }
6376
6377 if ($nbSituationInvoiceForThirdparty > 0) {
6378 $opt = '<option class="minwidth100" value="" selected>&nbsp;</option>'.$opt;
6379 } else {
6380 $opt = '<option class="minwidth100" value="-1" selected>'.$langs->trans('NoSituations').'</option>';
6381 }
6382
6383 return $opt;
6384 }
6385
6395 public function selectUnits($selected = '', $htmlname = 'units', $showempty = 0, $unit_type = '')
6396 {
6397 global $langs;
6398
6399 $langs->load('products');
6400
6401 $return = '<select class="flat" id="' . $htmlname . '" name="' . $htmlname . '">';
6402
6403 $sql = "SELECT rowid, label, code FROM " . $this->db->prefix() . "c_units";
6404 $sql .= ' WHERE active > 0';
6405 if (!empty($unit_type)) {
6406 $sql .= " AND unit_type = '" . $this->db->escape($unit_type) . "'";
6407 }
6408 $sql .= " ORDER BY sortorder";
6409
6410 $resql = $this->db->query($sql);
6411 if ($resql && $this->db->num_rows($resql) > 0) {
6412 if ($showempty) {
6413 $return .= '<option value="-1"></option>';
6414 }
6415
6416 while ($res = $this->db->fetch_object($resql)) {
6417 $unitLabel = $res->label;
6418 if (!empty($langs->tab_translate['unit' . $res->code])) { // check if Translation is available before
6419 $unitLabel = $langs->trans('unit' . $res->code) != $res->label ? $langs->trans('unit' . $res->code) : $res->label;
6420 }
6421
6422 if ($selected == $res->rowid) {
6423 $return .= '<option value="' . $res->rowid . '" selected>' . $unitLabel . '</option>';
6424 } else {
6425 $return .= '<option value="' . $res->rowid . '">' . $unitLabel . '</option>';
6426 }
6427 }
6428 $return .= '</select>';
6429
6430 $return .= ajax_combobox($htmlname);
6431 }
6432 return $return;
6433 }
6434
6435 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6436
6452 public function select_comptes($selected = '', $htmlname = 'accountid', $status = 0, $filtre = '', $useempty = 0, $moreattrib = '', $showcurrency = 0, $morecss = '', $nooutput = 0, $addentrynone = 0)
6453 {
6454 // phpcs:enable
6455 global $langs;
6456
6457 $out = '';
6458
6459 $langs->loadLangs(array("admin", "banks"));
6460 $num = 0;
6461
6462 $sql = "SELECT rowid, label, bank, clos as status, currency_code";
6463 $sql .= " FROM " . $this->db->prefix() . "bank_account";
6464 $sql .= " WHERE entity IN (" . getEntity('bank_account') . ")";
6465 if ($status != 2) {
6466 $sql .= " AND clos = " . (int) $status;
6467 }
6468 if ($filtre) {
6469 $sql .= forgeSQLFromUniversalSearchCriteria($filtre);
6470 }
6471 $sql .= " ORDER BY label";
6472
6473 dol_syslog(get_class($this) . "::select_comptes", LOG_DEBUG);
6474 $result = $this->db->query($sql);
6475 if ($result) {
6476 $num = $this->db->num_rows($result);
6477 $i = 0;
6478
6479 $out .= '<select id="select' . $htmlname . '" class="flat selectbankaccount' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '"' . ($moreattrib ? ' ' . $moreattrib : '') . '>';
6480
6481 if ($num == 0) {
6482 if ($status == 0) {
6483 $out .= '<option class="opacitymedium" value="-1">' . $langs->trans("NoActiveBankAccountDefined") . '</span>';
6484 } else {
6485 $out .= '<option class="opacitymedium" value="-1">' . $langs->trans("NoBankAccountDefined") . '</span>';
6486 }
6487 } else {
6488 if (!empty($useempty) && !is_numeric($useempty)) {
6489 $out .= '<option value="-1">'.$langs->trans($useempty).'</option>';
6490 } elseif ($useempty == 1 || ($useempty == 2 && $num > 1)) {
6491 $out .= '<option value="-1">&nbsp;</option>';
6492 }
6493 }
6494
6495 while ($i < $num) {
6496 $obj = $this->db->fetch_object($result);
6497
6498 $labeltoshow = trim($obj->label);
6499 $labeltoshowhtml = trim($obj->label);
6500 if ($showcurrency) {
6501 $labeltoshow .= ' (' . $obj->currency_code . ')';
6502 $labeltoshowhtml .= ' <span class="opacitymedium">(' . $obj->currency_code . ')</span>';
6503 }
6504 if ($status == 2 && $obj->status == 1) {
6505 $labeltoshow .= ' (' . $langs->trans("Closed") . ')';
6506 $labeltoshowhtml .= ' <span class="opacitymedium">(' . $langs->trans("Closed") . ')</span>';
6507 }
6508
6509 if ($selected == $obj->rowid || ($useempty == 2 && $num == 1 && empty($selected))) {
6510 $out .= '<option value="' . $obj->rowid . '" data-currency-code="' . $obj->currency_code . '" data-html="'.dolPrintHTMLForAttribute($labeltoshowhtml).'" selected>';
6511 } else {
6512 $out .= '<option value="' . $obj->rowid . '" data-currency-code="' . $obj->currency_code . '" data-html="'.dolPrintHTMLForAttribute($labeltoshowhtml).'">';
6513 }
6514 $out .= $labeltoshow;
6515 $out .= '</option>';
6516 $i++;
6517 }
6518
6519 if (!empty($addentrynone)) {
6520 $out .= '<option value="-2"'.($selected == -2 ? ' selected="selected"' : '').' data-html="'.dolPrintHTMLForAttribute('<span class="opacitymedium">'.$langs->trans("None").'</span>').'">'.$langs->trans("None").'</option>';
6521 }
6522
6523 $out .= "</select>";
6524 $out .= ajax_combobox('select' . $htmlname);
6525 } else {
6526 dol_print_error($this->db);
6527 }
6528
6529 // Output or return
6530 if (empty($nooutput)) {
6531 print $out;
6532 } else {
6533 return $out;
6534 }
6535
6536 return $num;
6537 }
6538
6552 public function selectRib($selected = '', $htmlname = 'ribcompanyid', $filtre = '', $useempty = 0, $moreattrib = '', $showibanbic = 0, $morecss = '', $nooutput = 0)
6553 {
6554 // phpcs:enable
6555 global $langs;
6556
6557 $out = '';
6558
6559 $langs->loadLangs(array("admin", "banks"));
6560 $num = 0;
6561
6562 $sql = "SELECT rowid, label, bank, status, iban_prefix, bic, default_rib";
6563 $sql .= " FROM " . $this->db->prefix() . "societe_rib";
6564 $sql .= " WHERE type = 'ban'";
6565 if ($filtre) {
6566 $sql .= forgeSQLFromUniversalSearchCriteria($filtre);
6567 }
6568 $sql .= " ORDER BY label";
6569 dol_syslog(get_class($this) . "::select_comptes", LOG_DEBUG);
6570 $result = $this->db->query($sql);
6571 if ($result) {
6572 $num = $this->db->num_rows($result);
6573 $i = 0;
6574
6575 $out .= '<select id="select' . $htmlname . '" class="flat selectbankaccount' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '"' . ($moreattrib ? ' ' . $moreattrib : '') . '>';
6576
6577 if ($num == 0) {
6578 $out .= '<option class="opacitymedium" value="-1">' . $langs->trans("NoBankAccountDefined") . '</span>';
6579 } else {
6580 if (!empty($useempty) && !is_numeric($useempty)) {
6581 $out .= '<option value="-1">'.$langs->trans($useempty).'</option>';
6582 } elseif ($useempty == 1 || ($useempty == 2 && $num > 1)) {
6583 $out .= '<option value="-1">&nbsp;</option>';
6584 }
6585 }
6586
6587 while ($i < $num) {
6588 $obj = $this->db->fetch_object($result);
6589 $iban = dolDecrypt($obj->iban_prefix);
6590 if ($selected == $obj->rowid || ($useempty == 2 && $num == 1 && empty($selected))) {
6591 $out .= '<option value="' . $obj->rowid . '" data-iban-prefix="' . $iban . ' data-bic="' . $obj->bic . '" selected>';
6592 } else {
6593 $out .= '<option value="' . $obj->rowid . '" data-iban-prefix="' . $iban . ' data-bic="' . $obj->bic . '">';
6594 }
6595 $out .= trim($obj->label);
6596 if ($showibanbic) {
6597 $out .= ' (' . $iban . '/' .$obj->bic. ')' . ($obj->default_rib ? ' ['.$langs->trans("ByDefault").']' : '');
6598 }
6599 $out .= '</option>';
6600 $i++;
6601 }
6602 $out .= "</select>";
6603 $out .= ajax_combobox('select' . $htmlname);
6604 } else {
6605 dol_print_error($this->db);
6606 }
6607
6608 // Output or return
6609 if (empty($nooutput)) {
6610 print $out;
6611 } else {
6612 return $out;
6613 }
6614
6615 return $num;
6616 }
6617
6629 public function selectEstablishments($selected = '', $htmlname = 'entity', $status = 0, $filtre = '', $useempty = 0, $moreattrib = '')
6630 {
6631 global $langs;
6632
6633 $langs->load("admin");
6634 $num = 0;
6635
6636 $sql = "SELECT rowid, name, fk_country, status, entity";
6637 $sql .= " FROM " . $this->db->prefix() . "establishment";
6638 $sql .= " WHERE 1=1";
6639 if ($status != 2) {
6640 $sql .= " AND status = " . (int) $status;
6641 }
6642 if ($filtre) {
6643 $sql .= forgeSQLFromUniversalSearchCriteria($filtre);
6644 }
6645 $sql .= " ORDER BY name";
6646
6647 dol_syslog(get_class($this) . "::select_establishment", LOG_DEBUG);
6648 $result = $this->db->query($sql);
6649 if ($result) {
6650 $num = $this->db->num_rows($result);
6651 $i = 0;
6652 if ($num) {
6653 print '<select id="select' . $htmlname . '" class="flat selectestablishment" name="' . $htmlname . '"' . ($moreattrib ? ' ' . $moreattrib : '') . '>';
6654 if ($useempty == 1 || ($useempty == 2 && $num > 1)) {
6655 print '<option value="-1">&nbsp;</option>';
6656 }
6657
6658 while ($i < $num) {
6659 $obj = $this->db->fetch_object($result);
6660 if ($selected == $obj->rowid) {
6661 print '<option value="' . $obj->rowid . '" selected>';
6662 } else {
6663 print '<option value="' . $obj->rowid . '">';
6664 }
6665 print trim($obj->name);
6666 if ($status == 2 && $obj->status == 1) {
6667 print ' (' . $langs->trans("Closed") . ')';
6668 }
6669 print '</option>';
6670 $i++;
6671 }
6672 print "</select>";
6673 } else {
6674 if ($status == 0) {
6675 print '<span class="opacitymedium">' . $langs->trans("NoActiveEstablishmentDefined") . '</span>';
6676 } else {
6677 print '<span class="opacitymedium">' . $langs->trans("NoEstablishmentFound") . '</span>';
6678 }
6679 }
6680
6681 return $num;
6682 } else {
6683 dol_print_error($this->db);
6684 return -1;
6685 }
6686 }
6687
6697 public function formSelectAccount($page, $selected = '', $htmlname = 'fk_account', $addempty = 0)
6698 {
6699 global $langs;
6700 if ($htmlname != "none") {
6701 print '<form method="POST" action="' . $page . '">';
6702 print '<input type="hidden" name="action" value="setbankaccount">';
6703 print '<input type="hidden" name="token" value="' . newToken() . '">';
6704 print img_picto('', 'bank_account', 'class="pictofixedwidth"');
6705 $nbaccountfound = $this->select_comptes($selected, $htmlname, 0, '', $addempty);
6706 if ($nbaccountfound > 0) {
6707 print '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
6708 }
6709 print '</form>';
6710 } else {
6711 $langs->load('banks');
6712
6713 if ($selected) {
6714 require_once DOL_DOCUMENT_ROOT . '/compta/bank/class/account.class.php';
6715 $bankstatic = new Account($this->db);
6716 $result = $bankstatic->fetch((int) $selected);
6717 if ($result) {
6718 print $bankstatic->getNomUrl(1);
6719 }
6720 } else {
6721 print "&nbsp;";
6722 }
6723 }
6724 }
6725
6737 public function formRib($page, $selected = '', $htmlname = 'ribcompanyid', $filtre = '', $addempty = 0, $showibanbic = 0)
6738 {
6739 global $langs;
6740 if ($htmlname != "none") {
6741 print '<form method="POST" action="' . $page . '">';
6742 print '<input type="hidden" name="action" value="setbankaccountcustomer">';
6743 print '<input type="hidden" name="token" value="' . newToken() . '">';
6744 $nbaccountfound = $this->selectRib($selected, $htmlname, $filtre, $addempty, '', $showibanbic);
6745 if ($nbaccountfound > 0) {
6746 print '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
6747 }
6748 print '</form>';
6749 } else {
6750 $langs->load('banks');
6751
6752 if ($selected) {
6753 require_once DOL_DOCUMENT_ROOT . '/societe/class/companybankaccount.class.php';
6754 $bankstatic = new CompanyBankAccount($this->db);
6755 $result = $bankstatic->fetch((int) $selected);
6756 if ($result) {
6757 print $bankstatic->label;
6758 if ($showibanbic) {
6759 print ' (' . $bankstatic->iban . '/' .$bankstatic->bic. ')';
6760 }
6761 }
6762 } else {
6763 print "&nbsp;";
6764 }
6765 }
6766 }
6767
6777 public function selectCategories($categtype, $htmlname, $object = null)
6778 {
6779 global $langs;
6780
6781 $out = '';
6782
6783 $cate_arbo = $this->select_all_categories($categtype, '', '', 64, 0, 3);
6784
6785 $arrayselected = array();
6786 if (GETPOSTISARRAY($htmlname)) {
6787 $arrayselected = GETPOST($htmlname, 'array:int');
6788 } elseif (is_object($object) && $object->id > 0) {
6789 $c = new Categorie($this->db);
6790 $cats = $c->containing($object->id, $categtype);
6791 $arrayselected = array();
6792 foreach ($cats as $cat) {
6793 $arrayselected[] = $cat->id;
6794 }
6795 }
6796
6797 $out .= img_picto('', 'category', 'class="pictofixedwidth"');
6798 $out .= $this->multiselectarray($htmlname, $cate_arbo, $arrayselected, 0, 0, 'minwidth100 widthcentpercentminusxx', 0, 0);
6799
6800 if (!getDolGlobalString('CATEGORY_EDIT_IN_MENU_NOT_IN_POPUP')) {
6801 // Add html code to add the edit button and go back
6802 $jsonclose = 'doJsCodeAfterPopupClose'.dol_sanitizeKeyCode($htmlname).'()';
6803 $urltoopen = '/categories/categorie_list.php?type='.urlencode($categtype).'&nosearch=1';
6804
6805 $s = dolButtonToOpenUrlInDialogPopup($htmlname, $langs->transnoentitiesnoconv("Categories"), img_picto('', 'add', 'class="editfielda"'), $urltoopen, '', '', '', $jsonclose);
6806 $out .= $s;
6807 // Add js code to add the edit button and go back
6808 $out .= '<!-- Add js code to open the popup for category/edit/add -->'."\n";
6809 $out .= '<script>function doJsCodeAfterPopupClose'.dol_sanitizeKeyCode($htmlname).'() {
6810 console.log("doJsCodeAfterPopupClose'.dol_sanitizeKeyCode($htmlname).' has been called, we refresh the combo content + refresh select2...");
6811
6812 // Call an ajax to reload values and update the select
6813
6814 $.ajax({
6815 url: \''.DOL_URL_ROOT.'/core/ajax/fetchCategories.php\',
6816 data: {
6817 action: \'getCategories\',
6818 type: \''.dol_escape_htmltag($categtype).'\'
6819 },
6820 type: \'GET\',
6821 dataType: \'json\',
6822 success: function (data) {
6823 var $select = $(\'#'.dol_sanitizeKeyCode($htmlname).'\');
6824 var selectedValues = $select.val(); // This is an array of selected values
6825 console.log(selectedValues);
6826 $select.empty();
6827 $.each(data, function (index, item) {
6828 $select.append(\'<option value="\' + item.id + \'" data-html="\' + item.htmlforattribute + \'">\' + item.htmlforoption + \'</option>\');
6829 });
6830 $select.val(selectedValues);
6831 },
6832 error: function (xhr, status, error) {
6833 console.log("Error when loading ajax page : " + error);
6834 }
6835 });
6836
6837 // Refresh select2 to take account of new values (enough for small change)
6838 $("#'.dol_sanitizeKeyCode($htmlname).'").trigger("change");
6839
6840 // Alternative if change in select is complex
6841 /*
6842 $("#'.dol_sanitizeKeyCode($htmlname).'").select2("destroy");
6843 $("#'.dol_sanitizeKeyCode($htmlname).'").select2();
6844 */
6845 }</script>';
6846 }
6847
6848 return $out;
6849 }
6850
6851
6852 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6872 public function select_all_categories($type, $selected = '', $htmlname = "parent", $maxlength = 64, $fromid = 0, $outputmode = 0, $include = 0, $morecss = '', $useempty = 1)
6873 {
6874 // phpcs:enable
6875 global $langs;
6876
6877 include_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
6878
6879 $cat = new Categorie($this->db);
6880
6881 if (is_numeric($type)) {
6882 $type = array_search($type, $cat->MAP_ID); // For backward compatibility
6883 }
6884
6885 $cate_arbo = $cat->get_full_arbo($type, $fromid, $include);
6886
6887 $outarray = array();
6888 $outarrayrichhtml = array();
6889
6890
6891 $output = '<select class="flat minwidth100' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" id="' . $htmlname . '">';
6892 $num = 0;
6893 if (is_array($cate_arbo)) {
6894 $num = count($cate_arbo);
6895
6896 if (!$num) {
6897 $langs->load("categories");
6898 $output .= '<option value="-1" disabled>' . $langs->trans("NoCategoriesDefined") . '</option>';
6899 } else {
6900 if ($useempty == 1 || ($useempty == 2 && $num > 1)) {
6901 $output .= '<option value="-1">&nbsp;</option>';
6902 }
6903 foreach ($cate_arbo as $key => $value) {
6904 if ($cate_arbo[$key]['id'] == $selected || ($selected === 'auto' && count($cate_arbo) == 1)) {
6905 $add = 'selected ';
6906 } else {
6907 $add = '';
6908 }
6909
6910 $labeltoshow = img_picto('', 'category', 'class="pictofixedwidth"'.(empty($cate_arbo[$key]['color']) ? '' : ' style="color: #' . $cate_arbo[$key]['color'] . '"'));
6911 $labeltoshow .= dol_trunc($cate_arbo[$key]['fulllabel'], $maxlength, 'middle');
6912
6913 $outarray[$cate_arbo[$key]['id']] = $cate_arbo[$key]['fulllabel'];
6914
6915 $outarrayrichhtml[$cate_arbo[$key]['id']] = $labeltoshow;
6916
6917 $output .= '<option ' . $add . 'value="' . $cate_arbo[$key]['id'] . '"';
6918 $output .= ' data-html="' . dol_escape_htmltag($labeltoshow) . '"';
6919 $output .= '>';
6920 // The visible (truncated) label is rendered via data-html in
6921 // templateResult of the select2 combobox; the bare option text
6922 // must keep the full label so that the select2 search matcher
6923 // (ajax_combobox in core/lib/ajax.lib.php) can find a hit on
6924 // characters that lie outside the truncated portion.
6925 $output .= dol_escape_htmltag($cate_arbo[$key]['fulllabel']);
6926 $output .= '</option>';
6927
6928 $cate_arbo[$key]['data-html'] = $labeltoshow;
6929 }
6930 }
6931 }
6932 $output .= '</select>';
6933 $output .= "\n";
6934
6935 $this->num = $num;
6936
6937 if ($outputmode == 2) {
6938 // TODO: handle error when $cate_arbo is not an array
6939 return $cate_arbo;
6940 } elseif ($outputmode == 1) {
6941 return $outarray;
6942 } elseif ($outputmode == 3) {
6943 return $outarrayrichhtml;
6944 }
6945 return $output;
6946 }
6947
6956 public function getHelpBlock($content, $icon = 'fa-question-circle')
6957 {
6958 global $langs;
6959
6960 // Sanitize content (assuming it might contain HTML, but escaping text nodes if needed)
6961 // We trust the caller to pass safe HTML or translated strings.
6962
6963 $html = '<details class="dolibarr-help-block" style="margin-top:8px;">';
6964 $html .= '<summary style="cursor:pointer; color:#0056b3; font-weight:normal; list-style:none; font-size:0.9em; display:flex; align-items:center;">';
6965 $html .= '<span class="fa ' . $icon . '" style="margin-right:6px;"></span>';
6966 $html .= $langs->trans("Help"); // Standardized title
6967 $html .= '</summary>';
6968 $html .= '<div style="margin-top:6px; padding:10px; background:#f8f9fa; border:1px solid #dee2e6; border-radius:4px; font-size:0.9em; color:#555; line-height:1.5;">';
6969 $html .= $content;
6970 $html .= '</div>';
6971 $html .= '</details>';
6972
6973 return $html;
6974 }
6975
6976 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6977
6996 public function form_confirm($page, $title, $question, $action, $formquestion = array(), $selectedchoice = "", $useajax = 0, $height = 170, $width = 500)
6997 {
6998 // phpcs:enable
6999 dol_syslog(__METHOD__ . ': using form_confirm is deprecated. Use formconfim instead.', LOG_WARNING);
7000 print $this->formconfirm($page, $title, $question, $action, $formquestion, $selectedchoice, $useajax, $height, $width);
7001 }
7002
7032 public function formconfirm($page, $title, $question, $action, $formquestion = '', $selectedchoice = '', $useajax = 0, $height = 0, $width = 600, $disableformtag = 0, $labelbuttonyes = 'Yes', $labelbuttonno = 'No', $helpContent = '')
7033 {
7034 global $langs, $conf;
7035
7036 $more = '';
7037 $formconfirm = '<!-- formconfirm - before call, page=' . dol_escape_htmltag($page) . ' -->';
7038
7039 $inputok = array();
7040 $inputko = array();
7041
7042 // Clean parameters
7043 $newselectedchoice = empty($selectedchoice) ? "no" : $selectedchoice;
7044 if ($conf->browser->layout == 'phone') {
7045 $width = '95%';
7046 }
7047
7048 // Set height automatically if not defined
7049 if (empty($height)) {
7050 $height = 185;
7051 if (is_array($formquestion)) {
7052 $height += (count($formquestion) * 40);
7053 }
7054 if ($question) {
7055 $height += dol_nboflines_bis($question, 80) * 40;
7056 }
7057 }
7058
7059 if (is_array($formquestion) && !empty($formquestion)) {
7060 // First add hidden fields and value
7061 foreach ($formquestion as $key => $input) {
7062 if (is_array($input) && !empty($input)) {
7063 if ($input['type'] == 'hidden') {
7064 $moreattr = (!empty($input['moreattr']) ? ' ' . $input['moreattr'] : '');
7065 $morecss = (!empty($input['morecss']) ? ' ' . $input['morecss'] : '');
7066
7067 $more .= '<input type="hidden" id="' . dol_escape_htmltag($input['name']) . '" name="' . dol_escape_htmltag($input['name']) . '" value="' . dol_escape_htmltag(isset($input['value']) ? $input['value'] : '') . '" class="' . $morecss . '"' . $moreattr . '>' . "\n"; // 'value' non fourni par tous les appelants
7068 }
7069 }
7070 }
7071
7072 // Now add questions
7073 $moreonecolumn = '';
7074 $more .= '<div class="tagtable paddingtopbottomonly centpercent noborderspacing">' . "\n";
7075 foreach ($formquestion as $key => $input) {
7076 if (is_array($input) && !empty($input)) {
7077 $size = (!empty($input['size']) ? ' size="' . $input['size'] . '"' : ''); // deprecated. Use morecss instead.
7078 $moreattr = (!empty($input['moreattr']) ? ' ' . $input['moreattr'] : '');
7079 $morecss = (!empty($input['morecss']) ? ' ' . $input['morecss'] : '');
7080
7081 if ($input['type'] == 'text' || $input['type'] == 'input') { // traditional input
7082 $more .= '<div class="tagtr"><div class="tagtd' . (empty($input['tdclass']) ? '' : (' ' . $input['tdclass'])) . '">' . ($input['label'] ?? '') . '</div><div class="tagtd"><input type="text" class="flat' . $morecss . '" id="' . dol_escape_htmltag($input['name']) . '" name="' . dol_escape_htmltag($input['name']) . '"' . $size . ' value="' . (empty($input['value']) ? '' : $input['value']) . '"' . $moreattr . ' spellcheck="false" /></div></div>' . "\n";
7083 } elseif ($input['type'] == 'password') {
7084 $more .= '<div class="tagtr"><div class="tagtd' . (empty($input['tdclass']) ? '' : (' ' . $input['tdclass'])) . '">' . ($input['label'] ?? '') . '</div><div class="tagtd"><input type="password" class="flat' . $morecss . '" id="' . dol_escape_htmltag($input['name']) . '" name="' . dol_escape_htmltag($input['name']) . '"' . $size . ' value="' . (empty($input['value']) ? '' : $input['value']) . '"' . $moreattr . ' /></div></div>' . "\n";
7085 } elseif ($input['type'] == 'textarea') {
7086 $moreonecolumn .= '<div class="margintoponly">';
7087 $moreonecolumn .= ($input['label'] ?? '') . '<br>';
7088 $moreonecolumn .= '<textarea name="' . dol_escape_htmltag($input['name']) . '" id="' . dol_escape_htmltag($input['name']) . '" class="' . $morecss . '"' . $moreattr . '>';
7089 $moreonecolumn .= $input['value'] ?? ''; // 'value' is optional (blank textarea by default), like for the 'text' and 'password' types above
7090 $moreonecolumn .= '</textarea>';
7091 $moreonecolumn .= '</div>';
7092 } elseif (in_array($input['type'], ['select', 'multiselect'])) {
7093 if (empty($morecss)) {
7094 $morecss = 'minwidth100';
7095 }
7096
7097 $show_empty = isset($input['select_show_empty']) ? $input['select_show_empty'] : 1;
7098 $key_in_label = isset($input['select_key_in_label']) ? $input['select_key_in_label'] : 0;
7099 $value_as_key = isset($input['select_value_as_key']) ? $input['select_value_as_key'] : 0;
7100 $translate = isset($input['select_translate']) ? $input['select_translate'] : 0;
7101 $maxlen = isset($input['select_maxlen']) ? $input['select_maxlen'] : 0;
7102 $disabled = isset($input['select_disabled']) ? $input['select_disabled'] : 0;
7103 $sort = isset($input['select_sort']) ? $input['select_sort'] : '';
7104
7105 $more .= '<div class="tagtr"><div class="tagtd' . (empty($input['tdclass']) ? '' : (' ' . $input['tdclass'])) . '">';
7106 if (!empty($input['label'])) {
7107 $more .= $input['label'] . '</div><div class="tagtd left">';
7108 }
7109 if ($input['type'] == 'select') {
7110 $more .= $this->selectarray($input['name'], $input['values'], isset($input['default']) ? $input['default'] : '-1', $show_empty, $key_in_label, $value_as_key, $moreattr, $translate, $maxlen, $disabled, $sort, $morecss);
7111 } else {
7112 $more .= $this->multiselectarray($input['name'], $input['values'], is_array($input['default']) ? $input['default'] : [$input['default']], $key_in_label, $value_as_key, $morecss, $translate, $maxlen, $moreattr);
7113 }
7114 $more .= '</div></div>' . "\n";
7115 } elseif ($input['type'] == 'checkbox') {
7116 $more .= '<div class="tagtr">';
7117 $more .= '<div class="tagtd' . (empty($input['tdclass']) ? '' : (' ' . $input['tdclass'])) . '"><label for="' . dol_escape_htmltag($input['name']) . '">' . $input['label'] . '</label></div><div class="tagtd">';
7118 $more .= '<input type="checkbox" class="flat' . ($morecss ? ' ' . $morecss : '') . '" id="' . dol_escape_htmltag($input['name']) . '" name="' . dol_escape_htmltag($input['name']) . '"' . $moreattr;
7119 if (isset($input['value']) && !is_bool($input['value']) && $input['value'] != 'false' && $input['value'] != '0' && $input['value'] != '') {
7120 $more .= ' checked';
7121 }
7122 if (isset($input['value']) && is_bool($input['value']) && $input['value']) {
7123 $more .= ' checked';
7124 }
7125 if (isset($input['disabled'])) {
7126 $more .= ' disabled';
7127 }
7128 $more .= ' /></div>';
7129 $more .= '</div>' . "\n";
7130 } elseif ($input['type'] == 'radio') {
7131 $i = 0;
7132 foreach ($input['values'] as $selkey => $selval) {
7133 $more .= '<div class="tagtr">';
7134 if (isset($input['label'])) {
7135 if ($i == 0) {
7136 $more .= '<div class="tagtd' . (empty($input['tdclass']) ? ' tdtop' : (' tdtop ' . $input['tdclass'])) . '">' . $input['label'] . '</div>';
7137 } else {
7138 $more .= '<div class="tagtd' . (empty($input['tdclass']) ? '' : (' "' . $input['tdclass'])) . '">&nbsp;</div>';
7139 }
7140 }
7141 $more .= '<div class="tagtd' . ($i == 0 ? ' tdtop' : '') . '"><input type="radio" class="flat' . $morecss . '" id="' . dol_escape_htmltag($input['name'] . $selkey) . '" name="' . dol_escape_htmltag($input['name']) . '" value="' . $selkey . '"' . $moreattr;
7142 if (!empty($input['disabled'])) {
7143 $more .= ' disabled';
7144 }
7145 if (isset($input['default']) && $input['default'] === $selkey) {
7146 $more .= ' checked="checked"';
7147 }
7148 $more .= ' /> ';
7149 $more .= '<label for="' . dol_escape_htmltag($input['name'] . $selkey) . '" class="valignmiddle">' . $selval . '</label>';
7150 $more .= '</div></div>' . "\n";
7151 $i++;
7152 }
7153 } elseif ($input['type'] == 'date' || $input['type'] == 'datetime') {
7154 $more .= '<div class="tagtr"><div class="tagtd' . (empty($input['tdclass']) ? '' : (' ' . $input['tdclass'])) . '">' . $input['label'] . '</div>';
7155 $more .= '<div class="tagtd">';
7156 $addnowlink = (empty($input['datenow']) ? 0 : 1);
7157 $h = $m = 0;
7158 if ($input['type'] == 'datetime') {
7159 $h = isset($input['hours']) ? $input['hours'] : 1;
7160 $m = isset($input['minutes']) ? $input['minutes'] : 1;
7161 }
7162 $more .= $this->selectDate(isset($input['value']) ? $input['value'] : -1, $input['name'], $h, $m, 0, '', 1, $addnowlink);
7163 $more .= '</div></div>'."\n";
7164 $formquestion[] = array('name' => $input['name'].'day');
7165 $formquestion[] = array('name' => $input['name'].'month');
7166 $formquestion[] = array('name' => $input['name'].'year');
7167 $formquestion[] = array('name' => $input['name'].'hour');
7168 $formquestion[] = array('name' => $input['name'].'min');
7169 } elseif ($input['type'] == 'other') { // can be 1 column or 2 depending if label is set or not
7170 $more .= '<div class="tagtr"><div class="tagtd'.(empty($input['tdclass']) ? '' : (' '.$input['tdclass'])).'">';
7171 if (!empty($input['label'])) {
7172 $more .= $input['label'] . '</div><div class="tagtd">';
7173 }
7174 if (!empty($input['value'])) {
7175 $more .= $input['value'];
7176 }
7177 $more .= '</div></div>' . "\n";
7178 } elseif ($input['type'] == 'onecolumn') {
7179 $moreonecolumn .= '<div class="margintoponly">';
7180 $moreonecolumn .= $input['value'] ?? '';
7181 $moreonecolumn .= '</div>' . "\n";
7182 } elseif ($input['type'] == 'hidden') {
7183 // Do nothing more, already added by a previous loop
7184 } elseif ($input['type'] == 'separator') {
7185 $more .= '<br>';
7186 } else {
7187 $more .= 'Error type ' . $input['type'] . ' for the confirm box is not a supported type';
7188 }
7189 }
7190 }
7191 $more .= '</div>' . "\n";
7192 $more .= $moreonecolumn;
7193 }
7194
7195 // JQUERY method dialog is broken with smartphone, we use standard HTML.
7196 // 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
7197 // See page product/card.php for example
7198 if (!empty($conf->dol_use_jmobile)) {
7199 $useajax = 0;
7200 }
7201 if (empty($conf->use_javascript_ajax)) {
7202 $useajax = 0;
7203 }
7204
7205 if ($useajax) {
7206 $autoOpen = true;
7207 $dialogconfirm = 'dialog-confirm';
7208 $button = '';
7209 if (!is_numeric($useajax)) {
7210 $button = $useajax;
7211 $useajax = 1;
7212 $autoOpen = false;
7213 $dialogconfirm .= '-' . $button;
7214 }
7215 $pageyes = $page . (preg_match('/\?/', $page) ? '&' : '?') . 'action=' . urlencode($action) . '&confirm=yes';
7216 $pageno = ($useajax == 2 ? $page . (preg_match('/\?/', $page) ? '&' : '?') . 'action=' . urlencode($action) . '&confirm=no' : '');
7217
7218 // Add input fields into list of fields to read during submit (inputok and inputko)
7219 if (is_array($formquestion)) {
7220 foreach ($formquestion as $key => $input) {
7221 //print "xx ".$key." rr ".is_array($input)."<br>\n";
7222 // Add name of fields to propagate with the GET when submitting the form with button OK.
7223 if (is_array($input) && isset($input['name'])) {
7224 if (strpos($input['name'], ',') > 0) {
7225 $inputok = array_merge($inputok, explode(',', $input['name']));
7226 } else {
7227 array_push($inputok, $input['name']);
7228 }
7229 }
7230 // Add name of fields to propagate with the GET when submitting the form with button KO.
7231 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
7232 if (is_array($input) && isset($input['inputko']) && $input['inputko'] == 1 && isset($input['name'])) {
7233 array_push($inputko, $input['name']);
7234 }
7235 }
7236 }
7237
7238 // Show JQuery confirm box.
7239 // Add 'flex-direction: column' and 'justify-content: space-between' to push content to top and buttons to bottom
7240 $formconfirm .= '<div id="' . $dialogconfirm . '" title="' . dol_escape_htmltag($title) . '" style="display: none;">';
7241 $formconfirm .= '<div style="display: flex; flex-direction: column; height: 100%;">';
7242 if (is_array($formquestion) && array_key_exists('text', $formquestion) && !empty($formquestion['text'])) {
7243 $formconfirm .= '<div class="confirmtext">' . $formquestion['text'] . '</div>' . "\n";
7244 }
7245 if (!empty($more)) {
7246 $formconfirm .= '<div class="confirmquestions">' . $more . '</div>' . "\n";
7247 }
7248 // NEW: Add help block if content provided
7249 if (!empty($helpContent)) {
7250 $formconfirm .= '<div style="text-align:left; margin-top:12px; padding-top:8px; border-top:1px solid #eee; clear:both;">';
7251 $formconfirm .= $this->getHelpBlock($helpContent);
7252 $formconfirm .= '</div>';
7253 }
7254 if (!empty($question)) {
7255 $formconfirm .= '<div class="confirmmessage" style="padding-top: 15px;">';
7256 $formconfirm .= img_help(0, '') . ' ' . $question;
7257 $formconfirm .= '</div>';
7258 }
7259 $formconfirm .= '</div>';
7260 $formconfirm .= '</div>' . "\n";
7261
7262 $formconfirm .= "\n<!-- begin code of popup for formconfirm page=" . $page . " -->\n";
7263 $formconfirm .= '<script nonce="' . getNonce() . '" type="text/javascript">' . "\n";
7264 $formconfirm .= "/* Code for the jQuery('#dialogforpopup').dialog() */\n";
7265 $formconfirm .= 'jQuery(document).ready(function() {
7266 $(function() {
7267 $( "#' . $dialogconfirm . '" ).dialog({
7268 autoOpen: ' . ($autoOpen ? "true" : "false") . ',';
7269 if ($newselectedchoice == 'no') {
7270 $formconfirm .= '
7271 open: function() {
7272 $(this).parent().find("button.ui-button:eq(2)").focus();
7273 },';
7274 }
7275
7276 $jsforcursor = '';
7277 if ($useajax == 1) {
7278 $jsforcursor = '// The call to urljump can be slow, so we set the wait cursor' . "\n";
7279 $jsforcursor .= 'jQuery("html,body,#id-container").addClass("cursorwait");' . "\n";
7280 }
7281
7282 $postconfirmas = 'GET';
7283 $maxurllengthforget = getDolGlobalInt('MAIN_MAX_URL_LENGTH_FOR_GET', 2000);
7284
7285 $formconfirm .= '
7286 resizable: false,
7287 height: \'' . dol_escape_js($height) . '\',
7288 width: \'' . dol_escape_js($width) . '\',
7289 modal: true,
7290 closeOnEscape: false,
7291 buttons: {
7292 \'' . dol_escape_js($langs->transnoentities($labelbuttonyes)) . '\': function() {
7293 var options = "token=' . urlencode(newToken()) . '";
7294 var inputok = ' . json_encode($inputok) . '; /* List of fields into form */
7295 var page = \'' . dol_escape_js(!empty($page) ? $page : '') . '\';
7296 var pageyes = \'' . dol_escape_js(!empty($pageyes) ? $pageyes : '') . '\';
7297
7298 if (inputok.length > 0) {
7299 $.each(inputok, function(i, inputname) {
7300 var more = "";
7301 var inputvalue;
7302 if ($("input[name=\'" + inputname + "\']").attr("type") == "radio") {
7303 inputvalue = $("input[name=\'" + inputname + "\']:checked").val();
7304 } else {
7305 if ($("#" + inputname).attr("type") == "checkbox") { more = ":checked"; }
7306 inputvalue = $("#" + inputname + more).val();
7307 }
7308 if (typeof inputvalue == "undefined") { inputvalue=""; }
7309 console.log("formconfirm check inputname="+inputname+" inputvalue="+inputvalue);
7310 options += "&" + inputname + "=" + encodeURIComponent(inputvalue);
7311 });
7312 }
7313 var urljump = pageyes + (pageyes.indexOf("?") < 0 ? "?" : "&") + options;
7314 if (pageyes.length > 0) {';
7315 if ($postconfirmas == 'GET') {
7316 $formconfirm .= 'dolSubmitConfirmForm(urljump, pageyes, options, ' . $maxurllengthforget . ');';
7317 } else {
7318 $formconfirm .= $jsforcursor;
7319 $formconfirm .= 'var post = $.post(
7320 pageyes,
7321 options,
7322 function(data) { $("body").html(data); jQuery("html,body,#id-container").removeClass("cursorwait"); }
7323 );';
7324 }
7325 $formconfirm .= '
7326 console.log("after post ok");
7327 }
7328 $(this).dialog("close");
7329 },
7330 \'' . dol_escape_js($langs->transnoentities($labelbuttonno)) . '\': function() {
7331 var options = "token=' . urlencode(newToken()) . '";
7332 var inputko = ' . json_encode($inputko) . '; /* List of fields into form */
7333 var page = \'' . dol_escape_js(!empty($page) ? $page : '') . '\';
7334 var pageno=\'' . dol_escape_js(!empty($pageno) ? $pageno : '') . '\';
7335 if (inputko.length > 0) {
7336 $.each(inputko, function(i, inputname) {
7337 var more = "";
7338 if ($("#" + inputname).attr("type") == "checkbox") { more = ":checked"; }
7339 var inputvalue = $("#" + inputname + more).val();
7340 if (typeof inputvalue == "undefined") { inputvalue=""; }
7341 options += "&" + inputname + "=" + encodeURIComponent(inputvalue);
7342 });
7343 }
7344 var urljump=pageno + (pageno.indexOf("?") < 0 ? "?" : "&") + options;
7345 //alert(urljump);
7346 if (pageno.length > 0) {';
7347 if ($postconfirmas == 'GET') {
7348 $formconfirm .= 'dolSubmitConfirmForm(urljump, pageno, options, ' . $maxurllengthforget . ');';
7349 } else {
7350 $formconfirm .= $jsforcursor;
7351 $formconfirm .= 'var post = $.post(
7352 pageno,
7353 options,
7354 function(data) { $("body").html(data); jQuery("html,body,#id-container").removeClass("cursorwait"); }
7355 );';
7356 }
7357 $formconfirm .= '
7358 console.log("after post ko");
7359 }
7360 $(this).dialog("close");
7361 }
7362 }
7363 }
7364 );
7365
7366 var button = "' . $button . '";
7367 if (button.length > 0) {
7368 $( "#" + button ).click(function() {
7369 $("#' . $dialogconfirm . '").dialog("open");
7370 });
7371 }
7372 });
7373 });
7374 </script>';
7375 $formconfirm .= "<!-- end ajax formconfirm -->\n";
7376 } else {
7377 $formconfirm .= "\n<!-- begin formconfirm page=" . dol_escape_htmltag($page) . " -->\n";
7378
7379 if (empty($disableformtag)) {
7380 $formconfirm .= '<form method="POST" action="' . $page . '" class="notoptoleftnoright">' . "\n";
7381 }
7382
7383 $formconfirm .= '<input type="hidden" name="action" value="' . $action . '">' . "\n";
7384 $formconfirm .= '<input type="hidden" name="token" value="' . newToken() . '">' . "\n";
7385
7386 $formconfirm .= '<div class="valid">' . "\n";
7387
7388 // Line title
7389 $formconfirm .= '<div class="validtitre">';
7390 $formconfirm .= img_picto('', 'pictoconfirm') . ' ' . $title;
7391 $formconfirm .= '</div>' . "\n";
7392
7393 // Line text
7394 if (is_array($formquestion) && array_key_exists('text', $formquestion) && !empty($formquestion['text'])) {
7395 $formconfirm .= '<div class="valid">' . $formquestion['text'] . '</div>' . "\n";
7396 }
7397
7398 // Line form fields
7399 if ($more) {
7400 $formconfirm .= '<div>' . "\n";
7401 $formconfirm .= $more;
7402 $formconfirm .= '</div>' . "\n";
7403 }
7404
7405 // NEW: Help block row (between form fields and question)
7406 if (!empty($helpContent)) {
7407 $formconfirm .= '<div style="padding-top:8px; border-top:1px solid #888;">';
7408 $formconfirm .= $this->getHelpBlock($helpContent);
7409 $formconfirm .= '</div>' . "\n";
7410 }
7411
7412 // Let's add a row that acts as a spacer.
7413 $formconfirm .= '<div style="padding-top: 20px;"></div>' . "\n";
7414
7415 // Question row
7416 $formconfirm .= '<div class="inline-block">' . $question . '</div>';
7417
7418 $formconfirm .= '<div class="inline-block">';
7419 $formconfirm .= $this->selectyesno("confirm", $newselectedchoice, 0, false, 0, 0, 'marginleftonly marginrightonly', $labelbuttonyes, $labelbuttonno);
7420 $formconfirm .= '<input class="button valignmiddle confirmvalidatebutton small" type="submit" value="' . $langs->trans("Validate") . '">';
7421 $formconfirm .= '</div>';
7422
7423 $formconfirm .= '</div>';
7424
7425 if (empty($disableformtag)) {
7426 $formconfirm .= "</form>\n";
7427 }
7428 $formconfirm .= '<br>';
7429
7430 if (!empty($conf->use_javascript_ajax)) {
7431 $formconfirm .= '<!-- code to disable button to avoid double clic -->';
7432 $formconfirm .= '<script nonce="' . getNonce() . '" type="text/javascript">' . "\n";
7433 $formconfirm .= '
7434 $(document).ready(function () {
7435 $(".confirmvalidatebutton").on("click", function() {
7436 console.log("We click on button confirmvalidatebutton");
7437 $(this).attr("disabled", "disabled");
7438 setTimeout(\'$(".confirmvalidatebutton").removeAttr("disabled")\', 3000);
7439 //console.log($(this).closest("form"));
7440 $(this).closest("form").submit();
7441 });
7442 });
7443 ';
7444 $formconfirm .= '</script>' . "\n";
7445 }
7446
7447 $formconfirm .= "<!-- end formconfirm -->\n";
7448 }
7449
7450 return $formconfirm;
7451 }
7452
7453 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
7454
7471 public function form_project($page, $socid, $selected = '', $htmlname = 'projectid', $discard_closed = 0, $maxlength = 20, $forcefocus = 0, $nooutput = 0, $textifnoproject = '', $morecss = '', $option = '')
7472 {
7473 // phpcs:enable
7474 global $langs;
7475
7476 require_once DOL_DOCUMENT_ROOT . '/core/lib/project.lib.php';
7477 require_once DOL_DOCUMENT_ROOT . '/core/class/html.formprojet.class.php';
7478
7479 $out = '';
7480
7481 $formproject = new FormProjets($this->db);
7482
7483 $langs->load("project");
7484 if ($htmlname != "none") {
7485 $out .= '<form method="post" action="' . $page . '">';
7486 $out .= '<input type="hidden" name="action" value="classin">';
7487 $out .= '<input type="hidden" name="token" value="' . newToken() . '">';
7488 $out .= $formproject->select_projects($socid, $selected, $htmlname, $maxlength, 0, 1, $discard_closed, $forcefocus, 0, 0, '', 1, 0, $morecss);
7489 $out .= '<input type="submit" class="button smallpaddingimp" value="' . $langs->trans("Modify") . '">';
7490 $out .= '</form>';
7491 } else {
7492 $out .= '<span class="project_head_block">';
7493 if ($selected instanceof Project) {
7494 $out .= $selected->getNomUrl(0, $option, 1);
7495 } elseif (is_numeric($selected)) {
7496 $projet = new Project($this->db);
7497 $projet->fetch((int) $selected);
7498 $out .= $projet->getNomUrl(0, $option, 1);
7499 } else {
7500 $out .= '<span class="opacitymedium">' . $textifnoproject . '</span>';
7501 }
7502 $out .= '</span>';
7503 }
7504
7505 if (empty($nooutput)) {
7506 print $out;
7507 return '';
7508 }
7509 return $out;
7510 }
7511
7512 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
7513
7529 public function form_conditions_reglement($page, $selected = '', $htmlname = 'cond_reglement_id', $addempty = 0, $type = '', $filtertype = -1, $deposit_percent = -1, $nooutput = 0)
7530 {
7531 // phpcs:enable
7532 global $langs;
7533
7534 $selected = (int) $selected;
7535
7536 $out = '';
7537
7538 if ($htmlname != "none") {
7539 $out .= '<form method="POST" action="' . $page . '">';
7540 $out .= '<input type="hidden" name="action" value="setconditions">';
7541 $out .= '<input type="hidden" name="token" value="' . newToken() . '">';
7542 if ($type) {
7543 $out .= '<input type="hidden" name="type" value="' . dol_escape_htmltag($type) . '">';
7544 }
7545 $out .= $this->getSelectConditionsPaiements($selected, $htmlname, $filtertype, $addempty, 0, '', $deposit_percent);
7546 $out .= '<input type="submit" class="button valignmiddle smallpaddingimp" value="' . $langs->trans("Modify") . '">';
7547 $out .= '</form>';
7548 } else {
7549 if ($selected) {
7550 $this->load_cache_conditions_paiements();
7551
7552 if (isset($this->cache_conditions_paiements[$selected])) {
7553 $label = $this->cache_conditions_paiements[$selected]['label'];
7554
7555 if (!empty($this->cache_conditions_paiements[$selected]['deposit_percent'])) {
7556 $label = str_replace('__DEPOSIT_PERCENT__', $deposit_percent > 0 ? $deposit_percent : $this->cache_conditions_paiements[$selected]['deposit_percent'], $label);
7557 }
7558
7559 $out .= $label;
7560 } else {
7561 $langs->load('errors');
7562 $out .= $langs->trans('ErrorNotInDictionaryPaymentConditions', $selected);
7563 }
7564 } else {
7565 $out .= '&nbsp;';
7566 }
7567 }
7568
7569 if (empty($nooutput)) {
7570 print $out;
7571 return '';
7572 }
7573 return $out;
7574 }
7575
7576 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
7577
7588 public function form_rule_for_lines_dates($page, $selected = '', $htmlname = 'rule_for_lines_dates', $addempty = 0, $nooutput = 0): string
7589 {
7590 // phpcs:enable
7591 global $langs;
7592
7593 $out = '';
7594
7595 if ($htmlname != 'none') {
7596 $out .= '<form method="POST" action="' . $page . '">';
7597 $out .= '<input type="hidden" name="action" value="setruleforlinesdates">';
7598 $out .= '<input type="hidden" name="token" value="' . newToken() . '">';
7599 $out .= $this->getSelectRuleForLinesDates($selected, $htmlname, $addempty);
7600 $out .= '<input type="submit" class="button valignmiddle smallpaddingimp" value="' . $langs->trans("Modify") . '">';
7601 $out .= '</form>';
7602 } else {
7603 if (isset($selected)) {
7604 $this->load_cache_rule_for_lines_dates();
7605 if (isset($this->cache_rule_for_lines_dates[$selected])) {
7606 $label = $this->cache_rule_for_lines_dates[$selected];
7607 $out .= $langs->trans($label);
7608 }
7609 } else {
7610 $out .= '&nbsp;';
7611 }
7612 }
7613
7614 if (empty($nooutput)) {
7615 print $out;
7616 return '';
7617 }
7618
7619 return $out;
7620 }
7621
7622 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
7623
7633 public function form_availability($page, $selected = '', $htmlname = 'availability', $addempty = 0)
7634 {
7635 dol_syslog(__METHOD__, LOG_DEBUG);
7636 // phpcs:enable
7637 global $langs;
7638 if ($htmlname != "none") {
7639 print '<form method="post" action="' . $page . '">';
7640 print '<input type="hidden" name="action" value="setavailability">';
7641 print '<input type="hidden" name="token" value="' . newToken() . '">';
7642 print $this->selectAvailabilityDelay($selected, $htmlname, '', $addempty, '', 1);
7643 print '<input type="submit" name="modify" class="button smallpaddingimp" value="' . $langs->trans("Modify") . '">';
7644 print '<input type="submit" name="cancel" class="button smallpaddingimp" value="' . $langs->trans("Cancel") . '">';
7645 print '</form>';
7646 } else {
7647 if ($selected) {
7648 $this->load_cache_availability();
7649 // @phan-suppress-next-line PhanTypeMismatchProperty
7650 if (isset($this->cache_availability[$selected])) {
7651 print $this->cache_availability[$selected]['label'];
7652 } else {
7653 print "&nbsp;";
7654 }
7655 } else {
7656 print "&nbsp;";
7657 }
7658 }
7659 }
7660
7672 public function formInputReason($page, $selected = '', $htmlname = 'demandreason', $addempty = 0, $morecss = '')
7673 {
7674 global $langs;
7675 if ($htmlname != "none") {
7676 print '<form method="post" action="' . $page . '">';
7677 print '<input type="hidden" name="action" value="setdemandreason">';
7678 print '<input type="hidden" name="token" value="' . newToken() . '">';
7679 $this->selectInputReason($selected, $htmlname, '-1', $addempty, $morecss);
7680 print '<input type="submit" class="button smallpaddingimp" value="' . $langs->trans("Modify") . '">';
7681 print '</form>';
7682 } else {
7683 if ($selected) {
7684 $this->loadCacheInputReason();
7685 foreach ($this->cache_demand_reason as $key => $val) {
7686 if ($val['id'] == $selected) {
7687 print $val['label'];
7688 break;
7689 }
7690 }
7691 } else {
7692 print "&nbsp;";
7693 }
7694 }
7695 }
7696
7697 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
7698
7712 public function form_date($page, $selected, $htmlname, $displayhour = 0, $displaymin = 0, $nooutput = 0, $type = '')
7713 {
7714 // phpcs:enable
7715 global $langs;
7716
7717 $ret = '';
7718
7719 if ($htmlname != "none") {
7720 $ret .= '<form method="POST" action="' . $page . '" name="form' . $htmlname . '">';
7721 $ret .= '<input type="hidden" name="action" value="set' . $htmlname . '">';
7722 $ret .= '<input type="hidden" name="token" value="' . newToken() . '">';
7723 if ($type) {
7724 $ret .= '<input type="hidden" name="type" value="' . dol_escape_htmltag($type) . '">';
7725 }
7726 $ret .= '<table class="nobordernopadding">';
7727 $ret .= '<tr><td>';
7728 $ret .= $this->selectDate($selected, $htmlname, $displayhour, $displaymin, 1, 'form' . $htmlname, 1, 0);
7729 $ret .= '</td>';
7730 $ret .= '<td class="left"><input type="submit" class="button smallpaddingimp" value="' . $langs->trans("Modify") . '"></td>';
7731 $ret .= '</tr></table></form>';
7732 } else {
7733 if ($displayhour) {
7734 $ret .= dol_print_date($selected, 'dayhour');
7735 } else {
7736 $ret .= dol_print_date($selected, 'day');
7737 }
7738 }
7739
7740 if (empty($nooutput)) {
7741 print $ret;
7742 }
7743 return $ret;
7744 }
7745
7746
7747 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
7748
7759 public function form_users($page, $selected = '', $htmlname = 'userid', $exclude = array(), $include = array())
7760 {
7761 // phpcs:enable
7762 global $langs;
7763
7764 if ($htmlname != "none") {
7765 print '<form method="POST" action="' . $page . '" name="form' . $htmlname . '">';
7766 print '<input type="hidden" name="action" value="set' . $htmlname . '">';
7767 print '<input type="hidden" name="token" value="' . newToken() . '">';
7768 print $this->select_dolusers($selected, $htmlname, 1, $exclude, 0, $include);
7769 print '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
7770 print '</form>';
7771 } else {
7772 if ($selected) {
7773 require_once DOL_DOCUMENT_ROOT . '/user/class/user.class.php';
7774 $theuser = new User($this->db);
7775 $theuser->fetch((int) $selected);
7776 print $theuser->getNomUrl(1);
7777 } else {
7778 print "&nbsp;";
7779 }
7780 }
7781 }
7782
7783
7784 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
7785
7800 public function form_modes_reglement($page, $selected = '', $htmlname = 'mode_reglement_id', $filtertype = '', $active = 1, $addempty = 0, $type = '', $nooutput = 0, $short = 0)
7801 {
7802 // phpcs:enable
7803 global $langs;
7804
7805 $out = '';
7806 if ($htmlname != "none") {
7807 $out .= '<form method="POST" action="' . $page . '">';
7808 $out .= '<input type="hidden" name="action" value="setmode">';
7809 $out .= '<input type="hidden" name="token" value="' . newToken() . '">';
7810 if ($type) {
7811 $out .= '<input type="hidden" name="type" value="' . dol_escape_htmltag($type) . '">';
7812 }
7813 $out .= $this->select_types_paiements($selected, $htmlname, $filtertype, 0, $addempty, 0, 0, $active, '', 1);
7814 $out .= '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
7815 $out .= '</form>';
7816 } else {
7817 if ((int) $selected) {
7818 $this->load_cache_types_paiements();
7819 if ($short) {
7820 $out .= $this->cache_types_paiements[(int) $selected]['shortlabel'] ?? '&nbsp;';
7821 } else {
7822 $out .= $this->cache_types_paiements[(int) $selected]['label'] ?? '&nbsp;';
7823 }
7824 } else {
7825 $out .= "&nbsp;";
7826 }
7827 }
7828
7829 if ($nooutput) {
7830 return $out;
7831 } else {
7832 print $out;
7833 }
7834 return '';
7835 }
7836
7847 public function formSelectTransportMode($page, $selected = '', $htmlname = 'transport_mode_id', $active = 1, $addempty = 0)
7848 {
7849 global $langs;
7850 if ($htmlname != "none") {
7851 print '<form method="POST" action="' . $page . '">';
7852 print '<input type="hidden" name="action" value="settransportmode">';
7853 print '<input type="hidden" name="token" value="' . newToken() . '">';
7854 $this->selectTransportMode($selected, $htmlname, 0, $addempty, 0, 0, $active);
7855 print '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
7856 print '</form>';
7857 } else {
7858 if ($selected) {
7859 $this->load_cache_transport_mode();
7860 print $this->cache_transport_mode[$selected]['label'];
7861 } else {
7862 print "&nbsp;";
7863 }
7864 }
7865 }
7866
7867 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
7868
7877 public function form_multicurrency_code($page, $selected = '', $htmlname = 'multicurrency_code')
7878 {
7879 // phpcs:enable
7880 global $langs;
7881 if ($htmlname != "none") {
7882 print '<form method="POST" action="' . $page . '">';
7883 print '<input type="hidden" name="action" value="setmulticurrencycode">';
7884 print '<input type="hidden" name="token" value="' . newToken() . '">';
7885 print $this->selectMultiCurrency($selected, $htmlname, 0);
7886 print '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
7887 print '</form>';
7888 } else {
7889 require_once DOL_DOCUMENT_ROOT . '/core/lib/company.lib.php';
7890 print !empty($selected) ? currency_name($selected, 1) : '&nbsp;';
7891 }
7892 }
7893
7894 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
7895
7906 public function form_multicurrency_rate($page, $rate = 0.0, $htmlname = 'multicurrency_tx', $currency = '', $rate_direct = 0.0)
7907 {
7908 // phpcs:enable
7909 global $langs, $conf;
7910
7911 if ($htmlname != "none") {
7912 print '<form method="POST" action="' . $page . '">';
7913 print '<input type="hidden" name="action" value="setmulticurrencyrate">';
7914 print '<input type="hidden" name="token" value="' . newToken() . '">';
7915 print '<input type="text" class="maxwidth75" name="' . $htmlname . '" value="' . (!empty($rate) ? price(price2num($rate, 'CR')) : 1) . '" spellcheck="false" /> ';
7916 print '<select name="calculation_mode" id="calculation_mode">';
7917 print '<option value="1">Change ' . $langs->trans("PriceUHT") . ' of lines</option>';
7918 print '<option value="2">Change ' . $langs->trans("PriceUHTCurrency") . ' of lines</option>';
7919 print '</select> ';
7920 print ajax_combobox("calculation_mode");
7921 print '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
7922 print '</form>';
7923 } else {
7924 if (!empty($rate)) {
7925 print price($rate, 1, $langs, 0, 0);
7926 if ($currency && $rate != 1) {
7932 if (getDolGlobalString('MULTICURRENCY_USE_RATE_DIRECT')) {
7933 print ' &nbsp; <span class="opacitymedium">(' . price($rate_direct, 1, $langs, 0, 0) . ' ' . $conf->currency . ' = 1 ' . $currency . ')</span>';
7934 } else {
7935 print ' &nbsp; <span class="opacitymedium">(' . price($rate, 1, $langs, 0, 0) . ' ' . $currency . ' = 1 ' . $conf->currency . ')</span>';
7936 }
7937 }
7938 } else {
7939 print 1;
7940 }
7941 }
7942 }
7943
7944 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
7945
7963 public function form_remise_dispo($page, $selected, $htmlname, $socid, $amount, $filter = '', $maxvalue = 0, $more = '', $hidelist = 0, $discount_type = 0, $filterabsolutediscount = 0, $filtercreditnote = 0)
7964 {
7965 // phpcs:enable
7966 global $conf, $langs;
7967
7968 if ($htmlname != "none") {
7969 print '<form method="post" action="' . $page . '" class="inline-block">';
7970 print '<input type="hidden" name="action" value="setabsolutediscount">';
7971 print '<input type="hidden" name="token" value="' . newToken() . '">';
7972 print '<div class="inline-block">';
7973 if (!empty($discount_type)) {
7974 if (getDolGlobalString('FACTURE_SUPPLIER_DEPOSITS_ARE_JUST_PAYMENTS')) {
7975 if (!$filter || $filter == "fk_invoice_supplier_source IS NULL") {
7976 $translationKey = 'HasAbsoluteDiscountFromSupplier'; // If we want deposit to be subtracted to payments only and not to total of final invoice
7977 } else {
7978 $translationKey = 'HasCreditNoteFromSupplier';
7979 }
7980 } else {
7981 if (!$filter || $filter == "fk_invoice_supplier_source IS NULL OR (description LIKE '(DEPOSIT)%' AND description NOT LIKE '(EXCESS PAID)%')") {
7982 $translationKey = 'HasAbsoluteDiscountFromSupplier';
7983 } else {
7984 $translationKey = 'HasCreditNoteFromSupplier';
7985 }
7986 }
7987 } else {
7988 if (getDolGlobalString('FACTURE_DEPOSITS_ARE_JUST_PAYMENTS')) {
7989 if (!$filter || $filter == "fk_facture_source IS NULL") {
7990 $translationKey = 'CompanyHasAbsoluteDiscount'; // If we want deposit to be subtracted to payments only and not to total of final invoice
7991 } else {
7992 $translationKey = 'CompanyHasCreditNote';
7993 }
7994 } else {
7995 if (!$filter || $filter == "fk_facture_source IS NULL OR (description LIKE '(DEPOSIT)%' AND description NOT LIKE '(EXCESS RECEIVED)%')") {
7996 $translationKey = 'CompanyHasAbsoluteDiscount';
7997 } else {
7998 $translationKey = 'CompanyHasCreditNote';
7999 }
8000 }
8001 }
8002 print $langs->trans($translationKey, price($amount, 0, $langs, 0, 0, -1, $conf->currency));
8003 if (empty($hidelist)) {
8004 print ' ';
8005 }
8006 print '</div>';
8007 if (empty($hidelist)) {
8008 print '<div class="inline-block" style="padding-right: 10px">';
8009 $newfilter = 'discount_type = ' . intval($discount_type);
8010 if (!empty($discount_type)) {
8011 $newfilter .= ' AND fk_invoice_supplier IS NULL AND fk_invoice_supplier_line IS NULL'; // Supplier discounts available
8012 } else {
8013 $newfilter .= ' AND fk_facture IS NULL AND fk_facture_line IS NULL'; // Customer discounts available
8014 }
8015 if ($filter) {
8016 $sanitizedfilter = $filter; // @phan-suppress-current-line SqlInjection
8017 $newfilter .= ' AND (' . $sanitizedfilter . ')';
8018 }
8019 // output the combo of discounts
8020 $nbqualifiedlines = $this->select_remises((string) $selected, $htmlname, $newfilter, $socid, $maxvalue);
8021 if ($nbqualifiedlines > 0) {
8022 print ' &nbsp; <input type="submit" class="button smallpaddingimp" value="' . dol_escape_htmltag($langs->trans("UseLine")) . '"';
8023 if (!empty($discount_type) && $filter && $filter != "fk_invoice_supplier_source IS NULL OR (description LIKE '(DEPOSIT)%' AND description NOT LIKE '(EXCESS PAID)%')") {
8024 print ' title="' . $langs->trans("UseCreditNoteInInvoicePayment") . '"';
8025 }
8026 if (empty($discount_type) && $filter && $filter != "fk_facture_source IS NULL OR (description LIKE '(DEPOSIT)%' AND description NOT LIKE '(EXCESS RECEIVED)%')") {
8027 print ' title="' . $langs->trans("UseCreditNoteInInvoicePayment") . '"';
8028 }
8029
8030 print '>';
8031 }
8032 print '</div>';
8033 }
8034 if ($more) {
8035 print '<div class="inline-block">';
8036 print $more;
8037 print '</div>';
8038 }
8039 print '</form>';
8040 } else {
8041 if ($selected) {
8042 print $selected;
8043 } else {
8044 print "0";
8045 }
8046 }
8047 }
8048
8049
8050 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
8051
8061 public function form_contacts($page, $societe, $selected = '', $htmlname = 'contactid')
8062 {
8063 // phpcs:enable
8064 global $langs;
8065
8066 if ($htmlname != "none") {
8067 print '<form method="post" action="' . $page . '">';
8068 print '<input type="hidden" name="action" value="set_contact">';
8069 print '<input type="hidden" name="token" value="' . newToken() . '">';
8070 print '<table class="nobordernopadding">';
8071 print '<tr><td>';
8072 print $this->selectcontacts($societe->id, $selected, $htmlname);
8073 $num = $this->num;
8074 if ($num == 0) {
8075 $addcontact = (getDolGlobalString('SOCIETE_ADDRESSES_MANAGEMENT') ? $langs->trans("AddContact") : $langs->trans("AddContactAddress"));
8076 print '<a href="' . DOL_URL_ROOT . '/contact/card.php?socid=' . $societe->id . '&action=create&backtoreferer=1">' . $addcontact . '</a>';
8077 }
8078 print '</td>';
8079 print '<td class="left"><input type="submit" class="button smallpaddingimp" value="' . $langs->trans("Modify") . '"></td>';
8080 print '</tr></table></form>';
8081 } else {
8082 if ($selected) {
8083 require_once DOL_DOCUMENT_ROOT . '/contact/class/contact.class.php';
8084 $contact = new Contact($this->db);
8085 $contact->fetch((int) $selected);
8086 print $contact->getFullName($langs);
8087 } else {
8088 print "&nbsp;";
8089 }
8090 }
8091 }
8092
8093 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
8094
8111 public function form_thirdparty($page, $selected = '', $htmlname = 'socid', $filter = '', $showempty = 0, $showtype = 0, $forcecombo = 0, $events = array(), $nooutput = 0, $excludeids = array(), $textifnothirdparty = '')
8112 {
8113 // phpcs:enable
8114 global $langs;
8115
8116 $out = '';
8117 if ($htmlname != "none") {
8118 $limit = getDolGlobalInt('THIRDPARTY_LIMIT_SIZE');
8119
8120 $out .= '<form method="post" action="' . $page . '">';
8121 $out .= '<input type="hidden" name="action" value="set_thirdparty">';
8122 $out .= '<input type="hidden" name="token" value="' . newToken() . '">';
8123 $out .= $this->select_company($selected, $htmlname, $filter, $showempty, $showtype, $forcecombo, $events, $limit, 'minwidth100', '', '', 1, array(), false, $excludeids);
8124 $out .= '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
8125 $out .= '</form>';
8126 } else {
8127 if ($selected) {
8128 require_once DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php';
8129 $soc = new Societe($this->db);
8130 $soc->fetch((int) $selected);
8131 $out .= $soc->getNomUrl(0, '');
8132 } else {
8133 $out .= '<span class="opacitymedium">' . $textifnothirdparty . '</span>';
8134 }
8135 }
8136
8137 if ($nooutput) {
8138 return $out;
8139 } else {
8140 print $out;
8141 }
8142
8143 return '';
8144 }
8145
8146 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
8147
8156 public function select_currency($selected = '', $htmlname = 'currency_id')
8157 {
8158 // phpcs:enable
8159 print $this->selectCurrency($selected, $htmlname);
8160 }
8161
8171 public function selectCurrency($selected = '', $htmlname = 'currency_id', $mode = 0, $useempty = '')
8172 {
8173 global $langs, $user;
8174
8175 $langs->loadCacheCurrencies('');
8176
8177 $out = '';
8178
8179 if ($selected == 'euro' || $selected == 'euros') {
8180 $selected = 'EUR'; // Pour compatibilite
8181 }
8182
8183 $out .= '<select class="flat maxwidth200onsmartphone minwidth300" name="' . $htmlname . '" id="' . $htmlname . '">';
8184 if ($useempty) {
8185 $out .= '<option value="-1" selected></option>';
8186 }
8187 foreach ($langs->cache_currencies as $code_iso => $currency) {
8188 $labeltoshow = $currency['label'];
8189 if ($mode == 1) {
8190 $labeltoshow .= ' <span class="opacitymedium">(' . $code_iso . ')</span>';
8191 } elseif ($mode == 2) {
8192 $labeltoshow .= ' <span class="opacitymedium">(' . $code_iso.' - '.$langs->getCurrencySymbol($code_iso) . ')</span>';
8193 } else {
8194 $labeltoshow .= ' <span class="opacitymedium">(' . $langs->getCurrencySymbol($code_iso) . ')</span>';
8195 }
8196
8197 if ($selected && $selected == $code_iso) {
8198 $out .= '<option value="' . $code_iso . '" selected data-html="' . dol_escape_htmltag($labeltoshow) . '">';
8199 } else {
8200 $out .= '<option value="' . $code_iso . '" data-html="' . dol_escape_htmltag($labeltoshow) . '">';
8201 }
8202 $out .= dol_string_nohtmltag($labeltoshow);
8203 $out .= '</option>';
8204 }
8205 $out .= '</select>';
8206 if ($user->admin) {
8207 $out .= info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
8208 }
8209
8210 // Make select dynamic
8211 include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
8212 $out .= ajax_combobox($htmlname);
8213
8214 return $out;
8215 }
8216
8229 public function selectMultiCurrency($selected = '', $htmlname = 'multicurrency_code', $useempty = 0, $filter = '', $excludeConfCurrency = false, $morecss = 'maxwidth200 widthcentpercentminusx')
8230 {
8231 global $conf, $langs;
8232
8233 $langs->loadCacheCurrencies(''); // Load ->cache_currencies
8234
8235 $TCurrency = array();
8236
8237 $sql = "SELECT code FROM " . $this->db->prefix() . "multicurrency";
8238 $sql .= " WHERE entity IN ('" . getEntity('multicurrency') . "')";
8239 if ($filter) {
8240 $sql .= forgeSQLFromUniversalSearchCriteria($filter);
8241 }
8242 $resql = $this->db->query($sql);
8243 if ($resql) {
8244 while ($obj = $this->db->fetch_object($resql)) {
8245 $TCurrency[$obj->code] = $obj->code;
8246 }
8247 }
8248
8249 $out = '';
8250 $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" id="' . $htmlname . '">';
8251 if ($useempty) {
8252 $out .= '<option value="">&nbsp;</option>';
8253 }
8254 // If company current currency not in table, we add it into list. Should always be available.
8255 if (!in_array($conf->currency, $TCurrency) && !$excludeConfCurrency) {
8256 $TCurrency[$conf->currency] = $conf->currency;
8257 }
8258 if (count($TCurrency) > 0) {
8259 foreach ($langs->cache_currencies as $code_iso => $currency) {
8260 if (isset($TCurrency[$code_iso])) {
8261 if (!empty($selected) && $selected == $code_iso) {
8262 $out .= '<option value="' . $code_iso . '" selected="selected">';
8263 } else {
8264 $out .= '<option value="' . $code_iso . '">';
8265 }
8266
8267 $out .= $currency['label'];
8268 $out .= ' (' . $langs->getCurrencySymbol($code_iso) . ')';
8269 $out .= '</option>';
8270 }
8271 }
8272 }
8273
8274 $out .= '</select>';
8275
8276 // Make select dynamic
8277 include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
8278 $out .= ajax_combobox($htmlname);
8279
8280 return $out;
8281 }
8282
8283 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
8284
8291 public function load_cache_vatrates($country_code)
8292 {
8293 // phpcs:enable
8294 global $langs, $user, $hookmanager;
8295
8296 $num = count($this->cache_vatrates);
8297 if ($num > 0) {
8298 return $num; // Cache already loaded
8299 }
8300
8301 dol_syslog(__METHOD__, LOG_DEBUG);
8302
8303 // entity and fk_pays are returned so a hook on loadDictionaryCache can tell two rows apart
8304 // when the dictionary is read across entities (most rows carry an empty code)
8305 $sql = "SELECT t.rowid, t.entity, t.fk_pays, t.type_vat, t.code, t.taux, t.localtax1, t.localtax1_type, t.localtax2, t.localtax2_type, t.recuperableonly, t.einvoice_vatex";
8306 $sql .= " FROM ".$this->db->prefix()."c_tva as t, ".$this->db->prefix()."c_country as c";
8307 $sql .= " WHERE t.fk_pays = c.rowid";
8308 $sql .= " AND t.active > 0";
8309 $sql .= " AND t.entity IN (".getEntity('c_tva').")";
8310 $sql .= " AND c.code IN (" . $this->db->sanitize($country_code, 1) . ")";
8311 $sql .= " ORDER BY t.code ASC, t.taux ASC, t.recuperableonly ASC";
8312
8313 $resql = $this->db->query($sql);
8314 if ($resql) {
8315 $num = $this->db->num_rows($resql);
8316 if ($num) {
8317 for ($i = 0; $i < $num; $i++) {
8318 $obj = $this->db->fetch_object($resql);
8319
8320 $tmparray = array();
8321 $tmparray['rowid'] = (int) $obj->rowid;
8322 $tmparray['entity'] = (int) $obj->entity;
8323 $tmparray['fk_pays'] = (int) $obj->fk_pays;
8324 $tmparray['type_vat'] = ($obj->type_vat <= 0 ? 0 : $obj->type_vat); // Some version have type_vat corrupted with value -1
8325 $tmparray['code'] = $obj->code;
8326 $tmparray['txtva'] = $obj->taux;
8327 $tmparray['nprtva'] = $obj->recuperableonly;
8328 $tmparray['localtax1'] = $obj->localtax1;
8329 $tmparray['localtax1_type'] = $obj->localtax1_type;
8330 $tmparray['localtax2'] = $obj->localtax2;
8331 $tmparray['localtax2_type'] = $obj->localtax1_type;
8332 $tmparray['einvoice_vatex'] = $obj->einvoice_vatex;
8333
8334 $tmparray['label'] = $obj->taux . '%' . ($obj->code ? ' (' . $obj->code . ')' : ''); // Label must contains only 0-9 , . % or *
8335 $tmparray['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
8336 $positiverates = '';
8337 if ($obj->taux) {
8338 $positiverates .= ($positiverates ? '/' : '') . $obj->taux;
8339 }
8340 if ($obj->localtax1) {
8341 $positiverates .= ($positiverates ? '/' : '') . $obj->localtax1;
8342 }
8343 if ($obj->localtax2) {
8344 $positiverates .= ($positiverates ? '/' : '') . $obj->localtax2;
8345 }
8346 if (empty($positiverates)) {
8347 $positiverates = '0';
8348 }
8349 $tmparray['labelpositiverates'] = $positiverates . ($obj->code ? ' (' . $obj->code . ')' : ''); // Must never be used as key, only label
8350
8351 $this->cache_vatrates[$obj->rowid] = $tmparray;
8352 }
8353
8354 $parameters = array('dictionary' => 'vatrate', 'country_code' => $country_code);
8355 $reshook = $hookmanager->executeHooks('loadDictionaryCache', $parameters, $this); // Note that $action and $object may have been modified by hook
8356 if (empty($reshook)) {
8357 if (is_array($hookmanager->resArray) && count($hookmanager->resArray)) {
8358 $this->cache_vatrates = array_merge($this->cache_vatrates, $hookmanager->resArray);
8359 }
8360 } else {
8361 $this->cache_vatrates = $hookmanager->resArray;
8362 }
8363
8364 return count($this->cache_vatrates);
8365 } else {
8366 $this->error = '<span class="error">';
8367 $this->error .= $langs->trans("ErrorNoVATRateDefinedForSellerCountry", $country_code);
8368 $reg = array();
8369 if (!empty($user) && $user->admin && preg_match('/\'(..)\'/', $country_code, $reg)) {
8370 $langs->load("errors");
8371 $new_country_code = $reg[1];
8372 $country_id = dol_getIdFromCode($this->db, $new_country_code, 'c_country', 'code', 'rowid');
8373 $this->error .= '<br>'.$langs->trans("ErrorFixThisHere", DOL_URL_ROOT.'/admin/dict.php?id=10'.($country_id > 0 ? '&countryidforinsert='.$country_id : ''));
8374 }
8375 $this->error .= '</span>';
8376 return -1;
8377 }
8378 } else {
8379 $this->error = '<span class="error">' . $this->db->error() . '</span>';
8380 return -2;
8381 }
8382 }
8383
8384 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
8385
8408 public function load_tva($htmlname = 'tauxtva', $selectedrate = '', $societe_vendeuse = null, $societe_acheteuse = null, $idprod = 0, $info_bits = 0, $type = '', $options_only = false, $mode = 0, $type_vat = 0)
8409 {
8410 // phpcs:enable
8411 global $langs, $mysoc, $hookmanager;
8412
8413 $langs->load('errors');
8414
8415 $return = '';
8416 // Bypass the default method
8417 $hookmanager->initHooks(array('commonobject'));
8418 $info_bits == 1 ? $is_npr = 1 : $is_npr = 0;
8419 $parameters = array(
8420 'htmlname' => $htmlname,
8421 'selectedrate' => $selectedrate,
8422 'seller' => $societe_vendeuse,
8423 'buyer' => $societe_acheteuse,
8424 'idprod' => $idprod,
8425 'is_npr' => $is_npr,
8426 'type' => $type,
8427 'options_only' => $options_only,
8428 'mode' => $mode,
8429 'type_vat' => $type_vat
8430 );
8431 $reshook = $hookmanager->executeHooks('load_tva', $parameters);
8432 if ($reshook > 0) {
8433 return $hookmanager->resPrint;
8434 } elseif ($reshook === 0) {
8435 $return .= $hookmanager->resPrint;
8436 }
8437
8438 // Define defaultnpr, defaultttx and defaultcode
8439 $defaultnpr = ($info_bits & 0x01);
8440 $defaultnpr = (preg_match('/\*/', $selectedrate) ? 1 : $defaultnpr);
8441 $defaulttx = str_replace('*', '', $selectedrate);
8442 $defaultcode = '';
8443 $reg = array();
8444 if (preg_match('/\‍((.*)\‍)/', $defaulttx, $reg)) {
8445 $defaultcode = $reg[1];
8446 $defaulttx = preg_replace('/\s*\‍(.*\‍)/', '', $defaulttx);
8447 }
8448 //var_dump($selectedrate.'-'.$defaulttx.'-'.$defaultnpr.'-'.$defaultcode);
8449
8450 // Check parameters
8451 if (is_object($societe_vendeuse) && !$societe_vendeuse->country_code) {
8452 if ($societe_vendeuse->id == $mysoc->id) {
8453 $return .= '<span class="error">' . $langs->trans("ErrorYourCountryIsNotDefined") . '</span>';
8454 } else {
8455 $return .= '<span class="error">' . $langs->trans("ErrorSupplierCountryIsNotDefined") . '</span>';
8456 }
8457 return $return;
8458 }
8459
8460 //var_dump($societe_acheteuse);
8461 //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";
8462 //exit;
8463
8464 // Define list of countries to use to search VAT rates to show
8465 // First we defined code_country to use to find list.
8466 // country_code must be a c_country ISO code (e.g. 'FR', 'CH'). In some setups it may hold a
8467 // country label (e.g. 'Suisse') instead, which would make the "c.code IN (...)" lookup done by
8468 // load_cache_vatrates() match nothing and wrongly force the VAT rate to 0%. A valid ISO code is
8469 // always 2 chars, so when the value is not a well formed ISO code (empty or a label) and we have
8470 // a valid country id, we recover the ISO code from the authoritative country id. This way we do
8471 // not run any SQL on each page access when we already have a valid ISO code.
8472 $sellercountrycode = is_object($societe_vendeuse) ? $societe_vendeuse->country_code : $mysoc->country_code;
8473 $sellercountryid = is_object($societe_vendeuse) ? $societe_vendeuse->country_id : $mysoc->country_id;
8474 if ((int) $sellercountryid > 0 && strlen((string) $sellercountrycode) != 2) {
8475 $tmpcountrycode = dol_getIdFromCode($this->db, (string) $sellercountryid, 'c_country', 'rowid', 'code');
8476 if (!empty($tmpcountrycode) && !is_numeric($tmpcountrycode)) {
8477 $sellercountrycode = $tmpcountrycode;
8478 }
8479 }
8480 $code_country = "'" . $sellercountrycode . "'"; // Pour compatibilite ascendente
8481
8482 if ($societe_vendeuse == $mysoc && getDolGlobalString('SERVICE_ARE_ECOMMERCE_200238EC')) { // If option to have vat for end customer for services is on
8483 require_once DOL_DOCUMENT_ROOT . '/core/lib/company.lib.php';
8484 // If SERVICE_ARE_ECOMMERCE_200238EC=1 combo list vat rate of purchaser and seller countries
8485 // If SERVICE_ARE_ECOMMERCE_200238EC=2 combo list only the vat rate of the purchaser country
8486 $selectVatComboMode = getDolGlobalString('SERVICE_ARE_ECOMMERCE_200238EC');
8487 if (is_object($societe_vendeuse) && is_object($societe_acheteuse) && isInEEC($societe_vendeuse) && isInEEC($societe_acheteuse) && !$societe_acheteuse->isACompany()) {
8488 // We also add the buyer country code
8489 if (is_numeric($type)) {
8490 if ($type == 1) { // We know product is a service
8491 switch ($selectVatComboMode) {
8492 case '1':
8493 $code_country .= ",'" . $societe_acheteuse->country_code . "'";
8494 break;
8495 case '2':
8496 $code_country = "'" . $societe_acheteuse->country_code . "'";
8497 break;
8498 }
8499 }
8500 } elseif (!$idprod) { // We don't know type of product
8501 switch ($selectVatComboMode) {
8502 case '1':
8503 $code_country .= ",'" . $societe_acheteuse->country_code . "'";
8504 break;
8505 case '2':
8506 $code_country = "'" . $societe_acheteuse->country_code . "'";
8507 break;
8508 }
8509 } else {
8510 $prodstatic = new Product($this->db);
8511 $prodstatic->fetch($idprod);
8512 if ($prodstatic->type == Product::TYPE_SERVICE) { // We know product is a service
8513 $code_country .= ",'" . $societe_acheteuse->country_code . "'";
8514 }
8515 }
8516 }
8517 }
8518
8519 // Now we load the list of VAT
8520 $this->load_cache_vatrates($code_country); // If no vat defined, return -1 with message into this->error
8521
8522 // Keep only the VAT qualified for $type_vat
8523 $arrayofvatrates = array();
8524 foreach ($this->cache_vatrates as $cachevalue) {
8525 if (empty($cachevalue['type_vat']) || $cachevalue['type_vat'] == $type_vat) {
8526 $arrayofvatrates[] = $cachevalue;
8527 }
8528 }
8529
8530 $num = count($arrayofvatrates);
8531 if ($num > 0) {
8532 // Define the vat rate to preselect (if defaulttx not forced so is -1 or '')
8533 if ($defaulttx < 0 || dol_strlen($defaulttx) == 0) {
8534 // Define a default thirdparty to use if the seller or buyer is not defined
8535 $tmpthirdparty = new Societe($this->db);
8536 $tmpthirdparty->country_code = $mysoc->country_code;
8537
8538 $defaulttx = get_default_tva(is_object($societe_vendeuse) ? $societe_vendeuse : $tmpthirdparty, (is_object($societe_acheteuse) ? $societe_acheteuse : $tmpthirdparty), $idprod);
8539 $defaultnpr = get_default_npr(is_object($societe_vendeuse) ? $societe_vendeuse : $tmpthirdparty, (is_object($societe_acheteuse) ? $societe_acheteuse : $tmpthirdparty), $idprod);
8540
8541 if (preg_match('/\‍((.*)\‍)/', $defaulttx, $reg)) {
8542 $defaultcode = $reg[1];
8543 $defaulttx = preg_replace('/\s*\‍(.*\‍)/', '', $defaulttx);
8544 }
8545 if (empty($defaulttx)) {
8546 $defaultnpr = 0;
8547 }
8548 }
8549
8550 // If we fails to find a default vat rate, we take the last one in list
8551 // Because they are sorted in ascending order, the last one will be the higher one (we suppose the higher one is the current rate)
8552 if ($defaulttx < 0 || dol_strlen($defaulttx) == 0) {
8553 if (!getDolGlobalString('MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS')) {
8554 // We take the last one found in list
8555 $defaulttx = $arrayofvatrates[$num - 1]['txtva'];
8556 } else {
8557 // We will use the rate defined into MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS
8558 $defaulttx = '';
8559 if (getDolGlobalString('MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS') != 'none') {
8560 $defaulttx = getDolGlobalString('MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS');
8561 }
8562 if (preg_match('/\‍((.*)\‍)/', $defaulttx, $reg)) {
8563 $defaultcode = $reg[1];
8564 $defaulttx = preg_replace('/\s*\‍(.*\‍)/', '', $defaulttx);
8565 }
8566 }
8567 }
8568
8569 // Disabled is true if the seller is not subject to VAT
8570 $disabled = false;
8571 $title = '';
8572 if (is_object($societe_vendeuse) && $societe_vendeuse->id == $mysoc->id && empty($societe_vendeuse->tva_assuj)) {
8573 // When we are seller and we do not use VAT, we want to force to disable VAT selection, except if EXPENSEREPORT_OVERRIDE_VAT is set
8574 // EXPENSEREPORT_OVERRIDE_VAT is a strange option that allow to override/enable VAT regardless of sellet vat option - needed for expense report if
8575 // expense report used for business expenses instead of using supplier invoices (but this is a very bad idea !)
8576 if (!getDolGlobalString('EXPENSEREPORT_OVERRIDE_VAT')) {
8577 $title = ' title="' . dol_escape_htmltag($langs->trans('VATIsNotUsed')) . '"';
8578 $disabled = true;
8579 }
8580 }
8581
8582 if (!$options_only) {
8583 $return .= '<select class="flat valignmiddle minwidth75imp maxwidth100 right" id="' . $htmlname . '" name="' . $htmlname . '"' . ($disabled ? ' disabled' : '') . $title . '>';
8584 }
8585
8586 $selectedfound = false;
8587 foreach ($arrayofvatrates as $rate) {
8588 // Keep only 0 if seller is not subject to VAT
8589 if ($disabled && $rate['txtva'] != 0) {
8590 continue;
8591 }
8592
8593 // Define key to use into select list
8594 $key = $rate['txtva'];
8595 $key .= $rate['nprtva'] ? '*' : '';
8596 if ($mode > 0 && $rate['code']) {
8597 $key .= ' (' . $rate['code'] . ')';
8598 }
8599 if ($mode < 0) {
8600 $key = $rate['rowid'];
8601 }
8602
8603 $return .= '<option value="' . $key . '" data-vatid="'.$rate['rowid'].'"';
8604 if (!$selectedfound) {
8605 if ($defaultcode) { // If defaultcode is defined, we used it in priority to select combo option instead of using rate+npr flag
8606 if ($defaultcode == $rate['code']) {
8607 $return .= ' selected';
8608 $selectedfound = true;
8609 }
8610 } elseif ($rate['txtva'] == $defaulttx && $rate['nprtva'] == $defaultnpr) {
8611 $return .= ' selected';
8612 $selectedfound = true;
8613 }
8614 }
8615 $return .= '>';
8616
8617 // Show label of VAT
8618 if ($mysoc->country_code == 'IN' || getDolGlobalString('MAIN_VAT_LABEL_IS_POSITIVE_RATES')) {
8619 // Label with all localtax and code. For example: x.y / a.b / c.d (CODE)'
8620 $return .= $rate['labelpositiverates'];
8621 } else {
8622 // Simple label
8623 $return .= vatrate($rate['label']);
8624 }
8625
8626 //$return.=($rate['code']?' '.$rate['code']:'');
8627 $return .= (empty($rate['code']) && $rate['nprtva']) ? ' *' : ''; // We show the * (old behaviour only if new vat code is not used)
8628
8629 $return .= '</option>';
8630 }
8631
8632 if (!$options_only) {
8633 $return .= '</select>';
8634 //$return .= ajax_combobox($htmlname); // This break for the moment the dynamic autoselection of a value when selecting a product in object lines
8635 }
8636 } else {
8637 $return .= $this->error;
8638 }
8639
8640 $this->num = $num;
8641 return $return;
8642 }
8643
8644
8645 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
8646
8671 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 = 0, $addplusone = '', $adddateof = '')
8672 {
8673 // phpcs:enable
8674 dol_syslog(__METHOD__ . ': using select_date is deprecated. Use selectDate instead.', LOG_WARNING);
8675 $retstring = $this->selectDate($set_time, $prefix, $h, $m, $empty, $form_name, $d, $addnowlink, $disabled, $fullday, $addplusone, $adddateof);
8676 if (!empty($nooutput)) {
8677 return $retstring;
8678 }
8679 print $retstring;
8680
8681 return '';
8682 }
8683
8699 public function selectDateToDate($set_time = '', $set_time_end = '', $prefix = 're', $empty = 0, $forcenewline = 0)
8700 {
8701 global $langs;
8702
8703 $ret = $this->selectDate($set_time, $prefix . '_start', 0, 0, $empty, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("from"), 'tzuserrel');
8704 if ($forcenewline) {
8705 $ret .= '<br>';
8706 }
8707 $ret .= $this->selectDate($set_time_end, $prefix . '_end', 0, 0, $empty, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("to"), 'tzuserrel');
8708 return $ret;
8709 }
8710
8739 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', $calendarpicto = '')
8740 {
8741 global $conf, $langs;
8742
8743 if ($gm === 'auto') {
8744 $gm = (empty($conf) ? 'tzserver' : $conf->tzuserinputkey);
8745 }
8746
8747 $retstring = '';
8748
8749 if ($prefix == '') {
8750 $prefix = 're';
8751 }
8752 if ($h == '') {
8753 $h = 0;
8754 }
8755 if ($m == '') {
8756 $m = 0;
8757 }
8758 $emptydate = 0;
8759 $emptyhours = 0;
8760 if ($stepminutes <= 0 || $stepminutes > 30) {
8761 $stepminutes = 1;
8762 }
8763 if ($empty == 1) {
8764 $emptydate = 1;
8765 $emptyhours = 1;
8766 }
8767 if ($empty == 2) {
8768 $emptydate = 0;
8769 $emptyhours = 1;
8770 }
8771 $orig_set_time = $set_time;
8772
8773 if ($set_time === '' && $emptydate == 0) {
8774 include_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php';
8775 if ($gm == 'tzuser' || $gm == 'tzuserrel') {
8776 $set_time = dol_now($gm);
8777 } else {
8778 $set_time = dol_now('tzuser') - (getServerTimeZoneInt('now') * 3600); // set_time must be relative to PHP server timezone
8779 }
8780 }
8781
8782 // Analysis of the preselected date
8783 $reg = array();
8784 $shour = '';
8785 $smin = '';
8786 $ssec = '';
8787 if (!empty($set_time) && preg_match('/^([0-9]+)\-([0-9]+)\-([0-9]+)\s?([0-9]+)?:?([0-9]+)?/', (string) $set_time, $reg)) { // deprecated usage
8788 // Date format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'
8789 $syear = (!empty($reg[1]) ? $reg[1] : '');
8790 $smonth = (!empty($reg[2]) ? $reg[2] : '');
8791 $sday = (!empty($reg[3]) ? $reg[3] : '');
8792 $shour = (!empty($reg[4]) ? $reg[4] : '');
8793 $smin = (!empty($reg[5]) ? $reg[5] : '');
8794 } elseif (strval($set_time) != '' && $set_time != -1) {
8795 // set_time est un timestamps (0 possible)
8796 $syear = dol_print_date($set_time, "%Y", $gm);
8797 $smonth = dol_print_date($set_time, "%m", $gm);
8798 $sday = dol_print_date($set_time, "%d", $gm);
8799 if ($orig_set_time != '') {
8800 $shour = dol_print_date($set_time, "%H", $gm);
8801 $smin = dol_print_date($set_time, "%M", $gm);
8802 $ssec = dol_print_date($set_time, "%S", $gm);
8803 }
8804 } else {
8805 // Date est '' ou vaut -1
8806 $syear = '';
8807 $smonth = '';
8808 $sday = '';
8809 $shour = getDolGlobalString('MAIN_DEFAULT_DATE_HOUR', ($h == -1 ? '23' : ''));
8810 $smin = getDolGlobalString('MAIN_DEFAULT_DATE_MIN', ($h == -1 ? '59' : ''));
8811 $ssec = getDolGlobalString('MAIN_DEFAULT_DATE_SEC', ($h == -1 ? '59' : ''));
8812 }
8813 if ($h == 3 || $h == 4) {
8814 $shour = '';
8815 }
8816 if ($m == 3) {
8817 $smin = '';
8818 }
8819
8820 $nowgmt = dol_now('gmt');
8821 //var_dump(dol_print_date($nowgmt, 'dayhourinputnoreduce', 'tzuserrel'));
8822
8823 // You can set MAIN_POPUP_CALENDAR to 'eldy' or 'jquery'
8824 $usecalendar = 'combo';
8825 if (!empty($conf->use_javascript_ajax) && (!getDolGlobalString('MAIN_POPUP_CALENDAR') || getDolGlobalString('MAIN_POPUP_CALENDAR') != "none")) {
8826 $usecalendar = ((!getDolGlobalString('MAIN_POPUP_CALENDAR') || getDolGlobalString('MAIN_POPUP_CALENDAR') == 'eldy') ? 'jquery' : getDolGlobalString("MAIN_POPUP_CALENDAR"));
8827 }
8828 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
8829 // If we use a text browser or screen reader, we use the 'combo' date selector
8830 $usecalendar = 'html';
8831 }
8832
8833 if ($d) {
8834 // Show date with popup
8835 if ($usecalendar != 'combo') {
8836 // Set $format and $formatjs and $formatjquery
8837 $reduceformat = (!empty($conf->dol_optimize_smallscreen) ? 1 : 0); // Test on original $format param.
8838 if ($reduceformat) {
8839 $format = str_replace('%Y', '%y', $langs->transnoentitiesnoconv("FormatDateShortInput")); // FormatDateShortInput for dol_print_date / FormatDateShortJavaInput that is same for javascript
8840 $formatjslong = $langs->transnoentitiesnoconv("FormatDateShortJavaInput"); // don't trust the name
8841 $formatjs = str_replace('yyyy', 'yy', $langs->transnoentitiesnoconv("FormatDateShortJavaInput"));
8842 $formatjquery = str_replace('yyyy', 'yy', $langs->trans("FormatDateShortJQueryInput"));
8843 } else {
8844 $format = $langs->transnoentitiesnoconv("FormatDateShortInput"); // FormatDateShortInput for dol_print_date is same than FormatDateShortJavaInput for javascript
8845 $formatjslong = $langs->transnoentitiesnoconv("FormatDateShortJavaInput"); // don't trust the name
8846 $formatjs = $langs->transnoentitiesnoconv("FormatDateShortJavaInput"); // FormatDateShortInput for dol_print_date is same than FormatDateShortJavaInput for javascript
8847 $formatjquery = $langs->trans("FormatDateShortJQueryInput");
8848 }
8849
8850 // Set formatted_date (for example: '%d/%m/%Y', '%m-%d-%y', ...
8851 $formatted_date = '';
8852 if (strval($set_time) != '' && $set_time != -1) {
8853 $formatted_date = dol_print_date($set_time, $format, $gm); // FormatDateShortInput for dol_print_date / FormatDateShortJavaInput that is same for javascript
8854 }
8855
8856 // Calendrier popup version eldy
8857 if ($usecalendar == "eldy") {
8858 // To have this manager working back, you must retrieve all functions showDP child found into the lib_head.js of v4 for example
8859 // and load the js that contains them so the call of showDP will works.
8860 /*
8861 // Input area to enter date manually
8862 $retstring .= '<!-- datepicker usecalendar=eldy --><input id="' . $prefix . '" name="' . $prefix . '" type="text" class="maxwidthdate center" maxlength="11" value="' . $formatted_date . '"';
8863 $retstring .= ($disabled ? ' disabled' : '');
8864 $retstring .= ' onChange="dpChangeDay(\'' . dol_escape_js($prefix) . '\',\'' . dol_escape_js($formatjslong")) . '\'); "'; // FormatDateShortInput for dol_print_date is same than FormatDateShortJavaInput for javascript
8865 $retstring .= ' autocomplete="off">';
8866
8867 // Icon calendar
8868 $retstringbuttom = '';
8869 if (!$disabled) {
8870 $retstringbuttom = '<button id="' . $prefix . 'Button" type="button" class="dpInvisibleButtons"';
8871 $base = DOL_URL_ROOT . '/core/';
8872 $retstringbuttom .= ' onClick="showDP(\'' . dol_escape_js($base) . '\',\'' . dol_escape_js($prefix) . '\',\'' . dol_escape_js($langs->trans("FormatDateShortJavaInput")) . '\',\'' . dol_escape_js($langs->defaultlang) . '\');"';
8873 $retstringbuttom .= '>' . img_object($langs->trans("SelectDate"), 'calendarday', 'class="datecallink paddingright"') . '</button>';
8874 } else {
8875 $retstringbuttom = '<button id="' . $prefix . 'Button" type="button" class="dpInvisibleButtons">' . img_object($langs->trans("Disabled"), 'calendarday', 'class="datecallink paddingright"') . '</button>';
8876 }
8877 $retstring = $retstringbuttom . $retstring;
8878
8879 $retstring .= '<input type="hidden" id="' . $prefix . 'day" name="' . $prefix . 'day" value="' . $sday . '">' . "\n";
8880 $retstring .= '<input type="hidden" id="' . $prefix . 'month" name="' . $prefix . 'month" value="' . $smonth . '">' . "\n";
8881 $retstring .= '<input type="hidden" id="' . $prefix . 'year" name="' . $prefix . 'year" value="' . $syear . '">' . "\n";
8882 */
8883 } elseif ($usecalendar == 'jquery' || $usecalendar == 'html') {
8884 if (!$disabled && $usecalendar != 'html') {
8885 // Output javascript for datepicker
8886 $minYear = getDolGlobalInt('MIN_YEAR_SELECT_DATE', (idate('Y') - 100));
8887 $maxYear = getDolGlobalInt('MAX_YEAR_SELECT_DATE', (idate('Y') + 100));
8888
8889 $retstring .= '<!-- datepicker usecalendar='.$usecalendar.' --><script nonce="' . getNonce() . '" type="text/javascript">';
8890 $retstring .= "$(function(){ $('#" . $prefix . "').datepicker({
8891 dateFormat: '" . dol_escape_js($formatjquery) . "',
8892 autoclose: true,
8893 todayHighlight: true,
8894 yearRange: '" . $minYear . ":" . $maxYear . "',";
8895 if (!empty($conf->dol_use_jmobile)) {
8896 $retstring .= "
8897 beforeShow: function (input, datePicker) {
8898 input.disabled = true;
8899 },
8900 onClose: function (dateText, datePicker) {
8901 this.disabled = false;
8902 },
8903 ";
8904 }
8905 // Note: We don't need monthNames, monthNamesShort, dayNames, dayNamesShort, dayNamesMin, they are set globally on datepicker component in lib_head.js.php
8906 if (!getDolGlobalString('MAIN_POPUP_CALENDAR_ON_FOCUS')) {
8907 $buttonImage = $calendarpicto ?: DOL_URL_ROOT . "/theme/" . dol_escape_js($conf->theme) . "/img/object_calendarday.png"; // Correctly wrapped in '' below @phan-suppress-current-line FunctionMissingSingleQuoteWrapping
8908 $retstring .= "
8909 showOn: 'button', /* both has problem with autocompletion */
8910 buttonImage: '" . $buttonImage . "',
8911 buttonImageOnly: true";
8912 }
8913 $retstring .= "
8914 }) });";
8915 $retstring .= "</script>";
8916 }
8917
8918 // Input area to enter date manually
8919 $retstring .= '<div class="nowraponall inline-block divfordateinput">';
8920 $retstring .= '<input id="'.$prefix.'" name="'.$prefix.'" type="'.($usecalendar == 'html' ? "date" : "text").'" class="maxwidthdate'.(getDolUserString('MAIN_OPTIMIZEFORTEXTBROWSER') ? ' textbrowser' : '').' center" maxlength="11" value="'.$formatted_date.'"';
8921 $retstring .= ($disabled ? ' disabled' : '');
8922 $retstring .= ($placeholder ? ' placeholder="' . dol_escape_htmltag($placeholder) . '"' : '');
8923 $retstring .= ' onChange="dpChangeDay(\'' . dol_escape_js($prefix) . '\',\'' . dol_escape_js($usecalendar == 'html' ? 'yyyy-mm-dd' : $formatjslong) . '\'); "'; // FormatDateShortInput for dol_print_date / FormatDateShortJavaInput that is same for javascript
8924 $retstring .= ' autocomplete="off">';
8925
8926 // Icon calendar
8927 if ($disabled) {
8928 $retstringbutton = '<button id="' . $prefix . 'Button" type="button" class="dpInvisibleButtons">' . img_object($langs->trans("Disabled"), 'calendarday', 'class="datecallink ui-datepicker-notrigger"') . '</button>';
8929 $retstring .= $retstringbutton;
8930 }
8931
8932 $retstring .= '</div>';
8933 $retstring .= '<input type="hidden" id="' . $prefix . 'day" name="' . $prefix . 'day" value="' . $sday . '">' . "\n";
8934 $retstring .= '<input type="hidden" id="' . $prefix . 'month" name="' . $prefix . 'month" value="' . $smonth . '">' . "\n";
8935 $retstring .= '<input type="hidden" id="' . $prefix . 'year" name="' . $prefix . 'year" value="' . $syear . '">' . "\n";
8936 } else {
8937 $retstring .= "Bad value of MAIN_POPUP_CALENDAR";
8938 }
8939 } else {
8940 // Show date with combo selects
8941 // Day
8942 $retstring .= '<select' . ($disabled ? ' disabled' : '') . ' class="flat valignmiddle maxwidth50imp" id="' . $prefix . 'day" name="' . $prefix . 'day">';
8943
8944 if ($emptydate || $set_time == -1) {
8945 $retstring .= '<option value="0" selected>&nbsp;</option>';
8946 }
8947
8948 for ($day = 1; $day <= 31; $day++) {
8949 $retstring .= '<option value="' . $day . '"' . ($day == $sday ? ' selected' : '') . '>' . $day . '</option>';
8950 }
8951
8952 $retstring .= "</select>";
8953
8954 $retstring .= '<select' . ($disabled ? ' disabled' : '') . ' class="flat valignmiddle maxwidth75imp" id="' . $prefix . 'month" name="' . $prefix . 'month">';
8955 if ($emptydate || $set_time == -1) {
8956 $retstring .= '<option value="0" selected>&nbsp;</option>';
8957 }
8958
8959 // Month
8960 for ($month = 1; $month <= 12; $month++) {
8961 $retstring .= '<option value="' . $month . '"' . ($month == $smonth ? ' selected' : '') . '>';
8962 $retstring .= dol_print_date(mktime(12, 0, 0, $month, 1, 2000), "%b");
8963 $retstring .= "</option>";
8964 }
8965 $retstring .= "</select>";
8966
8967 // Year
8968 if ($emptydate || $set_time == -1) {
8969 $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 . '">';
8970 } else {
8971 $retstring .= '<select' . ($disabled ? ' disabled' : '') . ' class="flat valignmiddle maxwidth75imp" id="' . $prefix . 'year" name="' . $prefix . 'year">';
8972
8973 $syear = (int) $syear;
8974 for ($year = $syear - 10; $year < (int) $syear + 10; $year++) {
8975 $retstring .= '<option value="' . $year . '"' . ($year == $syear ? ' selected' : '') . '>' . $year . '</option>';
8976 }
8977 $retstring .= "</select>\n";
8978 }
8979 }
8980 }
8981
8982 if ($d && $h) {
8983 $retstring .= (($h == 2 || $h == 4) ? '<br>' : ' ');
8984 $retstring .= '<span class="nowraponall">';
8985 }
8986
8987 if ($h) {
8988 $hourstart = 0;
8989 $hourend = 24;
8990 if ($openinghours != '') {
8991 $openinghours = explode(',', $openinghours);
8992 $hourstart = $openinghours[0];
8993 $hourend = $openinghours[1];
8994 if ($hourend < $hourstart) {
8995 $hourend = $hourstart;
8996 }
8997 }
8998
8999 // Show hour
9000 $retstring .= '<select' . ($disabled ? ' disabled' : '') . ' class="flat valignmiddle maxwidth75 '; // Note maxwidth50 generates truncated number on some desktops even with same version of chrome that works on others
9001 $retstring .= ($fullday ? $fullday . 'hour' : '') . '" id="' . $prefix . 'hour" name="' . $prefix . 'hour">';
9002 if ($emptyhours) {
9003 $retstring .= '<option value="-1">&nbsp;</option>';
9004 }
9005 for ($hour = $hourstart; $hour < $hourend; $hour++) {
9006 if (strlen($hour) < 2) {
9007 $hour = "0" . $hour;
9008 }
9009 $retstring .= '<option value="' . $hour . '"' . (($hour == $shour) ? ' selected' : '') . '>' . $hour;
9010 $retstring .= '</option>';
9011 }
9012 $retstring .= '</select>';
9013
9014 if ($disabled) {
9015 $retstring .= '<input type="hidden" id="' . $prefix . 'hour" name="' . $prefix . 'hour" value="' . $shour . '">' . "\n";
9016 }
9017 if ($m) {
9018 $retstring .= ":";
9019 }
9020 }
9021
9022 if ($m) {
9023 // Show minutes
9024 $retstring .= '<select ' . ($disabled ? ' disabled' : '') . ' class="flat valignmiddle maxwidth75 '; // Note maxwidth50 generates truncated number on some desktops even with same version of chrome that works on others
9025 $retstring .= ($fullday ? $fullday . 'min' : '') . '" id="' . $prefix . 'min" name="' . $prefix . 'min">';
9026 if ($emptyhours) {
9027 $retstring .= '<option value="-1">&nbsp;</option>';
9028 }
9029 for ($min = 0; $min < 60; $min += $stepminutes) {
9030 $min_str = sprintf("%02d", $min);
9031 $retstring .= '<option value="' . $min_str . '"' . (($min_str == $smin) ? ' selected' : '') . '>' . $min_str . '</option>';
9032 }
9033 $retstring .= '</select>';
9034 if ($disabled) {
9035 $retstring .= '<input type="hidden" id="' . $prefix . 'min" name="' . $prefix . 'min" value="' . $smin . '">' . "\n";
9036 }
9037 // Add also seconds
9038 $retstring .= '<input type="hidden" name="' . $prefix . 'sec" value="' . $ssec . '">';
9039 }
9040
9041 if ($d && $h) {
9042 $retstring .= '</span>';
9043 }
9044
9045 // Add a "Now" link
9046 if (!empty($conf->use_javascript_ajax) && $addnowlink && !$disabled) {
9047 // Script which will be inserted in the onClick of the "Now" link
9048 $reset_scripts = "";
9049 if ($addnowlink == 2) { // local computer time
9050 // pad add leading 0 on numbers
9051 $reset_scripts .= "Number.prototype.pad = function(size) {
9052 var s = String(this);
9053 while (s.length < (size || 2)) {s = '0' + s;}
9054 return s;
9055 };
9056 var d = new Date();";
9057 }
9058
9059 // Generate the date part, depending on the use or not of the javascript calendar
9060 if ($addnowlink == 1) { // server time expressed in user time setup
9061 $reset_scripts .= 'jQuery(\'#' . $prefix . '\').val(\'' . dol_print_date($nowgmt, 'day', 'tzuserrel') . '\');';
9062 $reset_scripts .= 'jQuery(\'#' . $prefix . 'day\').val(\'' . dol_print_date($nowgmt, '%d', 'tzuserrel') . '\');';
9063 $reset_scripts .= 'jQuery(\'#' . $prefix . 'month\').val(\'' . dol_print_date($nowgmt, '%m', 'tzuserrel') . '\');';
9064 $reset_scripts .= 'jQuery(\'#' . $prefix . 'year\').val(\'' . dol_print_date($nowgmt, '%Y', 'tzuserrel') . '\');';
9065 } elseif ($addnowlink == 2) {
9066 /* Disabled because the output does not use the string format defined by FormatDateShort key to forge the value into #prefix.
9067 * This break application for foreign languages.
9068 $reset_scripts .= 'jQuery(\'#'.$prefix.'\').val(d.toLocaleDateString(\''.str_replace('_', '-', $langs->defaultlang).'\'));';
9069 $reset_scripts .= 'jQuery(\'#'.$prefix.'day\').val(d.getDate().pad());';
9070 $reset_scripts .= 'jQuery(\'#'.$prefix.'month\').val(parseInt(d.getMonth().pad()) + 1);';
9071 $reset_scripts .= 'jQuery(\'#'.$prefix.'year\').val(d.getFullYear());';
9072 */
9073 $reset_scripts .= 'jQuery(\'#' . $prefix . '\').val(\'' . dol_print_date($nowgmt, 'day', 'tzuserrel') . '\');';
9074 $reset_scripts .= 'jQuery(\'#' . $prefix . 'day\').val(\'' . dol_print_date($nowgmt, '%d', 'tzuserrel') . '\');';
9075 $reset_scripts .= 'jQuery(\'#' . $prefix . 'month\').val(\'' . dol_print_date($nowgmt, '%m', 'tzuserrel') . '\');';
9076 $reset_scripts .= 'jQuery(\'#' . $prefix . 'year\').val(\'' . dol_print_date($nowgmt, '%Y', 'tzuserrel') . '\');';
9077 }
9078 /*if ($usecalendar == "eldy")
9079 {
9080 $base=DOL_URL_ROOT.'/core/';
9081 $reset_scripts .= 'resetDP(\''.$base.'\',\''.$prefix.'\',\''.$langs->trans("FormatDateShortJavaInput").'\',\''.$langs->defaultlang.'\');';
9082 }
9083 else
9084 {
9085 $reset_scripts .= 'this.form.elements[\''.$prefix.'day\'].value=formatDate(new Date(), \'d\'); ';
9086 $reset_scripts .= 'this.form.elements[\''.$prefix.'month\'].value=formatDate(new Date(), \'M\'); ';
9087 $reset_scripts .= 'this.form.elements[\''.$prefix.'year\'].value=formatDate(new Date(), \'yyyy\'); ';
9088 }*/
9089 // Update the hour part
9090 if ($h) {
9091 if ($fullday) {
9092 $reset_scripts .= " if (jQuery('#fullday:checked').val() == null) {";
9093 }
9094 //$reset_scripts .= 'this.form.elements[\''.$prefix.'hour\'].value=formatDate(new Date(), \'HH\'); ';
9095 if ($addnowlink == 1) {
9096 $reset_scripts .= 'jQuery(\'#' . $prefix . 'hour\').val(\'' . dol_print_date($nowgmt, '%H', 'tzuserrel') . '\');';
9097 $reset_scripts .= 'jQuery(\'#' . $prefix . 'hour\').change();';
9098 } elseif ($addnowlink == 2) {
9099 $reset_scripts .= 'jQuery(\'#' . $prefix . 'hour\').val(d.getHours().pad());';
9100 $reset_scripts .= 'jQuery(\'#' . $prefix . 'hour\').change();';
9101 }
9102
9103 if ($fullday) {
9104 $reset_scripts .= ' } ';
9105 }
9106 }
9107 // Update the minute part
9108 if ($m) {
9109 if ($fullday) {
9110 $reset_scripts .= " if (jQuery('#fullday:checked').val() == null) {";
9111 }
9112 //$reset_scripts .= 'this.form.elements[\''.$prefix.'min\'].value=formatDate(new Date(), \'mm\'); ';
9113 if ($addnowlink == 1) {
9114 $reset_scripts .= 'jQuery(\'#' . $prefix . 'min\').val(\'' . dol_print_date($nowgmt, '%M', 'tzuserrel') . '\');';
9115 $reset_scripts .= 'jQuery(\'#' . $prefix . 'min\').change();';
9116 } elseif ($addnowlink == 2) {
9117 $reset_scripts .= 'jQuery(\'#' . $prefix . 'min\').val(d.getMinutes().pad());';
9118 $reset_scripts .= 'jQuery(\'#' . $prefix . 'min\').change();';
9119 }
9120 if ($fullday) {
9121 $reset_scripts .= ' } ';
9122 }
9123 }
9124 // If reset_scripts is not empty, print the link with the reset_scripts in the onClick
9125 if ($reset_scripts && !getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
9126 $retstring .= ' <button class="dpInvisibleButtons datenowlink" id="' . $prefix . 'ButtonNow" type="button" name="_useless" value="now" onClick="' . $reset_scripts . '">';
9127 $retstring .= $langs->trans("Now");
9128 $retstring .= '</button> ';
9129 }
9130 }
9131
9132 // Add a "Plus one hour" link
9133 if ($conf->use_javascript_ajax && $addplusone && !$disabled) {
9134 // Script which will be inserted in the onClick of the "Add plusone" link
9135 $reset_scripts = "";
9136
9137 // Generate the date part, depending on the use or not of the javascript calendar
9138 $reset_scripts .= 'jQuery(\'#' . $prefix . '\').val(\'' . dol_print_date($nowgmt, 'dayinputnoreduce', 'tzuserrel') . '\');';
9139 $reset_scripts .= 'jQuery(\'#' . $prefix . 'day\').val(\'' . dol_print_date($nowgmt, '%d', 'tzuserrel') . '\');';
9140 $reset_scripts .= 'jQuery(\'#' . $prefix . 'month\').val(\'' . dol_print_date($nowgmt, '%m', 'tzuserrel') . '\');';
9141 $reset_scripts .= 'jQuery(\'#' . $prefix . 'year\').val(\'' . dol_print_date($nowgmt, '%Y', 'tzuserrel') . '\');';
9142 // Update the hour part
9143 if ($h) {
9144 if ($fullday) {
9145 $reset_scripts .= " if (jQuery('#fullday:checked').val() == null) {";
9146 }
9147 $reset_scripts .= 'jQuery(\'#' . $prefix . 'hour\').val(\'' . dol_print_date($nowgmt, '%H', 'tzuserrel') . '\');';
9148 if ($fullday) {
9149 $reset_scripts .= ' } ';
9150 }
9151 }
9152 // Update the minute part
9153 if ($m) {
9154 if ($fullday) {
9155 $reset_scripts .= " if (jQuery('#fullday:checked').val() == null) {";
9156 }
9157 $reset_scripts .= 'jQuery(\'#' . $prefix . 'min\').val(\'' . dol_print_date($nowgmt, '%M', 'tzuserrel') . '\');';
9158 if ($fullday) {
9159 $reset_scripts .= ' } ';
9160 }
9161 }
9162 // If reset_scripts is not empty, print the link with the reset_scripts in the onClick
9163 if ($reset_scripts && empty($conf->dol_optimize_smallscreen)) {
9164 $retstring .= ' <button class="dpInvisibleButtons datenowlink" id="' . $prefix . 'ButtonPlusOne" type="button" name="_useless2" value="plusone" onClick="' . $reset_scripts . '">';
9165 $retstring .= $langs->trans("DateStartPlusOne");
9166 $retstring .= '</button> ';
9167 }
9168 }
9169
9170 // Add a link to set data
9171 if ($conf->use_javascript_ajax && !empty($adddateof) && !$disabled) {
9172 if (!is_array($adddateof)) {
9173 $arrayofdateof = array(array('adddateof' => $adddateof, 'labeladddateof' => $labeladddateof));
9174 } else {
9175 $arrayofdateof = $adddateof;
9176 }
9177 foreach ($arrayofdateof as $valuedateof) {
9178 $tmpadddateof = empty($valuedateof['adddateof']) ? 0 : $valuedateof['adddateof'];
9179 $tmplabeladddateof = empty($valuedateof['labeladddateof']) ? '' : $valuedateof['labeladddateof'];
9180 $tmparray = dol_getdate($tmpadddateof);
9181 if (empty($tmplabeladddateof)) {
9182 $tmplabeladddateof = $langs->trans("DateInvoice");
9183 }
9184 $reset_scripts = 'console.log(\'Click on now link\'); ';
9185 $reset_scripts .= 'jQuery(\'#'.$prefix.'\').val(\''.dol_print_date($tmpadddateof, 'dayinputnoreduce').'\');';
9186 $reset_scripts .= 'jQuery(\'#'.$prefix.'day\').val(\''.$tmparray['mday'].'\');';
9187 $reset_scripts .= 'jQuery(\'#'.$prefix.'month\').val(\''.$tmparray['mon'].'\');';
9188 $reset_scripts .= 'jQuery(\'#'.$prefix.'year\').val(\''.$tmparray['year'].'\');';
9189 $retstring .= ' - <button class="dpInvisibleButtons datenowlink" id="dateofinvoice" type="button" name="_dateofinvoice" value="now" onclick="'.$reset_scripts.'">'.$tmplabeladddateof.'</button>';
9190 }
9191 }
9192
9193 return $retstring;
9194 }
9195
9205 public function selectTypeDuration($prefix, $selected = 'i', $excludetypes = array(), $morecss = 'minwidth75 maxwidth100')
9206 {
9207 global $langs;
9208
9209 $TDurationTypes = $this->getDurationTypes($langs);
9210
9211 // Removed undesired duration types
9212 foreach ($excludetypes as $value) {
9213 unset($TDurationTypes[$value]);
9214 }
9215
9216 $retstring = '<select class="flat'.($morecss ? ' '.$morecss : '').'" id="select_' . $prefix . 'type_duration" name="' . $prefix . 'type_duration">';
9217 foreach ($TDurationTypes as $key => $typeduration) {
9218 $retstring .= '<option value="' . $key . '"';
9219 if ($key == $selected) {
9220 $retstring .= " selected";
9221 }
9222 $retstring .= ">" . $typeduration . "</option>";
9223 }
9224 $retstring .= "</select>";
9225
9226 $retstring .= ajax_combobox('select_' . $prefix . 'type_duration');
9227
9228 return $retstring;
9229 }
9230
9231 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
9232
9246 public function select_duration($prefix, $iSecond = '', $disabled = 0, $typehour = 'select', $minunderhours = 0, $nooutput = 0)
9247 {
9248 // phpcs:enable
9249 global $langs;
9250
9251 $retstring = '<span class="nowraponall">';
9252
9253 $hourSelected = '';
9254 $minSelected = '';
9255
9256 // Hours
9257 if ($iSecond != '') {
9258 require_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php';
9259
9260 $hourSelected = convertSecondToTime($iSecond, 'allhour');
9261 $minSelected = convertSecondToTime($iSecond, 'min');
9262 }
9263
9264 if ($typehour == 'select') {
9265 $retstring .= '<select class="flat" id="select_' . $prefix . 'hour" name="' . $prefix . 'hour"' . ($disabled ? ' disabled' : '') . '>';
9266 for ($hour = 0; $hour < 25; $hour++) { // For a duration, we allow 24 hours
9267 $retstring .= '<option value="' . $hour . '"';
9268 if (is_numeric($hourSelected) && $hourSelected == $hour) {
9269 $retstring .= " selected";
9270 }
9271 $retstring .= ">" . $hour . "</option>";
9272 }
9273 $retstring .= "</select>";
9274 } elseif ($typehour == 'text' || $typehour == 'textselect') {
9275 $retstring .= '<input placeholder="' . $langs->trans('HourShort') . '" type="number" min="0" name="' . $prefix . 'hour"' . ($disabled ? ' disabled' : '') . ' class="flat maxwidth50 inputhour right" value="' . (($hourSelected != '') ? ((int) $hourSelected) : '') . '">';
9276 } else {
9277 return 'BadValueForParameterTypeHour';
9278 }
9279
9280 if ($typehour != 'text') {
9281 $retstring .= ' ' . $langs->trans('HourShort');
9282 } else {
9283 $retstring .= '<span class="">:</span>';
9284 }
9285
9286 // Minutes
9287 if ($minunderhours) {
9288 $retstring .= '<br>';
9289 } else {
9290 if ($typehour != 'text') {
9291 $retstring .= '<span class="hideonsmartphone">&nbsp;</span>';
9292 }
9293 }
9294
9295 if ($typehour == 'select' || $typehour == 'textselect') {
9296 $retstring .= '<select class="flat" id="select_' . $prefix . 'min" name="' . $prefix . 'min"' . ($disabled ? ' disabled' : '') . '>';
9297 $step = getDolGlobalInt('MAIN_DURATION_STEP');
9298 $duration_step = ($step > 0) ? $step : 5;
9299 for ($min = 0; $min <= 59; $min += $duration_step) {
9300 $retstring .= '<option value="' . $min . '"';
9301 if (is_numeric($minSelected) && $minSelected == $min) {
9302 $retstring .= ' selected';
9303 }
9304 $retstring .= '>' . $min . '</option>';
9305 }
9306 $retstring .= "</select>";
9307 } elseif ($typehour == 'text') {
9308 $retstring .= '<input placeholder="' . $langs->trans('MinuteShort') . '" type="number" min="0" name="' . $prefix . 'min"' . ($disabled ? ' disabled' : '') . ' class="flat maxwidth50 inputminute right" value="' . (($minSelected != '') ? ((int) $minSelected) : '') . '">';
9309 }
9310
9311 if ($typehour != 'text') {
9312 $retstring .= ' ' . $langs->trans('MinuteShort');
9313 }
9314
9315 $retstring .= "</span>";
9316
9317 if (!empty($nooutput)) {
9318 return $retstring;
9319 }
9320
9321 print $retstring;
9322
9323 return '';
9324 }
9325
9345 public function selectTickets($selected = '', $htmlname = 'ticketid', $filtertype = '', $limit = 0, $status = 1, $selected_input_value = '', $hidelabel = 0, $ajaxoptions = array(), $socid = 0, $showempty = '1', $forcecombo = 0, $morecss = '', $selected_combinations = null, $nooutput = 0)
9346 {
9347 global $langs, $conf;
9348
9349 $out = '';
9350
9351 // check parameters
9352 if (is_null($ajaxoptions)) {
9353 $ajaxoptions = array();
9354 }
9355
9356 if (!empty($conf->use_javascript_ajax) && getDolGlobalString('TICKET_USE_SEARCH_TO_SELECT')) {
9357 $placeholder = '';
9358
9359 if ($selected && empty($selected_input_value)) {
9360 require_once DOL_DOCUMENT_ROOT . '/ticket/class/ticket.class.php';
9361 $tickettmpselect = new Ticket($this->db);
9362 $tickettmpselect->fetch((int) $selected);
9363 $selected_input_value = $tickettmpselect->ref;
9364 unset($tickettmpselect);
9365 }
9366
9367 $urloption = '';
9368 $out .= ajax_autocompleter($selected, $htmlname, DOL_URL_ROOT . '/ticket/ajax/tickets.php', $urloption, $conf->global->PRODUIT_USE_SEARCH_TO_SELECT, 1, $ajaxoptions);
9369
9370 if (empty($hidelabel)) {
9371 $out .= $langs->trans("RefOrLabel") . ' : ';
9372 } elseif ($hidelabel > 1) {
9373 $placeholder = ' placeholder="' . $langs->trans("RefOrLabel") . '"';
9374 if ($hidelabel == 2) {
9375 $out .= img_picto($langs->trans("Search"), 'search');
9376 }
9377 }
9378 $out .= '<input type="text" class="minwidth100" name="search_' . $htmlname . '" id="search_' . $htmlname . '" value="' . $selected_input_value . '"' . $placeholder . ' ' . (getDolGlobalString('PRODUCT_SEARCH_AUTOFOCUS') ? 'autofocus' : '') . ' />';
9379 if ($hidelabel == 3) {
9380 $out .= img_picto($langs->trans("Search"), 'search');
9381 }
9382 } else {
9383 $out .= $this->selectTicketsList($selected, $htmlname, $filtertype, $limit, '', $status, 0, $showempty, $forcecombo, $morecss);
9384 }
9385
9386 if (empty($nooutput)) {
9387 print $out;
9388 } else {
9389 return $out;
9390 }
9391 return '';
9392 }
9393
9394
9411 public function selectTicketsList($selected = '', $htmlname = 'ticketid', $filtertype = '', $limit = 20, $filterkey = '', $status = 1, $outputmode = 0, $showempty = '1', $forcecombo = 0, $morecss = '')
9412 {
9413 global $langs;
9414
9415 $out = '';
9416 $outarray = array();
9417
9418 $selectFields = " p.rowid, p.ref, p.message";
9419
9420 $sql = "SELECT ";
9421 $sql .= $this->db->sanitize($selectFields, 0, 0, 1);
9422 $sql .= " FROM " . $this->db->prefix() . "ticket as p";
9423 $sql .= ' WHERE p.entity IN (' . getEntity('ticket') . ')';
9424
9425 // Add criteria on ref/label
9426 if ($filterkey != '') {
9427 $sql .= ' AND (';
9428 $prefix = getDolGlobalString('TICKET_DONOTSEARCH_ANYWHERE') ? '' : '%'; // Can use index if TICKET_DONOTSEARCH_ANYWHERE is on
9429 // For natural search
9430 $search_crit = explode(' ', $filterkey);
9431 $i = 0;
9432 if (count($search_crit) > 1) {
9433 $sql .= "(";
9434 }
9435 foreach ($search_crit as $crit) {
9436 if ($i > 0) {
9437 $sql .= " AND ";
9438 }
9439 $sql .= "(p.ref LIKE '" . $this->db->escape($prefix . $crit) . "%' OR p.subject LIKE '" . $this->db->escape($prefix . $crit) . "%'";
9440 $sql .= ")";
9441 $i++;
9442 }
9443 if (count($search_crit) > 1) {
9444 $sql .= ")";
9445 }
9446 $sql .= ')';
9447 }
9448
9449 $sql .= $this->db->plimit($limit, 0);
9450
9451 // Build output string
9452 dol_syslog(get_class($this) . "::selectTicketsList search tickets", LOG_DEBUG);
9453 $result = $this->db->query($sql);
9454 if ($result) {
9455 require_once DOL_DOCUMENT_ROOT . '/ticket/class/ticket.class.php';
9456 require_once DOL_DOCUMENT_ROOT . '/core/lib/ticket.lib.php';
9457
9458 $num = $this->db->num_rows($result);
9459
9460 $events = array();
9461
9462 if (!$forcecombo) {
9463 include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
9464 $out .= ajax_combobox($htmlname, $events, getDolGlobalInt('TICKET_USE_SEARCH_TO_SELECT'));
9465 }
9466
9467 $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" id="' . $htmlname . '">';
9468
9469 $textifempty = '';
9470 // Do not use textifempty = ' ' or '&nbsp;' here, or search on key will search on ' key'.
9471 //if (!empty($conf->use_javascript_ajax) || $forcecombo) $textifempty='';
9472 if (getDolGlobalString('TICKET_USE_SEARCH_TO_SELECT')) {
9473 if ($showempty && !is_numeric($showempty)) {
9474 $textifempty = $langs->trans($showempty);
9475 } else {
9476 $textifempty .= $langs->trans("All");
9477 }
9478 } else {
9479 if ($showempty && !is_numeric($showempty)) {
9480 $textifempty = $langs->trans($showempty);
9481 }
9482 }
9483 if ($showempty) {
9484 $out .= '<option value="0" selected>' . $textifempty . '</option>';
9485 }
9486
9487 $i = 0;
9488 while ($num && $i < $num) {
9489 $opt = '';
9490 $optJson = array();
9491 $objp = $this->db->fetch_object($result);
9492
9493 $this->constructTicketListOption($objp, $opt, $optJson, $selected, $filterkey);
9494 '@phan-var-force array{key:string,value:mixed,type:int} $optJson';
9495 // Add new entry
9496 // "key" value of json key array is used by jQuery automatically as selected value
9497 // "label" value of json key array is used by jQuery automatically as text for combo box
9498 $out .= $opt;
9499 array_push($outarray, $optJson);
9500
9501 $i++;
9502 }
9503
9504 $out .= '</select>';
9505
9506 $this->db->free($result);
9507
9508 if (empty($outputmode)) {
9509 return $out;
9510 }
9511 return $outarray;
9512 } else {
9513 dol_print_error($this->db);
9514 }
9515
9516 return array();
9517 }
9518
9530 protected function constructTicketListOption(&$objp, &$opt, &$optJson, $selected, $filterkey = '')
9531 {
9532 $outkey = '';
9533 $outref = '';
9534 $outtype = '';
9535
9536 $outkey = $objp->rowid;
9537 $outref = $objp->ref;
9538
9539 $opt = '<option value="' . $objp->rowid . '"';
9540 $opt .= ($objp->rowid == $selected) ? ' selected' : '';
9541 $opt .= '>';
9542 $opt .= $objp->ref;
9543 $objRef = $objp->ref;
9544 if (!empty($filterkey) && $filterkey != '') {
9545 $objRef = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', $objRef, 1);
9546 }
9547
9548 $opt .= "</option>\n";
9549 $optJson = array('key' => $outkey, 'value' => $outref, 'type' => $outtype);
9550 }
9551
9571 public function selectProjects($selected = '', $htmlname = 'projectid', $filtertype = '', $limit = 0, $status = 1, $selected_input_value = '', $hidelabel = 0, $ajaxoptions = array(), $socid = 0, $showempty = '1', $forcecombo = 0, $morecss = '', $selected_combinations = null, $nooutput = 0)
9572 {
9573 global $langs, $conf;
9574
9575 $out = '';
9576
9577 // check parameters
9578 if (is_null($ajaxoptions)) {
9579 $ajaxoptions = array();
9580 }
9581
9582 if (!empty($conf->use_javascript_ajax) && getDolGlobalString('TICKET_USE_SEARCH_TO_SELECT')) {
9583 $placeholder = '';
9584
9585 if ($selected && empty($selected_input_value)) {
9586 require_once DOL_DOCUMENT_ROOT . '/projet/class/project.class.php';
9587 $projecttmpselect = new Project($this->db);
9588 $projecttmpselect->fetch((int) $selected);
9589 $selected_input_value = $projecttmpselect->ref;
9590 unset($projecttmpselect);
9591 }
9592
9593 $urloption = '';
9594 $out .= ajax_autocompleter($selected, $htmlname, DOL_URL_ROOT . '/projet/ajax/projects.php', $urloption, $conf->global->PRODUIT_USE_SEARCH_TO_SELECT, 1, $ajaxoptions);
9595
9596 if (empty($hidelabel)) {
9597 $out .= $langs->trans("RefOrLabel") . ' : ';
9598 } elseif ($hidelabel > 1) {
9599 $placeholder = ' placeholder="' . $langs->trans("RefOrLabel") . '"';
9600 if ($hidelabel == 2) {
9601 $out .= img_picto($langs->trans("Search"), 'search');
9602 }
9603 }
9604 $out .= '<input type="text" class="minwidth100" name="search_' . $htmlname . '" id="search_' . $htmlname . '" value="' . $selected_input_value . '"' . $placeholder . ' ' . (getDolGlobalString('PRODUCT_SEARCH_AUTOFOCUS') ? 'autofocus' : '') . ' />';
9605 if ($hidelabel == 3) {
9606 $out .= img_picto($langs->trans("Search"), 'search');
9607 }
9608 } else {
9609 $out .= $this->selectProjectsList($selected, $htmlname, $filtertype, $limit, '', $status, 0, $showempty, $forcecombo, $morecss);
9610 }
9611
9612 if (empty($nooutput)) {
9613 print $out;
9614 } else {
9615 return $out;
9616 }
9617 return '';
9618 }
9619
9636 public function selectProjectsList($selected = '', $htmlname = 'projectid', $filtertype = '', $limit = 20, $filterkey = '', $status = 1, $outputmode = 0, $showempty = '1', $forcecombo = 0, $morecss = '')
9637 {
9638 global $langs, $conf;
9639
9640 $out = '';
9641 $outarray = array();
9642
9643 $selectFields = " p.rowid, p.ref";
9644
9645 $sql = "SELECT ";
9646 $sql .= $this->db->sanitize($selectFields, 0, 0, 1);
9647 $sql .= " FROM " . $this->db->prefix() . "projet as p";
9648 $sql .= ' WHERE p.entity IN (' . getEntity('project') . ')';
9649
9650 // Add criteria on ref/label
9651 if ($filterkey != '') {
9652 $sql .= ' AND (';
9653 $prefix = !getDolGlobalString('TICKET_DONOTSEARCH_ANYWHERE') ? '%' : ''; // Can use index if PRODUCT_DONOTSEARCH_ANYWHERE is on
9654 // For natural search
9655 $search_crit = explode(' ', $filterkey);
9656 $i = 0;
9657 if (count($search_crit) > 1) {
9658 $sql .= "(";
9659 }
9660 foreach ($search_crit as $crit) {
9661 if ($i > 0) {
9662 $sql .= " AND ";
9663 }
9664 $sql .= "p.ref LIKE '" . $this->db->escape($prefix . $crit) . "%'";
9665 $sql .= "";
9666 $i++;
9667 }
9668 if (count($search_crit) > 1) {
9669 $sql .= ")";
9670 }
9671 $sql .= ')';
9672 }
9673
9674 $sql .= $this->db->plimit($limit, 0);
9675
9676 // Build output string
9677 dol_syslog(get_class($this) . "::selectProjectsList search projects", LOG_DEBUG);
9678 $result = $this->db->query($sql);
9679 if ($result) {
9680 require_once DOL_DOCUMENT_ROOT . '/projet/class/project.class.php';
9681 require_once DOL_DOCUMENT_ROOT . '/core/lib/project.lib.php';
9682
9683 $num = $this->db->num_rows($result);
9684
9685 $events = array();
9686
9687 if (!$forcecombo) {
9688 include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
9689 $out .= ajax_combobox($htmlname, $events, getDolGlobalInt('PROJECT_USE_SEARCH_TO_SELECT'));
9690 }
9691
9692 $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" id="' . $htmlname . '">';
9693
9694 $textifempty = '';
9695 // Do not use textifempty = ' ' or '&nbsp;' here, or search on key will search on ' key'.
9696 //if (!empty($conf->use_javascript_ajax) || $forcecombo) $textifempty='';
9697 if (getDolGlobalString('PROJECT_USE_SEARCH_TO_SELECT')) {
9698 if ($showempty && !is_numeric($showempty)) {
9699 $textifempty = $langs->trans($showempty);
9700 } else {
9701 $textifempty .= $langs->trans("All");
9702 }
9703 } else {
9704 if ($showempty && !is_numeric($showempty)) {
9705 $textifempty = $langs->trans($showempty);
9706 }
9707 }
9708 if ($showempty) {
9709 $out .= '<option value="0" selected>' . $textifempty . '</option>';
9710 }
9711
9712 $i = 0;
9713 while ($num && $i < $num) {
9714 $opt = '';
9715 $optJson = array();
9716 $objp = $this->db->fetch_object($result);
9717
9718 $this->constructProjectListOption($objp, $opt, $optJson, $selected, $filterkey);
9719 // Add new entry
9720 // "key" value of json key array is used by jQuery automatically as selected value
9721 // "label" value of json key array is used by jQuery automatically as text for combo box
9722 $out .= $opt;
9723 array_push($outarray, $optJson);
9724
9725 $i++;
9726 }
9727
9728 $out .= '</select>';
9729
9730 $this->db->free($result);
9731
9732 if (empty($outputmode)) {
9733 return $out;
9734 }
9735 return $outarray;
9736 } else {
9737 dol_print_error($this->db);
9738 }
9739
9740 return array();
9741 }
9742
9756 protected function constructProjectListOption(&$objp, &$opt, &$optJson, $selected, $filterkey = '')
9757 {
9758 $outkey = '';
9759 $outref = '';
9760 $outtype = '';
9761
9762 $label = $objp->label;
9763
9764 $outkey = $objp->rowid;
9765 $outref = $objp->ref;
9766 $outlabel = $objp->label;
9767 $outtype = $objp->fk_product_type;
9768
9769 $opt = '<option value="' . $objp->rowid . '"';
9770 $opt .= ($objp->rowid == $selected) ? ' selected' : '';
9771 $opt .= '>';
9772 $opt .= $objp->ref;
9773 $objRef = $objp->ref;
9774 if (!empty($filterkey) && $filterkey != '') {
9775 $objRef = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', (string) $objRef, 1);
9776 }
9777
9778 $opt .= "</option>\n";
9779 $optJson = array('key' => $outkey, 'value' => $outref, 'type' => $outtype);
9780 }
9781
9782
9803 public function selectMembers($selected = '', $htmlname = 'adherentid', $filtertype = '', $limit = 0, $status = 1, $selected_input_value = '', $hidelabel = 0, $ajaxoptions = array(), $socid = 0, $showempty = '1', $forcecombo = 0, $morecss = '', $selected_combinations = null, $nooutput = 0, $excludeids = array())
9804 {
9805 global $langs, $conf;
9806
9807 $out = '';
9808
9809 // check parameters
9810 if (is_null($ajaxoptions)) {
9811 $ajaxoptions = array();
9812 }
9813
9814 if (!empty($conf->use_javascript_ajax) && getDolGlobalString('TICKET_USE_SEARCH_TO_SELECT')) {
9815 $placeholder = '';
9816
9817 if ($selected && empty($selected_input_value)) {
9818 require_once DOL_DOCUMENT_ROOT . '/adherents/class/adherent.class.php';
9819 $adherenttmpselect = new Adherent($this->db);
9820 $adherenttmpselect->fetch((int) $selected);
9821 $selected_input_value = $adherenttmpselect->ref;
9822 unset($adherenttmpselect);
9823 }
9824
9825 $urloption = '';
9826
9827 $out .= ajax_autocompleter($selected, $htmlname, DOL_URL_ROOT . '/adherents/ajax/adherents.php', $urloption, $conf->global->PRODUIT_USE_SEARCH_TO_SELECT, 1, $ajaxoptions);
9828
9829 if (empty($hidelabel)) {
9830 $out .= $langs->trans("RefOrLabel") . ' : ';
9831 } elseif ($hidelabel > 1) {
9832 $placeholder = ' placeholder="' . $langs->trans("RefOrLabel") . '"';
9833 if ($hidelabel == 2) {
9834 $out .= img_picto($langs->trans("Search"), 'search');
9835 }
9836 }
9837 $out .= '<input type="text" class="minwidth100" name="search_' . $htmlname . '" id="search_' . $htmlname . '" value="' . $selected_input_value . '"' . $placeholder . ' ' . (getDolGlobalString('PRODUCT_SEARCH_AUTOFOCUS') ? 'autofocus' : '') . ' />';
9838 if ($hidelabel == 3) {
9839 $out .= img_picto($langs->trans("Search"), 'search');
9840 }
9841 } else {
9842 $filterkey = '';
9843
9844 $out .= $this->selectMembersList($selected, $htmlname, $filtertype, $limit, $filterkey, $status, 0, $showempty, $forcecombo, $morecss, $excludeids);
9845 }
9846
9847 if (empty($nooutput)) {
9848 print $out;
9849 } else {
9850 return $out;
9851 }
9852 return '';
9853 }
9854
9872 public function selectMembersList($selected = '', $htmlname = 'adherentid', $filtertype = '', $limit = 20, $filterkey = '', $status = 1, $outputmode = 0, $showempty = '1', $forcecombo = 0, $morecss = '', $excludeids = array())
9873 {
9874 global $langs, $conf;
9875
9876 $out = '';
9877 $outarray = array();
9878
9879 $selectFields = " p.rowid, p.ref, p.firstname, p.lastname, p.fk_adherent_type";
9880
9881 $sql = "SELECT ";
9882 $sql .= $this->db->sanitize($selectFields, 0, 0, 1);
9883 $sql .= " FROM " . $this->db->prefix() . "adherent as p";
9884 $sql .= ' WHERE p.entity IN (' . getEntity('adherent') . ')';
9885
9886 // Add criteria on ref/label
9887 if ($filterkey != '') {
9888 $sql .= ' AND (';
9889 $prefix = !getDolGlobalString('MEMBER_DONOTSEARCH_ANYWHERE') ? '%' : ''; // Can use index if PRODUCT_DONOTSEARCH_ANYWHERE is on
9890 // For natural search
9891 $search_crit = explode(' ', $filterkey);
9892 $i = 0;
9893 if (count($search_crit) > 1) {
9894 $sql .= "(";
9895 }
9896 foreach ($search_crit as $crit) {
9897 if ($i > 0) {
9898 $sql .= " AND ";
9899 }
9900 $sql .= "(p.firstname LIKE '" . $this->db->escape($prefix . $crit) . "%'";
9901 $sql .= " OR p.lastname LIKE '" . $this->db->escape($prefix . $crit) . "%')";
9902 $i++;
9903 }
9904 if (count($search_crit) > 1) {
9905 $sql .= ")";
9906 }
9907 $sql .= ')';
9908 }
9909 if ($status != -1) {
9910 $sql .= ' AND statut = ' . ((int) $status);
9911 }
9912 if (!empty($excludeids)) {
9913 $sql .= " AND p.rowid NOT IN (" . $this->db->sanitize(implode(',', $excludeids)) . ")";
9914 }
9915 $sql .= $this->db->plimit($limit, 0);
9916
9917 // Build output string
9918 dol_syslog(get_class($this) . "::selectMembersList search adherents", LOG_DEBUG);
9919 $result = $this->db->query($sql);
9920 if ($result) {
9921 require_once DOL_DOCUMENT_ROOT . '/adherents/class/adherent.class.php';
9922 require_once DOL_DOCUMENT_ROOT . '/core/lib/member.lib.php';
9923
9924 $num = $this->db->num_rows($result);
9925
9926 $events = array();
9927
9928 if (!$forcecombo) {
9929 include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
9930 $out .= ajax_combobox($htmlname, $events, getDolGlobalInt('PROJECT_USE_SEARCH_TO_SELECT'));
9931 }
9932
9933 $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" id="' . $htmlname . '">';
9934
9935 $textifempty = '';
9936 // Do not use textifempty = ' ' or '&nbsp;' here, or search on key will search on ' key'.
9937 //if (!empty($conf->use_javascript_ajax) || $forcecombo) $textifempty='';
9938 if (getDolGlobalString('PROJECT_USE_SEARCH_TO_SELECT')) {
9939 if ($showempty && !is_numeric($showempty)) {
9940 $textifempty = $langs->trans($showempty);
9941 } else {
9942 $textifempty .= $langs->trans("All");
9943 }
9944 } else {
9945 if ($showempty && !is_numeric($showempty)) {
9946 $textifempty = $langs->trans($showempty);
9947 }
9948 }
9949 if ($showempty) {
9950 $out .= '<option value="-1" selected>' . $textifempty . '</option>';
9951 }
9952
9953 $i = 0;
9954 while ($num && $i < $num) {
9955 $opt = '';
9956 $optJson = array();
9957 $objp = $this->db->fetch_object($result);
9958
9959 $this->constructMemberListOption($objp, $opt, $optJson, $selected, $filterkey);
9960
9961 // Add new entry
9962 // "key" value of json key array is used by jQuery automatically as selected value
9963 // "label" value of json key array is used by jQuery automatically as text for combo box
9964 $out .= $opt;
9965 array_push($outarray, $optJson);
9966
9967 $i++;
9968 }
9969
9970 $out .= '</select>';
9971
9972 $this->db->free($result);
9973
9974 if (empty($outputmode)) {
9975 return $out;
9976 }
9977 return $outarray;
9978 } else {
9979 dol_print_error($this->db);
9980 }
9981
9982 return array();
9983 }
9984
9996 protected function constructMemberListOption(&$objp, &$opt, &$optJson, $selected, $filterkey = '')
9997 {
9998 $outkey = '';
9999 $outlabel = '';
10000 $outtype = '';
10001
10002 $outkey = $objp->rowid;
10003 $outlabel = dolGetFirstLastname($objp->firstname, $objp->lastname);
10004 $outtype = $objp->fk_adherent_type;
10005
10006 $opt = '<option value="' . $objp->rowid . '"';
10007 $opt .= ($objp->rowid == $selected) ? ' selected' : '';
10008 $opt .= '>';
10009 if (!empty($filterkey) && $filterkey != '') {
10010 $outlabel = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', $outlabel, 1);
10011 }
10012 $opt .= $outlabel;
10013 $opt .= "</option>\n";
10014
10015 $optJson = array('key' => $outkey, 'value' => $outlabel, 'type' => $outtype);
10016 }
10017
10039 public function selectForForms($objectdesc, $htmlname, $preSelectedValue, $showempty = '', $searchkey = '', $placeholder = '', $morecss = '', $moreparams = '', $forcecombo = 0, $disabled = 0, $selected_input_value = '', $objectfield = '')
10040 {
10041 global $conf, $extrafields, $user, $hookmanager, $action;
10042
10043 // Example of common usage for a link to a thirdparty
10044
10045 // We got this in a modulebuilder form of "MyObject" of module "mymodule".
10046 // When ->fields is array( ... "fk_soc" => array("type"=>"integer:Societe:societe/class/societe.class.php:1:((status:=:1) AND (entity:IN:__SHARED_ENTITIES__))" ...), we have
10047 // $objectdesc = 'Societe'
10048 // $objectfield = Method 1: 'myobject@mymodule:fk_soc' ('fk_soc' is code to retrieve myobject->fields['fk_soc'])
10049 // Method 2 recommended (it can be the array): array("type"=>"integer:Societe:societe/class/societe.class.php:1:((status:=:1) AND (entity:IN:__SHARED_ENTITIES__))" ...)
10050
10051 // We got this when showing an extrafields on resource that is a link to societe
10052 // When extrafields 'link_to_societe' for object Resource is 'link' to 'Societe:societe/class/societe.class.php:1:((status:=:1) AND (entity:IN:__SHARED_ENTITIES__))', we have
10053 // $objectdesc = 'Societe'
10054 // $objectfield = Method 1: 'resource:options_link_to_societe'
10055 // Method 2 recommended (it can be the array): array("type"=>'Societe:societe/class/societe.class.php:1:((status:=:1) AND (entity:IN:__SHARED_ENTITIES__))" ...)
10056
10057 // With old usage:
10058 // $objectdesc = 'Societe:societe/class/societe.class.php:1:((status:=:1) AND (entity:IN:__SHARED_ENTITIES__))'
10059 // $objectfield = ''
10060
10061 //var_dump($objectdesc.' '.$objectfield);
10062 //debug_print_backtrace();
10063
10064 $objectdescorig = $objectdesc;
10065 $objecttmp = null;
10066 $InfoFieldList = array();
10067 $classname = '';
10068 $filter = ''; // Ensure filter has value (for static analysis)
10069 $sortfield = ''; // Ensure filter has value (for static analysis)
10070
10071 if (is_array($objectfield)) { // objectfield is an array
10072 $objectdesc = $objectfield['type'];
10073 $objectdesc = preg_replace('/^integer[^:]*:/', '', $objectdesc);
10074 } elseif ($objectfield) { // objectfield is a string. We must retrieve the objectdesc from the field or extrafield. Deprecated, it is better to provide the array record directly.
10075 // Example: $objectfield = 'product:options_package' or 'myobject@mymodule:options_myfield'
10076 $tmparray = explode(':', $objectfield);
10077
10078 // Get instance of object from $element
10079 $objectforfieldstmp = fetchObjectByElement(0, strtolower($tmparray[0]));
10080
10081 if (is_object($objectforfieldstmp)) {
10082 $objectdesc = '';
10083
10084 $reg = array();
10085 if (preg_match('/^options_(.*)$/', $tmparray[1], $reg)) {
10086 // For a property in extrafields
10087 $key = $reg[1];
10088 // fetch optionals attributes and labels
10089 $extrafields->fetch_name_optionals_label($objectforfieldstmp->table_element);
10090
10091 if (!empty($extrafields->attributes[$objectforfieldstmp->table_element]['type'][$key]) && $extrafields->attributes[$objectforfieldstmp->table_element]['type'][$key] == 'link') {
10092 if (!empty($extrafields->attributes[$objectforfieldstmp->table_element]['param'][$key]['options'])) {
10093 $tmpextrafields = array_keys($extrafields->attributes[$objectforfieldstmp->table_element]['param'][$key]['options']);
10094 $objectdesc = $tmpextrafields[0];
10095 }
10096 }
10097 } else {
10098 // For a property in ->fields
10099 if (array_key_exists($tmparray[1], $objectforfieldstmp->fields)) {
10100 $objectdesc = $objectforfieldstmp->fields[$tmparray[1]]['type'];
10101 $objectdesc = preg_replace('/^integer[^:]*:/', '', $objectdesc);
10102 }
10103 }
10104 }
10105 }
10106
10107 if ($objectdesc) {
10108 // Example of value for $objectdesc:
10109 // Bom:bom/class/bom.class.php:0:t.status=1
10110 // Bom:bom/class/bom.class.php:0:t.status=1:ref
10111 // Bom:bom/class/bom.class.php:0:(t.status:=:1) OR (t.field2:=:2):ref
10112 $InfoFieldList = explode(":", $objectdesc, 4);
10113 $vartmp = (empty($InfoFieldList[3]) ? '' : $InfoFieldList[3]);
10114 $reg = array();
10115 if (preg_match('/^.*:(\w*)$/', $vartmp, $reg)) {
10116 $InfoFieldList[4] = $reg[1]; // take the sort field
10117 }
10118 $InfoFieldList[3] = preg_replace('/:\w*$/', '', $vartmp); // take the filter field
10119
10120 $classname = $InfoFieldList[0];
10121 $classpath = empty($InfoFieldList[1]) ? '' : $InfoFieldList[1];
10122 //$addcreatebuttonornot = empty($InfoFieldList[2]) ? 0 : $InfoFieldList[2];
10123 $filter = empty($InfoFieldList[3]) ? '' : $InfoFieldList[3];
10124 $sortfield = empty($InfoFieldList[4]) ? '' : $InfoFieldList[4];
10125
10126 // Load object according to $id and $element
10127 $objecttmp = fetchObjectByElement(0, strtolower($InfoFieldList[0]));
10128
10129 // Fallback to another solution to get $objecttmp
10130 if (empty($objecttmp) && !empty($classpath)) {
10131 dol_include_once($classpath);
10132
10133 if ($classname && class_exists($classname)) {
10134 $objecttmp = new $classname($this->db);
10135 }
10136 }
10137 }
10138
10139 // Make some replacement in $filter. May not be used if we used the ajax mode with $objectfield. In such a case
10140 // we propagate the $objectfield and not the filter and replacement is done by the ajax/selectobject.php component.
10141 $sharedentities = (is_object($objecttmp) && property_exists($objecttmp, 'element')) ? getEntity($objecttmp->element) : strtolower($classname);
10142 $filter = str_replace(
10143 array('__ENTITY__', '__SHARED_ENTITIES__', '__USER_ID__'),
10144 array($conf->entity, $sharedentities, $user->id),
10145 $filter
10146 );
10147
10148 if (!is_object($objecttmp)) {
10149 dol_syslog('selectForForms: Error bad setup of field objectdescorig=' . $objectdescorig.', objectfield='.(is_array($objectfield) ? 'array' : $objectfield).', objectdesc='.$objectdesc, LOG_WARNING);
10150 return 'selectForForms: Error bad setup of field objectdescorig=' . $objectdescorig.', objectfield='.(is_array($objectfield) ? 'array' : $objectfield).', objectdesc='.$objectdesc;
10151 }
10152 '@phan-var-force CommonObject $objecttmp';
10154 //var_dump($filter);
10155 $prefixforautocompletemode = $objecttmp->element;
10156 if ($prefixforautocompletemode == 'societe') {
10157 $prefixforautocompletemode = 'company';
10158 }
10159 if ($prefixforautocompletemode == 'product') {
10160 $prefixforautocompletemode = 'produit';
10161 }
10162
10163 $confkeyforautocompletemode = strtoupper($prefixforautocompletemode) . '_USE_SEARCH_TO_SELECT'; // For example COMPANY_USE_SEARCH_TO_SELECT
10164
10165 dol_syslog(get_class($this) . "::selectForForms filter=" . $filter, LOG_DEBUG);
10166
10167 // Generate the combo HTML component
10168 $out = '';
10169 if (!empty($conf->use_javascript_ajax) && getDolGlobalString($confkeyforautocompletemode) && !$forcecombo) {
10170 // No immediate load of all database
10171 $placeholder = '';
10172
10173 if ($preSelectedValue && empty($selected_input_value)) {
10174 $objecttmp->fetch($preSelectedValue);
10175 $selected_input_value = ($prefixforautocompletemode == 'company' ? $objecttmp->name : $objecttmp->ref);
10176
10177 $oldValueForShowOnCombobox = 0;
10178 foreach ($objecttmp->fields as $fieldK => $fielV) {
10179 if (!array_key_exists('showoncombobox', $fielV) || !$fielV['showoncombobox'] || empty($objecttmp->$fieldK)) {
10180 continue;
10181 }
10182
10183 if (!$oldValueForShowOnCombobox) {
10184 $selected_input_value = '';
10185 }
10186
10187 $selected_input_value .= $oldValueForShowOnCombobox ? ' - ' : '';
10188 $selected_input_value .= $objecttmp->$fieldK;
10189 $oldValueForShowOnCombobox = empty($fielV['showoncombobox']) ? 0 : $fielV['showoncombobox'];
10190 }
10191 }
10192
10193 // Set url and param to call to get json of the search results
10194 $urlforajaxcall = DOL_URL_ROOT . '/core/ajax/selectobject.php';
10195 $urloption = 'htmlname=' . urlencode($htmlname) . '&outjson=1&objectdesc=' . urlencode($objectdescorig) . (is_scalar($objectfield) ? '&objectfield='.urlencode($objectfield) : '') . ($sortfield ? '&sortfield=' . urlencode($sortfield) : '');
10196 //$urloption = 'htmlname=' . urlencode($htmlname) . '&outjson=1'.(is_scalar($objectfield) ? '&objectfield='.urlencode($objectfield) : '') . ($sortfield ? '&sortfield=' . urlencode($sortfield) : '');
10197
10198 // Hook 'selectForFormsListUrl' - Added to allow modules to modify the AJAX URL
10199 $parameters = array(
10200 'urloption' => $urloption,
10201 'object' => $objecttmp,
10202 'htmlname' => $htmlname,
10203 'filter' => $filter,
10204 'searchkey' => $searchkey,
10205 );
10206 $reshook = $hookmanager->executeHooks('selectForFormsListUrl', $parameters, $objecttmp, $action);
10207 if (!empty($reshook)) {
10208 $urloption = $hookmanager->resPrint;
10209 $hookmanager->resPrint = '';
10210 }
10211
10212 // Activate the auto complete using ajax call.
10213 $out .= ajax_autocompleter((string) $preSelectedValue, $htmlname, $urlforajaxcall, $urloption, getDolGlobalInt($confkeyforautocompletemode), 0);
10214 $out .= '<!-- force css to be higher than dialog popup --><style type="text/css">.ui-autocomplete { z-index: 1010; }</style>';
10215 $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) . '"' : '') . ' />';
10216 } else {
10217 // Immediate load of table record.
10218 $out .= $this->selectForFormsList($objecttmp, $htmlname, $preSelectedValue, $showempty, $searchkey, $placeholder, $morecss, $moreparams, $forcecombo, 0, $disabled, $sortfield, $filter);
10219 }
10220
10221 return $out;
10222 }
10223
10224
10246 public function selectForFormsList($objecttmp, $htmlname, $preselectedvalue, $showempty = '', $searchkey = '', $placeholder = '', $morecss = '', $moreparams = '', $forcecombo = 0, $outputmode = 0, $disabled = 0, $sortfield = '', $filter = '', $sortorder = 'ASC')
10247 {
10248 global $langs, $user, $hookmanager;
10249
10250 //print "$htmlname, $preselectedvalue, $showempty, $searchkey, $placeholder, $morecss, $moreparams, $forcecombo, $outputmode, $disabled";
10251
10252 $prefixforautocompletemode = $objecttmp->element;
10253 if ($prefixforautocompletemode == 'societe') {
10254 $prefixforautocompletemode = 'company';
10255 }
10256 $confkeyforautocompletemode = strtoupper($prefixforautocompletemode) . '_USE_SEARCH_TO_SELECT'; // For example COMPANY_USE_SEARCH_TO_SELECT
10257
10258 $fieldstoshow = '';
10259 if (!empty($objecttmp->fields)) { // For object that declare it, it is better to use declared fields (like societe, contact, ...)
10260 $tmpfieldstoshow = '';
10261 foreach ($objecttmp->fields as $key => $val) {
10262 if (! (int) dol_eval((string) $val['enabled'], 1, 1, '1')) {
10263 continue;
10264 }
10265 if (!empty($val['showoncombobox'])) {
10266 $tmpfieldstoshow .= ($tmpfieldstoshow ? ',' : '') . 't.' . $key;
10267 }
10268 }
10269 if ($tmpfieldstoshow) {
10270 $fieldstoshow = $tmpfieldstoshow;
10271 }
10272 } elseif ($objecttmp->element === 'category') {
10273 $fieldstoshow = 't.label';
10274 } else {
10275 // For backward compatibility
10276 $objecttmp->fields['ref'] = array('type' => 'varchar(30)', 'label' => 'Ref', 'enabled' => 1, 'position' => 10, 'visible' => 4, 'showoncombobox' => 1);
10277 }
10278
10279 if (empty($fieldstoshow)) {
10280 if (!empty($objecttmp->parent_element)) {
10281 $fieldstoshow = 'o.ref';
10282 if (empty($sortfield)) {
10283 $sortfield = 'o.ref';
10284 }
10285 if (in_array($objecttmp->element, ['commandedet', 'propaldet', 'facturedet', 'expeditiondet'])) {
10286 $fieldstoshow .= ',p.ref AS p_ref,p.label,t.description';
10287 $sortfield .= ', p.ref';
10288 }
10289 } elseif (isset($objecttmp->fields['ref'])) {
10290 $fieldstoshow = 't.ref';
10291 } else {
10292 $langs->load("errors");
10293 $this->error = $langs->trans("ErrorNoFieldWithAttributeShowoncombobox");
10294 return $langs->trans('ErrorNoFieldWithAttributeShowoncombobox');
10295 }
10296 }
10297
10298 $out = '';
10299 $outarray = array();
10300 $tmparray = array();
10301
10302 $num = 0;
10303
10304 $sanitizedfieldstoshow = $fieldstoshow;
10305
10306 // Search data
10307 $sql = "SELECT t.rowid, " . $sanitizedfieldstoshow . " FROM " . $this->db->prefix() . $this->db->sanitize($objecttmp->table_element) . " as t";
10308 if (!empty($objecttmp->isextrafieldmanaged)) {
10309 $extrafieldTable = $objecttmp->table_element;
10310 if ($extrafieldTable == 'categorie') {
10311 $extrafieldTable = 'categories'; // For compatibility
10312 }
10313 $sql .= " LEFT JOIN " . $this->db->prefix() . $this->db->sanitize($extrafieldTable) . "_extrafields as e ON t.rowid = e.fk_object";
10314 }
10315 if (!empty($objecttmp->parent_element)) { // If parent_element is defined
10316 '@phan-var-force CommonObjectLine $objecttmp';
10317 $parent_properties = getElementProperties($objecttmp->parent_element);
10318 // @phan-suppress-next-line SqlInjection
10319 $sql .= " INNER JOIN " . $this->db->prefix() . $this->db->sanitize($parent_properties['table_element']) . " as o ON o.rowid = t.".$this->db->sanitize($objecttmp->fk_parent_attribute);
10320 }
10321 if (!empty($objecttmp->parent_element) && in_array($objecttmp->parent_element, ['commande', 'propal', 'facture', 'expedition'])) {
10322 $sql .= " LEFT JOIN " . $this->db->prefix() . "product as p ON p.rowid = t.fk_product";
10323 }
10324 if (!empty($objecttmp->ismultientitymanaged)) {
10325 if ($objecttmp->ismultientitymanaged == 1) { // @phan-suppress-current-line PhanPluginEmptyStatementIf
10326 // No need to join/link another table
10327 }
10328 if (!is_numeric($objecttmp->ismultientitymanaged)) {
10329 $tmparray = explode('@', $objecttmp->ismultientitymanaged);
10330 $sql .= " INNER JOIN " . $this->db->prefix() . $this->db->sanitize($tmparray[1]) . " as parenttable ON parenttable.rowid = t." . $this->db->sanitize($tmparray[0]);
10331 }
10332 }
10333
10334 // Add where from hooks
10335 $parameters = array(
10336 'object' => $objecttmp,
10337 'htmlname' => $htmlname,
10338 'filter' => $filter,
10339 'searchkey' => $searchkey
10340 );
10341
10342 $reshook = $hookmanager->executeHooks('selectForFormsListWhere', $parameters); // Note that $action and $object may have been modified by hook
10343 if (!empty($hookmanager->resPrint)) {
10344 $sql .= $hookmanager->resPrint;
10345 } else {
10346 $sql .= " WHERE 1=1";
10347
10348 // If table need a multientity restriction
10349 if (!empty($objecttmp->ismultientitymanaged)) {
10350 if ($objecttmp->ismultientitymanaged == 1) {
10351 $sql .= " AND t.entity IN (" . getEntity($objecttmp->element) . ")";
10352 }
10353 if (!is_numeric($objecttmp->ismultientitymanaged)) {
10354 $sql .= " AND parenttable.entity = t." . $this->db->sanitize($tmparray[0]);
10355 }
10356 // If the parent table is llx_societe and user is not an external user (a more robust test done later for external users),
10357 // then we must also check that user has permissions
10358 if ($objecttmp->ismultientitymanaged === 'fk_soc@societe') {
10359 if (!$user->hasRight('societe', 'client', 'voir') && empty($user->socid)) {
10360 $sql .= " AND EXISTS (SELECT sc.rowid FROM ".$this->db->prefix() . "societe_commerciaux as sc";
10361 $sql .= " WHERE sc.fk_soc = t.fk_soc AND sc.fk_user = ".((int) $user->id).")";
10362 }
10363 }
10364 }
10365
10366 // If user is external user, we must also make a test on thirdparty
10367 if (!empty($user->socid)) {
10368 if ($objecttmp->element == 'societe') {
10369 $sql .= " AND t.rowid = " . ((int) $user->socid);
10370 } elseif (!empty($objecttmp->fields['fk_soc']) || !empty($objecttmp->fields['t.fk_soc']) || property_exists($objecttmp, 'fk_soc') || property_exists($objecttmp, 'socid')) {
10371 $sql .= " AND t.fk_soc = " . ((int) $user->socid);
10372 } elseif (!empty($objecttmp->parent_element)) {
10373 $tmpparent = fetchObjectByElement(0, $objecttmp->parent_element, '', 1);
10374 if (is_object($tmpparent) && (!empty($tmpparent->fields['fk_soc']) || !empty($tmpparent->fields['t.fk_soc']) || property_exists($tmpparent, 'fk_soc') || property_exists($tmpparent, 'socid'))) {
10375 $sql .= " AND o.fk_soc = " . ((int) $user->socid);
10376 }
10377 }
10378 }
10379
10380 $splittedfieldstoshow = explode(',', $fieldstoshow);
10381 foreach ($splittedfieldstoshow as &$field2) {
10382 if (is_numeric($pos = strpos($field2, ' '))) {
10383 $field2 = substr($field2, 0, $pos);
10384 }
10385 }
10386 if ($searchkey != '') {
10387 $sql .= natural_search($splittedfieldstoshow, $searchkey);
10388 }
10389
10390 if ($filter) { // Syntax example "(t.ref:like:'SO-%') and (t.date_creation:>:'20160101')"
10391 $errormessage = '';
10392 $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
10393 if ($errormessage) {
10394 return 'Error forging a SQL request from an universal criteria: ' . $errormessage;
10395 }
10396 }
10397 }
10398 $sql .= $this->db->order($sortfield ? $sortfield : $fieldstoshow, $sortorder);
10399 //$sql.=$this->db->plimit($limit, 0);
10400 //print $sql;
10401
10402 // Build output string
10403 $resql = $this->db->query($sql);
10404 if ($resql) {
10405 // Construct $out and $outarray
10406 $out .= '<select id="' . $htmlname . '" class="flat minwidth100' . ($morecss ? ' ' . $morecss : '') . '"' . ($disabled ? ' disabled="disabled"' : '') . ($moreparams ? ' ' . $moreparams : '') . ' name="' . $htmlname . '">' . "\n";
10407
10408 // 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
10409 $textifempty = '&nbsp;';
10410
10411 //if (!empty($conf->use_javascript_ajax) || $forcecombo) $textifempty='';
10412 if (getDolGlobalInt($confkeyforautocompletemode)) {
10413 if ($showempty && !is_numeric($showempty)) {
10414 $textifempty = $langs->trans($showempty);
10415 } else {
10416 $textifempty .= $langs->trans("All");
10417 }
10418 }
10419 if ($showempty) {
10420 $out .= '<option value="-1">' . $textifempty . '</option>' . "\n";
10421 }
10422
10423 $num = $this->db->num_rows($resql);
10424 $i = 0;
10425 if ($num) {
10426 while ($i < $num) {
10427 $obj = $this->db->fetch_object($resql);
10428 $label = '';
10429 $labelhtml = '';
10430 $tmparray = explode(',', $fieldstoshow);
10431 $oldvalueforshowoncombobox = 0;
10432 foreach ($tmparray as $key => $val) {
10433 $val = preg_replace('/(t|p|o)\./', '', $val);
10434 $label .= (($label && $obj->$val) ? ($oldvalueforshowoncombobox != $objecttmp->fields[$val]['showoncombobox'] ? ' - ' : ' ') : '');
10435 $labelhtml .= (($label && $obj->$val) ? ($oldvalueforshowoncombobox != $objecttmp->fields[$val]['showoncombobox'] ? ' - ' : ' ') : '');
10436 $label .= $obj->$val;
10437 $labelhtml .= $obj->$val;
10438
10439 $oldvalueforshowoncombobox = empty($objecttmp->fields[$val]['showoncombobox']) ? 0 : $objecttmp->fields[$val]['showoncombobox'];
10440 }
10441 if (empty($outputmode)) {
10442 if ($preselectedvalue > 0 && $preselectedvalue == $obj->rowid) {
10443 $out .= '<option value="' . $obj->rowid . '" selected data-html="' . dol_escape_htmltag($labelhtml, 0, 0, '', 0, 1) . '">' . dol_escape_htmltag($label, 0, 0, '', 0, 1) . '</option>';
10444 } else {
10445 $out .= '<option value="' . $obj->rowid . '" data-html="' . dol_escape_htmltag($labelhtml, 0, 0, '', 0, 1) . '">' . dol_escape_htmltag($label, 0, 0, '', 0, 1) . '</option>';
10446 }
10447 } else {
10448 array_push($outarray, array('key' => $obj->rowid, 'value' => $label, 'label' => $label));
10449 }
10450
10451 $i++;
10452 if (($i % 10) == 0) {
10453 $out .= "\n";
10454 }
10455 }
10456 }
10457
10458 $out .= '</select>' . "\n";
10459
10460 if (!$forcecombo) {
10461 include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
10462 $out .= ajax_combobox($htmlname, array(), getDolGlobalInt($confkeyforautocompletemode, 0));
10463 }
10464 } else {
10465 dol_print_error($this->db);
10466 }
10467
10468 $this->result = array('nbofelement' => $num);
10469
10470 if ($outputmode) {
10471 return $outarray;
10472 }
10473 return $out;
10474 }
10475
10486 public static function radio($htmlName, $radioItems, $selected = '', $moreGlobalParams = [])
10487 {
10488 // Default parameters for each radio input
10489 $defaultParams = [
10490 'disabled' => false,
10491 'attr' => [
10492 'type' => 'radio',
10493 'name' => $htmlName,
10494 ],
10495 'attrLabel' => [],
10496 'labelIsHtml' => false
10497 ];
10498
10499 // Merge global parameters with defaults
10500 $params = array_merge_recursive_distinct($defaultParams, $moreGlobalParams);
10501
10502 $out = '';
10503 if (!empty($radioItems)) {
10504 foreach ($radioItems as $key => $item) {
10505 // Normalize item to array structure if it's a simple string
10506 if (!is_array($item)) {
10507 $item = [
10508 'attr' => [
10509 'value' => $key,
10510 ],
10511 'label' => $item
10512 ];
10513 }
10514
10515 // Default properties for individual item
10516 $defaultItem = [
10517 'attr' => [
10518 'value' => !isset($item['attr']['value']) ? $key : '',
10519 ],
10520 'label' => '',
10521 ];
10522
10523 // Merge defaults with global params and item-specific properties
10524 $defaultItem = array_merge_recursive_distinct($params, $defaultItem);
10525 $item = array_merge_recursive_distinct($defaultItem, $item);
10526
10527 // Determine if this radio should be checked
10528 if ((is_array($selected) && in_array($item['attr']['value'], $selected, true)) || $selected === $item['attr']['value']) {
10529 $item['attr']['checked'] = true;
10530 }
10531
10532 // Build HTML attributes for input and label
10533 $inputAttributes = implode(' ', commonHtmlAttributeBuilder($item['attr']));
10534 $labelAttributes = implode(' ', commonHtmlAttributeBuilder($item['attrLabel']));
10535
10536 // prevent accidental Xss todo : escape $item['label'] but html friendly compatible
10537 $text = $item['labelIsHtml'] ? $item['label'] : htmlspecialchars($item['label'], ENT_QUOTES | ENT_SUBSTITUTE);
10538
10539 // Generate HTML
10540 $out .= '<label ' . $labelAttributes . '><input ' . $inputAttributes . ' /> ' . $text . '</label> ';
10541 }
10542 }
10543
10544 return $out;
10545 }
10546
10547
10571 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 = 'minwidth75', $addjscombo = 1, $moreparamonempty = '', $disablebademail = 0, $nohtmlescape = 0)
10572 {
10573 global $conf, $langs;
10574
10575 // Do we want a multiselect ?
10576 //$jsbeautify = 0;
10577 //if (preg_match('/^multi/',$htmlname)) $jsbeautify = 1;
10578 $jsbeautify = 1;
10579
10580 if ($value_as_key) {
10581 $array = array_combine($array, $array);
10582 }
10583
10584 '@phan-var-force array{label:string,data-html:string,disable?:int<0,1>,css?:string} $array'; // Array combine breaks information
10585
10586 $out = '';
10587
10588 if ($addjscombo < 0) {
10589 if (!getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
10590 $addjscombo = 1;
10591 } else {
10592 $addjscombo = 0;
10593 }
10594 }
10595 $idname = str_replace(array('[', ']'), array('', ''), $htmlname);
10596 $out .= '<select id="' . preg_replace('/^\./', '', $idname) . '" ' . ($disabled ? 'disabled="disabled" ' : '') . 'class="flat ' . (preg_replace('/^\./', '', $htmlname)) . ($morecss ? ' ' . $morecss : '') . ' selectformat"';
10597 $out .= ' name="' . preg_replace('/^\./', '', $htmlname) . '" ' . ($moreparam ? $moreparam : '');
10598 $out .= '>'."\n";
10599
10600 if ($show_empty) {
10601 $textforempty = ' ';
10602 if (!empty($conf->use_javascript_ajax)) {
10603 $textforempty = '&nbsp;'; // If we use ajaxcombo, we need &nbsp; here to avoid to have an empty element that is too small.
10604 }
10605 if (!is_numeric($show_empty)) {
10606 $textforempty = $show_empty;
10607 }
10608 $out .= '<option class="optiongrey" ' . ($moreparamonempty ? $moreparamonempty . ' ' : '') . 'value="' . (((int) $show_empty) < 0 ? $show_empty : -1) . '"' . ($id == $show_empty ? ' selected' : '') . '>' . dol_escape_htmltag($textforempty) . '</option>' . "\n";
10609 }
10610 if (is_array($array)) {
10611 // Translate
10612 if ($translate) {
10613 foreach ($array as $key => $value) {
10614 if (!is_array($value)) {
10615 $array[$key] = $langs->trans($value);
10616 } else {
10617 $array[$key]['label'] = $langs->trans($value['label']);
10618 }
10619 }
10620 }
10621 // Sort
10622 if ($sort == 'ASC') {
10623 asort($array);
10624 } elseif ($sort == 'DESC') {
10625 arsort($array);
10626 }
10627
10628 foreach ($array as $key => $tmpvalue) {
10629 if (is_array($tmpvalue)) {
10630 $value = $tmpvalue['label'];
10631 //$valuehtml = empty($tmpvalue['data-html']) ? $value : $tmpvalue['data-html'];
10632 $disabled = empty($tmpvalue['disabled']) ? '' : ' disabled';
10633 $style = empty($tmpvalue['css']) ? '' : ' class="' . $tmpvalue['css'] . '"';
10634 } else {
10635 $value = $tmpvalue;
10636 //$valuehtml = $tmpvalue;
10637 $disabled = '';
10638 $style = '';
10639 }
10640 if (!empty($disablebademail)) {
10641 if (($disablebademail == 1 && !preg_match('/&lt;.+@.+&gt;/', $value))
10642 || ($disablebademail == 2 && preg_match('/---/', $value))) {
10643 $disabled = ' disabled';
10644 $style = ' class="warning"';
10645 }
10646 }
10647 if ($key_in_label) {
10648 if (empty($nohtmlescape)) {
10649 $selectOptionValue = dol_escape_htmltag($key . ' - ' . ($maxlen ? dol_trunc($value, $maxlen) : $value));
10650 } else {
10651 $selectOptionValue = $key . ' - ' . ($maxlen ? dol_trunc($value, $maxlen) : $value);
10652 }
10653 } else {
10654 if (empty($nohtmlescape)) {
10655 $selectOptionValue = dol_escape_htmltag($maxlen ? dol_trunc($value, $maxlen) : $value);
10656 } else {
10657 $selectOptionValue = $maxlen ? dol_trunc($value, $maxlen) : $value;
10658 }
10659 if ($value == '' || $value == '-') {
10660 $selectOptionValue = '&nbsp;';
10661 }
10662 }
10663 $out .= '<option value="' . $key . '"';
10664 $out .= $style . $disabled;
10665 $out .= is_array($tmpvalue) && !empty($tmpvalue['parent']) ? ' parent="' . dolPrintHTMLForAttribute($tmpvalue['parent']) . '"' : '';
10666 if (is_array($id)) {
10667 if (in_array($key, $id) && !$disabled) {
10668 $out .= ' selected'; // To preselect a value
10669 }
10670 } else {
10671 $id = (string) $id; // if $id = 0, then $id = '0'
10672 if ($id != '' && (($id == (string) $key) || ($id == 'ifone' && count($array) == 1)) && !$disabled) {
10673 $out .= ' selected'; // To preselect a value
10674 }
10675 }
10676
10677 if (is_array($tmpvalue)) {
10678 foreach ($tmpvalue as $keyforvalue => $valueforvalue) {
10679 if ($keyforvalue == 'labelhtml') {
10680 $keyforvalue = 'data-html';
10681 }
10682 if (preg_match('/^data-/', $keyforvalue)) { // The best solution if you want to use HTML values into the list is to use data-html.
10683 $out .= ' '.dol_escape_htmltag($keyforvalue).'="'.dol_escape_htmltag($valueforvalue).'"';
10684 }
10685 }
10686 } elseif (!empty($nohtmlescape)) { // deprecated. Use instead the previous cas, an array with 'data-html', 'data-xxx' ... to use HTML content in the select
10687 $out .= ' data-html="' . dol_escape_htmltag($selectOptionValue) . '"';
10688 }
10689
10690 $out .= '>';
10691 $out .= $selectOptionValue;
10692 $out .= "</option>\n";
10693 }
10694 }
10695 $out .= "</select>";
10696
10697 // Add code for jquery to use multiselect
10698 if ($addjscombo && $jsbeautify) {
10699 // Enhance with select2
10700 include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
10701 $out .= ajax_combobox($idname, array(), 0, 0, 'resolve', (((int) $show_empty) < 0 ? (string) $show_empty : '-1'), $morecss);
10702 }
10703
10704 return $out;
10705 }
10706
10725 public static function selectArrayAjax($htmlname, $url, $id = '', $moreparam = '', $moreparamtourl = '', $disabled = 0, $minimumInputLength = 1, $morecss = '', $callurlonselect = 0, $placeholder = '', $acceptdelayedhtml = 0)
10726 {
10727 global $conf;
10728 global $delayedhtmlcontent; // Will be used later outside of this function
10729
10730 // TODO Use an internal dolibarr component instead of select2
10731 if (!getDolGlobalString('MAIN_USE_JQUERY_MULTISELECT') && !defined('REQUIRE_JQUERY_MULTISELECT')) {
10732 return '';
10733 }
10734
10735 $out = '<select type="text" class="' . $htmlname . ($morecss ? ' ' . $morecss : '') . '" ' . ($moreparam ? $moreparam . ' ' : '') . 'name="' . $htmlname . '"></select>';
10736
10737 $outdelayed = '';
10738 if (!empty($conf->use_javascript_ajax)) {
10739 $tmpplugin = 'select2';
10740 $outdelayed = "\n" . '<!-- JS CODE TO ENABLE ' . $tmpplugin . ' for id ' . $htmlname . ' -->
10741 <script nonce="' . getNonce() . '">
10742 $(document).ready(function () {
10743
10744 ' . ($callurlonselect ? 'var saveRemoteData = [];' : '') . '
10745
10746 $(".' . $htmlname . '").select2({
10747 ajax: {
10748 dir: "ltr",
10749 url: "' . $url . '",
10750 dataType: \'json\',
10751 delay: 250,
10752 data: function (params) {
10753 return {
10754 q: params.term, // search term
10755 page: params.page
10756 }
10757 },
10758 processResults: function (data) {
10759 // parse the results into the format expected by Select2.
10760 // since we are using custom formatting functions we do not need to alter the remote JSON data
10761 //console.log(data);
10762 saveRemoteData = data;
10763 /* format json result for select2 */
10764 result = []
10765 $.each( data, function( key, value ) {
10766 result.push({id: key, text: value.text});
10767 });
10768 //return {results:[{id:\'none\', text:\'aa\'}, {id:\'rrr\', text:\'Red\'},{id:\'bbb\', text:\'Search a into projects\'}], more:false}
10769 //console.log(result);
10770 return {results: result, more: false}
10771 },
10772 cache: true
10773 },
10774 language: (typeof select2arrayoflanguage === \'undefined\') ? \'en\' : select2arrayoflanguage,
10775 containerCssClass: \':all:\', /* Line to add class from the original SELECT propagated to the new <span class="select2-selection...> tag */
10776 placeholder: \'' . dol_escape_js($placeholder) . '\',
10777 escapeMarkup: function (markup) { return markup; }, // let our custom formatter work
10778 minimumInputLength: ' . ((int) $minimumInputLength) . ',
10779 formatResult: function (result, container, query, escapeMarkup) {
10780 return escapeMarkup(result.text);
10781 },
10782 });
10783
10784 ' . ($callurlonselect ? '
10785 /* Code to execute a GET when we select a value */
10786 $(".' . $htmlname . '").change(function() {
10787 var selected = $(\'.' . dol_escape_js($htmlname) . '\').val();
10788 console.log("We select in selectArrayAjax the entry "+selected)
10789 $(\'.' . dol_escape_js($htmlname) . '\').val(""); /* reset visible combo value */
10790 $.each( saveRemoteData, function( key, value ) {
10791 if (key == selected)
10792 {
10793 console.log("selectArrayAjax - Do a redirect to "+value.url)
10794 location.assign(value.url);
10795 }
10796 });
10797 });' : '') . '
10798
10799 });
10800 </script>';
10801 }
10802
10803 if ($acceptdelayedhtml) {
10804 $delayedhtmlcontent .= $outdelayed;
10805 } else {
10806 $out .= $outdelayed;
10807 }
10808 return $out;
10809 }
10810
10830 public static function selectArrayFilter($htmlname, $array, $id = '', $moreparam = '', $disableFiltering = 0, $disabled = 0, $minimumInputLength = 1, $morecss = '', $callurlonselect = 0, $placeholder = '', $acceptdelayedhtml = 0, $textfortitle = '')
10831 {
10832 global $conf;
10833 global $delayedhtmlcontent; // Will be used later outside of this function
10834
10835 // TODO Use an internal dolibarr component instead of select2
10836 if (!getDolGlobalString('MAIN_USE_JQUERY_MULTISELECT') && !defined('REQUIRE_JQUERY_MULTISELECT')) {
10837 return '';
10838 }
10839
10840 $out = '<select type="text"'.($textfortitle ? ' title="'.dol_escape_htmltag($textfortitle).'"' : '').' id="'.$htmlname.'" class="'.$htmlname.($morecss ? ' ' . $morecss : '').'"'.($moreparam ? ' '.$moreparam : '').' name="'.$htmlname.'"><option></option></select>';
10841
10842 $formattedarrayresult = array();
10843
10844 foreach ($array as $key => $value) {
10845 $o = new stdClass();
10846 $o->id = $key;
10847 $o->text = $value['text'];
10848 $o->url = $value['url'];
10849 $formattedarrayresult[] = $o;
10850 }
10851
10852 $outdelayed = '';
10853 if (!empty($conf->use_javascript_ajax)) {
10854 $tmpplugin = 'select2';
10855 $outdelayed = "\n" . '<!-- JS CODE TO ENABLE ' . $tmpplugin . ' for id ' . $htmlname . ' -->
10856 <script nonce="' . getNonce() . '">
10857 $(document).ready(function () {
10858 var data = ' . json_encode($formattedarrayresult) . ';
10859
10860 ' . ($callurlonselect ? 'var saveRemoteData = ' . json_encode($array) . ';' : '') . '
10861
10862 $(\'.' . dol_escape_js($htmlname) . '\').select2({
10863 data: data,
10864 language: (typeof select2arrayoflanguage === \'undefined\') ? \'en\' : select2arrayoflanguage,
10865 containerCssClass: \':all:\', /* Line to add class from the original SELECT propagated to the new <span class="select2-selection...> tag */
10866 placeholder: \'' . dol_escape_js($placeholder) . '\',
10867 escapeMarkup: function (markup) { return markup; }, // let our custom formatter work
10868 minimumInputLength: ' . ((int) $minimumInputLength) . ',
10869 formatResult: function (result, container, query, escapeMarkup) {
10870 return escapeMarkup(result.text);
10871 },
10872 matcher: function (params, data) {
10873
10874 if(! data.id) return null;';
10875
10876 if ($callurlonselect) {
10877 // We forge the url with 'sall='
10878 $outdelayed .= '
10879
10880 var urlBase = data.url;
10881 var separ = urlBase.indexOf("?") >= 0 ? "&" : "?";
10882 /* console.log("params.term="+params.term); */
10883 /* console.log("params.term encoded="+encodeURIComponent(params.term)); */
10884 saveRemoteData[data.id].url = urlBase + separ + "search_all=" + encodeURIComponent(params.term.replace(/\"/g, ""));';
10885 }
10886
10887 if (!$disableFiltering) {
10888 $outdelayed .= '
10889
10890 if(data.text.match(new RegExp(params.term))) {
10891 return data;
10892 }
10893
10894 return null;';
10895 } else {
10896 $outdelayed .= '
10897
10898 return data;';
10899 }
10900
10901 $outdelayed .= '
10902 }
10903 });
10904
10905 ' . ($callurlonselect ? '
10906 /* Code to execute a GET when we select a value */
10907 $(\'.' . dol_escape_js($htmlname) . '\').change(function() {
10908 var selected = $(\'.' . dol_escape_js($htmlname) . '\').val();
10909 console.log("We select "+selected)
10910
10911 $(\'.' . dol_escape_js($htmlname) . '\').val(""); /* reset visible combo value */
10912 $.each( saveRemoteData, function( key, value ) {
10913 if (key == selected)
10914 {
10915 console.log("selectArrayFilter - Do a redirect to "+value.url)
10916 location.assign(value.url);
10917 }
10918 });
10919 });' : '') . '
10920
10921 });
10922 </script>';
10923 }
10924
10925 if ($acceptdelayedhtml) {
10926 $delayedhtmlcontent .= $outdelayed;
10927 } else {
10928 $out .= $outdelayed;
10929 }
10930 return $out;
10931 }
10932
10951 public static function multiselectarray($htmlname, $array, $selected = array(), $key_in_label = 0, $value_as_key = 0, $morecss = '', $translate = 0, $width = 0, $moreattrib = '', $nu = '', $placeholder = '', $addjscombo = -1)
10952 {
10953 global $conf, $langs;
10954 $out = '';
10955
10956 if ($addjscombo < 0) {
10957 if (!getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
10958 $addjscombo = 1;
10959 } else {
10960 $addjscombo = 0;
10961 }
10962 }
10963
10964 $useenhancedmultiselect = 0;
10965 if (!empty($conf->use_javascript_ajax) && !defined('MAIN_DO_NOT_USE_JQUERY_MULTISELECT') && (getDolGlobalString('MAIN_USE_JQUERY_MULTISELECT') || defined('REQUIRE_JQUERY_MULTISELECT'))) {
10966 if ($addjscombo) {
10967 $useenhancedmultiselect = 1; // Use the js multiselect in one line. Possible only if $addjscombo not 0.
10968 }
10969 }
10970
10971 $out .= '<span class="multiselectarray'.$htmlname.'">';
10972
10973 // We need a hidden field because when using the multiselect, if we unselect all, there is no
10974 // variable submitted at all, so no way to make a difference between variable not submitted and variable
10975 // submitted to nothing.
10976 $out .= '<input type="hidden" name="'.$htmlname.'_multiselect" value="1">';
10977 // Output select component
10978 $out .= '<select id="'.$htmlname.'" class="multiselect' . ($useenhancedmultiselect ? ' multiselectononeline' : '') . ($morecss ? ' ' . $morecss : '') . '" multiple name="' . $htmlname . '[]"' . ($moreattrib ? ' ' . $moreattrib : '') . ($width ? ' style="width: ' . (preg_match('/%/', (string) $width) ? $width : $width . 'px') . '"' : '') . '>' . "\n";
10979 if (is_array($array) && !empty($array)) {
10980 if ($value_as_key) {
10981 $array = array_combine($array, $array);
10982 }
10983
10984 if (!empty($array)) {
10985 foreach ($array as $key => $value) {
10986 $tmpkey = $key;
10987 $tmplabel = $value;
10988 $tmplabelhtml = '';
10989 $tmpcolor = '';
10990 $tmppicto = '';
10991 $tmpdisabled = '';
10992 if (is_array($value) && array_key_exists('id', $value) && array_key_exists('label', $value)) {
10993 $tmpkey = $value['id'];
10994 $tmplabel = empty($value['label']) ? '' : $value['label'];
10995 $tmplabelhtml = empty($value['labelhtml']) ? (empty($value['data-html']) ? '' : $value['data-html']) : $value['labelhtml'];
10996 $tmpcolor = empty($value['color']) ? '' : $value['color'];
10997 $tmppicto = empty($value['picto']) ? '' : $value['picto'];
10998 $tmpdisabled = empty($value['disabled']) ? '' : $value['disabled'];
10999 }
11000 $newval = ($translate ? $langs->trans($tmplabel) : $tmplabel);
11001 $newval = ($key_in_label ? $tmpkey . ' - ' . $newval : $newval);
11002
11003 $tmplabelhtml = ($translate ? $langs->trans($tmplabelhtml) : $tmplabelhtml);
11004 $tmplabelhtml = ($key_in_label ? $tmpkey . ' - ' . $tmplabelhtml : $tmplabelhtml);
11005
11006 $out .= '<option value="' . $tmpkey . '"';
11007 if (is_array($selected) && !empty($selected) && in_array((string) $tmpkey, $selected) && ((string) $tmpkey != '')) {
11008 $out .= ' selected';
11009 }
11010 $out .= is_array($value) && array_key_exists('parent', $value) && !empty($value['parent']) ? ' parent="' . dolPrintHTMLForAttribute($value['parent']) . '"' : '';
11011 if ($tmpdisabled) {
11012 $out .= ' disabled="disabled"';
11013 }
11014 if (!empty($tmplabelhtml)) {
11015 $out .= ' data-html="' . dolPrintHTMLForAttribute($tmplabelhtml) . '"';
11016 } else {
11017 $tmplabelhtml = ($tmppicto ? img_picto('', $tmppicto, 'class="pictofixedwidth" style="color: #' . $tmpcolor . '"') : '') . $newval;
11018 $out .= ' data-html="' . dolPrintHTMLForAttribute($tmplabelhtml) . '"';
11019 }
11020 $out .= '>';
11021 $out .= dol_htmlentitiesbr($newval);
11022 $out .= '</option>' . "\n";
11023 }
11024 }
11025 }
11026 $out .= '</select>' . "\n";
11027
11028 $out .= '</span>';
11029
11030 // Add code for jquery to use multiselect
11031 if (!empty($conf->use_javascript_ajax) && getDolGlobalString('MAIN_USE_JQUERY_MULTISELECT') || defined('REQUIRE_JQUERY_MULTISELECT')) {
11032 $out .= "\n" . '<!-- JS CODE TO ENABLE select for id ' . $htmlname . ', addjscombo=' . $addjscombo . ' -->';
11033 $out .= "\n" . '<script nonce="' . getNonce() . '">' . "\n";
11034 if ($addjscombo == 1) {
11035 $moreselect2theme = ($morecss ? dol_escape_js(' '.$morecss) : '');
11036 $moreselect2theme = preg_replace('/widthcentpercentminus[^\s]*/', '', $moreselect2theme);
11037
11038 $widthTypeOfAutocomplete = 'resolve';
11039
11040 $tmpplugin = getDolGlobalString('MAIN_USE_JQUERY_MULTISELECT', (defined('REQUIRE_JQUERY_MULTISELECT') ? constant('REQUIRE_JQUERY_MULTISELECT') : 'select2'));
11041
11042 // If property data-html set, we decode html entities and use this.
11043 // Note that HTML content must have been sanitized from js with dol_escape_htmltag(xxx, 0, 0, '', 0, 1) when building the select option.
11044 // TODO Move this into common js ?
11045 $out .= 'function formatResult(record, container) {' . "\n";
11046 $out .= ' if ($(record.element).attr("data-html") != undefined && typeof htmlEntityDecodeJs === "function") {';
11047 $out .= ' return htmlEntityDecodeJs($(record.element).attr("data-html"));';
11048 $out .= ' }'."\n";
11049 $out .= ' return record.text;';
11050 $out .= '}' . "\n";
11051
11052 $out .= 'function formatSelection(record) {' . "\n";
11053 $out .= ' return record.text;';
11054 $out .= '}' . "\n";
11055
11056 // Load the select2 enhancer
11057 //$out .= 'console.log(\'addjscombo=1 for htmlname=' . dol_escape_js($htmlname) . '\');';
11058 $out .= '$(document).ready(function () {
11059 $(\'#' . dol_escape_js($htmlname) . '\').' . $tmpplugin . '({';
11060 // when $morecss contains 'onrightofpage', the select2 component must also be inside a parent with class="parentonrightofpage"
11061 if (preg_match('/onrightofpage/', $morecss)) { // In this cas, htmlname must be an ID not a class.
11062 $out .= ' dropdownAutoWidth: true, ';
11063 $out .= ' dropdownParent: $(\'#'.$htmlname.'\').parent(), ';
11064 }
11065 if ($placeholder) {
11066 $out .= '
11067 placeholder: {
11068 id: \'-1\',
11069 text: \''.dol_escape_js($placeholder).'\'
11070 },';
11071 }
11072 $out .= ' dir: \'ltr\',
11073 width: \''.dol_escape_js($widthTypeOfAutocomplete).'\', /* off or resolve */
11074 theme: \'default' . dol_escape_js($moreselect2theme) . '\', /* to add css on generated html components */
11075 containerCssClass: \':all:\', /* Line to add class of origin SELECT propagated to the new <span class="select2-selection...> tag (ko with multiselect) */
11076 selectionCssClass: \':all:\', /* Line to add class of origin SELECT propagated to the new <span class="select2-selection...> tag */
11077 dropdownCssClass: \'dol-dropdown-dialog dol-dropdown-dialogmulti\', /* Line to add class on the new <span class="select2-selection...> tag (ok with multiselect). Need full version of select2. */
11078 // Specify format function for dropdown item
11079 formatResult: formatResult,
11080 templateResult: formatResult, /* For 4.0 */
11081 escapeMarkup: function (markup) { return markup; }, // let our custom formatter work
11082 // Specify format function for selected item
11083 formatSelection: formatSelection,
11084 templateSelection: formatSelection, /* For 4.0 */
11085 language: (typeof select2arrayoflanguage === \'undefined\') ? \'en\' : select2arrayoflanguage
11086 });
11087
11088 /* Add also morecss to the css .select2 that is after the #htmlname, for component that are shown dynamically after load, because select2 set
11089 the size only if component is not hidden by default on load */
11090 $(\'#' . dol_escape_js($htmlname) . ' + .select2\').addClass(\'' . dol_escape_js($morecss) . '\');
11091 });' . "\n";
11092 } elseif ($addjscombo == 2 && !defined('DISABLE_MULTISELECT')) {
11093 // Add other js lib
11094 // TODO external lib multiselect/jquery.multi-select.js must have been loaded to use this multiselect plugin
11095 // ...
11096 $out .= 'console.log(\'addjscombo=2 for htmlname=' . dol_escape_js($htmlname) . '\');';
11097 $out .= '$(document).ready(function () {
11098 $(\'#' . dol_escape_js($htmlname) . '\').multiSelect({
11099 containerHTML: \'<div class="multi-select-container">\',
11100 menuHTML: \'<div class="multi-select-menu">\',
11101 buttonHTML: \'<span class="multi-select-button ' . dol_escape_js($morecss) . '">\',
11102 menuItemHTML: \'<label class="multi-select-menuitem">\',
11103 activeClass: \'multi-select-container--open\',
11104 noneText: \'' . dol_escape_js($placeholder) . '\'
11105 });
11106 })';
11107 }
11108 $out .= '</script>';
11109 }
11110
11111 return $out;
11112 }
11113
11114
11128 public static function multiSelectArrayWithCheckbox($htmlname, &$array, $varpage, $pos = '', $draganddrop = 0)
11129 {
11130 global $conf, $langs, $user;
11131
11132 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
11133 return '';
11134 }
11135 if (empty($array)) {
11136 return '';
11137 }
11138
11139 $tmpvar = "MAIN_SELECTEDFIELDS_" . $varpage; // To get list of saved selected fields to show
11140
11141 if (!empty($user->conf->$tmpvar)) { // A list of fields was already customized for user
11142 $tmparray = explode(',', $user->conf->$tmpvar);
11143 foreach ($array as $key => $val) {
11144 //var_dump($key);
11145 //var_dump($tmparray);
11146 if (in_array($key, $tmparray)) {
11147 $array[$key]['checked'] = 1;
11148 } else {
11149 $array[$key]['checked'] = 0;
11150 }
11151 }
11152 } else { // There is no list of fields already customized for user
11153 foreach ($array as $key => $val) {
11154 if (!empty($array[$key]['checked']) && $array[$key]['checked'] < 0) {
11155 $array[$key]['checked'] = 0;
11156 }
11157 }
11158 }
11159
11160 $listoffieldsforselection = '';
11161 $listcheckedstring = '';
11162
11163 foreach ($array as $key => $val) {
11164 // var_dump($val);
11165 // var_dump(array_key_exists('enabled', $val));
11166 // var_dump(!$val['enabled']);
11167 if (array_key_exists('enabled', $val) && isset($val['enabled']) && !$val['enabled']) {
11168 unset($array[$key]); // We don't want this field
11169 continue;
11170 }
11171 if (!empty($val['type']) && $val['type'] == 'separate') {
11172 // Field remains in array but we don't add it into $listoffieldsforselection
11173 //$listoffieldsforselection .= '<li>-----</li>';
11174 continue;
11175 }
11176 if (!empty($val['label']) && $val['label']) {
11177 if (!empty($val['langfile']) && is_object($langs)) {
11178 $langs->load($val['langfile']);
11179 }
11180
11181 // Note: $val['checked'] <> 0 means we must show the field into the combo list @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
11182 $listoffieldsforselection .= '<li '.(!empty($draganddrop) ? 'class="fieldsortable" id="'.$key : '').'"><input type="checkbox" id="checkbox' . $key . '" value="' . $key . '"' . ((!array_key_exists('checked', $val) || empty($val['checked']) || $val['checked'] == '-1') ? '' : ' checked="checked"') . ' data-position="'.(empty($val['position']) ? '' : $val['position']).'" />';
11183 $listoffieldsforselection .= '<label for="checkbox' . $key . '" class="paddingleft">';
11184 $listoffieldsforselection .= dolPrintHTML(dol_string_nohtmltag($langs->trans($val['label'])));
11185 $listoffieldsforselection .= '</label>';
11186 if (!empty($draganddrop)) {
11187 $listoffieldsforselection .= img_picto($langs->trans("MoveField", !empty($key) ? $key : 'none'), 'grip_title', 'class="opacitymedium boxhandle hideonsmartphone cursormove marginleftonly"');
11188 }
11189 $listoffieldsforselection .= '</li>';
11190 $listcheckedstring .= (empty($val['checked']) ? '' : $key . ',');
11191 }
11192 }
11193
11194 $out = '<!-- Component multiSelectArrayWithCheckbox ' . $htmlname . ' -->
11195
11196 <dl class="dropdown">
11197 <dt>
11198 <a href="#' . $htmlname . '" class="multiselectpicto">
11199 ' . img_picto('', 'list') . '
11200 </a>
11201 <input type="hidden" class="' . $htmlname . '" name="' . $htmlname . '" value="' . $listcheckedstring . '">
11202 </dt>
11203 <dd class="dropdowndd">
11204 <div class="multiselectcheckbox'.$htmlname.'">
11205 <ul class="'.$htmlname.(((string) $pos == '1' || (string) $pos == 'left') ? 'left' : '').(!empty($draganddrop) ? ' sortable' : '').'">
11206 <li class="liinputsearch">
11207 <input class="inputsearch_dropdownselectedfields width90p minwidth200imp" style="width:90%;" type="text" placeholder="'.$langs->trans('Search').'">
11208 </li>
11209 '.$listoffieldsforselection.'
11210 </ul>
11211 </div>
11212 </dd>
11213 </dl>
11214
11215 <script>
11216 function updateFieldOrder() {
11217 var positionfields = $(".sortable").sortable("toArray");
11218 $.ajax({
11219 url: \''.DOL_URL_ROOT.'/core/ajax/changepositionfields.php?positionfields=\'+positionfields+\'&token='.newToken().'&action=listafterchangingpositionfields&contextpage='.$varpage.'&userid='.$user->id.'\',
11220 async: false,
11221 success: function () {
11222 // reload page
11223 window.location.href = "'.$_SERVER["PHP_SELF"].'";
11224 }
11225 });
11226 }
11227 $( ".sortable" ).sortable({
11228 handle: \'.boxhandle\',
11229 revert: \'invalid\',
11230 items: \'.fieldsortable\',
11231 stop: function(event, ui) {
11232 console.log("We moved box so we call updateBoxOrder with ajax actions");
11233 updateFieldOrder(); /* 1 to avoid message after a move */
11234 }
11235 });
11236 </script>
11237
11238 <script nonce="' . getNonce() . '" type="text/javascript">
11239 jQuery(document).ready(function () {
11240 $(\'.multiselectcheckbox' . $htmlname . ' input[type="checkbox"]\').on("click", function () {
11241 console.log("A new field was added/removed, we edit field input[name=formfilteraction]");
11242
11243 $("input:hidden[name=formfilteraction]").val(\'listafterchangingselectedfields\'); // Update field so we know we changed something on selected fields after POST
11244
11245 var title = $(this).val() + ",";
11246 if ($(this).is(\':checked\')) {
11247 $(\'.' . $htmlname . '\').val(title + $(\'.' . $htmlname . '\').val());
11248 }
11249 else {
11250 $(\'.' . $htmlname . '\').val( $(\'.' . $htmlname . '\').val().replace(title, \'\') )
11251 }
11252 // Now, we submit page
11253 //$(this).parents(\'form:first\').submit();
11254 });
11255
11256 $("input.inputsearch_dropdownselectedfields").on("keyup", function() {
11257 console.log("keyup on inputsearch_dropdownselectedfields");
11258 var value = $(this).val().toLowerCase();
11259 $(\'.multiselectcheckbox'.$htmlname.' li > label\').filter(function() {
11260 $(this).parent().toggle($(this).text().toLowerCase().indexOf(value) > -1)
11261 });
11262 });
11263 ';
11264 if (empty($conf->browser->layout) || $conf->browser->layout != 'phone') {
11265 $out .= '
11266 $(".dropdown dt a").on("click", function () {
11267 console.log("Click on dropdown, we set focus to search field");
11268 setTimeout(() => { $(\'.inputsearch_dropdownselectedfields\').focus(); }, 200);
11269 });';
11270 }
11271 $out .= '
11272 });
11273 </script>
11274
11275 ';
11276 return $out;
11277 }
11278
11288 public function showCategories($id, $type, $rendermode = 0, $nolink = 0)
11289 {
11290 global $conf;
11291
11292 include_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
11293
11294 $cat = new Categorie($this->db);
11295 $categories = $cat->containing($id, $type);
11296
11297 if ($rendermode == 1 || $rendermode == 2) {
11298 $toprint = array();
11299 foreach ($categories as $c) {
11300 $ways = $c->print_all_ways('auto', ($nolink ? 'none' : ''), 0, 1, ($rendermode == 2 ? 0 : 1)); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
11301 foreach ($ways as $way) {
11302 $color = $c->color;
11303 $sfortag = '<li class="select2-search-choice-dolibarr noborderoncategories'.(empty($toprint) ? ' nomarginleft' : '');
11304 $forced_color = 'categtextwhite'; // We want color white because the getNomUrl of a tag is always called inside a dark background like '<span color="bbb"></span>' to show it as a tag. TODO Add this in param to force when called outside of span.
11305 if ($c->color && colorIsLight($c->color)) {
11306 $forced_color = 'categtextblack';
11307 }
11308 $sfortag .= ' '.$forced_color;
11309 $sfortag .= '"';
11310 $sfortag .= ($color ? ' style="background: #' . $color . ';"' : ' style="background: #bbb"');
11311 $titlestring = $ways[0];
11312 $titlestring = str_replace('>', ' - ', dol_string_nohtmltag($titlestring));
11313 $sfortag .= ' title="' . dolPrintHTMLForAttribute($titlestring) . '"';
11314 $sfortag .= '>';
11315 if ($rendermode == 1) {
11316 $sfortag .= '<a href="'.DOL_URL_ROOT.'/categories/viewcat.php?id='.((int) $c->id).'&type='.urlencode($c->type).'" class="'.$forced_color.'">';
11317 $sfortag .= img_picto('', 'category', 'class="paddingright"');
11318 if ($conf->dol_optimize_smallscreen) {
11319 $sfortag .= dolPrintHTML(dol_trunc($c->label, 8));
11320 } else {
11321 $sfortag .= dolPrintHTML($c->label);
11322 }
11323 $sfortag .= '</a>';
11324 } else {
11325 $sfortag .= $way;
11326 }
11327 $sfortag .= '</li>';
11328
11329 $toprint[] = $sfortag; // Add tag in list of tag to show
11330 }
11331 }
11332 if (empty($toprint)) {
11333 return '';
11334 } else {
11335 return '<div class="select2-container-multi-dolibarr"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
11336 }
11337 }
11338
11339 if ($rendermode == 0) {
11340 $arrayselected = array();
11341 $cate_arbo = $this->select_all_categories($type, '', 'parent', 64, 0, 3);
11342 foreach ($categories as $c) {
11343 $arrayselected[(string) $c->id] = (string) $c->id;
11344 }
11345
11346 return $this->multiselectarray('categories', $cate_arbo, $arrayselected, 0, 0, '', 0, '100%', 'disabled', 'category');
11347 }
11348
11349 return 'ErrorBadValueForParameterRenderMode'; // Should not happened
11350 }
11351
11361 public function showLinkedObjectBlock($object, $morehtmlright = '', $compatibleImportElementsList = array(), $title = 'RelatedObjects')
11362 {
11363 global $conf, $langs, $hookmanager;
11364 global $action;
11365 global $db, $user; // Will be used into tpl
11366
11367 dol_syslog(__METHOD__, LOG_DEBUG);
11368
11369 $object->fetchObjectLinked();
11370
11371 // Bypass the default method
11372 $hookmanager->initHooks(array('commonobject'));
11373 $parameters = array(
11374 'morehtmlright' => $morehtmlright,
11375 'compatibleImportElementsList' => &$compatibleImportElementsList,
11376 );
11377 $reshook = $hookmanager->executeHooks('showLinkedObjectBlock', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
11378
11379 $nbofdifferenttypes = count($object->linkedObjects);
11380
11381 if (empty($reshook)) {
11382 print '<!-- showLinkedObjectBlock -->';
11383 print load_fiche_titre($langs->trans($title), $morehtmlright, '', 0, '', 'showlinkedobjectblock');
11384
11385
11386 print '<div class="div-table-responsive-no-min">';
11387 print '<table class="noborder allwidth" data-block="showLinkedObject" data-element="' . $object->element . '" data-elementid="' . $object->id . '" >';
11388
11389 print '<tr class="liste_titre">';
11390 print '<td>' . $langs->trans("Type") . '</td>';
11391 print '<td>' . $langs->trans("Ref") . '</td>';
11392 print '<td></td>';
11393 print '<td></td>';
11394 print '<td class="right">' . $langs->trans("AmountHTShort") . '</td>';
11395 print '<td class="right">' . $langs->trans("Status") . '</td>';
11396 print '<td></td>';
11397 print '</tr>';
11398
11399 $nboftypesoutput = 0;
11400
11401 foreach ($object->linkedObjects as $objecttype => $objects) {
11402 $tplpath = $element = $subelement = $objecttype;
11403
11404 // to display import button on tpl
11405 global $showImportButton; // Will be used into tpl
11406 $showImportButton = false;
11407 if (!empty($compatibleImportElementsList) && in_array($element, $compatibleImportElementsList)) {
11408 $showImportButton = true;
11409 }
11410
11411 $regs = array();
11412
11413 if ($objecttype != 'supplier_proposal' && preg_match('/^([^_]+)_([^_]+)/i', $objecttype, $regs)) {
11414 $element = $regs[1];
11415 $subelement = $regs[2];
11416 $tplpath = $element . '/' . $subelement;
11417 }
11418 $tplname = 'linkedobjectblock';
11419
11420 // If we ask a resource form external module (instead of default path)
11421 if (preg_match('/^([^@]+)@([^@]+)$/i', $objecttype, $regs)) { // 'myobject@mymodule'
11422 $element = $regs[1];
11423 $module = $regs[2];
11424 $tplpath = $module. '/' . $element;
11425 $tplname = $tplname.'_'.$element;
11426 }
11427
11428 // To work with non standard path
11429 if ($objecttype == 'facture') {
11430 $tplpath = 'compta/' . $element;
11431 if (!isModEnabled('invoice')) {
11432 continue; // Do not show if module disabled
11433 }
11434 } elseif ($objecttype == 'facturerec') {
11435 $tplpath = 'compta/facture';
11436 $tplname = 'linkedobjectblockForRec';
11437 if (!isModEnabled('invoice')) {
11438 continue; // Do not show if module disabled
11439 }
11440 } elseif ($objecttype == 'propal') {
11441 $tplpath = 'comm/' . $element;
11442 if (!isModEnabled('propal')) {
11443 continue; // Do not show if module disabled
11444 }
11445 } elseif ($objecttype == 'supplier_proposal') {
11446 if (!isModEnabled('supplier_proposal')) {
11447 continue; // Do not show if module disabled
11448 }
11449 } elseif ($objecttype == 'shipping' || $objecttype == 'shipment' || $objecttype == 'expedition') {
11450 $tplpath = 'expedition';
11451 if (!isModEnabled('shipping')) {
11452 continue; // Do not show if module disabled
11453 }
11454 } elseif ($objecttype == 'reception') {
11455 $tplpath = 'reception';
11456 if (!isModEnabled('reception')) {
11457 continue; // Do not show if module disabled
11458 }
11459 } elseif ($objecttype == 'delivery') {
11460 $tplpath = 'delivery';
11461 if (!getDolGlobalInt('MAIN_SUBMODULE_DELIVERY')) {
11462 continue; // Do not show if sub module disabled
11463 }
11464 } elseif ($objecttype == 'ficheinter') {
11465 $tplpath = 'fichinter';
11466 if (!isModEnabled('intervention')) {
11467 continue; // Do not show if module disabled
11468 }
11469 } elseif ($objecttype == 'invoice_supplier') {
11470 $tplpath = 'fourn/facture';
11471 } elseif ($objecttype == 'order_supplier') {
11472 $tplpath = 'fourn/commande';
11473 } elseif ($objecttype == 'expensereport') {
11474 $tplpath = 'expensereport';
11475 } elseif ($objecttype == 'subscription') {
11476 $tplpath = 'adherents';
11477 } elseif ($objecttype == 'conferenceorbooth') {
11478 $tplpath = 'eventorganization';
11479 } elseif ($objecttype == 'conferenceorboothattendee') {
11480 $tplpath = 'eventorganization';
11481 } elseif ($objecttype == 'mo') {
11482 $tplpath = 'mrp';
11483 if (!isModEnabled('mrp')) {
11484 continue; // Do not show if module disabled
11485 }
11486 } elseif ($objecttype == 'project_task') {
11487 $tplpath = 'projet/tasks';
11488 }
11489
11490 global $linkedObjectBlock; // Will be used into tpl
11491 $linkedObjectBlock = $objects;
11492
11493 // Output template part (modules that overwrite templates must declare this into descriptor)
11494 $dirtpls = array_merge($conf->modules_parts['tpl'], array('/' . $tplpath . '/tpl'));
11495
11496 foreach ($dirtpls as $reldir) {
11497 $reldir = rtrim($reldir, '/');
11498 if ($nboftypesoutput == ($nbofdifferenttypes - 1)) { // No more type to show after
11499 global $noMoreLinkedObjectBlockAfter; // Will be used into tpl
11500 $noMoreLinkedObjectBlockAfter = 1;
11501 }
11502 $file = dol_buildpath($reldir . '/' . $tplname . '.tpl.php');
11503 if (file_exists($file)) {
11504 $res = @include $file;
11505 if ($res) {
11506 $nboftypesoutput++;
11507 break;
11508 }
11509 }
11510 }
11511 }
11512
11513 if (!$nboftypesoutput) {
11514 print '<tr><td colspan="7"><span class="opacitymedium">' . $langs->trans("None") . '</span></td></tr>';
11515 }
11516
11517 print '</table>';
11518
11519 if (!empty($compatibleImportElementsList)) {
11520 $res = @include dol_buildpath('core/tpl/objectlinked_lineimport.tpl.php');
11521 }
11522
11523 print '</div>';
11524 }
11525
11526 return $nbofdifferenttypes;
11527 }
11528
11538 public function showLinkToObjectBlock($object, $restrictlinksto = array(), $excludelinksto = array(), $nooutput = 0)
11539 {
11540 global $conf, $langs, $hookmanager, $form;
11541 global $action;
11542
11543 dol_syslog(__METHOD__, LOG_DEBUG);
11544
11545 if (empty($form)) {
11546 $form = new Form($this->db);
11547 }
11548
11549 $linktoelem = '';
11550 $linktoelemlist = '';
11551 $listofidcompanytoscan = '';
11552
11553 if (!is_object($object->thirdparty)) {
11554 if ($object->element == 'subscription' && isset($object->fk_adherent)) {
11555 $subby = new Subscription($object->db);
11556 $subby->fetch($object->id);
11557 $adh = new Adherent($object->db);
11558 //$fk_adherent = $object->fk_adherent;
11559 // creating new subscription object only to fetch the adherent which obviously exists given the if statement above are Inefficient, but else phan complains
11560 $fk_adherent = $subby->fk_adherent;
11561 $adh->fetch($fk_adherent);
11562 $thirdparty_id = $adh->fetch_thirdparty();
11563 }
11564 } else {
11565 $thirdparty_id = $object->thirdparty->id;
11566 }
11567
11568 $possiblelinks = array();
11569
11570 $dontIncludeCompletedItems = getDolGlobalString('DONT_INCLUDE_COMPLETED_ELEMENTS_LINKS');
11571
11572 if (!empty($thirdparty_id) && $thirdparty_id > 0) {
11573 $listofidcompanytoscan = (int) $thirdparty_id;
11574 if (is_object($object->thirdparty) && ($object->thirdparty->parent > 0) && getDolGlobalString('THIRDPARTY_INCLUDE_PARENT_IN_LINKTO')) {
11575 $listofidcompanytoscan .= ',' . (int) $object->thirdparty->parent;
11576 }
11577 if (($object->fk_project > 0) && getDolGlobalString('THIRDPARTY_INCLUDE_PROJECT_THIRDPARY_IN_LINKTO')) {
11578 include_once DOL_DOCUMENT_ROOT . '/projet/class/project.class.php';
11579 $tmpproject = new Project($this->db);
11580 $tmpproject->fetch((int) $object->fk_project);
11581 if ($tmpproject->socid > 0 && ($tmpproject->socid != $thirdparty_id)) {
11582 $listofidcompanytoscan .= ',' . (int) $tmpproject->socid;
11583 }
11584 unset($tmpproject);
11585 }
11586
11587 $possiblelinks = array(
11588 'propal' => array(
11589 'enabled' => isModEnabled('propal'),
11590 'perms' => 1,
11591 'label' => 'LinkToProposal',
11592 'sql' => "SELECT s.rowid as socid, s.nom as name, s.client, t.rowid, t.ref, t.ref_client, t.total_ht FROM " . $this->db->prefix() . "societe as s, " . $this->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') . ')'.($dontIncludeCompletedItems ? ' AND t.fk_statut < 4' : ''),
11593 ),
11594 'shipping' => array(
11595 'enabled' => isModEnabled('shipping'),
11596 'perms' => 1,
11597 'label' => 'LinkToExpedition',
11598 'sql' => "SELECT s.rowid as socid, s.nom as name, s.client, t.rowid, t.ref FROM " . $this->db->prefix() . "societe as s, " . $this->db->prefix() . "expedition as t WHERE t.fk_soc = s.rowid AND t.fk_soc IN (" . $this->db->sanitize($listofidcompanytoscan) . ') AND t.entity IN (' . getEntity('shipping') . ')'.($dontIncludeCompletedItems ? ' AND t.fk_statut < 2' : ''),
11599 ),
11600 'order' => array(
11601 'enabled' => isModEnabled('order'),
11602 'perms' => 1,
11603 'label' => 'LinkToOrder',
11604 'sql' => "SELECT s.rowid as socid, s.nom as name, s.client, t.rowid, t.ref, t.ref_client, t.total_ht FROM " . $this->db->prefix() . "societe as s, " . $this->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') . ')'.($dontIncludeCompletedItems ? ' AND t.facture < 1' : ''),
11605 'linkname' => 'commande',
11606 ),
11607 'subscription' => array(
11608 'enabled' => isModEnabled('member'),
11609 'perms' => 1,
11610 'label' => 'LinkToMemberSubscription',
11611 'sql' => "SELECT a.fk_soc as socid, CONCAT(a.firstname, ' ', a.lastname) as name, a.entity as client, sub.rowid, sub.note as ref, '' as ref_client, sub.subscription as total_ht FROM " . $this->db->prefix() . "adherent as a, " . $this->db->prefix() . "subscription as sub WHERE sub.fk_adherent = a.rowid AND a.fk_soc IN (" . $this->db->sanitize($listofidcompanytoscan) . ') AND a.entity IN (' . getEntity('subscription') . ')',
11612 'linkname' => 'subscription',
11613 ),
11614 'conferenceorboothattendee' => array(
11615 'enabled' => isModEnabled('eventorganization'),
11616 'perms' => 1,
11617 'label' => 'LinkToConferenceOrBoothAttendee',
11618 'sql' => "SELECT s.rowid as socid, CONCAT(a.firstname, ' ', a.lastname) as name, a.rowid as rowid, a.fk_project as fk_project, a.ref as ref, a.email as email, a.date_subscription as date_subscription FROM "
11619 .$this->db->prefix()."societe as s, "
11620 .$this->db->prefix()."eventorganization_conferenceorboothattendee as a WHERE a.fk_soc = s.rowid AND a.fk_soc IN ("
11621 .$this->db->sanitize($listofidcompanytoscan) . ') AND s.entity IN (' . getEntity('conferenceorboothattendee') . ')'
11622 . (empty($object->fk_project) ? '' : ' AND a.fk_project = ' . (int) $object->fk_project),
11623 'linkname' => 'attendee'
11624 ),
11625 'invoice' => array(
11626 'enabled' => isModEnabled('invoice'),
11627 'perms' => 1,
11628 'label' => 'LinkToInvoice',
11629 'sql' => "SELECT s.rowid as socid, s.nom as name, s.client, t.rowid, t.ref, t.ref_client, t.total_ht FROM " . $this->db->prefix() . "societe as s, " . $this->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') . ')'.($dontIncludeCompletedItems ? ' AND t.paye < 1' : ''),
11630 'linkname' => 'facture',
11631 ),
11632 'invoice_template' => array(
11633 'enabled' => isModEnabled('invoice'),
11634 'perms' => 1,
11635 'label' => 'LinkToTemplateInvoice',
11636 'sql' => "SELECT s.rowid as socid, s.nom as name, s.client, t.rowid, t.titre as ref, t.total_ht FROM " . $this->db->prefix() . "societe as s, " . $this->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') . ')',
11637 ),
11638 'contrat' => array(
11639 'enabled' => isModEnabled('contract'),
11640 'perms' => 1,
11641 'label' => 'LinkToContract',
11642 '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
11643 FROM " . $this->db->prefix() . "societe as s, " . $this->db->prefix() . "contrat as t, " . $this->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',
11644 ),
11645 'fichinter' => array(
11646 'enabled' => isModEnabled('intervention'),
11647 'perms' => 1,
11648 'label' => 'LinkToIntervention',
11649 'sql' => "SELECT s.rowid as socid, s.nom as name, s.client, t.rowid, t.ref FROM " . $this->db->prefix() . "societe as s, " . $this->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') . ')',
11650 ),
11651 'supplier_proposal' => array(
11652 'enabled' => isModEnabled('supplier_proposal'),
11653 'perms' => 1,
11654 'label' => 'LinkToSupplierProposal',
11655 'sql' => "SELECT s.rowid as socid, s.nom as name, s.client, t.rowid, t.ref, '' as ref_supplier, t.total_ht FROM " . $this->db->prefix() . "societe as s, " . $this->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') . ')'.($dontIncludeCompletedItems ? ' AND t.fk_statut < 4' : ''),
11656 ),
11657 'order_supplier' => array(
11658 'enabled' => isModEnabled("supplier_order"),
11659 'perms' => 1,
11660 'label' => 'LinkToSupplierOrder',
11661 'sql' => "SELECT s.rowid as socid, s.nom as name, s.client, t.rowid, t.ref, t.ref_supplier, t.total_ht FROM " . $this->db->prefix() . "societe as s, " . $this->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') . ')'.($dontIncludeCompletedItems ? ' AND t.billed < 1' : ''),
11662 ),
11663 'invoice_supplier' => array(
11664 'enabled' => isModEnabled("supplier_invoice"),
11665 'perms' => 1, 'label' => 'LinkToSupplierInvoice',
11666 'sql' => "SELECT s.rowid as socid, s.nom as name, s.client, t.rowid, t.ref, t.ref_supplier, t.total_ht FROM " . $this->db->prefix() . "societe as s, " . $this->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') . ')'.($dontIncludeCompletedItems ? ' AND t.paye < 1' : ''),
11667 ),
11668 'ticket' => array(
11669 'enabled' => isModEnabled('ticket'),
11670 'perms' => 1,
11671 'label' => 'LinkToTicket',
11672 'sql' => "SELECT s.rowid as socid, s.nom as name, s.client, t.rowid, t.ref, t.track_id, '0' as total_ht FROM " . $this->db->prefix() . "societe as s, " . $this->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') . ')'.($dontIncludeCompletedItems ? ' AND t.fk_statut < 8' : ''),
11673 ),
11674 'mo' => array(
11675 'enabled' => isModEnabled('mrp'),
11676 'perms' => 1,
11677 'label' => 'LinkToMo',
11678 'sql' => "SELECT s.rowid as socid, s.nom as name, s.client, t.rowid, t.ref, t.rowid, '0' as total_ht FROM " . $this->db->prefix() . "societe as s INNER JOIN " . $this->db->prefix() . "mrp_mo as t ON t.fk_soc = s.rowid WHERE t.fk_soc IN (" . $this->db->sanitize($listofidcompanytoscan) . ') AND t.entity IN (' . getEntity('mo') . ')'.($dontIncludeCompletedItems ? ' AND t.status < 3' : ''),
11679 ),
11680 );
11681 }
11682
11683 if ($object->table_element == 'commande_fournisseur') {
11684 $possiblelinks['mo']['sql'] = "SELECT s.rowid as socid, s.nom as name, s.client, t.rowid, t.ref, t.rowid, '0' as total_ht FROM ".$this->db->prefix()."societe as s INNER JOIN ".$this->db->prefix().'mrp_mo as t ON t.fk_soc = s.rowid WHERE t.entity IN ('.getEntity('mo').')'.($dontIncludeCompletedItems ? ' AND t.status < 3' : '');
11685 } elseif ($object->table_element == 'mrp_mo') {
11686 $possiblelinks['order_supplier']['sql'] = "SELECT s.rowid as socid, s.nom as name, s.client, t.rowid, t.ref, t.ref_supplier, t.total_ht FROM ".$this->db->prefix()."societe as s, ".$this->db->prefix().'commande_fournisseur as t WHERE t.fk_soc = s.rowid AND t.entity IN ('.getEntity('commande_fournisseur').')'.($dontIncludeCompletedItems ? ' AND t.billed < 1' : '');
11687 }
11688
11689 $reshook = 0; // Ensure $reshook is defined for static analysis
11690 if (!empty($listofidcompanytoscan)) { // If empty, we don't have criteria to scan the object we can link to
11691 // Can complete the possiblelink array
11692 $hookmanager->initHooks(array('commonobject'));
11693 $parameters = array('listofidcompanytoscan' => $listofidcompanytoscan, 'possiblelinks' => $possiblelinks);
11694 $reshook = $hookmanager->executeHooks('showLinkToObjectBlock', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
11695 }
11696
11697 if (empty($reshook)) {
11698 if (is_array($hookmanager->resArray) && count($hookmanager->resArray)) {
11699 $possiblelinks = array_merge($possiblelinks, $hookmanager->resArray);
11700 }
11701 } elseif ($reshook > 0) {
11702 if (is_array($hookmanager->resArray) && count($hookmanager->resArray)) {
11703 $possiblelinks = $hookmanager->resArray;
11704 }
11705 }
11706
11707 if (!empty($possiblelinks)) {
11708 $object->fetchObjectLinked();
11709 }
11710
11711 // Build the html part with possible suggested links
11712 $htmltoenteralink = '';
11713 foreach ($possiblelinks as $key => $possiblelink) {
11714 $num = 0;
11715 if (empty($possiblelink['enabled'])) {
11716 continue;
11717 }
11718
11719
11720 // If we ask a resource form external module (instead of default path)
11721 $module = '';
11722 if (preg_match('/^([^@]+)@([^@]+)$/i', $key, $regs)) { // 'myobject@mymodule'
11723 $key = $regs[1];
11724 $module = $regs[2];
11725 }
11726
11727 if (!empty($possiblelink['perms']) && (empty($restrictlinksto) || in_array($key, $restrictlinksto)) && (empty($excludelinksto) || !in_array($key, $excludelinksto))) {
11728 $htmltoenteralink .= '<div id="' . $key . 'list"' . (empty($conf->use_javascript_ajax) ? '' : ' style="display:none"') . '>';
11729
11730 // Section for free ref input
11731 if (!getDolGlobalString('MAIN_HIDE_LINK_BY_REF_IN_LINKTO')) {
11732 $htmltoenteralink .= '<br>'."\n";
11733 $htmltoenteralink .= '<!-- form to add a link from anywhere -->'."\n";
11734 $htmltoenteralink .= '<form action="' . $_SERVER["PHP_SELF"] . '" method="POST" name="formlinkedbyref' . $key . '">';
11735 $htmltoenteralink .= '<input type="hidden" name="token" value="' . newToken() . '">';
11736 $htmltoenteralink .= '<input type="hidden" name="action" value="addlinkbyref">';
11737 $htmltoenteralink .= '<input type="hidden" name="id" value="' . $object->id . '">';
11738 $htmltoenteralink .= '<input type="hidden" name="addlink" value="' . $key .(!empty($module) ? '@'.$module : ''). '">';
11739 $htmltoenteralink .= '<table class="noborder">';
11740 $htmltoenteralink .= '<tr class="liste_titre">';
11741 //print '<td>' . $langs->trans("Ref") . '</td>';
11742 $htmltoenteralink .= '<td class="center"><input type="text" placeholder="'.dol_escape_htmltag($langs->trans("Ref")).'" name="reftolinkto" value="' . dol_escape_htmltag(GETPOST('reftolinkto', 'alpha')) . '">';
11743 $htmltoenteralink .= '<br>';
11744 $htmltoenteralink .= '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans('ToLink') . '">&nbsp;';
11745 $htmltoenteralink .= '<input type="submit" class="button smallpaddingimp" name="cancel" value="' . $langs->trans('Cancel') . '">';
11746 $htmltoenteralink .= '</td>';
11747 $htmltoenteralink .= '</tr>';
11748 $htmltoenteralink .= '</table>';
11749 $htmltoenteralink .= '</form>';
11750 }
11751
11752 $sql = $possiblelink['sql'];
11753
11754 $resqllist = $this->db->query($sql);
11755 if ($resqllist) {
11756 $num = $this->db->num_rows($resqllist);
11757
11758 if ($num > 0) {
11759 // Section for free predefined list
11760 if (getDolGlobalString('MAIN_HIDE_LINK_BY_REF_IN_LINKTO')) {
11761 $htmltoenteralink .= '<br>';
11762 }
11763 $htmltoenteralink .= '<!-- form to add a link from object to same thirdparty -->'."\n";
11764 $htmltoenteralink .= '<form action="' . $_SERVER["PHP_SELF"] . '" method="POST" name="formlinked' . $key . '">';
11765 $htmltoenteralink .= '<input type="hidden" name="token" value="' . newToken() . '">';
11766 $htmltoenteralink .= '<input type="hidden" name="action" value="addlink">';
11767 $htmltoenteralink .= '<input type="hidden" name="id" value="' . $object->id . '">';
11768 $htmltoenteralink .= '<input type="hidden" name="addlink" value="' . $key . (!empty($module) ? '@'.$module : ''). '">';
11769 $htmltoenteralink .= '<table class="noborder">';
11770
11771 switch ($key) {
11772 case 'conferenceorboothattendee':
11773 // Custom logic for linking to attendees
11774 $htmltoenteralink .= $this->makeAddLinkToAttendee($object, $key, $possiblelink, $num, $resqllist);
11775 break;
11776
11777 default:
11778 // Standard logic for all other object types
11779 $htmltoenteralink .= $this->makeAddLinkToObject($object, $key, $possiblelink, $num, $resqllist);
11780 break;
11781 }
11782
11783 $htmltoenteralink .= '</table>';
11784 $htmltoenteralink .= '<div class="center">';
11785 if ($num) {
11786 $htmltoenteralink .= '<input type="submit" class="button valignmiddle marginleftonly marginrightonly smallpaddingimp" value="' . $langs->trans('ToLink') . '">';
11787 }
11788 if (empty($conf->use_javascript_ajax)) {
11789 $htmltoenteralink .= '<input type="submit" class="button button-cancel marginleftonly marginrightonly smallpaddingimp" name="cancel" value="' . $langs->trans("Cancel") . '"></div>';
11790 } else {
11791 $htmltoenteralink .= '<input type="submit" onclick="jQuery(\'#' . $key . 'list\').toggle(); return false;" class="button button-cancel marginleftonly marginrightonly smallpaddingimp" name="cancel" value="' . $langs->trans("Cancel") . '"></div>';
11792 }
11793 $htmltoenteralink .= '</form>';
11794 }
11795
11796 $this->db->free($resqllist);
11797 } else {
11798 dol_print_error($this->db);
11799 }
11800 $htmltoenteralink .= '</div>';
11801
11802
11803 // Complete the list for the combo box
11804 if ($num > 0 || !getDolGlobalString('MAIN_HIDE_LINK_BY_REF_IN_LINKTO')) {
11805 $linktoelemlist .= '<li><a href="#linkto' . $key . '" class="linkto dropdowncloseonclick" rel="' . $key . '">' . $langs->trans($possiblelink['label']) . ' (' . $num . ')</a></li>';
11806 // } else $linktoelem.=$langs->trans($possiblelink['label']);
11807 } else {
11808 $linktoelemlist .= '<li><span class="linktodisabled">' . $langs->trans($possiblelink['label']) . ' (0)</span></li>';
11809 }
11810 }
11811 }
11812
11813 if ($linktoelemlist) {
11814 $linktoelem = '
11815 <dl class="dropdown" id="linktoobjectname">
11816 ';
11817 if (!empty($conf->use_javascript_ajax)) {
11818 $linktoelem .= '<dt><a href="#linktoobjectname"><span class="fas fa-link paddingrightonly"></span>' . $langs->trans("LinkTo") . '...</a></dt>';
11819 }
11820 $linktoelem .= '<dd>
11821 <div class="multiselectlinkto">
11822 <ul class="ulselectedfields">' . $linktoelemlist . '
11823 </ul>
11824 </div>
11825 </dd>
11826 </dl>';
11827 } else {
11828 $linktoelem = '';
11829 }
11830
11831 if (!empty($conf->use_javascript_ajax)) {
11832 print '<!-- Add js to show linkto box -->
11833 <script nonce="' . getNonce() . '">
11834 jQuery(document).ready(function() {
11835 jQuery(".linkto").click(function() {
11836 console.log("We choose to show/hide links for rel="+jQuery(this).attr(\'rel\')+" so #"+jQuery(this).attr(\'rel\')+"list");
11837 jQuery("#"+jQuery(this).attr(\'rel\')+"list").toggle();
11838 });
11839 });
11840 </script>
11841 ';
11842 }
11843
11844 if ($nooutput) {
11845 return array('linktoelem' => $linktoelem, 'htmltoenteralink' => $htmltoenteralink);
11846 } else {
11847 print $htmltoenteralink;
11848 }
11849
11850 return $linktoelem;
11851 }
11852
11867 public function selectyesno($htmlname, $value = '', $option = 0, $disabled = false, $useempty = 0, $addjscombo = 0, $morecss = 'yesno width75', $labelyes = 'Yes', $labelno = 'No')
11868 {
11869 global $langs;
11870
11871 $yes = "yes";
11872 $no = "no";
11873 if ($option) {
11874 $yes = "1";
11875 $no = "0";
11876 }
11877
11878 $disabled = ($disabled ? ' disabled' : '');
11879
11880 $resultyesno = '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" id="' . $htmlname . '" name="' . $htmlname . '"' . $disabled . '>' . "\n";
11881 if ($useempty) {
11882 $resultyesno .= '<option value="-1"' . (($value < 0) ? ' selected' : '') . '>&nbsp;</option>' . "\n";
11883 }
11884 if (("$value" == 'yes') || ($value == 1)) {
11885 $resultyesno .= '<option value="' . $yes . '" selected>' . $langs->trans($labelyes) . '</option>' . "\n";
11886 $resultyesno .= '<option value="' . $no . '">' . $langs->trans($labelno) . '</option>' . "\n";
11887 } else {
11888 $selected = (($useempty && $value != '0' && $value != 'no') ? '' : ' selected');
11889 $resultyesno .= '<option value="' . $yes . '">' . $langs->trans($labelyes) . '</option>' . "\n";
11890 $resultyesno .= '<option value="' . $no . '"' . $selected . '>' . $langs->trans($labelno) . '</option>' . "\n";
11891 }
11892 $resultyesno .= '</select>' . "\n";
11893
11894 if ($addjscombo) {
11895 $resultyesno .= ajax_combobox($htmlname, array(), 0, 0, 'resolve', ($useempty < 0 ? (string) $useempty : '-1'), $morecss);
11896 }
11897
11898 return $resultyesno;
11899 }
11900
11901 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
11902
11912 public function select_export_model($selected = '', $htmlname = 'exportmodelid', $type = '', $useempty = 0)
11913 {
11914 // phpcs:enable
11915 $sql = "SELECT rowid, label";
11916 $sql .= " FROM " . $this->db->prefix() . "export_model";
11917 $sql .= " WHERE type = '" . $this->db->escape($type) . "'";
11918 $sql .= " ORDER BY rowid";
11919 $result = $this->db->query($sql);
11920 if ($result) {
11921 print '<select class="flat" id="select_' . $htmlname . '" name="' . $htmlname . '">';
11922 if ($useempty) {
11923 print '<option value="-1">&nbsp;</option>';
11924 }
11925
11926 $num = $this->db->num_rows($result);
11927 $i = 0;
11928 while ($i < $num) {
11929 $obj = $this->db->fetch_object($result);
11930 if ($selected == $obj->rowid) {
11931 print '<option value="' . $obj->rowid . '" selected>';
11932 } else {
11933 print '<option value="' . $obj->rowid . '">';
11934 }
11935 print $obj->label;
11936 print '</option>';
11937 $i++;
11938 }
11939 print "</select>";
11940 } else {
11941 dol_print_error($this->db);
11942 }
11943 }
11944
11963 public function showrefnav($object, $paramid, $morehtml = '', $shownav = 1, $fieldid = 'rowid', $fieldref = 'ref', $morehtmlref = '', $moreparam = '', $nodbprefix = 0, $morehtmlleft = '', $morehtmlstatus = '', $morehtmlright = '')
11964 {
11965 global $conf, $langs, $hookmanager, $extralanguages;
11966
11967 $ret = '';
11968 if (empty($fieldid)) {
11969 $fieldid = 'rowid';
11970 }
11971 if (empty($fieldref)) {
11972 $fieldref = 'ref';
11973 }
11974
11975 // Preparing gender's display if there is one
11976 $addgendertxt = '';
11977 if (property_exists($object, 'gender') && !empty($object->gender)) {
11978 $addgendertxt = ' ';
11979 switch ($object->gender) {
11980 case 'man':
11981 $addgendertxt .= '<i class="fas fa-mars valignmiddle"></i>';
11982 break;
11983 case 'woman':
11984 $addgendertxt .= '<i class="fas fa-venus valignmiddle"></i>';
11985 break;
11986 case 'other':
11987 $addgendertxt .= '<i class="fas fa-transgender valignmiddle"></i>';
11988 break;
11989 }
11990 }
11991
11992 // Add where from hooks
11993 if (is_object($hookmanager)) {
11994 $parameters = array('showrefnav' => true);
11995 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $object); // Note that $action and $object may have been modified by hook
11996 if (!empty($hookmanager->resPrint)) {
11997 if (empty($object->next_prev_filter) && preg_match('/^\s*AND/i', $hookmanager->resPrint)) {
11998 $object->next_prev_filter = (string) preg_replace('/^\s*AND\s*/i', '', $hookmanager->resPrint);
11999 } elseif (!empty($object->next_prev_filter) && !preg_match('/^\s*AND/i', $hookmanager->resPrint)) {
12000 $object->next_prev_filter .= ' AND '.$hookmanager->resPrint;
12001 } else {
12002 $object->next_prev_filter .= $hookmanager->resPrint;
12003 }
12004 }
12005 }
12006
12007 $previous_ref = $next_ref = '';
12008 if ($shownav) {
12009 //print "paramid=$paramid,morehtml=$morehtml,shownav=$shownav,fieldid=$fieldid,filedref=$fieldref,morehtmlref=$morehtmlref,moreparam=$moreparam";
12010 $object->load_previous_next_ref((isset($object->next_prev_filter) ? $object->next_prev_filter : ''), $fieldid, $nodbprefix);
12011
12012 $navurl = $_SERVER["PHP_SELF"];
12013
12014 // Special case for token card
12015 if ($paramid == 'api_token_card') {
12016 if (preg_match('/\/user\/api_token/', $navurl)) {
12017 $navurl = preg_replace('/card/', 'list', $navurl);
12018 $paramid = 'id';
12019 }
12020 }
12021
12022 // Special case for project/task page
12023 if ($paramid == 'project_ref') {
12024 if (preg_match('/\/tasks\/(task|contact|note|document)\.php/', $navurl)) { // TODO Remove this when nav with project_ref on task pages are ok
12025 $navurl = preg_replace('/\/tasks\/(task|contact|time|note|document)\.php/', '/tasks.php', $navurl);
12026 $paramid = 'ref';
12027 }
12028 }
12029
12030 $previous_ref = $object->ref_previous ? '<a accesskey="p" alt="'.dol_escape_htmltag($langs->trans("Previous")).'" title="' . $conf->browser->stringforfirstkey . ' p" class="classfortooltip reposition" 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>';
12031 $next_ref = $object->ref_next ? '<a accesskey="n" alt="'.dol_escape_htmltag($langs->trans("Next")).'" title="' . $conf->browser->stringforfirstkey . ' n" class="classfortooltip reposition" 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>';
12032 }
12033
12034 //print "xx".$previous_ref."x".$next_ref;
12035 $ret .= '<!-- Start banner content --><div style="vertical-align: middle">';
12036
12037 // Right part of banner
12038 if ($morehtmlright) {
12039 $ret .= '<div class="inline-block floatleft">' . $morehtmlright . '</div>';
12040 }
12041
12042 if ($previous_ref || $next_ref || $morehtml) {
12043 $ret .= '<div class="pagination paginationref"><ul class="right">';
12044 }
12045 if ($morehtml && getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2) {
12046 $ret .= '<!-- morehtml --><li class="noborder litext' . (($shownav && $previous_ref && $next_ref) ? ' clearbothonsmartphone' : '') . '">' . $morehtml . '</li>';
12047 }
12048 if ($shownav && ($previous_ref || $next_ref)) {
12049 $ret .= '<li class="pagination">' . $previous_ref . '</li>';
12050 $ret .= '<li class="pagination">' . $next_ref . '</li>';
12051 }
12052 if ($previous_ref || $next_ref || $morehtml) {
12053 $ret .= '</ul></div>';
12054 }
12055
12056 // Status
12057 $parameters = array('morehtmlstatus' => $morehtmlstatus);
12058 $reshook = $hookmanager->executeHooks('moreHtmlStatus', $parameters, $object); // Note that $action and $object may have been modified by hook
12059 if (empty($reshook)) {
12060 $morehtmlstatus .= $hookmanager->resPrint;
12061 } else {
12062 $morehtmlstatus = $hookmanager->resPrint;
12063 }
12064 if ($morehtmlstatus) {
12065 $ret .= '<!-- status --><div class="statusref">' . $morehtmlstatus . '</div>';
12066 }
12067
12068 $parameters = array();
12069 $reshook = $hookmanager->executeHooks('moreHtmlRef', $parameters, $object); // Note that $action and $object may have been modified by hook
12070 if (empty($reshook)) {
12071 $morehtmlref .= $hookmanager->resPrint;
12072 } elseif ($reshook > 0) {
12073 $morehtmlref = $hookmanager->resPrint;
12074 }
12075
12076 // Left part of banner
12077 if ($morehtmlleft) {
12078 if ($conf->browser->layout == 'phone') {
12079 $ret .= '<!-- morehtmlleft --><div class="floatleft">' . $morehtmlleft . '</div>';
12080 } else {
12081 $ret .= '<!-- morehtmlleft --><div class="inline-block floatleft">' . $morehtmlleft . '</div>';
12082 }
12083 }
12084
12085 //if ($conf->browser->layout == 'phone') $ret.='<div class="clearboth"></div>';
12086 $ret .= '<!-- Ref or ID --><div class="inline-block floatleft valignmiddle maxwidth750 marginbottomonly refid' . (($shownav && ($previous_ref || $next_ref)) ? ' refidpadding' : '') . '">';
12087
12088 // For thirdparty, contact, user, member, the ref is the id, so we show something else
12089 if ($object->element == 'societe') {
12090 $ret .= '<span class="valignmiddle">'.dolPrintHTML((string) $object->name).'</span>';
12091
12092 // List of extra languages
12093 $arrayoflangcode = array();
12094 if (getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE')) {
12095 $arrayoflangcode[] = getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE');
12096 }
12097
12098 if (is_array($arrayoflangcode) && count($arrayoflangcode)) {
12099 if (!is_object($extralanguages)) {
12100 include_once DOL_DOCUMENT_ROOT . '/core/class/extralanguages.class.php';
12101 $extralanguages = new ExtraLanguages($this->db);
12102 }
12103 $extralanguages->fetch_name_extralanguages('societe');
12104
12105 // Guard against PHP 8 'Undefined array key' when MAIN_USE_ALTERNATE_TRANSLATION_FOR
12106 // is not configured and fetch_name_extralanguages() leaves attributes empty (issue #34596).
12107 if (!empty($extralanguages->attributes['societe']) && !empty($extralanguages->attributes['societe']['name'])) {
12108 $object->fetchValuesForExtraLanguages();
12109
12110 $htmltext = '';
12111 // If there is extra languages
12112 foreach ($arrayoflangcode as $extralangcode) {
12113 $htmltext .= picto_from_langcode($extralangcode, 'class="pictoforlang paddingright"');
12114 if ($object->array_languages['name'][$extralangcode]) {
12115 $htmltext .= $object->array_languages['name'][$extralangcode];
12116 } else {
12117 $htmltext .= '<span class="opacitymedium">' . $langs->trans("SwitchInEditModeToAddTranslation") . '</span>';
12118 }
12119 }
12120 $ret .= '<!-- Show translations of name -->' . "\n";
12121 $ret .= $this->textwithpicto('', $htmltext, -1, 'language', 'opacitymedium paddingleft');
12122 }
12123 }
12124 } elseif ($object->element == 'member') {
12125 '@phan-var-force Adherent $object';
12126 $ret .= $object->ref . '<br>';
12127 $fullname = $object->getFullName($langs);
12128 if ($object->morphy == 'mor' && $object->societe) {
12129 $ret .= '<span class="valignmiddle">'.dolPrintHTML((string) $object->societe) . ((!empty($fullname) && $object->societe != $fullname) ? ' (' . dol_htmlentities($fullname) . $addgendertxt . ')' : '').'</span>';
12130 } else {
12131 $ret .= '<span class="valignmiddle">'.dolPrintHTML($fullname) . $addgendertxt . ((!empty($object->societe) && $object->societe != $fullname) ? ' (' . dol_htmlentities((string) $object->societe) . ')' : '').'</span>';
12132 }
12133 } elseif (in_array($object->element, array('contact', 'user'))) {
12134 $ret .= '<span class="valignmiddle">'.dolPrintHTML($object->getFullName($langs)).'</span>'.$addgendertxt;
12135 } elseif ($object->element == 'usergroup') {
12136 $ret .= dol_htmlentities((string) $object->name);
12137 } elseif (in_array($object->element, array('action', 'agenda'))) {
12138 '@phan-var-force ActionComm $object';
12139 $ret .= $object->ref . '<br>' . $object->label;
12140 } elseif (in_array($object->element, array('adherent_type'))) {
12141 $ret .= $object->label;
12142 } elseif ($object->element == 'ecm_directories') {
12143 $ret .= '';
12144 } elseif ($object->element == 'accountingbookkeeping' && !empty($object->context['mode']) && $object->context['mode'] == '_tmp') {
12145 $ret .= '<span class="valignmiddle">'.$langs->trans("Draft").'</span>';
12146 } elseif ($object instanceof Ticket) {
12147 '@phan-var-force Ticket $object';
12148 $ret .= '<span class="valignmiddle">'.dolPrintHTML(!empty($object->$fieldref) ? $object->$fieldref : "").'</span>';
12149 $ret .= ' &nbsp; <span class="nobold small" title="'.dolPrintHTMLForAttribute($langs->trans("TicketTrackId")).'">('.$object->track_id.')</span>';
12150 } elseif ($fieldref != 'none') {
12151 // Generic case
12152 $ret .= '<span class="valignmiddle">'.dolPrintHTML(!empty($object->$fieldref) ? $object->$fieldref : "").'</span>';
12153 }
12154 if ($morehtmlref) {
12155 // don't add a additional space, when "$morehtmlref" starts with a HTML div tag
12156 if (substr($morehtmlref, 0, 4) != '<div') {
12157 $ret .= ' ';
12158 }
12159
12160 $ret .= '<!-- morehtmlref -->'.$morehtmlref;
12161 }
12162
12163 $ret .= '</div>';
12164
12165 $ret .= '</div><!-- End banner content -->';
12166
12167 return $ret;
12168 }
12169
12170
12179 public function showbarcode(&$object, $width = 100, $morecss = '')
12180 {
12181 //Check if barcode is filled in the card
12182 if (empty($object->barcode)) {
12183 return '';
12184 }
12185
12186 // Complete object if not complete
12187 if (empty($object->barcode_type_code) || empty($object->barcode_type_coder)) {
12188 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
12189 $result = $object->fetchBarCode();
12190 //Check if fetchBarCode() failed
12191 if ($result < 1) {
12192 return '<!-- ErrorFetchBarcode -->';
12193 }
12194 }
12195
12196 // Barcode image @phan-suppress-next-line PhanUndeclaredProperty
12197 $url = DOL_URL_ROOT . '/viewimage.php?modulepart=barcode&generator=' . urlencode($object->barcode_type_coder) . '&code=' . urlencode($object->barcode) . '&encoding=' . urlencode($object->barcode_type_code);
12198 $out = '<!-- url barcode = ' . $url . ' -->';
12199 $out .= '<img src="' . $url . '"' . ($morecss ? ' class="' . $morecss . '"' : '') . '>';
12200
12201 return $out;
12202 }
12203
12222 public static function showphoto($modulepart, $object, $width = 100, $height = 0, $caneditfield = 0, $cssclass = 'photowithmargin', $imagesize = '', $addlinktofullsize = 1, $cache = 0, $forcecapture = '', $noexternsourceoverwrite = 0, $usesharelinkifavailable = 0)
12223 {
12224 global $conf, $db, $langs;
12225
12226 $entity = (empty($object->entity) ? $conf->entity : $object->entity);
12227 $id = (empty($object->id) ? $object->rowid : $object->id); // @phan-suppress-current-line PhanUndeclaredProperty (->rowid)
12228
12229 $dir = '';
12230 $file = '';
12231 $originalfile = '';
12232 $altfile = '';
12233 $email = '';
12234 $capture = '';
12235 if ($modulepart == 'societe') {
12236 $dir = $conf->societe->multidir_output[$entity];
12237 if (!empty($object->logo)) {
12238 if (dolIsAllowedForPreview($object->logo)) {
12239 if ((string) $imagesize == 'mini') {
12240 $file = get_exdir(0, 0, 0, 0, $object, 'thirdparty') . 'logos/' . getImageFileNameForSize($object->logo, '_mini'); // getImageFileNameForSize include the thumbs
12241 } elseif ((string) $imagesize == 'small') {
12242 $file = get_exdir(0, 0, 0, 0, $object, 'thirdparty') . 'logos/' . getImageFileNameForSize($object->logo, '_small');
12243 } else {
12244 $file = get_exdir(0, 0, 0, 0, $object, 'thirdparty') . 'logos/' . $object->logo;
12245 }
12246 $originalfile = get_exdir(0, 0, 0, 0, $object, 'thirdparty') . 'logos/' . $object->logo;
12247 }
12248 }
12249 $email = $object->email;
12250 } elseif ($modulepart == 'contact') {
12251 $dir = $conf->societe->multidir_output[$entity] . '/contact';
12252 $photo = $object->photo; // Copy to help static analysis
12253 if (!empty($photo)) {
12254 if (dolIsAllowedForPreview($photo)) {
12255 if ((string) $imagesize == 'mini') {
12256 $file = get_exdir(0, 0, 0, 0, $object, 'contact') . 'photos/' . getImageFileNameForSize($photo, '_mini');
12257 } elseif ((string) $imagesize == 'small') {
12258 $file = get_exdir(0, 0, 0, 0, $object, 'contact') . 'photos/' . getImageFileNameForSize($photo, '_small');
12259 } else {
12260 $file = get_exdir(0, 0, 0, 0, $object, 'contact') . 'photos/' . $photo;
12261 }
12262 $originalfile = get_exdir(0, 0, 0, 0, $object, 'contact') . 'photos/' . $photo;
12263 }
12264 }
12265 $email = $object->email;
12266 $capture = 'user';
12267 } elseif ($modulepart == 'userphoto') {
12268 $dir = $conf->user->dir_output;
12269 $photo = $object->photo; // Copy to help static analysis
12270 if (!empty($photo)) {
12271 if (dolIsAllowedForPreview($photo)) {
12272 if ((string) $imagesize == 'mini') {
12273 $file = get_exdir(0, 0, 0, 0, $object, 'user') . 'photos/' . getImageFileNameForSize($photo, '_mini');
12274 } elseif ((string) $imagesize == 'small') {
12275 $file = get_exdir(0, 0, 0, 0, $object, 'user') . 'photos/' . getImageFileNameForSize($photo, '_small');
12276 } else {
12277 $file = get_exdir(0, 0, 0, 0, $object, 'user') . 'photos/' . $photo;
12278 }
12279 $originalfile = get_exdir(0, 0, 0, 0, $object, 'user') . 'photos/' . $photo;
12280 }
12281 }
12282 if (getDolGlobalString('MAIN_OLD_IMAGE_LINKS')) {
12283 $altfile = $object->id . ".jpg"; // For backward compatibility
12284 }
12285 $email = $object->email;
12286 $capture = 'user';
12287 } elseif ($modulepart == 'memberphoto') {
12288 $dir = $conf->member->dir_output;
12289 $photo = $object->photo; // Copy to help static analysis
12290 if (!empty($photo)) {
12291 if (dolIsAllowedForPreview($photo)) {
12292 if ((string) $imagesize == 'mini') {
12293 $file = get_exdir(0, 0, 0, 0, $object, 'member') . 'photos/' . getImageFileNameForSize($photo, '_mini');
12294 } elseif ((string) $imagesize == 'small') {
12295 $file = get_exdir(0, 0, 0, 0, $object, 'member') . 'photos/' . getImageFileNameForSize($photo, '_small');
12296 } else {
12297 $file = get_exdir(0, 0, 0, 0, $object, 'member') . 'photos/' . $photo;
12298 }
12299 $originalfile = get_exdir(0, 0, 0, 0, $object, 'member') . 'photos/' . $photo;
12300 }
12301 }
12302 if (getDolGlobalString('MAIN_OLD_IMAGE_LINKS')) {
12303 $altfile = $object->id . ".jpg"; // For backward compatibility
12304 }
12305 $email = $object->email;
12306 $capture = 'user';
12307 } else {
12308 // Generic case to show photos
12309 // TODO Implement this method in previous objects so we can always use this generic method.
12310 if ($modulepart != "unknown" && method_exists($object, 'getDataToShowPhoto')) {
12311 $tmpdata = $object->getDataToShowPhoto($modulepart, $imagesize);
12312
12313 $dir = $tmpdata['dir'];
12314 $file = $tmpdata['file'];
12315 $originalfile = $tmpdata['originalfile'];
12316 $altfile = $tmpdata['altfile'];
12317 $email = $tmpdata['email'];
12318 $capture = $tmpdata['capture'];
12319 }
12320 }
12321
12322 if ($forcecapture) {
12323 $capture = $forcecapture;
12324 }
12325
12326 $ret = '';
12327
12328 if ($dir) {
12329 if ($file && file_exists($dir . "/" . $file)) {
12330 if ($addlinktofullsize) {
12331 $urladvanced = getAdvancedPreviewUrl($modulepart, $originalfile, 0, '&entity=' . $entity);
12332 if ($urladvanced) {
12333 $ret .= '<a href="' . $urladvanced . '">';
12334 } else {
12335 $ret .= '<a href="' . DOL_URL_ROOT . '/viewimage.php?modulepart=' . $modulepart . '&entity=' . $entity . '&file=' . urlencode($originalfile) . '&cache=' . $cache . '">';
12336 }
12337 }
12338
12339 $sharekey = '';
12340 if ($usesharelinkifavailable) {
12341 // $dir is a full path '/home/.../dolibarr_documents/module'
12342 $relativefileforecm = preg_replace('/^'.preg_quote(DOL_DATA_ROOT.'/', '/').'/', '', $dir.'/'.$originalfile);
12343 // $relativefileforecme = 'module/...'
12344 require_once DOL_DOCUMENT_ROOT . '/ecm/class/ecmfiles.class.php';
12345 $ecmfiles = new EcmFiles($db);
12346 $ecmfiles->fetch(0, '', $relativefileforecm);
12347
12348 $sharekey = (string) $ecmfiles->share;
12349 }
12350
12351 if (!empty($sharekey)) {
12352 $ret .= '<img alt="" class="photo' . $modulepart . ($cssclass ? ' ' . $cssclass : '') . ' photologo' . (preg_replace('/[^a-z]/i', '_', $file)) . '" ' . ($width ? ' width="' . $width . '"' : '') . ($height ? ' height="' . $height . '"' : '') . ' src="' . DOL_URL_ROOT . '/viewimage.php?hashp=' . urlencode($sharekey) . '&cache=' . urlencode((string) $cache) . '">';
12353 } else {
12354 $ret .= '<img alt="" 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=' . urlencode($modulepart) . '&entity=' . ((int) $entity) . '&file=' . urlencode($file) . '&cache=' . urlencode((string) $cache) . '">';
12355 }
12356 if ($addlinktofullsize) {
12357 $ret .= '</a>';
12358 }
12359 } elseif ($altfile && file_exists($dir . "/" . $altfile)) {
12360 if ($addlinktofullsize) {
12361 $urladvanced = getAdvancedPreviewUrl($modulepart, $originalfile, 0, '&entity=' . $entity);
12362 if ($urladvanced) {
12363 $ret .= '<a href="' . $urladvanced . '">';
12364 } else {
12365 $ret .= '<a href="' . DOL_URL_ROOT . '/viewimage.php?modulepart=' . $modulepart . '&entity=' . $entity . '&file=' . urlencode($originalfile) . '&cache=' . $cache . '">';
12366 }
12367 }
12368 $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=' . urlencode($modulepart) . '&entity=' . ((int) $entity) . '&file=' . urlencode($altfile) . '&cache=' . urlencode((string) $cache) . '">';
12369 if ($addlinktofullsize) {
12370 $ret .= '</a>';
12371 }
12372 } else {
12373 $nophoto = '/public/theme/common/nophoto.png';
12374 $defaultimg = 'identicon'; // For gravatar
12375 if (in_array($modulepart, array('societe', 'userphoto', 'contact', 'memberphoto'))) { // For modules that need a special image when photo not found
12376 if ($modulepart == 'societe' || ($modulepart == 'memberphoto' && !empty($object->morphy) && strpos($object->morphy, 'mor') !== false)) {
12377 $nophoto = 'company';
12378 } else {
12379 $nophoto = '/public/theme/common/user_anonymous.png';
12380 if (!empty($object->gender) && $object->gender == 'man') {
12381 $nophoto = '/public/theme/common/user_man.png';
12382 }
12383 if (!empty($object->gender) && $object->gender == 'woman') {
12384 $nophoto = '/public/theme/common/user_woman.png';
12385 }
12386 }
12387 }
12388
12389 if (isModEnabled('gravatar') && $email && empty($noexternsourceoverwrite)) {
12390 // see https://gravatar.com/site/implement/images/php/
12391 $ret .= '<!-- Put link to gravatar -->';
12392 $ret .= '<img class="gravatar photo' . $modulepart . ($cssclass ? ' ' . $cssclass : '') . '" alt="" title="'.dolPrintHTMLForAttribute('Gravatar avatar - '.$email).'" ' . ($width ? ' width="' . $width . '"' : '') . ($height ? ' height="' . $height . '"' : '') . ' src="https://www.gravatar.com/avatar/' . dol_hash(strtolower(trim($email)), 'sha256', 1) . '?s=' . $width . '&d=' . $defaultimg . '">'; // gravatar need md5 hash
12393 } else {
12394 if ($nophoto == 'company') {
12395 $ret .= '<div class="divforspanimg valignmiddle inline-block center photo' . $modulepart . ($cssclass ? ' ' . $cssclass : '') . '" alt="" ' . ($width ? ' width="' . $width . '"' : '') . ($height ? ' height="' . $height . '"' : '') . '>' . img_picto('', 'company') . '</div>';
12396 //$ret .= '<div class="difforspanimgright"></div>';
12397 } else {
12398 $ret .= '<img class="photo' . $modulepart . ($cssclass ? ' ' . $cssclass : '') . '" alt="" ' . ($width ? ' width="' . $width . '"' : '') . ($height ? ' height="' . $height . '"' : '') . ' src="' . DOL_URL_ROOT . $nophoto . '">';
12399 }
12400 }
12401 }
12402
12403 if ($caneditfield) {
12404 if ($object->photo) {
12405 $ret .= "<br>\n";
12406 }
12407 $ret .= '<table class="nobordernopadding centpercent">';
12408 if ($object->photo) {
12409 $ret .= '<tr><td><input type="checkbox" class="flat photodelete" name="deletephoto" id="photodelete"> <label for="photodelete">' . $langs->trans("Delete") . '</label><br><br></td></tr>';
12410 }
12411 $ret .= '<tr><td class="tdoverflow">';
12412 $maxfilesizearray = getMaxFileSizeArray();
12413 $maxmin = $maxfilesizearray['maxmin'];
12414 if ($maxmin > 0) {
12415 $ret .= '<input type="hidden" name="MAX_FILE_SIZE" value="' . ($maxmin * 1024) . '">'; // MAX_FILE_SIZE must precede the field type=file
12416 }
12417 $ret .= '<input type="file" class="flat maxwidth200onsmartphone" name="photo" id="photoinput" accept="image/*"' . ($capture ? ' capture="' . dolPrintHTMLForAttribute($capture) . '"' : '') . '>';
12418 $ret .= '</td></tr>';
12419 $ret .= '</table>';
12420 }
12421 }
12422
12423 return $ret;
12424 }
12425
12426 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
12427
12444 public function select_dolgroups($selected = 0, $htmlname = 'groupid', $show_empty = 0, $exclude = '', $disabled = 0, $include = '', $enableonly = array(), $force_entity = '0', $multiple = false, $morecss = 'minwidth200')
12445 {
12446 // phpcs:enable
12447 global $conf, $user, $langs;
12448
12449 // Allow excluding groups
12450 $excludeGroups = null;
12451 if (is_array($exclude)) {
12452 $excludeGroups = implode(",", $exclude);
12453 }
12454 // Allow including groups
12455 $includeGroups = null;
12456 if (is_array($include)) {
12457 $includeGroups = implode(",", $include);
12458 }
12459
12460 if (!is_array($selected)) {
12461 $selected = array($selected);
12462 }
12463
12464 $out = '';
12465
12466 // Build sql to search groups
12467 $sql = "SELECT ug.rowid, ug.nom as name";
12468 if (isModEnabled('multicompany') && $conf->entity == 1 && $user->admin && !$user->entity) {
12469 $sql .= ", e.label";
12470 }
12471 $sql .= " FROM " . $this->db->prefix() . "usergroup as ug ";
12472 if (isModEnabled('multicompany') && $conf->entity == 1 && $user->admin && !$user->entity) {
12473 $sql .= " LEFT JOIN " . $this->db->prefix() . "entity as e ON e.rowid=ug.entity";
12474 if ($force_entity) {
12475 $sql .= " WHERE ug.entity IN (0, " . ((int) $force_entity) . ")";
12476 } else {
12477 $sql .= " WHERE ug.entity IS NOT NULL";
12478 }
12479 } else {
12480 $sql .= " WHERE ug.entity IN (0, " . ((int) $conf->entity) . ")";
12481 }
12482 if (is_array($exclude) && $excludeGroups) {
12483 $sql .= " AND ug.rowid NOT IN (" . $this->db->sanitize($excludeGroups) . ")";
12484 }
12485 if (is_array($include) && $includeGroups) {
12486 $sql .= " AND ug.rowid IN (" . $this->db->sanitize($includeGroups) . ")";
12487 }
12488 $sql .= " ORDER BY ug.nom ASC";
12489
12490 dol_syslog(get_class($this) . "::select_dolgroups", LOG_DEBUG);
12491 $resql = $this->db->query($sql);
12492 if ($resql) {
12493 // Enhance with select2
12494 include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
12495
12496 $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" id="' . $htmlname . '" name="' . $htmlname . ($multiple ? '[]' : '') . '" ' . ($multiple ? 'multiple' : '') . ' ' . ($disabled ? ' disabled' : '') . '>';
12497
12498 $num = $this->db->num_rows($resql);
12499 $i = 0;
12500 if ($num) {
12501 if ($show_empty && !$multiple) {
12502 $textforempty = '&nbsp;';
12503 if (!is_numeric($show_empty)) {
12504 $textforempty = dol_escape_htmltag($show_empty);
12505 }
12506 $out .= '<option value="-1"' . (in_array(-1, $selected) ? ' selected' : '') . '>' . $textforempty . '</option>' . "\n";
12507 }
12508
12509 while ($i < $num) {
12510 $obj = $this->db->fetch_object($resql);
12511 $disableline = 0;
12512 if (is_array($enableonly) && count($enableonly) && !in_array($obj->rowid, $enableonly)) {
12513 $disableline = 1;
12514 }
12515
12516 $label = $obj->name;
12517 $labelhtml = $obj->name;
12518 if (isModEnabled('multicompany') && !getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE') && $conf->entity == 1) {
12519 $label .= " (" . $obj->label . ")";
12520 $labelhtml .= ' <span class="opacitymedium">(' . $obj->label . ')</span>';
12521 }
12522
12523 $out .= '<option value="' . $obj->rowid . '"';
12524 if ($disableline) {
12525 $out .= ' disabled';
12526 }
12527 if ((isset($selected[0]) && is_object($selected[0]) && $selected[0]->id == $obj->rowid)
12528 || ((!isset($selected[0]) || !is_object($selected[0])) && !empty($selected) && in_array($obj->rowid, $selected))) {
12529 $out .= ' selected';
12530 }
12531 $out .= ' data-html="'.dol_escape_htmltag($labelhtml).'"';
12532 $out .= '>';
12533 $out .= $label;
12534 $out .= '</option>';
12535 $i++;
12536 }
12537 } else {
12538 if ($show_empty) {
12539 $out .= '<option value="-1"' . (in_array(-1, $selected) ? ' selected' : '') . '></option>' . "\n";
12540 }
12541 $out .= '<option value="" disabled>' . $langs->trans("NoUserGroupDefined") . '</option>';
12542 }
12543 $out .= '</select>';
12544
12545 $out .= ajax_combobox($htmlname);
12546 } else {
12547 dol_print_error($this->db);
12548 }
12549
12550 return $out;
12551 }
12552
12553
12560 public function showFilterButtons($pos = '')
12561 {
12562 $out = '<div class="nowraponall">';
12563 $out .= '<button type="submit" class="liste_titre button_search reposition" name="button_search_x" value="x"><span class="fas fa-search"></span></button>';
12564 $out .= '<button type="submit" class="liste_titre button_removefilter reposition" name="button_removefilter_x" value="x"><span class="fas fa-times"></span></button>';
12565 $out .= '</div>';
12566
12567 return $out;
12568 }
12569
12578 public function showCheckAddButtons($cssclass = 'checkforaction', $calljsfunction = 0, $massactionname = "massaction")
12579 {
12580 global $conf;
12581
12582 $out = '';
12583
12584 if (!empty($conf->use_javascript_ajax)) {
12585 $out .= '<div class="inline-block checkallactions"><input type="checkbox" id="' . $cssclass . 's" name="' . $cssclass . 's" class="checkallactions"></div>';
12586 }
12587 $out .= '<script nonce="' . getNonce() . '">
12588 $(document).ready(function() {
12589 $("#' . $cssclass . 's").click(function() {
12590 if($(this).is(\':checked\')){
12591 console.log("We check all ' . $cssclass . ' and trigger the change method");
12592 $(".' . $cssclass . '").prop(\'checked\', true).trigger(\'change\');
12593 }
12594 else
12595 {
12596 console.log("We uncheck all");
12597 $(".' . $cssclass . '").prop(\'checked\', false).trigger(\'change\');
12598 }' . "\n";
12599 if ($calljsfunction) {
12600 $out .= 'if (typeof initCheckForSelect == \'function\') { initCheckForSelect(0, "' . $massactionname . '", "' . $cssclass . '"); } else { console.log("No function initCheckForSelect found. Call won\'t be done."); }';
12601 }
12602 $out .= ' });
12603 $(".' . $cssclass . '").change(function() {
12604 console.log("We check and change the tr class highlight after a change on .'.$cssclass.'");
12605 var $row = $(this).closest("tr");
12606 if ($row.length) {
12607 var anyChecked = $row.find(\'input[type="checkbox"].' . $cssclass . ':checked\').length > 0;
12608 console.log("anychecked="+anyChecked);
12609 if (!anyChecked) {
12610 $row.removeClass("highlight");
12611 } else {
12612 $row.addClass("highlight");
12613 }
12614 }
12615 });
12616 });
12617 </script>';
12618
12619 return $out;
12620 }
12621
12631 public function showFilterAndCheckAddButtons($addcheckuncheckall = 0, $cssclass = 'checkforaction', $calljsfunction = 0, $massactionname = "massaction")
12632 {
12633 $out = $this->showFilterButtons();
12634 if ($addcheckuncheckall) {
12635 $out .= $this->showCheckAddButtons($cssclass, $calljsfunction, $massactionname);
12636 }
12637 return $out;
12638 }
12639
12653 public function selectExpenseCategories($selected = '', $htmlname = 'fk_c_exp_tax_cat', $useempty = 0, $excludeid = array(), $target = '', $default_selected = 0, $params = array(), $info_admin = 1)
12654 {
12655 global $langs, $user;
12656
12657 $out = '';
12658 $sql = "SELECT rowid, label FROM " . $this->db->prefix() . "c_exp_tax_cat WHERE active = 1";
12659 $sql .= " AND entity IN (0," . getEntity('exp_tax_cat') . ")";
12660 if (!empty($excludeid)) {
12661 $sql .= " AND rowid NOT IN (" . $this->db->sanitize(implode(',', $excludeid)) . ")";
12662 }
12663 $sql .= " ORDER BY label";
12664
12665 $resql = $this->db->query($sql);
12666 if ($resql) {
12667 $out = '<select id="select_' . $htmlname . '" name="' . $htmlname . '" class="' . $htmlname . ' flat minwidth75imp maxwidth200">';
12668 if ($useempty) {
12669 $out .= '<option value="0">&nbsp;</option>';
12670 }
12671
12672 while ($obj = $this->db->fetch_object($resql)) {
12673 $out .= '<option ' . ($selected == $obj->rowid ? 'selected="selected"' : '') . ' value="' . $obj->rowid . '">' . $langs->trans($obj->label) . '</option>';
12674 }
12675 $out .= '</select>';
12676 $out .= ajax_combobox('select_' . $htmlname);
12677
12678 if (!empty($htmlname) && $user->admin && $info_admin) {
12679 $out .= ' ' . info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
12680 }
12681
12682 if (!empty($target)) {
12683 $sql = "SELECT c.id FROM " . $this->db->prefix() . "c_type_fees as c WHERE c.code = 'EX_KME' AND c.active = 1";
12684 $resql = $this->db->query($sql);
12685 if ($resql) {
12686 if ($this->db->num_rows($resql) > 0) {
12687 $obj = $this->db->fetch_object($resql);
12688 $out .= '<script nonce="' . getNonce() . '">
12689 $(function() {
12690 $("select[name=' . $target . ']").on("change", function() {
12691 var current_val = $(this).val();
12692 if (current_val == ' . $obj->id . ') {';
12693 if (!empty($default_selected) || !empty($selected)) {
12694 $out .= '$("select[name=' . $htmlname . ']").val("' . ($default_selected > 0 ? $default_selected : $selected) . '");';
12695 }
12696
12697 $out .= '
12698 $("select[name=' . $htmlname . ']").change();
12699 }
12700 });
12701
12702 $("select[name=' . $htmlname . ']").change(function() {
12703
12704 if ($("select[name=' . $target . ']").val() == ' . $obj->id . ') {
12705 // get price of kilometer to fill the unit price
12706 $.ajax({
12707 method: "POST",
12708 dataType: "json",
12709 data: { fk_c_exp_tax_cat: $(this).val(), token: \'' . currentToken() . '\' },
12710 url: "' . (DOL_URL_ROOT . '/expensereport/ajax/ajaxik.php?' . implode('&', $params)) . '",
12711 }).done(function( data, textStatus, jqXHR ) {
12712 console.log(data);
12713 if (typeof data.up != "undefined") {
12714 $("input[name=value_unit]").val(data.up);
12715 $("select[name=' . $htmlname . ']").attr("title", data.title);
12716 } else {
12717 $("input[name=value_unit]").val("");
12718 $("select[name=' . $htmlname . ']").attr("title", "");
12719 }
12720 });
12721 }
12722 });
12723 });
12724 </script>';
12725 }
12726 }
12727 }
12728 } else {
12729 dol_print_error($this->db);
12730 }
12731
12732 return $out;
12733 }
12734
12743 public function selectExpenseRanges($selected = '', $htmlname = 'fk_range', $useempty = 0)
12744 {
12745 global $conf, $langs;
12746
12747 $out = '';
12748 $sql = "SELECT rowid, range_ik FROM " . $this->db->prefix() . "c_exp_tax_range";
12749 $sql .= " WHERE entity = " . ((int) $conf->entity) . " AND active = 1";
12750
12751 $resql = $this->db->query($sql);
12752 if ($resql) {
12753 $out = '<select id="select_' . $htmlname . '" name="' . $htmlname . '" class="' . $htmlname . ' flat minwidth75imp">';
12754 if ($useempty) {
12755 $out .= '<option value="0"></option>';
12756 }
12757
12758 while ($obj = $this->db->fetch_object($resql)) {
12759 $out .= '<option ' . ($selected == $obj->rowid ? 'selected="selected"' : '') . ' value="' . $obj->rowid . '">' . price($obj->range_ik, 0, $langs, 1, 0) . '</option>';
12760 }
12761 $out .= '</select>';
12762 } else {
12763 dol_print_error($this->db);
12764 }
12765
12766 return $out;
12767 }
12768
12779 public function selectExpenseFees($selected = '', $htmlname = 'fk_c_type_fees', $useempty = 0, $allchoice = 1, $useid = 0)
12780 {
12781 global $langs;
12782
12783 $out = '';
12784 $sql = "SELECT id, code, label";
12785 $sql .= " FROM ".$this->db->prefix()."c_type_fees";
12786 $sql .= " WHERE active = 1";
12787
12788 $resql = $this->db->query($sql);
12789 if ($resql) {
12790 $out = '<select id="select_' . $htmlname . '" name="' . $htmlname . '" class="' . $htmlname . ' flat minwidth75imp">';
12791 if ($useempty) {
12792 $out .= '<option value="0"></option>';
12793 }
12794 if ($allchoice) {
12795 $out .= '<option value="-1">' . $langs->trans('AllExpenseReport') . '</option>';
12796 }
12797
12798 $field = 'code';
12799 if ($useid) {
12800 $field = 'id';
12801 }
12802
12803 while ($obj = $this->db->fetch_object($resql)) {
12804 $key = $langs->trans($obj->code);
12805 $out .= '<option ' . ($selected == $obj->{$field} ? 'selected="selected"' : '') . ' value="' . $obj->{$field} . '">' . ($key != $obj->code ? $key : $obj->label) . '</option>';
12806 }
12807 $out .= '</select>';
12808
12809 $out .= ajax_combobox('select_'.$htmlname);
12810 } else {
12811 dol_print_error($this->db);
12812 }
12813
12814 return $out;
12815 }
12816
12835 public function selectInvoiceForTimeProject($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)
12836 {
12837 global $user, $conf, $langs;
12838
12839 require_once DOL_DOCUMENT_ROOT . '/projet/class/project.class.php';
12840
12841 if (is_null($usertofilter)) {
12842 $usertofilter = $user;
12843 }
12844
12845 $out = '';
12846
12847 $hideunselectables = false;
12848 if (getDolGlobalString('INVOICE_HIDE_UNSELECTABLES')) {
12849 $hideunselectables = true;
12850 }
12851
12852 if (empty($projectsListId)) {
12853 if (!$usertofilter->hasRight('projet', 'all', 'lire')) {
12854 $projectstatic = new Project($this->db);
12855 $projectsListId = $projectstatic->getProjectsAuthorizedForUser($usertofilter, 0, 1);
12856 }
12857 }
12858
12859 // Search all projects
12860 $sql = "SELECT f.rowid, f.ref as fref, 'nolabel' as flabel, p.rowid as pid, f.ref, p.title, p.fk_soc, p.fk_statut, p.public, s.nom as name";
12861 $sql .= " FROM " . $this->db->prefix() . "facture as f";
12862 $sql .= " INNER JOIN " . $this->db->prefix() . "projet as p ON p.entity IN (" . getEntity('project') . ") AND f.fk_projet = p.rowid";
12863 $sql .= " LEFT JOIN " . $this->db->prefix() . "societe as s ON s.rowid = p.fk_soc";
12864 $sql .= " WHERE f.fk_statut = 0"; // Draft invoices only
12865 //if ($projectsListId) $sql.= " AND p.rowid IN (".$this->db->sanitize($projectsListId).")";
12866 //if ($socid == 0) $sql.= " AND (p.fk_soc=0 OR p.fk_soc IS NULL)";
12867 //if ($socid > 0) $sql.= " AND (p.fk_soc=".((int) $socid)." OR p.fk_soc IS NULL)";
12868 $sql .= " ORDER BY p.ref, f.ref ASC";
12869
12870 $resql = $this->db->query($sql);
12871 if ($resql) {
12872 // Use select2 selector
12873 if (!empty($conf->use_javascript_ajax)) {
12874 include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
12875 $comboenhancement = ajax_combobox($htmlname, array(), 0, $forcefocus);
12876 $out .= $comboenhancement;
12877 $morecss = 'minwidth200imp maxwidth500';
12878 }
12879
12880 if (empty($option_only)) {
12881 $out .= '<select class="valignmiddle flat' . ($morecss ? ' ' . $morecss : '') . '"' . ($disabled ? ' disabled="disabled"' : '') . ' id="' . $htmlname . '" name="' . $htmlname . '">';
12882 }
12883 if (!empty($show_empty)) {
12884 $out .= '<option value="0" class="optiongrey">';
12885 if (!is_numeric($show_empty)) {
12886 $out .= $show_empty;
12887 } else {
12888 $out .= '&nbsp;';
12889 }
12890 $out .= '</option>';
12891 }
12892 $num = $this->db->num_rows($resql);
12893 $i = 0;
12894 if ($num) {
12895 while ($i < $num) {
12896 $obj = $this->db->fetch_object($resql);
12897 // 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.
12898 if ($socid > 0 && (empty($obj->fk_soc) || $obj->fk_soc == $socid) && !$usertofilter->hasRight('societe', 'lire')) {
12899 // Do nothing
12900 } else {
12901 if ($discard_closed == 1 && $obj->fk_statut == Project::STATUS_CLOSED) {
12902 $i++;
12903 continue;
12904 }
12905
12906 $labeltoshow = '';
12907
12908 if ($showproject == 'all') {
12909 $labeltoshow .= dol_trunc($obj->ref, 18); // Invoice ref
12910 if ($obj->name) {
12911 $labeltoshow .= ' - ' . $obj->name; // Soc name
12912 }
12913
12914 $disabled = 0;
12915 if ($obj->fk_statut == Project::STATUS_DRAFT) {
12916 $disabled = 1;
12917 $labeltoshow .= ' - ' . $langs->trans("Draft");
12918 } elseif ($obj->fk_statut == Project::STATUS_CLOSED) {
12919 if ($discard_closed == 2) {
12920 $disabled = 1;
12921 }
12922 $labeltoshow .= ' - ' . $langs->trans("Closed");
12923 } elseif ($socid > 0 && (!empty($obj->fk_soc) && $obj->fk_soc != $socid)) {
12924 $disabled = 1;
12925 $labeltoshow .= ' - ' . $langs->trans("LinkedToAnotherCompany");
12926 }
12927 }
12928
12929 if (!empty($selected) && $selected == $obj->rowid) {
12930 $out .= '<option value="' . $obj->rowid . '" selected';
12931 //if ($disabled) $out.=' disabled'; // with select2, field can't be preselected if disabled
12932 $out .= '>' . $labeltoshow . '</option>';
12933 } else {
12934 if ($hideunselectables && $disabled && ($selected != $obj->rowid)) {
12935 $resultat = '';
12936 } else {
12937 $resultat = '<option value="' . $obj->rowid . '"';
12938 if ($disabled) {
12939 $resultat .= ' disabled';
12940 }
12941 //if ($obj->public) $labeltoshow.=' ('.$langs->trans("Public").')';
12942 //else $labeltoshow.=' ('.$langs->trans("Private").')';
12943 $resultat .= '>';
12944 $resultat .= $labeltoshow;
12945 $resultat .= '</option>';
12946 }
12947 $out .= $resultat;
12948 }
12949 }
12950 $i++;
12951 }
12952 }
12953 if (empty($option_only)) {
12954 $out .= '</select>';
12955 }
12956
12957 $this->db->free($resql);
12958
12959 return $out;
12960 } else {
12961 dol_print_error($this->db);
12962 return '';
12963 }
12964 }
12965
12980 public function selectInvoiceRec($selected = '', $htmlname = 'facrecid', $maxlength = 24, $option_only = 0, $show_empty = '1', $forcefocus = 0, $disabled = 0, $morecss = 'maxwidth500')
12981 {
12982 global $conf, $langs;
12983
12984 $out = '';
12985
12986 dol_syslog('FactureRec::fetch', LOG_DEBUG);
12987
12988 $sql = 'SELECT f.rowid, f.entity, f.titre as title, f.suspended, f.fk_soc';
12989 $sql .= ' FROM ' . MAIN_DB_PREFIX . 'facture_rec as f';
12990 $sql .= " WHERE f.entity IN (" . getEntity('invoice') . ")";
12991 $sql .= " ORDER BY f.titre ASC";
12992
12993 $resql = $this->db->query($sql);
12994 if ($resql) {
12995 // Use select2 selector
12996 if (!empty($conf->use_javascript_ajax)) {
12997 include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
12998 $comboenhancement = ajax_combobox($htmlname, array(), 0, $forcefocus);
12999 $out .= $comboenhancement;
13000 $morecss = 'minwidth200imp maxwidth500';
13001 }
13002
13003 if (empty($option_only)) {
13004 $out .= '<select class="valignmiddle flat' . ($morecss ? ' ' . $morecss : '') . '"' . ($disabled ? ' disabled="disabled"' : '') . ' id="' . $htmlname . '" name="' . $htmlname . '">';
13005 }
13006 if (!empty($show_empty)) {
13007 $out .= '<option value="0" class="optiongrey">';
13008 if (!is_numeric($show_empty)) {
13009 $out .= $show_empty;
13010 } else {
13011 $out .= '&nbsp;';
13012 }
13013 $out .= '</option>';
13014 }
13015 $num = $this->db->num_rows($resql);
13016 if ($num) {
13017 while ($obj = $this->db->fetch_object($resql)) {
13018 $labeltoshow = dol_trunc($obj->title, 18); // Invoice ref
13019
13020 $disabled = 0;
13021 if (!empty($obj->suspended)) {
13022 $disabled = 1;
13023 $labeltoshow .= ' - ' . $langs->trans("Closed");
13024 }
13025
13026
13027 if (!empty($selected) && $selected == $obj->rowid) {
13028 $out .= '<option value="' . $obj->rowid . '" selected';
13029 //if ($disabled) $out.=' disabled'; // with select2, field can't be preselected if disabled
13030 $out .= '>' . $labeltoshow . '</option>';
13031 } else {
13032 if ($disabled && ($selected != $obj->rowid)) {
13033 $resultat = '';
13034 } else {
13035 $resultat = '<option value="' . $obj->rowid . '"';
13036 if ($disabled) {
13037 $resultat .= ' disabled';
13038 }
13039 $resultat .= '>';
13040 $resultat .= $labeltoshow;
13041 $resultat .= '</option>';
13042 }
13043 $out .= $resultat;
13044 }
13045 }
13046 }
13047 if (empty($option_only)) {
13048 $out .= '</select>';
13049 }
13050
13051 print $out;
13052
13053 $this->db->free($resql);
13054 return $num;
13055 } else {
13056 $this->errors[] = $this->db->lasterror;
13057 return -1;
13058 }
13059 }
13060
13061
13072 public function searchComponent($arrayofcriterias, $search_component_params, $arrayofinputfieldsalreadyoutput = array(), $search_component_params_hidden = '', $arrayoffiltercriterias = array())
13073 {
13074 // TODO: Use $arrayoffiltercriterias param instead of $arrayofcriterias to include linked object fields in search
13075 global $langs, $form;
13076
13077 //require_once DOL_DOCUMENT_ROOT."/core/class/html.formother.class.php";
13078 //$formother = new FormOther($this->db);
13079
13080 if ($search_component_params_hidden != '' && !preg_match('/^\‍(.*\‍)$/', $search_component_params_hidden)) { // If $search_component_params_hidden does not start and end with ()
13081 $search_component_params_hidden = '(' . $search_component_params_hidden . ')';
13082 }
13083
13084 $ret = '<!-- searchComponent -->';
13085
13086 $ret .= '<div class="divadvancedsearchfieldcomp centpercent inline-block">';
13087 $ret .= '<a href="#" class="dropdownsearch-toggle unsetcolor">';
13088 $ret .= '<span class="fas fa-filter linkobject boxfilter paddingright pictofixedwidth" title="' . dol_escape_htmltag($langs->trans("Filters")) . '" id="idsubimgproductdistribution"></span>';
13089 $ret .= '</a>';
13090
13091 $ret .= '<div class="divadvancedsearchfieldcompinput centpercentminusx inline-block minwidth500 maxwidth300onsmartphone">';
13092
13093 // Show select fields as tags.
13094 $ret .= '<div id="divsearch_component_params" name="divsearch_component_params" class="noborderbottom search_component_params inline-block valignmiddle">';
13095
13096 if ($search_component_params_hidden) {
13097 // Split the criteria on each AND
13098 //var_dump($search_component_params_hidden);
13099
13100 $arrayofandtags = dolForgeExplodeAnd($search_component_params_hidden);
13101
13102 // $arrayofandtags is now array( '...' , '...', ...)
13103 // Show each AND part
13104 foreach ($arrayofandtags as $tmpkey => $tmpval) {
13105 $errormessage = '';
13106 $searchtags = forgeSQLFromUniversalSearchCriteria($tmpval, $errormessage, 1, 1);
13107 if ($errormessage) {
13108 $this->error = 'ERROR in parsing search string: '.$errormessage;
13109 }
13110 // Remove first and last parenthesis but only if first is the opening and last the closing of the same group
13111 include_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
13112 $searchtags = removeGlobalParenthesis($searchtags);
13113
13114 $ret .= '<span class="marginleftonlyshort valignmiddle tagsearch" data-ufilterid="'.($tmpkey + 1).'" data-ufilter="'.dol_escape_htmltag($tmpval).'">';
13115 $ret .= '<span class="tagsearchdelete select2-selection__choice__remove" data-ufilterid="'.($tmpkey + 1).'">x</span> ';
13116 $ret .= dol_escape_htmltag($searchtags);
13117 $ret .= '</span>';
13118 }
13119 }
13120
13121 //$ret .= '<button type="submit" class="liste_titre button_search paddingleftonly" name="button_search_x" value="x"><span class="fa fa-search"></span></button>';
13122
13123 //$ret .= search_component_params
13124 //$texttoshow = '<div class="opacitymedium inline-block search_component_searchtext">'.$langs->trans("Search").'</div>';
13125 //$ret .= '<div class="search_component inline-block valignmiddle">'.$texttoshow.'</div>';
13126
13127 $show_search_component_params_hidden = 1;
13128 if ($show_search_component_params_hidden) {
13129 $ret .= '<input type="hidden" name="show_search_component_params_hidden" value="1">';
13130 }
13131 $ret .= "<!-- We store the full Universal Search String into this field. For example: (t.ref:like:'SO-%') AND ((t.ref:like:'CO-%') OR (t.ref:like:'AA%')) -->";
13132 $ret .= '<input type="hidden" id="search_component_params_hidden" name="search_component_params_hidden" value="' . dol_escape_htmltag($search_component_params_hidden) . '">';
13133 // $ret .= "<!-- sql= ".forgeSQLFromUniversalSearchCriteria($search_component_params_hidden, $errormessage)." -->";
13134
13135 // TODO : Use $arrayoffiltercriterias instead of $arrayofcriterias
13136 // For compatibility with forms that show themself the search criteria in addition of this component, we output these fields
13137 foreach ($arrayofcriterias as $criteria) {
13138 foreach ($criteria as $criteriafamilykey => $criteriafamilyval) {
13139 if (in_array('search_' . $criteriafamilykey, $arrayofinputfieldsalreadyoutput)) {
13140 continue;
13141 }
13142 if (in_array($criteriafamilykey, array('rowid', 'ref_ext', 'entity', 'extraparams'))) {
13143 continue;
13144 }
13145 if (in_array($criteriafamilyval['type'], array('date', 'datetime', 'timestamp'))) {
13146 $ret .= '<input type="hidden" name="search_' . $criteriafamilykey . '_start">';
13147 $ret .= '<input type="hidden" name="search_' . $criteriafamilykey . '_startyear">';
13148 $ret .= '<input type="hidden" name="search_' . $criteriafamilykey . '_startmonth">';
13149 $ret .= '<input type="hidden" name="search_' . $criteriafamilykey . '_startday">';
13150 $ret .= '<input type="hidden" name="search_' . $criteriafamilykey . '_end">';
13151 $ret .= '<input type="hidden" name="search_' . $criteriafamilykey . '_endyear">';
13152 $ret .= '<input type="hidden" name="search_' . $criteriafamilykey . '_endmonth">';
13153 $ret .= '<input type="hidden" name="search_' . $criteriafamilykey . '_endday">';
13154 } else {
13155 $ret .= '<input type="hidden" name="search_' . $criteriafamilykey . '">';
13156 }
13157 }
13158 }
13159
13160 $ret .= '</div>';
13161
13162 $ret .= "<!-- Field to enter a generic filter string: t.ref:like:'SO-%', t.date_creation:>:'20160101', t.date_creation:<:'2016-01-01 12:30:00', t.nature:is:NULL, t.field2:isnot:NULL -->\n";
13163 $ret .= '<input type="text" placeholder="' . $langs->trans("Filters") . '" id="search_component_params_input" name="search_component_params_input" class="noborderall search_component_input" value="">';
13164
13165 $ret .= '</div>';
13166 $ret .= '</div>';
13167
13168 $ret .= '<script>
13169 jQuery(".tagsearchdelete").click(function(e) {
13170 var filterid = $(this).parents().attr("data-ufilterid");
13171 console.log("We click to delete the criteria nb "+filterid);
13172
13173 // Regenerate the search_component_params_hidden with all data-ufilter except the one to delete, and post the page
13174 var newparamstring = \'\';
13175 $(\'.tagsearch\').each(function(index, element) {
13176 tmpfilterid = $(this).attr("data-ufilterid");
13177 if (tmpfilterid != filterid) {
13178 // We keep this criteria
13179 if (newparamstring == \'\') {
13180 newparamstring = $(this).attr("data-ufilter");
13181 } else {
13182 newparamstring = newparamstring + \' AND \' + $(this).attr("data-ufilter");
13183 }
13184 }
13185 });
13186 console.log("newparamstring = "+newparamstring);
13187
13188 jQuery("#search_component_params_hidden").val(newparamstring);
13189
13190 // We repost the form
13191 $(this).closest(\'form\').submit();
13192 });
13193
13194 jQuery("#search_component_params_input").keydown(function(e) {
13195 console.log("We press a key on the filter field that is "+jQuery("#search_component_params_input").val());
13196 console.log(e.which);
13197 if (jQuery("#search_component_params_input").val() == "" && e.which == 8) {
13198 /* We click on back when the input field is already empty */
13199 event.preventDefault();
13200 jQuery("#divsearch_component_params .tagsearch").last().remove();
13201 /* Regenerate content of search_component_params_hidden from remaining .tagsearch */
13202 var s = "";
13203 jQuery("#divsearch_component_params .tagsearch").each(function( index ) {
13204 if (s != "") {
13205 s = s + " AND ";
13206 }
13207 s = s + $(this).attr("data-ufilter");
13208 });
13209 console.log("New value for search_component_params_hidden = "+s);
13210 jQuery("#search_component_params_hidden").val(s);
13211 }
13212 });
13213
13214 </script>
13215 ';
13216
13217 // Convert $arrayoffiltercriterias into a json object that can be used in jquery to build the search component dynamically
13218 $arrayoffiltercriterias_json = json_encode($arrayoffiltercriterias);
13219
13220 // Build $arrayoffilterelements that is an array of elements (tables) with the list of their fields,
13221 // so we can show a single 2 levels combo: select first the element (the table), then the field appears
13222 // into the same combo, in a second level shown under the element
13223 $arrayoffilterelements = array();
13224 foreach ($arrayoffiltercriterias as $key => $val) {
13225 // The element (table) is the part of the field key before the last dot ('t.ref' -> 't', 't__fk_project.ref' -> 't__fk_project')
13226 $tmpelementkey = preg_replace('/\.[^.]*$/', '', $key);
13227 // Extrafields of an element are stored with the alias 'te', 'te__...', so we map them onto the same element than their parent table 't', 't__...'
13228 $tmpelementkey = preg_replace('/^te(__|$)/', 't$1', $tmpelementkey);
13229
13230 $tmpelementlabel = $tmpelementkey;
13231 $tmpelementpicto = '';
13232 $tmpfieldlabel = $key;
13233 if (!empty($val['labelnohtml'])) {
13234 $tmpfieldlabel = $val['labelnohtml'];
13235 // The labelnohtml is 'LabelOfElement: LabelOfField', so we extract the label of the element and the label of the field
13236 if (preg_match('/^(.+?): /', $val['labelnohtml'], $tmpmatch)) {
13237 $tmpelementlabel = $tmpmatch[1];
13238 $tmpfieldlabel = trim(substr($val['labelnohtml'], strlen($tmpelementlabel) + 1));
13239 }
13240 if (!empty($val['label'])) {
13241 // The label is the picto of the element followed by the labelnohtml, so we extract the picto of the element
13242 $tmpelementpicto = trim(str_replace($val['labelnohtml'], '', $val['label']));
13243 }
13244 }
13245
13246 if (!isset($arrayoffilterelements[$tmpelementkey])) {
13247 $arrayoffilterelements[$tmpelementkey] = array('picto' => $tmpelementpicto, 'label' => $tmpelementlabel, 'fields' => array());
13248 }
13249 $arrayoffilterelements[$tmpelementkey]['fields'][$key] = array('label' => $tmpfieldlabel, 'type' => $val['type']);
13250 }
13251 $ret .= '<script>
13252 var arrayoffiltercriterias = ' . $arrayoffiltercriterias_json . ';
13253 </script>';
13254
13255
13256 // Adding the div for search assistance
13257 $ret .= '<div class="search-component-assistance">';
13258 $ret .= '<div>';
13259
13260 $ret .= '<p class="assistance-title">' . img_picto('', 'filter') . ' ' . $langs->trans('FilterAssistance') . ' </p>';
13261
13262 $ret .= '<p class="assistance-errors error" style="display:none">' . $langs->trans('AllFieldsRequired') . ' </p>';
13263
13264 // Combo with 2 levels to select the field: select first the element (the table), the list of fields of the element
13265 // appears then under the element into the same combo, then select the field
13266 // (this is a pure JS combo, we do not use select2 for this component)
13267 $ret .= '<div class="operand valigntop">';
13268 $ret .= '<div class="fieldcombo">';
13269 // Hidden input to store the selected field ('' or a field key like 't.ref' or 't__fk_project.ref')
13270 $ret .= '<input type="hidden" id="search_filter_field" name="search_filter_field" value="">';
13271 // Button of the combo (show the placeholder "Fields" or the selected "element: field")
13272 $ret .= '<button type="button" class="fieldcombo-toggle"><span class="fieldcombo-label opacitymedium">' . dol_escape_htmltag($langs->trans('Fields')) . '</span><span class="fas fa-caret-down fieldcombo-caret"></span></button>';
13273 // Panel of the combo, with the list of elements (the root tables), and into each element, the list of
13274 // its fields then the list of its sub-elements (sub-tables, indented, after the fields). The fields and
13275 // the sub-elements are hidden by default and shown only after a search or a click on the element.
13276 $ret .= '<div class="fieldcombo-panel" style="display:none">';
13277 $ret .= '<div class="liinputsearch"><input type="text" class="fieldcombo-search noborderfocus" placeholder="' . dol_escape_htmltag($langs->trans('Search')) . '"></div>';
13278 $ret .= '<div class="fieldcombo-list">';
13279
13280 // Recursive rendering of an element of the combo, with the list of its sub-elements (indented into it)
13281 // and the list of its fields. A direct sub-element is an element whose key starts with the key of its parent
13282 // element followed by '__', without any other '__' after (so 't__fk_soc' is a sub-element of 't',
13283 // 't__fk_soc__fk_pays' is a sub-element of 't__fk_soc' and not a direct sub-element of 't')
13284 $renderElementCombo = null;
13285 $renderElementCombo = function ($tmpelementkey) use ($arrayoffilterelements, &$renderElementCombo) {
13286 $tmpelementval = $arrayoffilterelements[$tmpelementkey];
13287 $arrayofchildelementkeys = array();
13288 foreach ($arrayoffilterelements as $tmpchildelementkey => $tmpchildelementval) {
13289 if (strpos($tmpchildelementkey, $tmpelementkey.'__') === 0 && strpos(substr($tmpchildelementkey, strlen($tmpelementkey) + 2), '__') === false) {
13290 $arrayofchildelementkeys[] = $tmpchildelementkey;
13291 }
13292 }
13293 $out = '';
13294 $out .= '<div class="fieldcombo-element" data-element="' . dolPrintHTMLForAttribute($tmpelementkey) . '">';
13295 $out .= '<div class="fieldcombo-elementheader" tabindex="0"><span class="fieldcombo-elementlabel">' . $tmpelementval['picto'] . ' ' . dol_escape_htmltag($tmpelementval['label']) . '</span><span class="fas fa-caret-right fieldcombo-elementcaret"></span></div>';
13296 // The list of fields is hidden by default, it is shown only after a search or a click on the element
13297 $out .= '<div class="fieldcombo-fields" style="display:none">';
13298 foreach ($tmpelementval['fields'] as $tmpfieldkey => $tmpfieldval) {
13299 $out .= '<div class="fieldcombo-field" tabindex="0" data-field="' . dolPrintHTMLForAttribute($tmpfieldkey) . '" data-type="' . dolPrintHTMLForAttribute($tmpfieldval['type']) . '">' . dol_escape_htmltag($tmpfieldval['label']) . '</div>';
13300 }
13301 $out .= '</div>';
13302 if (!empty($arrayofchildelementkeys)) {
13303 // The list of sub-elements is shown after the fields, it is hidden by default and it is shown
13304 // (indented) only after a search or a click on the element
13305 $out .= '<div class="fieldcombo-children" style="display:none">';
13306 foreach ($arrayofchildelementkeys as $tmpchildelementkey) {
13307 $out .= $renderElementCombo($tmpchildelementkey);
13308 }
13309 $out .= '</div>';
13310 }
13311 $out .= '</div>';
13312 return $out;
13313 };
13314
13315 foreach ($arrayoffilterelements as $tmpelementkey => $tmpelementval) {
13316 // The parent element is the part of the key before the last '__' ('t__fk_soc' -> 't', 't__fk_soc__fk_pays' -> 't__fk_soc')
13317 $tmpparentkey = '';
13318 if (strpos($tmpelementkey, '__') !== false) {
13319 $tmparrayofkey = explode('__', $tmpelementkey);
13320 array_pop($tmparrayofkey);
13321 $tmpparentkey = implode('__', $tmparrayofkey);
13322 }
13323 // At root level, we render only the elements without parent element (or with a parent not found into
13324 // the list), the sub-elements are rendered recursively into their parent element
13325 if ($tmpparentkey == '' || !isset($arrayoffilterelements[$tmpparentkey])) {
13326 $ret .= $renderElementCombo($tmpelementkey);
13327 }
13328 }
13329 $ret .= '</div>';
13330 $ret .= '</div>';
13331 $ret .= '</div>';
13332 $ret .= '</div>';
13333
13334 $ret .= '<span class="separator"></span>';
13335
13336 // Operator selector (will be populated dynamically)
13337 $ret .= '<div class="operator valigntop">';
13338 $ret .= '<select class="operator-selector width150" id="operator-selector"">';
13339 $ret .= '</select>';
13340 $ret .= '<script>$(document).ready(function() {';
13341 $ret .= ' $(".operator-selector").select2({';
13342 $ret .= ' placeholder: \'' . dol_escape_js($langs->transnoentitiesnoconv('Operator')) . '\'';
13343 $ret .= ' });';
13344 $ret .= '});</script>';
13345 $ret .= '</div>';
13346
13347 $ret .= '<span class="separator"></span>';
13348
13349 $ret .= '<div class="value valigntop">';
13350 // Input field for entering values
13351 $ret .= '<input type="text" class="flat width100 value-input" placeholder="' . dolPrintHTML($langs->trans('Value')) . '">';
13352
13353 // Date selector
13354 $dateOne = '';
13355 $ret .= '<span class="date-one" style="display:none">';
13356 $ret .= $form->selectDate(($dateOne ? $dateOne : -1), 'dateone', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '');
13357 $ret .= '</span>';
13358
13359 // Value selector (will be populated dynamically) based on search_filter_field value if a selected value has an array of values
13360 $ret .= '<select class="value-selector width150" id="value-selector" style="display:none">';
13361 $ret .= '</select>';
13362 $ret .= '<script>
13363 $(document).ready(function() {
13364 $("#value-selector").select2({
13365 placeholder: \'' . dol_escape_js($langs->trans('Value')) . '\'
13366 });
13367 $("#value-selector").hide();
13368 $("#value-selector").next(".select2-container").hide();
13369 });
13370 </script>';
13371
13372 $ret .= '</div>';
13373
13374 $ret .= '<div class="btn-div">';
13375 $ret .= '<button class="button buttongen button-save add-filter-btn" type="button">' . $langs->trans("addToFilter") . '</button>';
13376 $ret .= '</div>';
13377
13378 $ret .= '</div>';
13379 //$ret .= '</tbody></table>';
13380
13381 // End of the assistance div
13382 $ret .= '</div>';
13383
13384 // Script jQuery to show/hide the floating assistance
13385 $ret .= '<script>
13386 $(document).ready(function() {
13387 $("#search_component_params_input").on("click", function() {
13388 const inputPosition = $(this).offset();
13389 const inputHeight = $(this).outerHeight();
13390 $(".search-component-assistance").css({
13391 top: inputPosition.top + inputHeight + 5 + "px",
13392 left: $("#divsearch_component_params").position().left
13393 }).slideToggle(200);
13394 });
13395 $(document).on("click", function(e) {
13396 if (!$(e.target).closest("#search_component_params_input, .search-component-assistance, #ui-datepicker-div").length) {
13397 $(".search-component-assistance").hide();
13398 }
13399 });
13400 });
13401 </script>';
13402
13403 $ret .= '<script>
13404 $(document).ready(function() {
13405 // Reset and hide all the value input fields (this does not clear the operator selector)
13406 function resetValueInputs() {
13407 $(".value-input, .dateone, .datemonth, .dateyear").val("").hide();
13408 $("#datemonth, #dateyear").val(null).trigger("change.select2");
13409 $("#dateone").datepicker("setDate", null);
13410 $(".date-one, .date-month, .date-year").hide();
13411 $("#value-selector").val("").hide();
13412 $("#value-selector").next(".select2-container").hide();
13413 $("#value-selector").val(null).trigger("change.select2");
13414 }
13415
13416 // Clear the operator selector and reset and hide all the value input fields
13417 function resetOperatorAndValueInputs() {
13418 $(".operator-selector").empty();
13419 resetValueInputs();
13420 }
13421
13422 // JS code of the 2 levels combo to select first the element (the table) then the field
13423 // Click on the toggle button: show/hide the panel of the combo
13424 $(".fieldcombo-toggle").on("click", function(e) {
13425 e.stopPropagation();
13426 $(this).closest(".fieldcombo").find(".fieldcombo-panel").toggle();
13427 // Reset the search input and the filter, so the panel always opens with the first level open
13428 // (the fields and the sub-tables of the main object are shown, the deeper levels stay closed)
13429 $(this).closest(".fieldcombo").find(".fieldcombo-search").val("").trigger("keyup");
13430 });
13431
13432 // Click on an element (a table): show the list of its sub-elements (indented) and the list of its fields.
13433 // We close all the other elements before, except the parent elements of the clicked element (else it would be hidden)
13434 $(".fieldcombo-elementheader").on("click", function(e) {
13435 e.stopPropagation();
13436 const elementdiv = $(this).closest(".fieldcombo-element");
13437 const wasopen = elementdiv.hasClass("open");
13438 $(".fieldcombo-element").removeClass("open");
13439 $(".fieldcombo-fields, .fieldcombo-children").hide();
13440 $(".fieldcombo-field").show();
13441 elementdiv.parents(".fieldcombo-element").addClass("open").show().children(".fieldcombo-fields, .fieldcombo-children").show();
13442 if (!wasopen) {
13443 elementdiv.addClass("open").show().children(".fieldcombo-fields, .fieldcombo-children").show();
13444 }
13445 });
13446
13447 // Click on a field (second level of the combo): select the field and close the combo
13448 $(".fieldcombo-field").on("click", function(e) {
13449 e.stopPropagation();
13450 const fieldlabel = $(this).text();
13451 const elementlabelhtml = $(this).closest(".fieldcombo-element").find(".fieldcombo-elementlabel").html();
13452 $(".fieldcombo-field").removeClass("selected");
13453 $(this).addClass("selected");
13454 $(".fieldcombo-label").html(elementlabelhtml).removeClass("opacitymedium").append(document.createTextNode(": " + fieldlabel));
13455 $("#search_filter_field").attr("data-type", $(this).attr("data-type")).val($(this).attr("data-field"));
13456 $(".fieldcombo-panel").hide();
13457 $("#search_filter_field").trigger("change");
13458 });
13459
13460 // Type something into the search input to filter the list of elements and fields
13461 $(".fieldcombo-search").on("keyup change", function() {
13462 const term = $(this).val().toLowerCase();
13463 // Without search term, we show the first level (the main object) open, so its fields and
13464 // its sub-tables are shown, but the deeper levels stay closed
13465 if (term === "") {
13466 $(".fieldcombo-element").removeClass("open").show();
13467 $(".fieldcombo-fields, .fieldcombo-children").hide();
13468 $(".fieldcombo-field").show();
13469 $(".fieldcombo-list > .fieldcombo-element").addClass("open").children(".fieldcombo-fields, .fieldcombo-children").show();
13470 return;
13471 }
13472 // With a search term, we show the elements matching by their label or their fields, and we also
13473 // show the matching sub-elements into their parent element, so we open the parents of the matches
13474 $(".fieldcombo-element").each(function() {
13475 const elementmatch = $(this).children(".fieldcombo-elementheader").find(".fieldcombo-elementlabel").text().toLowerCase().indexOf(term) > -1;
13476 let hasfieldmatch = false;
13477 $(this).children(".fieldcombo-fields").children(".fieldcombo-field").each(function() {
13478 const fieldmatch = elementmatch || $(this).text().toLowerCase().indexOf(term) > -1;
13479 $(this).toggle(fieldmatch);
13480 if (fieldmatch) {
13481 hasfieldmatch = true;
13482 }
13483 });
13484 $(this).toggleClass("open", elementmatch || hasfieldmatch);
13485 $(this).children(".fieldcombo-fields").toggle(elementmatch || hasfieldmatch);
13486 });
13487 $(".fieldcombo-element").each(function() {
13488 if ($(this).hasClass("open")) {
13489 // The element matches, so we show it and we open its parent elements to make it visible into them
13490 $(this).show();
13491 $(this).parents(".fieldcombo-element").addClass("open").show().children(".fieldcombo-children").show();
13492 } else {
13493 $(this).hide();
13494 }
13495 });
13496 });
13497
13498 // Press Escape to close the combo, press Enter on a row to activate it
13499 $(document).on("keydown", function(e) {
13500 if (e.which === 27) {
13501 $(".fieldcombo-panel").hide();
13502 }
13503 });
13504 $(".fieldcombo-elementheader, .fieldcombo-field").on("keydown", function(e) {
13505 if (e.which === 13) {
13506 e.preventDefault();
13507 $(this).trigger("click");
13508 }
13509 });
13510
13511 // Close the combo when we click outside of it
13512 $(document).on("click", function(e) {
13513 if (!$(e.target).closest(".fieldcombo").length) {
13514 $(".fieldcombo-panel").hide();
13515 }
13516 });
13517
13518 $("#search_filter_field").on("change", function() {
13519 console.log("We change search_filter_field");
13520
13521 let maybenull = 0;
13522 let fieldType = $(this).attr("data-type");
13523 const selectedFieldValue = $(this).val();
13524
13525 // If the selected option is the placeholder (no field selected), then we reset the operator and value fields
13526 if (arrayoffiltercriterias[selectedFieldValue] === undefined) {
13527 resetOperatorAndValueInputs();
13528 return;
13529 }
13530
13531 // If the selected field has an array of values then ask toshow the value selector instead of the value input
13532 if (arrayoffiltercriterias[selectedFieldValue]["arrayofkeyval"] !== undefined) {
13533 fieldType = "select";
13534 }
13535
13536 // If the selected field may be null then ask to append the "IsDefined" and "IsNotDefined" operators
13537 if (arrayoffiltercriterias[selectedFieldValue]["maybenull"] !== undefined) {
13538 maybenull = 1;
13539 }
13540 const operators = getOperatorsForFieldType(fieldType, maybenull);
13541 const operatorSelector = $(".operator-selector");
13542
13543 // Clear existing options
13544 operatorSelector.empty();
13545
13546 // Populate operators
13547 Object.entries(operators).forEach(function([operator, label]) {
13548 operatorSelector.append("<option value=\'" + operator + "\'>" + label + "</option>");
13549 });
13550
13551 operatorSelector.trigger("change.select2");
13552
13553 // Clear and hide all the value input elements initially (the operator selector has just been populated, we do not clear it)
13554 resetValueInputs();
13555
13556 if (fieldType === "date" || fieldType === "datetime" || fieldType === "timestamp") {
13557 $(".date-one").show();
13558 } else if (arrayoffiltercriterias[selectedFieldValue]["arrayofkeyval"] !== undefined) {
13559 var arrayofkeyval = arrayoffiltercriterias[selectedFieldValue]["arrayofkeyval"];
13560 var valueSelector = $("#value-selector");
13561 valueSelector.empty();
13562 Object.entries(arrayofkeyval).forEach(function([key, val]) {
13563 valueSelector.append("<option value=\'" + key + "\'>" + val + "</option>");
13564 });
13565 valueSelector.trigger("change.select2");
13566
13567 $("#value-selector").show();
13568 $("#value-selector").next(".select2-container").show();
13569 } else {
13570 $(".value-input").show();
13571 }
13572 });
13573
13574 $("#operator-selector").on("change", function() {
13575 console.log("We change operator-selector");
13576
13577 const selectedOperator = $(this).find(":selected").val();
13578 if (selectedOperator === "IsDefined" || selectedOperator === "IsNotDefined") {
13579 // Disable all value input elements
13580 $(".value-input, .dateone, .datemonth, .dateyear").val("").prop("disabled", true);
13581 $("#datemonth, #dateyear").val(null).trigger("change.select2");
13582 $("#dateone").datepicker("setDate", null).datepicker("option", "disabled", true);
13583 $(".date-one, .date-month, .date-year").prop("disabled", true);
13584 $("#value-selector").val("").prop("disabled", true);
13585 $("#value-selector").val(null).trigger("change.select2");
13586 } else {
13587 // Enable all value input elements
13588 $(".value-input, .dateone, .datemonth, .dateyear").prop("disabled", false);
13589 $(".date-one, .date-month, .date-year").prop("disabled", false);
13590 $("#dateone").datepicker("option", "disabled", false);
13591 $("#value-selector").prop("disabled", false);
13592 }
13593 });
13594
13595 $(".add-filter-btn").on("click", function(event) {
13596 console.log("We click on add-filter-btn");
13597
13598 event.preventDefault();
13599
13600 const field = $("#search_filter_field").val();
13601 const operator = $(".operator-selector").val();
13602 let value = $(".value-input").val();
13603 const fieldType = $("#search_filter_field").attr("data-type");
13604
13605 if (["date", "datetime", "timestamp"].includes(fieldType)) {
13606 const year = $("#dateoneyear").val().toString().padStart(4, "0");;
13607 const month = $("#dateonemonth").val().toString().padStart(2, "0");
13608 const day = $("#dateoneday").val().toString().padStart(2, "0");
13609 value = `${year}-${month}-${day}`;
13610 console.log("value="+value);
13611 }
13612
13613 // If the selected field has an array of values then take the selected value
13614 if (arrayoffiltercriterias[field] !== undefined && arrayoffiltercriterias[field]["arrayofkeyval"] !== undefined) {
13615 value = $("#value-selector").val();
13616 }
13617
13618 // If the operator is "IsDefined" or "IsNotDefined" then set the value to 1 (it will not be used)
13619 if (operator === "IsDefined" || operator === "IsNotDefined") {
13620 value = "1";
13621 }
13622
13623 const filterString = generateFilterString(field, operator, value, fieldType);
13624
13625 // Submit the form
13626 if (filterString !== "" && field !== "" && field !== "-1" && field !== null && operator !== "" && operator !== null && value !== "" && value !== null) {
13627 $("#search_component_params_input").val($("#search_component_params_input").val() + " " + filterString);
13628 $("#search_component_params_input").closest("form").submit();
13629 } else {
13630 $(".assistance-errors").show();
13631 }
13632 });
13633 });
13634 </script>';
13635
13636 return $ret;
13637 }
13638
13650 public function selectModelMail($prefix, $modelType = '', $default = 0, $addjscombo = 0, $selected = 0, $morecss = '')
13651 {
13652 global $langs, $user;
13653
13654 $retstring = '';
13655
13656 $TModels = array();
13657
13658 include_once DOL_DOCUMENT_ROOT . '/core/class/html.formmail.class.php';
13659 $formmail = new FormMail($this->db);
13660 $result = $formmail->fetchAllEMailTemplate($modelType, $user, $langs);
13661
13662 if ($default) {
13663 $TModels[0] = $langs->trans('DefaultMailModel');
13664 }
13665 if ($result > 0) {
13666 foreach ($formmail->lines_model as $model) {
13667 $TModels[(int) $model->id] = $model->label;
13668 }
13669 }
13670
13671 $retstring .= '<select class="flat'.($morecss ? ' '.$morecss : '').'" id="select_' . $prefix . 'model_mail" name="' . $prefix . 'model_mail">';
13672
13673 foreach ($TModels as $id_model => $label_model) {
13674 $retstring .= '<option value="' . $id_model . '"';
13675 if (!empty($selected) && ((int) $selected) == $id_model) {
13676 $retstring .= "selected";
13677 }
13678 $retstring .= ">" . $label_model . "</option>";
13679 }
13680
13681 $retstring .= "</select>";
13682
13683 if ($addjscombo) {
13684 $retstring .= ajax_combobox('select_' . $prefix . 'model_mail');
13685 }
13686
13687 return $retstring;
13688 }
13689
13701 public function buttonsSaveCancel($save_label = 'Save', $cancel_label = 'Cancel', $morebuttons = array(), $withoutdiv = false, $morecss = '', $dol_openinpopup = '')
13702 {
13703 global $langs;
13704
13705 $buttons = array();
13706
13707 $save = array(
13708 'name' => 'save',
13709 'label_key' => $save_label,
13710 );
13711
13712 if ($save_label == 'Create' || $save_label == 'Add') {
13713 $save['name'] = 'add';
13714 } elseif ($save_label == 'Modify') {
13715 $save['name'] = 'edit';
13716 }
13717
13718 $cancel = array(
13719 'name' => 'cancel',
13720 'label_key' => 'Cancel',
13721 );
13722
13723 // If MAIN_BUTTON_POSITION_FIRST_OR_LEFT not set, default is to have main action first, then complementary, then cancel at end
13724 if (!getDolGlobalInt('MAIN_BUTTON_POSITION_FIRST_OR_LEFT')) {
13725 !empty($save_label) ? $buttons[] = $save : '';
13726 if (!empty($morebuttons)) {
13727 $buttons[] = $morebuttons;
13728 }
13729 !empty($cancel_label) ? $buttons[] = $cancel : '';
13730 } else {
13731 if (!empty($morebuttons)) {
13732 $buttons[] = $morebuttons;
13733 }
13734 !empty($cancel_label) ? $buttons[] = $cancel : '';
13735 !empty($save_label) ? $buttons[] = $save : '';
13736 }
13737
13738 $retstring = $withoutdiv ? '' : '<div class="center">';
13739
13740 foreach ($buttons as $button) {
13741 $addclass = empty($button['addclass']) ? '' : $button['addclass'];
13742 // Add onclick to disable submit buttons (except cancel) after first click to prevent duplicate form submissions on slow connections
13743 $onclick = ($button['name'] !== 'cancel') ? ' onclick="if(this.form && this.form.checkValidity && !this.form.checkValidity()) { return true; } this.disabled=true; this.form.submit();"' : '';
13744 $retstring .= '<input type="submit" class="button marginleftonly marginrightonly button-' . $button['name'] . ($morecss ? ' ' . $morecss : '') . ' ' . $addclass . '" name="' . $button['name'] . '" value="' . dol_escape_htmltag($langs->transnoentities($button['label_key'])) . '"' . $onclick . '>';
13745 }
13746 $retstring .= $withoutdiv ? '' : '</div>';
13747
13748 if ($dol_openinpopup) {
13749 $retstring .= '<!-- buttons are shown into a $dol_openinpopup=' . dol_escape_htmltag($dol_openinpopup) . ' context, so we enable the close of dialog on cancel -->' . "\n";
13750 $retstring .= '<script nonce="' . getNonce() . '">';
13751 $retstring .= 'jQuery(".button-cancel").click(function(e) {
13752 e.preventDefault(); console.log(\'We click on cancel in iframe popup ' . dol_escape_js($dol_openinpopup) . '\');
13753 window.parent.jQuery(\'#idfordialog' . dol_escape_js($dol_openinpopup) . '\').dialog(\'close\');
13754 });';
13755 $retstring .= '</script>';
13756 }
13757
13758 return $retstring;
13759 }
13760
13761
13762 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
13763
13770 {
13771 // phpcs:enable
13772 global $langs;
13773
13774 $num = count($this->cache_invoice_subtype);
13775 if ($num > 0) {
13776 return 0; // Cache already loaded
13777 }
13778
13779 dol_syslog(__METHOD__, LOG_DEBUG);
13780
13781 $sql = "SELECT rowid, code, label as label";
13782 $sql .= " FROM " . MAIN_DB_PREFIX . 'c_invoice_subtype';
13783 $sql .= " WHERE active = 1";
13784
13785 $resql = $this->db->query($sql);
13786 if ($resql) {
13787 $num = $this->db->num_rows($resql);
13788 $i = 0;
13789 while ($i < $num) {
13790 $obj = $this->db->fetch_object($resql);
13791
13792 // If translation exists, we use it, otherwise we take the default wording
13793 $label = ($langs->trans("InvoiceSubtype" . $obj->rowid) != "InvoiceSubtype" . $obj->rowid) ? $langs->trans("InvoiceSubtype" . $obj->rowid) : (($obj->label != '-') ? $obj->label : '');
13794 $this->cache_invoice_subtype[$obj->rowid]['rowid'] = $obj->rowid;
13795 $this->cache_invoice_subtype[$obj->rowid]['code'] = $obj->code;
13796 $this->cache_invoice_subtype[$obj->rowid]['label'] = $label;
13797 $i++;
13798 }
13799
13800 $this->cache_invoice_subtype = dol_sort_array($this->cache_invoice_subtype, 'code', 'asc', 0, 0, 1);
13801
13802 return $num;
13803 } else {
13804 dol_print_error($this->db);
13805 return -1;
13806 }
13807 }
13808
13809
13820 public function getSelectInvoiceSubtype($selected = 0, $htmlname = 'subtypeid', $addempty = 0, $noinfoadmin = 0, $morecss = '')
13821 {
13822 global $langs, $user;
13823
13824 $out = '';
13825 dol_syslog(__METHOD__ . " selected=" . $selected . ", htmlname=" . $htmlname, LOG_DEBUG);
13826
13827 $this->load_cache_invoice_subtype();
13828
13829 $out .= '<select id="' . $htmlname . '" class="flat selectsubtype' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '">';
13830 if ($addempty) {
13831 $out .= '<option value="0">&nbsp;</option>';
13832 }
13833
13834 foreach ($this->cache_invoice_subtype as $rowid => $subtype) {
13835 $label = $subtype['label'];
13836 $out .= '<option value="' . $subtype['rowid'] . '"';
13837 if ($selected == $subtype['rowid']) {
13838 $out .= ' selected="selected"';
13839 }
13840 $out .= '>';
13841 $out .= $label;
13842 $out .= '</option>';
13843 }
13844
13845 $out .= '</select>';
13846 if ($user->admin && empty($noinfoadmin)) {
13847 $out .= info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
13848 }
13849 $out .= ajax_combobox($htmlname);
13850
13851 return $out;
13852 }
13853
13863 public function getSearchFilterToolInput($dataTarget, $htmlName = 'search-tools-input', $value = '', $params = [])
13864 {
13865 global $langs;
13866
13867 $attr = array(
13868 'type' => 'search',
13869 'name' => $htmlName,
13870 'value' => $value,
13871 'class' => "search-tool-input",
13872 'placeholder' => $langs->trans('Search'),
13873 'autocomplete' => 'off'
13874 );
13875
13876 // Optional data attr
13877 // 'autofocus' : will set auto focus on field ,
13878 // data-counter-target : will get count results
13879 // data-no-item-target : will be display if count results is 0
13880
13881 if ($dataTarget !== false) {
13882 $attr['data-search-tool-target'] = $dataTarget;
13883 }
13884
13885 // Override attr
13886 if (!empty($params['attr']) && is_array($params['attr'])) {
13887 foreach ($params['attr'] as $key => $value) {
13888 if ($key == 'class') {
13889 $attr['class'] .= ' '.$value;
13890 } elseif ($key == 'classOverride') {
13891 $attr['class'] = $value;
13892 } else {
13893 $attr[$key] = $value;
13894 }
13895 }
13896 }
13897
13898 // automatic add tooltip when title is detected
13899 if (!empty($attr['title']) && !empty($attr['class']) && strpos($attr['class'], 'classfortooltip') === false) {
13900 $attr['class'] .= ' classfortooltip';
13901 }
13902
13903 $TCompiledAttr = [];
13904 foreach ($attr as $key => $value) {
13905 if (in_array($key, ['data-target'])
13906 || (!empty($params['use_unsecured_unescapedattr']) && is_array($params['use_unsecured_unescapedattr']) && in_array($key, $params['use_unsecured_unescapedattr']))) { // Not recommended
13907 $value = dol_htmlentities($value, ENT_QUOTES | ENT_SUBSTITUTE);
13908 } else {
13909 $value = dolPrintHTMLForAttribute($value);
13910 }
13911
13912 $TCompiledAttr[] = $key . '="' . $value . '"'; // $value has been escaped by the dolPrintHTMLForAttribute... just before
13913 }
13914
13915 $compiledAttributes = implode(' ', $TCompiledAttr);
13916
13917
13918 return '<div class="search-tool-container"><input '.$compiledAttributes.'></div>';
13919 }
13920
13934 public function inputType($type, $name, $value = '', $id = '', $morecss = '', $moreparam = '', $label = '', $addInputLabel = '')
13935 {
13936 $out = '';
13937 if ($label != '') {
13938 $out .= '<label for="' . dolPrintHTMLForAttribute($id) . '">';
13939 }
13940 $out .= '<input type="' . dolPrintHTMLForAttribute($type) . '"';
13941 $out .= ' class="flat valignmiddle maxwidthonsmartphone ' . dolPrintHTMLForAttribute($morecss) . '"';
13942 if ($id != '') {
13943 $out .= ' id="' . dolPrintHTMLForAttribute($id) . '"';
13944 }
13945 $out .= ' name="' . dolPrintHTMLForAttribute($name) . '"';
13946 $out .= ' value="' . dolPrintHTMLForAttribute($value) . '" ';
13947 $out .= ($moreparam ? ' ' . $moreparam : '');
13948 $out .= ' />' . $addInputLabel;
13949 if ($label != '') {
13950 $out .= $label . '</label>';
13951 }
13952
13953 return $out;
13954 }
13955
13968 public function inputSelectAjax($htmlName, $array, $id, $ajaxUrl, $ajaxData = [], $morecss = 'minwidth75', $moreparam = '')
13969 {
13970 $out = "
13971 <script>
13972 $(document).ready(function () {
13973 $('#" . $htmlName . "').select2({
13974 ajax: {
13975 url: '" . $ajaxUrl . "',
13976 dataType: 'json',
13977 delay: 250, // wait 250 milliseconds before triggering the request
13978 data: function (params) {
13979 var query = {
13980 search: params.term,
13981 page: params.page || 1";
13982 if (!empty($ajaxData) && is_array($ajaxData)) {
13983 foreach ($ajaxData as $key => $value) {
13984 $out .= ", " . $key . ": '" . $value . "'";
13985 }
13986 }
13987 $out .= "
13988 }
13989 return query;
13990 }
13991 }
13992 })
13993 });
13994 </script>";
13995
13996 $out .= $this->selectarray($htmlName, $array, $id, 0, 0, 0, $moreparam, 0, 0, 0, '', $morecss);
13997
13998 return $out;
13999 }
14000
14010 public function inputHtml($htmlName, $value, $morecss = '', $moreparam = '')
14011 {
14012 require_once DOL_DOCUMENT_ROOT . '/core/class/doleditor.class.php';
14013 $doleditor = new DolEditor($htmlName, $value, '', 200, 'dolibarr_notes', 'In', false, false, isModEnabled('fckeditor') && getDolGlobalInt('FCKEDITOR_ENABLE_SOCIETE'), ROWS_5, '90%');
14014
14015 return (string) $doleditor->Create(1, '', true, '', '', $moreparam, $morecss);
14016 }
14017
14028 public function inputText($htmlName, $value, $morecss = '', $moreparam = '', $options = array())
14029 {
14030 global $langs;
14031
14032 $out = '';
14033 if (!empty($options)) {
14034 // If the textarea field has a list of arrayofkeyval into its definition, we suggest a combo with possible values to fill the textarea.
14035 $out .= $this->selectarray($htmlName . "_multiinput", $options, '', 1, 0, 0, $moreparam, 0, 0, 0, '', "flat maxwidthonphone" . $morecss);
14036 $out .= '<input id="' . $htmlName . '_multiinputadd" type="button" class="button" value="' . $langs->trans("Add") . '">';
14037 $out .= "<script>";
14038 $out .= '
14039 function handlemultiinputdisabling(htmlname){
14040 console.log("We handle the disabling of used options for "+htmlname+"_multiinput");
14041 multiinput = $("#"+htmlname+"_multiinput");
14042 multiinput.find("option").each(function(){
14043 tmpval = $("#"+htmlname).val();
14044 tmpvalarray = tmpval.split("\n");
14045 valtotest = $(this).val();
14046 if(tmpvalarray.includes(valtotest)){
14047 $(this).prop("disabled",true);
14048 } else {
14049 if($(this).prop("disabled") == true){
14050 console.log(valtotest)
14051 $(this).prop("disabled", false);
14052 }
14053 }
14054 });
14055 }
14056
14057 $(document).ready(function () {
14058 $("#' . $htmlName . '_multiinputadd").on("click",function() {
14059 tmpval = $("#' . $htmlName . '").val();
14060 tmpvalarray = tmpval.split(",");
14061 valtotest = $("#' . $htmlName . '_multiinput").val();
14062 if(valtotest != -1 && !tmpvalarray.includes(valtotest)){
14063 console.log("We add the selected value to the text area ' . $htmlName . '");
14064 if(tmpval == ""){
14065 tmpval = valtotest;
14066 } else {
14067 tmpval = tmpval + "\n" + valtotest;
14068 }
14069 $("#' . $htmlName . '").val(tmpval);
14070 handlemultiinputdisabling("' . $htmlName . '");
14071 $("#' . $htmlName . '_multiinput").val(-1);
14072 } else {
14073 console.log("We add nothing the text area ' . $htmlName . '");
14074 }
14075 });
14076 $("#' . $htmlName . '").on("change",function(){
14077 handlemultiinputdisabling("' . $htmlName . '");
14078 });
14079 handlemultiinputdisabling("' . $htmlName . '");
14080 })';
14081 $out .= "</script>";
14082 $value = str_replace(',', "\n", $value);
14083 }
14084
14085 require_once DOL_DOCUMENT_ROOT . '/core/class/doleditor.class.php';
14086 $doleditor = new DolEditor($htmlName, (string) $value, '', 200, 'dolibarr_notes', 'In', false, false, false, ROWS_5, '90%');
14087 $out .= (string) $doleditor->Create(1, '', true, '', '', $moreparam, $morecss);
14088
14089 return $out;
14090 }
14091
14102 public function inputRadio($htmlName, $options, $selectedValue, $morecss = '', $moreparam = '')
14103 {
14104 $out = '';
14105 foreach ($options as $optionKey => $optionLabel) {
14106 $selected = ((string) $selectedValue) === ((string) $optionKey) ? ' checked="checked"' : '';
14107 $optionId = $htmlName . '_' . $optionKey;
14108 $out .= '<input class="flat' . $morecss . '" type="radio" name="' . $htmlName . '" id="' . $optionId . '" value="' . dolPrintHTMLForAttribute((string) $optionKey) . '"' . $selected . $moreparam . '/><label for="' . $optionId . '">' . $optionLabel . '</label><br>';
14109 }
14110
14111 return $out;
14112 }
14113
14124 public function inputStars($htmlName, $size, $value, $morecss = '', $moreparam = '')
14125 {
14126 $out = '<input type="hidden" class="flat ' . $morecss . '" name="' . $htmlName . '" id="' . $htmlName . '" value="' . dolPrintHTMLForAttribute((string) $value) . '"' . $moreparam . '>';
14127 $out .= '<div class="star-selection" id="' . $htmlName . '_selection">';
14128 for ($i = 1; $i <= $size; $i++) {
14129 $out .= '<span class="star" data-value="' . $i . '">' . img_picto('', 'fontawesome_star_fas') . '</span>';
14130 }
14131 $out .= '</div>';
14132 $out .= '<script>
14133 jQuery(function($) { /* commonobject.class.php 1 */
14134 let container = $("#' . $htmlName . '_selection");
14135 let selectedStars = parseInt($("#' . $htmlName . '").val()) || 0;
14136 container.find(".star").each(function() {
14137 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
14138 });
14139 container.find(".star").on("mouseover", function() {
14140 let selectedStar = $(this).data("value");
14141 container.find(".star").each(function() {
14142 $(this).toggleClass("active", $(this).data("value") <= selectedStar);
14143 });
14144 });
14145 container.on("mouseout", function() {
14146 container.find(".star").each(function() {
14147 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
14148 });
14149 });
14150 container.find(".star").off("click").on("click", function() {
14151 selectedStars = $(this).data("value");
14152 if (selectedStars === 1 && $("#' . $htmlName . '").val() == 1) {
14153 selectedStars = 0;
14154 }
14155 $("#' . $htmlName . '").val(selectedStars);
14156 container.find(".star").each(function() {
14157 $(this).toggleClass("active", $(this).data("value") <= selectedStars);
14158 });
14159 });
14160 });
14161 </script>';
14162
14163 return $out;
14164 }
14165
14175 public function inputIcon($htmlName, $value, $morecss = '', $moreparam = '')
14176 {
14177 global $langs;
14178
14179 /* External lib inclusion are not allowed in backoffice. Also lib is included several time if there is several icon file.
14180 Some code must be added into main when MAIN_ADD_ICONPICKER_JS is set to add of lib in html header
14181 $out ='<link rel="stylesheet" href="'.dol_buildpath('/myfield/css/fontawesome-iconpicker.min.css', 1).'">';
14182 $out.='<script src="'.dol_buildpath('/myfield/js/fontawesome-iconpicker.min.js', 1).'"></script>';
14183 */
14184 $out = '<input type="text" class="form-control icp icp-auto iconpicker-element iconpicker-input flat ' . $morecss . ' maxwidthonsmartphone"';
14185 $out .= ' name="' . $htmlName . '" id="' . $htmlName . '" value="' . dolPrintHTMLForAttribute((string) $value) . '" ' . ((string) $moreparam) . '>';
14186 if (getDolGlobalInt('MAIN_ADD_ICONPICKER_JS')) {
14187 $out .= '<script>';
14188 $options = "{ title: '<b>" . $langs->trans("IconFieldSelector") . "</b>', placement: 'right', showFooter: false, templates: {";
14189 $options .= "iconpicker: '<div class=\"iconpicker\"><div style=\"background-color:#EFEFEF;\" class=\"iconpicker-items\"></div></div>',";
14190 $options .= "iconpickerItem: '<a role=\"button\" href=\"#\" class=\"iconpicker-item\" style=\"background-color:#DDDDDD;\"><i></i></a>',";
14191 // $options.="buttons: '<button style=\"background-color:#FFFFFF;\" class=\"iconpicker-btn iconpicker-btn-cancel btn btn-default btn-sm\">".$langs->trans("Cancel")."</button>";
14192 // $options.="<button style=\"background-color:#FFFFFF;\" class=\"iconpicker-btn iconpicker-btn-accept btn btn-primary btn-sm\">".$langs->trans("Save")."</button>',";
14193 $options .= "footer: '<div class=\"popover-footer\" style=\"background-color:#EFEFEF;\"></div>',";
14194 $options .= "search: '<input type=\"search\" class\"form-control iconpicker-search\" placeholder=\"" . $langs->trans("TypeToFilter") . "\" />',";
14195 $options .= "popover: '<div class=\"iconpicker-popover popover\">";
14196 $options .= " <div class=\"arrow\" ></div>";
14197 $options .= " <div class=\"popover-title\" style=\"text-align:center;background-color:#EFEFEF;\"></div>";
14198 $options .= " <div class=\"popover-content \" ></div>";
14199 $options .= "</div>'}}";
14200 $out .= "$('#" . $htmlName . "').iconpicker(" . $options . ");";
14201 $out .= '</script>';
14202 }
14203
14204 return $out;
14205 }
14206
14215 public function inputGeoPoint($htmlName, $value, $type = '')
14216 {
14217 require_once DOL_DOCUMENT_ROOT . '/core/class/dolgeophp.class.php';
14218 require_once DOL_DOCUMENT_ROOT . '/core/class/geomapeditor.class.php';
14219 $dolgeophp = new DolGeoPHP($this->db);
14220 $geomapeditor = new GeoMapEditor();
14221
14222 $geojson = '{}';
14223 $centroidjson = getDolGlobalString('MAIN_INFO_SOCIETE_GEO_COORDINATES', '{}');
14224 if (!empty($value)) {
14225 $tmparray = $dolgeophp->parseGeoString($value);
14226 $geojson = $tmparray['geojson'];
14227 $centroidjson = $tmparray['centroidjson'];
14228 }
14229
14230 return $geomapeditor->getHtml($htmlName, $geojson, $centroidjson, $type);
14231 }
14232
14239 public function outputMultiValues($values)
14240 {
14241 $out = '';
14242 $toPrint = array();
14243 $values = is_array($values) ? $values : array();
14244
14245 foreach ($values as $value) {
14246 $toPrint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $value . '</li>';
14247 }
14248 if (!empty($toPrint)) {
14249 $out = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toPrint) . '</ul></div>';
14250 }
14251
14252 return $out;
14253 }
14254
14262 public function outputStars($size, $value)
14263 {
14264 $out = '<div class="star-selection" data-value="' . dolPrintHTMLForAttribute((string) $value) . '">';
14265 for ($i = 1; $i <= $size; $i++) {
14266 $out .= '<span class="star' . ($i <= $value ? ' active' : '') . '" data-value="' . $i . '">' . img_picto('', 'fontawesome_star_fas') . '</span>';
14267 }
14268 $out .= '</div>';
14269
14270 return $out;
14271 }
14272
14279 public function outputIcon($value)
14280 {
14281 $out = '<span class="' . dolPrintHTMLForAttribute((string) $value) . '"></span>';
14282
14283 return $out;
14284 }
14285
14293 public function outputGeoPoint($value, $type)
14294 {
14295 $out = '';
14296
14297 if (!empty($value)) {
14298 require_once DOL_DOCUMENT_ROOT . '/core/class/dolgeophp.class.php';
14299 $dolgeophp = new DolGeoPHP($this->db);
14300 if ($type == 'point') {
14301 $out = $dolgeophp->getXYString($value);
14302 } else { // multipts, linestrg, polygon
14303 $out = $dolgeophp->getPointString($value);
14304 }
14305 }
14306
14307 return $out;
14308 }
14309
14324 public function getNomUrl(&$object, $withpicto = 0, $option = '', $maxlength = 0, $save_lastsearch_value = -1, $notooltip = 0, $morecss = '', $add_label = 0, $sep = ' - ')
14325 {
14326 if (is_object($object) && method_exists($object, 'getNomUrl')) {
14327 $out = $object->getNomUrl($withpicto, $option, $maxlength, $save_lastsearch_value, $notooltip, $morecss, $add_label, $sep);
14328 return $out;
14329 } else {
14330 return '';
14331 }
14332 }
14333}
$id
Support class for third parties, contacts, members, users or resources.
Definition account.php:47
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
$propal type
'integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]',...
Definition propal.php:280
ajax_autocompleter($selected, $htmlname, $url, $urloption='', $minLength=2, $autoselect=0, $ajaxoptions=array(), $moreparams='')
Generic function that return javascript to add to transform a common input text or select field into ...
Definition ajax.lib.php:50
ajax_combobox($htmlname, $events=array(), $minLengthToAutocomplete=0, $forcefocus=0, $widthTypeOfAutocomplete='resolve', $idforemptyvalue='-1', $morecss='', $placeholder='')
Convert a html select field into an ajax combobox.
Definition ajax.lib.php:477
$c
Definition line.php:346
Class to manage bank accounts.
Class to manage members of a foundation.
Class to manage categories.
Class to manage bank accounts description of third parties.
Class to manage contact/addresses.
Class to manage a WYSIWYG editor.
Class to manage Geo processing Usage: $dolgeophp=new DolGeoPHP($db);.
DAO Resource object.
Class to manage ECM files.
const STATUS_OPEN_INTERNAL
Warehouse open and only operations for stock transfers/corrections allowed (not for customer shipping...
const STATUS_OPEN_ALL
Warehouse open and any operations are allowed (customer shipping, supplier dispatch,...
const STATUS_CLOSED
Warehouse closed, inactive.
Class to manage standard extra languages.
Class to manage invoice templates.
Class to manage generation of HTML components Only common components must be here.
showLinkToObjectBlock($object, $restrictlinksto=array(), $excludelinksto=array(), $nooutput=0)
Show block with links "to link to" other objects.
inputSelectAjax($htmlName, $array, $id, $ajaxUrl, $ajaxData=[], $morecss='minwidth75', $moreparam='')
Html for select with get options by AJAX.
selectMultiCurrency($selected='', $htmlname='multicurrency_code', $useempty=0, $filter='', $excludeConfCurrency=false, $morecss='maxwidth200 widthcentpercentminusx')
Return array of currencies in user language.
showFilterButtons($pos='')
Return HTML to show the search and clear search button.
load_cache_vatrates($country_code)
Load into the cache ->cache_vatrates, all the vat rates of a country.
inputRadio($htmlName, $options, $selectedValue, $morecss='', $moreparam='')
Html for input radio.
select_comptes($selected='', $htmlname='accountid', $status=0, $filtre='', $useempty=0, $moreattrib='', $showcurrency=0, $morecss='', $nooutput=0, $addentrynone=0)
Return a HTML select list of bank accounts.
static selectArrayAjax($htmlname, $url, $id='', $moreparam='', $moreparamtourl='', $disabled=0, $minimumInputLength=1, $morecss='', $callurlonselect=0, $placeholder='', $acceptdelayedhtml=0)
Return a HTML select string, built from an array of key+value, but content returned into select come ...
inputGeoPoint($htmlName, $value, $type='')
Html for input geo point.
editfieldval($text, $htmlname, $value, $object, $perm, $typeofdata='string', $editvalue='', $extObject=null, $custommsg=null, $moreparam='', $notabletag=1, $formatfunc='', $paramid='id', $gm='auto', $moreoptions=array(), $editaction='')
Output value of a field for an editable field.
form_availability($page, $selected='', $htmlname='availability', $addempty=0)
Show a form to select a delivery delay.
showLinkedObjectBlock($object, $morehtmlright='', $compatibleImportElementsList=array(), $title='RelatedObjects')
Show linked object block.
selectMassAction($selected, $arrayofaction, $alwaysvisible=0, $name='massaction', $cssclass='checkforselect')
Generate select HTML to choose massaction.
getSelectRuleForLinesDates($selected='', $htmlname='rule_for_lines_dates', $addempty=0)
Returns select with rule for lines dates.
select_dolresources_forevent($action='', $htmlname='userid', $show_empty=0, $exclude=null, $disabled=0, $include=array(), $enableonly=array(), $force_entity='0', $maxlength=0, $showstatus=0, $morefilter='', $showproperties=0, $listofresourceid=array())
Return select list of resources.
form_multicurrency_code($page, $selected='', $htmlname='multicurrency_code')
Show form with multicurrency code.
static radio($htmlName, $radioItems, $selected='', $moreGlobalParams=[])
Generates a set of HTML radio inputs from an array of key-value items.
formRib($page, $selected='', $htmlname='ribcompanyid', $filtre='', $addempty=0, $showibanbic=0)
Display form to select bank customer account.
showFilterAndCheckAddButtons($addcheckuncheckall=0, $cssclass='checkforaction', $calljsfunction=0, $massactionname="massaction")
Return HTML to show the search and clear search button.
select_produits($selected=0, $htmlname='productid', $filtertype='', $limit=0, $price_level=0, $status=1, $finished=2, $selected_input_value='', $hidelabel=0, $ajaxoptions=array(), $socid=0, $showempty='1', $forcecombo=0, $morecss='', $hidepriceinlabel=0, $warehouseStatus='', $selected_combinations=null, $nooutput=0, $status_purchase=-1, $warehouseId=0)
Return list of products.
inputType($type, $name, $value='', $id='', $morecss='', $moreparam='', $label='', $addInputLabel='')
Html for input with label.
select_dolgroups($selected=0, $htmlname='groupid', $show_empty=0, $exclude='', $disabled=0, $include='', $enableonly=array(), $force_entity='0', $multiple=false, $morecss='minwidth200')
Return select list of user groups.
selectInputReason($selected='', $htmlname='demandreasonid', $exclude='', $addempty=0, $morecss='', $notooltip=0)
Return list of input reason (events that triggered an object creation, like after sending an emailing...
select_types_paiements($selected='', $htmlname='paiementtype', $filtertype='', $format=0, $empty=1, $noadmininfo=0, $maxlength=0, $active=1, $morecss='', $nooutput=0)
Return list of payment methods Constant MAIN_DEFAULT_PAYMENT_TYPE_ID can used to set default value bu...
select_currency($selected='', $htmlname='currency_id')
Returns the list of currencies in the user's language.
inputStars($htmlName, $size, $value, $morecss='', $moreparam='')
Html for input stars.
formSelectTransportMode($page, $selected='', $htmlname='transport_mode_id', $active=1, $addempty=0)
Show form with transport mode.
selectShippingMethod($selected='', $htmlname='shipping_method_id', $filtre='', $useempty=0, $moreattrib='', $noinfoadmin=0, $morecss='')
Return a HTML select list of shipping mode.
select_produits_list($selected=0, $htmlname='productid', $filtertype='', $limit=1000, $price_level=0, $filterkey='', $status=1, $finished=2, $outputmode=0, $socid=0, $showempty='1', $forcecombo=0, $morecss='maxwidth500', $hidepriceinlabel=0, $warehouseStatus='', $status_purchase=-1, $warehouseId=0)
Return list of products for a customer.
selectRib($selected='', $htmlname='ribcompanyid', $filtre='', $useempty=0, $moreattrib='', $showibanbic=0, $morecss='', $nooutput=0)
Return a HTML select list of bank accounts customer.
formSelectShippingMethod($page, $selected='', $htmlname='shipping_method_id', $addempty=0)
Display form to select shipping mode.
getSelectInvoiceSubtype($selected=0, $htmlname='subtypeid', $addempty=0, $noinfoadmin=0, $morecss='')
Return list of invoice subtypes.
form_contacts($page, $societe, $selected='', $htmlname='contactid')
Show forms to select a contact.
load_tva($htmlname='tauxtva', $selectedrate='', $societe_vendeuse=null, $societe_acheteuse=null, $idprod=0, $info_bits=0, $type='', $options_only=false, $mode=0, $type_vat=0)
Output an HTML select vat rate.
load_cache_availability()
Load int a cache property the list of possible delivery delays.
select_bom($selected='', $htmlname='bom_id', $limit=0, $status=1, $type=0, $showempty='1', $morecss='', $nooutput='', $forcecombo=0, $TProducts=[])
Return list of BOM for customer in Ajax if Ajax activated or go to select_produits_list.
form_rule_for_lines_dates($page, $selected='', $htmlname='rule_for_lines_dates', $addempty=0, $nooutput=0)
Form select for rule for lines dates.
static selectarray($htmlname, $array, $id='', $show_empty=0, $key_in_label=0, $value_as_key=0, $moreparam='', $translate=0, $maxlen=0, $disabled=0, $sort='', $morecss='minwidth75', $addjscombo=1, $moreparamonempty='', $disablebademail=0, $nohtmlescape=0)
Return a HTML select string, built from an array of key+value.
selectInvoiceRec($selected='', $htmlname='facrecid', $maxlength=24, $option_only=0, $show_empty='1', $forcefocus=0, $disabled=0, $morecss='maxwidth500')
Output a combo list with invoices qualified for a third party TODO Bad method.
inputIcon($htmlName, $value, $morecss='', $moreparam='')
Html for input icon.
form_multicurrency_rate($page, $rate=0.0, $htmlname='multicurrency_tx', $currency='', $rate_direct=0.0)
Show form with multicurrency rate.
editInPlace($object, $value, $htmlname, $condition, $inputType='textarea', $editvalue=null, $extObject=null, $custommsg=null)
Output edit in place form.
form_modes_reglement($page, $selected='', $htmlname='mode_reglement_id', $filtertype='', $active=1, $addempty=0, $type='', $nooutput=0, $short=0)
Show form with payment mode.
getPhoneInputSharedJs($countrySelectorId)
Return inline JS for country-selector → phone code sync (output once per page).
showPhoneInput($phoneValue, $htmlname, $country_id_hint=0, $picto='object_phoning', $morecss='maxwidth150', $maxlength=0, $countrySelectorId='selectcountry_id')
Show a self-contained phone input: hidden field + country code dropdown + number text field + JS.
selectUnits($selected='', $htmlname='units', $showempty=0, $unit_type='')
Creates HTML units selector (code => label)
static showphoto($modulepart, $object, $width=100, $height=0, $caneditfield=0, $cssclass='photowithmargin', $imagesize='', $addlinktofullsize=1, $cache=0, $forcecapture='', $noexternsourceoverwrite=0, $usesharelinkifavailable=0)
Return HTML code to output a photo.
constructProductListOption(&$objp, &$opt, &$optJson, $price_level, $selected, $hidepriceinlabel=0, $filterkey='', $novirtualstock=0)
Function to forge the string with OPTIONs of SELECT.
selectTransportMode($selected='', $htmlname='transportmode', $format=0, $empty=1, $noadmininfo=0, $maxlength=0, $active=1, $morecss='')
Return list of transport mode for intracomm report.
form_conditions_reglement($page, $selected='', $htmlname='cond_reglement_id', $addempty=0, $type='', $filtertype=-1, $deposit_percent=-1, $nooutput=0)
Show a form to select payment conditions.
selectSituationInvoices($selected='', $socid=0)
Creates HTML last in cycle situation invoices selector.
selectPriceBaseType($selected='', $htmlname='price_base_type', $addjscombo=0)
Selection HT or TTC.
load_cache_transport_mode()
getSearchFilterToolInput($dataTarget, $htmlName='search-tools-input', $value='', $params=[])
select_conditions_paiements($selected=0, $htmlname='condid', $filtertype=-1, $addempty=0, $noinfoadmin=0, $morecss='', $deposit_percent=-1, $noprint=0)
print list of payment modes.
showbarcode(&$object, $width=100, $morecss='')
Return HTML code to output a barcode.
load_cache_rule_for_lines_dates()
Loads into a cache property the list of possible rules for line dates.
form_remise_dispo($page, $selected, $htmlname, $socid, $amount, $filter='', $maxvalue=0, $more='', $hidelist=0, $discount_type=0, $filterabsolutediscount=0, $filtercreditnote=0)
Show a select box with available absolute discounts.
getPhoneInputFieldJs($htmlname, $codename)
Return inline JS that syncs the hidden phone field from select + text input.
form_confirm($page, $title, $question, $action, $formquestion=array(), $selectedchoice="", $useajax=0, $height=170, $width=500)
load_cache_conditions_paiements()
Load into cache list of payment terms.
selectExpenseCategories($selected='', $htmlname='fk_c_exp_tax_cat', $useempty=0, $excludeid=array(), $target='', $default_selected=0, $params=array(), $info_admin=1)
Return HTML to show the select of expense categories.
selectPhoneCode($selected='', $htmlname='phone_code', $morecss='maxwidth150', $showempty=0, $country_id_hint=0)
Return a select list of country phone calling codes.
select_product_fourn_price($productid, $htmlname='productfournpriceid', $selected_supplier=0)
Return list of suppliers prices for a product.
getNomUrl(&$object, $withpicto=0, $option='', $maxlength=0, $save_lastsearch_value=-1, $notooltip=0, $morecss='', $add_label=0, $sep=' - ')
Return link of object.
form_project($page, $socid, $selected='', $htmlname='projectid', $discard_closed=0, $maxlength=20, $forcefocus=0, $nooutput=0, $textifnoproject='', $morecss='', $option='')
Show a form to select a project.
selectAvailabilityDelay($selected='', $htmlname='availid', $filtertype='', $addempty=0, $morecss='', $noouput=0)
Return the list of type of delay available.
outputGeoPoint($value, $type)
Html for show geo point.
form_date($page, $selected, $htmlname, $displayhour=0, $displaymin=0, $nooutput=0, $type='')
Show a form + html select a date.
showCheckAddButtons($cssclass='checkforaction', $calljsfunction=0, $massactionname="massaction")
Return HTML to show the search and clear search button.
__construct($db)
Constructor.
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', $calendarpicto='')
Show a HTML widget to input a date or combo list for day, month, years and optionally hours and minut...
inputText($htmlName, $value, $morecss='', $moreparam='', $options=array())
Html for HTML area.
select_all_categories($type, $selected='', $htmlname="parent", $maxlength=64, $fromid=0, $outputmode=0, $include=0, $morecss='', $useempty=1)
Return list of categories having chosen type.
textwithpicto($text, $htmltooltip, $direction=1, $type='help', $extracss='valignmiddle', $noencodehtmltext=0, $notabs=3, $tooltiptrigger='', $forcenowrap=0)
Show a text with a picto and a tooltip on picto.
select_date($set_time='', $prefix='re', $h=0, $m=0, $empty=0, $form_name="", $d=1, $addnowlink=0, $nooutput=0, $disabled=0, $fullday=0, $addplusone='', $adddateof='')
Show a HTML widget to input a date or combo list for day, month, years and optionally hours and minut...
load_cache_invoice_subtype()
Load into cache list of invoice subtypes.
select_export_model($selected='', $htmlname='exportmodelid', $type='', $useempty=0)
Return list of export templates.
selectDateToDate($set_time='', $set_time_end='', $prefix='re', $empty=0, $forcenewline=0)
Show 2 HTML widget to input a date or combo list for day, month, years and optionally hours and minut...
outputMultiValues($values)
Html for show selected multiple values.
textwithtooltip($text, $htmltext, $tooltipon=1, $direction=0, $img='', $extracss='', $notabs=3, $incbefore='', $noencodehtmltext=0, $tooltiptrigger='', $forcenowrap=0)
Show a text and picto with tooltip on text or picto.
selectCategories($categtype, $htmlname, $object=null)
Return HTML component to select a category.
formconfirm($page, $title, $question, $action, $formquestion='', $selectedchoice='', $useajax=0, $height=0, $width=600, $disableformtag=0, $labelbuttonyes='Yes', $labelbuttonno='No', $helpContent='')
Show a confirmation HTML form or AJAX popup.
outputIcon($value)
Html for show icon.
getHelpBlock($content, $icon='fa-question-circle')
Generate a collapsible help block with a standard '?' icon.
getSelectConditionsPaiements($selected=0, $htmlname='condid', $filtertype=-1, $addempty=0, $noinfoadmin=0, $morecss='', $deposit_percent=-1)
Return list of payment modes.
widgetForTranslation($fieldname, $object, $perm, $typeofdata='string', $check='', $morecss='')
Output edit in place form.
outputStars($size, $value)
Html for show stars.
getDurationTypes(Translate $langs, $plurial=true, $reverse=false)
Return an array of Duration Types.
form_thirdparty($page, $selected='', $htmlname='socid', $filter='', $showempty=0, $showtype=0, $forcecombo=0, $events=array(), $nooutput=0, $excludeids=array(), $textifnothirdparty='')
Output html select to select thirdparty.
selectEstablishments($selected='', $htmlname='entity', $status=0, $filtre='', $useempty=0, $moreattrib='')
Return a HTML select list of establishment.
formInputReason($page, $selected='', $htmlname='demandreason', $addempty=0, $morecss='')
Output HTML form to select list of input reason (events that triggered an object creation,...
formSelectAccount($page, $selected='', $htmlname='fk_account', $addempty=0)
Display form to select bank account.
form_users($page, $selected='', $htmlname='userid', $exclude=array(), $include=array())
Show a select form to choose a user.
editfieldkey($text, $htmlname, $preselected, $object, $perm, $typeofdata='string', $moreparam='', $fieldrequired=0, $notabletag=0, $paramid='id', $help='')
Output key field for an editable field.
showCategories($id, $type, $rendermode=0, $nolink=0)
Render list of categories linked to object with id $id and type $type.
load_cache_types_paiements()
Load into the cacha array all possible payment modes.
select_produits_fournisseurs_list($socid, $selected='', $htmlname='productid', $filtertype='', $notused='', $filterkey='', $statut=-1, $outputmode=0, $limit=100, $alsoproductwithnosupplierprice=0, $morecss='', $showstockinlist=0, $placeholder='')
Return list of suppliers products.
selectCurrency($selected='', $htmlname='currency_id', $mode=0, $useempty='')
Returns the list of currencies in the user's language.
selectyesno($htmlname, $value='', $option=0, $disabled=false, $useempty=0, $addjscombo=0, $morecss='yesno width75', $labelyes='Yes', $labelno='No')
Return an html string with a select combo box to choose yes or no.
inputHtml($htmlName, $value, $morecss='', $moreparam='')
Html for HTML area.
select_produits_fournisseurs($socid, $selected='', $htmlname='productid', $filtertype='', $notused='', $ajaxoptions=array(), $hidelabel=0, $alsoproductwithnosupplierprice=0, $morecss='', $placeholder='', $nooutput=0)
Return list of products for customer (in Ajax if Ajax activated or go to select_produits_fournisseurs...
showrefnav($object, $paramid, $morehtml='', $shownav=1, $fieldid='rowid', $fieldref='ref', $morehtmlref='', $moreparam='', $nodbprefix=0, $morehtmlleft='', $morehtmlstatus='', $morehtmlright='')
Return a HTML area with the reference of object and a navigation bar for a business object Note: To c...
searchComponent($arrayofcriterias, $search_component_params, $arrayofinputfieldsalreadyoutput=array(), $search_component_params_hidden='', $arrayoffiltercriterias=array())
Output the component to make advanced search criteria.
selectForFormsList($objecttmp, $htmlname, $preselectedvalue, $showempty='', $searchkey='', $placeholder='', $morecss='', $moreparams='', $forcecombo=0, $outputmode=0, $disabled=0, $sortfield='', $filter='', $sortorder='ASC')
Output html form to select an object.
Class to manage building of HTML components.
Class to manage forms for the module resource.
Class to manage a Leaflet map width geometrics objects.
Class for MyObject.
Class to parse product price expressions.
Class to manage predefined suppliers products.
Class to manage products or services.
const TYPE_PRODUCT
Regular product.
const TYPE_SERVICE
Service.
Class to manage projects.
Class to manage third parties objects (customers, suppliers, prospects...)
Class to manage subscriptions of foundation members.
Class to manage translations.
Class to manage Dolibarr users.
print $langs trans("Ref").' m titre as m m statut as status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition index.php:169
if(! $sortfield) if(! $sortorder) $module
Definition list.php:193
getCountry($searchkey, $withcode='', $dbtouse=null, $outputlangs=null, $entconv=1, $searchlabel='')
Return country label, code or id from an id, code or label.
currency_name($code_iso, $withcode=0, $outputlangs=null)
Return label of currency or code+label.
isInEEC($object)
Return if a country of an object is inside the EEC (European Economic Community)
global $mysoc
getServerTimeZoneInt($refgmtdate='now')
Return server timezone int.
Definition date.lib.php:87
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
removeGlobalParenthesis($string)
Remove first and last parenthesis but only if first is the opening and last the closing of the same g...
dol_now($mode='gmt')
Return date for now.
dol_print_email($email, $contactid=0, $socid=0, $addlink=0, $max=0, $showinvalid=2, $withpicto=0, $morecss='paddingrightonly')
Show EMail link formatted for HTML output.
dol_getIdFromCode($db, $key, $tablename, $fieldkey='code', $fieldid='id', $entityfilter=0, $filters='', $useCache=true)
Return an id or code from a code or id.
dolForgeExplodeAnd($sqlfilters)
Explode an universal search string with AND parts.
vatrate($rate, $addpercent=false, $info_bits=0, $usestarfornpr=0, $html=0)
Return a string with VAT rate label formatted for view output Used into pdf and HTML pages.
dol_print_phone($phone, $countrycode='', $contactid=0, $socid=0, $addlink='', $separ="&nbsp;", $withpicto='', $titlealt='', $adddivfloat=0, $morecss='paddingright')
Format phone numbers according to country.
dol_string_nohtmltag($stringtoclean, $removelinefeed=1, $pagecodeto='UTF-8', $strip_tags=0, $removedoublespaces=1)
Clean a string from all HTML tags and entities.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
currentToken()
Return the value of token currently saved into session with name 'token'.
dolBuildUrl($url, $params=[], $addtoken=false, $anchor='')
Return path of url.
dol_eval($s, $returnvalue=1, $hideerrors=1, $onlysimplestring='1')
Replace eval function to add more security.
dol_nl2br($stringtoencode, $nl2brmode=0, $forxml=false)
Replace CRLF in string with a HTML BR tag.
dol_print_url($url, $target='_blank', $max=32, $withpicto=0, $morecss='')
Show Url link.
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
getDolUserString($key, $default='', $tmpuser=null)
Return Dolibarr user constant string value.
GETPOSTISARRAY($paramname, $method=0)
Return true if the parameter $paramname is submit from a POST OR GET as an array.
natural_search($fields, $value, $mode=0, $nofirstand=0, $sqltoadd='')
Generate natural SQL search string for a criteria (this criteria can be tested on one or several fiel...
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
getElementProperties($elementType)
Get an array with properties of an element.
dol_escape_js($stringtoescape, $mode=0, $noescapebackslashn=0)
Returns text escaped for inclusion into JavaScript code.
dol_sort_array(&$array, $index, $order='asc', $natsort=0, $case_sensitive=0, $keepindex=0)
Advanced sort array by the value of a given key, which produces ascending (default) or descending out...
if(!function_exists( 'dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
newToken()
Return the value of token currently saved into session with name 'newtoken'.
dol_string_unaccent($str)
Clean a string from all accent characters to be used as ref, login or by dol_sanitizeFileName.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0, $nodefault=0)
Return value of a param into GET or POST supervariable.
dol_string_neverthesehtmltags($stringtoclean, $disallowed_tags=array('textarea'), $cleanalsosomestyles=0)
Clean a string from some undesirable HTML tags.
get_default_npr(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod=0, $idprodfournprice=0)
Function that returns whether VAT must be recoverable collected VAT (e.g.: VAT NPR in France)
dol_htmlentities($string, $flags=ENT_QUOTES|ENT_SUBSTITUTE, $encoding='UTF-8', $double_encode=false)
Replace htmlentities functions.
forgeSQLFromUniversalSearchCriteria($filter, &$errorstr='', $noand=0, $nopar=0, $noerror=0, $forbiddenfields=array())
forgeSQLFromUniversalSearchCriteria
getImageFileNameForSize($file, $extName, $extImgTarget='')
Return the filename of file to get the thumbs.
colorIsLight($stringcolor)
Return true if the color is light.
dolIsAllowedForPreview($file)
Return if a file is qualified for preview.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '...' if string larger than length.
getNonce()
Return a random string to be used as a nonce value for js.
GETPOSTISSET($paramname)
Return true if we are in a context of submitting the parameter $paramname from a POST of a form.
dol_htmlentitiesbr($stringtoencode, $nl2brmode=0, $pagecodefrom='UTF-8', $removelasteolbr=1)
This function is called to encode a string into a HTML string but differs from htmlentities because a...
dol_htmlwithnojs($stringtoencode, $nouseofiframesandbox=0, $check='restricthtml')
Sanitize a HTML to remove js, dangerous content and external links.
dol_string_onlythesehtmltags($stringtoclean, $cleanalsosomestyles=1, $removeclassattribute=1, $cleanalsojavascript=0, $allowiframe=0, $allowed_tags=array(), $allowlink=0, $allowscript=0, $allowstyle=0, $allowphp=0)
Clean a string to keep only desirable HTML tags.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
dol_sanitizeKeyCode($str)
Clean a string to use it as a key or code.
isModEnabled($module)
Is Dolibarr module enabled.
array_merge_recursive_distinct(array $array1, array $array2)
Recursively merges two arrays while preserving keys and replacing existing values.
get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod=0, $idprodfournprice=0)
Function that return vat rate of a product line (according to seller, buyer and product vat rate) VAT...
get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart='')
Return a path to have a the directory according to object where files are stored.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
dol_nboflines_bis($text, $maxlinesize=0, $charset='UTF-8')
Return nb of lines of a formatted text with and (WARNING: string must not have mixed and br sep...
multi select button
0 = Do not include form tag and submit button -1 = Do not include form tag but include submit button
treeview li table
No Email.
a disabled
picto_from_langcode($codelang, $moreatt='', $notitlealt=0)
Return img flag of country for a language code or country code.
commonHtmlAttributeBuilder($attr, array $unescapedAttr=[])
Builds an array of safe and properly escaped HTML attributes from a key-value pair list.
dolButtonToOpenUrlInDialogPopup($name, $label, $buttonstring, $url, $disabled='', $morecss='classlink button bordertransp', $jsonopen='', $jsonclose='', $accesskey='')
Return HTML code to output a button to open a dialog popup box.
Definition html.lib.php:435
showDimensionInBestUnit($dimension, $unit, $type, $outputlangs, $round=-1, $forceunitoutput='no', $use_short_label=0)
Output a dimension with best unit.
getAdvancedPreviewUrl($modulepart, $relativepath, $alldata=0, $param='')
Return URL we can use for advanced preview links.
load_fiche_titre($title, $morehtmlright='', $picto='generic', $pictoisfullpath=0, $id='', $morecssontable='', $morehtmlcenter='', $morecssonpicto='widthpictotitle')
Load a title with picto.
info_admin($text, $infoonimgalt=0, $nodiv=0, $admin='1', $morecss='hideonsmartphone', $textfordropdown='', $picto='', $textonpictotooltip='', $cssfordropdown='info_admin')
Show information in HTML for admin users or standard users.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Definition html.lib.php:181
Class to generate the form for creating a new ticket.
dol_get_trunk_prefix($db, $phone_code)
Get the national trunk prefix for a phone code.
dol_parse_phone($phone)
Parse a stored phone number into country code and number parts.
Definition phone.lib.php:34
dol_get_phone_code_from_country($db, $country_id)
Get the phone calling code for a country.
measuringUnitString($unitid, $measuring_style='', $unitscale=null, $use_short_label=0, $outputlangs=null)
Return translation label of a unit key.
$conf db user
Active Directory does not allow anonymous connections.
Definition repair.php:141
$conf db name
Only used if Module[ID]Name translation string is not found.
Definition repair.php:140
getMaxFileSizeArray()
Return the max allowed for file upload.
dol_hash($chain, $type='0', $nosalt=0, $mode=0)
Returns a hash (non reversible encryption) of a string.
dolDecrypt($chain, $key='', $patterntotest='')
Decode a string with a symmetric encryption.
testSqlAndScriptInject($val, $type)
Security: WAF layer for SQL Injection and XSS Injection (scripts) protection (Filters on GET,...
Definition waf.inc.php:103