Passed
Push — EXTRACT_CLASSES ( a2ff75...ae6b5c )
by Rafael
34:15
created

Form   F

Complexity

Total Complexity 2459

Size/Duplication

Total Lines 11167
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 5808
dl 0
loc 11167
rs 0.8
c 0
b 0
f 0
wmc 2459

How to fix   Complexity   

Complex Class

Complex classes like Form often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Form, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/* Copyright (c) 2002-2007  Rodolphe Quiedeville        <[email protected]>
4
 * Copyright (C) 2004-2012  Laurent Destailleur         <[email protected]>
5
 * Copyright (C) 2004       Benoit Mortier              <[email protected]>
6
 * Copyright (C) 2004       Sebastien Di Cintio         <[email protected]>
7
 * Copyright (C) 2004       Eric Seigne                 <[email protected]>
8
 * Copyright (C) 2005-2017  Regis Houssin               <[email protected]>
9
 * Copyright (C) 2006       Andre Cianfarani            <[email protected]>
10
 * Copyright (C) 2006       Marc Barilley/Ocebo         <[email protected]>
11
 * Copyright (C) 2007       Franky Van Liedekerke       <[email protected]>
12
 * Copyright (C) 2007       Patrick Raguin              <[email protected]>
13
 * Copyright (C) 2010       Juanjo Menent               <[email protected]>
14
 * Copyright (C) 2010-2021  Philippe Grand              <[email protected]>
15
 * Copyright (C) 2011       Herve Prot                  <[email protected]>
16
 * Copyright (C) 2012-2016  Marcos García               <[email protected]>
17
 * Copyright (C) 2012       Cedric Salvador             <[email protected]>
18
 * Copyright (C) 2012-2015  Raphaël Doursenaud          <[email protected]>
19
 * Copyright (C) 2014-2023  Alexandre Spangaro          <[email protected]>
20
 * Copyright (C) 2018-2022  Ferran Marcet               <[email protected]>
21
 * Copyright (C) 2018-2024  Frédéric France             <[email protected]>
22
 * Copyright (C) 2018       Nicolas ZABOURI	            <[email protected]>
23
 * Copyright (C) 2018       Christophe Battarel         <[email protected]>
24
 * Copyright (C) 2018       Josep Lluis Amador          <[email protected]>
25
 * Copyright (C) 2023		Joachim Kueter			    <[email protected]>
26
 * Copyright (C) 2023		Nick Fragoulis
27
 * Copyright (C) 2024		MDW							<[email protected]>
28
 * Copyright (C) 2024       Rafael San José             <[email protected]>
29
 *
30
 * This program is free software; you can redistribute it and/or modify
31
 * it under the terms of the GNU General Public License as published by
32
 * the Free Software Foundation; either version 3 of the License, or
33
 * (at your option) any later version.
34
 *
35
 * This program is distributed in the hope that it will be useful,
36
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
37
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
38
 * GNU General Public License for more details.
39
 *
40
 * You should have received a copy of the GNU General Public License
41
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
42
 */
43
44
namespace Dolibarr\Code\Core\Classes;
45
46
use DoliDB;
47
48
/**
49
 * \file       htdocs/core/class/html.form.class.php
50
 * \ingroup    core
51
 * \brief      File of class with all html predefined components
52
 */
53
54
55
/**
56
 * Class to manage generation of HTML components
57
 * Only common components must be here.
58
 *
59
 * TODO Merge all function load_cache_* and loadCache* (except load_cache_vatrates) into one generic function loadCacheTable
60
 */
61
class Form
62
{
63
    /**
64
     * @var DoliDB Database handler.
65
     */
66
    public $db;
67
68
    /**
69
     * @var string Error code (or message)
70
     */
71
    public $error = '';
72
73
    /**
74
     * @var string[]    Array of error strings
75
     */
76
    public $errors = array();
77
78
    // Some properties used to return data by some methods
79
    /** @var array<string,int> */
80
    public $result;
81
    /** @var int */
82
    public $num;
83
84
    // Cache arrays
85
    public $cache_types_paiements = array();
86
    public $cache_conditions_paiements = array();
87
    public $cache_transport_mode = array();
88
    public $cache_availability = array();
89
    public $cache_demand_reason = array();
90
    public $cache_types_fees = array();
91
    public $cache_vatrates = array();
92
    public $cache_invoice_subtype = array();
93
94
95
    /**
96
     * Constructor
97
     *
98
     * @param DoliDB $db Database handler
99
     */
100
    public function __construct($db)
101
    {
102
        $this->db = $db;
103
    }
104
105
    /**
106
     * Output key field for an editable field
107
     *
108
     * @param   string  $text           Text of label or key to translate
109
     * @param   string  $htmlname       Name of select field ('edit' prefix will be added)
110
     * @param   string  $preselected    Value to show/edit (not used in this function)
111
     * @param   object  $object         Object (on the page we show)
112
     * @param   boolean $perm           Permission to allow button to edit parameter. Set it to 0 to have a not edited field.
113
     * @param   string  $typeofdata     Type of data ('string' by default, 'email', 'amount:99', 'numeric:99', 'text' or 'textarea:rows:cols', 'datepicker' ('day' do not work, don't know why), 'dayhour' or 'datehourpicker' 'checkbox:ckeditor:dolibarr_zzz:width:height:savemethod:1:rows:cols', 'select;xxx[:class]'...)
114
     * @param   string  $moreparam      More param to add on a href URL.
115
     * @param   int     $fieldrequired  1 if we want to show field as mandatory using the "fieldrequired" CSS.
116
     * @param   int     $notabletag     1=Do not output table tags but output a ':', 2=Do not output table tags and no ':', 3=Do not output table tags but output a ' '
117
     * @param   string  $paramid        Key of parameter for id ('id', 'socid')
118
     * @param   string  $help           Tooltip help
119
     * @return  string                  HTML edit field
120
     */
121
    public function editfieldkey($text, $htmlname, $preselected, $object, $perm, $typeofdata = 'string', $moreparam = '', $fieldrequired = 0, $notabletag = 0, $paramid = 'id', $help = '')
122
    {
123
        global $langs;
124
125
        $ret = '';
126
127
        // TODO change for compatibility
128
        if (getDolGlobalString('MAIN_USE_JQUERY_JEDITABLE') && !preg_match('/^select;/', $typeofdata)) {
129
            if (!empty($perm)) {
130
                $tmp = explode(':', $typeofdata);
131
                $ret .= '<div class="editkey_' . $tmp[0] . (!empty($tmp[1]) ? ' ' . $tmp[1] : '') . '" id="' . $htmlname . '">';
132
                if ($fieldrequired) {
133
                    $ret .= '<span class="fieldrequired">';
134
                }
135
                if ($help) {
136
                    $ret .= $this->textwithpicto($langs->trans($text), $help);
137
                } else {
138
                    $ret .= $langs->trans($text);
139
                }
140
                if ($fieldrequired) {
141
                    $ret .= '</span>';
142
                }
143
                $ret .= '</div>' . "\n";
144
            } else {
145
                if ($fieldrequired) {
146
                    $ret .= '<span class="fieldrequired">';
147
                }
148
                if ($help) {
149
                    $ret .= $this->textwithpicto($langs->trans($text), $help);
150
                } else {
151
                    $ret .= $langs->trans($text);
152
                }
153
                if ($fieldrequired) {
154
                    $ret .= '</span>';
155
                }
156
            }
157
        } else {
158
            if (empty($notabletag) && $perm) {
159
                $ret .= '<table class="nobordernopadding centpercent"><tr><td class="nowrap">';
160
            }
161
            if ($fieldrequired) {
162
                $ret .= '<span class="fieldrequired">';
163
            }
164
            if ($help) {
165
                $ret .= $this->textwithpicto($langs->trans($text), $help);
166
            } else {
167
                $ret .= $langs->trans($text);
168
            }
169
            if ($fieldrequired) {
170
                $ret .= '</span>';
171
            }
172
            if (!empty($notabletag)) {
173
                $ret .= ' ';
174
            }
175
            if (empty($notabletag) && $perm) {
176
                $ret .= '</td>';
177
            }
178
            if (empty($notabletag) && $perm) {
179
                $ret .= '<td class="right">';
180
            }
181
            if ($htmlname && GETPOST('action', 'aZ09') != 'edit' . $htmlname && $perm) {
182
                $ret .= '<a class="editfielda reposition" href="' . $_SERVER["PHP_SELF"] . '?action=edit' . $htmlname . '&token=' . newToken() . '&' . $paramid . '=' . $object->id . $moreparam . '">' . img_edit($langs->trans('Edit'), ($notabletag ? 0 : 1)) . '</a>';
183
            }
184
            if (!empty($notabletag) && $notabletag == 1) {
185
                if ($text) {
186
                    $ret .= ' : ';
187
                } else {
188
                    $ret .= ' ';
189
                }
190
            }
191
            if (!empty($notabletag) && $notabletag == 3) {
192
                $ret .= ' ';
193
            }
194
            if (empty($notabletag) && $perm) {
195
                $ret .= '</td>';
196
            }
197
            if (empty($notabletag) && $perm) {
198
                $ret .= '</tr></table>';
199
            }
200
        }
201
202
        return $ret;
203
    }
204
205
    /**
206
     * Output value of a field for an editable field
207
     *
208
     * @param string    $text           Text of label (not used in this function)
209
     * @param string    $htmlname       Name of select field
210
     * @param string    $value          Value to show/edit
211
     * @param CommonObject  $object         Object (that we want to show)
212
     * @param boolean   $perm           Permission to allow button to edit parameter
213
     * @param string    $typeofdata     Type of data ('string' by default, 'checkbox', 'email', 'phone', 'amount:99', 'numeric:99',
214
     *                                  'text' or 'textarea:rows:cols%', 'safehtmlstring', 'restricthtml',
215
     *                                  'datepicker' ('day' do not work, don't know why), 'dayhour' or 'datehourpicker', 'ckeditor:dolibarr_zzz:width:height:savemethod:toolbarstartexpanded:rows:cols', 'select;xkey:xval,ykey:yval,...')
216
     * @param string    $editvalue      When in edit mode, use this value as $value instead of value (for example, you can provide here a formatted price instead of numeric value, or a select combo). Use '' to use same than $value
217
     * @param ?CommonObject $extObject  External object ???
218
     * @param mixed     $custommsg      String or Array of custom messages : eg array('success' => 'MyMessage', 'error' => 'MyMessage')
219
     * @param string    $moreparam      More param to add on the form on action href URL parameter
220
     * @param int       $notabletag     Do no output table tags
221
     * @param string    $formatfunc     Call a specific method of $object->$formatfunc to output field in view mode (For example: 'dol_print_email')
222
     * @param string    $paramid        Key of parameter for id ('id', 'socid')
223
     * @param string    $gm             'auto' or 'tzuser' or 'tzuserrel' or 'tzserver' (when $typeofdata is a date)
224
     * @param array<string,int>     $moreoptions    Array with more options. For example array('addnowlink'=>1), array('valuealreadyhtmlescaped'=>1)
225
     * @param string    $editaction     [=''] use GETPOST default action or set action to edit mode
226
     * @return string                   HTML edit field
227
     */
228
    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 = '')
229
    {
230
        global $conf, $langs;
231
232
        $ret = '';
233
234
        // Check parameters
235
        if (empty($typeofdata)) {
236
            return 'ErrorBadParameter typeofdata is empty';
237
        }
238
        // Clean parameter $typeofdata
239
        if ($typeofdata == 'datetime') {
240
            $typeofdata = 'dayhour';
241
        }
242
        $reg = array();
243
        if (preg_match('/^(\w+)\((\d+)\)$/', $typeofdata, $reg)) {
244
            if ($reg[1] == 'varchar') {
245
                $typeofdata = 'string';
246
            } elseif ($reg[1] == 'int') {
247
                $typeofdata = 'numeric';
248
            } else {
249
                return 'ErrorBadParameter ' . $typeofdata;
250
            }
251
        }
252
253
        // When option to edit inline is activated
254
        if (getDolGlobalString('MAIN_USE_JQUERY_JEDITABLE') && !preg_match('/^select;|day|datepicker|dayhour|datehourpicker/', $typeofdata)) { // TODO add jquery timepicker and support select
255
            $ret .= $this->editInPlace($object, $value, $htmlname, $perm, $typeofdata, $editvalue, $extObject, $custommsg);
256
        } else {
257
            if ($editaction == '') {
258
                $editaction = GETPOST('action', 'aZ09');
259
            }
260
            $editmode = ($editaction == 'edit' . $htmlname);
261
            if ($editmode) {    // edit mode
262
                $ret .= "\n";
263
                $ret .= '<form method="post" action="' . $_SERVER["PHP_SELF"] . ($moreparam ? '?' . $moreparam : '') . '">';
264
                $ret .= '<input type="hidden" name="action" value="set' . $htmlname . '">';
265
                $ret .= '<input type="hidden" name="token" value="' . newToken() . '">';
266
                $ret .= '<input type="hidden" name="' . $paramid . '" value="' . $object->id . '">';
267
                if (empty($notabletag)) {
268
                    $ret .= '<table class="nobordernopadding centpercent">';
269
                }
270
                if (empty($notabletag)) {
271
                    $ret .= '<tr><td>';
272
                }
273
                if (preg_match('/^(string|safehtmlstring|email|phone|url)/', $typeofdata)) {
274
                    $tmp = explode(':', $typeofdata);
275
                    $ret .= '<input type="text" id="' . $htmlname . '" name="' . $htmlname . '" value="' . ($editvalue ? $editvalue : $value) . '"' . (empty($tmp[1]) ? '' : ' size="' . $tmp[1] . '"') . ' autofocus>';
276
                } elseif (preg_match('/^(integer)/', $typeofdata)) {
277
                    $tmp = explode(':', $typeofdata);
278
                    $valuetoshow = price2num($editvalue ? $editvalue : $value, 0);
279
                    $ret .= '<input type="text" id="' . $htmlname . '" name="' . $htmlname . '" value="' . $valuetoshow . '"' . (empty($tmp[1]) ? '' : ' size="' . $tmp[1] . '"') . ' autofocus>';
280
                } elseif (preg_match('/^(numeric|amount)/', $typeofdata)) {
281
                    $tmp = explode(':', $typeofdata);
282
                    $valuetoshow = price2num($editvalue ? $editvalue : $value);
283
                    $ret .= '<input type="text" id="' . $htmlname . '" name="' . $htmlname . '" value="' . ($valuetoshow != '' ? price($valuetoshow) : '') . '"' . (empty($tmp[1]) ? '' : ' size="' . $tmp[1] . '"') . ' autofocus>';
284
                } elseif (preg_match('/^(checkbox)/', $typeofdata)) {
285
                    $tmp = explode(':', $typeofdata);
286
                    $ret .= '<input type="checkbox" id="' . $htmlname . '" name="' . $htmlname . '" value="' . ($value ? $value : 'on') . '"' . ($value ? ' checked' : '') . (empty($tmp[1]) ? '' : $tmp[1]) . '/>';
287
                } elseif (preg_match('/^text/', $typeofdata) || preg_match('/^note/', $typeofdata)) {    // if wysiwyg is enabled $typeofdata = 'ckeditor'
288
                    $tmp = explode(':', $typeofdata);
289
                    $cols = (empty($tmp[2]) ? '' : $tmp[2]);
290
                    $morealt = '';
291
                    if (preg_match('/%/', $cols)) {
292
                        $morealt = ' style="width: ' . $cols . '"';
293
                        $cols = '';
294
                    }
295
                    $valuetoshow = ($editvalue ? $editvalue : $value);
296
                    $ret .= '<textarea id="' . $htmlname . '" name="' . $htmlname . '" wrap="soft" rows="' . (empty($tmp[1]) ? '20' : $tmp[1]) . '"' . ($cols ? ' cols="' . $cols . '"' : 'class="quatrevingtpercent"') . $morealt . '" autofocus>';
297
                    // textarea convert automatically entities chars into simple chars.
298
                    // 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.
299
                    $valuetoshow = str_replace('&', '&amp;', $valuetoshow);
300
                    $ret .= dol_htmlwithnojs(dol_string_neverthesehtmltags($valuetoshow, array('textarea')));
301
                    $ret .= '</textarea>';
302
                } elseif ($typeofdata == 'day' || $typeofdata == 'datepicker') {
303
                    $addnowlink = empty($moreoptions['addnowlink']) ? 0 : $moreoptions['addnowlink'];
304
                    $adddateof = empty($moreoptions['adddateof']) ? '' : $moreoptions['adddateof'];
305
                    $labeladddateof = empty($moreoptions['labeladddateof']) ? '' : $moreoptions['labeladddateof'];
306
                    $ret .= $this->selectDate($value, $htmlname, 0, 0, 1, 'form' . $htmlname, 1, $addnowlink, 0, '', '', $adddateof, '', 1, $labeladddateof, '', $gm);
307
                } elseif ($typeofdata == 'dayhour' || $typeofdata == 'datehourpicker') {
308
                    $addnowlink = empty($moreoptions['addnowlink']) ? 0 : $moreoptions['addnowlink'];
309
                    $adddateof = empty($moreoptions['adddateof']) ? '' : $moreoptions['adddateof'];
310
                    $labeladddateof = empty($moreoptions['labeladddateof']) ? '' : $moreoptions['labeladddateof'];
311
                    $ret .= $this->selectDate($value, $htmlname, 1, 1, 1, 'form' . $htmlname, 1, $addnowlink, 0, '', '', $adddateof, '', 1, $labeladddateof, '', $gm);
312
                } elseif (preg_match('/^select;/', $typeofdata)) {
313
                    $arraydata = explode(',', preg_replace('/^select;/', '', $typeofdata));
314
                    $arraylist = array();
315
                    foreach ($arraydata as $val) {
316
                        $tmp = explode(':', $val);
317
                        $tmpkey = str_replace('|', ':', $tmp[0]);
318
                        $arraylist[$tmpkey] = $tmp[1];
319
                    }
320
                    $ret .= $this->selectarray($htmlname, $arraylist, $value);
321
                } elseif (preg_match('/^link/', $typeofdata)) {
322
                    // TODO Not yet implemented. See code for extrafields
323
                } elseif (preg_match('/^ckeditor/', $typeofdata)) {
324
                    $tmp = explode(':', $typeofdata); // Example: ckeditor:dolibarr_zzz:width:height:savemethod:toolbarstartexpanded:rows:cols:uselocalbrowser
325
                    require_once constant('DOL_DOCUMENT_ROOT') . '/core/class/doleditor.class.php';
326
                    $doleditor = new DolEditor($htmlname, ($editvalue ? $editvalue : $value), (empty($tmp[2]) ? '' : $tmp[2]), (empty($tmp[3]) ? '100' : $tmp[3]), (empty($tmp[1]) ? 'dolibarr_notes' : $tmp[1]), 'In', (empty($tmp[5]) ? 0 : $tmp[5]), (isset($tmp[8]) ? ($tmp[8] ? true : false) : true), true, (empty($tmp[6]) ? '20' : $tmp[6]), (empty($tmp[7]) ? '100' : $tmp[7]));
327
                    $ret .= $doleditor->Create(1);
328
                } elseif ($typeofdata == 'asis') {
329
                    $ret .= ($editvalue ? $editvalue : $value);
330
                }
331
                if (empty($notabletag)) {
332
                    $ret .= '</td>';
333
                }
334
335
                // Button save-cancel
336
                if (empty($notabletag)) {
337
                    $ret .= '<td>';
338
                }
339
                //else $ret.='<div class="clearboth"></div>';
340
                $ret .= '<input type="submit" class="smallpaddingimp button' . (empty($notabletag) ? '' : ' ') . '" name="modify" value="' . $langs->trans("Modify") . '">';
341
                if (preg_match('/ckeditor|textarea/', $typeofdata) && empty($notabletag)) {
342
                    $ret .= '<br>' . "\n";
343
                }
344
                $ret .= '<input type="submit" class="smallpaddingimp button button-cancel' . (empty($notabletag) ? '' : ' ') . '" name="cancel" value="' . $langs->trans("Cancel") . '">';
345
                if (empty($notabletag)) {
346
                    $ret .= '</td>';
347
                }
348
349
                if (empty($notabletag)) {
350
                    $ret .= '</tr></table>' . "\n";
351
                }
352
                $ret .= '</form>' . "\n";
353
            } else {        // view mode
354
                if (preg_match('/^email/', $typeofdata)) {
355
                    $ret .= dol_print_email($value, 0, 0, 0, 0, 1);
356
                } elseif (preg_match('/^phone/', $typeofdata)) {
357
                    $ret .= dol_print_phone($value, '_blank', 32, 1);
358
                } elseif (preg_match('/^url/', $typeofdata)) {
359
                    $ret .= dol_print_url($value, '_blank', 32, 1);
360
                } elseif (preg_match('/^(amount|numeric)/', $typeofdata)) {
361
                    $ret .= ($value != '' ? price($value, 0, $langs, 0, -1, -1, $conf->currency) : '');
362
                } elseif (preg_match('/^checkbox/', $typeofdata)) {
363
                    $tmp = explode(':', $typeofdata);
364
                    $ret .= '<input type="checkbox" disabled id="' . $htmlname . '" name="' . $htmlname . '" value="' . $value . '"' . ($value ? ' checked' : '') . ($tmp[1] ? $tmp[1] : '') . '/>';
365
                } elseif (preg_match('/^text/', $typeofdata) || preg_match('/^note/', $typeofdata)) {
366
                    $ret .= dol_htmlwithnojs(dol_string_onlythesehtmltags(dol_htmlentitiesbr($value), 1, 1, 1));
367
                } elseif (preg_match('/^(safehtmlstring|restricthtml)/', $typeofdata)) {    // 'restricthtml' is not an allowed type for editfieldval. Value is 'safehtmlstring'
368
                    $ret .= dol_htmlwithnojs(dol_string_onlythesehtmltags($value));
369
                } elseif ($typeofdata == 'day' || $typeofdata == 'datepicker') {
370
                    $ret .= '<span class="valuedate">' . dol_print_date($value, 'day', $gm) . '</span>';
371
                } elseif ($typeofdata == 'dayhour' || $typeofdata == 'datehourpicker') {
372
                    $ret .= '<span class="valuedate">' . dol_print_date($value, 'dayhour', $gm) . '</span>';
373
                } elseif (preg_match('/^select;/', $typeofdata)) {
374
                    $arraydata = explode(',', preg_replace('/^select;/', '', $typeofdata));
375
                    $arraylist = array();
376
                    foreach ($arraydata as $val) {
377
                        $tmp = explode(':', $val);
378
                        $arraylist[$tmp[0]] = $tmp[1];
379
                    }
380
                    $ret .= $arraylist[$value];
381
                    if ($htmlname == 'fk_product_type') {
382
                        if ($value == 0) {
383
                            $ret = img_picto($langs->trans("Product"), 'product', 'class="paddingleftonly paddingrightonly colorgrey"') . $ret;
384
                        } else {
385
                            $ret = img_picto($langs->trans("Service"), 'service', 'class="paddingleftonly paddingrightonly colorgrey"') . $ret;
386
                        }
387
                    }
388
                } elseif (preg_match('/^ckeditor/', $typeofdata)) {
389
                    $tmpcontent = dol_htmlentitiesbr($value);
390
                    if (getDolGlobalString('MAIN_DISABLE_NOTES_TAB')) {
391
                        $firstline = preg_replace('/<br>.*/', '', $tmpcontent);
392
                        $firstline = preg_replace('/[\n\r].*/', '', $firstline);
393
                        $tmpcontent = $firstline . ((strlen($firstline) != strlen($tmpcontent)) ? '...' : '');
394
                    }
395
                    // We don't use dol_escape_htmltag to get the html formatting active, but this need we must also
396
                    // clean data from some dangerous html
397
                    $ret .= dol_string_onlythesehtmltags(dol_htmlentitiesbr($tmpcontent));
398
                } else {
399
                    if (empty($moreoptions['valuealreadyhtmlescaped'])) {
400
                        $ret .= dol_escape_htmltag($value);
401
                    } else {
402
                        $ret .= $value;        // $value must be already html escaped.
403
                    }
404
                }
405
406
                // Custom format if parameter $formatfunc has been provided
407
                if ($formatfunc && method_exists($object, $formatfunc)) {
408
                    $ret = $object->$formatfunc($ret);
409
                }
410
            }
411
        }
412
        return $ret;
413
    }
414
415
    /**
416
     * Output edit in place form
417
     *
418
     * @param   string  $fieldname  Name of the field
419
     * @param   CommonObject    $object Object
420
     * @param   boolean $perm       Permission to allow button to edit parameter. Set it to 0 to have a not edited field.
421
     * @param   string  $typeofdata Type of data ('string' by default, 'email', 'amount:99', 'numeric:99', 'text' or 'textarea:rows:cols', 'datepicker' ('day' do not work, don't know why), 'ckeditor:dolibarr_zzz:width:height:savemethod:1:rows:cols', 'select;xxx[:class]'...)
422
     * @param   string  $check      Same coe than $check parameter of GETPOST()
423
     * @param   string  $morecss    More CSS
424
     * @return  string              HTML code for the edit of alternative language
425
     */
426
    public function widgetForTranslation($fieldname, $object, $perm, $typeofdata = 'string', $check = '', $morecss = '')
427
    {
428
        global $conf, $langs, $extralanguages;
429
430
        $result = '';
431
432
        // List of extra languages
433
        $arrayoflangcode = array();
434
        if (getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE')) {
435
            $arrayoflangcode[] = getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE');
436
        }
437
438
        if (is_array($arrayoflangcode) && count($arrayoflangcode)) {
439
            if (!is_object($extralanguages)) {
440
                include_once DOL_DOCUMENT_ROOT . '/core/class/extralanguages.class.php';
441
                $extralanguages = new ExtraLanguages($this->db);
442
            }
443
            $extralanguages->fetch_name_extralanguages('societe');
444
445
            if (!is_array($extralanguages->attributes[$object->element]) || empty($extralanguages->attributes[$object->element][$fieldname])) {
446
                return ''; // No extralang field to show
447
            }
448
449
            $result .= '<!-- Widget for translation -->' . "\n";
450
            $result .= '<div class="inline-block paddingleft image-' . $object->element . '-' . $fieldname . '">';
451
            $s = img_picto($langs->trans("ShowOtherLanguages"), 'language', '', false, 0, 0, '', 'fa-15 editfieldlang');
452
            $result .= $s;
453
            $result .= '</div>';
454
455
            $result .= '<div class="inline-block hidden field-' . $object->element . '-' . $fieldname . '">';
456
457
            $resultforextrlang = '';
458
            foreach ($arrayoflangcode as $langcode) {
459
                $valuetoshow = GETPOSTISSET('field-' . $object->element . "-" . $fieldname . "-" . $langcode) ? GETPOST('field-' . $object->element . '-' . $fieldname . "-" . $langcode, $check) : '';
460
                if (empty($valuetoshow)) {
461
                    $object->fetchValuesForExtraLanguages();
462
                    //var_dump($object->array_languages);
463
                    $valuetoshow = $object->array_languages[$fieldname][$langcode];
464
                }
465
466
                $s = picto_from_langcode($langcode, 'class="pictoforlang paddingright"');
467
                $resultforextrlang .= $s;
468
469
                // TODO Use the showInputField() method of ExtraLanguages object
470
                if ($typeofdata == 'textarea') {
471
                    $resultforextrlang .= '<textarea name="field-' . $object->element . "-" . $fieldname . "-" . $langcode . '" id="' . $fieldname . "-" . $langcode . '" class="' . $morecss . '" rows="' . ROWS_2 . '" wrap="soft">';
472
                    $resultforextrlang .= $valuetoshow;
473
                    $resultforextrlang .= '</textarea>';
474
                } else {
475
                    $resultforextrlang .= '<input type="text" class="inputfieldforlang ' . ($morecss ? ' ' . $morecss : '') . '" name="field-' . $object->element . '-' . $fieldname . '-' . $langcode . '" value="' . $valuetoshow . '">';
476
                }
477
            }
478
            $result .= $resultforextrlang;
479
480
            $result .= '</div>';
481
            $result .= '<script nonce="' . getNonce() . '">$(".image-' . $object->element . '-' . $fieldname . '").click(function() { console.log("Toggle lang widget"); jQuery(".field-' . $object->element . '-' . $fieldname . '").toggle(); });</script>';
482
        }
483
484
        return $result;
485
    }
486
487
    /**
488
     * Output edit in place form
489
     *
490
     * @param   CommonObject    $object     Object
491
     * @param   string  $value      Value to show/edit
492
     * @param   string  $htmlname   DIV ID (field name)
493
     * @param   int     $condition  Condition to edit
494
     * @param   string  $inputType  Type of input ('string', 'numeric', 'datepicker' ('day' do not work, don't know why), 'textarea:rows:cols', 'ckeditor:dolibarr_zzz:width:height:?:1:rows:cols', 'select:loadmethod:savemethod:buttononly')
495
     * @param   string  $editvalue  When in edit mode, use this value as $value instead of value
496
     * @param   ?CommonObject   $extObject  External object
497
     * @param   mixed   $custommsg  String or Array of custom messages : eg array('success' => 'MyMessage', 'error' => 'MyMessage')
498
     * @return  string              HTML edit in place
499
     */
500
    protected function editInPlace($object, $value, $htmlname, $condition, $inputType = 'textarea', $editvalue = null, $extObject = null, $custommsg = null)
501
    {
502
        $out = '';
503
504
        // Check parameters
505
        if (preg_match('/^text/', $inputType)) {
506
            $value = dol_nl2br($value);
507
        } elseif (preg_match('/^numeric/', $inputType)) {
508
            $value = price($value);
509
        } elseif ($inputType == 'day' || $inputType == 'datepicker') {
510
            $value = dol_print_date($value, 'day');
511
        }
512
513
        if ($condition) {
514
            $element = false;
515
            $table_element = false;
516
            $fk_element = false;
517
            $loadmethod = false;
518
            $savemethod = false;
519
            $ext_element = false;
520
            $button_only = false;
521
            $inputOption = '';
522
            $rows = '';
523
            $cols = '';
524
525
            if (is_object($object)) {
526
                $element = $object->element;
527
                $table_element = $object->table_element;
528
                $fk_element = $object->id;
529
            }
530
531
            if (is_object($extObject)) {
532
                $ext_element = $extObject->element;
533
            }
534
535
            if (preg_match('/^(string|email|numeric)/', $inputType)) {
536
                $tmp = explode(':', $inputType);
537
                $inputType = $tmp[0];
538
                if (!empty($tmp[1])) {
539
                    $inputOption = $tmp[1];
540
                }
541
                if (!empty($tmp[2])) {
542
                    $savemethod = $tmp[2];
543
                }
544
                $out .= '<input id="width_' . $htmlname . '" value="' . $inputOption . '" type="hidden"/>' . "\n";
545
            } elseif ((preg_match('/^day$/', $inputType)) || (preg_match('/^datepicker/', $inputType)) || (preg_match('/^datehourpicker/', $inputType))) {
546
                $tmp = explode(':', $inputType);
547
                $inputType = $tmp[0];
548
                if (!empty($tmp[1])) {
549
                    $inputOption = $tmp[1];
550
                }
551
                if (!empty($tmp[2])) {
552
                    $savemethod = $tmp[2];
553
                }
554
555
                $out .= '<input id="timestamp" type="hidden"/>' . "\n"; // Use for timestamp format
556
            } elseif (preg_match('/^(select|autocomplete)/', $inputType)) {
557
                $tmp = explode(':', $inputType);
558
                $inputType = $tmp[0];
559
                $loadmethod = $tmp[1];
560
                if (!empty($tmp[2])) {
561
                    $savemethod = $tmp[2];
562
                }
563
                if (!empty($tmp[3])) {
564
                    $button_only = true;
565
                }
566
            } elseif (preg_match('/^textarea/', $inputType)) {
567
                $tmp = explode(':', $inputType);
568
                $inputType = $tmp[0];
569
                $rows = (empty($tmp[1]) ? '8' : $tmp[1]);
570
                $cols = (empty($tmp[2]) ? '80' : $tmp[2]);
571
            } elseif (preg_match('/^ckeditor/', $inputType)) {
572
                $tmp = explode(':', $inputType);
573
                $inputType = $tmp[0];
574
                $toolbar = $tmp[1];
575
                if (!empty($tmp[2])) {
576
                    $width = $tmp[2];
577
                }
578
                if (!empty($tmp[3])) {
579
                    $height = $tmp[3];
580
                }
581
                if (!empty($tmp[4])) {
582
                    $savemethod = $tmp[4];
583
                }
584
585
                if (isModEnabled('fckeditor')) {
586
                    $out .= '<input id="ckeditor_toolbar" value="' . $toolbar . '" type="hidden"/>' . "\n";
587
                } else {
588
                    $inputType = 'textarea';
589
                }
590
            }
591
592
            $out .= '<input id="element_' . $htmlname . '" value="' . $element . '" type="hidden"/>' . "\n";
593
            $out .= '<input id="table_element_' . $htmlname . '" value="' . $table_element . '" type="hidden"/>' . "\n";
594
            $out .= '<input id="fk_element_' . $htmlname . '" value="' . $fk_element . '" type="hidden"/>' . "\n";
595
            $out .= '<input id="loadmethod_' . $htmlname . '" value="' . $loadmethod . '" type="hidden"/>' . "\n";
596
            if (!empty($savemethod)) {
597
                $out .= '<input id="savemethod_' . $htmlname . '" value="' . $savemethod . '" type="hidden"/>' . "\n";
598
            }
599
            if (!empty($ext_element)) {
600
                $out .= '<input id="ext_element_' . $htmlname . '" value="' . $ext_element . '" type="hidden"/>' . "\n";
601
            }
602
            if (!empty($custommsg)) {
603
                if (is_array($custommsg)) {
604
                    if (!empty($custommsg['success'])) {
605
                        $out .= '<input id="successmsg_' . $htmlname . '" value="' . $custommsg['success'] . '" type="hidden"/>' . "\n";
606
                    }
607
                    if (!empty($custommsg['error'])) {
608
                        $out .= '<input id="errormsg_' . $htmlname . '" value="' . $custommsg['error'] . '" type="hidden"/>' . "\n";
609
                    }
610
                } else {
611
                    $out .= '<input id="successmsg_' . $htmlname . '" value="' . $custommsg . '" type="hidden"/>' . "\n";
612
                }
613
            }
614
            if ($inputType == 'textarea') {
615
                $out .= '<input id="textarea_' . $htmlname . '_rows" value="' . $rows . '" type="hidden"/>' . "\n";
616
                $out .= '<input id="textarea_' . $htmlname . '_cols" value="' . $cols . '" type="hidden"/>' . "\n";
617
            }
618
            $out .= '<span id="viewval_' . $htmlname . '" class="viewval_' . $inputType . ($button_only ? ' inactive' : ' active') . '">' . $value . '</span>' . "\n";
619
            $out .= '<span id="editval_' . $htmlname . '" class="editval_' . $inputType . ($button_only ? ' inactive' : ' active') . ' hideobject">' . (!empty($editvalue) ? $editvalue : $value) . '</span>' . "\n";
620
        } else {
621
            $out = $value;
622
        }
623
624
        return $out;
625
    }
626
627
    /**
628
     *  Show a text and picto with tooltip on text or picto.
629
     *  Can be called by an instancied $form->textwithtooltip or by a static call Form::textwithtooltip
630
     *
631
     *  @param  string  $text               Text to show
632
     *  @param  string  $htmltext           HTML content of tooltip. Must be HTML/UTF8 encoded.
633
     *  @param  int     $tooltipon          1=tooltip on text, 2=tooltip on image, 3=tooltip on both
634
     *  @param  int     $direction          -1=image is before, 0=no image, 1=image is after
635
     *  @param  string  $img                Html code for image (use img_xxx() function to get it)
636
     *  @param  string  $extracss           Add a CSS style to td tags
637
     *  @param  int     $notabs             0=Include table and tr tags, 1=Do not include table and tr tags, 2=use div, 3=use span
638
     *  @param  string  $incbefore          Include code before the text
639
     *  @param  int     $noencodehtmltext   Do not encode into html entity the htmltext
640
     *  @param  string  $tooltiptrigger     ''=Tooltip on hover, 'abc'=Tooltip on click (abc is a unique key)
641
     *  @param  int     $forcenowrap        Force no wrap between text and picto (works with notabs=2 only)
642
     *  @return string                      Code html du tooltip (texte+picto)
643
     *  @see    textwithpicto()             Use textwithpicto() instead of textwithtooltip if you can.
644
     */
645
    public function textwithtooltip($text, $htmltext, $tooltipon = 1, $direction = 0, $img = '', $extracss = '', $notabs = 3, $incbefore = '', $noencodehtmltext = 0, $tooltiptrigger = '', $forcenowrap = 0)
646
    {
647
        if ($incbefore) {
648
            $text = $incbefore . $text;
649
        }
650
        if (!$htmltext) {
651
            return $text;
652
        }
653
        $direction = (int) $direction;    // For backward compatibility when $direction was set to '' instead of 0
654
655
        $tag = 'td';
656
        if ($notabs == 2) {
657
            $tag = 'div';
658
        }
659
        if ($notabs == 3) {
660
            $tag = 'span';
661
        }
662
        // Sanitize tooltip
663
        $htmltext = str_replace(array("\r", "\n"), '', $htmltext);
664
665
        $extrastyle = '';
666
        if ($direction < 0) {
667
            $extracss = ($extracss ? $extracss . ' ' : '') . ($notabs != 3 ? 'inline-block' : '');
668
            $extrastyle = 'padding: 0px; padding-left: 3px;';
669
        }
670
        if ($direction > 0) {
671
            $extracss = ($extracss ? $extracss . ' ' : '') . ($notabs != 3 ? 'inline-block' : '');
672
            $extrastyle = 'padding: 0px; padding-right: 3px;';
673
        }
674
675
        $classfortooltip = 'classfortooltip';
676
677
        $s = '';
678
        $textfordialog = '';
679
680
        if ($tooltiptrigger == '') {
681
            $htmltext = str_replace('"', '&quot;', $htmltext);
682
        } else {
683
            $classfortooltip = 'classfortooltiponclick';
684
            $textfordialog .= '<div style="display: none;" id="idfortooltiponclick_' . $tooltiptrigger . '" class="classfortooltiponclicktext">' . $htmltext . '</div>';
685
        }
686
        if ($tooltipon == 2 || $tooltipon == 3) {
687
            $paramfortooltipimg = ' class="' . $classfortooltip . ($notabs != 3 ? ' inline-block' : '') . ($extracss ? ' ' . $extracss : '') . '" style="padding: 0px;' . ($extrastyle ? ' ' . $extrastyle : '') . '"';
688
            if ($tooltiptrigger == '') {
689
                $paramfortooltipimg .= ' title="' . ($noencodehtmltext ? $htmltext : dol_escape_htmltag($htmltext, 1)) . '"'; // Attribute to put on img tag to store tooltip
690
            } else {
691
                $paramfortooltipimg .= ' dolid="' . $tooltiptrigger . '"';
692
            }
693
        } else {
694
            $paramfortooltipimg = ($extracss ? ' class="' . $extracss . '"' : '') . ($extrastyle ? ' style="' . $extrastyle . '"' : ''); // Attribute to put on td text tag
695
        }
696
        if ($tooltipon == 1 || $tooltipon == 3) {
697
            $paramfortooltiptd = ' class="' . ($tooltipon == 3 ? 'cursorpointer ' : '') . $classfortooltip . ' inline-block' . ($extracss ? ' ' . $extracss : '') . '" style="padding: 0px;' . ($extrastyle ? ' ' . $extrastyle : '') . '" ';
698
            if ($tooltiptrigger == '') {
699
                $paramfortooltiptd .= ' title="' . ($noencodehtmltext ? $htmltext : dol_escape_htmltag($htmltext, 1)) . '"'; // Attribute to put on td tag to store tooltip
700
            } else {
701
                $paramfortooltiptd .= ' dolid="' . $tooltiptrigger . '"';
702
            }
703
        } else {
704
            $paramfortooltiptd = ($extracss ? ' class="' . $extracss . '"' : '') . ($extrastyle ? ' style="' . $extrastyle . '"' : ''); // Attribute to put on td text tag
705
        }
706
        if (empty($notabs)) {
707
            $s .= '<table class="nobordernopadding"><tr style="height: auto;">';
708
        } elseif ($notabs == 2) {
709
            $s .= '<div class="inline-block' . ($forcenowrap ? ' nowrap' : '') . '">';
710
        }
711
        // Define value if value is before
712
        if ($direction < 0) {
713
            $s .= '<' . $tag . $paramfortooltipimg;
714
            if ($tag == 'td') {
715
                $s .= ' class="valigntop" width="14"';
716
            }
717
            $s .= '>' . $textfordialog . $img . '</' . $tag . '>';
718
        }
719
        // Use another method to help avoid having a space in value in order to use this value with jquery
720
        // Define label
721
        if ((string) $text != '') {
722
            $s .= '<' . $tag . $paramfortooltiptd . '>' . $text . '</' . $tag . '>';
723
        }
724
        // Define value if value is after
725
        if ($direction > 0) {
726
            $s .= '<' . $tag . $paramfortooltipimg;
727
            if ($tag == 'td') {
728
                $s .= ' class="valignmiddle" width="14"';
729
            }
730
            $s .= '>' . $textfordialog . $img . '</' . $tag . '>';
731
        }
732
        if (empty($notabs)) {
733
            $s .= '</tr></table>';
734
        } elseif ($notabs == 2) {
735
            $s .= '</div>';
736
        }
737
738
        return $s;
739
    }
740
741
    /**
742
     * Show a text with a picto and a tooltip on picto
743
     *
744
     * @param   string      $text               Text to show
745
     * @param   string      $htmltext           Content of tooltip
746
     * @param   int         $direction          1=Icon is after text, -1=Icon is before text, 0=no icon
747
     * @param   string      $type               Type of picto ('info', 'infoclickable', 'help', 'helpclickable', 'warning', 'superadmin', 'mypicto@mymodule', ...) or image filepath or 'none'
748
     * @param   string      $extracss           Add a CSS style to td, div or span tag
749
     * @param   int         $noencodehtmltext   Do not encode into html entity the htmltext
750
     * @param   int         $notabs             0=Include table and tr tags, 1=Do not include table and tr tags, 2=use div, 3=use span
751
     * @param   string      $tooltiptrigger     ''=Tooltip on hover and hidden on smartphone, 'abconsmartphone'=Tooltip on hover and on click on smartphone, 'abc'=Tooltip on click (abc is a unique key, clickable link is on image or on link if param $type='none' or on both if $type='xxxclickable')
752
     * @param   int         $forcenowrap        Force no wrap between text and picto (works with notabs=2 only)
753
     * @return  string                          HTML code of text, picto, tooltip
754
     */
755
    public function textwithpicto($text, $htmltext, $direction = 1, $type = 'help', $extracss = '', $noencodehtmltext = 0, $notabs = 3, $tooltiptrigger = '', $forcenowrap = 0)
756
    {
757
        global $conf, $langs;
758
759
        //For backwards compatibility
760
        if ($type == '0') {
761
            $type = 'info';
762
        } elseif ($type == '1') {
763
            $type = 'help';
764
        }
765
        // Clean parameters
766
        $tooltiptrigger = preg_replace('/[^a-z0-9]/i', '', $tooltiptrigger);
767
768
        if (preg_match('/onsmartphone$/', $tooltiptrigger) && empty($conf->dol_no_mouse_hover)) {
769
            $tooltiptrigger = preg_replace('/^.*onsmartphone$/', '', $tooltiptrigger);
770
        }
771
        $alt = '';
772
        if ($tooltiptrigger) {
773
            $alt = $langs->transnoentitiesnoconv("ClickToShowHelp");
774
        }
775
776
        // If info or help with no javascript, show only text
777
        if (empty($conf->use_javascript_ajax)) {
778
            if ($type == 'info' || $type == 'infoclickable' || $type == 'help' || $type == 'helpclickable') {
779
                return $text;
780
            } else {
781
                $alt = $htmltext;
782
                $htmltext = '';
783
            }
784
        }
785
786
        // If info or help with smartphone, show only text (tooltip hover can't works)
787
        if (!empty($conf->dol_no_mouse_hover) && empty($tooltiptrigger)) {
788
            if ($type == 'info' || $type == 'infoclickable' || $type == 'help' || $type == 'helpclickable') {
789
                return $text;
790
            }
791
        }
792
        // If info or help with smartphone, show only text (tooltip on click does not works with dialog on smaprtphone)
793
        //if (!empty($conf->dol_no_mouse_hover) && !empty($tooltiptrigger))
794
        //{
795
        //if ($type == 'info' || $type == 'help') return '<a href="'..'">'.$text.'</a>';
796
        //}
797
798
        $img = '';
799
        if ($type == 'info') {
800
            $img = img_help(0, $alt);
801
        } elseif ($type == 'help') {
802
            $img = img_help(($tooltiptrigger != '' ? 2 : 1), $alt);
803
        } elseif ($type == 'helpclickable') {
804
            $img = img_help(($tooltiptrigger != '' ? 2 : 1), $alt);
805
        } elseif ($type == 'superadmin') {
806
            // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
807
            $img = img_picto($alt, 'redstar');
808
        } elseif ($type == 'admin') {
809
            // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
810
            $img = img_picto($alt, 'star');
811
        } elseif ($type == 'warning') {
812
            $img = img_warning($alt);
813
        } elseif ($type != 'none') {
814
            // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
815
            $img = img_picto($alt, $type); // $type can be an image path
816
        }
817
818
        return $this->textwithtooltip($text, $htmltext, ((($tooltiptrigger && !$img) || strpos($type, 'clickable')) ? 3 : 2), $direction, $img, $extracss, $notabs, '', $noencodehtmltext, $tooltiptrigger, $forcenowrap);
819
    }
820
821
    /**
822
     * Generate select HTML to choose massaction
823
     *
824
     * @param string    $selected       Value auto selected when at least one record is selected. Not a preselected value. Use '0' by default.
825
     * @param array<string,string>  $arrayofaction  array('code'=>'label', ...). The code is the key stored into the GETPOST('massaction') when submitting action.
826
     * @param int       $alwaysvisible  1=select button always visible
827
     * @param string    $name           Name for massaction
828
     * @param string    $cssclass       CSS class used to check for select
829
     * @return string|void              Select list
830
     */
831
    public function selectMassAction($selected, $arrayofaction, $alwaysvisible = 0, $name = 'massaction', $cssclass = 'checkforselect')
832
    {
833
        global $conf, $langs, $hookmanager;
834
835
        $disabled = 0;
836
        $ret = '<div class="centpercent center">';
837
        $ret .= '<select class="flat' . (empty($conf->use_javascript_ajax) ? '' : ' hideobject') . ' ' . $name . ' ' . $name . 'select valignmiddle alignstart" id="' . $name . '" name="' . $name . '"' . ($disabled ? ' disabled="disabled"' : '') . '>';
838
839
        // 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.
840
        $parameters = array();
841
        $reshook = $hookmanager->executeHooks('addMoreMassActions', $parameters); // Note that $action and $object may have been modified by hook
842
        // check if there is a mass action
843
844
        if (is_array($arrayofaction) && count($arrayofaction) == 0 && empty($hookmanager->resPrint)) {
845
            return;
846
        }
847
        if (empty($reshook)) {
848
            $ret .= '<option value="0"' . ($disabled ? ' disabled="disabled"' : '') . '>-- ' . $langs->trans("SelectAction") . ' --</option>';
849
            if (is_array($arrayofaction)) {
850
                foreach ($arrayofaction as $code => $label) {
851
                    $ret .= '<option value="' . $code . '"' . ($disabled ? ' disabled="disabled"' : '') . ' data-html="' . dol_escape_htmltag($label) . '">' . $label . '</option>';
852
                }
853
            }
854
        }
855
        $ret .= $hookmanager->resPrint;
856
857
        $ret .= '</select>';
858
859
        if (empty($conf->dol_optimize_smallscreen)) {
860
            $ret .= ajax_combobox('.' . $name . 'select');
861
        }
862
863
        // 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
864
        $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.
865
        $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")) . '">';
866
        $ret .= '</div>';
867
868
        if (!empty($conf->use_javascript_ajax)) {
869
            $ret .= '<!-- JS CODE TO ENABLE mass action select -->
870
    		<script nonce="' . getNonce() . '">
871
                        function initCheckForSelect(mode, name, cssclass)	/* mode is 0 during init of page or click all, 1 when we click on 1 checkboxi, "name" refers to the class of the massaction button, "cssclass" to the class of the checkfor select boxes */
872
        		{
873
        			atleastoneselected=0;
874
                                jQuery("."+cssclass).each(function( index ) {
875
    	  				/* console.log( index + ": " + $( this ).text() ); */
876
    	  				if ($(this).is(\':checked\')) atleastoneselected++;
877
    	  			});
878
879
					console.log("initCheckForSelect mode="+mode+" name="+name+" cssclass="+cssclass+" atleastoneselected="+atleastoneselected);
880
881
    	  			if (atleastoneselected || ' . $alwaysvisible . ')
882
    	  			{
883
                                    jQuery("."+name).show();
884
        			    ' . ($selected ? 'if (atleastoneselected) { jQuery("."+name+"select").val("' . $selected . '").trigger(\'change\'); jQuery("."+name+"confirmed").prop(\'disabled\', false); }' : '') . '
885
        			    ' . ($selected ? 'if (! atleastoneselected) { jQuery("."+name+"select").val("0").trigger(\'change\'); jQuery("."+name+"confirmed").prop(\'disabled\', true); } ' : '') . '
886
    	  			}
887
    	  			else
888
    	  			{
889
                                    jQuery("."+name).hide();
890
                                    jQuery("."+name+"other").hide();
891
    	            }
892
        		}
893
894
        	jQuery(document).ready(function () {
895
                    initCheckForSelect(0, "' . $name . '", "' . $cssclass . '");
896
                    jQuery(".' . $cssclass . '").click(function() {
897
                        initCheckForSelect(1, "' . $name . '", "' . $cssclass . '");
898
                    });
899
                        jQuery(".' . $name . 'select").change(function() {
900
        			var massaction = $( this ).val();
901
        			var urlform = $( this ).closest("form").attr("action").replace("#show_files","");
902
        			if (massaction == "builddoc")
903
                    {
904
                        urlform = urlform + "#show_files";
905
    	            }
906
        			$( this ).closest("form").attr("action", urlform);
907
                    console.log("we select a mass action name=' . $name . ' massaction="+massaction+" - "+urlform);
908
        	        /* Warning: if you set submit button to disabled, post using Enter will no more work if there is no other button */
909
        			if ($(this).val() != \'0\')
910
    	  			{
911
                                        jQuery(".' . $name . 'confirmed").prop(\'disabled\', false);
912
										jQuery(".' . $name . 'other").hide();	/* To disable if another div was open */
913
                                        jQuery(".' . $name . '"+massaction).show();
914
    	  			}
915
    	  			else
916
    	  			{
917
                                        jQuery(".' . $name . 'confirmed").prop(\'disabled\', true);
918
										jQuery(".' . $name . 'other").hide();	/* To disable any div open */
919
    	  			}
920
    	        });
921
        	});
922
    		</script>
923
        	';
924
        }
925
926
        return $ret;
927
    }
928
929
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
930
931
    /**
932
     *  Return combo list of activated countries, into language of user
933
     *
934
     * @param string        $selected               Id or Code or Label of preselected country
935
     * @param string        $htmlname               Name of html select object
936
     * @param string        $htmloption             More html options on select object
937
     * @param integer       $maxlength              Max length for labels (0=no limit)
938
     * @param string        $morecss                More css class
939
     * @param string        $usecodeaskey           ''=Use id as key (default), 'code3'=Use code on 3 alpha as key, 'code2"=Use code on 2 alpha as key
940
     * @param int<0,1>|string   $showempty              Show empty choice
941
     * @param int<0,1>      $disablefavorites       1=Disable favorites,
942
     * @param int<0,1>      $addspecialentries      1=Add dedicated entries for group of countries (like 'European Economic Community', ...)
943
     * @param string[]      $exclude_country_code   Array of country code (iso2) to exclude
944
     * @param int<0,1>      $hideflags              Hide flags
945
     * @return string                               HTML string with select
946
     */
947
    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)
948
    {
949
		// phpcs:enable
950
        global $conf, $langs, $mysoc;
951
952
        $langs->load("dict");
953
954
        $out = '';
955
        $countryArray = array();
956
        $favorite = array();
957
        $label = array();
958
        $atleastonefavorite = 0;
959
960
        $sql = "SELECT rowid, code as code_iso, code_iso as code_iso3, label, favorite, eec";
961
        $sql .= " FROM " . $this->db->prefix() . "c_country";
962
        $sql .= " WHERE active > 0";
963
        //$sql.= " ORDER BY code ASC";
964
965
        dol_syslog(get_class($this) . "::select_country", LOG_DEBUG);
966
        $resql = $this->db->query($sql);
967
        if ($resql) {
968
            $out .= '<select id="select' . $htmlname . '" class="flat maxwidth200onsmartphone selectcountry' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" ' . $htmloption . '>';
969
            $num = $this->db->num_rows($resql);
970
            $i = 0;
971
            if ($num) {
972
                while ($i < $num) {
973
                    $obj = $this->db->fetch_object($resql);
974
975
                    $countryArray[$i]['rowid'] = $obj->rowid;
976
                    $countryArray[$i]['code_iso'] = $obj->code_iso;
977
                    $countryArray[$i]['code_iso3'] = $obj->code_iso3;
978
                    $countryArray[$i]['label'] = ($obj->code_iso && $langs->transnoentitiesnoconv("Country" . $obj->code_iso) != "Country" . $obj->code_iso ? $langs->transnoentitiesnoconv("Country" . $obj->code_iso) : ($obj->label != '-' ? $obj->label : ''));
979
                    $countryArray[$i]['favorite'] = $obj->favorite;
980
                    $countryArray[$i]['eec'] = $obj->eec;
981
                    $favorite[$i] = $obj->favorite;
982
                    $label[$i] = dol_string_unaccent($countryArray[$i]['label']);
983
                    $i++;
984
                }
985
986
                if (empty($disablefavorites)) {
987
                    $array1_sort_order = SORT_DESC;
988
                    $array2_sort_order = SORT_ASC;
989
                    array_multisort($favorite, $array1_sort_order, $label, $array2_sort_order, $countryArray);
990
                } else {
991
                    $countryArray = dol_sort_array($countryArray, 'label');
992
                }
993
994
                if ($showempty) {
995
                    if (is_numeric($showempty)) {
996
                        $out .= '<option value="">&nbsp;</option>' . "\n";
997
                    } else {
998
                        $out .= '<option value="-1">' . $langs->trans($showempty) . '</option>' . "\n";
999
                    }
1000
                }
1001
1002
                if ($addspecialentries) {    // Add dedicated entries for groups of countries
1003
                    //if ($showempty) $out.= '<option value="" disabled class="selectoptiondisabledwhite">--------------</option>';
1004
                    $out .= '<option value="special_allnotme"' . ($selected == 'special_allnotme' ? ' selected' : '') . '>' . $langs->trans("CountriesExceptMe", $langs->transnoentitiesnoconv("Country" . $mysoc->country_code)) . '</option>';
1005
                    $out .= '<option value="special_eec"' . ($selected == 'special_eec' ? ' selected' : '') . '>' . $langs->trans("CountriesInEEC") . '</option>';
1006
                    if ($mysoc->isInEEC()) {
1007
                        $out .= '<option value="special_eecnotme"' . ($selected == 'special_eecnotme' ? ' selected' : '') . '>' . $langs->trans("CountriesInEECExceptMe", $langs->transnoentitiesnoconv("Country" . $mysoc->country_code)) . '</option>';
1008
                    }
1009
                    $out .= '<option value="special_noteec"' . ($selected == 'special_noteec' ? ' selected' : '') . '>' . $langs->trans("CountriesNotInEEC") . '</option>';
1010
                    $out .= '<option value="" disabled class="selectoptiondisabledwhite">------------</option>';
1011
                }
1012
1013
                foreach ($countryArray as $row) {
1014
                    //if (empty($showempty) && empty($row['rowid'])) continue;
1015
                    if (empty($row['rowid'])) {
1016
                        continue;
1017
                    }
1018
                    if (is_array($exclude_country_code) && count($exclude_country_code) && in_array($row['code_iso'], $exclude_country_code)) {
1019
                        continue; // exclude some countries
1020
                    }
1021
1022
                    if (empty($disablefavorites) && $row['favorite'] && $row['code_iso']) {
1023
                        $atleastonefavorite++;
1024
                    }
1025
                    if (empty($row['favorite']) && $atleastonefavorite) {
1026
                        $atleastonefavorite = 0;
1027
                        $out .= '<option value="" disabled class="selectoptiondisabledwhite">------------</option>';
1028
                    }
1029
1030
                    $labeltoshow = '';
1031
                    if ($row['label']) {
1032
                        $labeltoshow .= dol_trunc($row['label'], $maxlength, 'middle');
1033
                    } else {
1034
                        $labeltoshow .= '&nbsp;';
1035
                    }
1036
                    if ($row['code_iso']) {
1037
                        $labeltoshow .= ' <span class="opacitymedium">(' . $row['code_iso'] . ')</span>';
1038
                        if (empty($hideflags)) {
1039
                            $tmpflag = picto_from_langcode($row['code_iso'], 'class="saturatemedium paddingrightonly"', 1);
1040
                            $labeltoshow = $tmpflag . ' ' . $labeltoshow;
1041
                        }
1042
                    }
1043
1044
                    if ($selected && $selected != '-1' && ($selected == $row['rowid'] || $selected == $row['code_iso'] || $selected == $row['code_iso3'] || $selected == $row['label'])) {
1045
                        $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']) . '">';
1046
                    } else {
1047
                        $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']) . '">';
1048
                    }
1049
                    $out .= $labeltoshow;
1050
                    $out .= '</option>' . "\n";
1051
                }
1052
            }
1053
            $out .= '</select>';
1054
        } else {
1055
            dol_print_error($this->db);
1056
        }
1057
1058
        // Make select dynamic
1059
        include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
1060
        $out .= ajax_combobox('select' . $htmlname, array(), 0, 0, 'resolve');
1061
1062
        return $out;
1063
    }
1064
1065
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1066
1067
    /**
1068
     *  Return select list of incoterms
1069
     *
1070
     * @param   string  $selected               Id or Code of preselected incoterm
1071
     * @param   string  $location_incoterms     Value of input location
1072
     * @param   string  $page                   Defined the form action
1073
     * @param   string  $htmlname               Name of html select object
1074
     * @param   string  $htmloption             Options html on select object
1075
     * @param   int<0,1>    $forcecombo             Force to load all values and output a standard combobox (with no beautification)
1076
     * @param   array<array{method:string,url:string,htmlname:string,params:array<string,string>}>  $events                 Event options to run on change. Example: array(array('method'=>'getContacts', 'url'=>dol_buildpath('/core/ajax/contacts.php',1), 'htmlname'=>'contactid', 'params'=>array('add-customer-contact'=>'disabled')))
1077
     * @param   int<0,1>    $disableautocomplete    Disable autocomplete
1078
     * @return  string                          HTML string with select and input
1079
     */
1080
    public function select_incoterms($selected = '', $location_incoterms = '', $page = '', $htmlname = 'incoterm_id', $htmloption = '', $forcecombo = 1, $events = array(), $disableautocomplete = 0)
1081
    {
1082
		// phpcs:enable
1083
        global $conf, $langs;
1084
1085
        $langs->load("dict");
1086
1087
        $out = '';
1088
        $moreattrib = '';
1089
        $incotermArray = array();
1090
1091
        $sql = "SELECT rowid, code";
1092
        $sql .= " FROM " . $this->db->prefix() . "c_incoterms";
1093
        $sql .= " WHERE active > 0";
1094
        $sql .= " ORDER BY code ASC";
1095
1096
        dol_syslog(get_class($this) . "::select_incoterm", LOG_DEBUG);
1097
        $resql = $this->db->query($sql);
1098
        if ($resql) {
1099
            if ($conf->use_javascript_ajax && !$forcecombo) {
1100
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
1101
                $out .= ajax_combobox($htmlname, $events);
1102
            }
1103
1104
            if (!empty($page)) {
1105
                $out .= '<form method="post" action="' . $page . '">';
1106
                $out .= '<input type="hidden" name="action" value="set_incoterms">';
1107
                $out .= '<input type="hidden" name="token" value="' . newToken() . '">';
1108
            }
1109
1110
            $out .= '<select id="' . $htmlname . '" class="flat selectincoterm width75" name="' . $htmlname . '" ' . $htmloption . '>';
1111
            $out .= '<option value="0">&nbsp;</option>';
1112
            $num = $this->db->num_rows($resql);
1113
            $i = 0;
1114
            if ($num) {
1115
                while ($i < $num) {
1116
                    $obj = $this->db->fetch_object($resql);
1117
                    $incotermArray[$i]['rowid'] = $obj->rowid;
1118
                    $incotermArray[$i]['code'] = $obj->code;
1119
                    $i++;
1120
                }
1121
1122
                foreach ($incotermArray as $row) {
1123
                    if ($selected && ($selected == $row['rowid'] || $selected == $row['code'])) {
1124
                        $out .= '<option value="' . $row['rowid'] . '" selected>';
1125
                    } else {
1126
                        $out .= '<option value="' . $row['rowid'] . '">';
1127
                    }
1128
1129
                    if ($row['code']) {
1130
                        $out .= $row['code'];
1131
                    }
1132
1133
                    $out .= '</option>';
1134
                }
1135
            }
1136
            $out .= '</select>';
1137
1138
            if ($conf->use_javascript_ajax && empty($disableautocomplete)) {
1139
                $out .= ajax_multiautocompleter('location_incoterms', array(), constant('BASE_URL') . '/core/ajax/locationincoterms.php') . "\n";
1140
                $moreattrib .= ' autocomplete="off"';
1141
            }
1142
            $out .= '<input id="location_incoterms" class="maxwidthonsmartphone type="text" name="location_incoterms" value="' . $location_incoterms . '">' . "\n";
1143
1144
            if (!empty($page)) {
1145
                $out .= '<input type="submit" class="button valignmiddle smallpaddingimp nomargintop nomarginbottom" value="' . $langs->trans("Modify") . '"></form>';
1146
            }
1147
        } else {
1148
            dol_print_error($this->db);
1149
        }
1150
1151
        return $out;
1152
    }
1153
1154
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1155
1156
    /**
1157
     * Return list of types of lines (product or service)
1158
     * Example: 0=product, 1=service, 9=other (for external module)
1159
     *
1160
     * @param   string              $selected   Preselected type
1161
     * @param   string              $htmlname   Name of field in html form
1162
     * @param   int<0,1>|string     $showempty  Add an empty field
1163
     * @param   int                 $hidetext   Do not show label 'Type' before combo box (used only if there is at least 2 choices to select)
1164
     * @param   integer             $forceall   1=Force to show products and services in combo list, whatever are activated modules, 0=No force, 2=Force to show only Products, 3=Force to show only services, -1=Force none (and set hidden field to 'service')
1165
     * @param   string              $morecss    More css
1166
     * @return  void
1167
     */
1168
    public function select_type_of_lines($selected = '', $htmlname = 'type', $showempty = 0, $hidetext = 0, $forceall = 0, $morecss = "")
1169
    {
1170
		// phpcs:enable
1171
        global $langs;
1172
1173
        // If product & services are enabled or both disabled.
1174
        if (
1175
            $forceall == 1 || (empty($forceall) && isModEnabled("product") && isModEnabled("service"))
1176
            || (empty($forceall) && !isModEnabled('product') && !isModEnabled('service'))
1177
        ) {
1178
            if (empty($hidetext)) {
1179
                print $langs->trans("Type") . ': ';
1180
            }
1181
            print '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" id="select_' . $htmlname . '" name="' . $htmlname . '">';
1182
            if ($showempty) {
1183
                print '<option value="-1"';
1184
                if ($selected == -1) {
1185
                    print ' selected';
1186
                }
1187
                print '>';
1188
                if (is_numeric($showempty)) {
1189
                    print '&nbsp;';
1190
                } else {
1191
                    print $showempty;
1192
                }
1193
                print '</option>';
1194
            }
1195
1196
            print '<option value="0"';
1197
            if (0 == $selected || ($selected == -1 && getDolGlobalString('MAIN_FREE_PRODUCT_CHECKED_BY_DEFAULT') == 'product')) {
1198
                print ' selected';
1199
            }
1200
            print '>' . $langs->trans("Product");
1201
1202
            print '<option value="1"';
1203
            if (1 == $selected || ($selected == -1 && getDolGlobalString('MAIN_FREE_PRODUCT_CHECKED_BY_DEFAULT') == 'service')) {
1204
                print ' selected';
1205
            }
1206
            print '>' . $langs->trans("Service");
1207
1208
            print '</select>';
1209
            print ajax_combobox('select_' . $htmlname);
1210
            //if ($user->admin) print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"),1);
1211
        }
1212
        if ((empty($forceall) && !isModEnabled('product') && isModEnabled("service")) || $forceall == 3) {
1213
            print $langs->trans("Service");
1214
            print '<input type="hidden" name="' . $htmlname . '" value="1">';
1215
        }
1216
        if ((empty($forceall) && isModEnabled("product") && !isModEnabled('service')) || $forceall == 2) {
1217
            print $langs->trans("Product");
1218
            print '<input type="hidden" name="' . $htmlname . '" value="0">';
1219
        }
1220
        if ($forceall < 0) {    // This should happened only for contracts when both predefined product and service are disabled.
1221
            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
1222
        }
1223
    }
1224
1225
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1226
1227
    /**
1228
     *    Load into cache cache_types_fees, array of types of fees
1229
     *
1230
     * @return     int             Nb of lines loaded, <0 if KO
1231
     */
1232
    public function load_cache_types_fees()
1233
    {
1234
		// phpcs:enable
1235
        global $langs;
1236
1237
        $num = count($this->cache_types_fees);
1238
        if ($num > 0) {
1239
            return 0; // Cache already loaded
1240
        }
1241
1242
        dol_syslog(__METHOD__, LOG_DEBUG);
1243
1244
        $langs->load("trips");
1245
1246
        $sql = "SELECT c.code, c.label";
1247
        $sql .= " FROM " . $this->db->prefix() . "c_type_fees as c";
1248
        $sql .= " WHERE active > 0";
1249
1250
        $resql = $this->db->query($sql);
1251
        if ($resql) {
1252
            $num = $this->db->num_rows($resql);
1253
            $i = 0;
1254
1255
            while ($i < $num) {
1256
                $obj = $this->db->fetch_object($resql);
1257
1258
                // Si traduction existe, on l'utilise, sinon on prend le libelle par default
1259
                $label = ($obj->code != $langs->trans($obj->code) ? $langs->trans($obj->code) : $langs->trans($obj->label));
1260
                $this->cache_types_fees[$obj->code] = $label;
1261
                $i++;
1262
            }
1263
1264
            asort($this->cache_types_fees);
1265
1266
            return $num;
1267
        } else {
1268
            dol_print_error($this->db);
1269
            return -1;
1270
        }
1271
    }
1272
1273
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1274
1275
    /**
1276
     *    Return list of types of notes
1277
     *
1278
     * @param string $selected Preselected type
1279
     * @param string $htmlname Name of field in form
1280
     * @param int $showempty Add an empty field
1281
     * @return    void
1282
     */
1283
    public function select_type_fees($selected = '', $htmlname = 'type', $showempty = 0)
1284
    {
1285
		// phpcs:enable
1286
        global $user, $langs;
1287
1288
        dol_syslog(__METHOD__ . " selected=" . $selected . ", htmlname=" . $htmlname, LOG_DEBUG);
1289
1290
        $this->load_cache_types_fees();
1291
1292
        print '<select id="select_' . $htmlname . '" class="flat" name="' . $htmlname . '">';
1293
        if ($showempty) {
1294
            print '<option value="-1"';
1295
            if ($selected == -1) {
1296
                print ' selected';
1297
            }
1298
            print '>&nbsp;</option>';
1299
        }
1300
1301
        foreach ($this->cache_types_fees as $key => $value) {
1302
            print '<option value="' . $key . '"';
1303
            if ($key == $selected) {
1304
                print ' selected';
1305
            }
1306
            print '>';
1307
            print $value;
1308
            print '</option>';
1309
        }
1310
1311
        print '</select>';
1312
        if ($user->admin) {
1313
            print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
1314
        }
1315
    }
1316
1317
1318
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1319
1320
    /**
1321
     *  Output html form to select a third party
1322
     *  This call select_thirdparty_list() or ajax depending on setup. This component is not able to support multiple select.
1323
     *
1324
     * @param int|string    $selected               Preselected ID
1325
     * @param string        $htmlname               Name of field in form
1326
     * @param string        $filter                 Optional filters criteras. WARNING: To avoid SQL injection, only few chars [.a-z0-9 =<>()] are allowed here. Example: ((s.client:IN:1,3) AND (s.status:=:1)). Do not use a filter coming from input of users.
1327
     * @param string|int<1,1>   $showempty          Add an empty field (Can be '1' or text key to use on empty line like 'SelectThirdParty')
1328
     * @param int           $showtype               Show third party type in combolist (customer, prospect or supplier)
1329
     * @param int           $forcecombo             Force to load all values and output a standard combobox (with no beautification)
1330
     * @param array<array{method:string,url:string,htmlname:string,params:array<string,string>}>    $events     Ajax event options to run on change. Example: array(array('method'=>'getContacts', 'url'=>dol_buildpath('/core/ajax/contacts.php',1), 'htmlname'=>'contactid', 'params'=>array('add-customer-contact'=>'disabled')))
1331
     * @param int           $limit                  Maximum number of elements
1332
     * @param string        $morecss                Add more css styles to the SELECT component
1333
     * @param string        $moreparam              Add more parameters onto the select tag. For example 'style="width: 95%"' to avoid select2 component to go over parent container
1334
     * @param string        $selected_input_value   Value of preselected input text (for use with ajax)
1335
     * @param int<0,3>      $hidelabel              Hide label (0=no, 1=yes, 2=show search icon (before) and placeholder, 3 search icon after)
1336
     * @param array<string,string|string[]> $ajaxoptions        Options for ajax_autocompleter
1337
     * @param bool          $multiple               add [] in the name of element and add 'multiple' attribute (not working with ajax_autocompleter)
1338
     * @param string[]      $excludeids             Exclude IDs from the select combo
1339
     * @param int<0,1>      $showcode               Show code
1340
     * @return string                               HTML string with select box for thirdparty.
1341
     */
1342
    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)
1343
    {
1344
		// phpcs:enable
1345
        global $conf, $langs;
1346
1347
        $out = '';
1348
1349
        if (!empty($conf->use_javascript_ajax) && getDolGlobalString('COMPANY_USE_SEARCH_TO_SELECT') && !$forcecombo) {
1350
            if (is_null($ajaxoptions)) {
1351
                $ajaxoptions = array();
1352
            }
1353
1354
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/ajax.lib.php';
1355
1356
            // No immediate load of all database
1357
            $placeholder = '';
1358
            if ($selected && empty($selected_input_value)) {
1359
                require_once constant('DOL_DOCUMENT_ROOT') . '/societe/class/societe.class.php';
1360
                $societetmp = new Societe($this->db);
1361
                $societetmp->fetch($selected);
1362
                $selected_input_value = $societetmp->name;
1363
                unset($societetmp);
1364
            }
1365
1366
            // mode 1
1367
            $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)) : '');
1368
1369
            $out .= '<!-- force css to be higher than dialog popup --><style type="text/css">.ui-autocomplete { z-index: 1010; }</style>';
1370
            if (empty($hidelabel)) {
1371
                print $langs->trans("RefOrLabel") . ' : ';
1372
            } elseif ($hidelabel > 1) {
1373
                $placeholder = $langs->trans("RefOrLabel");
1374
                if ($hidelabel == 2) {
1375
                    $out .= img_picto($langs->trans("Search"), 'search');
1376
                }
1377
            }
1378
            $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' : '') . ' />';
1379
            if ($hidelabel == 3) {
1380
                $out .= img_picto($langs->trans("Search"), 'search');
1381
            }
1382
1383
            $out .= ajax_event($htmlname, $events);
1384
1385
            $out .= ajax_autocompleter($selected, $htmlname, constant('BASE_URL') . '/societe/ajax/company.php', $urloption, getDolGlobalString('COMPANY_USE_SEARCH_TO_SELECT'), 0, $ajaxoptions);
1386
        } else {
1387
            // Immediate load of all database
1388
            $out .= $this->select_thirdparty_list($selected, $htmlname, $filter, $showempty, $showtype, $forcecombo, $events, '', 0, $limit, $morecss, $moreparam, $multiple, $excludeids, $showcode);
1389
        }
1390
1391
        return $out;
1392
    }
1393
1394
1395
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1396
1397
    /**
1398
     * Output html form to select a contact
1399
     * This call select_contacts() or ajax depending on setup. This component is not able to support multiple select.
1400
     *
1401
     * Return HTML code of the SELECT of list of all contacts (for a third party or all).
1402
     * This also set the number of contacts found into $this->num
1403
     *
1404
     * @param   int             $socid              Id of third party or 0 for all or -1 for empty list
1405
     * @param   int|string      $selected           ID of preselected contact id
1406
     * @param   string          $htmlname           Name of HTML field ('none' for a not editable field)
1407
     * @param   int<0,3>|string $showempty          0=no empty value, 1=add an empty value, 2=add line 'Internal' (used by user edit), 3=add an empty value only if more than one record into list
1408
     * @param   string          $exclude            List of contacts id to exclude
1409
     * @param   string          $limitto            Not used
1410
     * @param   integer         $showfunction       Add function into label
1411
     * @param   string          $morecss            Add more class to class style
1412
     * @param   bool            $nokeyifsocid       When 1, we force the option "Press a key to show list" to 0 if there is a value for $socid
1413
     * @param   integer         $showsoc            Add company into label
1414
     * @param   int             $forcecombo         1=Force to use combo box (so no ajax beautify effect)
1415
     * @param   array<array{method:string,url:string,htmlname:string,params:array<string,string>}>  $events     Event options. Example: array(array('method'=>'getContacts', 'url'=>dol_buildpath('/core/ajax/contacts.php',1), 'htmlname'=>'contactid', 'params'=>array('add-customer-contact'=>'disabled')))
1416
     * @param   string          $moreparam          Add more parameters onto the select tag. For example 'style="width: 95%"' to avoid select2 component to go over parent container
1417
     * @param   string          $htmlid             Html id to use instead of htmlname
1418
     * @param   string          $selected_input_value   Value of preselected input text (for use with ajax)
1419
     * @param   string          $filter             Optional filters criteras. WARNING: To avoid SQL injection, only few chars [.a-z0-9 =<>()] are allowed here. Example: ((s.client:IN:1,3) AND (s.status:=:1)). Do not use a filter coming from input of users.
1420
     * @return  int|string                          Return integer <0 if KO, HTML with select string if OK.
1421
     */
1422
    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 = '')
1423
    {
1424
		// phpcs:enable
1425
1426
        global $conf, $langs;
1427
1428
        $out = '';
1429
1430
        $sav = getDolGlobalString('CONTACT_USE_SEARCH_TO_SELECT');
1431
        if ($nokeyifsocid && $socid > 0) {
1432
            $conf->global->CONTACT_USE_SEARCH_TO_SELECT = 0;
1433
        }
1434
1435
        if (!empty($conf->use_javascript_ajax) && getDolGlobalString('CONTACT_USE_SEARCH_TO_SELECT') && !$forcecombo) {
1436
            if (is_null($events)) {
1437
                $events = array();
1438
            }
1439
1440
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/ajax.lib.php';
1441
1442
            // No immediate load of all database
1443
            $placeholder = '';
1444
            if ($selected && empty($selected_input_value)) {
1445
                require_once constant('DOL_DOCUMENT_ROOT') . '/contact/class/contact.class.php';
1446
                $contacttmp = new Contact($this->db);
1447
                $contacttmp->fetch($selected);
1448
                $selected_input_value = $contacttmp->getFullName($langs);
1449
                unset($contacttmp);
1450
            }
1451
            if (!is_numeric($showempty)) {
1452
                $placeholder = $showempty;
1453
            }
1454
1455
            // mode 1
1456
            $urloption = 'htmlname=' . urlencode((string) (str_replace('.', '_', $htmlname))) . '&outjson=1&filter=' . urlencode((string) ($filter)) . (empty($exclude) ? '' : '&exclude=' . urlencode($exclude)) . ($showsoc ? '&showsoc=' . urlencode((string) ($showsoc)) : '');
1457
1458
            $out .= '<!-- force css to be higher than dialog popup --><style type="text/css">.ui-autocomplete { z-index: 1010; }</style>';
1459
1460
            $out .= '<input type="text" class="' . $morecss . '" name="search_' . $htmlname . '" id="search_' . $htmlname . '" value="' . $selected_input_value . '"' . ($placeholder ? ' placeholder="' . dol_escape_htmltag($placeholder) . '"' : '') . ' ' . (getDolGlobalString('CONTACT_SEARCH_AUTOFOCUS') ? 'autofocus' : '') . ' />';
1461
1462
            $out .= ajax_event($htmlname, $events);
1463
1464
            $out .= ajax_autocompleter($selected, $htmlname, constant('BASE_URL') . '/contact/ajax/contact.php', $urloption, getDolGlobalString('CONTACT_USE_SEARCH_TO_SELECT'), 0, $events);
1465
        } else {
1466
            // Immediate load of all database
1467
            $multiple = false;
1468
            $disableifempty = 0;
1469
            $options_only = false;
1470
            $limitto = '';
1471
1472
            $out .= $this->selectcontacts($socid, $selected, $htmlname, $showempty, $exclude, $limitto, $showfunction, $morecss, $options_only, $showsoc, $forcecombo, $events, $moreparam, $htmlid, $multiple, $disableifempty);
1473
        }
1474
1475
        $conf->global->CONTACT_USE_SEARCH_TO_SELECT = $sav;
1476
1477
        return $out;
1478
    }
1479
1480
1481
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1482
1483
    /**
1484
     *  Output html form to select a third party.
1485
     *  Note: you must use the select_company() to get the component to select a third party. This function must only be called by select_company.
1486
     *
1487
     * @param string            $selected       Preselected type
1488
     * @param string            $htmlname       Name of field in form
1489
     * @param string            $filter         Optional filters criteras. WARNING: To avoid SQL injection, only few chars [.a-z0-9 =<>] are allowed here, example: 's.rowid <> x'
1490
     *                                          If you need parenthesis, use the Universal Filter Syntax, example: '(s.client:in:1,3)'
1491
     *                                          Do not use a filter coming from input of users.
1492
     * @param string|int<0,1>   $showempty      Add an empty field (Can be '1' or text to use on empty line like 'SelectThirdParty')
1493
     * @param int<0,1>          $showtype       Show third party type in combolist (customer, prospect or supplier)
1494
     * @param int               $forcecombo     Force to use standard HTML select component without beautification
1495
     * @param array<array{method:string,url:string,htmlname:string,params:array<string,string>}>    $events     Event options. Example: array(array('method'=>'getContacts', 'url'=>dol_buildpath('/core/ajax/contacts.php',1), 'htmlname'=>'contactid', 'params'=>array('add-customer-contact'=>'disabled')))
1496
     * @param string            $filterkey      Filter on key value
1497
     * @param int<0,1>          $outputmode     0=HTML select string, 1=Array
1498
     * @param int               $limit          Limit number of answers
1499
     * @param string            $morecss        Add more css styles to the SELECT component
1500
     * @param string            $moreparam      Add more parameters onto the select tag. For example 'style="width: 95%"' to avoid select2 component to go over parent container
1501
     * @param bool              $multiple       add [] in the name of element and add 'multiple' attribute
1502
     * @param string[]          $excludeids     Exclude IDs from the select combo
1503
     * @param int<0,1>          $showcode       Show code in list
1504
     * @return array<int,array{key:int,value:string,label:string,labelhtml:string}>|string              HTML string with
1505
     * @see select_company()
1506
     */
1507
    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)
1508
    {
1509
		// phpcs:enable
1510
        global $user, $langs;
1511
        global $hookmanager;
1512
1513
        $out = '';
1514
        $num = 0;
1515
        $outarray = array();
1516
1517
        if ($selected === '') {
1518
            $selected = array();
1519
        } elseif (!is_array($selected)) {
1520
            $selected = array($selected);
1521
        }
1522
1523
        // Clean $filter that may contains sql conditions so sql code
1524
        if (function_exists('testSqlAndScriptInject')) {
1525
            if (testSqlAndScriptInject($filter, 3) > 0) {
1526
                $filter = '';
1527
                return 'SQLInjectionTryDetected';
1528
            }
1529
        }
1530
1531
        if ($filter != '') {    // If a filter was provided
1532
            if (preg_match('/[\(\)]/', $filter)) {
1533
                // If there is one parenthesis inside the criteria, we assume it is an Universal Filter Syntax.
1534
                $errormsg = '';
1535
                $filter = forgeSQLFromUniversalSearchCriteria($filter, $errormsg, 1);
1536
1537
                // Redo clean $filter that may contains sql conditions so sql code
1538
                if (function_exists('testSqlAndScriptInject')) {
1539
                    if (testSqlAndScriptInject($filter, 3) > 0) {
1540
                        $filter = '';
1541
                        return 'SQLInjectionTryDetected';
1542
                    }
1543
                }
1544
            } else {
1545
                // If not, we do nothing. We already know that there is no parenthesis
1546
                // TODO Disallow this case in a future.
1547
                dol_syslog("Warning, select_thirdparty_list was called with a filter criteria not using the Universal Search Syntax.", LOG_WARNING);
1548
            }
1549
        }
1550
1551
        // We search companies
1552
        $sql = "SELECT s.rowid, s.nom as name, s.name_alias, s.tva_intra, s.client, s.fournisseur, s.code_client, s.code_fournisseur";
1553
        if (getDolGlobalString('COMPANY_SHOW_ADDRESS_SELECTLIST')) {
1554
            $sql .= ", s.address, s.zip, s.town";
1555
            $sql .= ", dictp.code as country_code";
1556
        }
1557
        $sql .= " FROM " . $this->db->prefix() . "societe as s";
1558
        if (getDolGlobalString('COMPANY_SHOW_ADDRESS_SELECTLIST')) {
1559
            $sql .= " LEFT JOIN " . $this->db->prefix() . "c_country as dictp ON dictp.rowid = s.fk_pays";
1560
        }
1561
        if (!$user->hasRight('societe', 'client', 'voir')) {
1562
            $sql .= ", " . $this->db->prefix() . "societe_commerciaux as sc";
1563
        }
1564
        $sql .= " WHERE s.entity IN (" . getEntity('societe') . ")";
1565
        if (!empty($user->socid)) {
1566
            $sql .= " AND s.rowid = " . ((int) $user->socid);
1567
        }
1568
        if ($filter) {
1569
            // $filter is safe because, if it contains '(' or ')', it has been sanitized by testSqlAndScriptInject() and forgeSQLFromUniversalSearchCriteria()
1570
            // if not, by testSqlAndScriptInject() only.
1571
            $sql .= " AND (" . $filter . ")";
1572
        }
1573
        if (!$user->hasRight('societe', 'client', 'voir')) {
1574
            $sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = " . ((int) $user->id);
1575
        }
1576
        if (getDolGlobalString('COMPANY_HIDE_INACTIVE_IN_COMBOBOX')) {
1577
            $sql .= " AND s.status <> 0";
1578
        }
1579
        if (!empty($excludeids)) {
1580
            $sql .= " AND s.rowid NOT IN (" . $this->db->sanitize(implode(',', $excludeids)) . ")";
1581
        }
1582
        // Add where from hooks
1583
        $parameters = array();
1584
        $reshook = $hookmanager->executeHooks('selectThirdpartyListWhere', $parameters); // Note that $action and $object may have been modified by hook
1585
        $sql .= $hookmanager->resPrint;
1586
        // Add criteria
1587
        if ($filterkey && $filterkey != '') {
1588
            $sql .= " AND (";
1589
            $prefix = !getDolGlobalString('COMPANY_DONOTSEARCH_ANYWHERE') ? '%' : ''; // Can use index if COMPANY_DONOTSEARCH_ANYWHERE is on
1590
            // For natural search
1591
            $search_crit = explode(' ', $filterkey);
1592
            $i = 0;
1593
            if (count($search_crit) > 1) {
1594
                $sql .= "(";
1595
            }
1596
            foreach ($search_crit as $crit) {
1597
                if ($i > 0) {
1598
                    $sql .= " AND ";
1599
                }
1600
                $sql .= "(s.nom LIKE '" . $this->db->escape($prefix . $crit) . "%')";
1601
                $i++;
1602
            }
1603
            if (count($search_crit) > 1) {
1604
                $sql .= ")";
1605
            }
1606
            if (isModEnabled('barcode')) {
1607
                $sql .= " OR s.barcode LIKE '" . $this->db->escape($prefix . $filterkey) . "%'";
1608
            }
1609
            $sql .= " OR s.code_client LIKE '" . $this->db->escape($prefix . $filterkey) . "%' OR s.code_fournisseur LIKE '" . $this->db->escape($prefix . $filterkey) . "%'";
1610
            $sql .= " OR s.name_alias LIKE '" . $this->db->escape($prefix . $filterkey) . "%' OR s.tva_intra LIKE '" . $this->db->escape($prefix . $filterkey) . "%'";
1611
            $sql .= ")";
1612
        }
1613
        $sql .= $this->db->order("nom", "ASC");
1614
        $sql .= $this->db->plimit($limit, 0);
1615
1616
        // Build output string
1617
        dol_syslog(get_class($this) . "::select_thirdparty_list", LOG_DEBUG);
1618
        $resql = $this->db->query($sql);
1619
        if ($resql) {
1620
            // Construct $out and $outarray
1621
            $out .= '<select id="' . $htmlname . '" class="flat' . ($morecss ? ' ' . $morecss : '') . '"' . ($moreparam ? ' ' . $moreparam : '') . ' name="' . $htmlname . ($multiple ? '[]' : '') . '" ' . ($multiple ? 'multiple' : '') . '>' . "\n";
1622
1623
            $textifempty = (($showempty && !is_numeric($showempty)) ? $langs->trans($showempty) : '');
1624
            if (getDolGlobalString('COMPANY_USE_SEARCH_TO_SELECT')) {
1625
                // Do not use textifempty = ' ' or '&nbsp;' here, or search on key will search on ' key'.
1626
                //if (!empty($conf->use_javascript_ajax) || $forcecombo) $textifempty='';
1627
                if ($showempty && !is_numeric($showempty)) {
1628
                    $textifempty = $langs->trans($showempty);
1629
                } else {
1630
                    $textifempty .= $langs->trans("All");
1631
                }
1632
            }
1633
            if ($showempty) {
1634
                $out .= '<option value="-1" data-html="' . dol_escape_htmltag('<span class="opacitymedium">' . ($textifempty ? $textifempty : '&nbsp;') . '</span>') . '">' . $textifempty . '</option>' . "\n";
1635
            }
1636
1637
            $companytemp = new Societe($this->db);
1638
1639
            $num = $this->db->num_rows($resql);
1640
            $i = 0;
1641
            if ($num) {
1642
                while ($i < $num) {
1643
                    $obj = $this->db->fetch_object($resql);
1644
                    $label = '';
1645
                    if ($showcode || getDolGlobalString('SOCIETE_ADD_REF_IN_LIST')) {
1646
                        if (($obj->client) && (!empty($obj->code_client))) {
1647
                            $label = $obj->code_client . ' - ';
1648
                        }
1649
                        if (($obj->fournisseur) && (!empty($obj->code_fournisseur))) {
1650
                            $label .= $obj->code_fournisseur . ' - ';
1651
                        }
1652
                        $label .= ' ' . $obj->name;
1653
                    } else {
1654
                        $label = $obj->name;
1655
                    }
1656
1657
                    if (!empty($obj->name_alias)) {
1658
                        $label .= ' (' . $obj->name_alias . ')';
1659
                    }
1660
1661
                    if (getDolGlobalString('SOCIETE_SHOW_VAT_IN_LIST') && !empty($obj->tva_intra)) {
1662
                        $label .= ' - ' . $obj->tva_intra;
1663
                    }
1664
1665
                    $labelhtml = $label;
1666
1667
                    if ($showtype) {
1668
                        $companytemp->id = $obj->rowid;
1669
                        $companytemp->client = $obj->client;
1670
                        $companytemp->fournisseur = $obj->fournisseur;
1671
                        $tmptype = $companytemp->getTypeUrl(1, '', 0, 'span');
1672
                        if ($tmptype) {
1673
                            $labelhtml .= ' ' . $tmptype;
1674
                        }
1675
1676
                        if ($obj->client || $obj->fournisseur) {
1677
                            $label .= ' (';
1678
                        }
1679
                        if ($obj->client == 1 || $obj->client == 3) {
1680
                            $label .= $langs->trans("Customer");
1681
                        }
1682
                        if ($obj->client == 2 || $obj->client == 3) {
1683
                            $label .= ($obj->client == 3 ? ', ' : '') . $langs->trans("Prospect");
1684
                        }
1685
                        if ($obj->fournisseur) {
1686
                            $label .= ($obj->client ? ', ' : '') . $langs->trans("Supplier");
1687
                        }
1688
                        if ($obj->client || $obj->fournisseur) {
1689
                            $label .= ')';
1690
                        }
1691
                    }
1692
1693
                    if (getDolGlobalString('COMPANY_SHOW_ADDRESS_SELECTLIST')) {
1694
                        $s = ($obj->address ? ' - ' . $obj->address : '') . ($obj->zip ? ' - ' . $obj->zip : '') . ($obj->town ? ' ' . $obj->town : '');
1695
                        if (!empty($obj->country_code)) {
1696
                            $s .= ', ' . $langs->trans('Country' . $obj->country_code);
1697
                        }
1698
                        $label .= $s;
1699
                        $labelhtml .= $s;
1700
                    }
1701
1702
                    if (empty($outputmode)) {
1703
                        if (in_array($obj->rowid, $selected)) {
1704
                            $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>';
1705
                        } else {
1706
                            $out .= '<option value="' . $obj->rowid . '" data-html="' . dol_escape_htmltag($labelhtml, 0, 0, '', 0, 1) . '">' . dol_escape_htmltag($label, 0, 0, '', 0, 1) . '</option>';
1707
                        }
1708
                    } else {
1709
                        array_push($outarray, array('key' => $obj->rowid, 'value' => $label, 'label' => $label, 'labelhtml' => $labelhtml));
1710
                    }
1711
1712
                    $i++;
1713
                    if (($i % 10) == 0) {
1714
                        $out .= "\n";
1715
                    }
1716
                }
1717
            }
1718
            $out .= '</select>' . "\n";
1719
            if (!$forcecombo) {
1720
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
1721
                $out .= ajax_combobox($htmlname, $events, getDolGlobalInt("COMPANY_USE_SEARCH_TO_SELECT"));
1722
            }
1723
        } else {
1724
            dol_print_error($this->db);
1725
        }
1726
1727
        $this->result = array('nbofthirdparties' => $num);
1728
1729
        if ($outputmode) {
1730
            return $outarray;
1731
        }
1732
        return $out;
1733
    }
1734
1735
1736
    /**
1737
     * Return HTML code of the SELECT of list of all contacts (for a third party or all).
1738
     * This also set the number of contacts found into $this->num
1739
     * Note: you must use the select_contact() to get the component to select a contact. This function must only be called by select_contact.
1740
     *
1741
     * @param   int                 $socid              Id of third party or 0 for all or -1 for empty list
1742
     * @param   array|int|string    $selected           Array of ID of preselected contact id
1743
     * @param   string              $htmlname           Name of HTML field ('none' for a not editable field)
1744
     * @param   int<0,3>|string     $showempty          0=no empty value, 1=add an empty value, 2=add line 'Internal' (used by user edit), 3=add an empty value only if more than one record into list
1745
     * @param   string              $exclude            List of contacts id to exclude
1746
     * @param   string              $limitto            Disable answers that are not id in this array list
1747
     * @param   integer             $showfunction       Add function into label
1748
     * @param   string              $morecss            Add more class to class style
1749
     * @param   int                 $options_only       1=Return options only (for ajax treatment), 2=Return array
1750
     * @param   integer             $showsoc            Add company into label
1751
     * @param   int                 $forcecombo         Force to use combo box (so no ajax beautify effect)
1752
     * @param   array<array{method:string,url:string,htmlname:string,params:array<string,string>}>  $events     Event options. Example: array(array('method'=>'getContacts', 'url'=>dol_buildpath('/core/ajax/contacts.php',1), 'htmlname'=>'contactid', 'params'=>array('add-customer-contact'=>'disabled')))
1753
     * @param   string              $moreparam          Add more parameters onto the select tag. For example 'style="width: 95%"' to avoid select2 component to go over parent container
1754
     * @param   string              $htmlid             Html id to use instead of htmlname
1755
     * @param   bool                $multiple           add [] in the name of element and add 'multiple' attribute
1756
     * @param   integer             $disableifempty     Set tag 'disabled' on select if there is no choice
1757
     * @param   string              $filter             Optional filters criteras. WARNING: To avoid SQL injection, only few chars [.a-z0-9 =<>] are allowed here, example: 's.rowid <> x'
1758
     *                                                  If you need parenthesis, use the Universal Filter Syntax, example: '(s.client:in:1,3)'
1759
     *                                                  Do not use a filter coming from input of users.
1760
     * @return  int|string|array<int,array{key:int,value:string,label:string,labelhtml:string}>     Return integer <0 if KO, HTML with select string if OK.
1761
     */
1762
    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 = '')
1763
    {
1764
        global $conf, $langs, $hookmanager, $action;
1765
1766
        $langs->load('companies');
1767
1768
        if (empty($htmlid)) {
1769
            $htmlid = $htmlname;
1770
        }
1771
        $num = 0;
1772
        $out = '';
1773
        $outarray = array();
1774
1775
        if ($selected === '') {
1776
            $selected = array();
1777
        } elseif (!is_array($selected)) {
1778
            $selected = array((int) $selected);
1779
        }
1780
1781
        // Clean $filter that may contains sql conditions so sql code
1782
        if (function_exists('testSqlAndScriptInject')) {
1783
            if (testSqlAndScriptInject($filter, 3) > 0) {
1784
                $filter = '';
1785
                return 'SQLInjectionTryDetected';
1786
            }
1787
        }
1788
1789
        if ($filter != '') {    // If a filter was provided
1790
            if (preg_match('/[\(\)]/', $filter)) {
1791
                // If there is one parenthesis inside the criteria, we assume it is an Universal Filter Syntax.
1792
                $errormsg = '';
1793
                $filter = forgeSQLFromUniversalSearchCriteria($filter, $errormsg, 1);
1794
1795
                // Redo clean $filter that may contains sql conditions so sql code
1796
                if (function_exists('testSqlAndScriptInject')) {
1797
                    if (testSqlAndScriptInject($filter, 3) > 0) {
1798
                        $filter = '';
1799
                        return 'SQLInjectionTryDetected';
1800
                    }
1801
                }
1802
            } else {
1803
                // If not, we do nothing. We already know that there is no parenthesis
1804
                // TODO Disallow this case in a future.
1805
                dol_syslog("Warning, select_thirdparty_list was called with a filter criteria not using the Universal Search Syntax.", LOG_WARNING);
1806
            }
1807
        }
1808
1809
        if (!is_object($hookmanager)) {
1810
            include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
1811
            $hookmanager = new HookManager($this->db);
1812
        }
1813
1814
        // We search third parties
1815
        $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";
1816
        if ($showsoc > 0 || getDolGlobalString('CONTACT_SHOW_EMAIL_PHONE_TOWN_SELECTLIST')) {
1817
            $sql .= ", s.nom as company, s.town AS company_town";
1818
        }
1819
        $sql .= " FROM " . $this->db->prefix() . "socpeople as sp";
1820
        if ($showsoc > 0 || getDolGlobalString('CONTACT_SHOW_EMAIL_PHONE_TOWN_SELECTLIST')) {
1821
            $sql .= " LEFT OUTER JOIN  " . $this->db->prefix() . "societe as s ON s.rowid=sp.fk_soc";
1822
        }
1823
        $sql .= " WHERE sp.entity IN (" . getEntity('contact') . ")";
1824
        if ($socid > 0 || $socid == -1) {
1825
            $sql .= " AND sp.fk_soc = " . ((int) $socid);
1826
        }
1827
        if (getDolGlobalString('CONTACT_HIDE_INACTIVE_IN_COMBOBOX')) {
1828
            $sql .= " AND sp.statut <> 0";
1829
        }
1830
        if ($filter) {
1831
            // $filter is safe because, if it contains '(' or ')', it has been sanitized by testSqlAndScriptInject() and forgeSQLFromUniversalSearchCriteria()
1832
            // if not, by testSqlAndScriptInject() only.
1833
            $sql .= " AND (" . $filter . ")";
1834
        }
1835
        // Add where from hooks
1836
        $parameters = array();
1837
        $reshook = $hookmanager->executeHooks('selectContactListWhere', $parameters); // Note that $action and $object may have been modified by hook
1838
        $sql .= $hookmanager->resPrint;
1839
        $sql .= " ORDER BY sp.lastname ASC";
1840
1841
        dol_syslog(get_class($this) . "::selectcontacts", LOG_DEBUG);
1842
        $resql = $this->db->query($sql);
1843
        if ($resql) {
1844
            $num = $this->db->num_rows($resql);
1845
1846
            if ($htmlname != 'none' && !$options_only) {
1847
                $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" id="' . $htmlid . '" name="' . $htmlname . ($multiple ? '[]' : '') . '" ' . (($num || empty($disableifempty)) ? '' : ' disabled') . ($multiple ? 'multiple' : '') . ' ' . (!empty($moreparam) ? $moreparam : '') . '>';
1848
            }
1849
1850
            if ($showempty && !is_numeric($showempty)) {
1851
                $textforempty = $showempty;
1852
                $out .= '<option class="optiongrey" value="-1"' . (in_array(-1, $selected) ? ' selected' : '') . '>' . $textforempty . '</option>';
1853
            } else {
1854
                if (($showempty == 1 || ($showempty == 3 && $num > 1)) && !$multiple) {
1855
                    $out .= '<option value="0"' . (in_array(0, $selected) ? ' selected' : '') . '>&nbsp;</option>';
1856
                }
1857
                if ($showempty == 2) {
1858
                    $out .= '<option value="0"' . (in_array(0, $selected) ? ' selected' : '') . '>-- ' . $langs->trans("Internal") . ' --</option>';
1859
                }
1860
            }
1861
1862
            $i = 0;
1863
            if ($num) {
1864
                include_once DOL_DOCUMENT_ROOT . '/contact/class/contact.class.php';
1865
                $contactstatic = new Contact($this->db);
1866
1867
                while ($i < $num) {
1868
                    $obj = $this->db->fetch_object($resql);
1869
1870
                    // Set email (or phones) and town extended infos
1871
                    $extendedInfos = '';
1872
                    if (getDolGlobalString('CONTACT_SHOW_EMAIL_PHONE_TOWN_SELECTLIST')) {
1873
                        $extendedInfos = array();
1874
                        $email = trim($obj->email);
1875
                        if (!empty($email)) {
1876
                            $extendedInfos[] = $email;
1877
                        } else {
1878
                            $phone = trim($obj->phone);
1879
                            $phone_perso = trim($obj->phone_perso);
1880
                            $phone_mobile = trim($obj->phone_mobile);
1881
                            if (!empty($phone)) {
1882
                                $extendedInfos[] = $phone;
1883
                            }
1884
                            if (!empty($phone_perso)) {
1885
                                $extendedInfos[] = $phone_perso;
1886
                            }
1887
                            if (!empty($phone_mobile)) {
1888
                                $extendedInfos[] = $phone_mobile;
1889
                            }
1890
                        }
1891
                        $contact_town = trim($obj->contact_town);
1892
                        $company_town = trim($obj->company_town);
1893
                        if (!empty($contact_town)) {
1894
                            $extendedInfos[] = $contact_town;
1895
                        } elseif (!empty($company_town)) {
1896
                            $extendedInfos[] = $company_town;
1897
                        }
1898
                        $extendedInfos = implode(' - ', $extendedInfos);
1899
                        if (!empty($extendedInfos)) {
1900
                            $extendedInfos = ' - ' . $extendedInfos;
1901
                        }
1902
                    }
1903
1904
                    $contactstatic->id = $obj->rowid;
1905
                    $contactstatic->lastname = $obj->lastname;
1906
                    $contactstatic->firstname = $obj->firstname;
1907
                    if ($obj->statut == 1) {
1908
                        $tmplabel = '';
1909
                        if ($htmlname != 'none') {
1910
                            $disabled = 0;
1911
                            if (is_array($exclude) && count($exclude) && in_array($obj->rowid, $exclude)) {
1912
                                $disabled = 1;
1913
                            }
1914
                            if (is_array($limitto) && count($limitto) && !in_array($obj->rowid, $limitto)) {
1915
                                $disabled = 1;
1916
                            }
1917
                            if (!empty($selected) && in_array($obj->rowid, $selected)) {
1918
                                $out .= '<option value="' . $obj->rowid . '"';
1919
                                if ($disabled) {
1920
                                    $out .= ' disabled';
1921
                                }
1922
                                $out .= ' selected>';
1923
1924
                                $tmplabel = $contactstatic->getFullName($langs) . $extendedInfos;
1925
                                if ($showfunction && $obj->poste) {
1926
                                    $tmplabel .= ' (' . $obj->poste . ')';
1927
                                }
1928
                                if (($showsoc > 0) && $obj->company) {
1929
                                    $tmplabel .= ' - (' . $obj->company . ')';
1930
                                }
1931
1932
                                $out .= $tmplabel;
1933
                                $out .= '</option>';
1934
                            } else {
1935
                                $out .= '<option value="' . $obj->rowid . '"';
1936
                                if ($disabled) {
1937
                                    $out .= ' disabled';
1938
                                }
1939
                                $out .= '>';
1940
1941
                                $tmplabel = $contactstatic->getFullName($langs) . $extendedInfos;
1942
                                if ($showfunction && $obj->poste) {
1943
                                    $tmplabel .= ' (' . $obj->poste . ')';
1944
                                }
1945
                                if (($showsoc > 0) && $obj->company) {
1946
                                    $tmplabel .= ' - (' . $obj->company . ')';
1947
                                }
1948
1949
                                $out .= $tmplabel;
1950
                                $out .= '</option>';
1951
                            }
1952
                        } else {
1953
                            if (in_array($obj->rowid, $selected)) {
1954
                                $tmplabel = $contactstatic->getFullName($langs) . $extendedInfos;
1955
                                if ($showfunction && $obj->poste) {
1956
                                    $tmplabel .= ' (' . $obj->poste . ')';
1957
                                }
1958
                                if (($showsoc > 0) && $obj->company) {
1959
                                    $tmplabel .= ' - (' . $obj->company . ')';
1960
                                }
1961
1962
                                $out .= $tmplabel;
1963
                            }
1964
                        }
1965
1966
                        if ($tmplabel != '') {
1967
                            array_push($outarray, array('key' => $obj->rowid, 'value' => $tmplabel, 'label' => $tmplabel, 'labelhtml' => $tmplabel));
1968
                        }
1969
                    }
1970
                    $i++;
1971
                }
1972
            } else {
1973
                $labeltoshow = ($socid != -1) ? ($langs->trans($socid ? "NoContactDefinedForThirdParty" : "NoContactDefined")) : $langs->trans('SelectAThirdPartyFirst');
1974
                $out .= '<option class="disabled" value="-1"' . (($showempty == 2 || $multiple) ? '' : ' selected') . ' disabled="disabled">';
1975
                $out .= $labeltoshow;
1976
                $out .= '</option>';
1977
            }
1978
1979
            $parameters = array(
1980
                'socid' => $socid,
1981
                'htmlname' => $htmlname,
1982
                'resql' => $resql,
1983
                'out' => &$out,
1984
                'showfunction' => $showfunction,
1985
                'showsoc' => $showsoc,
1986
            );
1987
1988
            $reshook = $hookmanager->executeHooks('afterSelectContactOptions', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1989
1990
            if ($htmlname != 'none' && !$options_only) {
1991
                $out .= '</select>';
1992
            }
1993
1994
            if ($conf->use_javascript_ajax && !$forcecombo && !$options_only) {
1995
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
1996
                $out .= ajax_combobox($htmlid, $events, getDolGlobalInt("CONTACT_USE_SEARCH_TO_SELECT"));
1997
            }
1998
1999
            $this->num = $num;
2000
2001
            if ($options_only === 2) {
2002
                // Return array of options
2003
                return $outarray;
2004
            } else {
2005
                return $out;
2006
            }
2007
        } else {
2008
            dol_print_error($this->db);
2009
            return -1;
2010
        }
2011
    }
2012
2013
2014
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2015
2016
    /**
2017
     *  Return HTML combo list of absolute discounts
2018
     *
2019
     * @param   string  $selected   Id Fixed reduction preselected
2020
     * @param   string  $htmlname   Name of the form field
2021
     * @param   string  $filter     Optional filter critreria
2022
     * @param   int     $socid      Id of thirdparty
2023
     * @param   int     $maxvalue   Max value for lines that can be selected
2024
     * @return  int                 Return number of qualifed lines in list
2025
     */
2026
    public function select_remises($selected, $htmlname, $filter, $socid, $maxvalue = 0)
2027
    {
2028
		// phpcs:enable
2029
        global $langs, $conf;
2030
2031
        // On recherche les remises
2032
        $sql = "SELECT re.rowid, re.amount_ht, re.amount_tva, re.amount_ttc,";
2033
        $sql .= " re.description, re.fk_facture_source";
2034
        $sql .= " FROM " . $this->db->prefix() . "societe_remise_except as re";
2035
        $sql .= " WHERE re.fk_soc = " . (int) $socid;
2036
        $sql .= " AND re.entity = " . $conf->entity;
2037
        if ($filter) {
2038
            $sql .= " AND " . $filter;
2039
        }
2040
        $sql .= " ORDER BY re.description ASC";
2041
2042
        dol_syslog(get_class($this) . "::select_remises", LOG_DEBUG);
2043
        $resql = $this->db->query($sql);
2044
        if ($resql) {
2045
            print '<select id="select_' . $htmlname . '" class="flat maxwidthonsmartphone" name="' . $htmlname . '">';
2046
            $num = $this->db->num_rows($resql);
2047
2048
            $qualifiedlines = $num;
2049
2050
            $i = 0;
2051
            if ($num) {
2052
                print '<option value="0">&nbsp;</option>';
2053
                while ($i < $num) {
2054
                    $obj = $this->db->fetch_object($resql);
2055
                    $desc = dol_trunc($obj->description, 40);
2056
                    if (preg_match('/\(CREDIT_NOTE\)/', $desc)) {
2057
                        $desc = preg_replace('/\(CREDIT_NOTE\)/', $langs->trans("CreditNote"), $desc);
2058
                    }
2059
                    if (preg_match('/\(DEPOSIT\)/', $desc)) {
2060
                        $desc = preg_replace('/\(DEPOSIT\)/', $langs->trans("Deposit"), $desc);
2061
                    }
2062
                    if (preg_match('/\(EXCESS RECEIVED\)/', $desc)) {
2063
                        $desc = preg_replace('/\(EXCESS RECEIVED\)/', $langs->trans("ExcessReceived"), $desc);
2064
                    }
2065
                    if (preg_match('/\(EXCESS PAID\)/', $desc)) {
2066
                        $desc = preg_replace('/\(EXCESS PAID\)/', $langs->trans("ExcessPaid"), $desc);
2067
                    }
2068
2069
                    $selectstring = '';
2070
                    if ($selected > 0 && $selected == $obj->rowid) {
2071
                        $selectstring = ' selected';
2072
                    }
2073
2074
                    $disabled = '';
2075
                    if ($maxvalue > 0 && $obj->amount_ttc > $maxvalue) {
2076
                        $qualifiedlines--;
2077
                        $disabled = ' disabled';
2078
                    }
2079
2080
                    if (getDolGlobalString('MAIN_SHOW_FACNUMBER_IN_DISCOUNT_LIST') && !empty($obj->fk_facture_source)) {
2081
                        $tmpfac = new Facture($this->db);
2082
                        if ($tmpfac->fetch($obj->fk_facture_source) > 0) {
2083
                            $desc = $desc . ' - ' . $tmpfac->ref;
2084
                        }
2085
                    }
2086
2087
                    print '<option value="' . $obj->rowid . '"' . $selectstring . $disabled . '>' . $desc . ' (' . price($obj->amount_ht) . ' ' . $langs->trans("HT") . ' - ' . price($obj->amount_ttc) . ' ' . $langs->trans("TTC") . ')</option>';
2088
                    $i++;
2089
                }
2090
            }
2091
            print '</select>';
2092
            print ajax_combobox('select_' . $htmlname);
2093
2094
            return $qualifiedlines;
2095
        } else {
2096
            dol_print_error($this->db);
2097
            return -1;
2098
        }
2099
    }
2100
2101
2102
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2103
2104
    /**
2105
     * Return the HTML select list of users
2106
     *
2107
     * @param string $selected Id user preselected
2108
     * @param string $htmlname Field name in form
2109
     * @param int<0,1> $show_empty 0=liste sans valeur nulle, 1=ajoute valeur inconnue
2110
     * @param int[] $exclude Array list of users id to exclude
2111
     * @param int<0,1> $disabled If select list must be disabled
2112
     * @param int[]|string $include Array list of users id to include. User '' for all users or 'hierarchy' to have only supervised users or 'hierarchyme' to have supervised + me
2113
     * @param int[]|int $enableonly Array list of users id to be enabled. All other must be disabled
2114
     * @param string $force_entity '0' or Ids of environment to force
2115
     * @return    void
2116
     * @deprecated        Use select_dolusers instead
2117
     * @see select_dolusers()
2118
     */
2119
    public function select_users($selected = '', $htmlname = 'userid', $show_empty = 0, $exclude = null, $disabled = 0, $include = '', $enableonly = array(), $force_entity = '0')
2120
    {
2121
		// phpcs:enable
2122
        print $this->select_dolusers($selected, $htmlname, $show_empty, $exclude, $disabled, $include, $enableonly, $force_entity);
2123
    }
2124
2125
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2126
2127
    /**
2128
     * Return select list of users
2129
     *
2130
     * @param string|int|User   $selected       User id or user object of user preselected. If 0 or < -2, we use id of current user. If -1 or '', keep unselected (if empty is allowed)
2131
     * @param string            $htmlname       Field name in form
2132
     * @param int<0,1>|string   $show_empty     0=list with no empty value, 1=add also an empty value into list
2133
     * @param int[]|null        $exclude        Array list of users id to exclude
2134
     * @param int               $disabled       If select list must be disabled
2135
     * @param int[]|string      $include        Array list of users id to include. User '' for all users or 'hierarchy' to have only supervised users or 'hierarchyme' to have supervised + me
2136
     * @param array|string      $enableonly     Array list of users id to be enabled. If defined, it means that others will be disabled
2137
     * @param string            $force_entity   '0' or list of Ids of environment to force, separated by a coma, or 'default' = do no extend to all entities allowed to superadmin.
2138
     * @param int               $maxlength      Maximum length of string into list (0=no limit)
2139
     * @param int<-1,1>         $showstatus     0=show user status only if status is disabled, 1=always show user status into label, -1=never show user status
2140
     * @param string            $morefilter     Add more filters into sql request (Example: 'employee = 1'). This value must not come from user input.
2141
     * @param integer           $show_every     0=default list, 1=add also a value "Everybody" at beginning of list
2142
     * @param string            $enableonlytext If option $enableonlytext is set, we use this text to explain into label why record is disabled. Not used if enableonly is empty.
2143
     * @param string            $morecss        More css
2144
     * @param int<0,1>          $notdisabled    Show only active users (this will also happened whatever is this option if USER_HIDE_INACTIVE_IN_COMBOBOX is on).
2145
     * @param int<0,2>          $outputmode     0=HTML select string, 1=Array, 2=Detailed array
2146
     * @param bool              $multiple       add [] in the name of element and add 'multiple' attribute
2147
     * @param int<0,1>          $forcecombo     Force the component to be a simple combo box without ajax
2148
     * @return string|array<int,string|array{id:int,label:string,labelhtml:string,color:string,picto:string}>   HTML select string
2149
     * @see select_dolgroups()
2150
     */
2151
    public function select_dolusers($selected = '', $htmlname = 'userid', $show_empty = 0, $exclude = null, $disabled = 0, $include = '', $enableonly = '', $force_entity = '', $maxlength = 0, $showstatus = 0, $morefilter = '', $show_every = 0, $enableonlytext = '', $morecss = '', $notdisabled = 0, $outputmode = 0, $multiple = false, $forcecombo = 0)
2152
    {
2153
		// phpcs:enable
2154
        global $conf, $user, $langs, $hookmanager;
2155
        global $action;
2156
2157
        // If no preselected user defined, we take current user
2158
        if ((is_numeric($selected) && ($selected < -2 || empty($selected))) && !getDolGlobalString('SOCIETE_DISABLE_DEFAULT_SALESREPRESENTATIVE')) {
2159
            $selected = $user->id;
2160
        }
2161
2162
        if ($selected === '') {
2163
            $selected = array();
2164
        } elseif (!is_array($selected)) {
2165
            $selected = array($selected);
2166
        }
2167
2168
        $excludeUsers = null;
2169
        $includeUsers = null;
2170
2171
        // Exclude some users
2172
        if (is_array($exclude)) {
2173
            $excludeUsers = implode(",", $exclude);
2174
        }
2175
        // Include some uses
2176
        if (is_array($include)) {
2177
            $includeUsers = implode(",", $include);
2178
        } elseif ($include == 'hierarchy') {
2179
            // Build list includeUsers to have only hierarchy
2180
            $includeUsers = implode(",", $user->getAllChildIds(0));
2181
        } elseif ($include == 'hierarchyme') {
2182
            // Build list includeUsers to have only hierarchy and current user
2183
            $includeUsers = implode(",", $user->getAllChildIds(1));
2184
        }
2185
2186
        $num = 0;
2187
2188
        $out = '';
2189
        $outarray = array();
2190
        $outarray2 = array();
2191
2192
        // Do we want to show the label of entity into the combo list ?
2193
        $showlabelofentity = isModEnabled('multicompany') && !getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE') && $conf->entity == 1 && !empty($user->admin) && empty($user->entity);
2194
        $userissuperadminentityone = isModEnabled('multicompany') && $conf->entity == 1 && $user->admin && empty($user->entity);
2195
2196
        // Forge request to select users
2197
        $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";
2198
        if ($showlabelofentity) {
2199
            $sql .= ", e.label";
2200
        }
2201
        $sql .= " FROM " . $this->db->prefix() . "user as u";
2202
        if ($showlabelofentity) {
2203
            $sql .= " LEFT JOIN " . $this->db->prefix() . "entity as e ON e.rowid = u.entity";
2204
        }
2205
        // Condition here should be the same than into societe->getSalesRepresentatives().
2206
        if ($userissuperadminentityone && $force_entity != 'default') {
2207
            if (!empty($force_entity)) {
2208
                $sql .= " WHERE u.entity IN (0, " . $this->db->sanitize($force_entity) . ")";
2209
            } else {
2210
                $sql .= " WHERE u.entity IS NOT NULL";
2211
            }
2212
        } else {
2213
            if (isModEnabled('multicompany') && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2214
                $sql .= " WHERE u.rowid IN (SELECT ug.fk_user FROM " . $this->db->prefix() . "usergroup_user as ug WHERE ug.entity IN (" . getEntity('usergroup') . "))";
2215
            } else {
2216
                $sql .= " WHERE u.entity IN (" . getEntity('user') . ")";
2217
            }
2218
        }
2219
2220
        if (!empty($user->socid)) {
2221
            $sql .= " AND u.fk_soc = " . ((int) $user->socid);
2222
        }
2223
        if (is_array($exclude) && $excludeUsers) {
2224
            $sql .= " AND u.rowid NOT IN (" . $this->db->sanitize($excludeUsers) . ")";
2225
        }
2226
        if ($includeUsers) {
2227
            $sql .= " AND u.rowid IN (" . $this->db->sanitize($includeUsers) . ")";
2228
        }
2229
        if (getDolGlobalString('USER_HIDE_INACTIVE_IN_COMBOBOX') || $notdisabled) {
2230
            $sql .= " AND u.statut <> 0";
2231
        }
2232
        if (getDolGlobalString('USER_HIDE_NONEMPLOYEE_IN_COMBOBOX') || $notdisabled) {
2233
            $sql .= " AND u.employee <> 0";
2234
        }
2235
        if (getDolGlobalString('USER_HIDE_EXTERNAL_IN_COMBOBOX') || $notdisabled) {
2236
            $sql .= " AND u.fk_soc IS NULL";
2237
        }
2238
        if (!empty($morefilter)) {
2239
            $sql .= " " . $morefilter;
2240
        }
2241
2242
        //Add hook to filter on user (for example on usergroup define in custom modules)
2243
        $reshook = $hookmanager->executeHooks('addSQLWhereFilterOnSelectUsers', array(), $this, $action);
2244
        if (!empty($reshook)) {
2245
            $sql .= $hookmanager->resPrint;
2246
        }
2247
2248
        if (!getDolGlobalString('MAIN_FIRSTNAME_NAME_POSITION')) {    // MAIN_FIRSTNAME_NAME_POSITION is 0 means firstname+lastname
2249
            $sql .= " ORDER BY u.statut DESC, u.firstname ASC, u.lastname ASC";
2250
        } else {
2251
            $sql .= " ORDER BY u.statut DESC, u.lastname ASC, u.firstname ASC";
2252
        }
2253
2254
        dol_syslog(get_class($this) . "::select_dolusers", LOG_DEBUG);
2255
2256
        $resql = $this->db->query($sql);
2257
        if ($resql) {
2258
            $num = $this->db->num_rows($resql);
2259
            $i = 0;
2260
            if ($num) {
2261
                // do not use maxwidthonsmartphone by default. Set it by caller so auto size to 100% will work when not defined
2262
                $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : ' minwidth200') . '" id="' . $htmlname . '" name="' . $htmlname . ($multiple ? '[]' : '') . '" ' . ($multiple ? 'multiple' : '') . ' ' . ($disabled ? ' disabled' : '') . '>';
2263
                if ($show_empty && !$multiple) {
2264
                    $textforempty = ' ';
2265
                    if (!empty($conf->use_javascript_ajax)) {
2266
                        $textforempty = '&nbsp;'; // If we use ajaxcombo, we need &nbsp; here to avoid to have an empty element that is too small.
2267
                    }
2268
                    if (!is_numeric($show_empty)) {
2269
                        $textforempty = $show_empty;
2270
                    }
2271
                    $out .= '<option class="optiongrey" value="' . ($show_empty < 0 ? $show_empty : -1) . '"' . ((empty($selected) || in_array(-1, $selected)) ? ' selected' : '') . '>' . $textforempty . '</option>' . "\n";
2272
                }
2273
                if ($show_every) {
2274
                    $out .= '<option value="-2"' . ((in_array(-2, $selected)) ? ' selected' : '') . '>-- ' . $langs->trans("Everybody") . ' --</option>' . "\n";
2275
                }
2276
2277
                $userstatic = new User($this->db);
2278
2279
                while ($i < $num) {
2280
                    $obj = $this->db->fetch_object($resql);
2281
2282
                    $userstatic->id = $obj->rowid;
2283
                    $userstatic->lastname = $obj->lastname;
2284
                    $userstatic->firstname = $obj->firstname;
2285
                    $userstatic->photo = $obj->photo;
2286
                    $userstatic->status = $obj->status;
2287
                    $userstatic->entity = $obj->entity;
2288
                    $userstatic->admin = $obj->admin;
2289
                    $userstatic->gender = $obj->gender;
2290
2291
                    $disableline = '';
2292
                    if (is_array($enableonly) && count($enableonly) && !in_array($obj->rowid, $enableonly)) {
2293
                        $disableline = ($enableonlytext ? $enableonlytext : '1');
2294
                    }
2295
2296
                    $labeltoshow = '';
2297
                    $labeltoshowhtml = '';
2298
2299
                    // $fullNameMode is 0=Lastname+Firstname (MAIN_FIRSTNAME_NAME_POSITION=1), 1=Firstname+Lastname (MAIN_FIRSTNAME_NAME_POSITION=0)
2300
                    $fullNameMode = 0;
2301
                    if (!getDolGlobalString('MAIN_FIRSTNAME_NAME_POSITION')) {
2302
                        $fullNameMode = 1; //Firstname+lastname
2303
                    }
2304
                    $labeltoshow .= $userstatic->getFullName($langs, $fullNameMode, -1, $maxlength);
2305
                    $labeltoshowhtml .= $userstatic->getFullName($langs, $fullNameMode, -1, $maxlength);
2306
                    if (empty($obj->firstname) && empty($obj->lastname)) {
2307
                        $labeltoshow .= $obj->login;
2308
                        $labeltoshowhtml .= $obj->login;
2309
                    }
2310
2311
                    // Complete name with a more info string like: ' (info1 - info2 - ...)'
2312
                    $moreinfo = '';
2313
                    $moreinfohtml = '';
2314
                    if (getDolGlobalString('MAIN_SHOW_LOGIN')) {
2315
                        $moreinfo .= ($moreinfo ? ' - ' : ' (');
2316
                        $moreinfohtml .= ($moreinfohtml ? ' - ' : ' <span class="opacitymedium">(');
2317
                        $moreinfo .= $obj->login;
2318
                        $moreinfohtml .= $obj->login;
2319
                    }
2320
                    if ($showstatus >= 0) {
2321
                        if ($obj->status == 1 && $showstatus == 1) {
2322
                            $moreinfo .= ($moreinfo ? ' - ' : ' (') . $langs->trans('Enabled');
2323
                            $moreinfohtml .= ($moreinfohtml ? ' - ' : ' <span class="opacitymedium">(') . $langs->trans('Enabled');
2324
                        }
2325
                        if ($obj->status == 0 && $showstatus == 1) {
2326
                            $moreinfo .= ($moreinfo ? ' - ' : ' (') . $langs->trans('Disabled');
2327
                            $moreinfohtml .= ($moreinfohtml ? ' - ' : ' <span class="opacitymedium">(') . $langs->trans('Disabled');
2328
                        }
2329
                    }
2330
                    if ($showlabelofentity) {
2331
                        if (empty($obj->entity)) {
2332
                            $moreinfo .= ($moreinfo ? ' - ' : ' (') . $langs->trans("AllEntities");
2333
                            $moreinfohtml .= ($moreinfohtml ? ' - ' : ' <span class="opacitymedium">(') . $langs->trans("AllEntities");
2334
                        } else {
2335
                            if ($obj->entity != $conf->entity) {
2336
                                $moreinfo .= ($moreinfo ? ' - ' : ' (') . ($obj->label ? $obj->label : $langs->trans("EntityNameNotDefined"));
2337
                                $moreinfohtml .= ($moreinfohtml ? ' - ' : ' <span class="opacitymedium">(') . ($obj->label ? $obj->label : $langs->trans("EntityNameNotDefined"));
2338
                            }
2339
                        }
2340
                    }
2341
                    $moreinfo .= (!empty($moreinfo) ? ')' : '');
2342
                    $moreinfohtml .= (!empty($moreinfohtml) ? ')</span>' : '');
2343
                    if (!empty($disableline) && $disableline != '1') {
2344
                        // Add text from $enableonlytext parameter
2345
                        $moreinfo .= ' - ' . $disableline;
2346
                        $moreinfohtml .= ' - ' . $disableline;
2347
                    }
2348
                    $labeltoshow .= $moreinfo;
2349
                    $labeltoshowhtml .= $moreinfohtml;
2350
2351
                    $out .= '<option value="' . $obj->rowid . '"';
2352
                    if (!empty($disableline)) {
2353
                        $out .= ' disabled';
2354
                    }
2355
                    if ((!empty($selected[0]) && is_object($selected[0])) ? $selected[0]->id == $obj->rowid : in_array($obj->rowid, $selected)) {
2356
                        $out .= ' selected';
2357
                    }
2358
                    $out .= ' data-html="';
2359
2360
                    $outhtml = $userstatic->getNomUrl(-3, '', 0, 1, 24, 1, 'login', '', 1) . ' ';
2361
                    if ($showstatus >= 0 && $obj->status == 0) {
2362
                        $outhtml .= '<strike class="opacitymediumxxx">';
2363
                    }
2364
                    $outhtml .= $labeltoshowhtml;
2365
                    if ($showstatus >= 0 && $obj->status == 0) {
2366
                        $outhtml .= '</strike>';
2367
                    }
2368
                    $labeltoshowhtml = $outhtml;
2369
2370
                    $out .= dol_escape_htmltag($outhtml);
2371
                    $out .= '">';
2372
                    $out .= $labeltoshow;
2373
                    $out .= '</option>';
2374
2375
                    $outarray[$userstatic->id] = $userstatic->getFullName($langs, $fullNameMode, -1, $maxlength) . $moreinfo;
2376
                    $outarray2[$userstatic->id] = array(
2377
                        'id' => $userstatic->id,
2378
                        'label' => $labeltoshow,
2379
                        'labelhtml' => $labeltoshowhtml,
2380
                        'color' => '',
2381
                        'picto' => ''
2382
                    );
2383
2384
                    $i++;
2385
                }
2386
            } else {
2387
                $out .= '<select class="flat" id="' . $htmlname . '" name="' . $htmlname . '" disabled>';
2388
                $out .= '<option value="">' . $langs->trans("None") . '</option>';
2389
            }
2390
            $out .= '</select>';
2391
2392
            if ($num && !$forcecombo) {
2393
                // Enhance with select2
2394
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
2395
                $out .= ajax_combobox($htmlname);
2396
            }
2397
        } else {
2398
            dol_print_error($this->db);
2399
        }
2400
2401
        $this->num = $num;
2402
2403
        if ($outputmode == 2) {
2404
            return $outarray2;
2405
        } elseif ($outputmode) {
2406
            return $outarray;
2407
        }
2408
2409
        return $out;
2410
    }
2411
2412
2413
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2414
    /**
2415
     * Return select list of users. Selected users are stored into session.
2416
     * List of users are provided into $_SESSION['assignedtouser'].
2417
     *
2418
     * @param string    $action             Value for $action
2419
     * @param string    $htmlname           Field name in form
2420
     * @param int<0,1>  $show_empty         0=list without the empty value, 1=add empty value
2421
     * @param int[]     $exclude            Array list of users id to exclude
2422
     * @param int<0,1>  $disabled           If select list must be disabled
2423
     * @param int[]|string  $include            Array list of users id to include or 'hierarchy' to have only supervised users
2424
     * @param int[]|int $enableonly         Array list of users id to be enabled. All other must be disabled
2425
     * @param string    $force_entity       '0' or Ids of environment to force
2426
     * @param int       $maxlength          Maximum length of string into list (0=no limit)
2427
     * @param int<0,1>  $showstatus         0=show user status only if status is disabled, 1=always show user status into label, -1=never show user status
2428
     * @param string    $morefilter         Add more filters into sql request
2429
     * @param int       $showproperties     Show properties of each attendees
2430
     * @param int[]     $listofuserid       Array with properties of each user
2431
     * @param int[]     $listofcontactid    Array with properties of each contact
2432
     * @param int[]     $listofotherid      Array with properties of each other contact
2433
     * @return    string                    HTML select string
2434
     * @see select_dolgroups()
2435
     */
2436
    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())
2437
    {
2438
		// phpcs:enable
2439
        global $langs;
2440
2441
        $userstatic = new User($this->db);
2442
        $out = '';
2443
2444
        if (!empty($_SESSION['assignedtouser'])) {
2445
            $assignedtouser = json_decode($_SESSION['assignedtouser'], true);
2446
            if (!is_array($assignedtouser)) {
2447
                $assignedtouser = array();
2448
            }
2449
        } else {
2450
            $assignedtouser = array();
2451
        }
2452
        $nbassignetouser = count($assignedtouser);
2453
2454
        //if ($nbassignetouser && $action != 'view') $out .= '<br>';
2455
        if ($nbassignetouser) {
2456
            $out .= '<ul class="attendees">';
2457
        }
2458
        $i = 0;
2459
        $ownerid = 0;
2460
        foreach ($assignedtouser as $key => $value) {
2461
            if ($value['id'] == $ownerid) {
2462
                continue;
2463
            }
2464
2465
            $out .= '<li>';
2466
            $userstatic->fetch($value['id']);
2467
            $out .= $userstatic->getNomUrl(-1);
2468
            if ($i == 0) {
2469
                $ownerid = $value['id'];
2470
                $out .= ' (' . $langs->trans("Owner") . ')';
2471
            }
2472
            if ($nbassignetouser > 1 && $action != 'view') {
2473
                $out .= ' <input type="image" style="border: 0px;" src="' . img_picto($langs->trans("Remove"), 'delete', '', 0, 1) . '" value="' . $userstatic->id . '" class="removedassigned reposition" id="removedassigned_' . $userstatic->id . '" name="removedassigned_' . $userstatic->id . '">';
2474
            }
2475
            // Show my availability
2476
            if ($showproperties) {
2477
                if ($ownerid == $value['id'] && is_array($listofuserid) && count($listofuserid) && in_array($ownerid, array_keys($listofuserid))) {
2478
                    $out .= '<div class="myavailability inline-block">';
2479
                    $out .= '<span class="hideonsmartphone">&nbsp;-&nbsp;<span class="opacitymedium">' . $langs->trans("Availability") . ':</span>  </span><input id="transparency" class="paddingrightonly" ' . ($action == 'view' ? 'disabled' : '') . ' type="checkbox" name="transparency"' . ($listofuserid[$ownerid]['transparency'] ? ' checked' : '') . '><label for="transparency">' . $langs->trans("Busy") . '</label>';
2480
                    $out .= '</div>';
2481
                }
2482
            }
2483
            //$out.=' '.($value['mandatory']?$langs->trans("Mandatory"):$langs->trans("Optional"));
2484
            //$out.=' '.($value['transparency']?$langs->trans("Busy"):$langs->trans("NotBusy"));
2485
2486
            $out .= '</li>';
2487
            $i++;
2488
        }
2489
        if ($nbassignetouser) {
2490
            $out .= '</ul>';
2491
        }
2492
2493
        // Method with no ajax
2494
        if ($action != 'view') {
2495
            $out .= '<input type="hidden" class="removedassignedhidden" name="removedassigned" value="">';
2496
            $out .= '<script nonce="' . getNonce() . '" type="text/javascript">jQuery(document).ready(function () {';
2497
            $out .= 'jQuery(".removedassigned").click(function() { jQuery(".removedassignedhidden").val(jQuery(this).val()); });';
2498
            $out .= 'jQuery(".assignedtouser").change(function() { console.log(jQuery(".assignedtouser option:selected").val());';
2499
            $out .= ' if (jQuery(".assignedtouser option:selected").val() > 0) { jQuery("#' . $action . 'assignedtouser").attr("disabled", false); }';
2500
            $out .= ' else { jQuery("#' . $action . 'assignedtouser").attr("disabled", true); }';
2501
            $out .= '});';
2502
            $out .= '})</script>';
2503
            $out .= $this->select_dolusers('', $htmlname, $show_empty, $exclude, $disabled, $include, $enableonly, $force_entity, $maxlength, $showstatus, $morefilter);
2504
            $out .= ' <input type="submit" disabled class="button valignmiddle smallpaddingimp reposition" id="' . $action . 'assignedtouser" name="' . $action . 'assignedtouser" value="' . dol_escape_htmltag($langs->trans("Add")) . '">';
2505
            $out .= '<br>';
2506
        }
2507
2508
        return $out;
2509
    }
2510
2511
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2512
    /**
2513
     * Return select list of resources. Selected resources are stored into session.
2514
     * List of resources are provided into $_SESSION['assignedtoresource'].
2515
     *
2516
     * @param string    $action             Value for $action
2517
     * @param string    $htmlname           Field name in form
2518
     * @param int       $show_empty         0=list without the empty value, 1=add empty value
2519
     * @param int[]     $exclude            Array list of users id to exclude
2520
     * @param int<0,1>  $disabled           If select list must be disabled
2521
     * @param int[]|string  $include        Array list of users id to include or 'hierarchy' to have only supervised users
2522
     * @param int[]     $enableonly         Array list of users id to be enabled. All other must be disabled
2523
     * @param string    $force_entity       '0' or Ids of environment to force
2524
     * @param int       $maxlength          Maximum length of string into list (0=no limit)
2525
     * @param int<-1,1> $showstatus         0=show user status only if status is disabled, 1=always show user status into label, -1=never show user status
2526
     * @param string    $morefilter         Add more filters into sql request
2527
     * @param int       $showproperties     Show properties of each attendees
2528
     * @param array<int,array{transparency:bool|int<0,1>}> $listofresourceid    Array with properties of each resource
2529
     * @return    string                    HTML select string
2530
     */
2531
    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())
2532
    {
2533
		// phpcs:enable
2534
        global $langs;
2535
2536
        require_once constant('DOL_DOCUMENT_ROOT') . '/resource/class/html.formresource.class.php';
2537
        require_once constant('DOL_DOCUMENT_ROOT') . '/resource/class/dolresource.class.php';
2538
        $formresources = new FormResource($this->db);
2539
        $resourcestatic = new Dolresource($this->db);
2540
2541
        $out = '';
2542
        if (!empty($_SESSION['assignedtoresource'])) {
2543
            $assignedtoresource = json_decode($_SESSION['assignedtoresource'], true);
2544
            if (!is_array($assignedtoresource)) {
2545
                $assignedtoresource = array();
2546
            }
2547
        } else {
2548
            $assignedtoresource = array();
2549
        }
2550
        $nbassignetoresource = count($assignedtoresource);
2551
2552
        //if ($nbassignetoresource && $action != 'view') $out .= '<br>';
2553
        if ($nbassignetoresource) {
2554
            $out .= '<ul class="attendees">';
2555
        }
2556
        $i = 0;
2557
2558
        foreach ($assignedtoresource as $key => $value) {
2559
            $out .= '<li>';
2560
            $resourcestatic->fetch($value['id']);
2561
            $out .= $resourcestatic->getNomUrl(-1);
2562
            if ($nbassignetoresource > 1 && $action != 'view') {
2563
                $out .= ' <input type="image" style="border: 0px;" src="' . img_picto($langs->trans("Remove"), 'delete', '', 0, 1) . '" value="' . $resourcestatic->id . '" class="removedassigned reposition" id="removedassignedresource_' . $resourcestatic->id . '" name="removedassignedresource_' . $resourcestatic->id . '">';
2564
            }
2565
            // Show my availability
2566
            if ($showproperties) {
2567
                if (is_array($listofresourceid) && count($listofresourceid)) {
2568
                    $out .= '<div class="myavailability inline-block">';
2569
                    $out .= '<span class="hideonsmartphone">&nbsp;-&nbsp;<span class="opacitymedium">' . $langs->trans("Availability") . ':</span>  </span><input id="transparencyresource" class="paddingrightonly" ' . ($action == 'view' ? 'disabled' : '') . ' type="checkbox" name="transparency"' . ($listofresourceid[$value['id']]['transparency'] ? ' checked' : '') . '><label for="transparency">' . $langs->trans("Busy") . '</label>';
2570
                    $out .= '</div>';
2571
                }
2572
            }
2573
            //$out.=' '.($value['mandatory']?$langs->trans("Mandatory"):$langs->trans("Optional"));
2574
            //$out.=' '.($value['transparency']?$langs->trans("Busy"):$langs->trans("NotBusy"));
2575
2576
            $out .= '</li>';
2577
            $i++;
2578
        }
2579
        if ($nbassignetoresource) {
2580
            $out .= '</ul>';
2581
        }
2582
2583
        // Method with no ajax
2584
        if ($action != 'view') {
2585
            $out .= '<input type="hidden" class="removedassignedhidden" name="removedassignedresource" value="">';
2586
            $out .= '<script nonce="' . getNonce() . '" type="text/javascript">jQuery(document).ready(function () {';
2587
            $out .= 'jQuery(".removedassignedresource").click(function() { jQuery(".removedassignedresourcehidden").val(jQuery(this).val()); });';
2588
            $out .= 'jQuery(".assignedtoresource").change(function() { console.log(jQuery(".assignedtoresource option:selected").val());';
2589
            $out .= ' if (jQuery(".assignedtoresource option:selected").val() > 0) { jQuery("#' . $action . 'assignedtoresource").attr("disabled", false); }';
2590
            $out .= ' else { jQuery("#' . $action . 'assignedtoresource").attr("disabled", true); }';
2591
            $out .= '});';
2592
            $out .= '})</script>';
2593
2594
            $events = array();
2595
            $out .= img_picto('', 'resource', 'class="pictofixedwidth"');
2596
            $out .= $formresources->select_resource_list(0, $htmlname, [], 1, 1, 0, $events, array(), 2, 0);
2597
            //$out .= $this->select_dolusers('', $htmlname, $show_empty, $exclude, $disabled, $include, $enableonly, $force_entity, $maxlength, $showstatus, $morefilter);
2598
            $out .= ' <input type="submit" disabled class="button valignmiddle smallpaddingimp reposition" id="' . $action . 'assignedtoresource" name="' . $action . 'assignedtoresource" value="' . dol_escape_htmltag($langs->trans("Add")) . '">';
2599
            $out .= '<br>';
2600
        }
2601
2602
        return $out;
2603
    }
2604
2605
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2606
2607
    /**
2608
     *  Return list of products for customer.
2609
     *  Use Ajax if Ajax activated or go to select_produits_list
2610
     *
2611
     *  @param      int         $selected               Preselected products
2612
     *  @param      string      $htmlname               Name of HTML select field (must be unique in page).
2613
     *  @param      int|string  $filtertype             Filter on product type (''=nofilter, 0=product, 1=service)
2614
     *  @param      int         $limit                  Limit on number of returned lines
2615
     *  @param      int         $price_level            Level of price to show
2616
     *  @param      int         $status                 Sell status: -1=No filter on sell status, 0=Products not on sell, 1=Products on sell
2617
     *  @param      int         $finished               2=all, 1=finished, 0=raw material
2618
     *  @param      string      $selected_input_value   Value of preselected input text (for use with ajax)
2619
     *  @param      int         $hidelabel              Hide label (0=no, 1=yes, 2=show search icon (before) and placeholder, 3 search icon after)
2620
     *  @param      array<string,string|string[]>   $ajaxoptions            Options for ajax_autocompleter
2621
     *  @param      int         $socid                  Thirdparty Id (to get also price dedicated to this customer)
2622
     *  @param      string|int<0,1> $showempty          '' to not show empty line. Translation key to show an empty line. '1' show empty line with no text.
2623
     *  @param      int         $forcecombo             Force to use combo box
2624
     *  @param      string      $morecss                Add more css on select
2625
     *  @param      int<0,1>    $hidepriceinlabel       1=Hide prices in label
2626
     *  @param      string      $warehouseStatus        Warehouse status filter to count the quantity in stock. Following comma separated filter options can be used
2627
     *                                                  'warehouseopen' = count products from open warehouses,
2628
     *                                                  'warehouseclosed' = count products from closed warehouses,
2629
     *                                                  'warehouseinternal' = count products from warehouses for internal correct/transfer only
2630
     *  @param      ?mixed[]    $selected_combinations  Selected combinations. Format: array([attrid] => attrval, [...])
2631
     *  @param      int<0,1>    $nooutput               No print if 1, return the output into a string
2632
     *  @param      int<-1,1>   $status_purchase        Purchase status: -1=No filter on purchase status, 0=Products not on purchase, 1=Products on purchase
2633
     *  @return     void|string
2634
     */
2635
    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)
2636
    {
2637
		// phpcs:enable
2638
        global $langs, $conf;
2639
2640
        $out = '';
2641
2642
        // check parameters
2643
        $price_level = (!empty($price_level) ? $price_level : 0);
2644
        if (is_null($ajaxoptions)) {
2645
            $ajaxoptions = array();
2646
        }
2647
2648
        if (strval($filtertype) === '' && (isModEnabled("product") || isModEnabled("service"))) {
2649
            if (isModEnabled("product") && !isModEnabled('service')) {
2650
                $filtertype = '0';
2651
            } elseif (!isModEnabled('product') && isModEnabled("service")) {
2652
                $filtertype = '1';
2653
            }
2654
        }
2655
2656
        if (!empty($conf->use_javascript_ajax) && getDolGlobalString('PRODUIT_USE_SEARCH_TO_SELECT')) {
2657
            $placeholder = '';
2658
2659
            if ($selected && empty($selected_input_value)) {
2660
                require_once constant('DOL_DOCUMENT_ROOT') . '/product/class/product.class.php';
2661
                $producttmpselect = new Product($this->db);
2662
                $producttmpselect->fetch($selected);
2663
                $selected_input_value = $producttmpselect->ref;
2664
                unset($producttmpselect);
2665
            }
2666
            // handle case where product or service module is disabled + no filter specified
2667
            if ($filtertype == '') {
2668
                if (!isModEnabled('product')) { // when product module is disabled, show services only
2669
                    $filtertype = 1;
2670
                } elseif (!isModEnabled('service')) { // when service module is disabled, show products only
2671
                    $filtertype = 0;
2672
                }
2673
            }
2674
            // mode=1 means customers products
2675
            $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;
2676
            $out .= ajax_autocompleter($selected, $htmlname, constant('BASE_URL') . '/product/ajax/products.php', $urloption, $conf->global->PRODUIT_USE_SEARCH_TO_SELECT, 1, $ajaxoptions);
2677
2678
            if (isModEnabled('variants') && is_array($selected_combinations)) {
2679
                // Code to automatically insert with javascript the select of attributes under the select of product
2680
                // when a parent of variant has been selected.
2681
                $out .= '
2682
				<!-- script to auto show attributes select tags if a variant was selected -->
2683
				<script nonce="' . getNonce() . '">
2684
					// auto show attributes fields
2685
					selected = ' . json_encode($selected_combinations) . ';
2686
					combvalues = {};
2687
2688
					jQuery(document).ready(function () {
2689
2690
						jQuery("input[name=\'prod_entry_mode\']").change(function () {
2691
							if (jQuery(this).val() == \'free\') {
2692
								jQuery(\'div#attributes_box\').empty();
2693
							}
2694
						});
2695
2696
						jQuery("input#' . $htmlname . '").change(function () {
2697
2698
							if (!jQuery(this).val()) {
2699
								jQuery(\'div#attributes_box\').empty();
2700
								return;
2701
							}
2702
2703
							console.log("A change has started. We get variants fields to inject html select");
2704
2705
							jQuery.getJSON("' . constant('BASE_URL') . '/variants/ajax/getCombinations.php", {
2706
								id: jQuery(this).val()
2707
							}, function (data) {
2708
								jQuery(\'div#attributes_box\').empty();
2709
2710
								jQuery.each(data, function (key, val) {
2711
2712
									combvalues[val.id] = val.values;
2713
2714
									var span = jQuery(document.createElement(\'div\')).css({
2715
										\'display\': \'table-row\'
2716
									});
2717
2718
									span.append(
2719
										jQuery(document.createElement(\'div\')).text(val.label).css({
2720
											\'font-weight\': \'bold\',
2721
											\'display\': \'table-cell\'
2722
										})
2723
									);
2724
2725
									var html = jQuery(document.createElement(\'select\')).attr(\'name\', \'combinations[\' + val.id + \']\').css({
2726
										\'margin-left\': \'15px\',
2727
										\'white-space\': \'pre\'
2728
									}).append(
2729
										jQuery(document.createElement(\'option\')).val(\'\')
2730
									);
2731
2732
									jQuery.each(combvalues[val.id], function (key, val) {
2733
										var tag = jQuery(document.createElement(\'option\')).val(val.id).html(val.value);
2734
2735
										if (selected[val.fk_product_attribute] == val.id) {
2736
											tag.attr(\'selected\', \'selected\');
2737
										}
2738
2739
										html.append(tag);
2740
									});
2741
2742
									span.append(html);
2743
									jQuery(\'div#attributes_box\').append(span);
2744
								});
2745
							})
2746
						});
2747
2748
						' . ($selected ? 'jQuery("input#' . $htmlname . '").change();' : '') . '
2749
					});
2750
				</script>
2751
                ';
2752
            }
2753
2754
            if (empty($hidelabel)) {
2755
                $out .= $langs->trans("RefOrLabel") . ' : ';
2756
            } elseif ($hidelabel > 1) {
2757
                $placeholder = ' placeholder="' . $langs->trans("RefOrLabel") . '"';
2758
                if ($hidelabel == 2) {
2759
                    $out .= img_picto($langs->trans("Search"), 'search');
2760
                }
2761
            }
2762
            $out .= '<input type="text" class="minwidth100' . ($morecss ? ' ' . $morecss : '') . '" name="search_' . $htmlname . '" id="search_' . $htmlname . '" value="' . $selected_input_value . '"' . $placeholder . ' ' . (getDolGlobalString('PRODUCT_SEARCH_AUTOFOCUS') ? 'autofocus' : '') . ' />';
2763
            if ($hidelabel == 3) {
2764
                $out .= img_picto($langs->trans("Search"), 'search');
2765
            }
2766
        } else {
2767
            $out .= $this->select_produits_list($selected, $htmlname, $filtertype, $limit, $price_level, '', $status, $finished, 0, $socid, $showempty, $forcecombo, $morecss, $hidepriceinlabel, $warehouseStatus, $status_purchase);
2768
        }
2769
2770
        if (empty($nooutput)) {
2771
            print $out;
2772
        } else {
2773
            return $out;
2774
        }
2775
    }
2776
2777
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2778
2779
    /**
2780
     *  Return list of BOM for customer in Ajax if Ajax activated or go to select_produits_list
2781
     *
2782
     * @param string    $selected Preselected BOM id
2783
     * @param string    $htmlname Name of HTML select field (must be unique in page).
2784
     * @param int       $limit Limit on number of returned lines
2785
     * @param int       $status Sell status -1=Return all bom, 0=Draft BOM, 1=Validated BOM
2786
     * @param int       $type type of the BOM (-1=Return all BOM, 0=Return disassemble BOM, 1=Return manufacturing BOM)
2787
     * @param string|int<0,1>   $showempty  '' to not show empty line. Translation key to show an empty line. '1' show empty line with no text.
2788
     * @param string    $morecss Add more css on select
2789
     * @param string    $nooutput No print, return the output into a string
2790
     * @param int       $forcecombo Force to use combo box
2791
     * @param string[]  $TProducts Add filter on a defined product
2792
     * @return void|string
2793
     */
2794
    public function select_bom($selected = '', $htmlname = 'bom_id', $limit = 0, $status = 1, $type = 0, $showempty = '1', $morecss = '', $nooutput = '', $forcecombo = 0, $TProducts = [])
2795
    {
2796
		// phpcs:enable
2797
        global $db;
2798
2799
        require_once constant('DOL_DOCUMENT_ROOT') . '/product/class/product.class.php';
2800
2801
        $error = 0;
2802
        $out = '';
2803
2804
        if (!$forcecombo) {
2805
            include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
2806
            $events = array();
2807
            $out .= ajax_combobox($htmlname, $events, getDolGlobalInt("PRODUIT_USE_SEARCH_TO_SELECT"));
2808
        }
2809
2810
        $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" id="' . $htmlname . '">';
2811
2812
        $sql = 'SELECT b.rowid, b.ref, b.label, b.fk_product';
2813
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'bom_bom as b';
2814
        $sql .= ' WHERE b.entity IN (' . getEntity('bom') . ')';
2815
        if (!empty($status)) {
2816
            $sql .= ' AND status = ' . (int) $status;
2817
        }
2818
        if (!empty($type)) {
2819
            $sql .= ' AND bomtype = ' . (int) $type;
2820
        }
2821
        if (!empty($TProducts)) {
2822
            $sql .= ' AND fk_product IN (' . $this->db->sanitize(implode(',', $TProducts)) . ')';
2823
        }
2824
        if (!empty($limit)) {
2825
            $sql .= ' LIMIT ' . (int) $limit;
2826
        }
2827
        $resql = $db->query($sql);
2828
        if ($resql) {
2829
            if ($showempty) {
2830
                $out .= '<option value="-1"';
2831
                if (empty($selected)) {
2832
                    $out .= ' selected';
2833
                }
2834
                $out .= '>&nbsp;</option>';
2835
            }
2836
            while ($obj = $db->fetch_object($resql)) {
2837
                $product = new Product($db);
2838
                $res = $product->fetch($obj->fk_product);
2839
                $out .= '<option value="' . $obj->rowid . '"';
2840
                if ($obj->rowid == $selected) {
2841
                    $out .= 'selected';
2842
                }
2843
                $out .= '>' . $obj->ref . ' - ' . $product->label . ' - ' . $obj->label . '</option>';
2844
            }
2845
        } else {
2846
            $error++;
2847
            dol_print_error($db);
2848
        }
2849
        $out .= '</select>';
2850
        if (empty($nooutput)) {
2851
            print $out;
2852
        } else {
2853
            return $out;
2854
        }
2855
    }
2856
2857
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2858
2859
    /**
2860
     * Return list of products for a customer.
2861
     * Called by select_produits.
2862
     *
2863
     * @param   int         $selected               Preselected product
2864
     * @param   string      $htmlname               Name of select html
2865
     * @param   string      $filtertype             Filter on product type (''=nofilter, 0=product, 1=service)
2866
     * @param   int         $limit                  Limit on number of returned lines
2867
     * @param   int         $price_level            Level of price to show
2868
     * @param   string      $filterkey              Filter on product
2869
     * @param   int         $status                 -1=Return all products, 0=Products not on sell, 1=Products on sell
2870
     * @param   int         $finished               Filter on finished field: 2=No filter
2871
     * @param   int         $outputmode             0=HTML select string, 1=Array
2872
     * @param   int         $socid                  Thirdparty Id (to get also price dedicated to this customer)
2873
     * @param   string|int<0,1> $showempty          '' to not show empty line. Translation key to show an empty line. '1' show empty line with no text.
2874
     * @param   int         $forcecombo             Force to use combo box
2875
     * @param   string      $morecss                Add more css on select
2876
     * @param   int         $hidepriceinlabel       1=Hide prices in label
2877
     * @param   string      $warehouseStatus        Warehouse status filter to group/count stock. Following comma separated filter options can be used.
2878
     *                                              'warehouseopen' = count products from open warehouses,
2879
     *                                              'warehouseclosed' = count products from closed warehouses,
2880
     *                                              'warehouseinternal' = count products from warehouses for internal correct/transfer only
2881
     * @param   int         $status_purchase        Purchase status -1=Return all products, 0=Products not on purchase, 1=Products on purchase
2882
     * @return  array|string                        Array of keys for json
2883
     */
2884
    public function select_produits_list($selected = 0, $htmlname = 'productid', $filtertype = '', $limit = 20, $price_level = 0, $filterkey = '', $status = 1, $finished = 2, $outputmode = 0, $socid = 0, $showempty = '1', $forcecombo = 0, $morecss = 'maxwidth500', $hidepriceinlabel = 0, $warehouseStatus = '', $status_purchase = -1)
2885
    {
2886
		// phpcs:enable
2887
        global $langs;
2888
        global $hookmanager;
2889
2890
        $out = '';
2891
        $outarray = array();
2892
2893
        // Units
2894
        if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
2895
            $langs->load('other');
2896
        }
2897
2898
        $warehouseStatusArray = array();
2899
        if (!empty($warehouseStatus)) {
2900
            require_once constant('DOL_DOCUMENT_ROOT') . '/product/stock/class/entrepot.class.php';
2901
            if (preg_match('/warehouseclosed/', $warehouseStatus)) {
2902
                $warehouseStatusArray[] = Entrepot::STATUS_CLOSED;
2903
            }
2904
            if (preg_match('/warehouseopen/', $warehouseStatus)) {
2905
                $warehouseStatusArray[] = Entrepot::STATUS_OPEN_ALL;
2906
            }
2907
            if (preg_match('/warehouseinternal/', $warehouseStatus)) {
2908
                $warehouseStatusArray[] = Entrepot::STATUS_OPEN_INTERNAL;
2909
            }
2910
        }
2911
2912
        $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";
2913
        if (count($warehouseStatusArray)) {
2914
            $selectFieldsGrouped = ", sum(" . $this->db->ifsql("e.statut IS NULL", "0", "ps.reel") . ") as stock"; // e.statut is null if there is no record in stock
2915
        } else {
2916
            $selectFieldsGrouped = ", " . $this->db->ifsql("p.stock IS NULL", 0, "p.stock") . " AS stock";
2917
        }
2918
2919
        $sql = "SELECT ";
2920
2921
        // Add select from hooks
2922
        $parameters = array();
2923
        $reshook = $hookmanager->executeHooks('selectProductsListSelect', $parameters); // Note that $action and $object may have been modified by hook
2924
        if (empty($reshook)) {
2925
            $sql .= $selectFields . $selectFieldsGrouped . $hookmanager->resPrint;
2926
        } else {
2927
            $sql .= $hookmanager->resPrint;
2928
        }
2929
2930
        if (getDolGlobalString('PRODUCT_SORT_BY_CATEGORY')) {
2931
            //Product category
2932
            $sql .= ", (SELECT " . $this->db->prefix() . "categorie_product.fk_categorie
2933
						FROM " . $this->db->prefix() . "categorie_product
2934
						WHERE " . $this->db->prefix() . "categorie_product.fk_product=p.rowid
2935
						LIMIT 1
2936
				) AS categorie_product_id ";
2937
        }
2938
2939
        //Price by customer
2940
        if (getDolGlobalString('PRODUIT_CUSTOMER_PRICES') && !empty($socid)) {
2941
            $sql .= ', pcp.rowid as idprodcustprice, pcp.price as custprice, pcp.price_ttc as custprice_ttc,';
2942
            $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';
2943
            $selectFields .= ", idprodcustprice, custprice, custprice_ttc, custprice_base_type, custtva_tx, custdefault_vat_code, custref";
2944
        }
2945
        // Units
2946
        if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
2947
            $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";
2948
            $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';
2949
        }
2950
2951
        // Multilang : we add translation
2952
        if (getDolGlobalInt('MAIN_MULTILANGS')) {
2953
            $sql .= ", pl.label as label_translated";
2954
            $sql .= ", pl.description as description_translated";
2955
            $selectFields .= ", label_translated";
2956
            $selectFields .= ", description_translated";
2957
        }
2958
        // Price by quantity
2959
        if (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES')) {
2960
            $sql .= ", (SELECT pp.rowid FROM " . $this->db->prefix() . "product_price as pp WHERE pp.fk_product = p.rowid";
2961
            if ($price_level >= 1 && getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES')) {
2962
                $sql .= " AND price_level = " . ((int) $price_level);
2963
            }
2964
            $sql .= " ORDER BY date_price";
2965
            $sql .= " DESC LIMIT 1) as price_rowid";
2966
            $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
2967
            if ($price_level >= 1 && getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES')) {
2968
                $sql .= " AND price_level = " . ((int) $price_level);
2969
            }
2970
            $sql .= " ORDER BY date_price";
2971
            $sql .= " DESC LIMIT 1) as price_by_qty";
2972
            $selectFields .= ", price_rowid, price_by_qty";
2973
        }
2974
2975
        $sql .= " FROM " . $this->db->prefix() . "product as p";
2976
2977
        if (getDolGlobalString('MAIN_SEARCH_PRODUCT_FORCE_INDEX')) {
2978
            $sql .= " USE INDEX (" . $this->db->sanitize(getDolGlobalString('MAIN_PRODUCT_FORCE_INDEX')) . ")";
2979
        }
2980
2981
        // Add from (left join) from hooks
2982
        $parameters = array();
2983
        $reshook = $hookmanager->executeHooks('selectProductsListFrom', $parameters); // Note that $action and $object may have been modified by hook
2984
        $sql .= $hookmanager->resPrint;
2985
2986
        if (count($warehouseStatusArray)) {
2987
            $sql .= " LEFT JOIN " . $this->db->prefix() . "product_stock as ps on ps.fk_product = p.rowid";
2988
            $sql .= " LEFT JOIN " . $this->db->prefix() . "entrepot as e on ps.fk_entrepot = e.rowid AND e.entity IN (" . getEntity('stock') . ")";
2989
            $sql .= ' AND e.statut IN (' . $this->db->sanitize($this->db->escape(implode(',', $warehouseStatusArray))) . ')'; // Return line if product is inside the selected stock. If not, an empty line will be returned so we will count 0.
2990
        }
2991
2992
        // include search in supplier ref
2993
        if (getDolGlobalString('MAIN_SEARCH_PRODUCT_BY_FOURN_REF')) {
2994
            $sql .= " LEFT JOIN " . $this->db->prefix() . "product_fournisseur_price as pfp ON p.rowid = pfp.fk_product";
2995
        }
2996
2997
        //Price by customer
2998
        if (getDolGlobalString('PRODUIT_CUSTOMER_PRICES') && !empty($socid)) {
2999
            $sql .= " LEFT JOIN  " . $this->db->prefix() . "product_customer_price as pcp ON pcp.fk_soc=" . ((int) $socid) . " AND pcp.fk_product=p.rowid";
3000
        }
3001
        // Units
3002
        if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
3003
            $sql .= " LEFT JOIN " . $this->db->prefix() . "c_units u ON u.rowid = p.fk_unit";
3004
        }
3005
        // Multilang : we add translation
3006
        if (getDolGlobalInt('MAIN_MULTILANGS')) {
3007
            $sql .= " LEFT JOIN " . $this->db->prefix() . "product_lang as pl ON pl.fk_product = p.rowid ";
3008
            if (getDolGlobalString('PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE') && !empty($socid)) {
3009
                require_once constant('DOL_DOCUMENT_ROOT') . '/societe/class/societe.class.php';
3010
                $soc = new Societe($this->db);
3011
                $result = $soc->fetch($socid);
3012
                if ($result > 0 && !empty($soc->default_lang)) {
3013
                    $sql .= " AND pl.lang = '" . $this->db->escape($soc->default_lang) . "'";
3014
                } else {
3015
                    $sql .= " AND pl.lang = '" . $this->db->escape($langs->getDefaultLang()) . "'";
3016
                }
3017
            } else {
3018
                $sql .= " AND pl.lang = '" . $this->db->escape($langs->getDefaultLang()) . "'";
3019
            }
3020
        }
3021
3022
        if (getDolGlobalString('PRODUIT_ATTRIBUTES_HIDECHILD')) {
3023
            $sql .= " LEFT JOIN " . $this->db->prefix() . "product_attribute_combination pac ON pac.fk_product_child = p.rowid";
3024
        }
3025
3026
        $sql .= ' WHERE p.entity IN (' . getEntity('product') . ')';
3027
3028
        if (getDolGlobalString('PRODUIT_ATTRIBUTES_HIDECHILD')) {
3029
            $sql .= " AND pac.rowid IS NULL";
3030
        }
3031
3032
        if ($finished == 0) {
3033
            $sql .= " AND p.finished = " . ((int) $finished);
3034
        } elseif ($finished == 1) {
3035
            $sql .= " AND p.finished = " . ((int) $finished);
3036
        }
3037
        if ($status >= 0) {
3038
            $sql .= " AND p.tosell = " . ((int) $status);
3039
        }
3040
        if ($status_purchase >= 0) {
3041
            $sql .= " AND p.tobuy = " . ((int) $status_purchase);
3042
        }
3043
        // Filter by product type
3044
        if (strval($filtertype) != '') {
3045
            $sql .= " AND p.fk_product_type = " . ((int) $filtertype);
3046
        } elseif (!isModEnabled('product')) { // when product module is disabled, show services only
3047
            $sql .= " AND p.fk_product_type = 1";
3048
        } elseif (!isModEnabled('service')) { // when service module is disabled, show products only
3049
            $sql .= " AND p.fk_product_type = 0";
3050
        }
3051
        // Add where from hooks
3052
        $parameters = array();
3053
        $reshook = $hookmanager->executeHooks('selectProductsListWhere', $parameters); // Note that $action and $object may have been modified by hook
3054
        $sql .= $hookmanager->resPrint;
3055
        // Add criteria on ref/label
3056
        if ($filterkey != '') {
3057
            $sql .= ' AND (';
3058
            $prefix = !getDolGlobalString('PRODUCT_DONOTSEARCH_ANYWHERE') ? '%' : ''; // Can use index if PRODUCT_DONOTSEARCH_ANYWHERE is on
3059
            // For natural search
3060
            $search_crit = explode(' ', $filterkey);
3061
            $i = 0;
3062
            if (count($search_crit) > 1) {
3063
                $sql .= "(";
3064
            }
3065
            foreach ($search_crit as $crit) {
3066
                if ($i > 0) {
3067
                    $sql .= " AND ";
3068
                }
3069
                $sql .= "(p.ref LIKE '" . $this->db->escape($prefix . $crit) . "%' OR p.label LIKE '" . $this->db->escape($prefix . $crit) . "%'";
3070
                if (getDolGlobalInt('MAIN_MULTILANGS')) {
3071
                    $sql .= " OR pl.label LIKE '" . $this->db->escape($prefix . $crit) . "%'";
3072
                }
3073
                if (getDolGlobalString('PRODUIT_CUSTOMER_PRICES') && !empty($socid)) {
3074
                    $sql .= " OR pcp.ref_customer LIKE '" . $this->db->escape($prefix . $crit) . "%'";
3075
                }
3076
                if (getDolGlobalString('PRODUCT_AJAX_SEARCH_ON_DESCRIPTION')) {
3077
                    $sql .= " OR p.description LIKE '" . $this->db->escape($prefix . $crit) . "%'";
3078
                    if (getDolGlobalInt('MAIN_MULTILANGS')) {
3079
                        $sql .= " OR pl.description LIKE '" . $this->db->escape($prefix . $crit) . "%'";
3080
                    }
3081
                }
3082
                if (getDolGlobalString('MAIN_SEARCH_PRODUCT_BY_FOURN_REF')) {
3083
                    $sql .= " OR pfp.ref_fourn LIKE '" . $this->db->escape($prefix . $crit) . "%'";
3084
                }
3085
                $sql .= ")";
3086
                $i++;
3087
            }
3088
            if (count($search_crit) > 1) {
3089
                $sql .= ")";
3090
            }
3091
            if (isModEnabled('barcode')) {
3092
                $sql .= " OR p.barcode LIKE '" . $this->db->escape($prefix . $filterkey) . "%'";
3093
            }
3094
            $sql .= ')';
3095
        }
3096
        if (count($warehouseStatusArray)) {
3097
            $sql .= " GROUP BY " . $selectFields;
3098
        }
3099
3100
        //Sort by category
3101
        if (getDolGlobalString('PRODUCT_SORT_BY_CATEGORY')) {
3102
            $sql .= " ORDER BY categorie_product_id ";
3103
            //ASC OR DESC order
3104
            (getDolGlobalInt('PRODUCT_SORT_BY_CATEGORY') == 1) ? $sql .= "ASC" : $sql .= "DESC";
3105
        } else {
3106
            $sql .= $this->db->order("p.ref");
3107
        }
3108
3109
        $sql .= $this->db->plimit($limit, 0);
3110
3111
        // Build output string
3112
        dol_syslog(get_class($this) . "::select_produits_list search products", LOG_DEBUG);
3113
        $result = $this->db->query($sql);
3114
        if ($result) {
3115
            require_once constant('DOL_DOCUMENT_ROOT') . '/product/class/product.class.php';
3116
            require_once constant('DOL_DOCUMENT_ROOT') . '/product/dynamic_price/class/price_parser.class.php';
3117
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/product.lib.php';
3118
3119
            $num = $this->db->num_rows($result);
3120
3121
            $events = array();
3122
3123
            if (!$forcecombo) {
3124
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
3125
                $out .= ajax_combobox($htmlname, $events, getDolGlobalInt("PRODUIT_USE_SEARCH_TO_SELECT"));
3126
            }
3127
3128
            $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" id="' . $htmlname . '">';
3129
3130
            $textifempty = '';
3131
            // Do not use textifempty = ' ' or '&nbsp;' here, or search on key will search on ' key'.
3132
            //if (!empty($conf->use_javascript_ajax) || $forcecombo) $textifempty='';
3133
            if (getDolGlobalString('PRODUIT_USE_SEARCH_TO_SELECT')) {
3134
                if ($showempty && !is_numeric($showempty)) {
3135
                    $textifempty = $langs->trans($showempty);
3136
                } else {
3137
                    $textifempty .= $langs->trans("All");
3138
                }
3139
            } else {
3140
                if ($showempty && !is_numeric($showempty)) {
3141
                    $textifempty = $langs->trans($showempty);
3142
                }
3143
            }
3144
            if ($showempty) {
3145
                $out .= '<option value="-1" selected>' . ($textifempty ? $textifempty : '&nbsp;') . '</option>';
3146
            }
3147
3148
            $i = 0;
3149
            while ($num && $i < $num) {
3150
                $opt = '';
3151
                $optJson = array();
3152
                $objp = $this->db->fetch_object($result);
3153
3154
                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
3155
                    $sql = "SELECT rowid, quantity, price, unitprice, remise_percent, remise, price_base_type";
3156
                    $sql .= " FROM " . $this->db->prefix() . "product_price_by_qty";
3157
                    $sql .= " WHERE fk_product_price = " . ((int) $objp->price_rowid);
3158
                    $sql .= " ORDER BY quantity ASC";
3159
3160
                    dol_syslog(get_class($this) . "::select_produits_list search prices by qty", LOG_DEBUG);
3161
                    $result2 = $this->db->query($sql);
3162
                    if ($result2) {
3163
                        $nb_prices = $this->db->num_rows($result2);
3164
                        $j = 0;
3165
                        while ($nb_prices && $j < $nb_prices) {
3166
                            $objp2 = $this->db->fetch_object($result2);
3167
3168
                            $objp->price_by_qty_rowid = $objp2->rowid;
3169
                            $objp->price_by_qty_price_base_type = $objp2->price_base_type;
3170
                            $objp->price_by_qty_quantity = $objp2->quantity;
3171
                            $objp->price_by_qty_unitprice = $objp2->unitprice;
3172
                            $objp->price_by_qty_remise_percent = $objp2->remise_percent;
3173
                            // For backward compatibility
3174
                            $objp->quantity = $objp2->quantity;
3175
                            $objp->price = $objp2->price;
3176
                            $objp->unitprice = $objp2->unitprice;
3177
                            $objp->remise_percent = $objp2->remise_percent;
3178
3179
                            //$objp->tva_tx is not overwritten by $objp2 value
3180
                            //$objp->default_vat_code is not overwritten by $objp2 value
3181
3182
                            $this->constructProductListOption($objp, $opt, $optJson, 0, $selected, $hidepriceinlabel, $filterkey);
3183
3184
                            $j++;
3185
3186
                            // Add new entry
3187
                            // "key" value of json key array is used by jQuery automatically as selected value
3188
                            // "label" value of json key array is used by jQuery automatically as text for combo box
3189
                            $out .= $opt;
3190
                            array_push($outarray, $optJson);
3191
                        }
3192
                    }
3193
                } else {
3194
                    if (isModEnabled('dynamicprices') && !empty($objp->fk_price_expression)) {
3195
                        $price_product = new Product($this->db);
3196
                        $price_product->fetch($objp->rowid, '', '', 1);
3197
3198
                        require_once constant('DOL_DOCUMENT_ROOT') . '/product/dynamic_price/class/price_parser.class.php';
3199
                        $priceparser = new PriceParser($this->db);
3200
                        $price_result = $priceparser->parseProduct($price_product);
3201
                        if ($price_result >= 0) {
3202
                            $objp->price = $price_result;
3203
                            $objp->unitprice = $price_result;
3204
                            //Calculate the VAT
3205
                            $objp->price_ttc = (float) price2num($objp->price) * (1 + ($objp->tva_tx / 100));
3206
                            $objp->price_ttc = price2num($objp->price_ttc, 'MU');
3207
                        }
3208
                    }
3209
3210
                    $this->constructProductListOption($objp, $opt, $optJson, $price_level, $selected, $hidepriceinlabel, $filterkey);
3211
                    // Add new entry
3212
                    // "key" value of json key array is used by jQuery automatically as selected value
3213
                    // "label" value of json key array is used by jQuery automatically as text for combo box
3214
                    $out .= $opt;
3215
                    array_push($outarray, $optJson);
3216
                }
3217
3218
                $i++;
3219
            }
3220
3221
            $out .= '</select>';
3222
3223
            $this->db->free($result);
3224
3225
            if (empty($outputmode)) {
3226
                return $out;
3227
            }
3228
3229
            return $outarray;
3230
        } else {
3231
            dol_print_error($this->db);
3232
        }
3233
3234
        return '';
3235
    }
3236
3237
    /**
3238
     * Function to forge the string with OPTIONs of SELECT.
3239
     * This define value for &$opt and &$optJson.
3240
     * This function is called by select_produits_list().
3241
     *
3242
     * @param stdClass  $objp           Resultset of fetch
3243
     * @param string    $opt            Option (var used for returned value in string option format)
3244
     * @param 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        Option (var used for returned value in json format)
3245
     * @param int       $price_level    Price level
3246
     * @param int       $selected       Preselected value
3247
     * @param int<0,1>  $hidepriceinlabel Hide price in label
3248
     * @param string    $filterkey      Filter key to highlight
3249
     * @param int<0,1>  $novirtualstock Do not load virtual stock, even if slow option STOCK_SHOW_VIRTUAL_STOCK_IN_PRODUCTS_COMBO is on.
3250
     * @return    void
3251
     */
3252
    protected function constructProductListOption(&$objp, &$opt, &$optJson, $price_level, $selected, $hidepriceinlabel = 0, $filterkey = '', $novirtualstock = 0)
3253
    {
3254
        global $langs, $conf, $user;
3255
        global $hookmanager;
3256
3257
        $outkey = '';
3258
        $outval = '';
3259
        $outref = '';
3260
        $outlabel = '';
3261
        $outlabel_translated = '';
3262
        $outdesc = '';
3263
        $outdesc_translated = '';
3264
        $outbarcode = '';
3265
        $outorigin = '';
3266
        $outtype = '';
3267
        $outprice_ht = '';
3268
        $outprice_ttc = '';
3269
        $outpricebasetype = '';
3270
        $outtva_tx = '';
3271
        $outdefault_vat_code = '';
3272
        $outqty = 1;
3273
        $outdiscount = 0;
3274
3275
        $maxlengtharticle = (!getDolGlobalString('PRODUCT_MAX_LENGTH_COMBO') ? 48 : $conf->global->PRODUCT_MAX_LENGTH_COMBO);
3276
3277
        $label = $objp->label;
3278
        if (!empty($objp->label_translated)) {
3279
            $label = $objp->label_translated;
3280
        }
3281
        if (!empty($filterkey) && $filterkey != '') {
3282
            $label = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', $label, 1);
3283
        }
3284
3285
        $outkey = $objp->rowid;
3286
        $outref = $objp->ref;
3287
        $outrefcust = empty($objp->custref) ? '' : $objp->custref;
3288
        $outlabel = $objp->label;
3289
        $outdesc = $objp->description;
3290
        if (getDolGlobalInt('MAIN_MULTILANGS')) {
3291
            $outlabel_translated = $objp->label_translated;
3292
            $outdesc_translated = $objp->description_translated;
3293
        }
3294
        $outbarcode = $objp->barcode;
3295
        $outorigin = $objp->fk_country;
3296
        $outpbq = empty($objp->price_by_qty_rowid) ? '' : $objp->price_by_qty_rowid;
3297
3298
        $outtype = $objp->fk_product_type;
3299
        $outdurationvalue = $outtype == Product::TYPE_SERVICE ? substr($objp->duration, 0, dol_strlen($objp->duration) - 1) : '';
3300
        $outdurationunit = $outtype == Product::TYPE_SERVICE ? substr($objp->duration, -1) : '';
3301
3302
        if ($outorigin && getDolGlobalString('PRODUCT_SHOW_ORIGIN_IN_COMBO')) {
3303
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/company.lib.php';
3304
        }
3305
3306
        // Units
3307
        $outvalUnits = '';
3308
        if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
3309
            if (!empty($objp->unit_short)) {
3310
                $outvalUnits .= ' - ' . $objp->unit_short;
3311
            }
3312
        }
3313
        if (getDolGlobalString('PRODUCT_SHOW_DIMENSIONS_IN_COMBO')) {
3314
            if (!empty($objp->weight) && $objp->weight_units !== null) {
3315
                $unitToShow = showDimensionInBestUnit($objp->weight, $objp->weight_units, 'weight', $langs);
3316
                $outvalUnits .= ' - ' . $unitToShow;
3317
            }
3318
            if ((!empty($objp->length) || !empty($objp->width) || !empty($objp->height)) && $objp->length_units !== null) {
3319
                $unitToShow = $objp->length . ' x ' . $objp->width . ' x ' . $objp->height . ' ' . measuringUnitString(0, 'size', $objp->length_units);
3320
                $outvalUnits .= ' - ' . $unitToShow;
3321
            }
3322
            if (!empty($objp->surface) && $objp->surface_units !== null) {
3323
                $unitToShow = showDimensionInBestUnit($objp->surface, $objp->surface_units, 'surface', $langs);
3324
                $outvalUnits .= ' - ' . $unitToShow;
3325
            }
3326
            if (!empty($objp->volume) && $objp->volume_units !== null) {
3327
                $unitToShow = showDimensionInBestUnit($objp->volume, $objp->volume_units, 'volume', $langs);
3328
                $outvalUnits .= ' - ' . $unitToShow;
3329
            }
3330
        }
3331
        if ($outdurationvalue && $outdurationunit) {
3332
            $da = array(
3333
                'h' => $langs->trans('Hour'),
3334
                'd' => $langs->trans('Day'),
3335
                'w' => $langs->trans('Week'),
3336
                'm' => $langs->trans('Month'),
3337
                'y' => $langs->trans('Year')
3338
            );
3339
            if (isset($da[$outdurationunit])) {
3340
                $outvalUnits .= ' - ' . $outdurationvalue . ' ' . $langs->transnoentities($da[$outdurationunit] . ($outdurationvalue > 1 ? 's' : ''));
3341
            }
3342
        }
3343
3344
        $opt = '<option value="' . $objp->rowid . '"';
3345
        $opt .= ($objp->rowid == $selected) ? ' selected' : '';
3346
        if (!empty($objp->price_by_qty_rowid) && $objp->price_by_qty_rowid > 0) {
3347
            $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 . '"';
3348
        }
3349
        if (isModEnabled('stock') && isset($objp->stock) && ($objp->fk_product_type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) {
3350
            if ($user->hasRight('stock', 'lire')) {
3351
                if ($objp->stock > 0) {
3352
                    $opt .= ' class="product_line_stock_ok"';
3353
                } elseif ($objp->stock <= 0) {
3354
                    $opt .= ' class="product_line_stock_too_low"';
3355
                }
3356
            }
3357
        }
3358
        if (getDolGlobalString('PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE')) {
3359
            $opt .= ' data-labeltrans="' . $outlabel_translated . '"';
3360
            $opt .= ' data-desctrans="' . dol_escape_htmltag($outdesc_translated) . '"';
3361
        }
3362
        $opt .= '>';
3363
        $opt .= $objp->ref;
3364
        if (!empty($objp->custref)) {
3365
            $opt .= ' (' . $objp->custref . ')';
3366
        }
3367
        if ($outbarcode) {
3368
            $opt .= ' (' . $outbarcode . ')';
3369
        }
3370
        $opt .= ' - ' . dol_trunc($label, $maxlengtharticle);
3371
        if ($outorigin && getDolGlobalString('PRODUCT_SHOW_ORIGIN_IN_COMBO')) {
3372
            $opt .= ' (' . getCountry($outorigin, 1) . ')';
3373
        }
3374
3375
        $objRef = $objp->ref;
3376
        if (!empty($objp->custref)) {
3377
            $objRef .= ' (' . $objp->custref . ')';
3378
        }
3379
        if (!empty($filterkey) && $filterkey != '') {
3380
            $objRef = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', $objRef, 1);
3381
        }
3382
        $outval .= $objRef;
3383
        if ($outbarcode) {
3384
            $outval .= ' (' . $outbarcode . ')';
3385
        }
3386
        $outval .= ' - ' . dol_trunc($label, $maxlengtharticle);
3387
        if ($outorigin && getDolGlobalString('PRODUCT_SHOW_ORIGIN_IN_COMBO')) {
3388
            $outval .= ' (' . getCountry($outorigin, 1) . ')';
3389
        }
3390
3391
        // Units
3392
        $opt .= $outvalUnits;
3393
        $outval .= $outvalUnits;
3394
3395
        $found = 0;
3396
3397
        // Multiprice
3398
        // If we need a particular price level (from 1 to n)
3399
        if (empty($hidepriceinlabel) && $price_level >= 1 && (getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES'))) {
3400
            $sql = "SELECT price, price_ttc, price_base_type, tva_tx, default_vat_code";
3401
            $sql .= " FROM " . $this->db->prefix() . "product_price";
3402
            $sql .= " WHERE fk_product = " . ((int) $objp->rowid);
3403
            $sql .= " AND entity IN (" . getEntity('productprice') . ")";
3404
            $sql .= " AND price_level = " . ((int) $price_level);
3405
            $sql .= " ORDER BY date_price DESC, rowid DESC"; // Warning DESC must be both on date_price and rowid.
3406
            $sql .= " LIMIT 1";
3407
3408
            dol_syslog(get_class($this) . '::constructProductListOption search price for product ' . $objp->rowid . ' AND level ' . $price_level, LOG_DEBUG);
3409
            $result2 = $this->db->query($sql);
3410
            if ($result2) {
3411
                $objp2 = $this->db->fetch_object($result2);
3412
                if ($objp2) {
3413
                    $found = 1;
3414
                    if ($objp2->price_base_type == 'HT') {
3415
                        $opt .= ' - ' . price($objp2->price, 1, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->trans("HT");
3416
                        $outval .= ' - ' . price($objp2->price, 0, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->transnoentities("HT");
3417
                    } else {
3418
                        $opt .= ' - ' . price($objp2->price_ttc, 1, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->trans("TTC");
3419
                        $outval .= ' - ' . price($objp2->price_ttc, 0, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->transnoentities("TTC");
3420
                    }
3421
                    $outprice_ht = price($objp2->price);
3422
                    $outprice_ttc = price($objp2->price_ttc);
3423
                    $outpricebasetype = $objp2->price_base_type;
3424
                    if (getDolGlobalString('PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL')) {  // using this option is a bug. kept for backward compatibility
3425
                        $outtva_tx = $objp2->tva_tx;                        // We use the vat rate on line of multiprice
3426
                        $outdefault_vat_code = $objp2->default_vat_code;    // We use the vat code on line of multiprice
3427
                    } else {
3428
                        $outtva_tx = $objp->tva_tx;                            // We use the vat rate of product, not the one on line of multiprice
3429
                        $outdefault_vat_code = $objp->default_vat_code;        // We use the vat code or product, not the one on line of multiprice
3430
                    }
3431
                }
3432
            } else {
3433
                dol_print_error($this->db);
3434
            }
3435
        }
3436
3437
        // Price by quantity
3438
        if (empty($hidepriceinlabel) && !empty($objp->quantity) && $objp->quantity >= 1 && (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES'))) {
3439
            $found = 1;
3440
            $outqty = $objp->quantity;
3441
            $outdiscount = $objp->remise_percent;
3442
            if ($objp->quantity == 1) {
3443
                $opt .= ' - ' . price($objp->unitprice, 1, $langs, 0, 0, -1, $conf->currency) . "/";
3444
                $outval .= ' - ' . price($objp->unitprice, 0, $langs, 0, 0, -1, $conf->currency) . "/";
3445
                $opt .= $langs->trans("Unit"); // Do not use strtolower because it breaks utf8 encoding
3446
                $outval .= $langs->transnoentities("Unit");
3447
            } else {
3448
                $opt .= ' - ' . price($objp->price, 1, $langs, 0, 0, -1, $conf->currency) . "/" . $objp->quantity;
3449
                $outval .= ' - ' . price($objp->price, 0, $langs, 0, 0, -1, $conf->currency) . "/" . $objp->quantity;
3450
                $opt .= $langs->trans("Units"); // Do not use strtolower because it breaks utf8 encoding
3451
                $outval .= $langs->transnoentities("Units");
3452
            }
3453
3454
            $outprice_ht = price($objp->unitprice);
3455
            $outprice_ttc = price($objp->unitprice * (1 + ($objp->tva_tx / 100)));
3456
            $outpricebasetype = $objp->price_base_type;
3457
            $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
3458
            $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
3459
        }
3460
        if (empty($hidepriceinlabel) && !empty($objp->quantity) && $objp->quantity >= 1) {
3461
            $opt .= " (" . price($objp->unitprice, 1, $langs, 0, 0, -1, $conf->currency) . "/" . $langs->trans("Unit") . ")"; // Do not use strtolower because it breaks utf8 encoding
3462
            $outval .= " (" . price($objp->unitprice, 0, $langs, 0, 0, -1, $conf->currency) . "/" . $langs->transnoentities("Unit") . ")"; // Do not use strtolower because it breaks utf8 encoding
3463
        }
3464
        if (empty($hidepriceinlabel) && !empty($objp->remise_percent) && $objp->remise_percent >= 1) {
3465
            $opt .= " - " . $langs->trans("Discount") . " : " . vatrate($objp->remise_percent) . ' %';
3466
            $outval .= " - " . $langs->transnoentities("Discount") . " : " . vatrate($objp->remise_percent) . ' %';
3467
        }
3468
3469
        // Price by customer
3470
        if (empty($hidepriceinlabel) && getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
3471
            if (!empty($objp->idprodcustprice)) {
3472
                $found = 1;
3473
3474
                if ($objp->custprice_base_type == 'HT') {
3475
                    $opt .= ' - ' . price($objp->custprice, 1, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->trans("HT");
3476
                    $outval .= ' - ' . price($objp->custprice, 0, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->transnoentities("HT");
3477
                } else {
3478
                    $opt .= ' - ' . price($objp->custprice_ttc, 1, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->trans("TTC");
3479
                    $outval .= ' - ' . price($objp->custprice_ttc, 0, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->transnoentities("TTC");
3480
                }
3481
3482
                $outprice_ht = price($objp->custprice);
3483
                $outprice_ttc = price($objp->custprice_ttc);
3484
                $outpricebasetype = $objp->custprice_base_type;
3485
                $outtva_tx = $objp->custtva_tx;
3486
                $outdefault_vat_code = $objp->custdefault_vat_code;
3487
            }
3488
        }
3489
3490
        // If level no defined or multiprice not found, we used the default price
3491
        if (empty($hidepriceinlabel) && !$found) {
3492
            if ($objp->price_base_type == 'HT') {
3493
                $opt .= ' - ' . price($objp->price, 1, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->trans("HT");
3494
                $outval .= ' - ' . price($objp->price, 0, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->transnoentities("HT");
3495
            } else {
3496
                $opt .= ' - ' . price($objp->price_ttc, 1, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->trans("TTC");
3497
                $outval .= ' - ' . price($objp->price_ttc, 0, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->transnoentities("TTC");
3498
            }
3499
            $outprice_ht = price($objp->price);
3500
            $outprice_ttc = price($objp->price_ttc);
3501
            $outpricebasetype = $objp->price_base_type;
3502
            $outtva_tx = $objp->tva_tx;
3503
            $outdefault_vat_code = $objp->default_vat_code;
3504
        }
3505
3506
        if (isModEnabled('stock') && isset($objp->stock) && ($objp->fk_product_type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) {
3507
            if ($user->hasRight('stock', 'lire')) {
3508
                $opt .= ' - ' . $langs->trans("Stock") . ': ' . price(price2num($objp->stock, 'MS'));
3509
3510
                if ($objp->stock > 0) {
3511
                    $outval .= ' - <span class="product_line_stock_ok">';
3512
                } elseif ($objp->stock <= 0) {
3513
                    $outval .= ' - <span class="product_line_stock_too_low">';
3514
                }
3515
                $outval .= $langs->transnoentities("Stock") . ': ' . price(price2num($objp->stock, 'MS'));
3516
                $outval .= '</span>';
3517
                if (empty($novirtualstock) && getDolGlobalString('STOCK_SHOW_VIRTUAL_STOCK_IN_PRODUCTS_COMBO')) {  // Warning, this option may slow down combo list generation
3518
                    $langs->load("stocks");
3519
3520
                    $tmpproduct = new Product($this->db);
3521
                    $tmpproduct->fetch($objp->rowid, '', '', '', 1, 1, 1); // Load product without lang and prices arrays (we just need to make ->virtual_stock() after)
3522
                    $tmpproduct->load_virtual_stock();
3523
                    $virtualstock = $tmpproduct->stock_theorique;
3524
3525
                    $opt .= ' - ' . $langs->trans("VirtualStock") . ':' . $virtualstock;
3526
3527
                    $outval .= ' - ' . $langs->transnoentities("VirtualStock") . ':';
3528
                    if ($virtualstock > 0) {
3529
                        $outval .= '<span class="product_line_stock_ok">';
3530
                    } elseif ($virtualstock <= 0) {
3531
                        $outval .= '<span class="product_line_stock_too_low">';
3532
                    }
3533
                    $outval .= $virtualstock;
3534
                    $outval .= '</span>';
3535
3536
                    unset($tmpproduct);
3537
                }
3538
            }
3539
        }
3540
3541
        $parameters = array('objp' => $objp);
3542
        $reshook = $hookmanager->executeHooks('constructProductListOption', $parameters); // Note that $action and $object may have been modified by hook
3543
        if (empty($reshook)) {
3544
            $opt .= $hookmanager->resPrint;
3545
        } else {
3546
            $opt = $hookmanager->resPrint;
3547
        }
3548
3549
        $opt .= "</option>\n";
3550
        $optJson = array(
3551
            'key' => $outkey,
3552
            'value' => $outref,
3553
            'label' => $outval,
3554
            'label2' => $outlabel,
3555
            'desc' => $outdesc,
3556
            'type' => $outtype,
3557
            'price_ht' => price2num($outprice_ht),
3558
            'price_ttc' => price2num($outprice_ttc),
3559
            'price_ht_locale' => price(price2num($outprice_ht)),
3560
            'price_ttc_locale' => price(price2num($outprice_ttc)),
3561
            'pricebasetype' => $outpricebasetype,
3562
            'tva_tx' => $outtva_tx,
3563
            'default_vat_code' => $outdefault_vat_code,
3564
            'qty' => $outqty,
3565
            'discount' => $outdiscount,
3566
            'duration_value' => $outdurationvalue,
3567
            'duration_unit' => $outdurationunit,
3568
            'pbq' => $outpbq,
3569
            'labeltrans' => $outlabel_translated,
3570
            'desctrans' => $outdesc_translated,
3571
            'ref_customer' => $outrefcust
3572
        );
3573
    }
3574
3575
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3576
3577
    /**
3578
     * Return list of products for customer (in Ajax if Ajax activated or go to select_produits_fournisseurs_list)
3579
     *
3580
     * @param int       $socid          Id third party
3581
     * @param string    $selected       Preselected product
3582
     * @param string    $htmlname       Name of HTML Select
3583
     * @param string    $filtertype     Filter on product type (''=nofilter, 0=product, 1=service)
3584
     * @param string    $filtre         For a SQL filter
3585
     * @param array<string,string|string[]> $ajaxoptions    Options for ajax_autocompleter
3586
     * @param int<0,1>  $hidelabel      Hide label (0=no, 1=yes)
3587
     * @param int<0,1>  $alsoproductwithnosupplierprice 1=Add also product without supplier prices
3588
     * @param string    $morecss        More CSS
3589
     * @param string    $placeholder    Placeholder
3590
     * @return    void
3591
     */
3592
    public function select_produits_fournisseurs($socid, $selected = '', $htmlname = 'productid', $filtertype = '', $filtre = '', $ajaxoptions = array(), $hidelabel = 0, $alsoproductwithnosupplierprice = 0, $morecss = '', $placeholder = '')
3593
    {
3594
		// phpcs:enable
3595
        global $langs, $conf;
3596
        global $price_level, $status, $finished;
3597
3598
        if (!isset($status)) {
3599
            $status = 1;
3600
        }
3601
3602
        $selected_input_value = '';
3603
        if (!empty($conf->use_javascript_ajax) && getDolGlobalString('PRODUIT_USE_SEARCH_TO_SELECT')) {
3604
            if ($selected > 0) {
3605
                require_once constant('DOL_DOCUMENT_ROOT') . '/product/class/product.class.php';
3606
                $producttmpselect = new Product($this->db);
3607
                $producttmpselect->fetch($selected);
3608
                $selected_input_value = $producttmpselect->ref;
3609
                unset($producttmpselect);
3610
            }
3611
3612
            // mode=2 means suppliers products
3613
            $urloption = ($socid > 0 ? 'socid=' . $socid . '&' : '') . 'htmlname=' . $htmlname . '&outjson=1&price_level=' . $price_level . '&type=' . $filtertype . '&mode=2&status=' . $status . '&finished=' . $finished . '&alsoproductwithnosupplierprice=' . $alsoproductwithnosupplierprice;
3614
            print ajax_autocompleter($selected, $htmlname, constant('BASE_URL') . '/product/ajax/products.php', $urloption, getDolGlobalString('PRODUIT_USE_SEARCH_TO_SELECT'), 0, $ajaxoptions);
3615
3616
            print($hidelabel ? '' : $langs->trans("RefOrLabel") . ' : ') . '<input type="text" class="' . $morecss . '" name="search_' . $htmlname . '" id="search_' . $htmlname . '" value="' . $selected_input_value . '"' . ($placeholder ? ' placeholder="' . $placeholder . '"' : '') . '>';
3617
        } else {
3618
            print $this->select_produits_fournisseurs_list($socid, $selected, $htmlname, $filtertype, $filtre, '', $status, 0, 0, $alsoproductwithnosupplierprice, $morecss, 0, $placeholder);
3619
        }
3620
    }
3621
3622
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3623
3624
    /**
3625
     *    Return list of suppliers products
3626
     *
3627
     * @param int $socid Id of supplier thirdparty (0 = no filter)
3628
     * @param string $selected Product price preselected (must be 'id' in product_fournisseur_price or 'idprod_IDPROD')
3629
     * @param string $htmlname Name of HTML select
3630
     * @param string $filtertype Filter on product type (''=nofilter, 0=product, 1=service)
3631
     * @param string $filtre Generic filter. Data must not come from user input.
3632
     * @param string $filterkey Filter of produdts
3633
     * @param int $statut -1=Return all products, 0=Products not on buy, 1=Products on buy
3634
     * @param int $outputmode 0=HTML select string, 1=Array
3635
     * @param int $limit Limit of line number
3636
     * @param int $alsoproductwithnosupplierprice 1=Add also product without supplier prices
3637
     * @param string $morecss Add more CSS
3638
     * @param int $showstockinlist Show stock information (slower).
3639
     * @param string $placeholder Placeholder
3640
     * @return array|string                Array of keys for json or HTML component
3641
     */
3642
    public function select_produits_fournisseurs_list($socid, $selected = '', $htmlname = 'productid', $filtertype = '', $filtre = '', $filterkey = '', $statut = -1, $outputmode = 0, $limit = 100, $alsoproductwithnosupplierprice = 0, $morecss = '', $showstockinlist = 0, $placeholder = '')
3643
    {
3644
		// phpcs:enable
3645
        global $langs, $conf, $user;
3646
        global $hookmanager;
3647
3648
        $out = '';
3649
        $outarray = array();
3650
3651
        $maxlengtharticle = (!getDolGlobalString('PRODUCT_MAX_LENGTH_COMBO') ? 48 : $conf->global->PRODUCT_MAX_LENGTH_COMBO);
3652
3653
        $langs->load('stocks');
3654
        // Units
3655
        if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
3656
            $langs->load('other');
3657
        }
3658
3659
        $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,";
3660
        $sql .= " pfp.ref_fourn, pfp.rowid as idprodfournprice, pfp.price as fprice, pfp.quantity, pfp.remise_percent, pfp.remise, pfp.unitprice, pfp.barcode";
3661
        if (isModEnabled('multicurrency')) {
3662
            $sql .= ", pfp.multicurrency_code, pfp.multicurrency_unitprice";
3663
        }
3664
        $sql .= ", pfp.fk_supplier_price_expression, pfp.fk_product, pfp.tva_tx, pfp.default_vat_code, pfp.fk_soc, s.nom as name";
3665
        $sql .= ", pfp.supplier_reputation";
3666
        // if we use supplier description of the products
3667
        if (getDolGlobalString('PRODUIT_FOURN_TEXTS')) {
3668
            $sql .= ", pfp.desc_fourn as description";
3669
        } else {
3670
            $sql .= ", p.description";
3671
        }
3672
        // Units
3673
        if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
3674
            $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";
3675
        }
3676
        $sql .= " FROM " . $this->db->prefix() . "product as p";
3677
        $sql .= " LEFT JOIN " . $this->db->prefix() . "product_fournisseur_price as pfp ON ( p.rowid = pfp.fk_product AND pfp.entity IN (" . getEntity('product') . ") )";
3678
        if ($socid > 0) {
3679
            $sql .= " AND pfp.fk_soc = " . ((int) $socid);
3680
        }
3681
        $sql .= " LEFT JOIN " . $this->db->prefix() . "societe as s ON pfp.fk_soc = s.rowid";
3682
        // Units
3683
        if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
3684
            $sql .= " LEFT JOIN " . $this->db->prefix() . "c_units u ON u.rowid = p.fk_unit";
3685
        }
3686
        $sql .= " WHERE p.entity IN (" . getEntity('product') . ")";
3687
        if ($statut != -1) {
3688
            $sql .= " AND p.tobuy = " . ((int) $statut);
3689
        }
3690
        if (strval($filtertype) != '') {
3691
            $sql .= " AND p.fk_product_type = " . ((int) $filtertype);
3692
        }
3693
        if (!empty($filtre)) {
3694
            $sql .= " " . $filtre;
3695
        }
3696
        // Add where from hooks
3697
        $parameters = array();
3698
        $reshook = $hookmanager->executeHooks('selectSuppliersProductsListWhere', $parameters); // Note that $action and $object may have been modified by hook
3699
        $sql .= $hookmanager->resPrint;
3700
        // Add criteria on ref/label
3701
        if ($filterkey != '') {
3702
            $sql .= ' AND (';
3703
            $prefix = !getDolGlobalString('PRODUCT_DONOTSEARCH_ANYWHERE') ? '%' : ''; // Can use index if PRODUCT_DONOTSEARCH_ANYWHERE is on
3704
            // For natural search
3705
            $search_crit = explode(' ', $filterkey);
3706
            $i = 0;
3707
            if (count($search_crit) > 1) {
3708
                $sql .= "(";
3709
            }
3710
            foreach ($search_crit as $crit) {
3711
                if ($i > 0) {
3712
                    $sql .= " AND ";
3713
                }
3714
                $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) . "%'";
3715
                if (getDolGlobalString('PRODUIT_FOURN_TEXTS')) {
3716
                    $sql .= " OR pfp.desc_fourn LIKE '" . $this->db->escape($prefix . $crit) . "%'";
3717
                }
3718
                $sql .= ")";
3719
                $i++;
3720
            }
3721
            if (count($search_crit) > 1) {
3722
                $sql .= ")";
3723
            }
3724
            if (isModEnabled('barcode')) {
3725
                $sql .= " OR p.barcode LIKE '" . $this->db->escape($prefix . $filterkey) . "%'";
3726
                $sql .= " OR pfp.barcode LIKE '" . $this->db->escape($prefix . $filterkey) . "%'";
3727
            }
3728
            $sql .= ')';
3729
        }
3730
        $sql .= " ORDER BY pfp.ref_fourn DESC, pfp.quantity ASC";
3731
        $sql .= $this->db->plimit($limit, 0);
3732
3733
        // Build output string
3734
3735
        dol_syslog(get_class($this) . "::select_produits_fournisseurs_list", LOG_DEBUG);
3736
        $result = $this->db->query($sql);
3737
        if ($result) {
3738
            require_once constant('DOL_DOCUMENT_ROOT') . '/product/dynamic_price/class/price_parser.class.php';
3739
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/product.lib.php';
3740
3741
            $num = $this->db->num_rows($result);
3742
3743
            //$out.='<select class="flat" id="select'.$htmlname.'" name="'.$htmlname.'">';  // remove select to have id same with combo and ajax
3744
            $out .= '<select class="flat ' . ($morecss ? ' ' . $morecss : '') . '" id="' . $htmlname . '" name="' . $htmlname . '">';
3745
            if (!$selected) {
3746
                $out .= '<option value="-1" selected>' . ($placeholder ? $placeholder : '&nbsp;') . '</option>';
3747
            } else {
3748
                $out .= '<option value="-1">' . ($placeholder ? $placeholder : '&nbsp;') . '</option>';
3749
            }
3750
3751
            $i = 0;
3752
            while ($i < $num) {
3753
                $objp = $this->db->fetch_object($result);
3754
3755
                if (is_null($objp->idprodfournprice)) {
3756
                    // There is no supplier price found, we will use the vat rate for sale
3757
                    $objp->tva_tx = $objp->tva_tx_sale;
3758
                    $objp->default_vat_code = $objp->default_vat_code_sale;
3759
                }
3760
3761
                $outkey = $objp->idprodfournprice; // id in table of price
3762
                if (!$outkey && $alsoproductwithnosupplierprice) {
3763
                    $outkey = 'idprod_' . $objp->rowid; // id of product
3764
                }
3765
3766
                $outref = $objp->ref;
3767
                $outbarcode = $objp->barcode;
3768
                $outqty = 1;
3769
                $outdiscount = 0;
3770
                $outtype = $objp->fk_product_type;
3771
                $outdurationvalue = $outtype == Product::TYPE_SERVICE ? substr($objp->duration, 0, dol_strlen($objp->duration) - 1) : '';
3772
                $outdurationunit = $outtype == Product::TYPE_SERVICE ? substr($objp->duration, -1) : '';
3773
3774
                // Units
3775
                $outvalUnits = '';
3776
                if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
3777
                    if (!empty($objp->unit_short)) {
3778
                        $outvalUnits .= ' - ' . $objp->unit_short;
3779
                    }
3780
                    if (!empty($objp->weight) && $objp->weight_units !== null) {
3781
                        $unitToShow = showDimensionInBestUnit($objp->weight, $objp->weight_units, 'weight', $langs);
3782
                        $outvalUnits .= ' - ' . $unitToShow;
3783
                    }
3784
                    if ((!empty($objp->length) || !empty($objp->width) || !empty($objp->height)) && $objp->length_units !== null) {
3785
                        $unitToShow = $objp->length . ' x ' . $objp->width . ' x ' . $objp->height . ' ' . measuringUnitString(0, 'size', $objp->length_units);
3786
                        $outvalUnits .= ' - ' . $unitToShow;
3787
                    }
3788
                    if (!empty($objp->surface) && $objp->surface_units !== null) {
3789
                        $unitToShow = showDimensionInBestUnit($objp->surface, $objp->surface_units, 'surface', $langs);
3790
                        $outvalUnits .= ' - ' . $unitToShow;
3791
                    }
3792
                    if (!empty($objp->volume) && $objp->volume_units !== null) {
3793
                        $unitToShow = showDimensionInBestUnit($objp->volume, $objp->volume_units, 'volume', $langs);
3794
                        $outvalUnits .= ' - ' . $unitToShow;
3795
                    }
3796
                    if ($outdurationvalue && $outdurationunit) {
3797
                        $da = array(
3798
                            'h' => $langs->trans('Hour'),
3799
                            'd' => $langs->trans('Day'),
3800
                            'w' => $langs->trans('Week'),
3801
                            'm' => $langs->trans('Month'),
3802
                            'y' => $langs->trans('Year')
3803
                        );
3804
                        if (isset($da[$outdurationunit])) {
3805
                            $outvalUnits .= ' - ' . $outdurationvalue . ' ' . $langs->transnoentities($da[$outdurationunit] . ($outdurationvalue > 1 ? 's' : ''));
3806
                        }
3807
                    }
3808
                }
3809
3810
                $objRef = $objp->ref;
3811
                if ($filterkey && $filterkey != '') {
3812
                    $objRef = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', $objRef, 1);
3813
                }
3814
                $objRefFourn = $objp->ref_fourn;
3815
                if ($filterkey && $filterkey != '') {
3816
                    $objRefFourn = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', $objRefFourn, 1);
3817
                }
3818
                $label = $objp->label;
3819
                if ($filterkey && $filterkey != '') {
3820
                    $label = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', $label, 1);
3821
                }
3822
3823
                switch ($objp->fk_product_type) {
3824
                    case Product::TYPE_PRODUCT:
3825
                        $picto = 'product';
3826
                        break;
3827
                    case Product::TYPE_SERVICE:
3828
                        $picto = 'service';
3829
                        break;
3830
                    default:
3831
                        $picto = '';
3832
                        break;
3833
                }
3834
3835
                if (empty($picto)) {
3836
                    $optlabel = '';
3837
                } else {
3838
                    $optlabel = img_object('', $picto, 'class="paddingright classfortooltip"', 0, 0, 1);
3839
                }
3840
3841
                $optlabel .= $objp->ref;
3842
                if (!empty($objp->idprodfournprice) && ($objp->ref != $objp->ref_fourn)) {
3843
                    $optlabel .= ' <span class="opacitymedium">(' . $objp->ref_fourn . ')</span>';
3844
                }
3845
                if (isModEnabled('barcode') && !empty($objp->barcode)) {
3846
                    $optlabel .= ' (' . $outbarcode . ')';
3847
                }
3848
                $optlabel .= ' - ' . dol_trunc($label, $maxlengtharticle);
3849
3850
                $outvallabel = $objRef;
3851
                if (!empty($objp->idprodfournprice) && ($objp->ref != $objp->ref_fourn)) {
3852
                    $outvallabel .= ' (' . $objRefFourn . ')';
3853
                }
3854
                if (isModEnabled('barcode') && !empty($objp->barcode)) {
3855
                    $outvallabel .= ' (' . $outbarcode . ')';
3856
                }
3857
                $outvallabel .= ' - ' . dol_trunc($label, $maxlengtharticle);
3858
3859
                // Units
3860
                $optlabel .= $outvalUnits;
3861
                $outvallabel .= $outvalUnits;
3862
3863
                if (!empty($objp->idprodfournprice)) {
3864
                    $outqty = $objp->quantity;
3865
                    $outdiscount = $objp->remise_percent;
3866
                    if (isModEnabled('dynamicprices') && !empty($objp->fk_supplier_price_expression)) {
3867
                        $prod_supplier = new ProductFournisseur($this->db);
3868
                        $prod_supplier->product_fourn_price_id = $objp->idprodfournprice;
3869
                        $prod_supplier->id = $objp->fk_product;
3870
                        $prod_supplier->fourn_qty = $objp->quantity;
3871
                        $prod_supplier->fourn_tva_tx = $objp->tva_tx;
3872
                        $prod_supplier->fk_supplier_price_expression = $objp->fk_supplier_price_expression;
3873
3874
                        require_once constant('DOL_DOCUMENT_ROOT') . '/product/dynamic_price/class/price_parser.class.php';
3875
                        $priceparser = new PriceParser($this->db);
3876
                        $price_result = $priceparser->parseProductSupplier($prod_supplier);
3877
                        if ($price_result >= 0) {
3878
                            $objp->fprice = $price_result;
3879
                            if ($objp->quantity >= 1) {
3880
                                $objp->unitprice = $objp->fprice / $objp->quantity; // Replace dynamically unitprice
3881
                            }
3882
                        }
3883
                    }
3884
                    if ($objp->quantity == 1) {
3885
                        $optlabel .= ' - ' . price($objp->fprice * (getDolGlobalString('DISPLAY_DISCOUNTED_SUPPLIER_PRICE') ? (1 - $objp->remise_percent / 100) : 1), 1, $langs, 0, 0, -1, $conf->currency) . "/";
3886
                        $outvallabel .= ' - ' . price($objp->fprice * (getDolGlobalString('DISPLAY_DISCOUNTED_SUPPLIER_PRICE') ? (1 - $objp->remise_percent / 100) : 1), 0, $langs, 0, 0, -1, $conf->currency) . "/";
3887
                        $optlabel .= $langs->trans("Unit"); // Do not use strtolower because it breaks utf8 encoding
3888
                        $outvallabel .= $langs->transnoentities("Unit");
3889
                    } else {
3890
                        $optlabel .= ' - ' . price($objp->fprice * (getDolGlobalString('DISPLAY_DISCOUNTED_SUPPLIER_PRICE') ? (1 - $objp->remise_percent / 100) : 1), 1, $langs, 0, 0, -1, $conf->currency) . "/" . $objp->quantity;
3891
                        $outvallabel .= ' - ' . price($objp->fprice * (getDolGlobalString('DISPLAY_DISCOUNTED_SUPPLIER_PRICE') ? (1 - $objp->remise_percent / 100) : 1), 0, $langs, 0, 0, -1, $conf->currency) . "/" . $objp->quantity;
3892
                        $optlabel .= ' ' . $langs->trans("Units"); // Do not use strtolower because it breaks utf8 encoding
3893
                        $outvallabel .= ' ' . $langs->transnoentities("Units");
3894
                    }
3895
3896
                    if ($objp->quantity > 1) {
3897
                        $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
3898
                        $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
3899
                    }
3900
                    if ($objp->remise_percent >= 1) {
3901
                        $optlabel .= " - " . $langs->trans("Discount") . " : " . vatrate($objp->remise_percent) . ' %';
3902
                        $outvallabel .= " - " . $langs->transnoentities("Discount") . " : " . vatrate($objp->remise_percent) . ' %';
3903
                    }
3904
                    if ($objp->duration) {
3905
                        $optlabel .= " - " . $objp->duration;
3906
                        $outvallabel .= " - " . $objp->duration;
3907
                    }
3908
                    if (!$socid) {
3909
                        $optlabel .= " - " . dol_trunc($objp->name, 8);
3910
                        $outvallabel .= " - " . dol_trunc($objp->name, 8);
3911
                    }
3912
                    if ($objp->supplier_reputation) {
3913
                        //TODO dictionary
3914
                        $reputations = array('' => $langs->trans('Standard'), 'FAVORITE' => $langs->trans('Favorite'), 'NOTTHGOOD' => $langs->trans('NotTheGoodQualitySupplier'), 'DONOTORDER' => $langs->trans('DoNotOrderThisProductToThisSupplier'));
3915
3916
                        $optlabel .= " - " . $reputations[$objp->supplier_reputation];
3917
                        $outvallabel .= " - " . $reputations[$objp->supplier_reputation];
3918
                    }
3919
                } else {
3920
                    $optlabel .= " - <span class='opacitymedium'>" . $langs->trans("NoPriceDefinedForThisSupplier") . '</span>';
3921
                    $outvallabel .= ' - ' . $langs->transnoentities("NoPriceDefinedForThisSupplier");
3922
                }
3923
3924
                if (isModEnabled('stock') && $showstockinlist && isset($objp->stock) && ($objp->fk_product_type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) {
3925
                    $novirtualstock = ($showstockinlist == 2);
3926
3927
                    if ($user->hasRight('stock', 'lire')) {
3928
                        $outvallabel .= ' - ' . $langs->trans("Stock") . ': ' . price(price2num($objp->stock, 'MS'));
3929
3930
                        if ($objp->stock > 0) {
3931
                            $optlabel .= ' - <span class="product_line_stock_ok">';
3932
                        } elseif ($objp->stock <= 0) {
3933
                            $optlabel .= ' - <span class="product_line_stock_too_low">';
3934
                        }
3935
                        $optlabel .= $langs->transnoentities("Stock") . ':' . price(price2num($objp->stock, 'MS'));
3936
                        $optlabel .= '</span>';
3937
                        if (empty($novirtualstock) && getDolGlobalString('STOCK_SHOW_VIRTUAL_STOCK_IN_PRODUCTS_COMBO')) {  // Warning, this option may slow down combo list generation
3938
                            $langs->load("stocks");
3939
3940
                            $tmpproduct = new Product($this->db);
3941
                            $tmpproduct->fetch($objp->rowid, '', '', '', 1, 1, 1); // Load product without lang and prices arrays (we just need to make ->virtual_stock() after)
3942
                            $tmpproduct->load_virtual_stock();
3943
                            $virtualstock = $tmpproduct->stock_theorique;
3944
3945
                            $outvallabel .= ' - ' . $langs->trans("VirtualStock") . ':' . $virtualstock;
3946
3947
                            $optlabel .= ' - ' . $langs->transnoentities("VirtualStock") . ':';
3948
                            if ($virtualstock > 0) {
3949
                                $optlabel .= '<span class="product_line_stock_ok">';
3950
                            } elseif ($virtualstock <= 0) {
3951
                                $optlabel .= '<span class="product_line_stock_too_low">';
3952
                            }
3953
                            $optlabel .= $virtualstock;
3954
                            $optlabel .= '</span>';
3955
3956
                            unset($tmpproduct);
3957
                        }
3958
                    }
3959
                }
3960
3961
                $optstart = '<option value="' . $outkey . '"';
3962
                if ($selected && $selected == $objp->idprodfournprice) {
3963
                    $optstart .= ' selected';
3964
                }
3965
                if (empty($objp->idprodfournprice) && empty($alsoproductwithnosupplierprice)) {
3966
                    $optstart .= ' disabled';
3967
                }
3968
3969
                if (!empty($objp->idprodfournprice) && $objp->idprodfournprice > 0) {
3970
                    $optstart .= ' data-product-id="' . dol_escape_htmltag($objp->rowid) . '"';
3971
                    $optstart .= ' data-price-id="' . dol_escape_htmltag($objp->idprodfournprice) . '"';
3972
                    $optstart .= ' data-qty="' . dol_escape_htmltag($objp->quantity) . '"';
3973
                    $optstart .= ' data-up="' . dol_escape_htmltag(price2num($objp->unitprice)) . '"';
3974
                    $optstart .= ' data-up-locale="' . dol_escape_htmltag(price($objp->unitprice)) . '"';
3975
                    $optstart .= ' data-discount="' . dol_escape_htmltag($outdiscount) . '"';
3976
                    $optstart .= ' data-tvatx="' . dol_escape_htmltag(price2num($objp->tva_tx)) . '"';
3977
                    $optstart .= ' data-tvatx-formated="' . dol_escape_htmltag(price($objp->tva_tx, 0, $langs, 1, -1, 2)) . '"';
3978
                    $optstart .= ' data-default-vat-code="' . dol_escape_htmltag($objp->default_vat_code) . '"';
3979
                    $optstart .= ' data-supplier-ref="' . dol_escape_htmltag($objp->ref_fourn) . '"';
3980
                    if (isModEnabled('multicurrency')) {
3981
                        $optstart .= ' data-multicurrency-code="' . dol_escape_htmltag($objp->multicurrency_code) . '"';
3982
                        $optstart .= ' data-multicurrency-up="' . dol_escape_htmltag($objp->multicurrency_unitprice) . '"';
3983
                    }
3984
                }
3985
                $optstart .= ' data-description="' . dol_escape_htmltag($objp->description, 0, 1) . '"';
3986
3987
                $outarrayentry = array(
3988
                    'key' => $outkey,
3989
                    'value' => $outref,
3990
                    'label' => $outvallabel,
3991
                    'qty' => $outqty,
3992
                    'price_qty_ht' => price2num($objp->fprice, 'MU'),    // Keep higher resolution for price for the min qty
3993
                    'price_unit_ht' => price2num($objp->unitprice, 'MU'),    // This is used to fill the Unit Price
3994
                    'price_ht' => price2num($objp->unitprice, 'MU'),        // This is used to fill the Unit Price (for compatibility)
3995
                    'tva_tx_formated' => price($objp->tva_tx, 0, $langs, 1, -1, 2),
3996
                    'tva_tx' => price2num($objp->tva_tx),
3997
                    'default_vat_code' => $objp->default_vat_code,
3998
                    'supplier_ref' => $objp->ref_fourn,
3999
                    'discount' => $outdiscount,
4000
                    'type' => $outtype,
4001
                    'duration_value' => $outdurationvalue,
4002
                    'duration_unit' => $outdurationunit,
4003
                    'disabled' => empty($objp->idprodfournprice),
4004
                    'description' => $objp->description
4005
                );
4006
                if (isModEnabled('multicurrency')) {
4007
                    $outarrayentry['multicurrency_code'] = $objp->multicurrency_code;
4008
                    $outarrayentry['multicurrency_unitprice'] = price2num($objp->multicurrency_unitprice, 'MU');
4009
                }
4010
4011
                $parameters = array(
4012
                    'objp' => &$objp,
4013
                    'optstart' => &$optstart,
4014
                    'optlabel' => &$optlabel,
4015
                    'outvallabel' => &$outvallabel,
4016
                    'outarrayentry' => &$outarrayentry
4017
                );
4018
                $reshook = $hookmanager->executeHooks('selectProduitsFournisseurListOption', $parameters, $this);
4019
4020
4021
                // Add new entry
4022
                // "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
4023
                // "label" value of json key array is used by jQuery automatically as text for combo box
4024
                $out .= $optstart . ' data-html="' . dol_escape_htmltag($optlabel) . '">' . $optlabel . "</option>\n";
4025
                $outarraypush = array(
4026
                    'key' => $outkey,
4027
                    'value' => $outref,
4028
                    'label' => $outvallabel,
4029
                    'qty' => $outqty,
4030
                    'price_qty_ht' => price2num($objp->fprice, 'MU'),        // Keep higher resolution for price for the min qty
4031
                    'price_qty_ht_locale' => price($objp->fprice),
4032
                    'price_unit_ht' => price2num($objp->unitprice, 'MU'),    // This is used to fill the Unit Price
4033
                    'price_unit_ht_locale' => price($objp->unitprice),
4034
                    'price_ht' => price2num($objp->unitprice, 'MU'),        // This is used to fill the Unit Price (for compatibility)
4035
                    'tva_tx_formated' => price($objp->tva_tx),
4036
                    'tva_tx' => price2num($objp->tva_tx),
4037
                    'default_vat_code' => $objp->default_vat_code,
4038
                    'supplier_ref' => $objp->ref_fourn,
4039
                    'discount' => $outdiscount,
4040
                    'type' => $outtype,
4041
                    'duration_value' => $outdurationvalue,
4042
                    'duration_unit' => $outdurationunit,
4043
                    'disabled' => empty($objp->idprodfournprice),
4044
                    'description' => $objp->description
4045
                );
4046
                if (isModEnabled('multicurrency')) {
4047
                    $outarraypush['multicurrency_code'] = $objp->multicurrency_code;
4048
                    $outarraypush['multicurrency_unitprice'] = price2num($objp->multicurrency_unitprice, 'MU');
4049
                }
4050
                array_push($outarray, $outarraypush);
4051
4052
                // Example of var_dump $outarray
4053
                // array(1) {[0]=>array(6) {[key"]=>string(1) "2" ["value"]=>string(3) "ppp"
4054
                //           ["label"]=>string(76) "ppp (<strong>f</strong>ff2) - ppp - 20,00 Euros/1unité (20,00 Euros/unité)"
4055
                //           ["qty"]=>string(1) "1" ["discount"]=>string(1) "0" ["disabled"]=>bool(false)
4056
                //}
4057
                //var_dump($outval); var_dump(utf8_check($outval)); var_dump(json_encode($outval));
4058
                //$outval=array('label'=>'ppp (<strong>f</strong>ff2) - ppp - 20,00 Euros/ Unité (20,00 Euros/unité)');
4059
                //var_dump($outval); var_dump(utf8_check($outval)); var_dump(json_encode($outval));
4060
4061
                $i++;
4062
            }
4063
            $out .= '</select>';
4064
4065
            $this->db->free($result);
4066
4067
            include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
4068
            $out .= ajax_combobox($htmlname);
4069
        } else {
4070
            dol_print_error($this->db);
4071
        }
4072
4073
        if (empty($outputmode)) {
4074
            return $out;
4075
        }
4076
        return $outarray;
4077
    }
4078
4079
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4080
4081
    /**
4082
     *    Return list of suppliers prices for a product
4083
     *
4084
     * @param int $productid Id of product
4085
     * @param string $htmlname Name of HTML field
4086
     * @param int $selected_supplier Pre-selected supplier if more than 1 result
4087
     * @return        string
4088
     */
4089
    public function select_product_fourn_price($productid, $htmlname = 'productfournpriceid', $selected_supplier = 0)
4090
    {
4091
		// phpcs:enable
4092
        global $langs, $conf;
4093
4094
        $langs->load('stocks');
4095
4096
        $sql = "SELECT p.rowid, p.ref, p.label, p.price, p.duration, pfp.fk_soc,";
4097
        $sql .= " pfp.ref_fourn, pfp.rowid as idprodfournprice, pfp.price as fprice, pfp.remise_percent, pfp.quantity, pfp.unitprice,";
4098
        $sql .= " pfp.fk_supplier_price_expression, pfp.fk_product, pfp.tva_tx, s.nom as name";
4099
        $sql .= " FROM " . $this->db->prefix() . "product as p";
4100
        $sql .= " LEFT JOIN " . $this->db->prefix() . "product_fournisseur_price as pfp ON p.rowid = pfp.fk_product";
4101
        $sql .= " LEFT JOIN " . $this->db->prefix() . "societe as s ON pfp.fk_soc = s.rowid";
4102
        $sql .= " WHERE pfp.entity IN (" . getEntity('productsupplierprice') . ")";
4103
        $sql .= " AND p.tobuy = 1";
4104
        $sql .= " AND s.fournisseur = 1";
4105
        $sql .= " AND p.rowid = " . ((int) $productid);
4106
        if (!getDolGlobalString('PRODUCT_BEST_SUPPLIER_PRICE_PRESELECTED')) {
4107
            $sql .= " ORDER BY s.nom, pfp.ref_fourn DESC";
4108
        } else {
4109
            $sql .= " ORDER BY pfp.unitprice ASC";
4110
        }
4111
4112
        dol_syslog(get_class($this) . "::select_product_fourn_price", LOG_DEBUG);
4113
        $result = $this->db->query($sql);
4114
4115
        if ($result) {
4116
            $num = $this->db->num_rows($result);
4117
4118
            $form = '<select class="flat" id="select_' . $htmlname . '" name="' . $htmlname . '">';
4119
4120
            if (!$num) {
4121
                $form .= '<option value="0">-- ' . $langs->trans("NoSupplierPriceDefinedForThisProduct") . ' --</option>';
4122
            } else {
4123
                require_once constant('DOL_DOCUMENT_ROOT') . '/product/dynamic_price/class/price_parser.class.php';
4124
                $form .= '<option value="0">&nbsp;</option>';
4125
4126
                $i = 0;
4127
                while ($i < $num) {
4128
                    $objp = $this->db->fetch_object($result);
4129
4130
                    $opt = '<option value="' . $objp->idprodfournprice . '"';
4131
                    //if there is only one supplier, preselect it
4132
                    if ($num == 1 || ($selected_supplier > 0 && $objp->fk_soc == $selected_supplier) || ($i == 0 && getDolGlobalString('PRODUCT_BEST_SUPPLIER_PRICE_PRESELECTED'))) {
4133
                        $opt .= ' selected';
4134
                    }
4135
                    $opt .= '>' . $objp->name . ' - ' . $objp->ref_fourn . ' - ';
4136
4137
                    if (isModEnabled('dynamicprices') && !empty($objp->fk_supplier_price_expression)) {
4138
                        $prod_supplier = new ProductFournisseur($this->db);
4139
                        $prod_supplier->product_fourn_price_id = $objp->idprodfournprice;
4140
                        $prod_supplier->id = $productid;
4141
                        $prod_supplier->fourn_qty = $objp->quantity;
4142
                        $prod_supplier->fourn_tva_tx = $objp->tva_tx;
4143
                        $prod_supplier->fk_supplier_price_expression = $objp->fk_supplier_price_expression;
4144
4145
                        require_once constant('DOL_DOCUMENT_ROOT') . '/product/dynamic_price/class/price_parser.class.php';
4146
                        $priceparser = new PriceParser($this->db);
4147
                        $price_result = $priceparser->parseProductSupplier($prod_supplier);
4148
                        if ($price_result >= 0) {
4149
                            $objp->fprice = $price_result;
4150
                            if ($objp->quantity >= 1) {
4151
                                $objp->unitprice = $objp->fprice / $objp->quantity;
4152
                            }
4153
                        }
4154
                    }
4155
                    if ($objp->quantity == 1) {
4156
                        $opt .= price($objp->fprice * (getDolGlobalString('DISPLAY_DISCOUNTED_SUPPLIER_PRICE') ? (1 - $objp->remise_percent / 100) : 1), 1, $langs, 0, 0, -1, $conf->currency) . "/";
4157
                    }
4158
4159
                    $opt .= $objp->quantity . ' ';
4160
4161
                    if ($objp->quantity == 1) {
4162
                        $opt .= $langs->trans("Unit");
4163
                    } else {
4164
                        $opt .= $langs->trans("Units");
4165
                    }
4166
                    if ($objp->quantity > 1) {
4167
                        $opt .= " - ";
4168
                        $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");
4169
                    }
4170
                    if ($objp->duration) {
4171
                        $opt .= " - " . $objp->duration;
4172
                    }
4173
                    $opt .= "</option>\n";
4174
4175
                    $form .= $opt;
4176
                    $i++;
4177
                }
4178
            }
4179
4180
            $form .= '</select>';
4181
            $this->db->free($result);
4182
            return $form;
4183
        } else {
4184
            dol_print_error($this->db);
4185
            return '';
4186
        }
4187
    }
4188
4189
4190
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4191
    /**
4192
     *      Load into cache list of payment terms
4193
     *
4194
     * @return     int             Nb of lines loaded, <0 if KO
4195
     */
4196
    public function load_cache_conditions_paiements()
4197
    {
4198
		// phpcs:enable
4199
        global $langs;
4200
4201
        $num = count($this->cache_conditions_paiements);
4202
        if ($num > 0) {
4203
            return 0; // Cache already loaded
4204
        }
4205
4206
        dol_syslog(__METHOD__, LOG_DEBUG);
4207
4208
        $sql = "SELECT rowid, code, libelle as label, deposit_percent";
4209
        $sql .= " FROM " . $this->db->prefix() . 'c_payment_term';
4210
        $sql .= " WHERE entity IN (" . getEntity('c_payment_term') . ")";
4211
        $sql .= " AND active > 0";
4212
        $sql .= " ORDER BY sortorder";
4213
4214
        $resql = $this->db->query($sql);
4215
        if ($resql) {
4216
            $num = $this->db->num_rows($resql);
4217
            $i = 0;
4218
            while ($i < $num) {
4219
                $obj = $this->db->fetch_object($resql);
4220
4221
                // Si traduction existe, on l'utilise, sinon on prend le libelle par default
4222
                $label = ($langs->trans("PaymentConditionShort" . $obj->code) != "PaymentConditionShort" . $obj->code ? $langs->trans("PaymentConditionShort" . $obj->code) : ($obj->label != '-' ? $obj->label : ''));
4223
                $this->cache_conditions_paiements[$obj->rowid]['code'] = $obj->code;
4224
                $this->cache_conditions_paiements[$obj->rowid]['label'] = $label;
4225
                $this->cache_conditions_paiements[$obj->rowid]['deposit_percent'] = $obj->deposit_percent;
4226
                $i++;
4227
            }
4228
4229
            //$this->cache_conditions_paiements=dol_sort_array($this->cache_conditions_paiements, 'label', 'asc', 0, 0, 1);     // We use the field sortorder of table
4230
4231
            return $num;
4232
        } else {
4233
            dol_print_error($this->db);
4234
            return -1;
4235
        }
4236
    }
4237
4238
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4239
4240
    /**
4241
     *      Load int a cache property th elist of possible delivery delays.
4242
     *
4243
     * @return     int             Nb of lines loaded, <0 if KO
4244
     */
4245
    public function load_cache_availability()
4246
    {
4247
		// phpcs:enable
4248
        global $langs;
4249
4250
        $num = count($this->cache_availability);    // TODO Use $conf->cache['availability'] instead of $this->cache_availability
4251
        if ($num > 0) {
4252
            return 0; // Cache already loaded
4253
        }
4254
4255
        dol_syslog(__METHOD__, LOG_DEBUG);
4256
4257
        $langs->load('propal');
4258
4259
        $sql = "SELECT rowid, code, label, position";
4260
        $sql .= " FROM " . $this->db->prefix() . 'c_availability';
4261
        $sql .= " WHERE active > 0";
4262
4263
        $resql = $this->db->query($sql);
4264
        if ($resql) {
4265
            $num = $this->db->num_rows($resql);
4266
            $i = 0;
4267
            while ($i < $num) {
4268
                $obj = $this->db->fetch_object($resql);
4269
4270
                // Si traduction existe, on l'utilise, sinon on prend le libelle par default
4271
                $label = ($langs->trans("AvailabilityType" . $obj->code) != "AvailabilityType" . $obj->code ? $langs->trans("AvailabilityType" . $obj->code) : ($obj->label != '-' ? $obj->label : ''));
4272
                $this->cache_availability[$obj->rowid]['code'] = $obj->code;
4273
                $this->cache_availability[$obj->rowid]['label'] = $label;
4274
                $this->cache_availability[$obj->rowid]['position'] = $obj->position;
4275
                $i++;
4276
            }
4277
4278
            $this->cache_availability = dol_sort_array($this->cache_availability, 'position', 'asc', 0, 0, 1);
4279
4280
            return $num;
4281
        } else {
4282
            dol_print_error($this->db);
4283
            return -1;
4284
        }
4285
    }
4286
4287
    /**
4288
     * Return the list of type of delay available.
4289
     *
4290
     * @param   string      $selected Id du type de delais pre-selectionne
4291
     * @param   string      $htmlname Nom de la zone select
4292
     * @param   string      $filtertype To add a filter
4293
     * @param   int         $addempty Add empty entry
4294
     * @param   string      $morecss More CSS
4295
     * @return  void
4296
     */
4297
    public function selectAvailabilityDelay($selected = '', $htmlname = 'availid', $filtertype = '', $addempty = 0, $morecss = '')
4298
    {
4299
        global $langs, $user;
4300
4301
        $this->load_cache_availability();
4302
4303
        dol_syslog(__METHOD__ . " selected=" . $selected . ", htmlname=" . $htmlname, LOG_DEBUG);
4304
4305
        print '<select id="' . $htmlname . '" class="flat' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '">';
4306
        if ($addempty) {
4307
            print '<option value="0">&nbsp;</option>';
4308
        }
4309
        foreach ($this->cache_availability as $id => $arrayavailability) {
4310
            if ($selected == $id) {
4311
                print '<option value="' . $id . '" selected>';
4312
            } else {
4313
                print '<option value="' . $id . '">';
4314
            }
4315
            print dol_escape_htmltag($arrayavailability['label']);
4316
            print '</option>';
4317
        }
4318
        print '</select>';
4319
        if ($user->admin) {
4320
            print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
4321
        }
4322
        print ajax_combobox($htmlname);
4323
    }
4324
4325
    /**
4326
     * Load into cache cache_demand_reason, array of input reasons
4327
     *
4328
     * @return     int             Nb of lines loaded, <0 if KO
4329
     */
4330
    public function loadCacheInputReason()
4331
    {
4332
        global $langs;
4333
4334
        $num = count($this->cache_demand_reason);    // TODO Use $conf->cache['input_reason'] instead of $this->cache_demand_reason
4335
        if ($num > 0) {
4336
            return 0; // Cache already loaded
4337
        }
4338
4339
        $sql = "SELECT rowid, code, label";
4340
        $sql .= " FROM " . $this->db->prefix() . 'c_input_reason';
4341
        $sql .= " WHERE active > 0";
4342
4343
        $resql = $this->db->query($sql);
4344
        if ($resql) {
4345
            $num = $this->db->num_rows($resql);
4346
            $i = 0;
4347
            $tmparray = array();
4348
            while ($i < $num) {
4349
                $obj = $this->db->fetch_object($resql);
4350
4351
                // Si traduction existe, on l'utilise, sinon on prend le libelle par default
4352
                $label = ($obj->label != '-' ? $obj->label : '');
4353
                if ($langs->trans("DemandReasonType" . $obj->code) != "DemandReasonType" . $obj->code) {
4354
                    $label = $langs->trans("DemandReasonType" . $obj->code); // So translation key DemandReasonTypeSRC_XXX will work
4355
                }
4356
                if ($langs->trans($obj->code) != $obj->code) {
4357
                    $label = $langs->trans($obj->code); // So translation key SRC_XXX will work
4358
                }
4359
4360
                $tmparray[$obj->rowid]['id'] = $obj->rowid;
4361
                $tmparray[$obj->rowid]['code'] = $obj->code;
4362
                $tmparray[$obj->rowid]['label'] = $label;
4363
                $i++;
4364
            }
4365
4366
            $this->cache_demand_reason = dol_sort_array($tmparray, 'label', 'asc', 0, 0, 1);
4367
4368
            unset($tmparray);
4369
            return $num;
4370
        } else {
4371
            dol_print_error($this->db);
4372
            return -1;
4373
        }
4374
    }
4375
4376
    /**
4377
     * Return list of input reason (events that triggered an object creation, like after sending an emailing, making an advert, ...)
4378
     * List found into table c_input_reason loaded by loadCacheInputReason
4379
     *
4380
     * @param   string      $selected   Id or code of type origin to select by default
4381
     * @param   string      $htmlname   Nom de la zone select
4382
     * @param   string      $exclude    To exclude a code value (Example: SRC_PROP)
4383
     * @param   int         $addempty   Add an empty entry
4384
     * @param   string      $morecss    Add more css to the HTML select component
4385
     * @param   int         $notooltip  Do not show the tooltip for admin
4386
     * @return  void
4387
     */
4388
    public function selectInputReason($selected = '', $htmlname = 'demandreasonid', $exclude = '', $addempty = 0, $morecss = '', $notooltip = 0)
4389
    {
4390
        global $langs, $user;
4391
4392
        $this->loadCacheInputReason();
4393
4394
        print '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" id="select_' . $htmlname . '" name="' . $htmlname . '">';
4395
        if ($addempty) {
4396
            print '<option value="0"' . (empty($selected) ? ' selected' : '') . '>&nbsp;</option>';
4397
        }
4398
        foreach ($this->cache_demand_reason as $id => $arraydemandreason) {
4399
            if ($arraydemandreason['code'] == $exclude) {
4400
                continue;
4401
            }
4402
4403
            if ($selected && ($selected == $arraydemandreason['id'] || $selected == $arraydemandreason['code'])) {
4404
                print '<option value="' . $arraydemandreason['id'] . '" selected>';
4405
            } else {
4406
                print '<option value="' . $arraydemandreason['id'] . '">';
4407
            }
4408
            $label = $arraydemandreason['label']; // Translation of label was already done into the ->loadCacheInputReason
4409
            print $langs->trans($label);
4410
            print '</option>';
4411
        }
4412
        print '</select>';
4413
        if ($user->admin && empty($notooltip)) {
4414
            print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
4415
        }
4416
        print ajax_combobox('select_' . $htmlname);
4417
    }
4418
4419
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4420
4421
    /**
4422
     *      Charge dans cache la liste des types de paiements possibles
4423
     *
4424
     * @return     int                 Nb of lines loaded, <0 if KO
4425
     */
4426
    public function load_cache_types_paiements()
4427
    {
4428
		// phpcs:enable
4429
        global $langs;
4430
4431
        $num = count($this->cache_types_paiements);        // TODO Use $conf->cache['payment_mode'] instead of $this->cache_types_paiements
4432
        if ($num > 0) {
4433
            return $num; // Cache already loaded
4434
        }
4435
4436
        dol_syslog(__METHOD__, LOG_DEBUG);
4437
4438
        $this->cache_types_paiements = array();
4439
4440
        $sql = "SELECT id, code, libelle as label, type, active";
4441
        $sql .= " FROM " . $this->db->prefix() . "c_paiement";
4442
        $sql .= " WHERE entity IN (" . getEntity('c_paiement') . ")";
4443
4444
        $resql = $this->db->query($sql);
4445
        if ($resql) {
4446
            $num = $this->db->num_rows($resql);
4447
            $i = 0;
4448
            while ($i < $num) {
4449
                $obj = $this->db->fetch_object($resql);
4450
4451
                // Si traduction existe, on l'utilise, sinon on prend le libelle par default
4452
                $label = ($langs->transnoentitiesnoconv("PaymentTypeShort" . $obj->code) != "PaymentTypeShort" . $obj->code ? $langs->transnoentitiesnoconv("PaymentTypeShort" . $obj->code) : ($obj->label != '-' ? $obj->label : ''));
4453
                $this->cache_types_paiements[$obj->id]['id'] = $obj->id;
4454
                $this->cache_types_paiements[$obj->id]['code'] = $obj->code;
4455
                $this->cache_types_paiements[$obj->id]['label'] = $label;
4456
                $this->cache_types_paiements[$obj->id]['type'] = $obj->type;
4457
                $this->cache_types_paiements[$obj->id]['active'] = $obj->active;
4458
                $i++;
4459
            }
4460
4461
            $this->cache_types_paiements = dol_sort_array($this->cache_types_paiements, 'label', 'asc', 0, 0, 1);
4462
4463
            return $num;
4464
        } else {
4465
            dol_print_error($this->db);
4466
            return -1;
4467
        }
4468
    }
4469
4470
4471
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4472
4473
    /**
4474
     *    print list of payment modes.
4475
     *    Constant MAIN_DEFAULT_PAYMENT_TERM_ID can be used to set default value but scope is all application, probably not what you want.
4476
     *    See instead to force the default value by the caller.
4477
     *
4478
     * @param int $selected Id of payment term to preselect by default
4479
     * @param string $htmlname Nom de la zone select
4480
     * @param int $filtertype If > 0, include payment terms with deposit percentage (for objects other than invoices and invoice templates)
4481
     * @param int $addempty Add an empty entry
4482
     * @param int $noinfoadmin 0=Add admin info, 1=Disable admin info
4483
     * @param string $morecss Add more CSS on select tag
4484
     * @param int    $deposit_percent < 0 : deposit_percent input makes no sense (for example, in list filters)
4485
     *                                0 : use default deposit percentage from entry
4486
     *                                > 0 : force deposit percentage (for example, from company object)
4487
     * @param int $noprint if set to one we return the html to print, if 0 (default) we print it
4488
     * @return    void|string
4489
     * @deprecated Use getSelectConditionsPaiements() instead and handle noprint locally.
4490
     */
4491
    public function select_conditions_paiements($selected = 0, $htmlname = 'condid', $filtertype = -1, $addempty = 0, $noinfoadmin = 0, $morecss = '', $deposit_percent = -1, $noprint = 0)
4492
    {
4493
		// phpcs:enable
4494
        $out = $this->getSelectConditionsPaiements($selected, $htmlname, $filtertype, $addempty, $noinfoadmin, $morecss, $deposit_percent);
4495
        if (empty($noprint)) {
4496
            print $out;
4497
        } else {
4498
            return $out;
4499
        }
4500
    }
4501
4502
4503
    /**
4504
     *    Return list of payment modes.
4505
     *    Constant MAIN_DEFAULT_PAYMENT_TERM_ID can be used to set default value but scope is all application, probably not what you want.
4506
     *    See instead to force the default value by the caller.
4507
     *
4508
     * @param int $selected Id of payment term to preselect by default
4509
     * @param string $htmlname Nom de la zone select
4510
     * @param int $filtertype If > 0, include payment terms with deposit percentage (for objects other than invoices and invoice templates)
4511
     * @param int $addempty Add an empty entry
4512
     * @param int $noinfoadmin 0=Add admin info, 1=Disable admin info
4513
     * @param string $morecss Add more CSS on select tag
4514
     * @param int    $deposit_percent < 0 : deposit_percent input makes no sense (for example, in list filters)
4515
     *                                0 : use default deposit percentage from entry
4516
     *                                > 0 : force deposit percentage (for example, from company object)
4517
     * @return    string                        String for the HTML select component
4518
     */
4519
    public function getSelectConditionsPaiements($selected = 0, $htmlname = 'condid', $filtertype = -1, $addempty = 0, $noinfoadmin = 0, $morecss = '', $deposit_percent = -1)
4520
    {
4521
        global $langs, $user, $conf;
4522
4523
        $out = '';
4524
        dol_syslog(__METHOD__ . " selected=" . $selected . ", htmlname=" . $htmlname, LOG_DEBUG);
4525
4526
        $this->load_cache_conditions_paiements();
4527
4528
        // Set default value if not already set by caller
4529
        if (empty($selected) && getDolGlobalString('MAIN_DEFAULT_PAYMENT_TERM_ID')) {
4530
            dol_syslog(__METHOD__ . "Using deprecated option MAIN_DEFAULT_PAYMENT_TERM_ID", LOG_NOTICE);
4531
            $selected = getDolGlobalString('MAIN_DEFAULT_PAYMENT_TERM_ID');
4532
        }
4533
4534
        $out .= '<select id="' . $htmlname . '" class="flat selectpaymentterms' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '">';
4535
        if ($addempty) {
4536
            $out .= '<option value="0">&nbsp;</option>';
4537
        }
4538
4539
        $selectedDepositPercent = null;
4540
4541
        foreach ($this->cache_conditions_paiements as $id => $arrayconditions) {
4542
            if ($filtertype <= 0 && !empty($arrayconditions['deposit_percent'])) {
4543
                continue;
4544
            }
4545
4546
            if ($selected == $id) {
4547
                $selectedDepositPercent = $deposit_percent > 0 ? $deposit_percent : $arrayconditions['deposit_percent'];
4548
                $out .= '<option value="' . $id . '" data-deposit_percent="' . $arrayconditions['deposit_percent'] . '" selected>';
4549
            } else {
4550
                $out .= '<option value="' . $id . '" data-deposit_percent="' . $arrayconditions['deposit_percent'] . '">';
4551
            }
4552
            $label = $arrayconditions['label'];
4553
4554
            if (!empty($arrayconditions['deposit_percent'])) {
4555
                $label = str_replace('__DEPOSIT_PERCENT__', $deposit_percent > 0 ? $deposit_percent : $arrayconditions['deposit_percent'], $label);
4556
            }
4557
4558
            $out .= $label;
4559
            $out .= '</option>';
4560
        }
4561
        $out .= '</select>';
4562
        if ($user->admin && empty($noinfoadmin)) {
4563
            $out .= info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
4564
        }
4565
        $out .= ajax_combobox($htmlname);
4566
4567
        if ($deposit_percent >= 0) {
4568
            $out .= ' <span id="' . $htmlname . '_deposit_percent_container"' . (empty($selectedDepositPercent) ? ' style="display: none"' : '') . '>';
4569
            $out .= $langs->trans('DepositPercent') . ' : ';
4570
            $out .= '<input id="' . $htmlname . '_deposit_percent" name="' . $htmlname . '_deposit_percent" class="maxwidth50" value="' . $deposit_percent . '" />';
4571
            $out .= '</span>';
4572
            $out .= '
4573
				<script nonce="' . getNonce() . '">
4574
					$(document).ready(function () {
4575
						$("#' . $htmlname . '").change(function () {
4576
							let $selected = $(this).find("option:selected");
4577
							let depositPercent = $selected.attr("data-deposit_percent");
4578
4579
							if (depositPercent.length > 0) {
4580
								$("#' . $htmlname . '_deposit_percent_container").show().find("#' . $htmlname . '_deposit_percent").val(depositPercent);
4581
							} else {
4582
								$("#' . $htmlname . '_deposit_percent_container").hide();
4583
							}
4584
4585
							return true;
4586
						});
4587
					});
4588
				</script>';
4589
        }
4590
4591
        return $out;
4592
    }
4593
4594
4595
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4596
4597
    /**
4598
     * Return list of payment methods
4599
     * Constant MAIN_DEFAULT_PAYMENT_TYPE_ID can used to set default value but scope is all application, probably not what you want.
4600
     *
4601
     * @param   string  $selected       Id or code or preselected payment mode
4602
     * @param   string  $htmlname       Name of select field
4603
     * @param   string  $filtertype     To filter on field type in llx_c_paiement ('CRDT' or 'DBIT' or array('code'=>xx,'label'=>zz))
4604
     * @param   int     $format         0=id+label, 1=code+code, 2=code+label, 3=id+code
4605
     * @param   int     $empty          1=can be empty, 0 otherwise
4606
     * @param   int     $noadmininfo    0=Add admin info, 1=Disable admin info
4607
     * @param   int     $maxlength      Max length of label
4608
     * @param   int     $active         Active or not, -1 = all
4609
     * @param   string  $morecss        Add more CSS on select tag
4610
     * @param   int     $nooutput       1=Return string, do not send to output
4611
     * @return  string|void             String for the HTML select component
4612
     */
4613
    public function select_types_paiements($selected = '', $htmlname = 'paiementtype', $filtertype = '', $format = 0, $empty = 1, $noadmininfo = 0, $maxlength = 0, $active = 1, $morecss = '', $nooutput = 0)
4614
    {
4615
		// phpcs:enable
4616
        global $langs, $user, $conf;
4617
4618
        $out = '';
4619
4620
        dol_syslog(__METHOD__ . " " . $selected . ", " . $htmlname . ", " . $filtertype . ", " . $format, LOG_DEBUG);
4621
4622
        $filterarray = array();
4623
        if ($filtertype == 'CRDT') {
4624
            $filterarray = array(0, 2, 3);
4625
        } elseif ($filtertype == 'DBIT') {
4626
            $filterarray = array(1, 2, 3);
4627
        } elseif ($filtertype != '' && $filtertype != '-1') {
4628
            $filterarray = explode(',', $filtertype);
4629
        }
4630
4631
        $this->load_cache_types_paiements();
4632
4633
        // Set default value if not already set by caller
4634
        if (empty($selected) && getDolGlobalString('MAIN_DEFAULT_PAYMENT_TYPE_ID')) {
4635
            dol_syslog(__METHOD__ . "Using deprecated option MAIN_DEFAULT_PAYMENT_TYPE_ID", LOG_NOTICE);
4636
            $selected = getDolGlobalString('MAIN_DEFAULT_PAYMENT_TYPE_ID');
4637
        }
4638
4639
        $out .= '<select id="select' . $htmlname . '" class="flat selectpaymenttypes' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '">';
4640
        if ($empty) {
4641
            $out .= '<option value="">&nbsp;</option>';
4642
        }
4643
        foreach ($this->cache_types_paiements as $id => $arraytypes) {
4644
            // If not good status
4645
            if ($active >= 0 && $arraytypes['active'] != $active) {
4646
                continue;
4647
            }
4648
4649
            // We skip of the user requested to filter on specific payment methods
4650
            if (count($filterarray) && !in_array($arraytypes['type'], $filterarray)) {
4651
                continue;
4652
            }
4653
4654
            // We discard empty lines if showempty is on because an empty line has already been output.
4655
            if ($empty && empty($arraytypes['code'])) {
4656
                continue;
4657
            }
4658
4659
            if ($format == 0) {
4660
                $out .= '<option value="' . $id . '"';
4661
            } elseif ($format == 1) {
4662
                $out .= '<option value="' . $arraytypes['code'] . '"';
4663
            } elseif ($format == 2) {
4664
                $out .= '<option value="' . $arraytypes['code'] . '"';
4665
            } elseif ($format == 3) {
4666
                $out .= '<option value="' . $id . '"';
4667
            }
4668
            // Print attribute selected or not
4669
            if ($format == 1 || $format == 2) {
4670
                if ($selected == $arraytypes['code']) {
4671
                    $out .= ' selected';
4672
                }
4673
            } else {
4674
                if ($selected == $id) {
4675
                    $out .= ' selected';
4676
                }
4677
            }
4678
            $out .= '>';
4679
            $value = '';
4680
            if ($format == 0) {
4681
                $value = ($maxlength ? dol_trunc($arraytypes['label'], $maxlength) : $arraytypes['label']);
4682
            } elseif ($format == 1) {
4683
                $value = $arraytypes['code'];
4684
            } elseif ($format == 2) {
4685
                $value = ($maxlength ? dol_trunc($arraytypes['label'], $maxlength) : $arraytypes['label']);
4686
            } elseif ($format == 3) {
4687
                $value = $arraytypes['code'];
4688
            }
4689
            $out .= $value ? $value : '&nbsp;';
4690
            $out .= '</option>';
4691
        }
4692
        $out .= '</select>';
4693
        if ($user->admin && !$noadmininfo) {
4694
            $out .= info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
4695
        }
4696
        $out .= ajax_combobox('select' . $htmlname);
4697
4698
        if (empty($nooutput)) {
4699
            print $out;
4700
        } else {
4701
            return $out;
4702
        }
4703
    }
4704
4705
4706
    /**
4707
     *  Selection HT or TTC
4708
     *
4709
     * @param string $selected Id pre-selectionne
4710
     * @param string $htmlname Nom de la zone select
4711
     * @param int    $addjscombo Add js combo
4712
     * @return    string                    Code of HTML select to chose tax or not
4713
     */
4714
    public function selectPriceBaseType($selected = '', $htmlname = 'price_base_type', $addjscombo = 0)
4715
    {
4716
        global $langs;
4717
4718
        $return = '<select class="flat maxwidth100" id="select_' . $htmlname . '" name="' . $htmlname . '">';
4719
        $options = array(
4720
            'HT' => $langs->trans("HT"),
4721
            'TTC' => $langs->trans("TTC")
4722
        );
4723
        foreach ($options as $id => $value) {
4724
            if ($selected == $id) {
4725
                $return .= '<option value="' . $id . '" selected>' . $value;
4726
            } else {
4727
                $return .= '<option value="' . $id . '">' . $value;
4728
            }
4729
            $return .= '</option>';
4730
        }
4731
        $return .= '</select>';
4732
        if ($addjscombo) {
4733
            $return .= ajax_combobox('select_' . $htmlname);
4734
        }
4735
4736
        return $return;
4737
    }
4738
4739
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4740
4741
    /**
4742
     *      Load in cache list of transport mode
4743
     *
4744
     * @return     int                 Nb of lines loaded, <0 if KO
4745
     */
4746
    public function load_cache_transport_mode()
4747
    {
4748
		// phpcs:enable
4749
        global $langs;
4750
4751
        $num = count($this->cache_transport_mode);        // TODO Use $conf->cache['payment_mode'] instead of $this->cache_transport_mode
4752
        if ($num > 0) {
4753
            return $num; // Cache already loaded
4754
        }
4755
4756
        dol_syslog(__METHOD__, LOG_DEBUG);
4757
4758
        $this->cache_transport_mode = array();
4759
4760
        $sql = "SELECT rowid, code, label, active";
4761
        $sql .= " FROM " . $this->db->prefix() . "c_transport_mode";
4762
        $sql .= " WHERE entity IN (" . getEntity('c_transport_mode') . ")";
4763
4764
        $resql = $this->db->query($sql);
4765
        if ($resql) {
4766
            $num = $this->db->num_rows($resql);
4767
            $i = 0;
4768
            while ($i < $num) {
4769
                $obj = $this->db->fetch_object($resql);
4770
4771
                // If traduction exist, we use it else we take the default label
4772
                $label = ($langs->transnoentitiesnoconv("PaymentTypeShort" . $obj->code) != "PaymentTypeShort" . $obj->code ? $langs->transnoentitiesnoconv("PaymentTypeShort" . $obj->code) : ($obj->label != '-' ? $obj->label : ''));
4773
                $this->cache_transport_mode[$obj->rowid]['rowid'] = $obj->rowid;
4774
                $this->cache_transport_mode[$obj->rowid]['code'] = $obj->code;
4775
                $this->cache_transport_mode[$obj->rowid]['label'] = $label;
4776
                $this->cache_transport_mode[$obj->rowid]['active'] = $obj->active;
4777
                $i++;
4778
            }
4779
4780
            $this->cache_transport_mode = dol_sort_array($this->cache_transport_mode, 'label', 'asc', 0, 0, 1);
4781
4782
            return $num;
4783
        } else {
4784
            dol_print_error($this->db);
4785
            return -1;
4786
        }
4787
    }
4788
4789
    /**
4790
     *      Return list of transport mode for intracomm report
4791
     *
4792
     * @param string $selected Id of the transport mode preselected
4793
     * @param string $htmlname Name of the select field
4794
     * @param int $format 0=id+label, 1=code+code, 2=code+label, 3=id+code
4795
     * @param int $empty 1=can be empty, 0 else
4796
     * @param int $noadmininfo 0=Add admin info, 1=Disable admin info
4797
     * @param int $maxlength Max length of label
4798
     * @param int $active Active or not, -1 = all
4799
     * @param string $morecss Add more CSS on select tag
4800
     * @return    void
4801
     */
4802
    public function selectTransportMode($selected = '', $htmlname = 'transportmode', $format = 0, $empty = 1, $noadmininfo = 0, $maxlength = 0, $active = 1, $morecss = '')
4803
    {
4804
        global $langs, $user;
4805
4806
        dol_syslog(__METHOD__ . " " . $selected . ", " . $htmlname . ", " . $format, LOG_DEBUG);
4807
4808
        $this->load_cache_transport_mode();
4809
4810
        print '<select id="select' . $htmlname . '" class="flat selectmodetransport' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '">';
4811
        if ($empty) {
4812
            print '<option value="">&nbsp;</option>';
4813
        }
4814
        foreach ($this->cache_transport_mode as $id => $arraytypes) {
4815
            // If not good status
4816
            if ($active >= 0 && $arraytypes['active'] != $active) {
4817
                continue;
4818
            }
4819
4820
            // We discard empty line if showempty is on because an empty line has already been output.
4821
            if ($empty && empty($arraytypes['code'])) {
4822
                continue;
4823
            }
4824
4825
            if ($format == 0) {
4826
                print '<option value="' . $id . '"';
4827
            } elseif ($format == 1) {
4828
                print '<option value="' . $arraytypes['code'] . '"';
4829
            } elseif ($format == 2) {
4830
                print '<option value="' . $arraytypes['code'] . '"';
4831
            } elseif ($format == 3) {
4832
                print '<option value="' . $id . '"';
4833
            }
4834
            // If text is selected, we compare with code, else with id
4835
            if (preg_match('/[a-z]/i', $selected) && $selected == $arraytypes['code']) {
4836
                print ' selected';
4837
            } elseif ($selected == $id) {
4838
                print ' selected';
4839
            }
4840
            print '>';
4841
            $value = '';
4842
            if ($format == 0) {
4843
                $value = ($maxlength ? dol_trunc($arraytypes['label'], $maxlength) : $arraytypes['label']);
4844
            } elseif ($format == 1) {
4845
                $value = $arraytypes['code'];
4846
            } elseif ($format == 2) {
4847
                $value = ($maxlength ? dol_trunc($arraytypes['label'], $maxlength) : $arraytypes['label']);
4848
            } elseif ($format == 3) {
4849
                $value = $arraytypes['code'];
4850
            }
4851
            print $value ? $value : '&nbsp;';
4852
            print '</option>';
4853
        }
4854
        print '</select>';
4855
        if ($user->admin && !$noadmininfo) {
4856
            print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
4857
        }
4858
    }
4859
4860
    /**
4861
     * Return a HTML select list of shipping mode
4862
     *
4863
     * @param string    $selected       Id shipping mode preselected
4864
     * @param string    $htmlname       Name of select zone
4865
     * @param string    $filtre         To filter list. This parameter must not come from input of users
4866
     * @param int       $useempty       1=Add an empty value in list, 2=Add an empty value in list only if there is more than 2 entries.
4867
     * @param string    $moreattrib     To add more attribute on select
4868
     * @param int       $noinfoadmin    0=Add admin info, 1=Disable admin info
4869
     * @param string    $morecss        More CSS
4870
     * @return void
4871
     */
4872
    public function selectShippingMethod($selected = '', $htmlname = 'shipping_method_id', $filtre = '', $useempty = 0, $moreattrib = '', $noinfoadmin = 0, $morecss = '')
4873
    {
4874
        global $langs, $user;
4875
4876
        $langs->load("admin");
4877
        $langs->load("deliveries");
4878
4879
        $sql = "SELECT rowid, code, libelle as label";
4880
        $sql .= " FROM " . $this->db->prefix() . "c_shipment_mode";
4881
        $sql .= " WHERE active > 0";
4882
        if ($filtre) {
4883
            $sql .= " AND " . $filtre;
4884
        }
4885
        $sql .= " ORDER BY libelle ASC";
4886
4887
        dol_syslog(get_class($this) . "::selectShippingMode", LOG_DEBUG);
4888
        $result = $this->db->query($sql);
4889
        if ($result) {
4890
            $num = $this->db->num_rows($result);
4891
            $i = 0;
4892
            if ($num) {
4893
                print '<select id="select' . $htmlname . '" class="flat selectshippingmethod' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '"' . ($moreattrib ? ' ' . $moreattrib : '') . '>';
4894
                if ($useempty == 1 || ($useempty == 2 && $num > 1)) {
4895
                    print '<option value="-1">&nbsp;</option>';
4896
                }
4897
                while ($i < $num) {
4898
                    $obj = $this->db->fetch_object($result);
4899
                    if ($selected == $obj->rowid) {
4900
                        print '<option value="' . $obj->rowid . '" selected>';
4901
                    } else {
4902
                        print '<option value="' . $obj->rowid . '">';
4903
                    }
4904
                    print ($langs->trans("SendingMethod" . strtoupper($obj->code)) != "SendingMethod" . strtoupper($obj->code)) ? $langs->trans("SendingMethod" . strtoupper($obj->code)) : $obj->label;
4905
                    print '</option>';
4906
                    $i++;
4907
                }
4908
                print "</select>";
4909
                if ($user->admin && empty($noinfoadmin)) {
4910
                    print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
4911
                }
4912
4913
                print ajax_combobox('select' . $htmlname);
4914
            } else {
4915
                print $langs->trans("NoShippingMethodDefined");
4916
            }
4917
        } else {
4918
            dol_print_error($this->db);
4919
        }
4920
    }
4921
4922
    /**
4923
     *    Display form to select shipping mode
4924
     *
4925
     * @param string $page Page
4926
     * @param string $selected Id of shipping mode
4927
     * @param string $htmlname Name of select html field
4928
     * @param int $addempty 1=Add an empty value in list, 2=Add an empty value in list only if there is more than 2 entries.
4929
     * @return    void
4930
     */
4931
    public function formSelectShippingMethod($page, $selected = '', $htmlname = 'shipping_method_id', $addempty = 0)
4932
    {
4933
        global $langs;
4934
4935
        $langs->load("deliveries");
4936
4937
        if ($htmlname != "none") {
4938
            print '<form method="POST" action="' . $page . '">';
4939
            print '<input type="hidden" name="action" value="setshippingmethod">';
4940
            print '<input type="hidden" name="token" value="' . newToken() . '">';
4941
            $this->selectShippingMethod($selected, $htmlname, '', $addempty);
4942
            print '<input type="submit" class="button valignmiddle" value="' . $langs->trans("Modify") . '">';
4943
            print '</form>';
4944
        } else {
4945
            if ($selected) {
4946
                $code = $langs->getLabelFromKey($this->db, $selected, 'c_shipment_mode', 'rowid', 'code');
4947
                print $langs->trans("SendingMethod" . strtoupper($code));
4948
            } else {
4949
                print "&nbsp;";
4950
            }
4951
        }
4952
    }
4953
4954
    /**
4955
     * Creates HTML last in cycle situation invoices selector
4956
     *
4957
     * @param string $selected Preselected ID
4958
     * @param int $socid Company ID
4959
     *
4960
     * @return    string                     HTML select
4961
     */
4962
    public function selectSituationInvoices($selected = '', $socid = 0)
4963
    {
4964
        global $langs;
4965
4966
        $langs->load('bills');
4967
4968
        $opt = '<option value="" selected></option>';
4969
        $sql = "SELECT rowid, ref, situation_cycle_ref, situation_counter, situation_final, fk_soc";
4970
        $sql .= ' FROM ' . $this->db->prefix() . 'facture';
4971
        $sql .= ' WHERE entity IN (' . getEntity('invoice') . ')';
4972
        $sql .= ' AND situation_counter >= 1';
4973
        $sql .= ' AND fk_soc = ' . (int) $socid;
4974
        $sql .= ' AND type <> 2';
4975
        $sql .= ' ORDER by situation_cycle_ref, situation_counter desc';
4976
        $resql = $this->db->query($sql);
4977
4978
        if ($resql && $this->db->num_rows($resql) > 0) {
4979
            // Last seen cycle
4980
            $ref = 0;
4981
            while ($obj = $this->db->fetch_object($resql)) {
4982
                //Same cycle ?
4983
                if ($obj->situation_cycle_ref != $ref) {
4984
                    // Just seen this cycle
4985
                    $ref = $obj->situation_cycle_ref;
4986
                    //not final ?
4987
                    if ($obj->situation_final != 1) {
4988
                        //Not prov?
4989
                        if (substr($obj->ref, 1, 4) != 'PROV') {
4990
                            if ($selected == $obj->rowid) {
4991
                                $opt .= '<option value="' . $obj->rowid . '" selected>' . $obj->ref . '</option>';
4992
                            } else {
4993
                                $opt .= '<option value="' . $obj->rowid . '">' . $obj->ref . '</option>';
4994
                            }
4995
                        }
4996
                    }
4997
                }
4998
            }
4999
        } else {
5000
            dol_syslog("Error sql=" . $sql . ", error=" . $this->error, LOG_ERR);
5001
        }
5002
        if ($opt == '<option value ="" selected></option>') {
5003
            $opt = '<option value ="0" selected>' . $langs->trans('NoSituations') . '</option>';
5004
        }
5005
        return $opt;
5006
    }
5007
5008
    /**
5009
     * Creates HTML units selector (code => label)
5010
     *
5011
     * @param   string      $selected   Preselected Unit ID
5012
     * @param   string      $htmlname   Select name
5013
     * @param   int<0,1>    $showempty  Add an empty line
5014
     * @param   string      $unit_type  Restrict to one given unit type
5015
     * @return  string                  HTML select
5016
     */
5017
    public function selectUnits($selected = '', $htmlname = 'units', $showempty = 0, $unit_type = '')
5018
    {
5019
        global $langs;
5020
5021
        $langs->load('products');
5022
5023
        $return = '<select class="flat" id="' . $htmlname . '" name="' . $htmlname . '">';
5024
5025
        $sql = "SELECT rowid, label, code FROM " . $this->db->prefix() . "c_units";
5026
        $sql .= ' WHERE active > 0';
5027
        if (!empty($unit_type)) {
5028
            $sql .= " AND unit_type = '" . $this->db->escape($unit_type) . "'";
5029
        }
5030
        $sql .= " ORDER BY sortorder";
5031
5032
        $resql = $this->db->query($sql);
5033
        if ($resql && $this->db->num_rows($resql) > 0) {
5034
            if ($showempty) {
5035
                $return .= '<option value="none"></option>';
5036
            }
5037
5038
            while ($res = $this->db->fetch_object($resql)) {
5039
                $unitLabel = $res->label;
5040
                if (!empty($langs->tab_translate['unit' . $res->code])) {    // check if Translation is available before
5041
                    $unitLabel = $langs->trans('unit' . $res->code) != $res->label ? $langs->trans('unit' . $res->code) : $res->label;
5042
                }
5043
5044
                if ($selected == $res->rowid) {
5045
                    $return .= '<option value="' . $res->rowid . '" selected>' . $unitLabel . '</option>';
5046
                } else {
5047
                    $return .= '<option value="' . $res->rowid . '">' . $unitLabel . '</option>';
5048
                }
5049
            }
5050
            $return .= '</select>';
5051
        }
5052
        return $return;
5053
    }
5054
5055
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5056
5057
    /**
5058
     *  Return a HTML select list of bank accounts
5059
     *
5060
     * @param int|string    $selected       Id account preselected
5061
     * @param string        $htmlname       Name of select zone
5062
     * @param int           $status         Status of searched accounts (0=open, 1=closed, 2=both)
5063
     * @param string        $filtre         To filter the list. This parameter must not come from input of users
5064
     * @param int|string    $useempty       1=Add an empty value in list, 2=Add an empty value in list only if there is more than 2 entries.
5065
     * @param string        $moreattrib     To add more attribute on select
5066
     * @param int           $showcurrency   Show currency in label
5067
     * @param string        $morecss        More CSS
5068
     * @param int           $nooutput       1=Return string, do not send to output
5069
     * @return int|string                   If noouput=0: Return integer <0 if error, Num of bank account found if OK (0, 1, 2, ...), If nooutput=1: Return a HTML select string.
5070
     */
5071
    public function select_comptes($selected = '', $htmlname = 'accountid', $status = 0, $filtre = '', $useempty = 0, $moreattrib = '', $showcurrency = 0, $morecss = '', $nooutput = 0)
5072
    {
5073
		// phpcs:enable
5074
        global $langs;
5075
5076
        $out = '';
5077
5078
        $langs->load("admin");
5079
        $num = 0;
5080
5081
        $sql = "SELECT rowid, label, bank, clos as status, currency_code";
5082
        $sql .= " FROM " . $this->db->prefix() . "bank_account";
5083
        $sql .= " WHERE entity IN (" . getEntity('bank_account') . ")";
5084
        if ($status != 2) {
5085
            $sql .= " AND clos = " . (int) $status;
5086
        }
5087
        if ($filtre) {  // TODO Support USF
5088
            $sql .= " AND " . $filtre;
5089
        }
5090
        $sql .= " ORDER BY label";
5091
5092
        dol_syslog(get_class($this) . "::select_comptes", LOG_DEBUG);
5093
        $result = $this->db->query($sql);
5094
        if ($result) {
5095
            $num = $this->db->num_rows($result);
5096
            $i = 0;
5097
            if ($num) {
5098
                $out .= '<select id="select' . $htmlname . '" class="flat selectbankaccount' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '"' . ($moreattrib ? ' ' . $moreattrib : '') . '>';
5099
5100
                if (!empty($useempty) && !is_numeric($useempty)) {
5101
                    $out .= '<option value="-1">' . $langs->trans($useempty) . '</option>';
5102
                } elseif ($useempty == 1 || ($useempty == 2 && $num > 1)) {
5103
                    $out .= '<option value="-1">&nbsp;</option>';
5104
                }
5105
5106
                while ($i < $num) {
5107
                    $obj = $this->db->fetch_object($result);
5108
                    if ($selected == $obj->rowid || ($useempty == 2 && $num == 1 && empty($selected))) {
5109
                        $out .= '<option value="' . $obj->rowid . '" data-currency-code="' . $obj->currency_code . '" selected>';
5110
                    } else {
5111
                        $out .= '<option value="' . $obj->rowid . '" data-currency-code="' . $obj->currency_code . '">';
5112
                    }
5113
                    $out .= trim($obj->label);
5114
                    if ($showcurrency) {
5115
                        $out .= ' (' . $obj->currency_code . ')';
5116
                    }
5117
                    if ($status == 2 && $obj->status == 1) {
5118
                        $out .= ' (' . $langs->trans("Closed") . ')';
5119
                    }
5120
                    $out .= '</option>';
5121
                    $i++;
5122
                }
5123
                $out .= "</select>";
5124
                $out .= ajax_combobox('select' . $htmlname);
5125
            } else {
5126
                if ($status == 0) {
5127
                    $out .= '<span class="opacitymedium">' . $langs->trans("NoActiveBankAccountDefined") . '</span>';
5128
                } else {
5129
                    $out .= '<span class="opacitymedium">' . $langs->trans("NoBankAccountFound") . '</span>';
5130
                }
5131
            }
5132
        } else {
5133
            dol_print_error($this->db);
5134
        }
5135
5136
        // Output or return
5137
        if (empty($nooutput)) {
5138
            print $out;
5139
        } else {
5140
            return $out;
5141
        }
5142
5143
        return $num;
5144
    }
5145
5146
    /**
5147
     * Return a HTML select list of establishment
5148
     *
5149
     * @param   string  $selected       Id establishment preselected
5150
     * @param   string  $htmlname       Name of select zone
5151
     * @param   int     $status         Status of searched establishment (0=open, 1=closed, 2=both)
5152
     * @param   string  $filtre         To filter list. This parameter must not come from input of users
5153
     * @param   int     $useempty       1=Add an empty value in list, 2=Add an empty value in list only if there is more than 2 entries.
5154
     * @param   string  $moreattrib     To add more attribute on select
5155
     * @return  int                     Return integer <0 if error, Num of establishment found if OK (0, 1, 2, ...)
5156
     */
5157
    public function selectEstablishments($selected = '', $htmlname = 'entity', $status = 0, $filtre = '', $useempty = 0, $moreattrib = '')
5158
    {
5159
        global $langs;
5160
5161
        $langs->load("admin");
5162
        $num = 0;
5163
5164
        $sql = "SELECT rowid, name, fk_country, status, entity";
5165
        $sql .= " FROM " . $this->db->prefix() . "establishment";
5166
        $sql .= " WHERE 1=1";
5167
        if ($status != 2) {
5168
            $sql .= " AND status = " . (int) $status;
5169
        }
5170
        if ($filtre) {  // TODO Support USF
5171
            $sql .= " AND " . $filtre;
5172
        }
5173
        $sql .= " ORDER BY name";
5174
5175
        dol_syslog(get_class($this) . "::select_establishment", LOG_DEBUG);
5176
        $result = $this->db->query($sql);
5177
        if ($result) {
5178
            $num = $this->db->num_rows($result);
5179
            $i = 0;
5180
            if ($num) {
5181
                print '<select id="select' . $htmlname . '" class="flat selectestablishment" name="' . $htmlname . '"' . ($moreattrib ? ' ' . $moreattrib : '') . '>';
5182
                if ($useempty == 1 || ($useempty == 2 && $num > 1)) {
5183
                    print '<option value="-1">&nbsp;</option>';
5184
                }
5185
5186
                while ($i < $num) {
5187
                    $obj = $this->db->fetch_object($result);
5188
                    if ($selected == $obj->rowid) {
5189
                        print '<option value="' . $obj->rowid . '" selected>';
5190
                    } else {
5191
                        print '<option value="' . $obj->rowid . '">';
5192
                    }
5193
                    print trim($obj->name);
5194
                    if ($status == 2 && $obj->status == 1) {
5195
                        print ' (' . $langs->trans("Closed") . ')';
5196
                    }
5197
                    print '</option>';
5198
                    $i++;
5199
                }
5200
                print "</select>";
5201
            } else {
5202
                if ($status == 0) {
5203
                    print '<span class="opacitymedium">' . $langs->trans("NoActiveEstablishmentDefined") . '</span>';
5204
                } else {
5205
                    print '<span class="opacitymedium">' . $langs->trans("NoEstablishmentFound") . '</span>';
5206
                }
5207
            }
5208
5209
            return $num;
5210
        } else {
5211
            dol_print_error($this->db);
5212
            return -1;
5213
        }
5214
    }
5215
5216
    /**
5217
     * Display form to select bank account
5218
     *
5219
     * @param string    $page       Page
5220
     * @param string    $selected   Id of bank account
5221
     * @param string    $htmlname   Name of select html field
5222
     * @param int       $addempty   1=Add an empty value in list, 2=Add an empty value in list only if there is more than 2 entries.
5223
     * @return                      void
5224
     */
5225
    public function formSelectAccount($page, $selected = '', $htmlname = 'fk_account', $addempty = 0)
5226
    {
5227
        global $langs;
5228
        if ($htmlname != "none") {
5229
            print '<form method="POST" action="' . $page . '">';
5230
            print '<input type="hidden" name="action" value="setbankaccount">';
5231
            print '<input type="hidden" name="token" value="' . newToken() . '">';
5232
            print img_picto('', 'bank_account', 'class="pictofixedwidth"');
5233
            $nbaccountfound = $this->select_comptes($selected, $htmlname, 0, '', $addempty);
5234
            if ($nbaccountfound > 0) {
5235
                print '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
5236
            }
5237
            print '</form>';
5238
        } else {
5239
            $langs->load('banks');
5240
5241
            if ($selected) {
5242
                require_once constant('DOL_DOCUMENT_ROOT') . '/compta/bank/class/account.class.php';
5243
                $bankstatic = new Account($this->db);
5244
                $result = $bankstatic->fetch($selected);
5245
                if ($result) {
5246
                    print $bankstatic->getNomUrl(1);
5247
                }
5248
            } else {
5249
                print "&nbsp;";
5250
            }
5251
        }
5252
    }
5253
5254
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5255
5256
    /**
5257
     * Return list of categories having chosen type
5258
     *
5259
     * @param   string|int          $type           Type of category ('customer', 'supplier', 'contact', 'product', 'member'). Old mode (0, 1, 2, ...) is deprecated.
5260
     * @param   string              $selected       Id of category preselected or 'auto' (autoselect category if there is only one element). Not used if $outputmode = 1.
5261
     * @param   string              $htmlname       HTML field name
5262
     * @param   int                 $maxlength      Maximum length for labels
5263
     * @param   int|string|array    $fromid         Keep only or Exclude (depending on $include parameter) all categories (including the leaf $fromid) into the tree after this id $fromid.
5264
     *                                              $fromid can be an :
5265
     *                                              - int (id of category)
5266
     *                                              - string (categories ids separated by comma)
5267
     *                                              - array (list of categories ids)
5268
     * @param   int<0,3>            $outputmode     0=HTML select string, 1=Array with full label only, 2=Array extended, 3=Array with full picto + label
5269
     * @param   int<0,1>            $include        [=0] Removed or 1=Keep only
5270
     * @param   string              $morecss        More CSS
5271
     * @param     int<0,2>          $useempty       0=No empty value, 1=Add an empty value in list, 2=Add an empty value in list only if there is more than 2 entries. Default is 1.
5272
     * @return  string|array<int,string>|array<int,array{id:int,fulllabel:string,color:string,picto:string}>|array<int,array{rowid:int,id:int,fk_parent:int,label:string,description:string,color:string,position:string,visible:int,ref_ext:string,picto:string,fullpath:string,fulllabel:string}>     String list or Array of categories
5273
     * @see select_categories()
5274
     */
5275
    public function select_all_categories($type, $selected = '', $htmlname = "parent", $maxlength = 64, $fromid = 0, $outputmode = 0, $include = 0, $morecss = '', $useempty = 1)
5276
    {
5277
		// phpcs:enable
5278
        global $conf, $langs;
5279
        $langs->load("categories");
5280
5281
        include_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
5282
5283
        // For backward compatibility
5284
        if (is_numeric($type)) {
5285
            dol_syslog(__METHOD__ . ': using numeric value for parameter type is deprecated. Use string code instead.', LOG_WARNING);
5286
        }
5287
5288
        if ($type === Categorie::TYPE_BANK_LINE) {
5289
            // TODO Move this into common category feature
5290
            $cate_arbo = array();
5291
            $sql = "SELECT c.label, c.rowid";
5292
            $sql .= " FROM " . $this->db->prefix() . "bank_categ as c";
5293
            $sql .= " WHERE entity = " . $conf->entity;
5294
            $sql .= " ORDER BY c.label";
5295
            $result = $this->db->query($sql);
5296
            if ($result) {
5297
                $num = $this->db->num_rows($result);
5298
                $i = 0;
5299
                while ($i < $num) {
5300
                    $objp = $this->db->fetch_object($result);
5301
                    if ($objp) {
5302
                        $cate_arbo[$objp->rowid] = array('id' => $objp->rowid, 'fulllabel' => $objp->label, 'color' => '', 'picto' => 'category');
5303
                    }
5304
                    $i++;
5305
                }
5306
                $this->db->free($result);
5307
            } else {
5308
                dol_print_error($this->db);
5309
            }
5310
        } else {
5311
            $cat = new Categorie($this->db);
5312
            $cate_arbo = $cat->get_full_arbo($type, $fromid, $include);
5313
        }
5314
5315
        $outarray = array();
5316
        $outarrayrichhtml = array();
5317
5318
5319
        $output = '<select class="flat minwidth100' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" id="' . $htmlname . '">';
5320
        if (is_array($cate_arbo)) {
5321
            $num = count($cate_arbo);
5322
5323
            if (!$num) {
5324
                $output .= '<option value="-1" disabled>' . $langs->trans("NoCategoriesDefined") . '</option>';
5325
            } else {
5326
                if ($useempty == 1 || ($useempty == 2 && $num > 1)) {
5327
                    $output .= '<option value="-1">&nbsp;</option>';
5328
                }
5329
                foreach ($cate_arbo as $key => $value) {
5330
                    if ($cate_arbo[$key]['id'] == $selected || ($selected === 'auto' && count($cate_arbo) == 1)) {
5331
                        $add = 'selected ';
5332
                    } else {
5333
                        $add = '';
5334
                    }
5335
5336
                    $labeltoshow = img_picto('', 'category', 'class="pictofixedwidth" style="color: #' . $cate_arbo[$key]['color'] . '"');
5337
                    $labeltoshow .= dol_trunc($cate_arbo[$key]['fulllabel'], $maxlength, 'middle');
5338
5339
                    $outarray[$cate_arbo[$key]['id']] = $cate_arbo[$key]['fulllabel'];
5340
5341
                    $outarrayrichhtml[$cate_arbo[$key]['id']] = $labeltoshow;
5342
5343
                    $output .= '<option ' . $add . 'value="' . $cate_arbo[$key]['id'] . '"';
5344
                    $output .= ' data-html="' . dol_escape_htmltag($labeltoshow) . '"';
5345
                    $output .= '>';
5346
                    $output .= dol_trunc($cate_arbo[$key]['fulllabel'], $maxlength, 'middle');
5347
                    $output .= '</option>';
5348
5349
                    $cate_arbo[$key]['data-html'] = $labeltoshow;
5350
                }
5351
            }
5352
        }
5353
        $output .= '</select>';
5354
        $output .= "\n";
5355
5356
        if ($outputmode == 2) {
5357
            // TODO: handle error when $cate_arbo is not an array
5358
            return $cate_arbo;
5359
        } elseif ($outputmode == 1) {
5360
            return $outarray;
5361
        } elseif ($outputmode == 3) {
5362
            return $outarrayrichhtml;
5363
        }
5364
        return $output;
5365
    }
5366
5367
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5368
5369
    /**
5370
     *     Show a confirmation HTML form or AJAX popup
5371
     *
5372
     * @param string $page Url of page to call if confirmation is OK
5373
     * @param string $title Title
5374
     * @param string $question Question
5375
     * @param string $action Action
5376
     * @param array{text:string}|array<array{label:string,type:string,size:string,morecss:string,moreattr:string,style:string}> $formquestion   An array with complementary inputs to add into forms: array(array('label'=> ,'type'=> , 'size'=>, 'morecss'=>, 'moreattr'=>'autofocus' or 'style=...'))
5377
     * @param string $selectedchoice "" or "no" or "yes"
5378
     * @param int|string $useajax 0=No, 1=Yes use Ajax to show the popup, 2=Yes and also submit page with &confirm=no if choice is No, 'xxx'=Yes and preoutput confirm box with div id=dialog-confirm-xxx
5379
     * @param int $height Force height of box
5380
     * @param int $width Force width of box
5381
     * @return    void
5382
     * @deprecated
5383
     * @see formconfirm()
5384
     */
5385
    public function form_confirm($page, $title, $question, $action, $formquestion = array(), $selectedchoice = "", $useajax = 0, $height = 170, $width = 500)
5386
    {
5387
		// phpcs:enable
5388
        dol_syslog(__METHOD__ . ': using form_confirm is deprecated. Use formconfim instead.', LOG_WARNING);
5389
        print $this->formconfirm($page, $title, $question, $action, $formquestion, $selectedchoice, $useajax, $height, $width);
5390
    }
5391
5392
    /**
5393
     *     Show a confirmation HTML form or AJAX popup.
5394
     *     Easiest way to use this is with useajax=1.
5395
     *     If you use useajax='xxx', you must also add jquery code to trigger opening of box (with correct parameters)
5396
     *     just after calling this method. For example:
5397
     *       print '<script nonce="'.getNonce().'" type="text/javascript">'."\n";
5398
     *       print 'jQuery(document).ready(function() {'."\n";
5399
     *       print 'jQuery(".xxxlink").click(function(e) { jQuery("#aparamid").val(jQuery(this).attr("rel")); jQuery("#dialog-confirm-xxx").dialog("open"); return false; });'."\n";
5400
     *       print '});'."\n";
5401
     *       print '</script>'."\n";
5402
     *
5403
     * @param string        $page               Url of page to call if confirmation is OK. Can contains parameters (param 'action' and 'confirm' will be reformatted)
5404
     * @param string        $title              Title
5405
     * @param string        $question           Question
5406
     * @param string        $action             Action
5407
     * @param array<array{name:string,value:string,values:string[],default:string,label:string,type:string,size:string,morecss:string,moreattr:string,style:string,inputko?:int<0,1>}>|string|null  $formquestion       An array with complementary inputs to add into forms: array(array('label'=> ,'type'=> , 'size'=>, 'morecss'=>, 'moreattr'=>'autofocus' or 'style=...'))
5408
     *                                                                                                                                                                                                                  'type' can be 'text', 'password', 'checkbox', 'radio', 'date', 'datetime', 'select', 'multiselect', 'morecss',
5409
     *                                                                                                                                                                                                                  'other', 'onecolumn' or 'hidden'...
5410
     * @param int<0,1>|''|'no'|'yes'|'1'|'0'    $selectedchoice     '' or 'no', or 'yes' or '1', 1, '0' or 0
5411
     * @param int<0,2>|string   $useajax            0=No, 1=Yes use Ajax to show the popup, 2=Yes and also submit page with &confirm=no if choice is No, 'xxx'=Yes and preoutput confirm box with div id=dialog-confirm-xxx
5412
     * @param int|string    $height             Force height of box (0 = auto)
5413
     * @param int           $width              Force width of box ('999' or '90%'). Ignored and forced to 90% on smartphones.
5414
     * @param int           $disableformtag     1=Disable form tag. Can be used if we are already inside a <form> section.
5415
     * @param string        $labelbuttonyes     Label for Yes
5416
     * @param string        $labelbuttonno      Label for No
5417
     * @return string                           HTML ajax code if a confirm ajax popup is required, Pure HTML code if it's an html form
5418
     */
5419
    public function formconfirm($page, $title, $question, $action, $formquestion = '', $selectedchoice = '', $useajax = 0, $height = 0, $width = 500, $disableformtag = 0, $labelbuttonyes = 'Yes', $labelbuttonno = 'No')
5420
    {
5421
        global $langs, $conf;
5422
5423
        $more = '<!-- formconfirm - before call, page=' . dol_escape_htmltag($page) . ' -->';
5424
        $formconfirm = '';
5425
        $inputok = array();
5426
        $inputko = array();
5427
5428
        // Clean parameters
5429
        $newselectedchoice = empty($selectedchoice) ? "no" : $selectedchoice;
5430
        if ($conf->browser->layout == 'phone') {
5431
            $width = '95%';
5432
        }
5433
5434
        // Set height automatically if not defined
5435
        if (empty($height)) {
5436
            $height = 220;
5437
            if (is_array($formquestion) && count($formquestion) > 2) {
5438
                $height += ((count($formquestion) - 2) * 24);
5439
            }
5440
        }
5441
5442
        if (is_array($formquestion) && !empty($formquestion)) {
5443
            // First add hidden fields and value
5444
            foreach ($formquestion as $key => $input) {
5445
                if (is_array($input) && !empty($input)) {
5446
                    if ($input['type'] == 'hidden') {
5447
                        $moreattr = (!empty($input['moreattr']) ? ' ' . $input['moreattr'] : '');
5448
                        $morecss = (!empty($input['morecss']) ? ' ' . $input['morecss'] : '');
5449
5450
                        $more .= '<input type="hidden" id="' . dol_escape_htmltag($input['name']) . '" name="' . dol_escape_htmltag($input['name']) . '" value="' . dol_escape_htmltag($input['value']) . '" class="' . $morecss . '"' . $moreattr . '>' . "\n";
5451
                    }
5452
                }
5453
            }
5454
5455
            // Now add questions
5456
            $moreonecolumn = '';
5457
            $more .= '<div class="tagtable paddingtopbottomonly centpercent noborderspacing">' . "\n";
5458
            foreach ($formquestion as $key => $input) {
5459
                if (is_array($input) && !empty($input)) {
5460
                    $size = (!empty($input['size']) ? ' size="' . $input['size'] . '"' : '');    // deprecated. Use morecss instead.
5461
                    $moreattr = (!empty($input['moreattr']) ? ' ' . $input['moreattr'] : '');
5462
                    $morecss = (!empty($input['morecss']) ? ' ' . $input['morecss'] : '');
5463
5464
                    if ($input['type'] == 'text') {
5465
                        $more .= '<div class="tagtr"><div class="tagtd' . (empty($input['tdclass']) ? '' : (' ' . $input['tdclass'])) . '">' . $input['label'] . '</div><div class="tagtd"><input type="text" class="flat' . $morecss . '" id="' . dol_escape_htmltag($input['name']) . '" name="' . dol_escape_htmltag($input['name']) . '"' . $size . ' value="' . (empty($input['value']) ? '' : $input['value']) . '"' . $moreattr . ' /></div></div>' . "\n";
5466
                    } elseif ($input['type'] == 'password') {
5467
                        $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";
5468
                    } elseif ($input['type'] == 'textarea') {
5469
                        /*$more .= '<div class="tagtr"><div class="tagtd'.(empty($input['tdclass']) ? '' : (' '.$input['tdclass'])).'">'.$input['label'].'</div><div class="tagtd">';
5470
                        $more .= '<textarea name="'.$input['name'].'" class="'.$morecss.'"'.$moreattr.'>';
5471
                        $more .= $input['value'];
5472
                        $more .= '</textarea>';
5473
                        $more .= '</div></div>'."\n";*/
5474
                        $moreonecolumn .= '<div class="margintoponly">';
5475
                        $moreonecolumn .= $input['label'] . '<br>';
5476
                        $moreonecolumn .= '<textarea name="' . dol_escape_htmltag($input['name']) . '" id="' . dol_escape_htmltag($input['name']) . '" class="' . $morecss . '"' . $moreattr . '>';
5477
                        $moreonecolumn .= $input['value'];
5478
                        $moreonecolumn .= '</textarea>';
5479
                        $moreonecolumn .= '</div>';
5480
                    } elseif (in_array($input['type'], ['select', 'multiselect'])) {
5481
                        if (empty($morecss)) {
5482
                            $morecss = 'minwidth100';
5483
                        }
5484
5485
                        $show_empty = isset($input['select_show_empty']) ? $input['select_show_empty'] : 1;
5486
                        $key_in_label = isset($input['select_key_in_label']) ? $input['select_key_in_label'] : 0;
5487
                        $value_as_key = isset($input['select_value_as_key']) ? $input['select_value_as_key'] : 0;
5488
                        $translate = isset($input['select_translate']) ? $input['select_translate'] : 0;
5489
                        $maxlen = isset($input['select_maxlen']) ? $input['select_maxlen'] : 0;
5490
                        $disabled = isset($input['select_disabled']) ? $input['select_disabled'] : 0;
5491
                        $sort = isset($input['select_sort']) ? $input['select_sort'] : '';
5492
5493
                        $more .= '<div class="tagtr"><div class="tagtd' . (empty($input['tdclass']) ? '' : (' ' . $input['tdclass'])) . '">';
5494
                        if (!empty($input['label'])) {
5495
                            $more .= $input['label'] . '</div><div class="tagtd left">';
5496
                        }
5497
                        if ($input['type'] == 'select') {
5498
                            $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);
5499
                        } else {
5500
                            $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);
5501
                        }
5502
                        $more .= '</div></div>' . "\n";
5503
                    } elseif ($input['type'] == 'checkbox') {
5504
                        $more .= '<div class="tagtr">';
5505
                        $more .= '<div class="tagtd' . (empty($input['tdclass']) ? '' : (' ' . $input['tdclass'])) . '"><label for="' . dol_escape_htmltag($input['name']) . '">' . $input['label'] . '</label></div><div class="tagtd">';
5506
                        $more .= '<input type="checkbox" class="flat' . ($morecss ? ' ' . $morecss : '') . '" id="' . dol_escape_htmltag($input['name']) . '" name="' . dol_escape_htmltag($input['name']) . '"' . $moreattr;
5507
                        if (!is_bool($input['value']) && $input['value'] != 'false' && $input['value'] != '0' && $input['value'] != '') {
5508
                            $more .= ' checked';
5509
                        }
5510
                        if (is_bool($input['value']) && $input['value']) {
5511
                            $more .= ' checked';
5512
                        }
5513
                        if (isset($input['disabled'])) {
5514
                            $more .= ' disabled';
5515
                        }
5516
                        $more .= ' /></div>';
5517
                        $more .= '</div>' . "\n";
5518
                    } elseif ($input['type'] == 'radio') {
5519
                        $i = 0;
5520
                        foreach ($input['values'] as $selkey => $selval) {
5521
                            $more .= '<div class="tagtr">';
5522
                            if (isset($input['label'])) {
5523
                                if ($i == 0) {
5524
                                    $more .= '<div class="tagtd' . (empty($input['tdclass']) ? ' tdtop' : (' tdtop ' . $input['tdclass'])) . '">' . $input['label'] . '</div>';
5525
                                } else {
5526
                                    $more .= '<div class="tagtd' . (empty($input['tdclass']) ? '' : (' "' . $input['tdclass'])) . '">&nbsp;</div>';
5527
                                }
5528
                            }
5529
                            $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;
5530
                            if (!empty($input['disabled'])) {
5531
                                $more .= ' disabled';
5532
                            }
5533
                            if (isset($input['default']) && $input['default'] === $selkey) {
5534
                                $more .= ' checked="checked"';
5535
                            }
5536
                            $more .= ' /> ';
5537
                            $more .= '<label for="' . dol_escape_htmltag($input['name'] . $selkey) . '" class="valignmiddle">' . $selval . '</label>';
5538
                            $more .= '</div></div>' . "\n";
5539
                            $i++;
5540
                        }
5541
                    } elseif ($input['type'] == 'date' || $input['type'] == 'datetime') {
5542
                        $more .= '<div class="tagtr"><div class="tagtd' . (empty($input['tdclass']) ? '' : (' ' . $input['tdclass'])) . '">' . $input['label'] . '</div>';
5543
                        $more .= '<div class="tagtd">';
5544
                        $addnowlink = (empty($input['datenow']) ? 0 : 1);
5545
                        $h = $m = 0;
5546
                        if ($input['type'] == 'datetime') {
5547
                            $h = isset($input['hours']) ? $input['hours'] : 1;
5548
                            $m = isset($input['minutes']) ? $input['minutes'] : 1;
5549
                        }
5550
                        $more .= $this->selectDate(isset($input['value']) ? $input['value'] : -1, $input['name'], $h, $m, 0, '', 1, $addnowlink);
5551
                        $more .= '</div></div>' . "\n";
5552
                        $formquestion[] = array('name' => $input['name'] . 'day');
5553
                        $formquestion[] = array('name' => $input['name'] . 'month');
5554
                        $formquestion[] = array('name' => $input['name'] . 'year');
5555
                        $formquestion[] = array('name' => $input['name'] . 'hour');
5556
                        $formquestion[] = array('name' => $input['name'] . 'min');
5557
                    } elseif ($input['type'] == 'other') { // can be 1 column or 2 depending if label is set or not
5558
                        $more .= '<div class="tagtr"><div class="tagtd' . (empty($input['tdclass']) ? '' : (' ' . $input['tdclass'])) . '">';
5559
                        if (!empty($input['label'])) {
5560
                            $more .= $input['label'] . '</div><div class="tagtd">';
5561
                        }
5562
                        $more .= $input['value'];
5563
                        $more .= '</div></div>' . "\n";
5564
                    } elseif ($input['type'] == 'onecolumn') {
5565
                        $moreonecolumn .= '<div class="margintoponly">';
5566
                        $moreonecolumn .= $input['value'];
5567
                        $moreonecolumn .= '</div>' . "\n";
5568
                    } elseif ($input['type'] == 'hidden') {
5569
                        // Do nothing more, already added by a previous loop
5570
                    } elseif ($input['type'] == 'separator') {
5571
                        $more .= '<br>';
5572
                    } else {
5573
                        $more .= 'Error type ' . $input['type'] . ' for the confirm box is not a supported type';
5574
                    }
5575
                }
5576
            }
5577
            $more .= '</div>' . "\n";
5578
            $more .= $moreonecolumn;
5579
        }
5580
5581
        // JQUERY method dialog is broken with smartphone, we use standard HTML.
5582
        // 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
5583
        // See page product/card.php for example
5584
        if (!empty($conf->dol_use_jmobile)) {
5585
            $useajax = 0;
5586
        }
5587
        if (empty($conf->use_javascript_ajax)) {
5588
            $useajax = 0;
5589
        }
5590
5591
        if ($useajax) {
5592
            $autoOpen = true;
5593
            $dialogconfirm = 'dialog-confirm';
5594
            $button = '';
5595
            if (!is_numeric($useajax)) {
5596
                $button = $useajax;
5597
                $useajax = 1;
5598
                $autoOpen = false;
5599
                $dialogconfirm .= '-' . $button;
5600
            }
5601
            $pageyes = $page . (preg_match('/\?/', $page) ? '&' : '?') . 'action=' . urlencode($action) . '&confirm=yes';
5602
            $pageno = ($useajax == 2 ? $page . (preg_match('/\?/', $page) ? '&' : '?') . 'action=' . urlencode($action) . '&confirm=no' : '');
5603
5604
            // Add input fields into list of fields to read during submit (inputok and inputko)
5605
            if (is_array($formquestion)) {
5606
                foreach ($formquestion as $key => $input) {
5607
                    //print "xx ".$key." rr ".is_array($input)."<br>\n";
5608
                    // Add name of fields to propagate with the GET when submitting the form with button OK.
5609
                    if (is_array($input) && isset($input['name'])) {
5610
                        if (strpos($input['name'], ',') > 0) {
5611
                            $inputok = array_merge($inputok, explode(',', $input['name']));
5612
                        } else {
5613
                            array_push($inputok, $input['name']);
5614
                        }
5615
                    }
5616
                    // Add name of fields to propagate with the GET when submitting the form with button KO.
5617
                    // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
5618
                    if (is_array($input) && isset($input['inputko']) && $input['inputko'] == 1 && isset($input['name'])) {
5619
                        array_push($inputko, $input['name']);
5620
                    }
5621
                }
5622
            }
5623
5624
            // Show JQuery confirm box.
5625
            $formconfirm .= '<div id="' . $dialogconfirm . '" title="' . dol_escape_htmltag($title) . '" style="display: none;">';
5626
            if (is_array($formquestion) && array_key_exists('text', $formquestion) && !empty($formquestion['text'])) {
5627
                $formconfirm .= '<div class="confirmtext">' . $formquestion['text'] . '</div>' . "\n";
5628
            }
5629
            if (!empty($more)) {
5630
                $formconfirm .= '<div class="confirmquestions">' . $more . '</div>' . "\n";
5631
            }
5632
            $formconfirm .= ($question ? '<div class="confirmmessage">' . img_help(0, '') . ' ' . $question . '</div>' : '');
5633
            $formconfirm .= '</div>' . "\n";
5634
5635
            $formconfirm .= "\n<!-- begin code of popup for formconfirm page=" . $page . " -->\n";
5636
            $formconfirm .= '<script nonce="' . getNonce() . '" type="text/javascript">' . "\n";
5637
            $formconfirm .= "/* Code for the jQuery('#dialogforpopup').dialog() */\n";
5638
            $formconfirm .= 'jQuery(document).ready(function() {
5639
            $(function() {
5640
            	$( "#' . $dialogconfirm . '" ).dialog(
5641
            	{
5642
                    autoOpen: ' . ($autoOpen ? "true" : "false") . ',';
5643
            if ($newselectedchoice == 'no') {
5644
                $formconfirm .= '
5645
						open: function() {
5646
            				$(this).parent().find("button.ui-button:eq(2)").focus();
5647
						},';
5648
            }
5649
5650
            $jsforcursor = '';
5651
            if ($useajax == 1) {
5652
                $jsforcursor = '// The call to urljump can be slow, so we set the wait cursor' . "\n";
5653
                $jsforcursor .= 'jQuery("html,body,#id-container").addClass("cursorwait");' . "\n";
5654
            }
5655
5656
            $postconfirmas = 'GET';
5657
5658
            $formconfirm .= '
5659
                    resizable: false,
5660
                    height: "' . $height . '",
5661
                    width: "' . $width . '",
5662
                    modal: true,
5663
                    closeOnEscape: false,
5664
                    buttons: {
5665
                        "' . dol_escape_js($langs->transnoentities($labelbuttonyes)) . '": function() {
5666
							var options = "token=' . urlencode(newToken()) . '";
5667
                        	var inputok = ' . json_encode($inputok) . ';	/* List of fields into form */
5668
							var page = "' . dol_escape_js(!empty($page) ? $page : '') . '";
5669
                         	var pageyes = "' . dol_escape_js(!empty($pageyes) ? $pageyes : '') . '";
5670
5671
                         	if (inputok.length > 0) {
5672
                         		$.each(inputok, function(i, inputname) {
5673
                         			var more = "";
5674
									var inputvalue;
5675
                         			if ($("input[name=\'" + inputname + "\']").attr("type") == "radio") {
5676
										inputvalue = $("input[name=\'" + inputname + "\']:checked").val();
5677
									} else {
5678
                         		    	if ($("#" + inputname).attr("type") == "checkbox") { more = ":checked"; }
5679
                         				inputvalue = $("#" + inputname + more).val();
5680
									}
5681
                         			if (typeof inputvalue == "undefined") { inputvalue=""; }
5682
									console.log("formconfirm check inputname="+inputname+" inputvalue="+inputvalue);
5683
                         			options += "&" + inputname + "=" + encodeURIComponent(inputvalue);
5684
                         		});
5685
                         	}
5686
                         	var urljump = pageyes + (pageyes.indexOf("?") < 0 ? "?" : "&") + options;
5687
            				if (pageyes.length > 0) {';
5688
            if ($postconfirmas == 'GET') {
5689
                $formconfirm .= 'location.href = urljump;';
5690
            } else {
5691
                $formconfirm .= $jsforcursor;
5692
                $formconfirm .= 'var post = $.post(
5693
									pageyes,
5694
									options,
5695
									function(data) { $("body").html(data); jQuery("html,body,#id-container").removeClass("cursorwait"); }
5696
								);';
5697
            }
5698
            $formconfirm .= '
5699
								console.log("after post ok");
5700
							}
5701
	                        $(this).dialog("close");
5702
                        },
5703
                        "' . dol_escape_js($langs->transnoentities($labelbuttonno)) . '": function() {
5704
                        	var options = "token=' . urlencode(newToken()) . '";
5705
                         	var inputko = ' . json_encode($inputko) . ';	/* List of fields into form */
5706
							var page = "' . dol_escape_js(!empty($page) ? $page : '') . '";
5707
                         	var pageno="' . dol_escape_js(!empty($pageno) ? $pageno : '') . '";
5708
                         	if (inputko.length > 0) {
5709
                         		$.each(inputko, function(i, inputname) {
5710
                         			var more = "";
5711
                         			if ($("#" + inputname).attr("type") == "checkbox") { more = ":checked"; }
5712
                         			var inputvalue = $("#" + inputname + more).val();
5713
                         			if (typeof inputvalue == "undefined") { inputvalue=""; }
5714
                         			options += "&" + inputname + "=" + encodeURIComponent(inputvalue);
5715
                         		});
5716
                         	}
5717
                         	var urljump=pageno + (pageno.indexOf("?") < 0 ? "?" : "&") + options;
5718
                         	//alert(urljump);
5719
            				if (pageno.length > 0) {';
5720
            if ($postconfirmas == 'GET') {
5721
                $formconfirm .= 'location.href = urljump;';
5722
            } else {
5723
                $formconfirm .= $jsforcursor;
5724
                $formconfirm .= 'var post = $.post(
5725
									pageno,
5726
									options,
5727
									function(data) { $("body").html(data); jQuery("html,body,#id-container").removeClass("cursorwait"); }
5728
								);';
5729
            }
5730
            $formconfirm .= '
5731
								console.log("after post ko");
5732
							}
5733
                            $(this).dialog("close");
5734
                        }
5735
                    }
5736
                }
5737
                );
5738
5739
            	var button = "' . $button . '";
5740
            	if (button.length > 0) {
5741
                	$( "#" + button ).click(function() {
5742
                		$("#' . $dialogconfirm . '").dialog("open");
5743
        			});
5744
                }
5745
            });
5746
            });
5747
            </script>';
5748
            $formconfirm .= "<!-- end ajax formconfirm -->\n";
5749
        } else {
5750
            $formconfirm .= "\n<!-- begin formconfirm page=" . dol_escape_htmltag($page) . " -->\n";
5751
5752
            if (empty($disableformtag)) {
5753
                $formconfirm .= '<form method="POST" action="' . $page . '" class="notoptoleftroright">' . "\n";
5754
            }
5755
5756
            $formconfirm .= '<input type="hidden" name="action" value="' . $action . '">' . "\n";
5757
            $formconfirm .= '<input type="hidden" name="token" value="' . newToken() . '">' . "\n";
5758
5759
            $formconfirm .= '<table class="valid centpercent">' . "\n";
5760
5761
            // Line title
5762
            $formconfirm .= '<tr class="validtitre"><td class="validtitre" colspan="2">';
5763
            $formconfirm .= img_picto('', 'pictoconfirm') . ' ' . $title;
5764
            $formconfirm .= '</td></tr>' . "\n";
5765
5766
            // Line text
5767
            if (is_array($formquestion) && array_key_exists('text', $formquestion) && !empty($formquestion['text'])) {
5768
                $formconfirm .= '<tr class="valid"><td class="valid" colspan="2">' . $formquestion['text'] . '</td></tr>' . "\n";
5769
            }
5770
5771
            // Line form fields
5772
            if ($more) {
5773
                $formconfirm .= '<tr class="valid"><td class="valid" colspan="2">' . "\n";
5774
                $formconfirm .= $more;
5775
                $formconfirm .= '</td></tr>' . "\n";
5776
            }
5777
5778
            // Line with question
5779
            $formconfirm .= '<tr class="valid">';
5780
            $formconfirm .= '<td class="valid">' . $question . '</td>';
5781
            $formconfirm .= '<td class="valid center">';
5782
            $formconfirm .= $this->selectyesno("confirm", $newselectedchoice, 0, false, 0, 0, 'marginleftonly marginrightonly', $labelbuttonyes, $labelbuttonno);
5783
            $formconfirm .= '<input class="button valignmiddle confirmvalidatebutton small" type="submit" value="' . $langs->trans("Validate") . '">';
5784
            $formconfirm .= '</td>';
5785
            $formconfirm .= '</tr>' . "\n";
5786
5787
            $formconfirm .= '</table>' . "\n";
5788
5789
            if (empty($disableformtag)) {
5790
                $formconfirm .= "</form>\n";
5791
            }
5792
            $formconfirm .= '<br>';
5793
5794
            if (!empty($conf->use_javascript_ajax)) {
5795
                $formconfirm .= '<!-- code to disable button to avoid double clic -->';
5796
                $formconfirm .= '<script nonce="' . getNonce() . '" type="text/javascript">' . "\n";
5797
                $formconfirm .= '
5798
				$(document).ready(function () {
5799
					$(".confirmvalidatebutton").on("click", function() {
5800
						console.log("We click on button confirmvalidatebutton");
5801
						$(this).attr("disabled", "disabled");
5802
						setTimeout(\'$(".confirmvalidatebutton").removeAttr("disabled")\', 3000);
5803
						//console.log($(this).closest("form"));
5804
						$(this).closest("form").submit();
5805
					});
5806
				});
5807
				';
5808
                $formconfirm .= '</script>' . "\n";
5809
            }
5810
5811
            $formconfirm .= "<!-- end formconfirm -->\n";
5812
        }
5813
5814
        return $formconfirm;
5815
    }
5816
5817
5818
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5819
5820
    /**
5821
     * Show a form to select a project
5822
     *
5823
     * @param   int         $page               Page
5824
     * @param   int         $socid              Id third party (-1=all, 0=only projects not linked to a third party, id=projects not linked or linked to third party id)
5825
     * @param   string      $selected           Id preselected project
5826
     * @param   string      $htmlname           Name of select field
5827
     * @param   int         $discard_closed     Discard closed projects (0=Keep,1=hide completely except $selected,2=Disable)
5828
     * @param   int         $maxlength          Max length
5829
     * @param   int         $forcefocus         Force focus on field (works with javascript only)
5830
     * @param   int         $nooutput           No print is done. String is returned.
5831
     * @param   string      $textifnoproject    Text to show if no project
5832
     * @param   string      $morecss            More CSS
5833
     * @return  string                          Return html content
5834
     */
5835
    public function form_project($page, $socid, $selected = '', $htmlname = 'projectid', $discard_closed = 0, $maxlength = 20, $forcefocus = 0, $nooutput = 0, $textifnoproject = '', $morecss = '')
5836
    {
5837
		// phpcs:enable
5838
        global $langs;
5839
5840
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/project.lib.php';
5841
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/class/html.formprojet.class.php';
5842
5843
        $out = '';
5844
5845
        $formproject = new FormProjets($this->db);
5846
5847
        $langs->load("project");
5848
        if ($htmlname != "none") {
5849
            $out .= '<form method="post" action="' . $page . '">';
5850
            $out .= '<input type="hidden" name="action" value="classin">';
5851
            $out .= '<input type="hidden" name="token" value="' . newToken() . '">';
5852
            $out .= $formproject->select_projects($socid, $selected, $htmlname, $maxlength, 0, 1, $discard_closed, $forcefocus, 0, 0, '', 1, 0, $morecss);
5853
            $out .= '<input type="submit" class="button smallpaddingimp" value="' . $langs->trans("Modify") . '">';
5854
            $out .= '</form>';
5855
        } else {
5856
            $out .= '<span class="project_head_block">';
5857
            if ($selected) {
5858
                $projet = new Project($this->db);
5859
                $projet->fetch($selected);
5860
                $out .= $projet->getNomUrl(0, '', 1);
5861
            } else {
5862
                $out .= '<span class="opacitymedium">' . $textifnoproject . '</span>';
5863
            }
5864
            $out .= '</span>';
5865
        }
5866
5867
        if (empty($nooutput)) {
5868
            print $out;
5869
            return '';
5870
        }
5871
        return $out;
5872
    }
5873
5874
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5875
5876
    /**
5877
     * Show a form to select payment conditions
5878
     *
5879
     * @param int       $page               Page
5880
     * @param string    $selected           Id condition pre-selectionne
5881
     * @param string    $htmlname           Name of select html field
5882
     * @param int       $addempty           Add empty entry
5883
     * @param string    $type               Type ('direct-debit' or 'bank-transfer')
5884
     * @param int       $filtertype         If > 0, include payment terms with deposit percentage (for objects other than invoices and invoice templates)
5885
     * @param int       $deposit_percent    < 0 : deposit_percent input makes no sense (for example, in list filters)
5886
     *                                      0 : use default deposit percentage from entry
5887
     *                                      > 0 : force deposit percentage (for example, from company object)
5888
     * @param int       $nooutput           No print is done. String is returned.
5889
     * @return string                       HTML output or ''
5890
     */
5891
    public function form_conditions_reglement($page, $selected = '', $htmlname = 'cond_reglement_id', $addempty = 0, $type = '', $filtertype = -1, $deposit_percent = -1, $nooutput = 0)
5892
    {
5893
		// phpcs:enable
5894
        global $langs;
5895
5896
        $out = '';
5897
5898
        if ($htmlname != "none") {
5899
            $out .= '<form method="POST" action="' . $page . '">';
5900
            $out .= '<input type="hidden" name="action" value="setconditions">';
5901
            $out .= '<input type="hidden" name="token" value="' . newToken() . '">';
5902
            if ($type) {
5903
                $out .= '<input type="hidden" name="type" value="' . dol_escape_htmltag($type) . '">';
5904
            }
5905
            $out .= $this->getSelectConditionsPaiements($selected, $htmlname, $filtertype, $addempty, 0, '', $deposit_percent);
5906
            $out .= '<input type="submit" class="button valignmiddle smallpaddingimp" value="' . $langs->trans("Modify") . '">';
5907
            $out .= '</form>';
5908
        } else {
5909
            if ($selected) {
5910
                $this->load_cache_conditions_paiements();
5911
                if (isset($this->cache_conditions_paiements[$selected])) {
5912
                    $label = $this->cache_conditions_paiements[$selected]['label'];
5913
5914
                    if (!empty($this->cache_conditions_paiements[$selected]['deposit_percent'])) {
5915
                        $label = str_replace('__DEPOSIT_PERCENT__', $deposit_percent > 0 ? $deposit_percent : $this->cache_conditions_paiements[$selected]['deposit_percent'], $label);
5916
                    }
5917
5918
                    $out .= $label;
5919
                } else {
5920
                    $langs->load('errors');
5921
                    $out .= $langs->trans('ErrorNotInDictionaryPaymentConditions');
5922
                }
5923
            } else {
5924
                $out .= '&nbsp;';
5925
            }
5926
        }
5927
5928
        if (empty($nooutput)) {
5929
            print $out;
5930
            return '';
5931
        }
5932
        return $out;
5933
    }
5934
5935
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5936
5937
    /**
5938
     *  Show a form to select a delivery delay
5939
     *
5940
     * @param   int         $page       Page
5941
     * @param   string      $selected   Id condition pre-selectionne
5942
     * @param   string      $htmlname   Name of select html field
5943
     * @param   int         $addempty   Add an empty entry
5944
     * @return  void
5945
     */
5946
    public function form_availability($page, $selected = '', $htmlname = 'availability', $addempty = 0)
5947
    {
5948
		// phpcs:enable
5949
        global $langs;
5950
        if ($htmlname != "none") {
5951
            print '<form method="post" action="' . $page . '">';
5952
            print '<input type="hidden" name="action" value="setavailability">';
5953
            print '<input type="hidden" name="token" value="' . newToken() . '">';
5954
            $this->selectAvailabilityDelay($selected, $htmlname, -1, $addempty);
5955
            print '<input type="submit" name="modify" class="button smallpaddingimp" value="' . $langs->trans("Modify") . '">';
5956
            print '<input type="submit" name="cancel" class="button smallpaddingimp" value="' . $langs->trans("Cancel") . '">';
5957
            print '</form>';
5958
        } else {
5959
            if ($selected) {
5960
                $this->load_cache_availability();
5961
                print $this->cache_availability[$selected]['label'];
5962
            } else {
5963
                print "&nbsp;";
5964
            }
5965
        }
5966
    }
5967
5968
    /**
5969
     *  Output HTML form to select list of input reason (events that triggered an object creation, like after sending an emailing, making an advert, ...)
5970
     *  List found into table c_input_reason loaded by loadCacheInputReason
5971
     *
5972
     * @param string $page Page
5973
     * @param string $selected Id condition pre-selectionne
5974
     * @param string $htmlname Name of select html field
5975
     * @param int $addempty Add empty entry
5976
     * @return    void
5977
     */
5978
    public function formInputReason($page, $selected = '', $htmlname = 'demandreason', $addempty = 0)
5979
    {
5980
        global $langs;
5981
        if ($htmlname != "none") {
5982
            print '<form method="post" action="' . $page . '">';
5983
            print '<input type="hidden" name="action" value="setdemandreason">';
5984
            print '<input type="hidden" name="token" value="' . newToken() . '">';
5985
            $this->selectInputReason($selected, $htmlname, -1, $addempty);
5986
            print '<input type="submit" class="button smallpaddingimp" value="' . $langs->trans("Modify") . '">';
5987
            print '</form>';
5988
        } else {
5989
            if ($selected) {
5990
                $this->loadCacheInputReason();
5991
                foreach ($this->cache_demand_reason as $key => $val) {
5992
                    if ($val['id'] == $selected) {
5993
                        print $val['label'];
5994
                        break;
5995
                    }
5996
                }
5997
            } else {
5998
                print "&nbsp;";
5999
            }
6000
        }
6001
    }
6002
6003
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6004
6005
    /**
6006
     *    Show a form + html select a date
6007
     *
6008
     * @param string $page Page
6009
     * @param string $selected Date preselected
6010
     * @param string $htmlname Html name of date input fields or 'none'
6011
     * @param int $displayhour Display hour selector
6012
     * @param int $displaymin Display minutes selector
6013
     * @param int $nooutput 1=No print output, return string
6014
     * @param string $type 'direct-debit' or 'bank-transfer'
6015
     * @return    string
6016
     * @see        selectDate()
6017
     */
6018
    public function form_date($page, $selected, $htmlname, $displayhour = 0, $displaymin = 0, $nooutput = 0, $type = '')
6019
    {
6020
		// phpcs:enable
6021
        global $langs;
6022
6023
        $ret = '';
6024
6025
        if ($htmlname != "none") {
6026
            $ret .= '<form method="POST" action="' . $page . '" name="form' . $htmlname . '">';
6027
            $ret .= '<input type="hidden" name="action" value="set' . $htmlname . '">';
6028
            $ret .= '<input type="hidden" name="token" value="' . newToken() . '">';
6029
            if ($type) {
6030
                $ret .= '<input type="hidden" name="type" value="' . dol_escape_htmltag($type) . '">';
6031
            }
6032
            $ret .= '<table class="nobordernopadding">';
6033
            $ret .= '<tr><td>';
6034
            $ret .= $this->selectDate($selected, $htmlname, $displayhour, $displaymin, 1, 'form' . $htmlname, 1, 0);
6035
            $ret .= '</td>';
6036
            $ret .= '<td class="left"><input type="submit" class="button smallpaddingimp" value="' . $langs->trans("Modify") . '"></td>';
6037
            $ret .= '</tr></table></form>';
6038
        } else {
6039
            if ($displayhour) {
6040
                $ret .= dol_print_date($selected, 'dayhour');
6041
            } else {
6042
                $ret .= dol_print_date($selected, 'day');
6043
            }
6044
        }
6045
6046
        if (empty($nooutput)) {
6047
            print $ret;
6048
        }
6049
        return $ret;
6050
    }
6051
6052
6053
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6054
6055
    /**
6056
     *  Show a select form to choose a user
6057
     *
6058
     * @param string $page Page
6059
     * @param string $selected Id of user preselected
6060
     * @param string $htmlname Name of input html field. If 'none', we just output the user link.
6061
     * @param int[] $exclude List of users id to exclude
6062
     * @param int[] $include List of users id to include
6063
     * @return    void
6064
     */
6065
    public function form_users($page, $selected = '', $htmlname = 'userid', $exclude = array(), $include = array())
6066
    {
6067
		// phpcs:enable
6068
        global $langs;
6069
6070
        if ($htmlname != "none") {
6071
            print '<form method="POST" action="' . $page . '" name="form' . $htmlname . '">';
6072
            print '<input type="hidden" name="action" value="set' . $htmlname . '">';
6073
            print '<input type="hidden" name="token" value="' . newToken() . '">';
6074
            print $this->select_dolusers($selected, $htmlname, 1, $exclude, 0, $include);
6075
            print '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
6076
            print '</form>';
6077
        } else {
6078
            if ($selected) {
6079
                require_once constant('DOL_DOCUMENT_ROOT') . '/user/class/user.class.php';
6080
                $theuser = new User($this->db);
6081
                $theuser->fetch($selected);
6082
                print $theuser->getNomUrl(1);
6083
            } else {
6084
                print "&nbsp;";
6085
            }
6086
        }
6087
    }
6088
6089
6090
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6091
6092
    /**
6093
     *    Show form with payment mode
6094
     *
6095
     * @param string $page Page
6096
     * @param string $selected Id mode pre-selectionne
6097
     * @param string $htmlname Name of select html field
6098
     * @param string $filtertype To filter on field type in llx_c_paiement ('CRDT' or 'DBIT' or array('code'=>xx,'label'=>zz))
6099
     * @param int $active Active or not, -1 = all
6100
     * @param int $addempty 1=Add empty entry
6101
     * @param string $type Type ('direct-debit' or 'bank-transfer')
6102
     * @param int $nooutput 1=Return string, no output
6103
     * @return    string                    HTML output or ''
6104
     */
6105
    public function form_modes_reglement($page, $selected = '', $htmlname = 'mode_reglement_id', $filtertype = '', $active = 1, $addempty = 0, $type = '', $nooutput = 0)
6106
    {
6107
		// phpcs:enable
6108
        global $langs;
6109
6110
        $out = '';
6111
        if ($htmlname != "none") {
6112
            $out .= '<form method="POST" action="' . $page . '">';
6113
            $out .= '<input type="hidden" name="action" value="setmode">';
6114
            $out .= '<input type="hidden" name="token" value="' . newToken() . '">';
6115
            if ($type) {
6116
                $out .= '<input type="hidden" name="type" value="' . dol_escape_htmltag($type) . '">';
6117
            }
6118
            $out .= $this->select_types_paiements($selected, $htmlname, $filtertype, 0, $addempty, 0, 0, $active, '', 1);
6119
            $out .= '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
6120
            $out .= '</form>';
6121
        } else {
6122
            if ($selected) {
6123
                $this->load_cache_types_paiements();
6124
                $out .= $this->cache_types_paiements[$selected]['label'];
6125
            } else {
6126
                $out .= "&nbsp;";
6127
            }
6128
        }
6129
6130
        if ($nooutput) {
6131
            return $out;
6132
        } else {
6133
            print $out;
6134
        }
6135
        return '';
6136
    }
6137
6138
    /**
6139
     *    Show form with transport mode
6140
     *
6141
     * @param string $page Page
6142
     * @param string $selected Id mode pre-select
6143
     * @param string $htmlname Name of select html field
6144
     * @param int $active Active or not, -1 = all
6145
     * @param int $addempty 1=Add empty entry
6146
     * @return    void
6147
     */
6148
    public function formSelectTransportMode($page, $selected = '', $htmlname = 'transport_mode_id', $active = 1, $addempty = 0)
6149
    {
6150
        global $langs;
6151
        if ($htmlname != "none") {
6152
            print '<form method="POST" action="' . $page . '">';
6153
            print '<input type="hidden" name="action" value="settransportmode">';
6154
            print '<input type="hidden" name="token" value="' . newToken() . '">';
6155
            $this->selectTransportMode($selected, $htmlname, 0, $addempty, 0, 0, $active);
6156
            print '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
6157
            print '</form>';
6158
        } else {
6159
            if ($selected) {
6160
                $this->load_cache_transport_mode();
6161
                print $this->cache_transport_mode[$selected]['label'];
6162
            } else {
6163
                print "&nbsp;";
6164
            }
6165
        }
6166
    }
6167
6168
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6169
6170
    /**
6171
     *    Show form with multicurrency code
6172
     *
6173
     * @param string $page Page
6174
     * @param string $selected code pre-selectionne
6175
     * @param string $htmlname Name of select html field
6176
     * @return    void
6177
     */
6178
    public function form_multicurrency_code($page, $selected = '', $htmlname = 'multicurrency_code')
6179
    {
6180
		// phpcs:enable
6181
        global $langs;
6182
        if ($htmlname != "none") {
6183
            print '<form method="POST" action="' . $page . '">';
6184
            print '<input type="hidden" name="action" value="setmulticurrencycode">';
6185
            print '<input type="hidden" name="token" value="' . newToken() . '">';
6186
            print $this->selectMultiCurrency($selected, $htmlname, 0);
6187
            print '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
6188
            print '</form>';
6189
        } else {
6190
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/company.lib.php';
6191
            print !empty($selected) ? currency_name($selected, 1) : '&nbsp;';
6192
        }
6193
    }
6194
6195
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6196
6197
    /**
6198
     *    Show form with multicurrency rate
6199
     *
6200
     * @param string $page Page
6201
     * @param double $rate Current rate
6202
     * @param string $htmlname Name of select html field
6203
     * @param string $currency Currency code to explain the rate
6204
     * @return    void
6205
     */
6206
    public function form_multicurrency_rate($page, $rate = 0.0, $htmlname = 'multicurrency_tx', $currency = '')
6207
    {
6208
		// phpcs:enable
6209
        global $langs, $mysoc, $conf;
6210
6211
        if ($htmlname != "none") {
6212
            print '<form method="POST" action="' . $page . '">';
6213
            print '<input type="hidden" name="action" value="setmulticurrencyrate">';
6214
            print '<input type="hidden" name="token" value="' . newToken() . '">';
6215
            print '<input type="text" class="maxwidth100" name="' . $htmlname . '" value="' . (!empty($rate) ? price(price2num($rate, 'CU')) : 1) . '" /> ';
6216
            print '<select name="calculation_mode">';
6217
            print '<option value="1">Change ' . $langs->trans("PriceUHT") . ' of lines</option>';
6218
            print '<option value="2">Change ' . $langs->trans("PriceUHTCurrency") . ' of lines</option>';
6219
            print '</select> ';
6220
            print '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
6221
            print '</form>';
6222
        } else {
6223
            if (!empty($rate)) {
6224
                print price($rate, 1, $langs, 0, 0);
6225
                if ($currency && $rate != 1) {
6226
                    print ' &nbsp; (' . price($rate, 1, $langs, 0, 0) . ' ' . $currency . ' = 1 ' . $conf->currency . ')';
6227
                }
6228
            } else {
6229
                print 1;
6230
            }
6231
        }
6232
    }
6233
6234
6235
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6236
6237
    /**
6238
     *    Show a select box with available absolute discounts
6239
     *
6240
     * @param string $page Page URL where form is shown
6241
     * @param int $selected Value preselected
6242
     * @param string $htmlname Name of SELECT component. If 'none', not changeable. Example 'remise_id'.
6243
     * @param int $socid Third party id
6244
     * @param float $amount Total amount available
6245
     * @param string $filter SQL filter on discounts
6246
     * @param int $maxvalue Max value for lines that can be selected
6247
     * @param string $more More string to add
6248
     * @param int $hidelist 1=Hide list
6249
     * @param int $discount_type 0 => customer discount, 1 => supplier discount
6250
     * @return    void
6251
     */
6252
    public function form_remise_dispo($page, $selected, $htmlname, $socid, $amount, $filter = '', $maxvalue = 0, $more = '', $hidelist = 0, $discount_type = 0)
6253
    {
6254
		// phpcs:enable
6255
        global $conf, $langs;
6256
        if ($htmlname != "none") {
6257
            print '<form method="post" action="' . $page . '">';
6258
            print '<input type="hidden" name="action" value="setabsolutediscount">';
6259
            print '<input type="hidden" name="token" value="' . newToken() . '">';
6260
            print '<div class="inline-block">';
6261
            if (!empty($discount_type)) {
6262
                if (getDolGlobalString('FACTURE_SUPPLIER_DEPOSITS_ARE_JUST_PAYMENTS')) {
6263
                    if (!$filter || $filter == "fk_invoice_supplier_source IS NULL") {
6264
                        $translationKey = 'HasAbsoluteDiscountFromSupplier'; // If we want deposit to be subtracted to payments only and not to total of final invoice
6265
                    } else {
6266
                        $translationKey = 'HasCreditNoteFromSupplier';
6267
                    }
6268
                } else {
6269
                    if (!$filter || $filter == "fk_invoice_supplier_source IS NULL OR (description LIKE '(DEPOSIT)%' AND description NOT LIKE '(EXCESS PAID)%')") {
6270
                        $translationKey = 'HasAbsoluteDiscountFromSupplier';
6271
                    } else {
6272
                        $translationKey = 'HasCreditNoteFromSupplier';
6273
                    }
6274
                }
6275
            } else {
6276
                if (getDolGlobalString('FACTURE_DEPOSITS_ARE_JUST_PAYMENTS')) {
6277
                    if (!$filter || $filter == "fk_facture_source IS NULL") {
6278
                        $translationKey = 'CompanyHasAbsoluteDiscount'; // If we want deposit to be subtracted to payments only and not to total of final invoice
6279
                    } else {
6280
                        $translationKey = 'CompanyHasCreditNote';
6281
                    }
6282
                } else {
6283
                    if (!$filter || $filter == "fk_facture_source IS NULL OR (description LIKE '(DEPOSIT)%' AND description NOT LIKE '(EXCESS RECEIVED)%')") {
6284
                        $translationKey = 'CompanyHasAbsoluteDiscount';
6285
                    } else {
6286
                        $translationKey = 'CompanyHasCreditNote';
6287
                    }
6288
                }
6289
            }
6290
            print $langs->trans($translationKey, price($amount, 0, $langs, 0, 0, -1, $conf->currency));
6291
            if (empty($hidelist)) {
6292
                print ' ';
6293
            }
6294
            print '</div>';
6295
            if (empty($hidelist)) {
6296
                print '<div class="inline-block" style="padding-right: 10px">';
6297
                $newfilter = 'discount_type=' . intval($discount_type);
6298
                if (!empty($discount_type)) {
6299
                    $newfilter .= ' AND fk_invoice_supplier IS NULL AND fk_invoice_supplier_line IS NULL'; // Supplier discounts available
6300
                } else {
6301
                    $newfilter .= ' AND fk_facture IS NULL AND fk_facture_line IS NULL'; // Customer discounts available
6302
                }
6303
                if ($filter) {
6304
                    $newfilter .= ' AND (' . $filter . ')';
6305
                }
6306
                // output the combo of discounts
6307
                $nbqualifiedlines = $this->select_remises($selected, $htmlname, $newfilter, $socid, $maxvalue);
6308
                if ($nbqualifiedlines > 0) {
6309
                    print ' &nbsp; <input type="submit" class="button smallpaddingimp" value="' . dol_escape_htmltag($langs->trans("UseLine")) . '"';
6310
                    if (!empty($discount_type) && $filter && $filter != "fk_invoice_supplier_source IS NULL OR (description LIKE '(DEPOSIT)%' AND description NOT LIKE '(EXCESS PAID)%')") {
6311
                        print ' title="' . $langs->trans("UseCreditNoteInInvoicePayment") . '"';
6312
                    }
6313
                    if (empty($discount_type) && $filter && $filter != "fk_facture_source IS NULL OR (description LIKE '(DEPOSIT)%' AND description NOT LIKE '(EXCESS RECEIVED)%')") {
6314
                        print ' title="' . $langs->trans("UseCreditNoteInInvoicePayment") . '"';
6315
                    }
6316
6317
                    print '>';
6318
                }
6319
                print '</div>';
6320
            }
6321
            if ($more) {
6322
                print '<div class="inline-block">';
6323
                print $more;
6324
                print '</div>';
6325
            }
6326
            print '</form>';
6327
        } else {
6328
            if ($selected) {
6329
                print $selected;
6330
            } else {
6331
                print "0";
6332
            }
6333
        }
6334
    }
6335
6336
6337
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6338
6339
    /**
6340
     *  Show forms to select a contact
6341
     *
6342
     * @param string    $page       Page
6343
     * @param Societe   $societe    Filter on third party
6344
     * @param string    $selected   Id contact pre-selectionne
6345
     * @param string    $htmlname   Name of HTML select. If 'none', we just show contact link.
6346
     * @return    void
6347
     */
6348
    public function form_contacts($page, $societe, $selected = '', $htmlname = 'contactid')
6349
    {
6350
		// phpcs:enable
6351
        global $langs;
6352
6353
        if ($htmlname != "none") {
6354
            print '<form method="post" action="' . $page . '">';
6355
            print '<input type="hidden" name="action" value="set_contact">';
6356
            print '<input type="hidden" name="token" value="' . newToken() . '">';
6357
            print '<table class="nobordernopadding">';
6358
            print '<tr><td>';
6359
            print $this->selectcontacts($societe->id, $selected, $htmlname);
6360
            $num = $this->num;
6361
            if ($num == 0) {
6362
                $addcontact = (getDolGlobalString('SOCIETE_ADDRESSES_MANAGEMENT') ? $langs->trans("AddContact") : $langs->trans("AddContactAddress"));
6363
                print '<a href="' . constant('BASE_URL') . '/contact/card.php?socid=' . $societe->id . '&amp;action=create&amp;backtoreferer=1">' . $addcontact . '</a>';
6364
            }
6365
            print '</td>';
6366
            print '<td class="left"><input type="submit" class="button smallpaddingimp" value="' . $langs->trans("Modify") . '"></td>';
6367
            print '</tr></table></form>';
6368
        } else {
6369
            if ($selected) {
6370
                require_once constant('DOL_DOCUMENT_ROOT') . '/contact/class/contact.class.php';
6371
                $contact = new Contact($this->db);
6372
                $contact->fetch($selected);
6373
                print $contact->getFullName($langs);
6374
            } else {
6375
                print "&nbsp;";
6376
            }
6377
        }
6378
    }
6379
6380
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6381
6382
    /**
6383
     *  Output html select to select thirdparty
6384
     *
6385
     * @param string    $page                   Page
6386
     * @param string    $selected               Id preselected
6387
     * @param string    $htmlname               Name of HTML select
6388
     * @param string    $filter                 Optional filters criteras. WARNING: To avoid SQL injection, only few chars [.a-z0-9 =<>()] are allowed here (example: 's.rowid <> x', 's.client IN (1,3)'). Do not use a filter coming from input of users.
6389
     * @param string|int<0,1>   $showempty      Add an empty field (Can be '1' or text key to use on empty line like 'SelectThirdParty')
6390
     * @param int<0,1>  $showtype               Show third party type in combolist (customer, prospect or supplier)
6391
     * @param int<0,1>  $forcecombo             Force to use combo box
6392
     * @param   array<array{method:string,url:string,htmlname:string,params:array<string,string>}>  $events     Event options. Example: array(array('method'=>'getContacts', 'url'=>dol_buildpath('/core/ajax/contacts.php',1), 'htmlname'=>'contactid', 'params'=>array('add-customer-contact'=>'disabled')))
6393
     * @param int       $nooutput               No print output. Return it only.
6394
     * @param int[]     $excludeids             Exclude IDs from the select combo
6395
     * @param string    $textifnothirdparty     Text to show if no thirdparty
6396
     * @return    string                        HTML output or ''
6397
     */
6398
    public function form_thirdparty($page, $selected = '', $htmlname = 'socid', $filter = '', $showempty = 0, $showtype = 0, $forcecombo = 0, $events = array(), $nooutput = 0, $excludeids = array(), $textifnothirdparty = '')
6399
    {
6400
		// phpcs:enable
6401
        global $langs;
6402
6403
        $out = '';
6404
        if ($htmlname != "none") {
6405
            $out .= '<form method="post" action="' . $page . '">';
6406
            $out .= '<input type="hidden" name="action" value="set_thirdparty">';
6407
            $out .= '<input type="hidden" name="token" value="' . newToken() . '">';
6408
            $out .= $this->select_company($selected, $htmlname, $filter, $showempty, $showtype, $forcecombo, $events, 0, 'minwidth100', '', '', 1, array(), false, $excludeids);
6409
            $out .= '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
6410
            $out .= '</form>';
6411
        } else {
6412
            if ($selected) {
6413
                require_once constant('DOL_DOCUMENT_ROOT') . '/societe/class/societe.class.php';
6414
                $soc = new Societe($this->db);
6415
                $soc->fetch($selected);
6416
                $out .= $soc->getNomUrl(0, '');
6417
            } else {
6418
                $out .= '<span class="opacitymedium">' . $textifnothirdparty . '</span>';
6419
            }
6420
        }
6421
6422
        if ($nooutput) {
6423
            return $out;
6424
        } else {
6425
            print $out;
6426
        }
6427
6428
        return '';
6429
    }
6430
6431
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6432
6433
    /**
6434
     *    Retourne la liste des devises, dans la langue de l'utilisateur
6435
     *
6436
     * @param string $selected preselected currency code
6437
     * @param string $htmlname name of HTML select list
6438
     * @deprecated
6439
     * @return    void
6440
     */
6441
    public function select_currency($selected = '', $htmlname = 'currency_id')
6442
    {
6443
		// phpcs:enable
6444
        print $this->selectCurrency($selected, $htmlname);
6445
    }
6446
6447
    /**
6448
     *  Retourne la liste des devises, dans la langue de l'utilisateur
6449
     *
6450
     * @param string $selected preselected currency code
6451
     * @param string $htmlname name of HTML select list
6452
     * @param int    $mode 0 = Add currency symbol into label, 1 = Add 3 letter iso code
6453
     * @param string $useempty '1'=Allow empty value
6454
     * @return    string
6455
     */
6456
    public function selectCurrency($selected = '', $htmlname = 'currency_id', $mode = 0, $useempty = '')
6457
    {
6458
        global $langs, $user;
6459
6460
        $langs->loadCacheCurrencies('');
6461
6462
        $out = '';
6463
6464
        if ($selected == 'euro' || $selected == 'euros') {
6465
            $selected = 'EUR'; // Pour compatibilite
6466
        }
6467
6468
        $out .= '<select class="flat maxwidth200onsmartphone minwidth300" name="' . $htmlname . '" id="' . $htmlname . '">';
6469
        if ($useempty) {
6470
            $out .= '<option value="-1" selected></option>';
6471
        }
6472
        foreach ($langs->cache_currencies as $code_iso => $currency) {
6473
            $labeltoshow = $currency['label'];
6474
            if ($mode == 1) {
6475
                $labeltoshow .= ' <span class="opacitymedium">(' . $code_iso . ')</span>';
6476
            } else {
6477
                $labeltoshow .= ' <span class="opacitymedium">(' . $langs->getCurrencySymbol($code_iso) . ')</span>';
6478
            }
6479
6480
            if ($selected && $selected == $code_iso) {
6481
                $out .= '<option value="' . $code_iso . '" selected data-html="' . dol_escape_htmltag($labeltoshow) . '">';
6482
            } else {
6483
                $out .= '<option value="' . $code_iso . '" data-html="' . dol_escape_htmltag($labeltoshow) . '">';
6484
            }
6485
            $out .= $labeltoshow;
6486
            $out .= '</option>';
6487
        }
6488
        $out .= '</select>';
6489
        if ($user->admin) {
6490
            $out .= info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
6491
        }
6492
6493
        // Make select dynamic
6494
        include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
6495
        $out .= ajax_combobox($htmlname);
6496
6497
        return $out;
6498
    }
6499
6500
    /**
6501
     *    Return array of currencies in user language
6502
     *
6503
     * @param string $selected Preselected currency code
6504
     * @param string $htmlname Name of HTML select list
6505
     * @param integer $useempty 1=Add empty line
6506
     * @param string $filter Optional filters criteras (example: 'code <> x', ' in (1,3)')
6507
     * @param bool $excludeConfCurrency false = If company current currency not in table, we add it into list. Should always be available.
6508
     *                                  true = we are in currency_rate update , we don't want to see conf->currency in select
6509
     * @param string $morecss More css
6510
     * @return    string
6511
     */
6512
    public function selectMultiCurrency($selected = '', $htmlname = 'multicurrency_code', $useempty = 0, $filter = '', $excludeConfCurrency = false, $morecss = '')
6513
    {
6514
        global $conf, $langs;
6515
6516
        $langs->loadCacheCurrencies(''); // Load ->cache_currencies
6517
6518
        $TCurrency = array();
6519
6520
        $sql = "SELECT code FROM " . $this->db->prefix() . "multicurrency";
6521
        $sql .= " WHERE entity IN ('" . getEntity('mutlicurrency') . "')";
6522
        if ($filter) {
6523
            $sql .= " AND " . $filter;
6524
        }
6525
        $resql = $this->db->query($sql);
6526
        if ($resql) {
6527
            while ($obj = $this->db->fetch_object($resql)) {
6528
                $TCurrency[$obj->code] = $obj->code;
6529
            }
6530
        }
6531
6532
        $out = '';
6533
        $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" id="' . $htmlname . '">';
6534
        if ($useempty) {
6535
            $out .= '<option value="">&nbsp;</option>';
6536
        }
6537
        // If company current currency not in table, we add it into list. Should always be available.
6538
        if (!in_array($conf->currency, $TCurrency) && !$excludeConfCurrency) {
6539
            $TCurrency[$conf->currency] = $conf->currency;
6540
        }
6541
        if (count($TCurrency) > 0) {
6542
            foreach ($langs->cache_currencies as $code_iso => $currency) {
6543
                if (isset($TCurrency[$code_iso])) {
6544
                    if (!empty($selected) && $selected == $code_iso) {
6545
                        $out .= '<option value="' . $code_iso . '" selected="selected">';
6546
                    } else {
6547
                        $out .= '<option value="' . $code_iso . '">';
6548
                    }
6549
6550
                    $out .= $currency['label'];
6551
                    $out .= ' (' . $langs->getCurrencySymbol($code_iso) . ')';
6552
                    $out .= '</option>';
6553
                }
6554
            }
6555
        }
6556
6557
        $out .= '</select>';
6558
6559
        // Make select dynamic
6560
        include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
6561
        $out .= ajax_combobox($htmlname);
6562
6563
        return $out;
6564
    }
6565
6566
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6567
6568
    /**
6569
     *  Load into the cache ->cache_vatrates, all the vat rates of a country
6570
     *
6571
     *  @param  string  $country_code       Country code with quotes ("'CA'", or "'CA,IN,...'")
6572
     *  @return int                         Nb of loaded lines, 0 if already loaded, <0 if KO
6573
     */
6574
    public function load_cache_vatrates($country_code)
6575
    {
6576
		// phpcs:enable
6577
        global $langs, $user;
6578
6579
        $num = count($this->cache_vatrates);
6580
        if ($num > 0) {
6581
            return $num; // Cache already loaded
6582
        }
6583
6584
        dol_syslog(__METHOD__, LOG_DEBUG);
6585
6586
        $sql = "SELECT t.rowid, t.type_vat, t.code, t.taux, t.localtax1, t.localtax1_type, t.localtax2, t.localtax2_type, t.recuperableonly";
6587
        $sql .= " FROM " . $this->db->prefix() . "c_tva as t, " . $this->db->prefix() . "c_country as c";
6588
        $sql .= " WHERE t.fk_pays = c.rowid";
6589
        $sql .= " AND t.active > 0";
6590
        $sql .= " AND t.entity IN (" . getEntity('c_tva') . ")";
6591
        $sql .= " AND c.code IN (" . $this->db->sanitize($country_code, 1) . ")";
6592
        $sql .= " ORDER BY t.code ASC, t.taux ASC, t.recuperableonly ASC";
6593
6594
        $resql = $this->db->query($sql);
6595
        if ($resql) {
6596
            $num = $this->db->num_rows($resql);
6597
            if ($num) {
6598
                for ($i = 0; $i < $num; $i++) {
6599
                    $obj = $this->db->fetch_object($resql);
6600
6601
                    $tmparray = array();
6602
                    $tmparray['rowid']          = $obj->rowid;
6603
                    $tmparray['type_vat']       = $obj->type_vat;
6604
                    $tmparray['code']           = $obj->code;
6605
                    $tmparray['txtva']          = $obj->taux;
6606
                    $tmparray['nprtva']         = $obj->recuperableonly;
6607
                    $tmparray['localtax1']      = $obj->localtax1;
6608
                    $tmparray['localtax1_type'] = $obj->localtax1_type;
6609
                    $tmparray['localtax2']      = $obj->localtax2;
6610
                    $tmparray['localtax2_type'] = $obj->localtax1_type;
6611
                    $tmparray['label']          = $obj->taux . '%' . ($obj->code ? ' (' . $obj->code . ')' : ''); // Label must contains only 0-9 , . % or *
6612
                    $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
6613
                    $positiverates = '';
6614
                    if ($obj->taux) {
6615
                        $positiverates .= ($positiverates ? '/' : '') . $obj->taux;
6616
                    }
6617
                    if ($obj->localtax1) {
6618
                        $positiverates .= ($positiverates ? '/' : '') . $obj->localtax1;
6619
                    }
6620
                    if ($obj->localtax2) {
6621
                        $positiverates .= ($positiverates ? '/' : '') . $obj->localtax2;
6622
                    }
6623
                    if (empty($positiverates)) {
6624
                        $positiverates = '0';
6625
                    }
6626
                    $tmparray['labelpositiverates'] = $positiverates . ($obj->code ? ' (' . $obj->code . ')' : ''); // Must never be used as key, only label
6627
6628
                    $this->cache_vatrates[$obj->rowid] = $tmparray;
6629
                }
6630
6631
                return $num;
6632
            } else {
6633
                $this->error = '<span class="error">';
6634
                $this->error .= $langs->trans("ErrorNoVATRateDefinedForSellerCountry", $country_code);
6635
                $reg = array();
6636
                if (!empty($user) && $user->admin && preg_match('/\'(..)\'/', $country_code, $reg)) {
6637
                    $langs->load("errors");
6638
                    $new_country_code = $reg[1];
6639
                    $country_id = dol_getIdFromCode($this->db, $new_country_code, 'c_pays', 'code', 'rowid');
6640
                    $this->error .= '<br>' . $langs->trans("ErrorFixThisHere", constant('BASE_URL') . '/admin/dict.php?id=10' . ($country_id > 0 ? '&countryidforinsert=' . $country_id : ''));
6641
                }
6642
                $this->error .= '</span>';
6643
                return -1;
6644
            }
6645
        } else {
6646
            $this->error = '<span class="error">' . $this->db->error() . '</span>';
6647
            return -2;
6648
        }
6649
    }
6650
6651
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6652
6653
    /**
6654
     *  Output an HTML select vat rate.
6655
     *  The name of this function should be selectVat. We keep bad name for compatibility purpose.
6656
     *
6657
     *  @param  string        $htmlname           Name of HTML select field
6658
     *  @param  float|string  $selectedrate       Force preselected vat rate. Can be '8.5' or '8.5 (NOO)' for example. Use '' for no forcing.
6659
     *  @param  Societe       $societe_vendeuse   Thirdparty seller
6660
     *  @param  Societe       $societe_acheteuse  Thirdparty buyer
6661
     *  @param  int           $idprod             Id product. O if unknown of NA.
6662
     *  @param  int           $info_bits          Miscellaneous information on line (1 for NPR)
6663
     *  @param  int|string    $type               ''=Unknown, 0=Product, 1=Service (Used if idprod not defined)
6664
     *                                            If seller not subject to VAT, default VAT=0. End of rule.
6665
     *                                            If (seller country==buyer country), then default VAT=product's VAT. End of rule.
6666
     *                                            If (seller and buyer in EU) and sold product = new means of transportation (car, boat, airplane), default VAT =0 (VAT must be paid by the buyer to his country's tax office and not the seller). End of rule.
6667
     *                                            If (seller and buyer in EU) and buyer=private person, then default VAT=VAT of sold product.  End of rule.
6668
     *                                            If (seller and buyer in EU) and buyer=company then default VAT =0. End of rule.
6669
     *                                            Else, default proposed VAT==0. End of rule.
6670
     *  @param  bool         $options_only        Return HTML options lines only (for ajax treatment)
6671
     *  @param  int          $mode                0=Use vat rate as key in combo list, 1=Add VAT code after vat rate into key, -1=Use id of vat line as key
6672
     *  @param  int          $type_vat            0=All type, 1=VAT rate sale, 2=VAT rate purchase
6673
     *  @return string
6674
     */
6675
    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)
6676
    {
6677
		// phpcs:enable
6678
        global $langs, $mysoc;
6679
6680
        $langs->load('errors');
6681
6682
        $return = '';
6683
6684
        // Define defaultnpr, defaultttx and defaultcode
6685
        $defaultnpr = ($info_bits & 0x01);
6686
        $defaultnpr = (preg_match('/\*/', $selectedrate) ? 1 : $defaultnpr);
6687
        $defaulttx = str_replace('*', '', $selectedrate);
6688
        $defaultcode = '';
6689
        $reg = array();
6690
        if (preg_match('/\((.*)\)/', $defaulttx, $reg)) {
6691
            $defaultcode = $reg[1];
6692
            $defaulttx = preg_replace('/\s*\(.*\)/', '', $defaulttx);
6693
        }
6694
        //var_dump($selectedrate.'-'.$defaulttx.'-'.$defaultnpr.'-'.$defaultcode);
6695
6696
        // Check parameters
6697
        if (is_object($societe_vendeuse) && !$societe_vendeuse->country_code) {
6698
            if ($societe_vendeuse->id == $mysoc->id) {
6699
                $return .= '<span class="error">' . $langs->trans("ErrorYourCountryIsNotDefined") . '</span>';
6700
            } else {
6701
                $return .= '<span class="error">' . $langs->trans("ErrorSupplierCountryIsNotDefined") . '</span>';
6702
            }
6703
            return $return;
6704
        }
6705
6706
        //var_dump($societe_acheteuse);
6707
        //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";
6708
        //exit;
6709
6710
        // Define list of countries to use to search VAT rates to show
6711
        // First we defined code_country to use to find list
6712
        if (is_object($societe_vendeuse)) {
6713
            $code_country = "'" . $societe_vendeuse->country_code . "'";
6714
        } else {
6715
            $code_country = "'" . $mysoc->country_code . "'"; // Pour compatibilite ascendente
6716
        }
6717
        if (getDolGlobalString('SERVICE_ARE_ECOMMERCE_200238EC')) {    // If option to have vat for end customer for services is on
6718
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/company.lib.php';
6719
            // If SERVICE_ARE_ECOMMERCE_200238EC=1 combo list vat rate of purchaser and seller countries
6720
            // If SERVICE_ARE_ECOMMERCE_200238EC=2 combo list only the vat rate of the purchaser country
6721
            $selectVatComboMode = getDolGlobalString('SERVICE_ARE_ECOMMERCE_200238EC');
6722
            if (isInEEC($societe_vendeuse) && isInEEC($societe_acheteuse) && !$societe_acheteuse->isACompany()) {
6723
                // We also add the buyer country code
6724
                if (is_numeric($type)) {
6725
                    if ($type == 1) { // We know product is a service
6726
                        switch ($selectVatComboMode) {
6727
                            case '1':
6728
                                $code_country .= ",'" . $societe_acheteuse->country_code . "'";
6729
                                break;
6730
                            case '2':
6731
                                $code_country = "'" . $societe_acheteuse->country_code . "'";
6732
                                break;
6733
                        }
6734
                    }
6735
                } elseif (!$idprod) {  // We don't know type of product
6736
                    switch ($selectVatComboMode) {
6737
                        case '1':
6738
                            $code_country .= ",'" . $societe_acheteuse->country_code . "'";
6739
                            break;
6740
                        case '2':
6741
                            $code_country = "'" . $societe_acheteuse->country_code . "'";
6742
                            break;
6743
                    }
6744
                } else {
6745
                    $prodstatic = new Product($this->db);
6746
                    $prodstatic->fetch($idprod);
6747
                    if ($prodstatic->type == Product::TYPE_SERVICE) {   // We know product is a service
6748
                        $code_country .= ",'" . $societe_acheteuse->country_code . "'";
6749
                    }
6750
                }
6751
            }
6752
        }
6753
6754
        // Now we load the list of VAT
6755
        $this->load_cache_vatrates($code_country); // If no vat defined, return -1 with message into this->error
6756
6757
        // Keep only the VAT qualified for $type_vat
6758
        $arrayofvatrates = array();
6759
        foreach ($this->cache_vatrates as $cachevalue) {
6760
            if (empty($cachevalue['type_vat']) || $cachevalue['type_vat'] != $type_vat) {
6761
                $arrayofvatrates[] = $cachevalue;
6762
            }
6763
        }
6764
6765
        $num = count($arrayofvatrates);
6766
6767
        if ($num > 0) {
6768
            // Definition du taux a pre-selectionner (si defaulttx non force et donc vaut -1 ou '')
6769
            if ($defaulttx < 0 || dol_strlen($defaulttx) == 0) {
6770
                $tmpthirdparty = new Societe($this->db);
6771
6772
                $defaulttx = get_default_tva($societe_vendeuse, (is_object($societe_acheteuse) ? $societe_acheteuse : $tmpthirdparty), $idprod);
6773
                $defaultnpr = get_default_npr($societe_vendeuse, (is_object($societe_acheteuse) ? $societe_acheteuse : $tmpthirdparty), $idprod);
6774
6775
                if (preg_match('/\((.*)\)/', $defaulttx, $reg)) {
6776
                    $defaultcode = $reg[1];
6777
                    $defaulttx = preg_replace('/\s*\(.*\)/', '', $defaulttx);
6778
                }
6779
                if (empty($defaulttx)) {
6780
                    $defaultnpr = 0;
6781
                }
6782
            }
6783
6784
            // If we fails to find a default vat rate, we take the last one in list
6785
            // Because they are sorted in ascending order, the last one will be the higher one (we suppose the higher one is the current rate)
6786
            if ($defaulttx < 0 || dol_strlen($defaulttx) == 0) {
6787
                if (!getDolGlobalString('MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS')) {
6788
                    // We take the last one found in list
6789
                    $defaulttx = $arrayofvatrates[$num - 1]['txtva'];
6790
                } else {
6791
                    // We will use the rate defined into MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS
6792
                    $defaulttx = '';
6793
                    if (getDolGlobalString('MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS') != 'none') {
6794
                        $defaulttx = getDolGlobalString('MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS');
6795
                    }
6796
                    if (preg_match('/\((.*)\)/', $defaulttx, $reg)) {
6797
                        $defaultcode = $reg[1];
6798
                        $defaulttx = preg_replace('/\s*\(.*\)/', '', $defaulttx);
6799
                    }
6800
                }
6801
            }
6802
6803
            // Disabled if seller is not subject to VAT
6804
            $disabled = false;
6805
            $title = '';
6806
            if (is_object($societe_vendeuse) && $societe_vendeuse->id == $mysoc->id && $societe_vendeuse->tva_assuj == "0") {
6807
                // Override/enable VAT for expense report regardless of global setting - needed if expense report used for business expenses instead
6808
                // of using supplier invoices (this is a very bad idea !)
6809
                if (!getDolGlobalString('EXPENSEREPORT_OVERRIDE_VAT')) {
6810
                    $title = ' title="' . dol_escape_htmltag($langs->trans('VATIsNotUsed')) . '"';
6811
                    $disabled = true;
6812
                }
6813
            }
6814
6815
            if (!$options_only) {
6816
                $return .= '<select class="flat minwidth75imp maxwidth100 right" id="' . $htmlname . '" name="' . $htmlname . '"' . ($disabled ? ' disabled' : '') . $title . '>';
6817
            }
6818
6819
            $selectedfound = false;
6820
            foreach ($arrayofvatrates as $rate) {
6821
                // Keep only 0 if seller is not subject to VAT
6822
                if ($disabled && $rate['txtva'] != 0) {
6823
                    continue;
6824
                }
6825
6826
                // Define key to use into select list
6827
                $key = $rate['txtva'];
6828
                $key .= $rate['nprtva'] ? '*' : '';
6829
                if ($mode > 0 && $rate['code']) {
6830
                    $key .= ' (' . $rate['code'] . ')';
6831
                }
6832
                if ($mode < 0) {
6833
                    $key = $rate['rowid'];
6834
                }
6835
6836
                $return .= '<option value="' . $key . '"';
6837
                if (!$selectedfound) {
6838
                    if ($defaultcode) { // If defaultcode is defined, we used it in priority to select combo option instead of using rate+npr flag
6839
                        if ($defaultcode == $rate['code']) {
6840
                            $return .= ' selected';
6841
                            $selectedfound = true;
6842
                        }
6843
                    } elseif ($rate['txtva'] == $defaulttx && $rate['nprtva'] == $defaultnpr) {
6844
                        $return .= ' selected';
6845
                        $selectedfound = true;
6846
                    }
6847
                }
6848
                $return .= '>';
6849
6850
                // Show label of VAT
6851
                if ($mysoc->country_code == 'IN' || getDolGlobalString('MAIN_VAT_LABEL_IS_POSITIVE_RATES')) {
6852
                    // Label with all localtax and code. For example:  x.y / a.b / c.d (CODE)'
6853
                    $return .= $rate['labelpositiverates'];
6854
                } else {
6855
                    // Simple label
6856
                    $return .= vatrate($rate['label']);
6857
                }
6858
6859
                //$return.=($rate['code']?' '.$rate['code']:'');
6860
                $return .= (empty($rate['code']) && $rate['nprtva']) ? ' *' : ''; // We show the *  (old behaviour only if new vat code is not used)
6861
6862
                $return .= '</option>';
6863
            }
6864
6865
            if (!$options_only) {
6866
                $return .= '</select>';
6867
                //$return .= ajax_combobox($htmlname);      // This break for the moment the dynamic autoselection of a value when selecting a product in object lines
6868
            }
6869
        } else {
6870
            $return .= $this->error;
6871
        }
6872
6873
        $this->num = $num;
6874
        return $return;
6875
    }
6876
6877
6878
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6879
6880
    /**
6881
     *  Show a HTML widget to input a date or combo list for day, month, years and optionally hours and minutes.
6882
     *  Fields are preselected with :
6883
     *                - set_time date (must be a local PHP server timestamp or string date with format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM')
6884
     *                - local date in user area, if set_time is '' (so if set_time is '', output may differs when done from two different location)
6885
     *                - Empty (fields empty), if set_time is -1 (in this case, parameter empty must also have value 1)
6886
     *
6887
     * @param integer|string $set_time Pre-selected date (must be a local PHP server timestamp), -1 to keep date not preselected, '' to use current date with 00:00 hour (Parameter 'empty' must be 0 or 2).
6888
     * @param string $prefix Prefix for fields name
6889
     * @param int $h 1 or 2=Show also hours (2=hours on a new line), -1 has same effect but hour and minutes are prefilled with 23:59 if date is empty, 3 show hour always empty
6890
     * @param int $m 1=Show also minutes, -1 has same effect but hour and minutes are prefilled with 23:59 if date is empty, 3 show minutes always empty
6891
     * @param int $empty 0=Fields required, 1=Empty inputs are allowed, 2=Empty inputs are allowed for hours only
6892
     * @param string $form_name Not used
6893
     * @param int $d 1=Show days, month, years
6894
     * @param int $addnowlink Add a link "Now"
6895
     * @param int $nooutput Do not output html string but return it
6896
     * @param int $disabled Disable input fields
6897
     * @param int $fullday When a checkbox with this html name is on, hour and day are set with 00:00 or 23:59
6898
     * @param string $addplusone Add a link "+1 hour". Value must be name of another select_date field.
6899
     * @param int|string $adddateof Add a link "Date of invoice" using the following date.
6900
     * @return    string                        '' or HTML component string if nooutput is 1
6901
     * @deprecated
6902
     * @see    selectDate(), form_date(), select_month(), select_year(), select_dayofweek()
6903
     */
6904
    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 = '')
6905
    {
6906
		// phpcs:enable
6907
        dol_syslog(__METHOD__ . ': using select_date is deprecated. Use selectDate instead.', LOG_WARNING);
6908
        $retstring = $this->selectDate($set_time, $prefix, $h, $m, $empty, $form_name, $d, $addnowlink, $disabled, $fullday, $addplusone, $adddateof);
6909
        if (!empty($nooutput)) {
6910
            return $retstring;
6911
        }
6912
        print $retstring;
6913
6914
        return '';
6915
    }
6916
6917
    /**
6918
     *  Show 2 HTML widget to input a date or combo list for day, month, years and optionally hours and minutes.
6919
     *  Fields are preselected with :
6920
     *              - set_time date (must be a local PHP server timestamp or string date with format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM')
6921
     *              - local date in user area, if set_time is '' (so if set_time is '', output may differs when done from two different location)
6922
     *              - Empty (fields empty), if set_time is -1 (in this case, parameter empty must also have value 1)
6923
     *
6924
     * @param integer|string $set_time Pre-selected date (must be a local PHP server timestamp), -1 to keep date not preselected, '' to use current date with 00:00 hour (Parameter 'empty' must be 0 or 2).
6925
     * @param integer|string $set_time_end Pre-selected date (must be a local PHP server timestamp), -1 to keep date not preselected, '' to use current date with 00:00 hour (Parameter 'empty' must be 0 or 2).
6926
     * @param string $prefix Prefix for fields name
6927
     * @param int    $empty 0=Fields required, 1=Empty inputs are allowed, 2=Empty inputs are allowed for hours only
6928
     * @param int    $forcenewline Force new line between the 2 dates.
6929
     * @return string                        Html for selectDate
6930
     * @see    form_date(), select_month(), select_year(), select_dayofweek()
6931
     */
6932
    public function selectDateToDate($set_time = '', $set_time_end = '', $prefix = 're', $empty = 0, $forcenewline = 0)
6933
    {
6934
        global $langs;
6935
6936
        $ret = $this->selectDate($set_time, $prefix . '_start', 0, 0, $empty, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("from"), 'tzuserrel');
6937
        if ($forcenewline) {
6938
            $ret .= '<br>';
6939
        }
6940
        $ret .= $this->selectDate($set_time_end, $prefix . '_end', 0, 0, $empty, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("to"), 'tzuserrel');
6941
        return $ret;
6942
    }
6943
6944
    /**
6945
     *  Show a HTML widget to input a date or combo list for day, month, years and optionally hours and minutes.
6946
     *  Fields are preselected with :
6947
     *              - set_time date (must be a local PHP server timestamp or string date with format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM')
6948
     *              - local date in user area, if set_time is '' (so if set_time is '', output may differs when done from two different location)
6949
     *              - Empty (fields empty), if set_time is -1 (in this case, parameter empty must also have value 1)
6950
     *
6951
     * @param integer|string        $set_time       Pre-selected date (must be a local PHP server timestamp), -1 to keep date not preselected, '' to use current date with 00:00 hour (Parameter 'empty' must be 0 or 2).
6952
     * @param string                $prefix         Prefix for fields name
6953
     * @param int                   $h              1 or 2=Show also hours (2=hours on a new line), -1 has same effect but hour and minutes are prefilled with 23:59 if date is empty, 3 or 4 (4=hours on a new line)=Show hour always empty
6954
     * @param int                   $m              1=Show also minutes, -1 has same effect but hour and minutes are prefilled with 23:59 if date is empty, 3 show minutes always empty
6955
     * @param int                   $empty          0=Fields required, 1=Empty inputs are allowed, 2=Empty inputs are allowed for hours only
6956
     * @param string                $form_name      Not used
6957
     * @param int<0,1>              $d              1=Show days, month, years
6958
     * @param int<0,2>              $addnowlink     Add a link "Now", 1 with server time, 2 with local computer time
6959
     * @param int<0,1>              $disabled       Disable input fields
6960
     * @param int|string            $fullday        When a checkbox with id #fullday is checked, hours are set with 00:00 (if value if 'fulldaystart') or 23:59 (if value is 'fulldayend')
6961
     * @param string                $addplusone     Add a link "+1 hour". Value must be name of another selectDate field.
6962
     * @param int|string|array      $adddateof      Add a link "Date of ..." using the following date. Must be array(array('adddateof'=>..., 'labeladddateof'=>...))
6963
     * @param string                $openinghours   Specify hour start and hour end for the select ex 8,20
6964
     * @param int                   $stepminutes    Specify step for minutes between 1 and 30
6965
     * @param string                $labeladddateof Label to use for the $adddateof parameter. Deprecated. Used only when $adddateof is not an array.
6966
     * @param string                $placeholder    Placeholder
6967
     * @param mixed                 $gm             'auto' (for backward compatibility, avoid this), 'gmt' or 'tzserver' or 'tzuserrel'
6968
     * @return string                               Html for selectDate
6969
     * @see    form_date(), select_month(), select_year(), select_dayofweek()
6970
     */
6971
    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')
6972
    {
6973
        global $conf, $langs;
6974
6975
        if ($gm === 'auto') {
6976
            $gm = (empty($conf) ? 'tzserver' : $conf->tzuserinputkey);
6977
        }
6978
6979
        $retstring = '';
6980
6981
        if ($prefix == '') {
6982
            $prefix = 're';
6983
        }
6984
        if ($h == '') {
6985
            $h = 0;
6986
        }
6987
        if ($m == '') {
6988
            $m = 0;
6989
        }
6990
        $emptydate = 0;
6991
        $emptyhours = 0;
6992
        if ($stepminutes <= 0 || $stepminutes > 30) {
6993
            $stepminutes = 1;
6994
        }
6995
        if ($empty == 1) {
6996
            $emptydate = 1;
6997
            $emptyhours = 1;
6998
        }
6999
        if ($empty == 2) {
7000
            $emptydate = 0;
7001
            $emptyhours = 1;
7002
        }
7003
        $orig_set_time = $set_time;
7004
7005
        if ($set_time === '' && $emptydate == 0) {
7006
            include_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php';
7007
            if ($gm == 'tzuser' || $gm == 'tzuserrel') {
7008
                $set_time = dol_now($gm);
7009
            } else {
7010
                $set_time = dol_now('tzuser') - (getServerTimeZoneInt('now') * 3600); // set_time must be relative to PHP server timezone
7011
            }
7012
        }
7013
7014
        // Analysis of the pre-selection date
7015
        $reg = array();
7016
        $shour = '';
7017
        $smin = '';
7018
        $ssec = '';
7019
        if (preg_match('/^([0-9]+)\-([0-9]+)\-([0-9]+)\s?([0-9]+)?:?([0-9]+)?/', $set_time, $reg)) {    // deprecated usage
7020
            // Date format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'
7021
            $syear = (!empty($reg[1]) ? $reg[1] : '');
7022
            $smonth = (!empty($reg[2]) ? $reg[2] : '');
7023
            $sday = (!empty($reg[3]) ? $reg[3] : '');
7024
            $shour = (!empty($reg[4]) ? $reg[4] : '');
7025
            $smin = (!empty($reg[5]) ? $reg[5] : '');
7026
        } elseif (strval($set_time) != '' && $set_time != -1) {
7027
            // set_time est un timestamps (0 possible)
7028
            $syear = dol_print_date($set_time, "%Y", $gm);
7029
            $smonth = dol_print_date($set_time, "%m", $gm);
7030
            $sday = dol_print_date($set_time, "%d", $gm);
7031
            if ($orig_set_time != '') {
7032
                $shour = dol_print_date($set_time, "%H", $gm);
7033
                $smin = dol_print_date($set_time, "%M", $gm);
7034
                $ssec = dol_print_date($set_time, "%S", $gm);
7035
            }
7036
        } else {
7037
            // Date est '' ou vaut -1
7038
            $syear = '';
7039
            $smonth = '';
7040
            $sday = '';
7041
            $shour = getDolGlobalString('MAIN_DEFAULT_DATE_HOUR', ($h == -1 ? '23' : ''));
7042
            $smin = getDolGlobalString('MAIN_DEFAULT_DATE_MIN', ($h == -1 ? '59' : ''));
7043
            $ssec = getDolGlobalString('MAIN_DEFAULT_DATE_SEC', ($h == -1 ? '59' : ''));
7044
        }
7045
        if ($h == 3 || $h == 4) {
7046
            $shour = '';
7047
        }
7048
        if ($m == 3) {
7049
            $smin = '';
7050
        }
7051
7052
        $nowgmt = dol_now('gmt');
7053
        //var_dump(dol_print_date($nowgmt, 'dayhourinputnoreduce', 'tzuserrel'));
7054
7055
        // You can set MAIN_POPUP_CALENDAR to 'eldy' or 'jquery'
7056
        $usecalendar = 'combo';
7057
        if (!empty($conf->use_javascript_ajax) && (!getDolGlobalString('MAIN_POPUP_CALENDAR') || getDolGlobalString('MAIN_POPUP_CALENDAR') != "none")) {
7058
            $usecalendar = ((!getDolGlobalString('MAIN_POPUP_CALENDAR') || getDolGlobalString('MAIN_POPUP_CALENDAR') == 'eldy') ? 'jquery' : $conf->global->MAIN_POPUP_CALENDAR);
7059
        }
7060
        if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
7061
            // If we use a text browser or screen reader, we use the 'combo' date selector
7062
            $usecalendar = 'html';
7063
        }
7064
7065
        if ($d) {
7066
            // Show date with popup
7067
            if ($usecalendar != 'combo') {
7068
                $formated_date = '';
7069
                //print "e".$set_time." t ".$conf->format_date_short;
7070
                if (strval($set_time) != '' && $set_time != -1) {
7071
                    //$formated_date=dol_print_date($set_time,$conf->format_date_short);
7072
                    $formated_date = dol_print_date($set_time, $langs->trans("FormatDateShortInput"), $gm); // FormatDateShortInput for dol_print_date / FormatDateShortJavaInput that is same for javascript
7073
                }
7074
7075
                // Calendrier popup version eldy
7076
                if ($usecalendar == "eldy") {
7077
                    // Input area to enter date manually
7078
                    $retstring .= '<input id="' . $prefix . '" name="' . $prefix . '" type="text" class="maxwidthdate center" maxlength="11" value="' . $formated_date . '"';
7079
                    $retstring .= ($disabled ? ' disabled' : '');
7080
                    $retstring .= ' onChange="dpChangeDay(\'' . $prefix . '\',\'' . $langs->trans("FormatDateShortJavaInput") . '\'); "'; // FormatDateShortInput for dol_print_date / FormatDateShortJavaInput that is same for javascript
7081
                    $retstring .= ' autocomplete="off">';
7082
7083
                    // Icon calendar
7084
                    $retstringbuttom = '';
7085
                    if (!$disabled) {
7086
                        $retstringbuttom = '<button id="' . $prefix . 'Button" type="button" class="dpInvisibleButtons"';
7087
                        $base = constant('BASE_URL') . '/core/';
7088
                        $retstringbuttom .= ' onClick="showDP(\'' . $base . '\',\'' . $prefix . '\',\'' . $langs->trans("FormatDateShortJavaInput") . '\',\'' . $langs->defaultlang . '\');"';
7089
                        $retstringbuttom .= '>' . img_object($langs->trans("SelectDate"), 'calendarday', 'class="datecallink"') . '</button>';
7090
                    } else {
7091
                        $retstringbuttom = '<button id="' . $prefix . 'Button" type="button" class="dpInvisibleButtons">' . img_object($langs->trans("Disabled"), 'calendarday', 'class="datecallink"') . '</button>';
7092
                    }
7093
                    $retstring = $retstringbuttom . $retstring;
7094
7095
                    $retstring .= '<input type="hidden" id="' . $prefix . 'day"   name="' . $prefix . 'day"   value="' . $sday . '">' . "\n";
7096
                    $retstring .= '<input type="hidden" id="' . $prefix . 'month" name="' . $prefix . 'month" value="' . $smonth . '">' . "\n";
7097
                    $retstring .= '<input type="hidden" id="' . $prefix . 'year"  name="' . $prefix . 'year"  value="' . $syear . '">' . "\n";
7098
                } elseif ($usecalendar == 'jquery' || $usecalendar == 'html') {
7099
                    if (!$disabled && $usecalendar != 'html') {
7100
                        // Output javascript for datepicker
7101
                        $minYear = getDolGlobalInt('MIN_YEAR_SELECT_DATE', (idate('Y') - 100));
7102
                        $maxYear = getDolGlobalInt('MAX_YEAR_SELECT_DATE', (idate('Y') + 100));
7103
7104
                        $retstring .= '<script nonce="' . getNonce() . '" type="text/javascript">';
7105
                        $retstring .= "$(function(){ $('#" . $prefix . "').datepicker({
7106
							dateFormat: '" . $langs->trans("FormatDateShortJQueryInput") . "',
7107
							autoclose: true,
7108
							todayHighlight: true,
7109
							yearRange: '" . $minYear . ":" . $maxYear . "',";
7110
                        if (!empty($conf->dol_use_jmobile)) {
7111
                            $retstring .= "
7112
								beforeShow: function (input, datePicker) {
7113
									input.disabled = true;
7114
								},
7115
								onClose: function (dateText, datePicker) {
7116
									this.disabled = false;
7117
								},
7118
								";
7119
                        }
7120
                        // Note: We don't need monthNames, monthNamesShort, dayNames, dayNamesShort, dayNamesMin, they are set globally on datepicker component in lib_head.js.php
7121
                        if (!getDolGlobalString('MAIN_POPUP_CALENDAR_ON_FOCUS')) {
7122
                            $retstring .= "
7123
								showOn: 'button',	/* both has problem with autocompletion */
7124
								buttonImage: '" . constant('DOL_URL_ROOT') . "/theme/" . dol_escape_js($conf->theme) . "/img/object_calendarday.png',
7125
								buttonImageOnly: true";
7126
                        }
7127
                        $retstring .= "
7128
							}) });";
7129
                        $retstring .= "</script>";
7130
                    }
7131
7132
                    // Input area to enter date manually
7133
                    $retstring .= '<div class="nowraponall inline-block divfordateinput">';
7134
                    $retstring .= '<input id="' . $prefix . '" name="' . $prefix . '" type="text" class="maxwidthdate center" maxlength="11" value="' . $formated_date . '"';
7135
                    $retstring .= ($disabled ? ' disabled' : '');
7136
                    $retstring .= ($placeholder ? ' placeholder="' . dol_escape_htmltag($placeholder) . '"' : '');
7137
                    $retstring .= ' onChange="dpChangeDay(\'' . dol_escape_js($prefix) . '\',\'' . dol_escape_js($langs->trans("FormatDateShortJavaInput")) . '\'); "'; // FormatDateShortInput for dol_print_date / FormatDateShortJavaInput that is same for javascript
7138
                    $retstring .= ' autocomplete="off">';
7139
7140
                    // Icone calendrier
7141
                    if ($disabled) {
7142
                        $retstringbutton = '<button id="' . $prefix . 'Button" type="button" class="dpInvisibleButtons">' . img_object($langs->trans("Disabled"), 'calendarday', 'class="datecallink"') . '</button>';
7143
                        $retstring = $retstringbutton . $retstring;
7144
                    }
7145
7146
                    $retstring .= '</div>';
7147
                    $retstring .= '<input type="hidden" id="' . $prefix . 'day"   name="' . $prefix . 'day"   value="' . $sday . '">' . "\n";
7148
                    $retstring .= '<input type="hidden" id="' . $prefix . 'month" name="' . $prefix . 'month" value="' . $smonth . '">' . "\n";
7149
                    $retstring .= '<input type="hidden" id="' . $prefix . 'year"  name="' . $prefix . 'year"  value="' . $syear . '">' . "\n";
7150
                } else {
7151
                    $retstring .= "Bad value of MAIN_POPUP_CALENDAR";
7152
                }
7153
            } else {
7154
                // Show date with combo selects
7155
                // Day
7156
                $retstring .= '<select' . ($disabled ? ' disabled' : '') . ' class="flat valignmiddle maxwidth50imp" id="' . $prefix . 'day" name="' . $prefix . 'day">';
7157
7158
                if ($emptydate || $set_time == -1) {
7159
                    $retstring .= '<option value="0" selected>&nbsp;</option>';
7160
                }
7161
7162
                for ($day = 1; $day <= 31; $day++) {
7163
                    $retstring .= '<option value="' . $day . '"' . ($day == $sday ? ' selected' : '') . '>' . $day . '</option>';
7164
                }
7165
7166
                $retstring .= "</select>";
7167
7168
                $retstring .= '<select' . ($disabled ? ' disabled' : '') . ' class="flat valignmiddle maxwidth75imp" id="' . $prefix . 'month" name="' . $prefix . 'month">';
7169
                if ($emptydate || $set_time == -1) {
7170
                    $retstring .= '<option value="0" selected>&nbsp;</option>';
7171
                }
7172
7173
                // Month
7174
                for ($month = 1; $month <= 12; $month++) {
7175
                    $retstring .= '<option value="' . $month . '"' . ($month == $smonth ? ' selected' : '') . '>';
7176
                    $retstring .= dol_print_date(mktime(12, 0, 0, $month, 1, 2000), "%b");
7177
                    $retstring .= "</option>";
7178
                }
7179
                $retstring .= "</select>";
7180
7181
                // Year
7182
                if ($emptydate || $set_time == -1) {
7183
                    $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 . '">';
7184
                } else {
7185
                    $retstring .= '<select' . ($disabled ? ' disabled' : '') . ' class="flat valignmiddle maxwidth75imp" id="' . $prefix . 'year" name="' . $prefix . 'year">';
7186
7187
                    $syear = (int) $syear;
7188
                    for ($year = $syear - 10; $year < (int) $syear + 10; $year++) {
7189
                        $retstring .= '<option value="' . $year . '"' . ($year == $syear ? ' selected' : '') . '>' . $year . '</option>';
7190
                    }
7191
                    $retstring .= "</select>\n";
7192
                }
7193
            }
7194
        }
7195
7196
        if ($d && $h) {
7197
            $retstring .= (($h == 2 || $h == 4) ? '<br>' : ' ');
7198
            $retstring .= '<span class="nowraponall">';
7199
        }
7200
7201
        if ($h) {
7202
            $hourstart = 0;
7203
            $hourend = 24;
7204
            if ($openinghours != '') {
7205
                $openinghours = explode(',', $openinghours);
7206
                $hourstart = $openinghours[0];
7207
                $hourend = $openinghours[1];
7208
                if ($hourend < $hourstart) {
7209
                    $hourend = $hourstart;
7210
                }
7211
            }
7212
            // Show hour
7213
            $retstring .= '<select' . ($disabled ? ' disabled' : '') . ' class="flat valignmiddle maxwidth50 ' . ($fullday ? $fullday . 'hour' : '') . '" id="' . $prefix . 'hour" name="' . $prefix . 'hour">';
7214
            if ($emptyhours) {
7215
                $retstring .= '<option value="-1">&nbsp;</option>';
7216
            }
7217
            for ($hour = $hourstart; $hour < $hourend; $hour++) {
7218
                if (strlen($hour) < 2) {
7219
                    $hour = "0" . $hour;
7220
                }
7221
                $retstring .= '<option value="' . $hour . '"' . (($hour == $shour) ? ' selected' : '') . '>' . $hour;
7222
                //$retstring .= (empty($conf->dol_optimize_smallscreen) ? '' : 'H');
7223
                $retstring .= '</option>';
7224
            }
7225
            $retstring .= '</select>';
7226
            //if ($m && empty($conf->dol_optimize_smallscreen)) $retstring .= ":";
7227
            if ($m) {
7228
                $retstring .= ":";
7229
            }
7230
        }
7231
7232
        if ($m) {
7233
            // Show minutes
7234
            $retstring .= '<select' . ($disabled ? ' disabled' : '') . ' class="flat valignmiddle maxwidth50 ' . ($fullday ? $fullday . 'min' : '') . '" id="' . $prefix . 'min" name="' . $prefix . 'min">';
7235
            if ($emptyhours) {
7236
                $retstring .= '<option value="-1">&nbsp;</option>';
7237
            }
7238
            for ($min = 0; $min < 60; $min += $stepminutes) {
7239
                $min_str = sprintf("%02d", $min);
7240
                $retstring .= '<option value="' . $min_str . '"' . (($min_str == $smin) ? ' selected' : '') . '>' . $min_str . '</option>';
7241
            }
7242
            $retstring .= '</select>';
7243
7244
            $retstring .= '<input type="hidden" name="' . $prefix . 'sec" value="' . $ssec . '">';
7245
        }
7246
7247
        if ($d && $h) {
7248
            $retstring .= '</span>';
7249
        }
7250
7251
        // Add a "Now" link
7252
        if (!empty($conf->use_javascript_ajax) && $addnowlink) {
7253
            // Script which will be inserted in the onClick of the "Now" link
7254
            $reset_scripts = "";
7255
            if ($addnowlink == 2) { // local computer time
7256
                // pad add leading 0 on numbers
7257
                $reset_scripts .= "Number.prototype.pad = function(size) {
7258
                        var s = String(this);
7259
                        while (s.length < (size || 2)) {s = '0' + s;}
7260
                        return s;
7261
                    };
7262
                    var d = new Date();";
7263
            }
7264
7265
            // Generate the date part, depending on the use or not of the javascript calendar
7266
            if ($addnowlink == 1) { // server time expressed in user time setup
7267
                $reset_scripts .= 'jQuery(\'#' . $prefix . '\').val(\'' . dol_print_date($nowgmt, 'day', 'tzuserrel') . '\');';
7268
                $reset_scripts .= 'jQuery(\'#' . $prefix . 'day\').val(\'' . dol_print_date($nowgmt, '%d', 'tzuserrel') . '\');';
7269
                $reset_scripts .= 'jQuery(\'#' . $prefix . 'month\').val(\'' . dol_print_date($nowgmt, '%m', 'tzuserrel') . '\');';
7270
                $reset_scripts .= 'jQuery(\'#' . $prefix . 'year\').val(\'' . dol_print_date($nowgmt, '%Y', 'tzuserrel') . '\');';
7271
            } elseif ($addnowlink == 2) {
7272
                /* Disabled because the output does not use the string format defined by FormatDateShort key to forge the value into #prefix.
7273
                 * This break application for foreign languages.
7274
                $reset_scripts .= 'jQuery(\'#'.$prefix.'\').val(d.toLocaleDateString(\''.str_replace('_', '-', $langs->defaultlang).'\'));';
7275
                $reset_scripts .= 'jQuery(\'#'.$prefix.'day\').val(d.getDate().pad());';
7276
                $reset_scripts .= 'jQuery(\'#'.$prefix.'month\').val(parseInt(d.getMonth().pad()) + 1);';
7277
                $reset_scripts .= 'jQuery(\'#'.$prefix.'year\').val(d.getFullYear());';
7278
                */
7279
                $reset_scripts .= 'jQuery(\'#' . $prefix . '\').val(\'' . dol_print_date($nowgmt, 'day', 'tzuserrel') . '\');';
7280
                $reset_scripts .= 'jQuery(\'#' . $prefix . 'day\').val(\'' . dol_print_date($nowgmt, '%d', 'tzuserrel') . '\');';
7281
                $reset_scripts .= 'jQuery(\'#' . $prefix . 'month\').val(\'' . dol_print_date($nowgmt, '%m', 'tzuserrel') . '\');';
7282
                $reset_scripts .= 'jQuery(\'#' . $prefix . 'year\').val(\'' . dol_print_date($nowgmt, '%Y', 'tzuserrel') . '\');';
7283
            }
7284
            /*if ($usecalendar == "eldy")
7285
            {
7286
                $base=DOL_URL_ROOT.'/core/';
7287
                $reset_scripts .= 'resetDP(\''.$base.'\',\''.$prefix.'\',\''.$langs->trans("FormatDateShortJavaInput").'\',\''.$langs->defaultlang.'\');';
7288
            }
7289
            else
7290
            {
7291
                $reset_scripts .= 'this.form.elements[\''.$prefix.'day\'].value=formatDate(new Date(), \'d\'); ';
7292
                $reset_scripts .= 'this.form.elements[\''.$prefix.'month\'].value=formatDate(new Date(), \'M\'); ';
7293
                $reset_scripts .= 'this.form.elements[\''.$prefix.'year\'].value=formatDate(new Date(), \'yyyy\'); ';
7294
            }*/
7295
            // Update the hour part
7296
            if ($h) {
7297
                if ($fullday) {
7298
                    $reset_scripts .= " if (jQuery('#fullday:checked').val() == null) {";
7299
                }
7300
                //$reset_scripts .= 'this.form.elements[\''.$prefix.'hour\'].value=formatDate(new Date(), \'HH\'); ';
7301
                if ($addnowlink == 1) {
7302
                    $reset_scripts .= 'jQuery(\'#' . $prefix . 'hour\').val(\'' . dol_print_date($nowgmt, '%H', 'tzuserrel') . '\');';
7303
                    $reset_scripts .= 'jQuery(\'#' . $prefix . 'hour\').change();';
7304
                } elseif ($addnowlink == 2) {
7305
                    $reset_scripts .= 'jQuery(\'#' . $prefix . 'hour\').val(d.getHours().pad());';
7306
                    $reset_scripts .= 'jQuery(\'#' . $prefix . 'hour\').change();';
7307
                }
7308
7309
                if ($fullday) {
7310
                    $reset_scripts .= ' } ';
7311
                }
7312
            }
7313
            // Update the minute part
7314
            if ($m) {
7315
                if ($fullday) {
7316
                    $reset_scripts .= " if (jQuery('#fullday:checked').val() == null) {";
7317
                }
7318
                //$reset_scripts .= 'this.form.elements[\''.$prefix.'min\'].value=formatDate(new Date(), \'mm\'); ';
7319
                if ($addnowlink == 1) {
7320
                    $reset_scripts .= 'jQuery(\'#' . $prefix . 'min\').val(\'' . dol_print_date($nowgmt, '%M', 'tzuserrel') . '\');';
7321
                    $reset_scripts .= 'jQuery(\'#' . $prefix . 'min\').change();';
7322
                } elseif ($addnowlink == 2) {
7323
                    $reset_scripts .= 'jQuery(\'#' . $prefix . 'min\').val(d.getMinutes().pad());';
7324
                    $reset_scripts .= 'jQuery(\'#' . $prefix . 'min\').change();';
7325
                }
7326
                if ($fullday) {
7327
                    $reset_scripts .= ' } ';
7328
                }
7329
            }
7330
            // If reset_scripts is not empty, print the link with the reset_scripts in the onClick
7331
            if ($reset_scripts && !getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
7332
                $retstring .= ' <button class="dpInvisibleButtons datenowlink" id="' . $prefix . 'ButtonNow" type="button" name="_useless" value="now" onClick="' . $reset_scripts . '">';
7333
                $retstring .= $langs->trans("Now");
7334
                $retstring .= '</button> ';
7335
            }
7336
        }
7337
7338
        // Add a "Plus one hour" link
7339
        if ($conf->use_javascript_ajax && $addplusone) {
7340
            // Script which will be inserted in the onClick of the "Add plusone" link
7341
            $reset_scripts = "";
7342
7343
            // Generate the date part, depending on the use or not of the javascript calendar
7344
            $reset_scripts .= 'jQuery(\'#' . $prefix . '\').val(\'' . dol_print_date($nowgmt, 'dayinputnoreduce', 'tzuserrel') . '\');';
7345
            $reset_scripts .= 'jQuery(\'#' . $prefix . 'day\').val(\'' . dol_print_date($nowgmt, '%d', 'tzuserrel') . '\');';
7346
            $reset_scripts .= 'jQuery(\'#' . $prefix . 'month\').val(\'' . dol_print_date($nowgmt, '%m', 'tzuserrel') . '\');';
7347
            $reset_scripts .= 'jQuery(\'#' . $prefix . 'year\').val(\'' . dol_print_date($nowgmt, '%Y', 'tzuserrel') . '\');';
7348
            // Update the hour part
7349
            if ($h) {
7350
                if ($fullday) {
7351
                    $reset_scripts .= " if (jQuery('#fullday:checked').val() == null) {";
7352
                }
7353
                $reset_scripts .= 'jQuery(\'#' . $prefix . 'hour\').val(\'' . dol_print_date($nowgmt, '%H', 'tzuserrel') . '\');';
7354
                if ($fullday) {
7355
                    $reset_scripts .= ' } ';
7356
                }
7357
            }
7358
            // Update the minute part
7359
            if ($m) {
7360
                if ($fullday) {
7361
                    $reset_scripts .= " if (jQuery('#fullday:checked').val() == null) {";
7362
                }
7363
                $reset_scripts .= 'jQuery(\'#' . $prefix . 'min\').val(\'' . dol_print_date($nowgmt, '%M', 'tzuserrel') . '\');';
7364
                if ($fullday) {
7365
                    $reset_scripts .= ' } ';
7366
                }
7367
            }
7368
            // If reset_scripts is not empty, print the link with the reset_scripts in the onClick
7369
            if ($reset_scripts && empty($conf->dol_optimize_smallscreen)) {
7370
                $retstring .= ' <button class="dpInvisibleButtons datenowlink" id="' . $prefix . 'ButtonPlusOne" type="button" name="_useless2" value="plusone" onClick="' . $reset_scripts . '">';
7371
                $retstring .= $langs->trans("DateStartPlusOne");
7372
                $retstring .= '</button> ';
7373
            }
7374
        }
7375
7376
        // Add a link to set data
7377
        if ($conf->use_javascript_ajax && !empty($adddateof)) {
7378
            if (!is_array($adddateof)) {
7379
                $arrayofdateof = array(array('adddateof' => $adddateof, 'labeladddateof' => $labeladddateof));
7380
            } else {
7381
                $arrayofdateof = $adddateof;
7382
            }
7383
            foreach ($arrayofdateof as $valuedateof) {
7384
                $tmpadddateof = empty($valuedateof['adddateof']) ? 0 : $valuedateof['adddateof'];
7385
                $tmplabeladddateof = empty($valuedateof['labeladddateof']) ? '' : $valuedateof['labeladddateof'];
7386
                $tmparray = dol_getdate($tmpadddateof);
7387
                if (empty($tmplabeladddateof)) {
7388
                    $tmplabeladddateof = $langs->trans("DateInvoice");
7389
                }
7390
                $reset_scripts = 'console.log(\'Click on now link\'); ';
7391
                $reset_scripts .= 'jQuery(\'#' . $prefix . '\').val(\'' . dol_print_date($tmpadddateof, 'dayinputnoreduce') . '\');';
7392
                $reset_scripts .= 'jQuery(\'#' . $prefix . 'day\').val(\'' . $tmparray['mday'] . '\');';
7393
                $reset_scripts .= 'jQuery(\'#' . $prefix . 'month\').val(\'' . $tmparray['mon'] . '\');';
7394
                $reset_scripts .= 'jQuery(\'#' . $prefix . 'year\').val(\'' . $tmparray['year'] . '\');';
7395
                $retstring .= ' - <button class="dpInvisibleButtons datenowlink" id="dateofinvoice" type="button" name="_dateofinvoice" value="now" onclick="' . $reset_scripts . '">' . $tmplabeladddateof . '</button>';
7396
            }
7397
        }
7398
7399
        return $retstring;
7400
    }
7401
7402
    /**
7403
     * selectTypeDuration
7404
     *
7405
     * @param string $prefix Prefix
7406
     * @param string $selected Selected duration type
7407
     * @param string[] $excludetypes Array of duration types to exclude. Example array('y', 'm')
7408
     * @return  string                    HTML select string
7409
     */
7410
    public function selectTypeDuration($prefix, $selected = 'i', $excludetypes = array())
7411
    {
7412
        global $langs;
7413
7414
        $TDurationTypes = array(
7415
            'y' => $langs->trans('Years'),
7416
            'm' => $langs->trans('Month'),
7417
            'w' => $langs->trans('Weeks'),
7418
            'd' => $langs->trans('Days'),
7419
            'h' => $langs->trans('Hours'),
7420
            'i' => $langs->trans('Minutes')
7421
        );
7422
7423
        // Removed undesired duration types
7424
        foreach ($excludetypes as $value) {
7425
            unset($TDurationTypes[$value]);
7426
        }
7427
7428
        $retstring = '<select class="flat minwidth75 maxwidth100" id="select_' . $prefix . 'type_duration" name="' . $prefix . 'type_duration">';
7429
        foreach ($TDurationTypes as $key => $typeduration) {
7430
            $retstring .= '<option value="' . $key . '"';
7431
            if ($key == $selected) {
7432
                $retstring .= " selected";
7433
            }
7434
            $retstring .= ">" . $typeduration . "</option>";
7435
        }
7436
        $retstring .= "</select>";
7437
7438
        $retstring .= ajax_combobox('select_' . $prefix . 'type_duration');
7439
7440
        return $retstring;
7441
    }
7442
7443
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
7444
7445
    /**
7446
     *  Function to show a form to select a duration on a page
7447
     *
7448
     * @param string $prefix Prefix for input fields
7449
     * @param int|string $iSecond Default preselected duration (number of seconds or '')
7450
     * @param int $disabled Disable the combo box
7451
     * @param string $typehour If 'select' then input hour and input min is a combo,
7452
     *                         If 'text' input hour is in text and input min is a text,
7453
     *                         If 'textselect' input hour is in text and input min is a combo
7454
     * @param integer $minunderhours If 1, show minutes selection under the hours
7455
     * @param int $nooutput Do not output html string but return it
7456
     * @return    string                        HTML component
7457
     */
7458
    public function select_duration($prefix, $iSecond = '', $disabled = 0, $typehour = 'select', $minunderhours = 0, $nooutput = 0)
7459
    {
7460
		// phpcs:enable
7461
        global $langs;
7462
7463
        $retstring = '<span class="nowraponall">';
7464
7465
        $hourSelected = '';
7466
        $minSelected = '';
7467
7468
        // Hours
7469
        if ($iSecond != '') {
7470
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/date.lib.php';
7471
7472
            $hourSelected = convertSecondToTime($iSecond, 'allhour');
7473
            $minSelected = convertSecondToTime($iSecond, 'min');
7474
        }
7475
7476
        if ($typehour == 'select') {
7477
            $retstring .= '<select class="flat" id="select_' . $prefix . 'hour" name="' . $prefix . 'hour"' . ($disabled ? ' disabled' : '') . '>';
7478
            for ($hour = 0; $hour < 25; $hour++) {    // For a duration, we allow 24 hours
7479
                $retstring .= '<option value="' . $hour . '"';
7480
                if (is_numeric($hourSelected) && $hourSelected == $hour) {
7481
                    $retstring .= " selected";
7482
                }
7483
                $retstring .= ">" . $hour . "</option>";
7484
            }
7485
            $retstring .= "</select>";
7486
        } elseif ($typehour == 'text' || $typehour == 'textselect') {
7487
            $retstring .= '<input placeholder="' . $langs->trans('HourShort') . '" type="number" min="0" name="' . $prefix . 'hour"' . ($disabled ? ' disabled' : '') . ' class="flat maxwidth50 inputhour right" value="' . (($hourSelected != '') ? ((int) $hourSelected) : '') . '">';
7488
        } else {
7489
            return 'BadValueForParameterTypeHour';
7490
        }
7491
7492
        if ($typehour != 'text') {
7493
            $retstring .= ' ' . $langs->trans('HourShort');
7494
        } else {
7495
            $retstring .= '<span class="">:</span>';
7496
        }
7497
7498
        // Minutes
7499
        if ($minunderhours) {
7500
            $retstring .= '<br>';
7501
        } else {
7502
            if ($typehour != 'text') {
7503
                $retstring .= '<span class="hideonsmartphone">&nbsp;</span>';
7504
            }
7505
        }
7506
7507
        if ($typehour == 'select' || $typehour == 'textselect') {
7508
            $retstring .= '<select class="flat" id="select_' . $prefix . 'min" name="' . $prefix . 'min"' . ($disabled ? ' disabled' : '') . '>';
7509
            for ($min = 0; $min <= 55; $min += 5) {
7510
                $retstring .= '<option value="' . $min . '"';
7511
                if (is_numeric($minSelected) && $minSelected == $min) {
7512
                    $retstring .= ' selected';
7513
                }
7514
                $retstring .= '>' . $min . '</option>';
7515
            }
7516
            $retstring .= "</select>";
7517
        } elseif ($typehour == 'text') {
7518
            $retstring .= '<input placeholder="' . $langs->trans('MinuteShort') . '" type="number" min="0" name="' . $prefix . 'min"' . ($disabled ? ' disabled' : '') . ' class="flat maxwidth50 inputminute right" value="' . (($minSelected != '') ? ((int) $minSelected) : '') . '">';
7519
        }
7520
7521
        if ($typehour != 'text') {
7522
            $retstring .= ' ' . $langs->trans('MinuteShort');
7523
        }
7524
7525
        $retstring .= "</span>";
7526
7527
        if (!empty($nooutput)) {
7528
            return $retstring;
7529
        }
7530
7531
        print $retstring;
7532
7533
        return '';
7534
    }
7535
7536
    /**
7537
     *  Return list of tickets in Ajax if Ajax activated or go to selectTicketsList
7538
     *
7539
     * @param   string      $selected       Preselected tickets
7540
     * @param   string      $htmlname       Name of HTML select field (must be unique in page).
7541
     * @param   string      $filtertype     To add a filter
7542
     * @param   int         $limit          Limit on number of returned lines
7543
     * @param   int         $status         Ticket status
7544
     * @param   string      $selected_input_value Value of preselected input text (for use with ajax)
7545
     * @param   int<0,3>    $hidelabel      Hide label (0=no, 1=yes, 2=show search icon (before) and placeholder, 3 search icon after)
7546
     * @param   array<string,string|string[]>   $ajaxoptions    Options for ajax_autocompleter
7547
     * @param   int         $socid Thirdparty Id (to get also price dedicated to this customer)
7548
     * @param   string|int<0,1> $showempty  '' to not show empty line. Translation key to show an empty line. '1' show empty line with no text.
7549
     * @param   int<0,1>    $forcecombo Force to use combo box
7550
     * @param   string      $morecss        Add more css on select
7551
     * @param   array<string,string>    $selected_combinations  Selected combinations. Format: array([attrid] => attrval, [...])
7552
     * @param   int<0,1>    $nooutput       No print, return the output into a string
7553
     * @return  string
7554
     */
7555
    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)
7556
    {
7557
        global $langs, $conf;
7558
7559
        $out = '';
7560
7561
        // check parameters
7562
        if (is_null($ajaxoptions)) {
7563
            $ajaxoptions = array();
7564
        }
7565
7566
        if (!empty($conf->use_javascript_ajax) && getDolGlobalString('TICKET_USE_SEARCH_TO_SELECT')) {
7567
            $placeholder = '';
7568
7569
            if ($selected && empty($selected_input_value)) {
7570
                require_once constant('DOL_DOCUMENT_ROOT') . '/ticket/class/ticket.class.php';
7571
                $tickettmpselect = new Ticket($this->db);
7572
                $tickettmpselect->fetch($selected);
7573
                $selected_input_value = $tickettmpselect->ref;
7574
                unset($tickettmpselect);
7575
            }
7576
7577
            $urloption = '';
7578
            $out .= ajax_autocompleter($selected, $htmlname, constant('BASE_URL') . '/ticket/ajax/tickets.php', $urloption, $conf->global->PRODUIT_USE_SEARCH_TO_SELECT, 1, $ajaxoptions);
7579
7580
            if (empty($hidelabel)) {
7581
                $out .= $langs->trans("RefOrLabel") . ' : ';
7582
            } elseif ($hidelabel > 1) {
7583
                $placeholder = ' placeholder="' . $langs->trans("RefOrLabel") . '"';
7584
                if ($hidelabel == 2) {
7585
                    $out .= img_picto($langs->trans("Search"), 'search');
7586
                }
7587
            }
7588
            $out .= '<input type="text" class="minwidth100" name="search_' . $htmlname . '" id="search_' . $htmlname . '" value="' . $selected_input_value . '"' . $placeholder . ' ' . (getDolGlobalString('PRODUCT_SEARCH_AUTOFOCUS') ? 'autofocus' : '') . ' />';
7589
            if ($hidelabel == 3) {
7590
                $out .= img_picto($langs->trans("Search"), 'search');
7591
            }
7592
        } else {
7593
            $out .= $this->selectTicketsList($selected, $htmlname, $filtertype, $limit, '', $status, 0, $showempty, $forcecombo, $morecss);
7594
        }
7595
7596
        if (empty($nooutput)) {
7597
            print $out;
7598
        } else {
7599
            return $out;
7600
        }
7601
        return '';
7602
    }
7603
7604
7605
    /**
7606
     * Return list of tickets.
7607
     *  Called by selectTickets.
7608
     *
7609
     * @param   string      $selected       Preselected ticket
7610
     * @param   string      $htmlname       Name of select html
7611
     * @param   string      $filtertype     Filter on ticket type
7612
     * @param   int         $limit Limit on number of returned lines
7613
     * @param   string      $filterkey      Filter on ticket ref or subject
7614
     * @param   int         $status         Ticket status
7615
     * @param   int         $outputmode     0=HTML select string, 1=Array
7616
     * @param   string|int<0,1> $showempty  '' to not show empty line. Translation key to show an empty line. '1' show empty line with no text.
7617
     * @param int $forcecombo Force to use combo box
7618
     * @param   string $morecss Add more css on select
7619
     * @return  array|string                Array of keys for json or HTML component
7620
     */
7621
    public function selectTicketsList($selected = '', $htmlname = 'ticketid', $filtertype = '', $limit = 20, $filterkey = '', $status = 1, $outputmode = 0, $showempty = '1', $forcecombo = 0, $morecss = '')
7622
    {
7623
        global $langs, $conf;
7624
7625
        $out = '';
7626
        $outarray = array();
7627
7628
        $selectFields = " p.rowid, p.ref, p.message";
7629
7630
        $sql = "SELECT ";
7631
        $sql .= $selectFields;
7632
        $sql .= " FROM " . $this->db->prefix() . "ticket as p";
7633
        $sql .= ' WHERE p.entity IN (' . getEntity('ticket') . ')';
7634
7635
        // Add criteria on ref/label
7636
        if ($filterkey != '') {
7637
            $sql .= ' AND (';
7638
            $prefix = !getDolGlobalString('TICKET_DONOTSEARCH_ANYWHERE') ? '%' : ''; // Can use index if PRODUCT_DONOTSEARCH_ANYWHERE is on
7639
            // For natural search
7640
            $search_crit = explode(' ', $filterkey);
7641
            $i = 0;
7642
            if (count($search_crit) > 1) {
7643
                $sql .= "(";
7644
            }
7645
            foreach ($search_crit as $crit) {
7646
                if ($i > 0) {
7647
                    $sql .= " AND ";
7648
                }
7649
                $sql .= "(p.ref LIKE '" . $this->db->escape($prefix . $crit) . "%' OR p.subject LIKE '" . $this->db->escape($prefix . $crit) . "%'";
7650
                $sql .= ")";
7651
                $i++;
7652
            }
7653
            if (count($search_crit) > 1) {
7654
                $sql .= ")";
7655
            }
7656
            $sql .= ')';
7657
        }
7658
7659
        $sql .= $this->db->plimit($limit, 0);
7660
7661
        // Build output string
7662
        dol_syslog(get_class($this) . "::selectTicketsList search tickets", LOG_DEBUG);
7663
        $result = $this->db->query($sql);
7664
        if ($result) {
7665
            require_once constant('DOL_DOCUMENT_ROOT') . '/ticket/class/ticket.class.php';
7666
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/ticket.lib.php';
7667
7668
            $num = $this->db->num_rows($result);
7669
7670
            $events = array();
7671
7672
            if (!$forcecombo) {
7673
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
7674
                $out .= ajax_combobox($htmlname, $events, $conf->global->TICKET_USE_SEARCH_TO_SELECT);
7675
            }
7676
7677
            $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" id="' . $htmlname . '">';
7678
7679
            $textifempty = '';
7680
            // Do not use textifempty = ' ' or '&nbsp;' here, or search on key will search on ' key'.
7681
            //if (!empty($conf->use_javascript_ajax) || $forcecombo) $textifempty='';
7682
            if (getDolGlobalString('TICKET_USE_SEARCH_TO_SELECT')) {
7683
                if ($showempty && !is_numeric($showempty)) {
7684
                    $textifempty = $langs->trans($showempty);
7685
                } else {
7686
                    $textifempty .= $langs->trans("All");
7687
                }
7688
            } else {
7689
                if ($showempty && !is_numeric($showempty)) {
7690
                    $textifempty = $langs->trans($showempty);
7691
                }
7692
            }
7693
            if ($showempty) {
7694
                $out .= '<option value="0" selected>' . $textifempty . '</option>';
7695
            }
7696
7697
            $i = 0;
7698
            while ($num && $i < $num) {
7699
                $opt = '';
7700
                $optJson = array();
7701
                $objp = $this->db->fetch_object($result);
7702
7703
                $this->constructTicketListOption($objp, $opt, $optJson, $selected, $filterkey);
7704
                // Add new entry
7705
                // "key" value of json key array is used by jQuery automatically as selected value
7706
                // "label" value of json key array is used by jQuery automatically as text for combo box
7707
                $out .= $opt;
7708
                array_push($outarray, $optJson);
7709
7710
                $i++;
7711
            }
7712
7713
            $out .= '</select>';
7714
7715
            $this->db->free($result);
7716
7717
            if (empty($outputmode)) {
7718
                return $out;
7719
            }
7720
            return $outarray;
7721
        } else {
7722
            dol_print_error($this->db);
7723
        }
7724
7725
        return array();
7726
    }
7727
7728
    /**
7729
     * constructTicketListOption.
7730
     * This define value for &$opt and &$optJson.
7731
     *
7732
     * @param object    $objp       Result set of fetch
7733
     * @param string    $opt        Option (var used for returned value in string option format)
7734
     * @param mixed[]   $optJson    Option (var used for returned value in json format)
7735
     * @param string    $selected   Preselected value
7736
     * @param string    $filterkey  Filter key to highlight
7737
     * @return    void
7738
     */
7739
    protected function constructTicketListOption(&$objp, &$opt, &$optJson, $selected, $filterkey = '')
7740
    {
7741
        $outkey = '';
7742
        $outref = '';
7743
        $outtype = '';
7744
7745
        $outkey = $objp->rowid;
7746
        $outref = $objp->ref;
7747
        $outtype = $objp->fk_product_type;
7748
7749
        $opt = '<option value="' . $objp->rowid . '"';
7750
        $opt .= ($objp->rowid == $selected) ? ' selected' : '';
7751
        $opt .= '>';
7752
        $opt .= $objp->ref;
7753
        $objRef = $objp->ref;
7754
        if (!empty($filterkey) && $filterkey != '') {
7755
            $objRef = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', $objRef, 1);
7756
        }
7757
7758
        $opt .= "</option>\n";
7759
        $optJson = array('key' => $outkey, 'value' => $outref, 'type' => $outtype);
7760
    }
7761
7762
    /**
7763
     *  Return list of projects in Ajax if Ajax activated or go to selectTicketsList
7764
     *
7765
     * @param   string  $selected               Preselected tickets
7766
     * @param   string  $htmlname               Name of HTML select field (must be unique in page).
7767
     * @param   string  $filtertype             To add a filter
7768
     * @param   int     $limit                  Limit on number of returned lines
7769
     * @param   int     $status                 Ticket status
7770
     * @param   string  $selected_input_value   Value of preselected input text (for use with ajax)
7771
     * @param   int<0,3>    $hidelabel              Hide label (0=no, 1=yes, 2=show search icon (before) and placeholder, 3 search icon after)
7772
     * @param   array<string,string|string[]>   $ajaxoptions            Options for ajax_autocompleter
7773
     * @param   int     $socid                  Thirdparty Id (to get also price dedicated to this customer)
7774
     * @param   string|int<0,1> $showempty      '' to not show empty line. Translation key to show an empty line. '1' show empty line with no text.
7775
     * @param   int<0,1>    $forcecombo             Force to use combo box
7776
     * @param   string  $morecss                Add more css on select
7777
     * @param   array<string,string> $selected_combinations     Selected combinations. Format: array([attrid] => attrval, [...])
7778
     * @param   int<0,1>    $nooutput               No print, return the output into a string
7779
     * @return  string
7780
     */
7781
    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)
7782
    {
7783
        global $langs, $conf;
7784
7785
        $out = '';
7786
7787
        // check parameters
7788
        if (is_null($ajaxoptions)) {
7789
            $ajaxoptions = array();
7790
        }
7791
7792
        if (!empty($conf->use_javascript_ajax) && getDolGlobalString('TICKET_USE_SEARCH_TO_SELECT')) {
7793
            $placeholder = '';
7794
7795
            if ($selected && empty($selected_input_value)) {
7796
                require_once constant('DOL_DOCUMENT_ROOT') . '/projet/class/project.class.php';
7797
                $projecttmpselect = new Project($this->db);
7798
                $projecttmpselect->fetch($selected);
7799
                $selected_input_value = $projecttmpselect->ref;
7800
                unset($projecttmpselect);
7801
            }
7802
7803
            $urloption = '';
7804
            $out .= ajax_autocompleter($selected, $htmlname, constant('BASE_URL') . '/projet/ajax/projects.php', $urloption, $conf->global->PRODUIT_USE_SEARCH_TO_SELECT, 1, $ajaxoptions);
7805
7806
            if (empty($hidelabel)) {
7807
                $out .= $langs->trans("RefOrLabel") . ' : ';
7808
            } elseif ($hidelabel > 1) {
7809
                $placeholder = ' placeholder="' . $langs->trans("RefOrLabel") . '"';
7810
                if ($hidelabel == 2) {
7811
                    $out .= img_picto($langs->trans("Search"), 'search');
7812
                }
7813
            }
7814
            $out .= '<input type="text" class="minwidth100" name="search_' . $htmlname . '" id="search_' . $htmlname . '" value="' . $selected_input_value . '"' . $placeholder . ' ' . (getDolGlobalString('PRODUCT_SEARCH_AUTOFOCUS') ? 'autofocus' : '') . ' />';
7815
            if ($hidelabel == 3) {
7816
                $out .= img_picto($langs->trans("Search"), 'search');
7817
            }
7818
        } else {
7819
            $out .= $this->selectProjectsList($selected, $htmlname, $filtertype, $limit, '', $status, 0, $showempty, $forcecombo, $morecss);
7820
        }
7821
7822
        if (empty($nooutput)) {
7823
            print $out;
7824
        } else {
7825
            return $out;
7826
        }
7827
        return '';
7828
    }
7829
7830
    /**
7831
     *    Return list of projects.
7832
     *  Called by selectProjects.
7833
     *
7834
     * @param string $selected Preselected project
7835
     * @param string $htmlname Name of select html
7836
     * @param string $filtertype Filter on project type
7837
     * @param int $limit Limit on number of returned lines
7838
     * @param string $filterkey Filter on project ref or subject
7839
     * @param int $status Ticket status
7840
     * @param int $outputmode 0=HTML select string, 1=Array
7841
     * @param string|int<0,1> $showempty '' to not show empty line. Translation key to show an empty line. '1' show empty line with no text.
7842
     * @param int $forcecombo Force to use combo box
7843
     * @param string $morecss Add more css on select
7844
     * @return     array|string                Array of keys for json or HTML component
7845
     */
7846
    public function selectProjectsList($selected = '', $htmlname = 'projectid', $filtertype = '', $limit = 20, $filterkey = '', $status = 1, $outputmode = 0, $showempty = '1', $forcecombo = 0, $morecss = '')
7847
    {
7848
        global $langs, $conf;
7849
7850
        $out = '';
7851
        $outarray = array();
7852
7853
        $selectFields = " p.rowid, p.ref";
7854
7855
        $sql = "SELECT ";
7856
        $sql .= $selectFields;
7857
        $sql .= " FROM " . $this->db->prefix() . "projet as p";
7858
        $sql .= ' WHERE p.entity IN (' . getEntity('project') . ')';
7859
7860
        // Add criteria on ref/label
7861
        if ($filterkey != '') {
7862
            $sql .= ' AND (';
7863
            $prefix = !getDolGlobalString('TICKET_DONOTSEARCH_ANYWHERE') ? '%' : ''; // Can use index if PRODUCT_DONOTSEARCH_ANYWHERE is on
7864
            // For natural search
7865
            $search_crit = explode(' ', $filterkey);
7866
            $i = 0;
7867
            if (count($search_crit) > 1) {
7868
                $sql .= "(";
7869
            }
7870
            foreach ($search_crit as $crit) {
7871
                if ($i > 0) {
7872
                    $sql .= " AND ";
7873
                }
7874
                $sql .= "p.ref LIKE '" . $this->db->escape($prefix . $crit) . "%'";
7875
                $sql .= "";
7876
                $i++;
7877
            }
7878
            if (count($search_crit) > 1) {
7879
                $sql .= ")";
7880
            }
7881
            $sql .= ')';
7882
        }
7883
7884
        $sql .= $this->db->plimit($limit, 0);
7885
7886
        // Build output string
7887
        dol_syslog(get_class($this) . "::selectProjectsList search projects", LOG_DEBUG);
7888
        $result = $this->db->query($sql);
7889
        if ($result) {
7890
            require_once constant('DOL_DOCUMENT_ROOT') . '/projet/class/project.class.php';
7891
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/project.lib.php';
7892
7893
            $num = $this->db->num_rows($result);
7894
7895
            $events = array();
7896
7897
            if (!$forcecombo) {
7898
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
7899
                $out .= ajax_combobox($htmlname, $events, $conf->global->PROJECT_USE_SEARCH_TO_SELECT);
7900
            }
7901
7902
            $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" id="' . $htmlname . '">';
7903
7904
            $textifempty = '';
7905
            // Do not use textifempty = ' ' or '&nbsp;' here, or search on key will search on ' key'.
7906
            //if (!empty($conf->use_javascript_ajax) || $forcecombo) $textifempty='';
7907
            if (getDolGlobalString('PROJECT_USE_SEARCH_TO_SELECT')) {
7908
                if ($showempty && !is_numeric($showempty)) {
7909
                    $textifempty = $langs->trans($showempty);
7910
                } else {
7911
                    $textifempty .= $langs->trans("All");
7912
                }
7913
            } else {
7914
                if ($showempty && !is_numeric($showempty)) {
7915
                    $textifempty = $langs->trans($showempty);
7916
                }
7917
            }
7918
            if ($showempty) {
7919
                $out .= '<option value="0" selected>' . $textifempty . '</option>';
7920
            }
7921
7922
            $i = 0;
7923
            while ($num && $i < $num) {
7924
                $opt = '';
7925
                $optJson = array();
7926
                $objp = $this->db->fetch_object($result);
7927
7928
                $this->constructProjectListOption($objp, $opt, $optJson, $selected, $filterkey);
7929
                // Add new entry
7930
                // "key" value of json key array is used by jQuery automatically as selected value
7931
                // "label" value of json key array is used by jQuery automatically as text for combo box
7932
                $out .= $opt;
7933
                array_push($outarray, $optJson);
7934
7935
                $i++;
7936
            }
7937
7938
            $out .= '</select>';
7939
7940
            $this->db->free($result);
7941
7942
            if (empty($outputmode)) {
7943
                return $out;
7944
            }
7945
            return $outarray;
7946
        } else {
7947
            dol_print_error($this->db);
7948
        }
7949
7950
        return array();
7951
    }
7952
7953
    /**
7954
     * constructProjectListOption.
7955
     * This define value for &$opt and &$optJson.
7956
     *
7957
     * @param stdClass  $objp       Result set of fetch
7958
     * @param string    $opt        Option (var used for returned value in string option format)
7959
     * @param array{key:string,value:string,type:string}    $optJson    Option (var used for returned value in json format)
7960
     * @param string    $selected   Preselected value
7961
     * @param string    $filterkey  Filter key to highlight
7962
     * @return    void
7963
     */
7964
    protected function constructProjectListOption(&$objp, &$opt, &$optJson, $selected, $filterkey = '')
7965
    {
7966
        $outkey = '';
7967
        $outref = '';
7968
        $outtype = '';
7969
7970
        $label = $objp->label;
7971
7972
        $outkey = $objp->rowid;
7973
        $outref = $objp->ref;
7974
        $outlabel = $objp->label;
7975
        $outtype = $objp->fk_product_type;
7976
7977
        $opt = '<option value="' . $objp->rowid . '"';
7978
        $opt .= ($objp->rowid == $selected) ? ' selected' : '';
7979
        $opt .= '>';
7980
        $opt .= $objp->ref;
7981
        $objRef = $objp->ref;
7982
        if (!empty($filterkey) && $filterkey != '') {
7983
            $objRef = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', $objRef, 1);
7984
        }
7985
7986
        $opt .= "</option>\n";
7987
        $optJson = array('key' => $outkey, 'value' => $outref, 'type' => $outtype);
7988
    }
7989
7990
7991
    /**
7992
     *  Return list of members in Ajax if Ajax activated or go to selectTicketsList
7993
     *
7994
     * @param string $selected Preselected tickets
7995
     * @param string $htmlname Name of HTML select field (must be unique in page).
7996
     * @param string $filtertype To add a filter
7997
     * @param int $limit Limit on number of returned lines
7998
     * @param int $status Ticket status
7999
     * @param string $selected_input_value Value of preselected input text (for use with ajax)
8000
     * @param int<0,3> $hidelabel Hide label (0=no, 1=yes, 2=show search icon before and placeholder, 3 search icon after)
8001
     * @param array<string,string|string[]> $ajaxoptions Options for ajax_autocompleter
8002
     * @param int $socid Thirdparty Id (to get also price dedicated to this customer)
8003
     * @param string|int<0,1> $showempty '' to not show empty line. Translation key to show an empty line. '1' show empty line with no text.
8004
     * @param int $forcecombo Force to use combo box
8005
     * @param string $morecss Add more css on select
8006
     * @param array<string,string> $selected_combinations Selected combinations. Format: array([attrid] => attrval, [...])
8007
     * @param int<0,1>  $nooutput No print, return the output into a string
8008
     * @return        string
8009
     */
8010
    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)
8011
    {
8012
        global $langs, $conf;
8013
8014
        $out = '';
8015
8016
        // check parameters
8017
        if (is_null($ajaxoptions)) {
8018
            $ajaxoptions = array();
8019
        }
8020
8021
        if (!empty($conf->use_javascript_ajax) && getDolGlobalString('TICKET_USE_SEARCH_TO_SELECT')) {
8022
            $placeholder = '';
8023
8024
            if ($selected && empty($selected_input_value)) {
8025
                use Dolibarr\Code\Adherents\Classes\Adherent;
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected T_USE on line 8025 at column 16
Loading history...
8026
                $adherenttmpselect = new Adherent($this->db);
8027
                $adherenttmpselect->fetch($selected);
8028
                $selected_input_value = $adherenttmpselect->ref;
8029
                unset($adherenttmpselect);
8030
            }
8031
8032
            $urloption = '';
8033
8034
            $out .= ajax_autocompleter($selected, $htmlname, constant('BASE_URL') . '/adherents/ajax/adherents.php', $urloption, $conf->global->PRODUIT_USE_SEARCH_TO_SELECT, 1, $ajaxoptions);
8035
8036
            if (empty($hidelabel)) {
8037
                $out .= $langs->trans("RefOrLabel") . ' : ';
8038
            } elseif ($hidelabel > 1) {
8039
                $placeholder = ' placeholder="' . $langs->trans("RefOrLabel") . '"';
8040
                if ($hidelabel == 2) {
8041
                    $out .= img_picto($langs->trans("Search"), 'search');
8042
                }
8043
            }
8044
            $out .= '<input type="text" class="minwidth100" name="search_' . $htmlname . '" id="search_' . $htmlname . '" value="' . $selected_input_value . '"' . $placeholder . ' ' . (getDolGlobalString('PRODUCT_SEARCH_AUTOFOCUS') ? 'autofocus' : '') . ' />';
8045
            if ($hidelabel == 3) {
8046
                $out .= img_picto($langs->trans("Search"), 'search');
8047
            }
8048
        } else {
8049
            $filterkey = '';
8050
8051
            $out .= $this->selectMembersList($selected, $htmlname, $filtertype, $limit, $filterkey, $status, 0, $showempty, $forcecombo, $morecss);
8052
        }
8053
8054
        if (empty($nooutput)) {
8055
            print $out;
8056
        } else {
8057
            return $out;
8058
        }
8059
        return '';
8060
    }
8061
8062
    /**
8063
     *    Return list of adherents.
8064
     *  Called by selectMembers.
8065
     *
8066
     * @param string $selected Preselected adherent
8067
     * @param string $htmlname Name of select html
8068
     * @param string $filtertype Filter on adherent type
8069
     * @param int $limit Limit on number of returned lines
8070
     * @param string $filterkey Filter on member status
8071
     * @param int $status Member status
8072
     * @param int $outputmode 0=HTML select string, 1=Array
8073
     * @param string|int<0,1> $showempty '' to not show empty line. Translation key to show an empty line. '1' show empty line with no text.
8074
     * @param int $forcecombo Force to use combo box
8075
     * @param string $morecss Add more css on select
8076
     * @return     array|string                Array of keys for json or HTML string component
8077
     */
8078
    public function selectMembersList($selected = '', $htmlname = 'adherentid', $filtertype = '', $limit = 20, $filterkey = '', $status = 1, $outputmode = 0, $showempty = '1', $forcecombo = 0, $morecss = '')
8079
    {
8080
        global $langs, $conf;
8081
8082
        $out = '';
8083
        $outarray = array();
8084
8085
        $selectFields = " p.rowid, p.ref, p.firstname, p.lastname, p.fk_adherent_type";
8086
8087
        $sql = "SELECT ";
8088
        $sql .= $selectFields;
8089
        $sql .= " FROM " . $this->db->prefix() . "adherent as p";
8090
        $sql .= ' WHERE p.entity IN (' . getEntity('adherent') . ')';
8091
8092
        // Add criteria on ref/label
8093
        if ($filterkey != '') {
8094
            $sql .= ' AND (';
8095
            $prefix = !getDolGlobalString('MEMBER_DONOTSEARCH_ANYWHERE') ? '%' : ''; // Can use index if PRODUCT_DONOTSEARCH_ANYWHERE is on
8096
            // For natural search
8097
            $search_crit = explode(' ', $filterkey);
8098
            $i = 0;
8099
            if (count($search_crit) > 1) {
8100
                $sql .= "(";
8101
            }
8102
            foreach ($search_crit as $crit) {
8103
                if ($i > 0) {
8104
                    $sql .= " AND ";
8105
                }
8106
                $sql .= "(p.firstname LIKE '" . $this->db->escape($prefix . $crit) . "%'";
8107
                $sql .= " OR p.lastname LIKE '" . $this->db->escape($prefix . $crit) . "%')";
8108
                $i++;
8109
            }
8110
            if (count($search_crit) > 1) {
8111
                $sql .= ")";
8112
            }
8113
            $sql .= ')';
8114
        }
8115
        if ($status != -1) {
8116
            $sql .= ' AND statut = ' . ((int) $status);
8117
        }
8118
        $sql .= $this->db->plimit($limit, 0);
8119
8120
        // Build output string
8121
        dol_syslog(get_class($this) . "::selectMembersList search adherents", LOG_DEBUG);
8122
        $result = $this->db->query($sql);
8123
        if ($result) {
8124
            use Dolibarr\Code\Adherents\Classes\Adherent;
8125
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/member.lib.php';
8126
8127
            $num = $this->db->num_rows($result);
8128
8129
            $events = array();
8130
8131
            if (!$forcecombo) {
8132
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
8133
                $out .= ajax_combobox($htmlname, $events, getDolGlobalString('PROJECT_USE_SEARCH_TO_SELECT') ? $conf->global->PROJECT_USE_SEARCH_TO_SELECT : '');
8134
            }
8135
8136
            $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" id="' . $htmlname . '">';
8137
8138
            $textifempty = '';
8139
            // Do not use textifempty = ' ' or '&nbsp;' here, or search on key will search on ' key'.
8140
            //if (!empty($conf->use_javascript_ajax) || $forcecombo) $textifempty='';
8141
            if (getDolGlobalString('PROJECT_USE_SEARCH_TO_SELECT')) {
8142
                if ($showempty && !is_numeric($showempty)) {
8143
                    $textifempty = $langs->trans($showempty);
8144
                } else {
8145
                    $textifempty .= $langs->trans("All");
8146
                }
8147
            } else {
8148
                if ($showempty && !is_numeric($showempty)) {
8149
                    $textifempty = $langs->trans($showempty);
8150
                }
8151
            }
8152
            if ($showempty) {
8153
                $out .= '<option value="-1" selected>' . $textifempty . '</option>';
8154
            }
8155
8156
            $i = 0;
8157
            while ($num && $i < $num) {
8158
                $opt = '';
8159
                $optJson = array();
8160
                $objp = $this->db->fetch_object($result);
8161
8162
                $this->constructMemberListOption($objp, $opt, $optJson, $selected, $filterkey);
8163
8164
                // Add new entry
8165
                // "key" value of json key array is used by jQuery automatically as selected value
8166
                // "label" value of json key array is used by jQuery automatically as text for combo box
8167
                $out .= $opt;
8168
                array_push($outarray, $optJson);
8169
8170
                $i++;
8171
            }
8172
8173
            $out .= '</select>';
8174
8175
            $this->db->free($result);
8176
8177
            if (empty($outputmode)) {
8178
                return $out;
8179
            }
8180
            return $outarray;
8181
        } else {
8182
            dol_print_error($this->db);
8183
        }
8184
8185
        return array();
8186
    }
8187
8188
    /**
8189
     * constructMemberListOption.
8190
     * This define value for &$opt and &$optJson.
8191
     *
8192
     * @param object    $objp           Result set of fetch
8193
     * @param string    $opt            Option (var used for returned value in string option format)
8194
     * @param mixed[]   $optJson        Option (var used for returned value in json format)
8195
     * @param string    $selected       Preselected value
8196
     * @param string    $filterkey      Filter key to highlight
8197
     * @return    void
8198
     */
8199
    protected function constructMemberListOption(&$objp, &$opt, &$optJson, $selected, $filterkey = '')
8200
    {
8201
        $outkey = '';
8202
        $outlabel = '';
8203
        $outtype = '';
8204
8205
        $outkey = $objp->rowid;
8206
        $outlabel = dolGetFirstLastname($objp->firstname, $objp->lastname);
8207
        $outtype = $objp->fk_adherent_type;
8208
8209
        $opt = '<option value="' . $objp->rowid . '"';
8210
        $opt .= ($objp->rowid == $selected) ? ' selected' : '';
8211
        $opt .= '>';
8212
        if (!empty($filterkey) && $filterkey != '') {
8213
            $outlabel = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', $outlabel, 1);
8214
        }
8215
        $opt .= $outlabel;
8216
        $opt .= "</option>\n";
8217
8218
        $optJson = array('key' => $outkey, 'value' => $outlabel, 'type' => $outtype);
8219
    }
8220
8221
    /**
8222
     * Generic method to select a component from a combo list.
8223
     * Can use autocomplete with ajax after x key pressed or a full combo, depending on setup.
8224
     * This is the generic method that will replace all specific existing methods.
8225
     *
8226
     * @param   string      $objectdesc             'ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'. For hard coded custom needs. Try to prefer method using $objectfield instead of $objectdesc.
8227
     * @param   string      $htmlname               Name of HTML select component
8228
     * @param   int         $preSelectedValue       Preselected value (ID of element)
8229
     * @param   string|int<0,1> $showempty          ''=empty values not allowed, 'string'=value show if we allow empty values (for example 'All', ...)
8230
     * @param   string      $searchkey              Search criteria
8231
     * @param   string      $placeholder            Place holder
8232
     * @param   string      $morecss                More CSS
8233
     * @param   string      $moreparams             More params provided to ajax call
8234
     * @param   int         $forcecombo             Force to load all values and output a standard combobox (with no beautification)
8235
     * @param   int<0,1>    $disabled               1=Html component is disabled
8236
     * @param   string      $selected_input_value   Value of preselected input text (for use with ajax)
8237
     * @param   string      $objectfield            Object:Field that contains the definition of parent (in table $fields or $extrafields). Example: 'Object:xxx' or 'Object@module:xxx' (old syntax 'Module_Object:xxx') or 'Object:options_xxx' or 'Object@module:options_xxx' (old syntax 'Module_Object:options_xxx')
8238
     * @return  string                              Return HTML string
8239
     * @see selectForFormsList(), select_thirdparty_list()
8240
     */
8241
    public function selectForForms($objectdesc, $htmlname, $preSelectedValue, $showempty = '', $searchkey = '', $placeholder = '', $morecss = '', $moreparams = '', $forcecombo = 0, $disabled = 0, $selected_input_value = '', $objectfield = '')
8242
    {
8243
        global $conf, $extrafields, $user;
8244
8245
        //var_dump($objectdesc); debug_print_backtrace();
8246
8247
        $objectdescorig = $objectdesc;
8248
        $objecttmp = null;
8249
        $InfoFieldList = array();
8250
        $classname = '';
8251
        $filter = '';  // Ensure filter has value (for static analysis)
8252
        $sortfield = '';  // Ensure filter has value (for static analysis)
8253
8254
        if ($objectfield) { // We must retrieve the objectdesc from the field or extrafield
8255
            // Example: $objectfield = 'product:options_package' or 'myobject@mymodule:options_myfield'
8256
            $tmparray = explode(':', $objectfield);
8257
8258
            // Get instance of object from $element
8259
            $objectforfieldstmp = fetchObjectByElement(0, strtolower($tmparray[0]));
8260
8261
            if (is_object($objectforfieldstmp)) {
8262
                $objectdesc = '';
8263
8264
                $reg = array();
8265
                if (preg_match('/^options_(.*)$/', $tmparray[1], $reg)) {
8266
                    // For a property in extrafields
8267
                    $key = $reg[1];
8268
                    // fetch optionals attributes and labels
8269
                    $extrafields->fetch_name_optionals_label($objectforfieldstmp->table_element);
8270
8271
                    if (!empty($extrafields->attributes[$objectforfieldstmp->table_element]['type'][$key]) && $extrafields->attributes[$objectforfieldstmp->table_element]['type'][$key] == 'link') {
8272
                        if (!empty($extrafields->attributes[$objectforfieldstmp->table_element]['param'][$key]['options'])) {
8273
                            $tmpextrafields = array_keys($extrafields->attributes[$objectforfieldstmp->table_element]['param'][$key]['options']);
8274
                            $objectdesc = $tmpextrafields[0];
8275
                        }
8276
                    }
8277
                } else {
8278
                    // For a property in ->fields
8279
                    if (array_key_exists($tmparray[1], $objectforfieldstmp->fields)) {
8280
                        $objectdesc = $objectforfieldstmp->fields[$tmparray[1]]['type'];
8281
                        $objectdesc = preg_replace('/^integer[^:]*:/', '', $objectdesc);
8282
                    }
8283
                }
8284
            }
8285
        }
8286
8287
        if ($objectdesc) {
8288
            // Example of value for $objectdesc:
8289
            // Bom:bom/class/bom.class.php:0:t.status=1
8290
            // Bom:bom/class/bom.class.php:0:t.status=1:ref
8291
            // Bom:bom/class/bom.class.php:0:(t.status:=:1) OR (t.field2:=:2):ref
8292
            $InfoFieldList = explode(":", $objectdesc, 4);
8293
            $vartmp = (empty($InfoFieldList[3]) ? '' : $InfoFieldList[3]);
8294
            $reg = array();
8295
            if (preg_match('/^.*:(\w*)$/', $vartmp, $reg)) {
8296
                $InfoFieldList[4] = $reg[1];    // take the sort field
8297
            }
8298
            $InfoFieldList[3] = preg_replace('/:\w*$/', '', $vartmp);    // take the filter field
8299
8300
            $classname = $InfoFieldList[0];
8301
            $classpath = empty($InfoFieldList[1]) ? '' : $InfoFieldList[1];
8302
            //$addcreatebuttonornot = empty($InfoFieldList[2]) ? 0 : $InfoFieldList[2];
8303
            $filter = empty($InfoFieldList[3]) ? '' : $InfoFieldList[3];
8304
            $sortfield = empty($InfoFieldList[4]) ? '' : $InfoFieldList[4];
8305
8306
            // Load object according to $id and $element
8307
            $objecttmp = fetchObjectByElement(0, strtolower($InfoFieldList[0]));
8308
8309
            // Fallback to another solution to get $objecttmp
8310
            if (empty($objecttmp) && !empty($classpath)) {
8311
                dol_include_once($classpath);
8312
8313
                if ($classname && class_exists($classname)) {
8314
                    $objecttmp = new $classname($this->db);
8315
                }
8316
            }
8317
        }
8318
8319
        // Make some replacement in $filter. May not be used if we used the ajax mode with $objectfield. In such a case
8320
        // we propagate the $objectfield and not the filter and replacement is done by the ajax/selectobject.php component.
8321
        $sharedentities = (is_object($objecttmp) && property_exists($objecttmp, 'element')) ? getEntity($objecttmp->element) : strtolower($classname);
8322
        $filter = str_replace(
8323
            array('__ENTITY__', '__SHARED_ENTITIES__', '__USER_ID__'),
8324
            array($conf->entity, $sharedentities, $user->id),
8325
            $filter
8326
        );
8327
8328
        if (!is_object($objecttmp)) {
8329
            dol_syslog('selectForForms: Error bad setup of field objectdescorig=' . $objectdescorig . ', objectfield=' . $objectfield . ', objectdesc=' . $objectdesc, LOG_WARNING);
8330
            return 'selectForForms: Error bad setup of field objectdescorig=' . $objectdescorig . ', objectfield=' . $objectfield . ', objectdesc=' . $objectdesc;
8331
        }
8332
        '@phan-var-force CommonObject $objecttmp';
8333
8334
        //var_dump($filter);
8335
        $prefixforautocompletemode = $objecttmp->element;
8336
        if ($prefixforautocompletemode == 'societe') {
8337
            $prefixforautocompletemode = 'company';
8338
        }
8339
        if ($prefixforautocompletemode == 'product') {
8340
            $prefixforautocompletemode = 'produit';
8341
        }
8342
        $confkeyforautocompletemode = strtoupper($prefixforautocompletemode) . '_USE_SEARCH_TO_SELECT'; // For example COMPANY_USE_SEARCH_TO_SELECT
8343
8344
        dol_syslog(get_class($this) . "::selectForForms filter=" . $filter, LOG_DEBUG);
8345
8346
        // Generate the combo HTML component
8347
        $out = '';
8348
        if (!empty($conf->use_javascript_ajax) && getDolGlobalString($confkeyforautocompletemode) && !$forcecombo) {
8349
            // No immediate load of all database
8350
            $placeholder = '';
8351
8352
            if ($preSelectedValue && empty($selected_input_value)) {
8353
                $objecttmp->fetch($preSelectedValue);
8354
                $selected_input_value = ($prefixforautocompletemode == 'company' ? $objecttmp->name : $objecttmp->ref);
8355
8356
                $oldValueForShowOnCombobox = 0;
8357
                foreach ($objecttmp->fields as $fieldK => $fielV) {
8358
                    if (empty($fielV['showoncombobox']) || empty($objecttmp->$fieldK)) {
8359
                        continue;
8360
                    }
8361
8362
                    if (!$oldValueForShowOnCombobox) {
8363
                        $selected_input_value = '';
8364
                    }
8365
8366
                    $selected_input_value .= $oldValueForShowOnCombobox ? ' - ' : '';
8367
                    $selected_input_value .= $objecttmp->$fieldK;
8368
                    $oldValueForShowOnCombobox = empty($fielV['showoncombobox']) ? 0 : $fielV['showoncombobox'];
8369
                }
8370
            }
8371
8372
            // Set url and param to call to get json of the search results
8373
            $urlforajaxcall = constant('BASE_URL') . '/core/ajax/selectobject.php';
8374
            $urloption = 'htmlname=' . urlencode($htmlname) . '&outjson=1&objectdesc=' . urlencode($objectdescorig) . '&objectfield=' . urlencode($objectfield) . ($sortfield ? '&sortfield=' . urlencode($sortfield) : '');
8375
8376
            // Activate the auto complete using ajax call.
8377
            $out .= ajax_autocompleter($preSelectedValue, $htmlname, $urlforajaxcall, $urloption, getDolGlobalString($confkeyforautocompletemode), 0);
8378
            $out .= '<!-- force css to be higher than dialog popup --><style type="text/css">.ui-autocomplete { z-index: 1010; }</style>';
8379
            $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) . '"' : '') . ' />';
8380
        } else {
8381
            // Immediate load of table record.
8382
            $out .= $this->selectForFormsList($objecttmp, $htmlname, $preSelectedValue, $showempty, $searchkey, $placeholder, $morecss, $moreparams, $forcecombo, 0, $disabled, $sortfield, $filter);
8383
        }
8384
8385
        return $out;
8386
    }
8387
8388
8389
    /**
8390
     * Output html form to select an object.
8391
     * Note, this function is called by selectForForms or by ajax selectobject.php
8392
     *
8393
     * @param Object        $objecttmp          Object to know the table to scan for combo.
8394
     * @param string        $htmlname           Name of HTML select component
8395
     * @param int           $preselectedvalue   Preselected value (ID of element)
8396
     * @param string|int<0,1>   $showempty      ''=empty values not allowed, 'string'=value show if we allow empty values (for example 'All', ...)
8397
     * @param string        $searchkey          Search value
8398
     * @param string        $placeholder        Place holder
8399
     * @param string        $morecss            More CSS
8400
     * @param string        $moreparams         More params provided to ajax call
8401
     * @param int           $forcecombo         Force to load all values and output a standard combobox (with no beautification)
8402
     * @param int           $outputmode         0=HTML select string, 1=Array
8403
     * @param int           $disabled           1=Html component is disabled
8404
     * @param string        $sortfield          Sort field
8405
     * @param string        $filter             Add more filter (Universal Search Filter)
8406
     * @return string|array                     Return HTML string
8407
     * @see selectForForms()
8408
     */
8409
    public function selectForFormsList($objecttmp, $htmlname, $preselectedvalue, $showempty = '', $searchkey = '', $placeholder = '', $morecss = '', $moreparams = '', $forcecombo = 0, $outputmode = 0, $disabled = 0, $sortfield = '', $filter = '')
8410
    {
8411
        global $langs, $user, $hookmanager;
8412
8413
        //print "$htmlname, $preselectedvalue, $showempty, $searchkey, $placeholder, $morecss, $moreparams, $forcecombo, $outputmode, $disabled";
8414
8415
        $prefixforautocompletemode = $objecttmp->element;
8416
        if ($prefixforautocompletemode == 'societe') {
8417
            $prefixforautocompletemode = 'company';
8418
        }
8419
        $confkeyforautocompletemode = strtoupper($prefixforautocompletemode) . '_USE_SEARCH_TO_SELECT'; // For example COMPANY_USE_SEARCH_TO_SELECT
8420
8421
        if (!empty($objecttmp->fields)) {    // For object that declare it, it is better to use declared fields (like societe, contact, ...)
8422
            $tmpfieldstoshow = '';
8423
            foreach ($objecttmp->fields as $key => $val) {
8424
                if (! (int) dol_eval($val['enabled'], 1, 1, '1')) {
8425
                    continue;
8426
                }
8427
                if (!empty($val['showoncombobox'])) {
8428
                    $tmpfieldstoshow .= ($tmpfieldstoshow ? ',' : '') . 't.' . $key;
8429
                }
8430
            }
8431
            if ($tmpfieldstoshow) {
8432
                $fieldstoshow = $tmpfieldstoshow;
8433
            }
8434
        } else {
8435
            // For backward compatibility
8436
            $objecttmp->fields['ref'] = array('type' => 'varchar(30)', 'label' => 'Ref', 'showoncombobox' => 1);
8437
        }
8438
8439
        if (empty($fieldstoshow)) {
8440
            if (isset($objecttmp->fields['ref'])) {
8441
                $fieldstoshow = 't.ref';
8442
            } else {
8443
                $langs->load("errors");
8444
                $this->error = $langs->trans("ErrorNoFieldWithAttributeShowoncombobox");
8445
                return $langs->trans('ErrorNoFieldWithAttributeShowoncombobox');
8446
            }
8447
        }
8448
8449
        $out = '';
8450
        $outarray = array();
8451
        $tmparray = array();
8452
8453
        $num = 0;
8454
8455
        // Search data
8456
        $sql = "SELECT t.rowid, " . $fieldstoshow . " FROM " . $this->db->prefix() . $objecttmp->table_element . " as t";
8457
        if (!empty($objecttmp->isextrafieldmanaged)) {
8458
            $sql .= " LEFT JOIN " . $this->db->prefix() . $objecttmp->table_element . "_extrafields as e ON t.rowid=e.fk_object";
8459
        }
8460
        if (isset($objecttmp->ismultientitymanaged)) {
8461
            if (!is_numeric($objecttmp->ismultientitymanaged)) {
8462
                $tmparray = explode('@', $objecttmp->ismultientitymanaged);
8463
                $sql .= " INNER JOIN " . $this->db->prefix() . $tmparray[1] . " as parenttable ON parenttable.rowid = t." . $tmparray[0];
8464
            }
8465
            if ($objecttmp->ismultientitymanaged === 'fk_soc@societe') {
8466
                if (!$user->hasRight('societe', 'client', 'voir')) {
8467
                    $sql .= ", " . $this->db->prefix() . "societe_commerciaux as sc";
8468
                }
8469
            }
8470
        }
8471
8472
        // Add where from hooks
8473
        $parameters = array(
8474
            'object' => $objecttmp,
8475
            'htmlname' => $htmlname,
8476
            'filter' => $filter,
8477
            'searchkey' => $searchkey
8478
        );
8479
8480
        $reshook = $hookmanager->executeHooks('selectForFormsListWhere', $parameters); // Note that $action and $object may have been modified by hook
8481
        if (!empty($hookmanager->resPrint)) {
8482
            $sql .= $hookmanager->resPrint;
8483
        } else {
8484
            $sql .= " WHERE 1=1";
8485
            if (isset($objecttmp->ismultientitymanaged)) {
8486
                if ($objecttmp->ismultientitymanaged == 1) {
8487
                    $sql .= " AND t.entity IN (" . getEntity($objecttmp->table_element) . ")";
8488
                }
8489
                if (!is_numeric($objecttmp->ismultientitymanaged)) {
8490
                    $sql .= " AND parenttable.entity = t." . $tmparray[0];
8491
                }
8492
                if ($objecttmp->ismultientitymanaged == 1 && !empty($user->socid)) {
8493
                    if ($objecttmp->element == 'societe') {
8494
                        $sql .= " AND t.rowid = " . ((int) $user->socid);
8495
                    } else {
8496
                        $sql .= " AND t.fk_soc = " . ((int) $user->socid);
8497
                    }
8498
                }
8499
                if ($objecttmp->ismultientitymanaged === 'fk_soc@societe') {
8500
                    if (!$user->hasRight('societe', 'client', 'voir')) {
8501
                        $sql .= " AND t.rowid = sc.fk_soc AND sc.fk_user = " . ((int) $user->id);
8502
                    }
8503
                }
8504
            }
8505
            if ($searchkey != '') {
8506
                $sql .= natural_search(explode(',', $fieldstoshow), $searchkey);
8507
            }
8508
8509
            if ($filter) {     // Syntax example "(t.ref:like:'SO-%') and (t.date_creation:<:'20160101')"
8510
                $errormessage = '';
8511
                $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
8512
                if ($errormessage) {
8513
                    return 'Error forging a SQL request from an universal criteria: ' . $errormessage;
8514
                }
8515
            }
8516
        }
8517
        $sql .= $this->db->order($sortfield ? $sortfield : $fieldstoshow, "ASC");
8518
        //$sql.=$this->db->plimit($limit, 0);
8519
        //print $sql;
8520
8521
        // Build output string
8522
        $resql = $this->db->query($sql);
8523
        if ($resql) {
8524
            // Construct $out and $outarray
8525
            $out .= '<select id="' . $htmlname . '" class="flat minwidth100' . ($morecss ? ' ' . $morecss : '') . '"' . ($disabled ? ' disabled="disabled"' : '') . ($moreparams ? ' ' . $moreparams : '') . ' name="' . $htmlname . '">' . "\n";
8526
8527
            // 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
8528
            $textifempty = '&nbsp;';
8529
8530
            //if (!empty($conf->use_javascript_ajax) || $forcecombo) $textifempty='';
8531
            if (getDolGlobalInt($confkeyforautocompletemode)) {
8532
                if ($showempty && !is_numeric($showempty)) {
8533
                    $textifempty = $langs->trans($showempty);
8534
                } else {
8535
                    $textifempty .= $langs->trans("All");
8536
                }
8537
            }
8538
            if ($showempty) {
8539
                $out .= '<option value="-1">' . $textifempty . '</option>' . "\n";
8540
            }
8541
8542
            $num = $this->db->num_rows($resql);
8543
            $i = 0;
8544
            if ($num) {
8545
                while ($i < $num) {
8546
                    $obj = $this->db->fetch_object($resql);
8547
                    $label = '';
8548
                    $labelhtml = '';
8549
                    $tmparray = explode(',', $fieldstoshow);
8550
                    $oldvalueforshowoncombobox = 0;
8551
                    foreach ($tmparray as $key => $val) {
8552
                        $val = preg_replace('/t\./', '', $val);
8553
                        $label .= (($label && $obj->$val) ? ($oldvalueforshowoncombobox != $objecttmp->fields[$val]['showoncombobox'] ? ' - ' : ' ') : '');
8554
                        $labelhtml .= (($label && $obj->$val) ? ($oldvalueforshowoncombobox != $objecttmp->fields[$val]['showoncombobox'] ? ' - ' : ' ') : '');
8555
                        $label .= $obj->$val;
8556
                        $labelhtml .= $obj->$val;
8557
8558
                        $oldvalueforshowoncombobox = empty($objecttmp->fields[$val]['showoncombobox']) ? 0 : $objecttmp->fields[$val]['showoncombobox'];
8559
                    }
8560
                    if (empty($outputmode)) {
8561
                        if ($preselectedvalue > 0 && $preselectedvalue == $obj->rowid) {
8562
                            $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>';
8563
                        } else {
8564
                            $out .= '<option value="' . $obj->rowid . '" data-html="' . dol_escape_htmltag($labelhtml, 0, 0, '', 0, 1) . '">' . dol_escape_htmltag($label, 0, 0, '', 0, 1) . '</option>';
8565
                        }
8566
                    } else {
8567
                        array_push($outarray, array('key' => $obj->rowid, 'value' => $label, 'label' => $label));
8568
                    }
8569
8570
                    $i++;
8571
                    if (($i % 10) == 0) {
8572
                        $out .= "\n";
8573
                    }
8574
                }
8575
            }
8576
8577
            $out .= '</select>' . "\n";
8578
8579
            if (!$forcecombo) {
8580
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
8581
                $out .= ajax_combobox($htmlname, array(), getDolGlobalInt($confkeyforautocompletemode, 0));
8582
            }
8583
        } else {
8584
            dol_print_error($this->db);
8585
        }
8586
8587
        $this->result = array('nbofelement' => $num);
8588
8589
        if ($outputmode) {
8590
            return $outarray;
8591
        }
8592
        return $out;
8593
    }
8594
8595
8596
    /**
8597
     *  Return a HTML select string, built from an array of key+value.
8598
     *  Note: Do not apply langs->trans function on returned content, content may be entity encoded twice.
8599
     *
8600
     * @param string        $htmlname           Name of html select area. Try to start name with "multi" or "search_multi" if this is a multiselect
8601
     * @param array{label:string,data-html:string,disable?:int<0,1>,css?:string}    $array  Array like array(key => value) or array(key=>array('label'=>..., 'data-...'=>..., 'disabled'=>..., 'css'=>...))
8602
     * @param string|string[] $id               Preselected key or array of preselected keys for multiselect. Use 'ifone' to autoselect record if there is only one record.
8603
     * @param int<0,1>|string   $show_empty         0 no empty value allowed, 1 or string to add an empty value into list (If 1: key is -1 and value is '' or '&nbsp;', If placeholder string: key is -1 and value is the string), <0 to add an empty value with key that is this value.
8604
     * @param int<0,1>      $key_in_label       1 to show key into label with format "[key] value"
8605
     * @param int<0,1>      $value_as_key       1 to use value as key
8606
     * @param string        $moreparam          Add more parameters onto the select tag. For example 'style="width: 95%"' to avoid select2 component to go over parent container
8607
     * @param int<0,1>      $translate          1=Translate and encode value
8608
     * @param int           $maxlen             Length maximum for labels
8609
     * @param int<0,1>      $disabled           Html select box is disabled
8610
     * @param string        $sort               'ASC' or 'DESC' = Sort on label, '' or 'NONE' or 'POS' = Do not sort, we keep original order
8611
     * @param string        $morecss            Add more class to css styles
8612
     * @param int           $addjscombo         Add js combo
8613
     * @param string        $moreparamonempty   Add more param on the empty option line. Not used if show_empty not set
8614
     * @param int           $disablebademail    1=Check if a not valid email, 2=Check string '---', and if found into value, disable and colorize entry
8615
     * @param int           $nohtmlescape       No html escaping (not recommended, use 'data-html' if you need to use label with HTML content).
8616
     * @return string                           HTML select string.
8617
     * @see multiselectarray(), selectArrayAjax(), selectArrayFilter()
8618
     */
8619
    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)
8620
    {
8621
        global $conf, $langs;
8622
8623
        // Do we want a multiselect ?
8624
        //$jsbeautify = 0;
8625
        //if (preg_match('/^multi/',$htmlname)) $jsbeautify = 1;
8626
        $jsbeautify = 1;
8627
8628
        if ($value_as_key) {
8629
            $array = array_combine($array, $array);
8630
        }
8631
8632
        '@phan-var-force array{label:string,data-html:string,disable?:int<0,1>,css?:string}	$array'; // Array combine breaks information
8633
8634
        $out = '';
8635
8636
        if ($addjscombo < 0) {
8637
            if (!getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
8638
                $addjscombo = 1;
8639
            } else {
8640
                $addjscombo = 0;
8641
            }
8642
        }
8643
        $idname = str_replace(array('[', ']'), array('', ''), $htmlname);
8644
        $out .= '<select id="' . preg_replace('/^\./', '', $idname) . '" ' . ($disabled ? 'disabled="disabled" ' : '') . 'class="flat ' . (preg_replace('/^\./', '', $htmlname)) . ($morecss ? ' ' . $morecss : '') . ' selectformat"';
8645
        $out .= ' name="' . preg_replace('/^\./', '', $htmlname) . '" ' . ($moreparam ? $moreparam : '');
8646
        $out .= '>' . "\n";
8647
8648
        if ($show_empty) {
8649
            $textforempty = ' ';
8650
            if (!empty($conf->use_javascript_ajax)) {
8651
                $textforempty = '&nbsp;'; // If we use ajaxcombo, we need &nbsp; here to avoid to have an empty element that is too small.
8652
            }
8653
            if (!is_numeric($show_empty)) {
8654
                $textforempty = $show_empty;
8655
            }
8656
            $out .= '<option class="optiongrey" ' . ($moreparamonempty ? $moreparamonempty . ' ' : '') . 'value="' . (((int) $show_empty) < 0 ? $show_empty : -1) . '"' . ($id == $show_empty ? ' selected' : '') . '>' . $textforempty . '</option>' . "\n";
8657
        }
8658
        if (is_array($array)) {
8659
            // Translate
8660
            if ($translate) {
8661
                foreach ($array as $key => $value) {
8662
                    if (!is_array($value)) {
8663
                        $array[$key] = $langs->trans($value);
8664
                    } else {
8665
                        $array[$key]['label'] = $langs->trans($value['label']);
8666
                    }
8667
                }
8668
            }
8669
            // Sort
8670
            if ($sort == 'ASC') {
8671
                asort($array);
8672
            } elseif ($sort == 'DESC') {
8673
                arsort($array);
8674
            }
8675
8676
            foreach ($array as $key => $tmpvalue) {
8677
                if (is_array($tmpvalue)) {
8678
                    $value = $tmpvalue['label'];
8679
                    //$valuehtml = empty($tmpvalue['data-html']) ? $value : $tmpvalue['data-html'];
8680
                    $disabled = empty($tmpvalue['disabled']) ? '' : ' disabled';
8681
                    $style = empty($tmpvalue['css']) ? '' : ' class="' . $tmpvalue['css'] . '"';
8682
                } else {
8683
                    $value = $tmpvalue;
8684
                    //$valuehtml = $tmpvalue;
8685
                    $disabled = '';
8686
                    $style = '';
8687
                }
8688
                if (!empty($disablebademail)) {
8689
                    if (
8690
                        ($disablebademail == 1 && !preg_match('/&lt;.+@.+&gt;/', $value))
8691
                        || ($disablebademail == 2 && preg_match('/---/', $value))
8692
                    ) {
8693
                        $disabled = ' disabled';
8694
                        $style = ' class="warning"';
8695
                    }
8696
                }
8697
                if ($key_in_label) {
8698
                    if (empty($nohtmlescape)) {
8699
                        $selectOptionValue = dol_escape_htmltag($key . ' - ' . ($maxlen ? dol_trunc($value, $maxlen) : $value));
8700
                    } else {
8701
                        $selectOptionValue = $key . ' - ' . ($maxlen ? dol_trunc($value, $maxlen) : $value);
8702
                    }
8703
                } else {
8704
                    if (empty($nohtmlescape)) {
8705
                        $selectOptionValue = dol_escape_htmltag($maxlen ? dol_trunc($value, $maxlen) : $value);
8706
                    } else {
8707
                        $selectOptionValue = $maxlen ? dol_trunc($value, $maxlen) : $value;
8708
                    }
8709
                    if ($value == '' || $value == '-') {
8710
                        $selectOptionValue = '&nbsp;';
8711
                    }
8712
                }
8713
                $out .= '<option value="' . $key . '"';
8714
                $out .= $style . $disabled;
8715
                if (is_array($id)) {
8716
                    if (in_array($key, $id) && !$disabled) {
8717
                        $out .= ' selected'; // To preselect a value
8718
                    }
8719
                } else {
8720
                    $id = (string) $id; // if $id = 0, then $id = '0'
8721
                    if ($id != '' && ($id == $key || ($id == 'ifone' && count($array) == 1)) && !$disabled) {
8722
                        $out .= ' selected'; // To preselect a value
8723
                    }
8724
                }
8725
                if (!empty($nohtmlescape)) {    // deprecated. Use instead the key 'data-html' into input $array, managed at next step to use HTML content.
8726
                    $out .= ' data-html="' . dol_escape_htmltag($selectOptionValue) . '"';
8727
                }
8728
8729
                if (is_array($tmpvalue)) {
8730
                    foreach ($tmpvalue as $keyforvalue => $valueforvalue) {
8731
                        if (preg_match('/^data-/', $keyforvalue)) { // The best solution if you want to use HTML values into the list is to use data-html.
8732
                            $out .= ' ' . dol_escape_htmltag($keyforvalue) . '="' . dol_escape_htmltag($valueforvalue) . '"';
8733
                        }
8734
                    }
8735
                }
8736
                $out .= '>';
8737
                $out .= $selectOptionValue;
8738
                $out .= "</option>\n";
8739
            }
8740
        }
8741
        $out .= "</select>";
8742
8743
        // Add code for jquery to use multiselect
8744
        if ($addjscombo && $jsbeautify) {
8745
            // Enhance with select2
8746
            include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
8747
            $out .= ajax_combobox($idname, array(), 0, 0, 'resolve', (((int) $show_empty) < 0 ? (string) $show_empty : '-1'), $morecss);
8748
        }
8749
8750
        return $out;
8751
    }
8752
8753
    /**
8754
     *    Return a HTML select string, built from an array of key+value, but content returned into select come from an Ajax call of an URL.
8755
     *  Note: Do not apply langs->trans function on returned content of Ajax service, content may be entity encoded twice.
8756
     *
8757
     * @param string $htmlname Name of html select area
8758
     * @param string $url Url. Must return a json_encode of array(key=>array('text'=>'A text', 'url'=>'An url'), ...)
8759
     * @param string $id Preselected key
8760
     * @param string $moreparam Add more parameters onto the select tag
8761
     * @param string $moreparamtourl Add more parameters onto the Ajax called URL
8762
     * @param int $disabled Html select box is disabled
8763
     * @param int $minimumInputLength Minimum Input Length
8764
     * @param string $morecss Add more class to css styles
8765
     * @param int $callurlonselect If set to 1, some code is added so an url return by the ajax is called when value is selected.
8766
     * @param string $placeholder String to use as placeholder
8767
     * @param integer $acceptdelayedhtml 1 = caller is requesting to have html js content not returned but saved into global $delayedhtmlcontent (so caller can show it at end of page to avoid flash FOUC effect)
8768
     * @return    string                        HTML select string
8769
     * @see selectArrayFilter(), ajax_combobox() in ajax.lib.php
8770
     */
8771
    public static function selectArrayAjax($htmlname, $url, $id = '', $moreparam = '', $moreparamtourl = '', $disabled = 0, $minimumInputLength = 1, $morecss = '', $callurlonselect = 0, $placeholder = '', $acceptdelayedhtml = 0)
8772
    {
8773
        global $conf, $langs;
8774
        global $delayedhtmlcontent;    // Will be used later outside of this function
8775
8776
        // TODO Use an internal dolibarr component instead of select2
8777
        if (!getDolGlobalString('MAIN_USE_JQUERY_MULTISELECT') && !defined('REQUIRE_JQUERY_MULTISELECT')) {
8778
            return '';
8779
        }
8780
8781
        $out = '<select type="text" class="' . $htmlname . ($morecss ? ' ' . $morecss : '') . '" ' . ($moreparam ? $moreparam . ' ' : '') . 'name="' . $htmlname . '"></select>';
8782
8783
        $outdelayed = '';
8784
        if (!empty($conf->use_javascript_ajax)) {
8785
            $tmpplugin = 'select2';
8786
            $outdelayed = "\n" . '<!-- JS CODE TO ENABLE ' . $tmpplugin . ' for id ' . $htmlname . ' -->
8787
		    	<script nonce="' . getNonce() . '">
8788
		    	$(document).ready(function () {
8789
8790
	    	        ' . ($callurlonselect ? 'var saveRemoteData = [];' : '') . '
8791
8792
	                $(".' . $htmlname . '").select2({
8793
				    	ajax: {
8794
					    	dir: "ltr",
8795
					    	url: "' . $url . '",
8796
					    	dataType: \'json\',
8797
					    	delay: 250,
8798
					    	data: function (params) {
8799
					    		return {
8800
							    	q: params.term, 	// search term
8801
					    			page: params.page
8802
					    		}
8803
				    		},
8804
				    		processResults: function (data) {
8805
				    			// parse the results into the format expected by Select2.
8806
				    			// since we are using custom formatting functions we do not need to alter the remote JSON data
8807
				    			//console.log(data);
8808
								saveRemoteData = data;
8809
					    	    /* format json result for select2 */
8810
					    	    result = []
8811
					    	    $.each( data, function( key, value ) {
8812
					    	       result.push({id: key, text: value.text});
8813
	                            });
8814
				    			//return {results:[{id:\'none\', text:\'aa\'}, {id:\'rrr\', text:\'Red\'},{id:\'bbb\', text:\'Search a into projects\'}], more:false}
8815
				    			//console.log(result);
8816
				    			return {results: result, more: false}
8817
				    		},
8818
				    		cache: true
8819
				    	},
8820
		 				language: select2arrayoflanguage,
8821
						containerCssClass: \':all:\',					/* Line to add class of origin SELECT propagated to the new <span class="select2-selection...> tag */
8822
					    placeholder: "' . dol_escape_js($placeholder) . '",
8823
				    	escapeMarkup: function (markup) { return markup; }, 	// let our custom formatter work
8824
				    	minimumInputLength: ' . ((int) $minimumInputLength) . ',
8825
				        formatResult: function (result, container, query, escapeMarkup) {
8826
	                        return escapeMarkup(result.text);
8827
	                    },
8828
				    });
8829
8830
	                ' . ($callurlonselect ? '
8831
	                /* Code to execute a GET when we select a value */
8832
	                $(".' . $htmlname . '").change(function() {
8833
				    	var selected = $(".' . $htmlname . '").val();
8834
	                	console.log("We select in selectArrayAjax the entry "+selected)
8835
				        $(".' . $htmlname . '").val("");  /* reset visible combo value */
8836
	    			    $.each( saveRemoteData, function( key, value ) {
8837
	    				        if (key == selected)
8838
	    			            {
8839
	    			                 console.log("selectArrayAjax - Do a redirect to "+value.url)
8840
	    			                 location.assign(value.url);
8841
	    			            }
8842
	                    });
8843
	    			});' : '') . '
8844
8845
	    	   });
8846
		       </script>';
8847
        }
8848
8849
        if ($acceptdelayedhtml) {
8850
            $delayedhtmlcontent .= $outdelayed;
8851
        } else {
8852
            $out .= $outdelayed;
8853
        }
8854
        return $out;
8855
    }
8856
8857
    /**
8858
     *  Return a HTML select string, built from an array of key+value, but content returned into select is defined into $array parameter.
8859
     *  Note: Do not apply langs->trans function on returned content of Ajax service, content may be entity encoded twice.
8860
     *
8861
     * @param string    $htmlname               Name of html select area
8862
     * @param array<string,array{text:string,url:string}>   $array  Array (key=>array('text'=>'A text', 'url'=>'An url'), ...)
8863
     * @param string    $id                     Preselected key
8864
     * @param string    $moreparam              Add more parameters onto the select tag
8865
     * @param int<0,1>  $disableFiltering       If set to 1, results are not filtered with searched string
8866
     * @param int<0,1>  $disabled               Html select box is disabled
8867
     * @param int       $minimumInputLength     Minimum Input Length
8868
     * @param string    $morecss                Add more class to css styles
8869
     * @param int<0,1>  $callurlonselect        If set to 1, some code is added so an url return by the ajax is called when value is selected.
8870
     * @param string    $placeholder            String to use as placeholder
8871
     * @param int<0,1>  $acceptdelayedhtml      1 = caller is requesting to have html js content not returned but saved into global $delayedhtmlcontent (so caller can show it at end of page to avoid flash FOUC effect)
8872
     * @param string    $textfortitle           Text to show on title.
8873
     * @return  string                          HTML select string
8874
     * @see selectArrayAjax(), ajax_combobox() in ajax.lib.php
8875
     */
8876
    public static function selectArrayFilter($htmlname, $array, $id = '', $moreparam = '', $disableFiltering = 0, $disabled = 0, $minimumInputLength = 1, $morecss = '', $callurlonselect = 0, $placeholder = '', $acceptdelayedhtml = 0, $textfortitle = '')
8877
    {
8878
        global $conf, $langs;
8879
        global $delayedhtmlcontent;    // Will be used later outside of this function
8880
8881
        // TODO Use an internal dolibarr component instead of select2
8882
        if (!getDolGlobalString('MAIN_USE_JQUERY_MULTISELECT') && !defined('REQUIRE_JQUERY_MULTISELECT')) {
8883
            return '';
8884
        }
8885
8886
        $out = '<select type="text"' . ($textfortitle ? ' title="' . dol_escape_htmltag($textfortitle) . '"' : '') . ' id="' . $htmlname . '" class="' . $htmlname . ($morecss ? ' ' . $morecss : '') . '"' . ($moreparam ? ' ' . $moreparam : '') . ' name="' . $htmlname . '"><option></option></select>';
8887
8888
        $formattedarrayresult = array();
8889
8890
        foreach ($array as $key => $value) {
8891
            $o = new stdClass();
8892
            $o->id = $key;
8893
            $o->text = $value['text'];
8894
            $o->url = $value['url'];
8895
            $formattedarrayresult[] = $o;
8896
        }
8897
8898
        $outdelayed = '';
8899
        if (!empty($conf->use_javascript_ajax)) {
8900
            $tmpplugin = 'select2';
8901
            $outdelayed = "\n" . '<!-- JS CODE TO ENABLE ' . $tmpplugin . ' for id ' . $htmlname . ' -->
8902
				<script nonce="' . getNonce() . '">
8903
				$(document).ready(function () {
8904
					var data = ' . json_encode($formattedarrayresult) . ';
8905
8906
					' . ($callurlonselect ? 'var saveRemoteData = ' . json_encode($array) . ';' : '') . '
8907
8908
					$(".' . $htmlname . '").select2({
8909
						data: data,
8910
						language: select2arrayoflanguage,
8911
						containerCssClass: \':all:\',					/* Line to add class of origin SELECT propagated to the new <span class="select2-selection...> tag */
8912
						placeholder: "' . dol_escape_js($placeholder) . '",
8913
						escapeMarkup: function (markup) { return markup; }, 	// let our custom formatter work
8914
						minimumInputLength: ' . $minimumInputLength . ',
8915
						formatResult: function (result, container, query, escapeMarkup) {
8916
							return escapeMarkup(result.text);
8917
						},
8918
						matcher: function (params, data) {
8919
8920
							if(! data.id) return null;';
8921
8922
            if ($callurlonselect) {
8923
                // We forge the url with 'sall='
8924
                $outdelayed .= '
8925
8926
							var urlBase = data.url;
8927
							var separ = urlBase.indexOf("?") >= 0 ? "&" : "?";
8928
							/* console.log("params.term="+params.term); */
8929
							/* console.log("params.term encoded="+encodeURIComponent(params.term)); */
8930
							saveRemoteData[data.id].url = urlBase + separ + "search_all=" + encodeURIComponent(params.term.replace(/\"/g, ""));';
8931
            }
8932
8933
            if (!$disableFiltering) {
8934
                $outdelayed .= '
8935
8936
							if(data.text.match(new RegExp(params.term))) {
8937
								return data;
8938
							}
8939
8940
							return null;';
8941
            } else {
8942
                $outdelayed .= '
8943
8944
							return data;';
8945
            }
8946
8947
            $outdelayed .= '
8948
						}
8949
					});
8950
8951
					' . ($callurlonselect ? '
8952
					/* Code to execute a GET when we select a value */
8953
					$(".' . $htmlname . '").change(function() {
8954
						var selected = $(".' . $htmlname . '").val();
8955
						console.log("We select "+selected)
8956
8957
						$(".' . $htmlname . '").val("");  /* reset visible combo value */
8958
						$.each( saveRemoteData, function( key, value ) {
8959
							if (key == selected)
8960
							{
8961
								console.log("selectArrayFilter - Do a redirect to "+value.url)
8962
								location.assign(value.url);
8963
							}
8964
						});
8965
					});' : '') . '
8966
8967
				});
8968
				</script>';
8969
        }
8970
8971
        if ($acceptdelayedhtml) {
8972
            $delayedhtmlcontent .= $outdelayed;
8973
        } else {
8974
            $out .= $outdelayed;
8975
        }
8976
        return $out;
8977
    }
8978
8979
    /**
8980
     * Show a multiselect form from an array. WARNING: Use this only for short lists.
8981
     *
8982
     * @param   string      $htmlname       Name of select
8983
     * @param   array<string,string|array{id:string,label:string,color:string,picto:string,labelhtml:string}>   $array          Array(key=>value) or Array(key=>array('id'=>key, 'label'=>value, 'color'=> , 'picto'=> , 'labelhtml'=> ))
8984
     * @param   string[]    $selected       Array of keys preselected
8985
     * @param   int<0,1>    $key_in_label   1 to show key like in "[key] value"
8986
     * @param   int<0,1>    $value_as_key   1 to use value as key
8987
     * @param   string      $morecss        Add more css style
8988
     * @param   int<0,1>    $translate      Translate and encode value
8989
     * @param   int|string  $width          Force width of select box. May be used only when using jquery couch. Example: 250, '95%'
8990
     * @param   string      $moreattrib     Add more options on select component. Example: 'disabled'
8991
     * @param   string      $elemtype       Type of element we show ('category', ...). Will execute a formatting function on it. To use in readonly mode if js component support HTML formatting.
8992
     * @param   string      $placeholder    String to use as placeholder
8993
     * @param   int<-1,1>   $addjscombo     Add js combo
8994
     * @return  string                      HTML multiselect string
8995
     * @see selectarray(), selectArrayAjax(), selectArrayFilter()
8996
     */
8997
    public static function multiselectarray($htmlname, $array, $selected = array(), $key_in_label = 0, $value_as_key = 0, $morecss = '', $translate = 0, $width = 0, $moreattrib = '', $elemtype = '', $placeholder = '', $addjscombo = -1)
8998
    {
8999
        global $conf, $langs;
9000
9001
        $out = '';
9002
9003
        if ($addjscombo < 0) {
9004
            if (!getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
9005
                $addjscombo = 1;
9006
            } else {
9007
                $addjscombo = 0;
9008
            }
9009
        }
9010
9011
        $useenhancedmultiselect = 0;
9012
        if (!empty($conf->use_javascript_ajax) && !defined('MAIN_DO_NOT_USE_JQUERY_MULTISELECT') && (getDolGlobalString('MAIN_USE_JQUERY_MULTISELECT') || defined('REQUIRE_JQUERY_MULTISELECT'))) {
9013
            if ($addjscombo) {
9014
                $useenhancedmultiselect = 1;    // Use the js multiselect in one line. Possible only if $addjscombo not 0.
9015
            }
9016
        }
9017
9018
        // We need a hidden field because when using the multiselect, if we unselect all, there is no
9019
        // variable submitted at all, so no way to make a difference between variable not submitted and variable
9020
        // submitted to nothing.
9021
        $out .= '<input type="hidden" name="' . $htmlname . '_multiselect" value="1">';
9022
        // Output select component
9023
        $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";
9024
        if (is_array($array) && !empty($array)) {
9025
            if ($value_as_key) {
9026
                $array = array_combine($array, $array);
9027
            }
9028
9029
            if (!empty($array)) {
9030
                foreach ($array as $key => $value) {
9031
                    $tmpkey = $key;
9032
                    $tmpvalue = $value;
9033
                    $tmpcolor = '';
9034
                    $tmppicto = '';
9035
                    $tmplabelhtml = '';
9036
                    if (is_array($value) && array_key_exists('id', $value) && array_key_exists('label', $value)) {
9037
                        $tmpkey = $value['id'];
9038
                        $tmpvalue = empty($value['label']) ? '' : $value['label'];
9039
                        $tmpcolor = empty($value['color']) ? '' : $value['color'];
9040
                        $tmppicto = empty($value['picto']) ? '' : $value['picto'];
9041
                        $tmplabelhtml = empty($value['labelhtml']) ? (empty($value['data-html']) ? '' : $value['data-html']) : $value['labelhtml'];
9042
                    }
9043
                    $newval = ($translate ? $langs->trans($tmpvalue) : $tmpvalue);
9044
                    $newval = ($key_in_label ? $tmpkey . ' - ' . $newval : $newval);
9045
9046
                    $out .= '<option value="' . $tmpkey . '"';
9047
                    if (is_array($selected) && !empty($selected) && in_array((string) $tmpkey, $selected) && ((string) $tmpkey != '')) {
9048
                        $out .= ' selected';
9049
                    }
9050
                    if (!empty($tmplabelhtml)) {
9051
                        $out .= ' data-html="' . dol_escape_htmltag($tmplabelhtml, 0, 0, '', 0, 1) . '"';
9052
                    } else {
9053
                        $tmplabelhtml = ($tmppicto ? img_picto('', $tmppicto, 'class="pictofixedwidth" style="color: #' . $tmpcolor . '"') : '') . $newval;
9054
                        $out .= ' data-html="' . dol_escape_htmltag($tmplabelhtml, 0, 0, '', 0, 1) . '"';
9055
                    }
9056
                    $out .= '>';
9057
                    $out .= dol_htmlentitiesbr($newval);
9058
                    $out .= '</option>' . "\n";
9059
                }
9060
            }
9061
        }
9062
        $out .= '</select>' . "\n";
9063
9064
        // Add code for jquery to use multiselect
9065
        if (!empty($conf->use_javascript_ajax) && getDolGlobalString('MAIN_USE_JQUERY_MULTISELECT') || defined('REQUIRE_JQUERY_MULTISELECT')) {
9066
            $out .= "\n" . '<!-- JS CODE TO ENABLE select for id ' . $htmlname . ', addjscombo=' . $addjscombo . ' -->';
9067
            $out .= "\n" . '<script nonce="' . getNonce() . '">' . "\n";
9068
            if ($addjscombo == 1) {
9069
                $tmpplugin = !getDolGlobalString('MAIN_USE_JQUERY_MULTISELECT') ? constant('REQUIRE_JQUERY_MULTISELECT') : $conf->global->MAIN_USE_JQUERY_MULTISELECT;
9070
                $out .= 'function formatResult(record, container) {' . "\n";
9071
                // If property data-html set, we decode html entities and use this.
9072
                // Note that HTML content must have been sanitized from js with dol_escape_htmltag(xxx, 0, 0, '', 0, 1) when building the select option.
9073
                $out .= '	if ($(record.element).attr("data-html") != undefined && typeof htmlEntityDecodeJs === "function") {';
9074
                //$out .= '     console.log("aaa");';
9075
                $out .= '		return htmlEntityDecodeJs($(record.element).attr("data-html"));';
9076
                $out .= '	}' . "\n";
9077
                $out .= '	return record.text;';
9078
                $out .= '}' . "\n";
9079
                $out .= 'function formatSelection(record) {' . "\n";
9080
                if ($elemtype == 'category') {
9081
                    $out .= 'return \'<span><img src="' . constant('DOL_URL_ROOT') . '/theme/eldy/img/object_category.png"> \'+record.text+\'</span>\';';
9082
                } else {
9083
                    $out .= 'return record.text;';
9084
                }
9085
                $out .= '}' . "\n";
9086
                $out .= '$(document).ready(function () {
9087
							$(\'#' . $htmlname . '\').' . $tmpplugin . '({';
9088
                if ($placeholder) {
9089
                    $out .= '
9090
								placeholder: {
9091
								    id: \'-1\',
9092
								    text: \'' . dol_escape_js($placeholder) . '\'
9093
								  },';
9094
                }
9095
                $out .= '		dir: \'ltr\',
9096
								containerCssClass: \':all:\',					/* Line to add class of origin SELECT propagated to the new <span class="select2-selection...> tag (ko with multiselect) */
9097
								dropdownCssClass: \'' . $morecss . '\',				/* Line to add class on the new <span class="select2-selection...> tag (ok with multiselect). Need full version of select2. */
9098
								// Specify format function for dropdown item
9099
								formatResult: formatResult,
9100
							 	templateResult: formatResult,		/* For 4.0 */
9101
								escapeMarkup: function (markup) { return markup; }, 	// let our custom formatter work
9102
								// Specify format function for selected item
9103
								formatSelection: formatSelection,
9104
							 	templateSelection: formatSelection		/* For 4.0 */
9105
							});
9106
9107
							/* Add also morecss to the css .select2 that is after the #htmlname, for component that are show dynamically after load, because select2 set
9108
								 the size only if component is not hidden by default on load */
9109
							$(\'#' . $htmlname . ' + .select2\').addClass(\'' . $morecss . '\');
9110
						});' . "\n";
9111
            } elseif ($addjscombo == 2 && !defined('DISABLE_MULTISELECT')) {
9112
                // Add other js lib
9113
                // TODO external lib multiselect/jquery.multi-select.js must have been loaded to use this multiselect plugin
9114
                // ...
9115
                $out .= 'console.log(\'addjscombo=2 for htmlname=' . $htmlname . '\');';
9116
                $out .= '$(document).ready(function () {
9117
							$(\'#' . $htmlname . '\').multiSelect({
9118
								containerHTML: \'<div class="multi-select-container">\',
9119
								menuHTML: \'<div class="multi-select-menu">\',
9120
								buttonHTML: \'<span class="multi-select-button ' . $morecss . '">\',
9121
								menuItemHTML: \'<label class="multi-select-menuitem">\',
9122
								activeClass: \'multi-select-container--open\',
9123
								noneText: \'' . $placeholder . '\'
9124
							});
9125
						})';
9126
            }
9127
            $out .= '</script>';
9128
        }
9129
9130
        return $out;
9131
    }
9132
9133
9134
    /**
9135
     * Show a multiselect dropbox from an array.
9136
     * If a saved selection of fields exists for user (into $user->conf->MAIN_SELECTEDFIELDS_contextofpage), we use this one instead of default.
9137
     *
9138
     * @param string    $htmlname   Name of HTML field
9139
     * @param array<string,array{label:string,checked:string,enabled?:string,type?:string,langfile?:string}>    $array  Array with array of fields we could show. This array may be modified according to setup of user.
9140
     * @param string    $varpage    Id of context for page. Can be set by caller with $varpage=(empty($contextpage)?$_SERVER["PHP_SELF"]:$contextpage);
9141
     * @param string    $pos        Position colon on liste value 'left' or '' (meaning 'right').
9142
     * @return string               HTML multiselect string
9143
     * @see selectarray()
9144
     */
9145
    public static function multiSelectArrayWithCheckbox($htmlname, &$array, $varpage, $pos = '')
9146
    {
9147
        global $langs, $user;
9148
9149
        if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
9150
            return '';
9151
        }
9152
        if (empty($array)) {
9153
            return '';
9154
        }
9155
9156
        $tmpvar = "MAIN_SELECTEDFIELDS_" . $varpage; // To get list of saved selected fields to show
9157
9158
        if (!empty($user->conf->$tmpvar)) {        // A list of fields was already customized for user
9159
            $tmparray = explode(',', $user->conf->$tmpvar);
9160
            foreach ($array as $key => $val) {
9161
                //var_dump($key);
9162
                //var_dump($tmparray);
9163
                if (in_array($key, $tmparray)) {
9164
                    $array[$key]['checked'] = 1;
9165
                } else {
9166
                    $array[$key]['checked'] = 0;
9167
                }
9168
            }
9169
        } else {                                // There is no list of fields already customized for user
9170
            foreach ($array as $key => $val) {
9171
                if (!empty($array[$key]['checked']) && $array[$key]['checked'] < 0) {
9172
                    $array[$key]['checked'] = 0;
9173
                }
9174
            }
9175
        }
9176
9177
        $listoffieldsforselection = '';
9178
        $listcheckedstring = '';
9179
9180
        foreach ($array as $key => $val) {
9181
            // var_dump($val);
9182
            // var_dump(array_key_exists('enabled', $val));
9183
            // var_dump(!$val['enabled']);
9184
            if (array_key_exists('enabled', $val) && isset($val['enabled']) && !$val['enabled']) {
9185
                unset($array[$key]); // We don't want this field
9186
                continue;
9187
            }
9188
            if (!empty($val['type']) && $val['type'] == 'separate') {
9189
                // Field remains in array but we don't add it into $listoffieldsforselection
9190
                //$listoffieldsforselection .= '<li>-----</li>';
9191
                continue;
9192
            }
9193
            if (!empty($val['label']) && $val['label']) {
9194
                if (!empty($val['langfile']) && is_object($langs)) {
9195
                    $langs->load($val['langfile']);
9196
                }
9197
9198
                // Note: $val['checked'] <> 0 means we must show the field into the combo list  @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
9199
                $listoffieldsforselection .= '<li><input type="checkbox" id="checkbox' . $key . '" value="' . $key . '"' . ((!array_key_exists('checked', $val) || empty($val['checked']) || $val['checked'] == '-1') ? '' : ' checked="checked"') . '/><label for="checkbox' . $key . '">' . dol_escape_htmltag($langs->trans($val['label'])) . '</label></li>';
9200
                $listcheckedstring .= (empty($val['checked']) ? '' : $key . ',');
9201
            }
9202
        }
9203
9204
        $out = '<!-- Component multiSelectArrayWithCheckbox ' . $htmlname . ' -->
9205
9206
        <dl class="dropdown">
9207
            <dt>
9208
            <a href="#' . $htmlname . '">
9209
              ' . img_picto('', 'list') . '
9210
            </a>
9211
            <input type="hidden" class="' . $htmlname . '" name="' . $htmlname . '" value="' . $listcheckedstring . '">
9212
            </dt>
9213
            <dd class="dropdowndd">
9214
                <div class="multiselectcheckbox' . $htmlname . '">
9215
                    <ul class="' . $htmlname . ($pos == '1' ? 'left' : '') . '">
9216
                    <li><input class="inputsearch_dropdownselectedfields width90p minwidth200imp" style="width:90%;" type="text" placeholder="' . $langs->trans('Search') . '"></li>
9217
                    ' . $listoffieldsforselection . '
9218
                    </ul>
9219
                </div>
9220
            </dd>
9221
        </dl>
9222
9223
        <script nonce="' . getNonce() . '" type="text/javascript">
9224
          jQuery(document).ready(function () {
9225
              $(\'.multiselectcheckbox' . $htmlname . ' input[type="checkbox"]\').on(\'click\', function () {
9226
                  console.log("A new field was added/removed, we edit field input[name=formfilteraction]");
9227
9228
                  $("input:hidden[name=formfilteraction]").val(\'listafterchangingselectedfields\');	// Update field so we know we changed something on selected fields after POST
9229
9230
                  var title = $(this).val() + ",";
9231
                  if ($(this).is(\':checked\')) {
9232
                      $(\'.' . $htmlname . '\').val(title + $(\'.' . $htmlname . '\').val());
9233
                  }
9234
                  else {
9235
                      $(\'.' . $htmlname . '\').val( $(\'.' . $htmlname . '\').val().replace(title, \'\') )
9236
                  }
9237
                  // Now, we submit page
9238
                  //$(this).parents(\'form:first\').submit();
9239
              });
9240
              $("input.inputsearch_dropdownselectedfields").on("keyup", function() {
9241
			    var value = $(this).val().toLowerCase();
9242
			    $(\'.multiselectcheckbox' . $htmlname . ' li > label\').filter(function() {
9243
			      $(this).parent().toggle($(this).text().toLowerCase().indexOf(value) > -1)
9244
			    });
9245
			  });
9246
9247
9248
           });
9249
        </script>
9250
9251
        ';
9252
        return $out;
9253
    }
9254
9255
    /**
9256
     * Render list of categories linked to object with id $id and type $type
9257
     *
9258
     * @param int       $id         Id of object
9259
     * @param string    $type       Type of category ('member', 'customer', 'supplier', 'product', 'contact'). Old mode (0, 1, 2, ...) is deprecated.
9260
     * @param int<0,1>  $rendermode 0=Default, use multiselect. 1=Emulate multiselect (recommended)
9261
     * @param int<0,1>  $nolink     1=Do not add html links
9262
     * @return string               String with categories
9263
     */
9264
    public function showCategories($id, $type, $rendermode = 0, $nolink = 0)
9265
    {
9266
        include_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
9267
9268
        $cat = new Categorie($this->db);
9269
        $categories = $cat->containing($id, $type);
9270
9271
        if ($rendermode == 1) {
9272
            $toprint = array();
9273
            foreach ($categories as $c) {
9274
                $ways = $c->print_all_ways(' &gt;&gt; ', ($nolink ? 'none' : ''), 0, 1); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
9275
                foreach ($ways as $way) {
9276
                    $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #bbb"') . '>' . $way . '</li>';
9277
                }
9278
            }
9279
            return '<div class="select2-container-multi-dolibarr"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
9280
        }
9281
9282
        if ($rendermode == 0) {
9283
            $arrayselected = array();
9284
            $cate_arbo = $this->select_all_categories($type, '', 'parent', 64, 0, 3);
9285
            foreach ($categories as $c) {
9286
                $arrayselected[] = $c->id;
9287
            }
9288
9289
            return $this->multiselectarray('categories', $cate_arbo, $arrayselected, 0, 0, '', 0, '100%', 'disabled', 'category');
9290
        }
9291
9292
        return 'ErrorBadValueForParameterRenderMode'; // Should not happened
9293
    }
9294
9295
    /**
9296
     *  Show linked object block.
9297
     *
9298
     * @param   CommonObject    $object                         Object we want to show links to
9299
     * @param   string          $morehtmlright                  More html to show on right of title
9300
     * @param   array<int,string>   $compatibleImportElementsList   Array of compatibles elements object for "import from" action
9301
     * @param   string          $title                          Title
9302
     * @return  int                                             Return Number of different types
9303
     */
9304
    public function showLinkedObjectBlock($object, $morehtmlright = '', $compatibleImportElementsList = array(), $title = 'RelatedObjects')
9305
    {
9306
        global $conf, $langs, $hookmanager;
9307
        global $bc, $action;
9308
9309
        $object->fetchObjectLinked();
9310
9311
        // Bypass the default method
9312
        $hookmanager->initHooks(array('commonobject'));
9313
        $parameters = array(
9314
            'morehtmlright' => $morehtmlright,
9315
            'compatibleImportElementsList' => &$compatibleImportElementsList,
9316
        );
9317
        $reshook = $hookmanager->executeHooks('showLinkedObjectBlock', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
9318
9319
        $nbofdifferenttypes = count($object->linkedObjects);
9320
9321
        if (empty($reshook)) {
9322
            print '<!-- showLinkedObjectBlock -->';
9323
            print load_fiche_titre($langs->trans($title), $morehtmlright, '', 0, 0, 'showlinkedobjectblock');
9324
9325
9326
            print '<div class="div-table-responsive-no-min">';
9327
            print '<table class="noborder allwidth" data-block="showLinkedObject" data-element="' . $object->element . '"  data-elementid="' . $object->id . '"   >';
9328
9329
            print '<tr class="liste_titre">';
9330
            print '<td>' . $langs->trans("Type") . '</td>';
9331
            print '<td>' . $langs->trans("Ref") . '</td>';
9332
            print '<td class="center"></td>';
9333
            print '<td class="center">' . $langs->trans("Date") . '</td>';
9334
            print '<td class="right">' . $langs->trans("AmountHTShort") . '</td>';
9335
            print '<td class="right">' . $langs->trans("Status") . '</td>';
9336
            print '<td></td>';
9337
            print '</tr>';
9338
9339
            $nboftypesoutput = 0;
9340
9341
            foreach ($object->linkedObjects as $objecttype => $objects) {
9342
                $tplpath = $element = $subelement = $objecttype;
9343
9344
                // to display import button on tpl
9345
                $showImportButton = false;
9346
                if (!empty($compatibleImportElementsList) && in_array($element, $compatibleImportElementsList)) {
9347
                    $showImportButton = true;
9348
                }
9349
9350
                $regs = array();
9351
                if ($objecttype != 'supplier_proposal' && preg_match('/^([^_]+)_([^_]+)/i', $objecttype, $regs)) {
9352
                    $element = $regs[1];
9353
                    $subelement = $regs[2];
9354
                    $tplpath = $element . '/' . $subelement;
9355
                }
9356
                $tplname = 'linkedobjectblock';
9357
9358
                // To work with non standard path
9359
                if ($objecttype == 'facture') {
9360
                    $tplpath = 'compta/' . $element;
9361
                    if (!isModEnabled('invoice')) {
9362
                        continue; // Do not show if module disabled
9363
                    }
9364
                } elseif ($objecttype == 'facturerec') {
9365
                    $tplpath = 'compta/facture';
9366
                    $tplname = 'linkedobjectblockForRec';
9367
                    if (!isModEnabled('invoice')) {
9368
                        continue; // Do not show if module disabled
9369
                    }
9370
                } elseif ($objecttype == 'propal') {
9371
                    $tplpath = 'comm/' . $element;
9372
                    if (!isModEnabled('propal')) {
9373
                        continue; // Do not show if module disabled
9374
                    }
9375
                } elseif ($objecttype == 'supplier_proposal') {
9376
                    if (!isModEnabled('supplier_proposal')) {
9377
                        continue; // Do not show if module disabled
9378
                    }
9379
                } elseif ($objecttype == 'shipping' || $objecttype == 'shipment' || $objecttype == 'expedition') {
9380
                    $tplpath = 'expedition';
9381
                    if (!isModEnabled('shipping')) {
9382
                        continue; // Do not show if module disabled
9383
                    }
9384
                } elseif ($objecttype == 'reception') {
9385
                    $tplpath = 'reception';
9386
                    if (!isModEnabled('reception')) {
9387
                        continue; // Do not show if module disabled
9388
                    }
9389
                } elseif ($objecttype == 'delivery') {
9390
                    $tplpath = 'delivery';
9391
                    if (!getDolGlobalInt('MAIN_SUBMODULE_DELIVERY')) {
9392
                        continue; // Do not show if sub module disabled
9393
                    }
9394
                } elseif ($objecttype == 'ficheinter') {
9395
                    $tplpath = 'fichinter';
9396
                    if (!isModEnabled('intervention')) {
9397
                        continue; // Do not show if module disabled
9398
                    }
9399
                } elseif ($objecttype == 'invoice_supplier') {
9400
                    $tplpath = 'fourn/facture';
9401
                } elseif ($objecttype == 'order_supplier') {
9402
                    $tplpath = 'fourn/commande';
9403
                } elseif ($objecttype == 'expensereport') {
9404
                    $tplpath = 'expensereport';
9405
                } elseif ($objecttype == 'subscription') {
9406
                    $tplpath = 'adherents';
9407
                } elseif ($objecttype == 'conferenceorbooth') {
9408
                    $tplpath = 'eventorganization';
9409
                } elseif ($objecttype == 'conferenceorboothattendee') {
9410
                    $tplpath = 'eventorganization';
9411
                } elseif ($objecttype == 'mo') {
9412
                    $tplpath = 'mrp';
9413
                    if (!isModEnabled('mrp')) {
9414
                        continue; // Do not show if module disabled
9415
                    }
9416
                }
9417
9418
                global $linkedObjectBlock;
9419
                $linkedObjectBlock = $objects;
9420
9421
                // Output template part (modules that overwrite templates must declare this into descriptor)
9422
                $dirtpls = array_merge($conf->modules_parts['tpl'], array('/' . $tplpath . '/tpl'));
9423
                foreach ($dirtpls as $reldir) {
9424
                    $reldir = rtrim($reldir, '/');
9425
                    if ($nboftypesoutput == ($nbofdifferenttypes - 1)) {    // No more type to show after
9426
                        global $noMoreLinkedObjectBlockAfter;
9427
                        $noMoreLinkedObjectBlockAfter = 1;
9428
                    }
9429
9430
                    $res = @include dol_buildpath($reldir . '/' . $tplname . '.tpl.php');
9431
                    if ($res) {
9432
                        $nboftypesoutput++;
9433
                        break;
9434
                    }
9435
                }
9436
            }
9437
9438
            if (!$nboftypesoutput) {
9439
                print '<tr><td class="impair" colspan="7"><span class="opacitymedium">' . $langs->trans("None") . '</span></td></tr>';
9440
            }
9441
9442
            print '</table>';
9443
9444
            if (!empty($compatibleImportElementsList)) {
9445
                $res = @include dol_buildpath('core/tpl/objectlinked_lineimport.tpl.php');
9446
            }
9447
9448
            print '</div>';
9449
        }
9450
9451
        return $nbofdifferenttypes;
9452
    }
9453
9454
    /**
9455
     *  Show block with links to link to other objects.
9456
     *
9457
     * @param   CommonObject    $object             Object we want to show links to
9458
     * @param   string[]            $restrictlinksto    Restrict links to some elements, for example array('order') or array('supplier_order'). null or array() if no restriction.
9459
     * @param   string[]            $excludelinksto     Do not show links of this type, for example array('order') or array('supplier_order'). null or array() if no exclusion.
9460
     * @return  string                              HTML block
9461
     */
9462
    public function showLinkToObjectBlock($object, $restrictlinksto = array(), $excludelinksto = array())
9463
    {
9464
        global $conf, $langs, $hookmanager;
9465
        global $action;
9466
9467
        $linktoelem = '';
9468
        $linktoelemlist = '';
9469
        $listofidcompanytoscan = '';
9470
9471
        if (!is_object($object->thirdparty)) {
9472
            $object->fetch_thirdparty();
9473
        }
9474
9475
        $possiblelinks = array();
9476
        if (is_object($object->thirdparty) && !empty($object->thirdparty->id) && $object->thirdparty->id > 0) {
9477
            $listofidcompanytoscan = $object->thirdparty->id;
9478
            if (($object->thirdparty->parent > 0) && getDolGlobalString('THIRDPARTY_INCLUDE_PARENT_IN_LINKTO')) {
9479
                $listofidcompanytoscan .= ',' . $object->thirdparty->parent;
9480
            }
9481
            if (($object->fk_project > 0) && getDolGlobalString('THIRDPARTY_INCLUDE_PROJECT_THIRDPARY_IN_LINKTO')) {
9482
                include_once DOL_DOCUMENT_ROOT . '/projet/class/project.class.php';
9483
                $tmpproject = new Project($this->db);
9484
                $tmpproject->fetch($object->fk_project);
9485
                if ($tmpproject->socid > 0 && ($tmpproject->socid != $object->thirdparty->id)) {
9486
                    $listofidcompanytoscan .= ',' . $tmpproject->socid;
9487
                }
9488
                unset($tmpproject);
9489
            }
9490
9491
            $possiblelinks = array(
9492
                'propal' => array(
9493
                    'enabled' => isModEnabled('propal'),
9494
                    'perms' => 1,
9495
                    'label' => 'LinkToProposal',
9496
                    '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') . ')'),
9497
                'shipping' => array(
9498
                    'enabled' => isModEnabled('shipping'),
9499
                    'perms' => 1,
9500
                    'label' => 'LinkToExpedition',
9501
                    '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') . ')'),
9502
                'order' => array(
9503
                    'enabled' => isModEnabled('order'),
9504
                    'perms' => 1,
9505
                    'label' => 'LinkToOrder',
9506
                    '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') . ')'),
9507
                'invoice' => array(
9508
                    'enabled' => isModEnabled('invoice'),
9509
                    'perms' => 1,
9510
                    'label' => 'LinkToInvoice',
9511
                    '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') . ')'),
9512
                'invoice_template' => array(
9513
                    'enabled' => isModEnabled('invoice'),
9514
                    'perms' => 1,
9515
                    'label' => 'LinkToTemplateInvoice',
9516
                    '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') . ')'),
9517
                'contrat' => array(
9518
                    'enabled' => isModEnabled('contract'),
9519
                    'perms' => 1,
9520
                    'label' => 'LinkToContract',
9521
                    '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
9522
							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'
9523
                ),
9524
                'fichinter' => array(
9525
                    'enabled' => isModEnabled('intervention'),
9526
                    'perms' => 1,
9527
                    'label' => 'LinkToIntervention',
9528
                    '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') . ')'),
9529
                'supplier_proposal' => array(
9530
                    'enabled' => isModEnabled('supplier_proposal'),
9531
                    'perms' => 1,
9532
                    'label' => 'LinkToSupplierProposal',
9533
                    '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') . ')'),
9534
                'order_supplier' => array(
9535
                    'enabled' => isModEnabled("supplier_order"),
9536
                    'perms' => 1,
9537
                    'label' => 'LinkToSupplierOrder',
9538
                    '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') . ')'),
9539
                'invoice_supplier' => array(
9540
                    'enabled' => isModEnabled("supplier_invoice"),
9541
                    'perms' => 1, 'label' => 'LinkToSupplierInvoice',
9542
                    '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') . ')'),
9543
                'ticket' => array(
9544
                    'enabled' => isModEnabled('ticket'),
9545
                    'perms' => 1,
9546
                    'label' => 'LinkToTicket',
9547
                    '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') . ')'),
9548
                'mo' => array(
9549
                    'enabled' => isModEnabled('mrp'),
9550
                    'perms' => 1,
9551
                    'label' => 'LinkToMo',
9552
                    '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') . ')')
9553
            );
9554
        }
9555
9556
        if ($object->table_element == 'commande_fournisseur') {
9557
            $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') . ')';
9558
        } elseif ($object->table_element == 'mrp_mo') {
9559
            $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') . ')';
9560
        }
9561
9562
        $reshook = 0; // Ensure $reshook is defined for static analysis
9563
        if (!empty($listofidcompanytoscan)) {  // If empty, we don't have criteria to scan the object we can link to
9564
            // Can complete the possiblelink array
9565
            $hookmanager->initHooks(array('commonobject'));
9566
            $parameters = array('listofidcompanytoscan' => $listofidcompanytoscan, 'possiblelinks' => $possiblelinks);
9567
            $reshook = $hookmanager->executeHooks('showLinkToObjectBlock', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
9568
        }
9569
9570
        if (empty($reshook)) {
9571
            if (is_array($hookmanager->resArray) && count($hookmanager->resArray)) {
9572
                $possiblelinks = array_merge($possiblelinks, $hookmanager->resArray);
9573
            }
9574
        } elseif ($reshook > 0) {
9575
            if (is_array($hookmanager->resArray) && count($hookmanager->resArray)) {
9576
                $possiblelinks = $hookmanager->resArray;
9577
            }
9578
        }
9579
9580
        foreach ($possiblelinks as $key => $possiblelink) {
9581
            $num = 0;
9582
9583
            if (empty($possiblelink['enabled'])) {
9584
                continue;
9585
            }
9586
9587
            if (!empty($possiblelink['perms']) && (empty($restrictlinksto) || in_array($key, $restrictlinksto)) && (empty($excludelinksto) || !in_array($key, $excludelinksto))) {
9588
                print '<div id="' . $key . 'list"' . (empty($conf->use_javascript_ajax) ? '' : ' style="display:none"') . '>';
9589
9590
                if (getDolGlobalString('MAIN_LINK_BY_REF_IN_LINKTO')) {
9591
                    print '<br>' . "\n";
9592
                    print '<!-- form to add a link from anywhere -->' . "\n";
9593
                    print '<form action="' . $_SERVER["PHP_SELF"] . '" method="POST" name="formlinkedbyref' . $key . '">';
9594
                    print '<input type="hidden" name="id" value="' . $object->id . '">';
9595
                    print '<input type="hidden" name="action" value="addlinkbyref">';
9596
                    print '<input type="hidden" name="token" value="' . newToken() . '">';
9597
                    print '<input type="hidden" name="addlink" value="' . $key . '">';
9598
                    print '<table class="noborder">';
9599
                    print '<tr>';
9600
                    //print '<td>' . $langs->trans("Ref") . '</td>';
9601
                    print '<td class="center"><input type="text" placeholder="' . dol_escape_htmltag($langs->trans("Ref")) . '" name="reftolinkto" value="' . dol_escape_htmltag(GETPOST('reftolinkto', 'alpha')) . '">&nbsp;';
9602
                    print '<input type="submit" class="button small valignmiddle" value="' . $langs->trans('ToLink') . '">&nbsp;';
9603
                    print '<input type="submit" class="button small" name="cancel" value="' . $langs->trans('Cancel') . '"></td>';
9604
                    print '</tr>';
9605
                    print '</table>';
9606
                    print '</form>';
9607
                }
9608
9609
                $sql = $possiblelink['sql'];
9610
9611
                $resqllist = $this->db->query($sql);
9612
                if ($resqllist) {
9613
                    $num = $this->db->num_rows($resqllist);
9614
                    $i = 0;
9615
9616
                    print '<br>';
9617
                    print '<!-- form to add a link from object to same thirdparty -->' . "\n";
9618
                    print '<form action="' . $_SERVER["PHP_SELF"] . '" method="POST" name="formlinked' . $key . '">';
9619
                    print '<input type="hidden" name="action" value="addlink">';
9620
                    print '<input type="hidden" name="token" value="' . newToken() . '">';
9621
                    print '<input type="hidden" name="id" value="' . $object->id . '">';
9622
                    print '<input type="hidden" name="addlink" value="' . $key . '">';
9623
                    print '<table class="noborder">';
9624
                    print '<tr class="liste_titre">';
9625
                    print '<td class="nowrap"></td>';
9626
                    print '<td class="center">' . $langs->trans("Ref") . '</td>';
9627
                    print '<td class="left">' . $langs->trans("RefCustomer") . '</td>';
9628
                    print '<td class="right">' . $langs->trans("AmountHTShort") . '</td>';
9629
                    print '<td class="left">' . $langs->trans("Company") . '</td>';
9630
                    print '</tr>';
9631
                    while ($i < $num) {
9632
                        $objp = $this->db->fetch_object($resqllist);
9633
9634
                        print '<tr class="oddeven">';
9635
                        print '<td class="left">';
9636
                        print '<input type="radio" name="idtolinkto" id="' . $key . '_' . $objp->rowid . '" value="' . $objp->rowid . '">';
9637
                        print '</td>';
9638
                        print '<td class="center"><label for="' . $key . '_' . $objp->rowid . '">' . $objp->ref . '</label></td>';
9639
                        print '<td>' . (!empty($objp->ref_client) ? $objp->ref_client : (!empty($objp->ref_supplier) ? $objp->ref_supplier : '')) . '</td>';
9640
                        print '<td class="right">';
9641
                        if ($possiblelink['label'] == 'LinkToContract') {
9642
                            $form = new Form($this->db);
9643
                            print $form->textwithpicto('', $langs->trans("InformationOnLinkToContract")) . ' ';
9644
                        }
9645
                        print '<span class="amount">' . (isset($objp->total_ht) ? price($objp->total_ht) : '') . '</span>';
9646
                        print '</td>';
9647
                        print '<td>' . $objp->name . '</td>';
9648
                        print '</tr>';
9649
                        $i++;
9650
                    }
9651
                    print '</table>';
9652
                    print '<div class="center">';
9653
                    if ($num) {
9654
                        print '<input type="submit" class="button valignmiddle marginleftonly marginrightonly small" value="' . $langs->trans('ToLink') . '">';
9655
                    }
9656
                    if (empty($conf->use_javascript_ajax)) {
9657
                        print '<input type="submit" class="button button-cancel marginleftonly marginrightonly small" name="cancel" value="' . $langs->trans("Cancel") . '"></div>';
9658
                    } else {
9659
                        print '<input type="submit" onclick="jQuery(\'#' . $key . 'list\').toggle(); return false;" class="button button-cancel marginleftonly marginrightonly small" name="cancel" value="' . $langs->trans("Cancel") . '"></div>';
9660
                    }
9661
                    print '</form>';
9662
                    $this->db->free($resqllist);
9663
                } else {
9664
                    dol_print_error($this->db);
9665
                }
9666
                print '</div>';
9667
9668
                //$linktoelem.=($linktoelem?' &nbsp; ':'');
9669
                if ($num > 0 || getDolGlobalString('MAIN_LINK_BY_REF_IN_LINKTO')) {
9670
                    $linktoelemlist .= '<li><a href="#linkto' . $key . '" class="linkto dropdowncloseonclick" rel="' . $key . '">' . $langs->trans($possiblelink['label']) . ' (' . $num . ')</a></li>';
9671
                    // } else $linktoelem.=$langs->trans($possiblelink['label']);
9672
                } else {
9673
                    $linktoelemlist .= '<li><span class="linktodisabled">' . $langs->trans($possiblelink['label']) . ' (0)</span></li>';
9674
                }
9675
            }
9676
        }
9677
9678
        if ($linktoelemlist) {
9679
            $linktoelem = '
9680
    		<dl class="dropdown" id="linktoobjectname">
9681
    		';
9682
            if (!empty($conf->use_javascript_ajax)) {
9683
                $linktoelem .= '<dt><a href="#linktoobjectname"><span class="fas fa-link paddingrightonly"></span>' . $langs->trans("LinkTo") . '...</a></dt>';
9684
            }
9685
            $linktoelem .= '<dd>
9686
    		<div class="multiselectlinkto">
9687
    		<ul class="ulselectedfields">' . $linktoelemlist . '
9688
    		</ul>
9689
    		</div>
9690
    		</dd>
9691
    		</dl>';
9692
        } else {
9693
            $linktoelem = '';
9694
        }
9695
9696
        if (!empty($conf->use_javascript_ajax)) {
9697
            print '<!-- Add js to show linkto box -->
9698
				<script nonce="' . getNonce() . '">
9699
				jQuery(document).ready(function() {
9700
					jQuery(".linkto").click(function() {
9701
						console.log("We choose to show/hide links for rel="+jQuery(this).attr(\'rel\')+" so #"+jQuery(this).attr(\'rel\')+"list");
9702
					    jQuery("#"+jQuery(this).attr(\'rel\')+"list").toggle();
9703
					});
9704
				});
9705
				</script>
9706
		    ';
9707
        }
9708
9709
        return $linktoelem;
9710
    }
9711
9712
    /**
9713
     *    Return an html string with a select combo box to choose yes or no
9714
     *
9715
     * @param string    $htmlname       Name of html select field
9716
     * @param string    $value          Pre-selected value
9717
     * @param int       $option         0 return yes/no, 1 return 1/0
9718
     * @param bool      $disabled       true or false
9719
     * @param int       $useempty       1=Add empty line
9720
     * @param int       $addjscombo     1=Add js beautifier on combo box
9721
     * @param string    $morecss        More CSS
9722
     * @param string    $labelyes       Label for Yes
9723
     * @param string    $labelno        Label for No
9724
     * @return    string                See option
9725
     */
9726
    public function selectyesno($htmlname, $value = '', $option = 0, $disabled = false, $useempty = 0, $addjscombo = 0, $morecss = '', $labelyes = 'Yes', $labelno = 'No')
9727
    {
9728
        global $langs;
9729
9730
        $yes = "yes";
9731
        $no = "no";
9732
        if ($option) {
9733
            $yes = "1";
9734
            $no = "0";
9735
        }
9736
9737
        $disabled = ($disabled ? ' disabled' : '');
9738
9739
        $resultyesno = '<select class="flat width75' . ($morecss ? ' ' . $morecss : '') . '" id="' . $htmlname . '" name="' . $htmlname . '"' . $disabled . '>' . "\n";
9740
        if ($useempty) {
9741
            $resultyesno .= '<option value="-1"' . (($value < 0) ? ' selected' : '') . '>&nbsp;</option>' . "\n";
9742
        }
9743
        if (("$value" == 'yes') || ($value == 1)) {
9744
            $resultyesno .= '<option value="' . $yes . '" selected>' . $langs->trans($labelyes) . '</option>' . "\n";
9745
            $resultyesno .= '<option value="' . $no . '">' . $langs->trans($labelno) . '</option>' . "\n";
9746
        } else {
9747
            $selected = (($useempty && $value != '0' && $value != 'no') ? '' : ' selected');
9748
            $resultyesno .= '<option value="' . $yes . '">' . $langs->trans($labelyes) . '</option>' . "\n";
9749
            $resultyesno .= '<option value="' . $no . '"' . $selected . '>' . $langs->trans($labelno) . '</option>' . "\n";
9750
        }
9751
        $resultyesno .= '</select>' . "\n";
9752
9753
        if ($addjscombo) {
9754
            $resultyesno .= ajax_combobox($htmlname, array(), 0, 0, 'resolve', ($useempty < 0 ? (string) $useempty : '-1'), $morecss);
9755
        }
9756
9757
        return $resultyesno;
9758
    }
9759
9760
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
9761
9762
    /**
9763
     *  Return list of export templates
9764
     *
9765
     * @param string $selected Id modele pre-selectionne
9766
     * @param string $htmlname Name of HTML select
9767
     * @param string $type Type of searched templates
9768
     * @param int $useempty Affiche valeur vide dans liste
9769
     * @return    void
9770
     */
9771
    public function select_export_model($selected = '', $htmlname = 'exportmodelid', $type = '', $useempty = 0)
9772
    {
9773
		// phpcs:enable
9774
        $sql = "SELECT rowid, label";
9775
        $sql .= " FROM " . $this->db->prefix() . "export_model";
9776
        $sql .= " WHERE type = '" . $this->db->escape($type) . "'";
9777
        $sql .= " ORDER BY rowid";
9778
        $result = $this->db->query($sql);
9779
        if ($result) {
9780
            print '<select class="flat" id="select_' . $htmlname . '" name="' . $htmlname . '">';
9781
            if ($useempty) {
9782
                print '<option value="-1">&nbsp;</option>';
9783
            }
9784
9785
            $num = $this->db->num_rows($result);
9786
            $i = 0;
9787
            while ($i < $num) {
9788
                $obj = $this->db->fetch_object($result);
9789
                if ($selected == $obj->rowid) {
9790
                    print '<option value="' . $obj->rowid . '" selected>';
9791
                } else {
9792
                    print '<option value="' . $obj->rowid . '">';
9793
                }
9794
                print $obj->label;
9795
                print '</option>';
9796
                $i++;
9797
            }
9798
            print "</select>";
9799
        } else {
9800
            dol_print_error($this->db);
9801
        }
9802
    }
9803
9804
    /**
9805
     * Return a HTML area with the reference of object and a navigation bar for a business object
9806
     * Note: To complete search with a particular filter on select, you can set $object->next_prev_filter set to define SQL criteria.
9807
     *
9808
     * @param CommonObject  $object         Object to show.
9809
     * @param string    $paramid        Name of parameter to use to name the id into the URL next/previous link.
9810
     * @param string    $morehtml       More html content to output just before the nav bar.
9811
     * @param int<0,1>  $shownav        Show Condition (navigation is shown if value is 1).
9812
     * @param string    $fieldid        Name of field id into database to use for select next and previous (we make the select max and min on this field compared to $object->ref). Use 'none' to disable next/prev.
9813
     * @param string    $fieldref       Name of field ref of object (object->ref) to show or 'none' to not show ref.
9814
     * @param string    $morehtmlref    More html to show after ref.
9815
     * @param string    $moreparam      More param to add in nav link url. Must start with '&...'.
9816
     * @param int<0,1>  $nodbprefix     Do not include DB prefix to forge table name.
9817
     * @param string    $morehtmlleft   More html code to show before ref.
9818
     * @param string    $morehtmlstatus More html code to show under navigation arrows (status place).
9819
     * @param string    $morehtmlright  More html code to show after ref.
9820
     * @return string                   Portion HTML with ref + navigation buttons
9821
     */
9822
    public function showrefnav($object, $paramid, $morehtml = '', $shownav = 1, $fieldid = 'rowid', $fieldref = 'ref', $morehtmlref = '', $moreparam = '', $nodbprefix = 0, $morehtmlleft = '', $morehtmlstatus = '', $morehtmlright = '')
9823
    {
9824
        global $conf, $langs, $hookmanager, $extralanguages;
9825
9826
        $ret = '';
9827
        if (empty($fieldid)) {
9828
            $fieldid = 'rowid';
9829
        }
9830
        if (empty($fieldref)) {
9831
            $fieldref = 'ref';
9832
        }
9833
9834
        // Preparing gender's display if there is one
9835
        $addgendertxt = '';
9836
        if (property_exists($object, 'gender') && !empty($object->gender)) {
9837
            $addgendertxt = ' ';
9838
            switch ($object->gender) {
9839
                case 'man':
9840
                    $addgendertxt .= '<i class="fas fa-mars"></i>';
9841
                    break;
9842
                case 'woman':
9843
                    $addgendertxt .= '<i class="fas fa-venus"></i>';
9844
                    break;
9845
                case 'other':
9846
                    $addgendertxt .= '<i class="fas fa-transgender"></i>';
9847
                    break;
9848
            }
9849
        }
9850
9851
        // Add where from hooks
9852
        if (is_object($hookmanager)) {
9853
            $parameters = array('showrefnav' => true);
9854
            $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $object); // Note that $action and $object may have been modified by hook
9855
            $object->next_prev_filter .= $hookmanager->resPrint;
9856
        }
9857
9858
        $previous_ref = $next_ref = '';
9859
        if ($shownav) {
9860
            //print "paramid=$paramid,morehtml=$morehtml,shownav=$shownav,$fieldid,$fieldref,$morehtmlref,$moreparam";
9861
            $object->load_previous_next_ref((isset($object->next_prev_filter) ? $object->next_prev_filter : ''), $fieldid, $nodbprefix);
9862
9863
            $navurl = $_SERVER["PHP_SELF"];
9864
            // Special case for project/task page
9865
            if ($paramid == 'project_ref') {
9866
                if (preg_match('/\/tasks\/(task|contact|note|document)\.php/', $navurl)) {     // TODO Remove this when nav with project_ref on task pages are ok
9867
                    $navurl = preg_replace('/\/tasks\/(task|contact|time|note|document)\.php/', '/tasks.php', $navurl);
9868
                    $paramid = 'ref';
9869
                }
9870
            }
9871
9872
            // accesskey is for Windows or Linux:  ALT + key for chrome, ALT + SHIFT + KEY for firefox
9873
            // accesskey is for Mac:               CTRL + key for all browsers
9874
            $stringforfirstkey = $langs->trans("KeyboardShortcut");
9875
            if ($conf->browser->name == 'chrome') {
9876
                $stringforfirstkey .= ' ALT +';
9877
            } elseif ($conf->browser->name == 'firefox') {
9878
                $stringforfirstkey .= ' ALT + SHIFT +';
9879
            } else {
9880
                $stringforfirstkey .= ' CTL +';
9881
            }
9882
9883
            $previous_ref = $object->ref_previous ? '<a accesskey="p" alt="' . dol_escape_htmltag($langs->trans("Previous")) . '" title="' . $stringforfirstkey . ' p" class="classfortooltip" href="' . $navurl . '?' . $paramid . '=' . urlencode($object->ref_previous) . $moreparam . '"><i class="fa fa-chevron-left"></i></a>' : '<span class="inactive"><i class="fa fa-chevron-left opacitymedium"></i></span>';
9884
            $next_ref = $object->ref_next ? '<a accesskey="n" alt="' . dol_escape_htmltag($langs->trans("Next")) . '" title="' . $stringforfirstkey . ' n" class="classfortooltip" href="' . $navurl . '?' . $paramid . '=' . urlencode($object->ref_next) . $moreparam . '"><i class="fa fa-chevron-right"></i></a>' : '<span class="inactive"><i class="fa fa-chevron-right opacitymedium"></i></span>';
9885
        }
9886
9887
        //print "xx".$previous_ref."x".$next_ref;
9888
        $ret .= '<!-- Start banner content --><div style="vertical-align: middle">';
9889
9890
        // Right part of banner
9891
        if ($morehtmlright) {
9892
            $ret .= '<div class="inline-block floatleft">' . $morehtmlright . '</div>';
9893
        }
9894
9895
        if ($previous_ref || $next_ref || $morehtml) {
9896
            $ret .= '<div class="pagination paginationref"><ul class="right">';
9897
        }
9898
        if ($morehtml && getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2) {
9899
            $ret .= '<!-- morehtml --><li class="noborder litext' . (($shownav && $previous_ref && $next_ref) ? ' clearbothonsmartphone' : '') . '">' . $morehtml . '</li>';
9900
        }
9901
        if ($shownav && ($previous_ref || $next_ref)) {
9902
            $ret .= '<li class="pagination">' . $previous_ref . '</li>';
9903
            $ret .= '<li class="pagination">' . $next_ref . '</li>';
9904
        }
9905
        if ($previous_ref || $next_ref || $morehtml) {
9906
            $ret .= '</ul></div>';
9907
        }
9908
9909
        // Status
9910
        $parameters = array('morehtmlstatus' => $morehtmlstatus);
9911
        $reshook = $hookmanager->executeHooks('moreHtmlStatus', $parameters, $object); // Note that $action and $object may have been modified by hook
9912
        if (empty($reshook)) {
9913
            $morehtmlstatus .= $hookmanager->resPrint;
9914
        } else {
9915
            $morehtmlstatus = $hookmanager->resPrint;
9916
        }
9917
        if ($morehtmlstatus) {
9918
            $ret .= '<div class="statusref">' . $morehtmlstatus . '</div>';
9919
        }
9920
9921
        $parameters = array();
9922
        $reshook = $hookmanager->executeHooks('moreHtmlRef', $parameters, $object); // Note that $action and $object may have been modified by hook
9923
        if (empty($reshook)) {
9924
            $morehtmlref .= $hookmanager->resPrint;
9925
        } elseif ($reshook > 0) {
9926
            $morehtmlref = $hookmanager->resPrint;
9927
        }
9928
9929
        // Left part of banner
9930
        if ($morehtmlleft) {
9931
            if ($conf->browser->layout == 'phone') {
9932
                $ret .= '<!-- morehtmlleft --><div class="floatleft">' . $morehtmlleft . '</div>';
9933
            } else {
9934
                $ret .= '<!-- morehtmlleft --><div class="inline-block floatleft">' . $morehtmlleft . '</div>';
9935
            }
9936
        }
9937
9938
        //if ($conf->browser->layout == 'phone') $ret.='<div class="clearboth"></div>';
9939
        $ret .= '<div class="inline-block floatleft valignmiddle maxwidth750 marginbottomonly refid' . (($shownav && ($previous_ref || $next_ref)) ? ' refidpadding' : '') . '">';
9940
9941
        // For thirdparty, contact, user, member, the ref is the id, so we show something else
9942
        if ($object->element == 'societe') {
9943
            $ret .= dol_htmlentities($object->name);
9944
9945
            // List of extra languages
9946
            $arrayoflangcode = array();
9947
            if (getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE')) {
9948
                $arrayoflangcode[] = getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE');
9949
            }
9950
9951
            if (is_array($arrayoflangcode) && count($arrayoflangcode)) {
9952
                if (!is_object($extralanguages)) {
9953
                    include_once DOL_DOCUMENT_ROOT . '/core/class/extralanguages.class.php';
9954
                    $extralanguages = new ExtraLanguages($this->db);
9955
                }
9956
                $extralanguages->fetch_name_extralanguages('societe');
9957
9958
                if (!empty($extralanguages->attributes['societe']['name'])) {
9959
                    $object->fetchValuesForExtraLanguages();
9960
9961
                    $htmltext = '';
9962
                    // If there is extra languages
9963
                    foreach ($arrayoflangcode as $extralangcode) {
9964
                        $htmltext .= picto_from_langcode($extralangcode, 'class="pictoforlang paddingright"');
9965
                        if ($object->array_languages['name'][$extralangcode]) {
9966
                            $htmltext .= $object->array_languages['name'][$extralangcode];
9967
                        } else {
9968
                            $htmltext .= '<span class="opacitymedium">' . $langs->trans("SwitchInEditModeToAddTranslation") . '</span>';
9969
                        }
9970
                    }
9971
                    $ret .= '<!-- Show translations of name -->' . "\n";
9972
                    $ret .= $this->textwithpicto('', $htmltext, -1, 'language', 'opacitymedium paddingleft');
9973
                }
9974
            }
9975
        } elseif ($object->element == 'member') {
9976
            '@phan-var-force Adherent $object';
9977
            $ret .= $object->ref . '<br>';
9978
            $fullname = $object->getFullName($langs);
9979
            if ($object->morphy == 'mor' && $object->societe) {
9980
                $ret .= dol_htmlentities($object->societe) . ((!empty($fullname) && $object->societe != $fullname) ? ' (' . dol_htmlentities($fullname) . $addgendertxt . ')' : '');
9981
            } else {
9982
                $ret .= dol_htmlentities($fullname) . $addgendertxt . ((!empty($object->societe) && $object->societe != $fullname) ? ' (' . dol_htmlentities($object->societe) . ')' : '');
9983
            }
9984
        } elseif (in_array($object->element, array('contact', 'user'))) {
9985
            $ret .= dol_htmlentities($object->getFullName($langs)) . $addgendertxt;
9986
        } elseif ($object->element == 'usergroup') {
9987
            $ret .= dol_htmlentities($object->name);
9988
        } elseif (in_array($object->element, array('action', 'agenda'))) {
9989
            '@phan-var-force ActionComm $object';
9990
            $ret .= $object->ref . '<br>' . $object->label;
9991
        } elseif (in_array($object->element, array('adherent_type'))) {
9992
            $ret .= $object->label;
9993
        } elseif ($object->element == 'ecm_directories') {
9994
            $ret .= '';
9995
        } elseif ($fieldref != 'none') {
9996
            $ret .= dol_htmlentities(!empty($object->$fieldref) ? $object->$fieldref : "");
9997
        }
9998
        if ($morehtmlref) {
9999
            // don't add a additional space, when "$morehtmlref" starts with a HTML div tag
10000
            if (substr($morehtmlref, 0, 4) != '<div') {
10001
                $ret .= ' ';
10002
            }
10003
10004
            $ret .= $morehtmlref;
10005
        }
10006
10007
        $ret .= '</div>';
10008
10009
        $ret .= '</div><!-- End banner content -->';
10010
10011
        return $ret;
10012
    }
10013
10014
10015
    /**
10016
     *  Return HTML code to output a barcode
10017
     *
10018
     * @param CommonObject $object Object containing data to retrieve file name
10019
     * @param int $width Width of photo
10020
     * @param string $morecss More CSS on img of barcode
10021
     * @return string                    HTML code to output barcode
10022
     */
10023
    public function showbarcode(&$object, $width = 100, $morecss = '')
10024
    {
10025
        global $conf;
10026
10027
        //Check if barcode is filled in the card
10028
        if (empty($object->barcode)) {
10029
            return '';
10030
        }
10031
10032
        // Complete object if not complete
10033
        if (empty($object->barcode_type_code) || empty($object->barcode_type_coder)) {
10034
            // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
10035
            $result = $object->fetch_barcode();
10036
            //Check if fetch_barcode() failed
10037
            if ($result < 1) {
10038
                return '<!-- ErrorFetchBarcode -->';
10039
            }
10040
        }
10041
10042
        // Barcode image  @phan-suppress-next-line PhanUndeclaredProperty
10043
        $url = constant('BASE_URL') . '/viewimage.php?modulepart=barcode&generator=' . urlencode($object->barcode_type_coder) . '&code=' . urlencode($object->barcode) . '&encoding=' . urlencode($object->barcode_type_code);
10044
        $out = '<!-- url barcode = ' . $url . ' -->';
10045
        $out .= '<img src="' . $url . '"' . ($morecss ? ' class="' . $morecss . '"' : '') . '>';
10046
10047
        return $out;
10048
    }
10049
10050
    /**
10051
     * Return HTML code to output a photo
10052
     *
10053
     * @param string    $modulepart                 Key to define module concerned ('societe', 'userphoto', 'memberphoto')
10054
     * @param Societe|Adherent|Contact|User|CommonObject    $object Object containing data to retrieve file name
10055
     * @param int       $width                      Width of photo
10056
     * @param int       $height                     Height of photo (auto if 0)
10057
     * @param int<0,1>  $caneditfield               Add edit fields
10058
     * @param string    $cssclass                   CSS name to use on img for photo
10059
     * @param string    $imagesize                  'mini', 'small' or '' (original)
10060
     * @param int<0,1>  $addlinktofullsize          Add link to fullsize image
10061
     * @param int<0,1>  $cache                      1=Accept to use image in cache
10062
     * @param string    $forcecapture               '', 'user' or 'environment'. Force parameter capture on HTML input file element to ask a smartphone to allow to open camera to take photo. Auto if ''.
10063
     * @param int<0,1>  $noexternsourceoverwrite    No overwrite image with extern source (like 'gravatar' or other module)
10064
     * @return string                               HTML code to output photo
10065
     * @see getImagePublicURLOfObject()
10066
     */
10067
    public static function showphoto($modulepart, $object, $width = 100, $height = 0, $caneditfield = 0, $cssclass = 'photowithmargin', $imagesize = '', $addlinktofullsize = 1, $cache = 0, $forcecapture = '', $noexternsourceoverwrite = 0)
10068
    {
10069
        global $conf, $langs;
10070
10071
        $entity = (empty($object->entity) ? $conf->entity : $object->entity);
10072
        $id = (empty($object->id) ? $object->rowid : $object->id);  // @phan-suppress-current-line PhanUndeclaredProperty (->rowid)
10073
10074
        $dir = '';
10075
        $file = '';
10076
        $originalfile = '';
10077
        $altfile = '';
10078
        $email = '';
10079
        $capture = '';
10080
        if ($modulepart == 'societe') {
10081
            $dir = $conf->societe->multidir_output[$entity];
10082
            if (!empty($object->logo)) {
10083
                if (dolIsAllowedForPreview($object->logo)) {
10084
                    if ((string) $imagesize == 'mini') {
10085
                        $file = get_exdir(0, 0, 0, 0, $object, 'thirdparty') . 'logos/' . getImageFileNameForSize($object->logo, '_mini'); // getImageFileNameForSize include the thumbs
10086
                    } elseif ((string) $imagesize == 'small') {
10087
                        $file = get_exdir(0, 0, 0, 0, $object, 'thirdparty') . 'logos/' . getImageFileNameForSize($object->logo, '_small');
10088
                    } else {
10089
                        $file = get_exdir(0, 0, 0, 0, $object, 'thirdparty') . 'logos/' . $object->logo;
10090
                    }
10091
                    $originalfile = get_exdir(0, 0, 0, 0, $object, 'thirdparty') . 'logos/' . $object->logo;
10092
                }
10093
            }
10094
            $email = $object->email;
10095
        } elseif ($modulepart == 'contact') {
10096
            $dir = $conf->societe->multidir_output[$entity] . '/contact';
10097
            if (!empty($object->photo)) {
10098
                if (dolIsAllowedForPreview($object->photo)) {
10099
                    if ((string) $imagesize == 'mini') {
10100
                        $file = get_exdir(0, 0, 0, 0, $object, 'contact') . 'photos/' . getImageFileNameForSize($object->photo, '_mini');
10101
                    } elseif ((string) $imagesize == 'small') {
10102
                        $file = get_exdir(0, 0, 0, 0, $object, 'contact') . 'photos/' . getImageFileNameForSize($object->photo, '_small');
10103
                    } else {
10104
                        $file = get_exdir(0, 0, 0, 0, $object, 'contact') . 'photos/' . $object->photo;
10105
                    }
10106
                    $originalfile = get_exdir(0, 0, 0, 0, $object, 'contact') . 'photos/' . $object->photo;
10107
                }
10108
            }
10109
            $email = $object->email;
10110
            $capture = 'user';
10111
        } elseif ($modulepart == 'userphoto') {
10112
            $dir = $conf->user->dir_output;
10113
            if (!empty($object->photo)) {
10114
                if (dolIsAllowedForPreview($object->photo)) {
10115
                    if ((string) $imagesize == 'mini') {
10116
                        $file = get_exdir(0, 0, 0, 0, $object, 'user') . 'photos/' . getImageFileNameForSize($object->photo, '_mini');
10117
                    } elseif ((string) $imagesize == 'small') {
10118
                        $file = get_exdir(0, 0, 0, 0, $object, 'user') . 'photos/' . getImageFileNameForSize($object->photo, '_small');
10119
                    } else {
10120
                        $file = get_exdir(0, 0, 0, 0, $object, 'user') . 'photos/' . $object->photo;
10121
                    }
10122
                    $originalfile = get_exdir(0, 0, 0, 0, $object, 'user') . 'photos/' . $object->photo;
10123
                }
10124
            }
10125
            if (getDolGlobalString('MAIN_OLD_IMAGE_LINKS')) {
10126
                $altfile = $object->id . ".jpg"; // For backward compatibility
10127
            }
10128
            $email = $object->email;
10129
            $capture = 'user';
10130
        } elseif ($modulepart == 'memberphoto') {
10131
            $dir = $conf->adherent->dir_output;
10132
            if (!empty($object->photo)) {
10133
                if (dolIsAllowedForPreview($object->photo)) {
10134
                    if ((string) $imagesize == 'mini') {
10135
                        $file = get_exdir(0, 0, 0, 0, $object, 'member') . 'photos/' . getImageFileNameForSize($object->photo, '_mini');
10136
                    } elseif ((string) $imagesize == 'small') {
10137
                        $file = get_exdir(0, 0, 0, 0, $object, 'member') . 'photos/' . getImageFileNameForSize($object->photo, '_small');
10138
                    } else {
10139
                        $file = get_exdir(0, 0, 0, 0, $object, 'member') . 'photos/' . $object->photo;
10140
                    }
10141
                    $originalfile = get_exdir(0, 0, 0, 0, $object, 'member') . 'photos/' . $object->photo;
10142
                }
10143
            }
10144
            if (getDolGlobalString('MAIN_OLD_IMAGE_LINKS')) {
10145
                $altfile = $object->id . ".jpg"; // For backward compatibility
10146
            }
10147
            $email = $object->email;
10148
            $capture = 'user';
10149
        } else {
10150
            // Generic case to show photos
10151
            // TODO Implement this method in previous objects so we can always use this generic method.
10152
            if ($modulepart != "unknown" && method_exists($object, 'getDataToShowPhoto')) {
10153
                $tmpdata = $object->getDataToShowPhoto($modulepart, $imagesize);
10154
10155
                $dir = $tmpdata['dir'];
10156
                $file = $tmpdata['file'];
10157
                $originalfile = $tmpdata['originalfile'];
10158
                $altfile = $tmpdata['altfile'];
10159
                $email = $tmpdata['email'];
10160
                $capture = $tmpdata['capture'];
10161
            }
10162
        }
10163
10164
        if ($forcecapture) {
10165
            $capture = $forcecapture;
10166
        }
10167
10168
        $ret = '';
10169
10170
        if ($dir) {
10171
            if ($file && file_exists($dir . "/" . $file)) {
10172
                if ($addlinktofullsize) {
10173
                    $urladvanced = getAdvancedPreviewUrl($modulepart, $originalfile, 0, '&entity=' . $entity);
10174
                    if ($urladvanced) {
10175
                        $ret .= '<a href="' . $urladvanced . '">';
10176
                    } else {
10177
                        $ret .= '<a href="' . constant('BASE_URL') . '/viewimage.php?modulepart=' . $modulepart . '&entity=' . $entity . '&file=' . urlencode($originalfile) . '&cache=' . $cache . '">';
10178
                    }
10179
                }
10180
                $ret .= '<img alt="" class="photo' . $modulepart . ($cssclass ? ' ' . $cssclass : '') . ' photologo' . (preg_replace('/[^a-z]/i', '_', $file)) . '" ' . ($width ? ' width="' . $width . '"' : '') . ($height ? ' height="' . $height . '"' : '') . ' src="' . constant('BASE_URL') . '/viewimage.php?modulepart=' . $modulepart . '&entity=' . $entity . '&file=' . urlencode($file) . '&cache=' . $cache . '">';
10181
                if ($addlinktofullsize) {
10182
                    $ret .= '</a>';
10183
                }
10184
            } elseif ($altfile && file_exists($dir . "/" . $altfile)) {
10185
                if ($addlinktofullsize) {
10186
                    $urladvanced = getAdvancedPreviewUrl($modulepart, $originalfile, 0, '&entity=' . $entity);
10187
                    if ($urladvanced) {
10188
                        $ret .= '<a href="' . $urladvanced . '">';
10189
                    } else {
10190
                        $ret .= '<a href="' . constant('BASE_URL') . '/viewimage.php?modulepart=' . $modulepart . '&entity=' . $entity . '&file=' . urlencode($originalfile) . '&cache=' . $cache . '">';
10191
                    }
10192
                }
10193
                $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="' . constant('BASE_URL') . '/viewimage.php?modulepart=' . $modulepart . '&entity=' . $entity . '&file=' . urlencode($altfile) . '&cache=' . $cache . '">';
10194
                if ($addlinktofullsize) {
10195
                    $ret .= '</a>';
10196
                }
10197
            } else {
10198
                $nophoto = '/public/theme/common/nophoto.png';
10199
                $defaultimg = 'identicon';        // For gravatar
10200
                if (in_array($modulepart, array('societe', 'userphoto', 'contact', 'memberphoto'))) {    // For modules that need a special image when photo not found
10201
                    if ($modulepart == 'societe' || ($modulepart == 'memberphoto' && !empty($object->morphy) && strpos($object->morphy, 'mor') !== false)) {
10202
                        $nophoto = 'company';
10203
                    } else {
10204
                        $nophoto = '/public/theme/common/user_anonymous.png';
10205
                        if (!empty($object->gender) && $object->gender == 'man') {
10206
                            $nophoto = '/public/theme/common/user_man.png';
10207
                        }
10208
                        if (!empty($object->gender) && $object->gender == 'woman') {
10209
                            $nophoto = '/public/theme/common/user_woman.png';
10210
                        }
10211
                    }
10212
                }
10213
10214
                if (isModEnabled('gravatar') && $email && empty($noexternsourceoverwrite)) {
10215
                    // see https://gravatar.com/site/implement/images/php/
10216
                    $ret .= '<!-- Put link to gravatar -->';
10217
                    $ret .= '<img class="photo' . $modulepart . ($cssclass ? ' ' . $cssclass : '') . '" alt="" title="' . $email . ' Gravatar avatar" ' . ($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
10218
                } else {
10219
                    if ($nophoto == 'company') {
10220
                        $ret .= '<div class="divforspanimg valignmiddle center photo' . $modulepart . ($cssclass ? ' ' . $cssclass : '') . '" alt="" ' . ($width ? ' width="' . $width . '"' : '') . ($height ? ' height="' . $height . '"' : '') . '>' . img_picto('', 'company') . '</div>';
10221
                        //$ret .= '<div class="difforspanimgright"></div>';
10222
                    } else {
10223
                        $ret .= '<img class="photo' . $modulepart . ($cssclass ? ' ' . $cssclass : '') . '" alt="" ' . ($width ? ' width="' . $width . '"' : '') . ($height ? ' height="' . $height . '"' : '') . ' src="' . constant('DOL_URL_ROOT') . $nophoto . '">';
10224
                    }
10225
                }
10226
            }
10227
10228
            if ($caneditfield) {
10229
                if ($object->photo) {
10230
                    $ret .= "<br>\n";
10231
                }
10232
                $ret .= '<table class="nobordernopadding centpercent">';
10233
                if ($object->photo) {
10234
                    $ret .= '<tr><td><input type="checkbox" class="flat photodelete" name="deletephoto" id="photodelete"> <label for="photodelete">' . $langs->trans("Delete") . '</label><br><br></td></tr>';
10235
                }
10236
                $ret .= '<tr><td class="tdoverflow">';
10237
                $maxfilesizearray = getMaxFileSizeArray();
10238
                $maxmin = $maxfilesizearray['maxmin'];
10239
                if ($maxmin > 0) {
10240
                    $ret .= '<input type="hidden" name="MAX_FILE_SIZE" value="' . ($maxmin * 1024) . '">';    // MAX_FILE_SIZE must precede the field type=file
10241
                }
10242
                $ret .= '<input type="file" class="flat maxwidth200onsmartphone" name="photo" id="photoinput" accept="image/*"' . ($capture ? ' capture="' . $capture . '"' : '') . '>';
10243
                $ret .= '</td></tr>';
10244
                $ret .= '</table>';
10245
            }
10246
        }
10247
10248
        return $ret;
10249
    }
10250
10251
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
10252
10253
    /**
10254
     * Return select list of groups
10255
     *
10256
     * @param int|object|object[]   $selected       Id group or group(s) preselected
10257
     * @param string                $htmlname       Field name in form
10258
     * @param int<0,1>              $show_empty     0=liste sans valeur nulle, 1=ajoute valeur inconnue
10259
     * @param string|int[]          $exclude        Array list of groups id to exclude
10260
     * @param int<0,1>              $disabled       If select list must be disabled
10261
     * @param string|int[]          $include        Array list of groups id to include
10262
     * @param int[]                 $enableonly     Array list of groups id to be enabled. All other must be disabled
10263
     * @param string                $force_entity   '0' or Ids of environment to force
10264
     * @param bool                  $multiple       add [] in the name of element and add 'multiple' attribute (not working with ajax_autocompleter)
10265
     * @param string                $morecss        More css to add to html component
10266
     * @return  string                              HTML Componont to select a group
10267
     * @see select_dolusers()
10268
     */
10269
    public function select_dolgroups($selected = 0, $htmlname = 'groupid', $show_empty = 0, $exclude = '', $disabled = 0, $include = '', $enableonly = array(), $force_entity = '0', $multiple = false, $morecss = 'minwidth200')
10270
    {
10271
		// phpcs:enable
10272
        global $conf, $user, $langs;
10273
10274
        // Allow excluding groups
10275
        $excludeGroups = null;
10276
        if (is_array($exclude)) {
10277
            $excludeGroups = implode(",", $exclude);
10278
        }
10279
        // Allow including groups
10280
        $includeGroups = null;
10281
        if (is_array($include)) {
10282
            $includeGroups = implode(",", $include);
10283
        }
10284
10285
        if (!is_array($selected)) {
10286
            $selected = array($selected);
10287
        }
10288
10289
        $out = '';
10290
10291
        // Build sql to search groups
10292
        $sql = "SELECT ug.rowid, ug.nom as name";
10293
        if (isModEnabled('multicompany') && $conf->entity == 1 && $user->admin && !$user->entity) {
10294
            $sql .= ", e.label";
10295
        }
10296
        $sql .= " FROM " . $this->db->prefix() . "usergroup as ug ";
10297
        if (isModEnabled('multicompany') && $conf->entity == 1 && $user->admin && !$user->entity) {
10298
            $sql .= " LEFT JOIN " . $this->db->prefix() . "entity as e ON e.rowid=ug.entity";
10299
            if ($force_entity) {
10300
                $sql .= " WHERE ug.entity IN (0, " . $force_entity . ")";
10301
            } else {
10302
                $sql .= " WHERE ug.entity IS NOT NULL";
10303
            }
10304
        } else {
10305
            $sql .= " WHERE ug.entity IN (0, " . $conf->entity . ")";
10306
        }
10307
        if (is_array($exclude) && $excludeGroups) {
10308
            $sql .= " AND ug.rowid NOT IN (" . $this->db->sanitize($excludeGroups) . ")";
10309
        }
10310
        if (is_array($include) && $includeGroups) {
10311
            $sql .= " AND ug.rowid IN (" . $this->db->sanitize($includeGroups) . ")";
10312
        }
10313
        $sql .= " ORDER BY ug.nom ASC";
10314
10315
        dol_syslog(get_class($this) . "::select_dolgroups", LOG_DEBUG);
10316
        $resql = $this->db->query($sql);
10317
        if ($resql) {
10318
            // Enhance with select2
10319
            include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
10320
10321
            $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" id="' . $htmlname . '" name="' . $htmlname . ($multiple ? '[]' : '') . '" ' . ($multiple ? 'multiple' : '') . ' ' . ($disabled ? ' disabled' : '') . '>';
10322
10323
            $num = $this->db->num_rows($resql);
10324
            $i = 0;
10325
            if ($num) {
10326
                if ($show_empty && !$multiple) {
10327
                    $out .= '<option value="-1"' . (in_array(-1, $selected) ? ' selected' : '') . '>&nbsp;</option>' . "\n";
10328
                }
10329
10330
                while ($i < $num) {
10331
                    $obj = $this->db->fetch_object($resql);
10332
                    $disableline = 0;
10333
                    if (is_array($enableonly) && count($enableonly) && !in_array($obj->rowid, $enableonly)) {
10334
                        $disableline = 1;
10335
                    }
10336
10337
                    $label = $obj->name;
10338
                    $labelhtml = $obj->name;
10339
                    if (isModEnabled('multicompany') && !getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE') && $conf->entity == 1) {
10340
                        $label .= " (" . $obj->label . ")";
10341
                        $labelhtml .= ' <span class="opacitymedium">(' . $obj->label . ')</span>';
10342
                    }
10343
10344
                    $out .= '<option value="' . $obj->rowid . '"';
10345
                    if ($disableline) {
10346
                        $out .= ' disabled';
10347
                    }
10348
                    if (
10349
                        (isset($selected[0]) && is_object($selected[0]) && $selected[0]->id == $obj->rowid)
10350
                        || ((!isset($selected[0]) || !is_object($selected[0])) && !empty($selected) && in_array($obj->rowid, $selected))
10351
                    ) {
10352
                        $out .= ' selected';
10353
                    }
10354
                    $out .= ' data-html="' . dol_escape_htmltag($labelhtml) . '"';
10355
                    $out .= '>';
10356
                    $out .= $label;
10357
                    $out .= '</option>';
10358
                    $i++;
10359
                }
10360
            } else {
10361
                if ($show_empty) {
10362
                    $out .= '<option value="-1"' . (in_array(-1, $selected) ? ' selected' : '') . '></option>' . "\n";
10363
                }
10364
                $out .= '<option value="" disabled>' . $langs->trans("NoUserGroupDefined") . '</option>';
10365
            }
10366
            $out .= '</select>';
10367
10368
            $out .= ajax_combobox($htmlname);
10369
        } else {
10370
            dol_print_error($this->db);
10371
        }
10372
10373
        return $out;
10374
    }
10375
10376
10377
    /**
10378
     *    Return HTML to show the search and clear search button
10379
     *
10380
     * @param string $pos Position of colon on the list. Value 'left' or 'right'
10381
     * @return    string
10382
     */
10383
    public function showFilterButtons($pos = '')
10384
    {
10385
        $out = '<div class="nowraponall">';
10386
        $out .= '<button type="submit" class="liste_titre button_search reposition" name="button_search_x" value="x"><span class="fas fa-search"></span></button>';
10387
        $out .= '<button type="submit" class="liste_titre button_removefilter reposition" name="button_removefilter_x" value="x"><span class="fas fa-times"></span></button>';
10388
        $out .= '</div>';
10389
10390
        return $out;
10391
    }
10392
10393
    /**
10394
     *    Return HTML to show the search and clear search button
10395
     *
10396
     * @param string $cssclass CSS class
10397
     * @param int $calljsfunction 0=default. 1=call function initCheckForSelect() after changing status of checkboxes
10398
     * @param string $massactionname Mass action button name that will launch an action on the selected items
10399
     * @return    string
10400
     */
10401
    public function showCheckAddButtons($cssclass = 'checkforaction', $calljsfunction = 0, $massactionname = "massaction")
10402
    {
10403
        global $conf;
10404
10405
        $out = '';
10406
10407
        if (!empty($conf->use_javascript_ajax)) {
10408
            $out .= '<div class="inline-block checkallactions"><input type="checkbox" id="' . $cssclass . 's" name="' . $cssclass . 's" class="checkallactions"></div>';
10409
        }
10410
        $out .= '<script nonce="' . getNonce() . '">
10411
            $(document).ready(function() {
10412
                $("#' . $cssclass . 's").click(function() {
10413
                    if($(this).is(\':checked\')){
10414
                        console.log("We check all ' . $cssclass . ' and trigger the change method");
10415
                		$(".' . $cssclass . '").prop(\'checked\', true).trigger(\'change\');
10416
                    }
10417
                    else
10418
                    {
10419
                        console.log("We uncheck all");
10420
                		$(".' . $cssclass . '").prop(\'checked\', false).trigger(\'change\');
10421
                    }' . "\n";
10422
        if ($calljsfunction) {
10423
            $out .= 'if (typeof initCheckForSelect == \'function\') { initCheckForSelect(0, "' . $massactionname . '", "' . $cssclass . '"); } else { console.log("No function initCheckForSelect found. Call won\'t be done."); }';
10424
        }
10425
        $out .= '         });
10426
        	        $(".' . $cssclass . '").change(function() {
10427
					$(this).closest("tr").toggleClass("highlight", this.checked);
10428
				});
10429
		 	});
10430
    	</script>';
10431
10432
        return $out;
10433
    }
10434
10435
    /**
10436
     *    Return HTML to show the search and clear search button
10437
     *
10438
     * @param int $addcheckuncheckall Add the check all/uncheck all checkbox (use javascript) and code to manage this
10439
     * @param string $cssclass CSS class
10440
     * @param int $calljsfunction 0=default. 1=call function initCheckForSelect() after changing status of checkboxes
10441
     * @param string $massactionname Mass action name
10442
     * @return    string
10443
     */
10444
    public function showFilterAndCheckAddButtons($addcheckuncheckall = 0, $cssclass = 'checkforaction', $calljsfunction = 0, $massactionname = "massaction")
10445
    {
10446
        $out = $this->showFilterButtons();
10447
        if ($addcheckuncheckall) {
10448
            $out .= $this->showCheckAddButtons($cssclass, $calljsfunction, $massactionname);
10449
        }
10450
        return $out;
10451
    }
10452
10453
    /**
10454
     * Return HTML to show the select of expense categories
10455
     *
10456
     * @param string    $selected       preselected category
10457
     * @param string    $htmlname       name of HTML select list
10458
     * @param int<0,1>  $useempty       1=Add empty line
10459
     * @param int[]     $excludeid      id to exclude
10460
     * @param string    $target         htmlname of target select to bind event
10461
     * @param int       $default_selected default category to select if fk_c_type_fees change = EX_KME
10462
     * @param array<string,int|string>  $params param to give
10463
     * @param int<0,1>  $info_admin     Show the tooltip help picto to setup list
10464
     * @return    string
10465
     */
10466
    public function selectExpenseCategories($selected = '', $htmlname = 'fk_c_exp_tax_cat', $useempty = 0, $excludeid = array(), $target = '', $default_selected = 0, $params = array(), $info_admin = 1)
10467
    {
10468
        global $langs, $user;
10469
10470
        $out = '';
10471
        $sql = "SELECT rowid, label FROM " . $this->db->prefix() . "c_exp_tax_cat WHERE active = 1";
10472
        $sql .= " AND entity IN (0," . getEntity('exp_tax_cat') . ")";
10473
        if (!empty($excludeid)) {
10474
            $sql .= " AND rowid NOT IN (" . $this->db->sanitize(implode(',', $excludeid)) . ")";
10475
        }
10476
        $sql .= " ORDER BY label";
10477
10478
        $resql = $this->db->query($sql);
10479
        if ($resql) {
10480
            $out = '<select id="select_' . $htmlname . '" name="' . $htmlname . '" class="' . $htmlname . ' flat minwidth75imp maxwidth200">';
10481
            if ($useempty) {
10482
                $out .= '<option value="0">&nbsp;</option>';
10483
            }
10484
10485
            while ($obj = $this->db->fetch_object($resql)) {
10486
                $out .= '<option ' . ($selected == $obj->rowid ? 'selected="selected"' : '') . ' value="' . $obj->rowid . '">' . $langs->trans($obj->label) . '</option>';
10487
            }
10488
            $out .= '</select>';
10489
            $out .= ajax_combobox('select_' . $htmlname);
10490
10491
            if (!empty($htmlname) && $user->admin && $info_admin) {
10492
                $out .= ' ' . info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
10493
            }
10494
10495
            if (!empty($target)) {
10496
                $sql = "SELECT c.id FROM " . $this->db->prefix() . "c_type_fees as c WHERE c.code = 'EX_KME' AND c.active = 1";
10497
                $resql = $this->db->query($sql);
10498
                if ($resql) {
10499
                    if ($this->db->num_rows($resql) > 0) {
10500
                        $obj = $this->db->fetch_object($resql);
10501
                        $out .= '<script nonce="' . getNonce() . '">
10502
							$(function() {
10503
								$("select[name=' . $target . ']").on("change", function() {
10504
									var current_val = $(this).val();
10505
									if (current_val == ' . $obj->id . ') {';
10506
                        if (!empty($default_selected) || !empty($selected)) {
10507
                            $out .= '$("select[name=' . $htmlname . ']").val("' . ($default_selected > 0 ? $default_selected : $selected) . '");';
10508
                        }
10509
10510
                        $out .= '
10511
										$("select[name=' . $htmlname . ']").change();
10512
									}
10513
								});
10514
10515
								$("select[name=' . $htmlname . ']").change(function() {
10516
10517
									if ($("select[name=' . $target . ']").val() == ' . $obj->id . ') {
10518
										// get price of kilometer to fill the unit price
10519
										$.ajax({
10520
											method: "POST",
10521
											dataType: "json",
10522
											data: { fk_c_exp_tax_cat: $(this).val(), token: \'' . currentToken() . '\' },
10523
											url: "' . (constant('BASE_URL') . '/expensereport/ajax/ajaxik.php?' . implode('&', $params)) . '",
10524
										}).done(function( data, textStatus, jqXHR ) {
10525
											console.log(data);
10526
											if (typeof data.up != "undefined") {
10527
												$("input[name=value_unit]").val(data.up);
10528
												$("select[name=' . $htmlname . ']").attr("title", data.title);
10529
											} else {
10530
												$("input[name=value_unit]").val("");
10531
												$("select[name=' . $htmlname . ']").attr("title", "");
10532
											}
10533
										});
10534
									}
10535
								});
10536
							});
10537
						</script>';
10538
                    }
10539
                }
10540
            }
10541
        } else {
10542
            dol_print_error($this->db);
10543
        }
10544
10545
        return $out;
10546
    }
10547
10548
    /**
10549
     * Return HTML to show the select ranges of expense range
10550
     *
10551
     * @param string $selected preselected category
10552
     * @param string $htmlname name of HTML select list
10553
     * @param integer $useempty 1=Add empty line
10554
     * @return    string
10555
     */
10556
    public function selectExpenseRanges($selected = '', $htmlname = 'fk_range', $useempty = 0)
10557
    {
10558
        global $conf, $langs;
10559
10560
        $out = '';
10561
        $sql = "SELECT rowid, range_ik FROM " . $this->db->prefix() . "c_exp_tax_range";
10562
        $sql .= " WHERE entity = " . $conf->entity . " AND active = 1";
10563
10564
        $resql = $this->db->query($sql);
10565
        if ($resql) {
10566
            $out = '<select id="select_' . $htmlname . '" name="' . $htmlname . '" class="' . $htmlname . ' flat minwidth75imp">';
10567
            if ($useempty) {
10568
                $out .= '<option value="0"></option>';
10569
            }
10570
10571
            while ($obj = $this->db->fetch_object($resql)) {
10572
                $out .= '<option ' . ($selected == $obj->rowid ? 'selected="selected"' : '') . ' value="' . $obj->rowid . '">' . price($obj->range_ik, 0, $langs, 1, 0) . '</option>';
10573
            }
10574
            $out .= '</select>';
10575
        } else {
10576
            dol_print_error($this->db);
10577
        }
10578
10579
        return $out;
10580
    }
10581
10582
    /**
10583
     * Return HTML to show a select of expense
10584
     *
10585
     * @param string $selected preselected category
10586
     * @param string $htmlname name of HTML select list
10587
     * @param integer $useempty 1=Add empty choice
10588
     * @param integer $allchoice 1=Add all choice
10589
     * @param integer $useid 0=use 'code' as key, 1=use 'id' as key
10590
     * @return    string
10591
     */
10592
    public function selectExpense($selected = '', $htmlname = 'fk_c_type_fees', $useempty = 0, $allchoice = 1, $useid = 0)
10593
    {
10594
        global $langs;
10595
10596
        $out = '';
10597
        $sql = "SELECT id, code, label";
10598
        $sql .= " FROM " . $this->db->prefix() . "c_type_fees";
10599
        $sql .= " WHERE active = 1";
10600
10601
        $resql = $this->db->query($sql);
10602
        if ($resql) {
10603
            $out = '<select id="select_' . $htmlname . '" name="' . $htmlname . '" class="' . $htmlname . ' flat minwidth75imp">';
10604
            if ($useempty) {
10605
                $out .= '<option value="0"></option>';
10606
            }
10607
            if ($allchoice) {
10608
                $out .= '<option value="-1">' . $langs->trans('AllExpenseReport') . '</option>';
10609
            }
10610
10611
            $field = 'code';
10612
            if ($useid) {
10613
                $field = 'id';
10614
            }
10615
10616
            while ($obj = $this->db->fetch_object($resql)) {
10617
                $key = $langs->trans($obj->code);
10618
                $out .= '<option ' . ($selected == $obj->{$field} ? 'selected="selected"' : '') . ' value="' . $obj->{$field} . '">' . ($key != $obj->code ? $key : $obj->label) . '</option>';
10619
            }
10620
            $out .= '</select>';
10621
10622
            $out .= ajax_combobox('select_' . $htmlname);
10623
        } else {
10624
            dol_print_error($this->db);
10625
        }
10626
10627
        return $out;
10628
    }
10629
10630
    /**
10631
     *  Output a combo list with invoices qualified for a third party
10632
     *
10633
     * @param int $socid Id third party (-1=all, 0=only projects not linked to a third party, id=projects not linked or linked to third party id)
10634
     * @param string $selected Id invoice preselected
10635
     * @param string $htmlname Name of HTML select
10636
     * @param int $maxlength Maximum length of label
10637
     * @param int $option_only Return only html options lines without the select tag
10638
     * @param string $show_empty Add an empty line ('1' or string to show for empty line)
10639
     * @param int $discard_closed Discard closed projects (0=Keep,1=hide completely,2=Disable)
10640
     * @param int $forcefocus Force focus on field (works with javascript only)
10641
     * @param int $disabled Disabled
10642
     * @param string $morecss More css added to the select component
10643
     * @param string $projectsListId ''=Automatic filter on project allowed. List of id=Filter on project ids.
10644
     * @param string $showproject 'all' = Show project info, ''=Hide project info
10645
     * @param User $usertofilter User object to use for filtering
10646
     * @return string            HTML Select Invoice
10647
     */
10648
    public function selectInvoice($socid = -1, $selected = '', $htmlname = 'invoiceid', $maxlength = 24, $option_only = 0, $show_empty = '1', $discard_closed = 0, $forcefocus = 0, $disabled = 0, $morecss = 'maxwidth500', $projectsListId = '', $showproject = 'all', $usertofilter = null)
10649
    {
10650
        global $user, $conf, $langs;
10651
10652
        require_once constant('DOL_DOCUMENT_ROOT') . '/projet/class/project.class.php';
10653
10654
        if (is_null($usertofilter)) {
10655
            $usertofilter = $user;
10656
        }
10657
10658
        $out = '';
10659
10660
        $hideunselectables = false;
10661
        if (getDolGlobalString('PROJECT_HIDE_UNSELECTABLES')) {
10662
            $hideunselectables = true;
10663
        }
10664
10665
        if (empty($projectsListId)) {
10666
            if (!$usertofilter->hasRight('projet', 'all', 'lire')) {
10667
                $projectstatic = new Project($this->db);
10668
                $projectsListId = $projectstatic->getProjectsAuthorizedForUser($usertofilter, 0, 1);
10669
            }
10670
        }
10671
10672
        // Search all projects
10673
        $sql = "SELECT f.rowid, f.ref as fref, 'nolabel' as flabel, p.rowid as pid, f.ref,
10674
            p.title, p.fk_soc, p.fk_statut, p.public,";
10675
        $sql .= ' s.nom as name';
10676
        $sql .= ' FROM ' . $this->db->prefix() . 'projet as p';
10677
        $sql .= ' LEFT JOIN ' . $this->db->prefix() . 'societe as s ON s.rowid = p.fk_soc,';
10678
        $sql .= ' ' . $this->db->prefix() . 'facture as f';
10679
        $sql .= " WHERE p.entity IN (" . getEntity('project') . ")";
10680
        $sql .= " AND f.fk_projet = p.rowid AND f.fk_statut=0"; //Brouillons seulement
10681
        //if ($projectsListId) $sql.= " AND p.rowid IN (".$this->db->sanitize($projectsListId).")";
10682
        //if ($socid == 0) $sql.= " AND (p.fk_soc=0 OR p.fk_soc IS NULL)";
10683
        //if ($socid > 0)  $sql.= " AND (p.fk_soc=".((int) $socid)." OR p.fk_soc IS NULL)";
10684
        $sql .= " ORDER BY p.ref, f.ref ASC";
10685
10686
        $resql = $this->db->query($sql);
10687
        if ($resql) {
10688
            // Use select2 selector
10689
            if (!empty($conf->use_javascript_ajax)) {
10690
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
10691
                $comboenhancement = ajax_combobox($htmlname, array(), 0, $forcefocus);
10692
                $out .= $comboenhancement;
10693
                $morecss = 'minwidth200imp maxwidth500';
10694
            }
10695
10696
            if (empty($option_only)) {
10697
                $out .= '<select class="valignmiddle flat' . ($morecss ? ' ' . $morecss : '') . '"' . ($disabled ? ' disabled="disabled"' : '') . ' id="' . $htmlname . '" name="' . $htmlname . '">';
10698
            }
10699
            if (!empty($show_empty)) {
10700
                $out .= '<option value="0" class="optiongrey">';
10701
                if (!is_numeric($show_empty)) {
10702
                    $out .= $show_empty;
10703
                } else {
10704
                    $out .= '&nbsp;';
10705
                }
10706
                $out .= '</option>';
10707
            }
10708
            $num = $this->db->num_rows($resql);
10709
            $i = 0;
10710
            if ($num) {
10711
                while ($i < $num) {
10712
                    $obj = $this->db->fetch_object($resql);
10713
                    // 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.
10714
                    if ($socid > 0 && (empty($obj->fk_soc) || $obj->fk_soc == $socid) && !$usertofilter->hasRight('societe', 'lire')) {
10715
                        // Do nothing
10716
                    } else {
10717
                        if ($discard_closed == 1 && $obj->fk_statut == Project::STATUS_CLOSED) {
10718
                            $i++;
10719
                            continue;
10720
                        }
10721
10722
                        $labeltoshow = '';
10723
10724
                        if ($showproject == 'all') {
10725
                            $labeltoshow .= dol_trunc($obj->ref, 18); // Invoice ref
10726
                            if ($obj->name) {
10727
                                $labeltoshow .= ' - ' . $obj->name; // Soc name
10728
                            }
10729
10730
                            $disabled = 0;
10731
                            if ($obj->fk_statut == Project::STATUS_DRAFT) {
10732
                                $disabled = 1;
10733
                                $labeltoshow .= ' - ' . $langs->trans("Draft");
10734
                            } elseif ($obj->fk_statut == Project::STATUS_CLOSED) {
10735
                                if ($discard_closed == 2) {
10736
                                    $disabled = 1;
10737
                                }
10738
                                $labeltoshow .= ' - ' . $langs->trans("Closed");
10739
                            } elseif ($socid > 0 && (!empty($obj->fk_soc) && $obj->fk_soc != $socid)) {
10740
                                $disabled = 1;
10741
                                $labeltoshow .= ' - ' . $langs->trans("LinkedToAnotherCompany");
10742
                            }
10743
                        }
10744
10745
                        if (!empty($selected) && $selected == $obj->rowid) {
10746
                            $out .= '<option value="' . $obj->rowid . '" selected';
10747
                            //if ($disabled) $out.=' disabled';                     // with select2, field can't be preselected if disabled
10748
                            $out .= '>' . $labeltoshow . '</option>';
10749
                        } else {
10750
                            if ($hideunselectables && $disabled && ($selected != $obj->rowid)) {
10751
                                $resultat = '';
10752
                            } else {
10753
                                $resultat = '<option value="' . $obj->rowid . '"';
10754
                                if ($disabled) {
10755
                                    $resultat .= ' disabled';
10756
                                }
10757
                                //if ($obj->public) $labeltoshow.=' ('.$langs->trans("Public").')';
10758
                                //else $labeltoshow.=' ('.$langs->trans("Private").')';
10759
                                $resultat .= '>';
10760
                                $resultat .= $labeltoshow;
10761
                                $resultat .= '</option>';
10762
                            }
10763
                            $out .= $resultat;
10764
                        }
10765
                    }
10766
                    $i++;
10767
                }
10768
            }
10769
            if (empty($option_only)) {
10770
                $out .= '</select>';
10771
            }
10772
10773
            $this->db->free($resql);
10774
10775
            return $out;
10776
        } else {
10777
            dol_print_error($this->db);
10778
            return '';
10779
        }
10780
    }
10781
10782
    /**
10783
     *  Output a combo list with invoices qualified for a third party
10784
     *
10785
     * @param string $selected Id invoice preselected
10786
     * @param string $htmlname Name of HTML select
10787
     * @param int $maxlength Maximum length of label
10788
     * @param int $option_only Return only html options lines without the select tag
10789
     * @param string $show_empty Add an empty line ('1' or string to show for empty line)
10790
     * @param int $forcefocus Force focus on field (works with javascript only)
10791
     * @param int $disabled Disabled
10792
     * @param string $morecss More css added to the select component
10793
     * @return int                    Nbr of project if OK, <0 if KO
10794
     */
10795
    public function selectInvoiceRec($selected = '', $htmlname = 'facrecid', $maxlength = 24, $option_only = 0, $show_empty = '1', $forcefocus = 0, $disabled = 0, $morecss = 'maxwidth500')
10796
    {
10797
        global $conf, $langs;
10798
10799
        $out = '';
10800
10801
        dol_syslog('FactureRec::fetch', LOG_DEBUG);
10802
10803
        $sql = 'SELECT f.rowid, f.entity, f.titre as title, f.suspended, f.fk_soc';
10804
        //$sql.= ', el.fk_source';
10805
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'facture_rec as f';
10806
        $sql .= " WHERE f.entity IN (" . getEntity('invoice') . ")";
10807
        $sql .= " ORDER BY f.titre ASC";
10808
10809
        $resql = $this->db->query($sql);
10810
        if ($resql) {
10811
            // Use select2 selector
10812
            if (!empty($conf->use_javascript_ajax)) {
10813
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
10814
                $comboenhancement = ajax_combobox($htmlname, array(), 0, $forcefocus);
10815
                $out .= $comboenhancement;
10816
                $morecss = 'minwidth200imp maxwidth500';
10817
            }
10818
10819
            if (empty($option_only)) {
10820
                $out .= '<select class="valignmiddle flat' . ($morecss ? ' ' . $morecss : '') . '"' . ($disabled ? ' disabled="disabled"' : '') . ' id="' . $htmlname . '" name="' . $htmlname . '">';
10821
            }
10822
            if (!empty($show_empty)) {
10823
                $out .= '<option value="0" class="optiongrey">';
10824
                if (!is_numeric($show_empty)) {
10825
                    $out .= $show_empty;
10826
                } else {
10827
                    $out .= '&nbsp;';
10828
                }
10829
                $out .= '</option>';
10830
            }
10831
            $num = $this->db->num_rows($resql);
10832
            if ($num) {
10833
                while ($obj = $this->db->fetch_object($resql)) {
10834
                    $labeltoshow = dol_trunc($obj->title, 18); // Invoice ref
10835
10836
                    $disabled = 0;
10837
                    if (!empty($obj->suspended)) {
10838
                        $disabled = 1;
10839
                        $labeltoshow .= ' - ' . $langs->trans("Closed");
10840
                    }
10841
10842
10843
                    if (!empty($selected) && $selected == $obj->rowid) {
10844
                        $out .= '<option value="' . $obj->rowid . '" selected';
10845
                        //if ($disabled) $out.=' disabled';                     // with select2, field can't be preselected if disabled
10846
                        $out .= '>' . $labeltoshow . '</option>';
10847
                    } else {
10848
                        if ($disabled && ($selected != $obj->rowid)) {
10849
                            $resultat = '';
10850
                        } else {
10851
                            $resultat = '<option value="' . $obj->rowid . '"';
10852
                            if ($disabled) {
10853
                                $resultat .= ' disabled';
10854
                            }
10855
                            $resultat .= '>';
10856
                            $resultat .= $labeltoshow;
10857
                            $resultat .= '</option>';
10858
                        }
10859
                        $out .= $resultat;
10860
                    }
10861
                }
10862
            }
10863
            if (empty($option_only)) {
10864
                $out .= '</select>';
10865
            }
10866
10867
            print $out;
10868
10869
            $this->db->free($resql);
10870
            return $num;
10871
        } else {
10872
            $this->errors[] = $this->db->lasterror;
10873
            return -1;
10874
        }
10875
    }
10876
10877
    /**
10878
     * Output the component to make advanced search criteries
10879
     *
10880
     * @param   array<array<string,array{type:string}>> $arrayofcriterias                   Array of available search criteria. Example: array($object->element => $object->fields, 'otherfamily' => otherarrayoffields, ...)
10881
     * @param   array<int,string>                       $search_component_params            Array of selected search criteria
10882
     * @param   string[]                                $arrayofinputfieldsalreadyoutput    Array of input fields already inform. The component will not generate a hidden input field if it is in this list.
10883
     * @param   string                                  $search_component_params_hidden     String with $search_component_params criteria
10884
     * @return  string                                                                      HTML component for advanced search
10885
     */
10886
    public function searchComponent($arrayofcriterias, $search_component_params, $arrayofinputfieldsalreadyoutput = array(), $search_component_params_hidden = '')
10887
    {
10888
        global $langs;
10889
10890
        if ($search_component_params_hidden != '' && !preg_match('/^\(.*\)$/', $search_component_params_hidden)) {    // If $search_component_params_hidden does not start and end with ()
10891
            $search_component_params_hidden = '(' . $search_component_params_hidden . ')';
10892
        }
10893
10894
        $ret = '';
10895
10896
        $ret .= '<div class="divadvancedsearchfieldcomp centpercent inline-block">';
10897
        $ret .= '<a href="#" class="dropdownsearch-toggle unsetcolor">';
10898
        $ret .= '<span class="fas fa-filter linkobject boxfilter paddingright pictofixedwidth" title="' . dol_escape_htmltag($langs->trans("Filters")) . '" id="idsubimgproductdistribution"></span>';
10899
        $ret .= '</a>';
10900
10901
        $ret .= '<div class="divadvancedsearchfieldcompinput inline-block minwidth500 maxwidth300onsmartphone">';
10902
10903
        // Show select fields as tags.
10904
        $ret .= '<div id="divsearch_component_params" name="divsearch_component_params" class="noborderbottom search_component_params inline-block valignmiddle">';
10905
10906
        if ($search_component_params_hidden) {
10907
            // Split the criteria on each AND
10908
            //var_dump($search_component_params_hidden);
10909
10910
            $arrayofandtags = dolForgeExplodeAnd($search_component_params_hidden);
10911
10912
            // $arrayofandtags is now array( '...' , '...', ...)
10913
            // Show each AND part
10914
            foreach ($arrayofandtags as $tmpkey => $tmpval) {
10915
                $errormessage = '';
10916
                $searchtags = forgeSQLFromUniversalSearchCriteria($tmpval, $errormessage, 1, 1);
10917
                if ($errormessage) {
10918
                    $this->error = 'ERROR in parsing search string: ' . $errormessage;
10919
                }
10920
                // Remove first and last parenthesis but only if first is the opening and last the closing of the same group
10921
                include_once DOL_DOCUMENT_ROOT . '/core/lib/functions2.lib.php';
10922
                $searchtags = removeGlobalParenthesis($searchtags);
10923
10924
                $ret .= '<span class="marginleftonlyshort valignmiddle tagsearch" data-ufilterid="' . ($tmpkey + 1) . '" data-ufilter="' . dol_escape_htmltag($tmpval) . '">';
10925
                $ret .= '<span class="tagsearchdelete select2-selection__choice__remove" data-ufilterid="' . ($tmpkey + 1) . '">x</span> ';
10926
                $ret .= dol_escape_htmltag($searchtags);
10927
                $ret .= '</span>';
10928
            }
10929
        }
10930
10931
        //$ret .= '<button type="submit" class="liste_titre button_search paddingleftonly" name="button_search_x" value="x"><span class="fa fa-search"></span></button>';
10932
10933
        //$ret .= search_component_params
10934
        //$texttoshow = '<div class="opacitymedium inline-block search_component_searchtext">'.$langs->trans("Search").'</div>';
10935
        //$ret .= '<div class="search_component inline-block valignmiddle">'.$texttoshow.'</div>';
10936
10937
        $show_search_component_params_hidden = 1;
10938
        if ($show_search_component_params_hidden) {
10939
            $ret .= '<input type="hidden" name="show_search_component_params_hidden" value="1">';
10940
        }
10941
        $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%')) -->";
10942
        $ret .= '<input type="hidden" id="search_component_params_hidden" name="search_component_params_hidden" value="' . dol_escape_htmltag($search_component_params_hidden) . '">';
10943
        // $ret .= "<!-- sql= ".forgeSQLFromUniversalSearchCriteria($search_component_params_hidden, $errormessage)." -->";
10944
10945
        // For compatibility with forms that show themself the search criteria in addition of this component, we output these fields
10946
        foreach ($arrayofcriterias as $criteria) {
10947
            foreach ($criteria as $criteriafamilykey => $criteriafamilyval) {
10948
                if (in_array('search_' . $criteriafamilykey, $arrayofinputfieldsalreadyoutput)) {
10949
                    continue;
10950
                }
10951
                if (in_array($criteriafamilykey, array('rowid', 'ref_ext', 'entity', 'extraparams'))) {
10952
                    continue;
10953
                }
10954
                if (in_array($criteriafamilyval['type'], array('date', 'datetime', 'timestamp'))) {
10955
                    $ret .= '<input type="hidden" name="search_' . $criteriafamilykey . '_start">';
10956
                    $ret .= '<input type="hidden" name="search_' . $criteriafamilykey . '_startyear">';
10957
                    $ret .= '<input type="hidden" name="search_' . $criteriafamilykey . '_startmonth">';
10958
                    $ret .= '<input type="hidden" name="search_' . $criteriafamilykey . '_startday">';
10959
                    $ret .= '<input type="hidden" name="search_' . $criteriafamilykey . '_end">';
10960
                    $ret .= '<input type="hidden" name="search_' . $criteriafamilykey . '_endyear">';
10961
                    $ret .= '<input type="hidden" name="search_' . $criteriafamilykey . '_endmonth">';
10962
                    $ret .= '<input type="hidden" name="search_' . $criteriafamilykey . '_endday">';
10963
                } else {
10964
                    $ret .= '<input type="hidden" name="search_' . $criteriafamilykey . '">';
10965
                }
10966
            }
10967
        }
10968
10969
        $ret .= '</div>';
10970
10971
        $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";
10972
        $ret .= '<input type="text" placeholder="' . $langs->trans("Filters") . '" id="search_component_params_input" name="search_component_params_input" class="noborderbottom search_component_input" value="">';
10973
10974
        $ret .= '</div>';
10975
        $ret .= '</div>';
10976
10977
        $ret .= '<script>
10978
		jQuery(".tagsearchdelete").click(function(e) {
10979
			var filterid = $(this).parents().attr("data-ufilterid");
10980
			console.log("We click to delete the criteria nb "+filterid);
10981
10982
			// Regenerate the search_component_params_hidden with all data-ufilter except the one to delete, and post the page
10983
			var newparamstring = \'\';
10984
			$(\'.tagsearch\').each(function(index, element) {
10985
				tmpfilterid = $(this).attr("data-ufilterid");
10986
				if (tmpfilterid != filterid) {
10987
					// We keep this criteria
10988
					if (newparamstring == \'\') {
10989
						newparamstring = $(this).attr("data-ufilter");
10990
					} else {
10991
						newparamstring = newparamstring + \' AND \' + $(this).attr("data-ufilter");
10992
					}
10993
				}
10994
			});
10995
			console.log("newparamstring = "+newparamstring);
10996
10997
			jQuery("#search_component_params_hidden").val(newparamstring);
10998
10999
			// We repost the form
11000
			$(this).closest(\'form\').submit();
11001
		});
11002
11003
		jQuery("#search_component_params_input").keydown(function(e) {
11004
			console.log("We press a key on the filter field that is "+jQuery("#search_component_params_input").val());
11005
			console.log(e.which);
11006
			if (jQuery("#search_component_params_input").val() == "" && e.which == 8) {
11007
				/* We click on back when the input field is already empty */
11008
			   	event.preventDefault();
11009
				jQuery("#divsearch_component_params .tagsearch").last().remove();
11010
				/* Regenerate content of search_component_params_hidden from remaining .tagsearch */
11011
				var s = "";
11012
				jQuery("#divsearch_component_params .tagsearch").each(function( index ) {
11013
					if (s != "") {
11014
						s = s + " AND ";
11015
					}
11016
					s = s + $(this).attr("data-ufilter");
11017
				});
11018
				console.log("New value for search_component_params_hidden = "+s);
11019
				jQuery("#search_component_params_hidden").val(s);
11020
			}
11021
		});
11022
11023
		</script>
11024
		';
11025
11026
        return $ret;
11027
    }
11028
11029
    /**
11030
     * selectModelMail
11031
     *
11032
     * @param   string      $prefix         Prefix
11033
     * @param   string      $modelType      Model type
11034
     * @param   int<0,1>    $default        1=Show also Default mail template
11035
     * @param   int<0,1>    $addjscombo     Add js combobox
11036
     * @return  string                      HTML select string
11037
     */
11038
    public function selectModelMail($prefix, $modelType = '', $default = 0, $addjscombo = 0)
11039
    {
11040
        global $langs, $user;
11041
11042
        $retstring = '';
11043
11044
        $TModels = array();
11045
11046
        include_once DOL_DOCUMENT_ROOT . '/core/class/html.formmail.class.php';
11047
        $formmail = new FormMail($this->db);
11048
        $result = $formmail->fetchAllEMailTemplate($modelType, $user, $langs);
11049
11050
        if ($default) {
11051
            $TModels[0] = $langs->trans('DefaultMailModel');
11052
        }
11053
        if ($result > 0) {
11054
            foreach ($formmail->lines_model as $model) {
11055
                $TModels[$model->id] = $model->label;
11056
            }
11057
        }
11058
11059
        $retstring .= '<select class="flat" id="select_' . $prefix . 'model_mail" name="' . $prefix . 'model_mail">';
11060
11061
        foreach ($TModels as $id_model => $label_model) {
11062
            $retstring .= '<option value="' . $id_model . '"';
11063
            $retstring .= ">" . $label_model . "</option>";
11064
        }
11065
11066
        $retstring .= "</select>";
11067
11068
        if ($addjscombo) {
11069
            $retstring .= ajax_combobox('select_' . $prefix . 'model_mail');
11070
        }
11071
11072
        return $retstring;
11073
    }
11074
11075
    /**
11076
     * Output the buttons to submit a creation/edit form
11077
     *
11078
     * @param   string  $save_label         Alternative label for save button
11079
     * @param   string  $cancel_label       Alternative label for cancel button
11080
     * @param   array<array{addclass?:string,name?:string,label_key?:string}> $morebuttons      Add additional buttons between save and cancel
11081
     * @param   bool    $withoutdiv         Option to remove enclosing centered div
11082
     * @param   string  $morecss            More CSS
11083
     * @param   string  $dol_openinpopup    If the button are shown in a context of a page shown inside a popup, we put here the string name of popup.
11084
     * @return  string                      Html code with the buttons
11085
     */
11086
    public function buttonsSaveCancel($save_label = 'Save', $cancel_label = 'Cancel', $morebuttons = array(), $withoutdiv = false, $morecss = '', $dol_openinpopup = '')
11087
    {
11088
        global $langs;
11089
11090
        $buttons = array();
11091
11092
        $save = array(
11093
            'name' => 'save',
11094
            'label_key' => $save_label,
11095
        );
11096
11097
        if ($save_label == 'Create' || $save_label == 'Add') {
11098
            $save['name'] = 'add';
11099
        } elseif ($save_label == 'Modify') {
11100
            $save['name'] = 'edit';
11101
        }
11102
11103
        $cancel = array(
11104
            'name' => 'cancel',
11105
            'label_key' => 'Cancel',
11106
        );
11107
11108
        !empty($save_label) ? $buttons[] = $save : '';
11109
11110
        if (!empty($morebuttons)) {
11111
            $buttons[] = $morebuttons;
11112
        }
11113
11114
        !empty($cancel_label) ? $buttons[] = $cancel : '';
11115
11116
        $retstring = $withoutdiv ? '' : '<div class="center">';
11117
11118
        foreach ($buttons as $button) {
11119
            $addclass = empty($button['addclass']) ? '' : $button['addclass'];
11120
            $retstring .= '<input type="submit" class="button button-' . $button['name'] . ($morecss ? ' ' . $morecss : '') . ' ' . $addclass . '" name="' . $button['name'] . '" value="' . dol_escape_htmltag($langs->trans($button['label_key'])) . '">';
11121
        }
11122
        $retstring .= $withoutdiv ? '' : '</div>';
11123
11124
        if ($dol_openinpopup) {
11125
            $retstring .= '<!-- buttons are shown into a $dol_openinpopup=' . $dol_openinpopup . ' context, so we enable the close of dialog on cancel -->' . "\n";
11126
            $retstring .= '<script nonce="' . getNonce() . '">';
11127
            $retstring .= 'jQuery(".button-cancel").click(function(e) {
11128
				e.preventDefault(); console.log(\'We click on cancel in iframe popup ' . $dol_openinpopup . '\');
11129
				window.parent.jQuery(\'#idfordialog' . $dol_openinpopup . '\').dialog(\'close\');
11130
				 });';
11131
            $retstring .= '</script>';
11132
        }
11133
11134
        return $retstring;
11135
    }
11136
11137
11138
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
11139
11140
    /**
11141
    * Load into cache list of invoice subtypes
11142
    *
11143
    * @return int             Nb of lines loaded, <0 if KO
11144
    */
11145
    public function load_cache_invoice_subtype()
11146
    {
11147
		// phpcs:enable
11148
        global $langs;
11149
11150
        $num = count($this->cache_invoice_subtype);
11151
        if ($num > 0) {
11152
            return 0; // Cache already loaded
11153
        }
11154
11155
        dol_syslog(__METHOD__, LOG_DEBUG);
11156
11157
        $sql = "SELECT rowid, code, label as label";
11158
        $sql .= " FROM " . MAIN_DB_PREFIX . 'c_invoice_subtype';
11159
        $sql .= " WHERE active = 1";
11160
11161
        $resql = $this->db->query($sql);
11162
        if ($resql) {
11163
            $num = $this->db->num_rows($resql);
11164
            $i = 0;
11165
            while ($i < $num) {
11166
                $obj = $this->db->fetch_object($resql);
11167
11168
                // If translation exists, we use it, otherwise we take the default wording
11169
                $label = ($langs->trans("InvoiceSubtype" . $obj->rowid) != "InvoiceSubtype" . $obj->rowid) ? $langs->trans("InvoiceSubtype" . $obj->rowid) : (($obj->label != '-') ? $obj->label : '');
11170
                $this->cache_invoice_subtype[$obj->rowid]['rowid'] = $obj->rowid;
11171
                $this->cache_invoice_subtype[$obj->rowid]['code'] = $obj->code;
11172
                $this->cache_invoice_subtype[$obj->rowid]['label'] = $label;
11173
                $i++;
11174
            }
11175
11176
            $this->cache_invoice_subtype = dol_sort_array($this->cache_invoice_subtype, 'code', 'asc', 0, 0, 1);
11177
11178
            return $num;
11179
        } else {
11180
            dol_print_error($this->db);
11181
            return -1;
11182
        }
11183
    }
11184
11185
11186
    /**
11187
    * Return list of invoice subtypes.
11188
    *
11189
    * @param int        $selected       Id of invoice subtype to preselect by default
11190
    * @param string     $htmlname       Select field name
11191
    * @param int<0,1>   $addempty       Add an empty entry
11192
    * @param int<0,1>   $noinfoadmin    0=Add admin info, 1=Disable admin info
11193
    * @param string $morecss        Add more CSS on select tag
11194
    * @return string                String for the HTML select component
11195
    */
11196
    public function getSelectInvoiceSubtype($selected = 0, $htmlname = 'subtypeid', $addempty = 0, $noinfoadmin = 0, $morecss = '')
11197
    {
11198
        global $langs, $user;
11199
11200
        $out = '';
11201
        dol_syslog(__METHOD__ . " selected=" . $selected . ", htmlname=" . $htmlname, LOG_DEBUG);
11202
11203
        $this->load_cache_invoice_subtype();
11204
11205
        $out .= '<select id="' . $htmlname . '" class="flat selectsubtype' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '">';
11206
        if ($addempty) {
11207
            $out .= '<option value="0">&nbsp;</option>';
11208
        }
11209
11210
        foreach ($this->cache_invoice_subtype as $rowid => $subtype) {
11211
            $label = $subtype['label'];
11212
            $out .= '<option value="' . $subtype['rowid'] . '"';
11213
            if ($selected == $subtype['rowid']) {
11214
                $out .= ' selected="selected"';
11215
            }
11216
            $out .= '>';
11217
            $out .= $label;
11218
            $out .= '</option>';
11219
        }
11220
11221
        $out .= '</select>';
11222
        if ($user->admin && empty($noinfoadmin)) {
11223
            $out .= info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
11224
        }
11225
        $out .= ajax_combobox($htmlname);
11226
11227
        return $out;
11228
    }
11229
}
11230