Form::form_project()   A
last analyzed

Complexity

Conditions 4

Size

Total Lines 36
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 25
nop 10
dl 0
loc 36
rs 9.52
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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 Dolibarr\Code\Adherents\Classes\Adherent;
47
use Dolibarr\Code\Categories\Classes\Categorie;
48
use Dolibarr\Code\Compta\Classes\Facture;
49
use Dolibarr\Code\Contact\Classes\Contact;
50
use Dolibarr\Code\Fourn\Classes\ProductFournisseur;
51
use Dolibarr\Code\Product\Classes\Entrepot;
52
use Dolibarr\Code\Product\Classes\PriceParser;
53
use Dolibarr\Code\Product\Classes\Product;
54
use Dolibarr\Code\Projet\Classes\Project;
55
use Dolibarr\Code\Resource\Classes\Dolresource;
56
use Dolibarr\Code\Resource\Classes\FormResource;
57
use Dolibarr\Code\Societe\Classes\Societe;
58
use Dolibarr\Code\Ticket\Classes\Ticket;
59
use Dolibarr\Code\User\Classes\User;
60
use Dolibarr\Core\Base\CommonObject;
61
use Dolibarr\Lib\Filters;
62
use Dolibarr\Lib\Misc;
63
use DoliDB;
64
use stdClass;
65
66
/**
67
 * \file       htdocs/core/class/html.form.class.php
68
 * \ingroup    core
69
 * \brief      File of class with all html predefined components
70
 */
71
72
73
/**
74
 * Class to manage generation of HTML components
75
 * Only common components must be here.
76
 *
77
 * TODO Merge all function load_cache_* and loadCache* (except load_cache_vatrates) into one generic function loadCacheTable
78
 */
79
class Form
80
{
81
    /**
82
     * @var DoliDB Database handler.
83
     */
84
    public $db;
85
86
    /**
87
     * @var string Error code (or message)
88
     */
89
    public $error = '';
90
91
    /**
92
     * @var string[]    Array of error strings
93
     */
94
    public $errors = array();
95
96
    // Some properties used to return data by some methods
97
    /** @var array<string,int> */
98
    public $result;
99
    /** @var int */
100
    public $num;
101
102
    // Cache arrays
103
    public $cache_types_paiements = array();
104
    public $cache_conditions_paiements = array();
105
    public $cache_transport_mode = array();
106
    public $cache_availability = array();
107
    public $cache_demand_reason = array();
108
    public $cache_types_fees = array();
109
    public $cache_vatrates = array();
110
    public $cache_invoice_subtype = array();
111
112
113
    /**
114
     * Constructor
115
     *
116
     * @param DoliDB $db Database handler
117
     */
118
    public function __construct(DoliDB $db)
119
    {
120
        $this->db = $db;
121
    }
122
123
    /**
124
     *    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.
125
     *  Note: Do not apply langs->trans function on returned content of Ajax service, content may be entity encoded twice.
126
     *
127
     * @param string $htmlname Name of html select area
128
     * @param string $url Url. Must return a json_encode of array(key=>array('text'=>'A text', 'url'=>'An url'), ...)
129
     * @param string $id Preselected key
130
     * @param string $moreparam Add more parameters onto the select tag
131
     * @param string $moreparamtourl Add more parameters onto the Ajax called URL
132
     * @param int $disabled Html select box is disabled
133
     * @param int $minimumInputLength Minimum Input Length
134
     * @param string $morecss Add more class to css styles
135
     * @param int $callurlonselect If set to 1, some code is added so an url return by the ajax is called when value is selected.
136
     * @param string $placeholder String to use as placeholder
137
     * @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)
138
     * @return    string                        HTML select string
139
     * @see selectArrayFilter(), ajax_combobox() in ajax.lib.php
140
     */
141
    public static function selectArrayAjax($htmlname, $url, $id = '', $moreparam = '', $moreparamtourl = '', $disabled = 0, $minimumInputLength = 1, $morecss = '', $callurlonselect = 0, $placeholder = '', $acceptdelayedhtml = 0)
142
    {
143
        global $conf, $langs;
144
        global $delayedhtmlcontent;    // Will be used later outside of this function
145
146
        // TODO Use an internal dolibarr component instead of select2
147
        if (!getDolGlobalString('MAIN_USE_JQUERY_MULTISELECT') && !defined('REQUIRE_JQUERY_MULTISELECT')) {
148
            return '';
149
        }
150
151
        $out = '<select type="text" class="' . $htmlname . ($morecss ? ' ' . $morecss : '') . '" ' . ($moreparam ? $moreparam . ' ' : '') . 'name="' . $htmlname . '"></select>';
152
153
        $outdelayed = '';
154
        if (!empty($conf->use_javascript_ajax)) {
155
            $tmpplugin = 'select2';
156
            $outdelayed = "\n" . '<!-- JS CODE TO ENABLE ' . $tmpplugin . ' for id ' . $htmlname . ' -->
157
		    	<script nonce="' . getNonce() . '">
158
		    	$(document).ready(function () {
159
160
	    	        ' . ($callurlonselect ? 'var saveRemoteData = [];' : '') . '
161
162
	                $(".' . $htmlname . '").select2({
163
				    	ajax: {
164
					    	dir: "ltr",
165
					    	url: "' . $url . '",
166
					    	dataType: \'json\',
167
					    	delay: 250,
168
					    	data: function (params) {
169
					    		return {
170
							    	q: params.term, 	// search term
171
					    			page: params.page
172
					    		}
173
				    		},
174
				    		processResults: function (data) {
175
				    			// parse the results into the format expected by Select2.
176
				    			// since we are using custom formatting functions we do not need to alter the remote JSON data
177
				    			//console.log(data);
178
								saveRemoteData = data;
179
					    	    /* format json result for select2 */
180
					    	    result = []
181
					    	    $.each( data, function( key, value ) {
182
					    	       result.push({id: key, text: value.text});
183
	                            });
184
				    			//return {results:[{id:\'none\', text:\'aa\'}, {id:\'rrr\', text:\'Red\'},{id:\'bbb\', text:\'Search a into projects\'}], more:false}
185
				    			//console.log(result);
186
				    			return {results: result, more: false}
187
				    		},
188
				    		cache: true
189
				    	},
190
		 				language: select2arrayoflanguage,
191
						containerCssClass: \':all:\',					/* Line to add class of origin SELECT propagated to the new <span class="select2-selection...> tag */
192
					    placeholder: "' . dol_escape_js($placeholder) . '",
193
				    	escapeMarkup: function (markup) { return markup; }, 	// let our custom formatter work
194
				    	minimumInputLength: ' . ((int)$minimumInputLength) . ',
195
				        formatResult: function (result, container, query, escapeMarkup) {
196
	                        return escapeMarkup(result.text);
197
	                    },
198
				    });
199
200
	                ' . ($callurlonselect ? '
201
	                /* Code to execute a GET when we select a value */
202
	                $(".' . $htmlname . '").change(function() {
203
				    	var selected = $(".' . $htmlname . '").val();
204
	                	console.log("We select in selectArrayAjax the entry "+selected)
205
				        $(".' . $htmlname . '").val("");  /* reset visible combo value */
206
	    			    $.each( saveRemoteData, function( key, value ) {
207
	    				        if (key == selected)
208
	    			            {
209
	    			                 console.log("selectArrayAjax - Do a redirect to "+value.url)
210
	    			                 location.assign(value.url);
211
	    			            }
212
	                    });
213
	    			});' : '') . '
214
215
	    	   });
216
		       </script>';
217
        }
218
219
        if ($acceptdelayedhtml) {
220
            $delayedhtmlcontent .= $outdelayed;
221
        } else {
222
            $out .= $outdelayed;
223
        }
224
        return $out;
225
    }
226
227
    /**
228
     *  Return a HTML select string, built from an array of key+value, but content returned into select is defined into $array parameter.
229
     *  Note: Do not apply langs->trans function on returned content of Ajax service, content may be entity encoded twice.
230
     *
231
     * @param string $htmlname Name of html select area
232
     * @param array<string,array{text:string,url:string}> $array Array (key=>array('text'=>'A text', 'url'=>'An url'), ...)
233
     * @param string $id Preselected key
234
     * @param string $moreparam Add more parameters onto the select tag
235
     * @param int<0,1> $disableFiltering If set to 1, results are not filtered with searched string
236
     * @param int<0,1> $disabled Html select box is disabled
237
     * @param int $minimumInputLength Minimum Input Length
238
     * @param string $morecss Add more class to css styles
239
     * @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.
240
     * @param string $placeholder String to use as placeholder
241
     * @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)
242
     * @param string $textfortitle Text to show on title.
243
     * @return  string                          HTML select string
244
     * @see selectArrayAjax(), ajax_combobox() in ajax.lib.php
245
     */
246
    public static function selectArrayFilter($htmlname, $array, $id = '', $moreparam = '', $disableFiltering = 0, $disabled = 0, $minimumInputLength = 1, $morecss = '', $callurlonselect = 0, $placeholder = '', $acceptdelayedhtml = 0, $textfortitle = '')
247
    {
248
        global $conf, $langs;
249
        global $delayedhtmlcontent;    // Will be used later outside of this function
250
251
        // TODO Use an internal dolibarr component instead of select2
252
        if (!getDolGlobalString('MAIN_USE_JQUERY_MULTISELECT') && !defined('REQUIRE_JQUERY_MULTISELECT')) {
253
            return '';
254
        }
255
256
        $out = '<select type="text"' . ($textfortitle ? ' title="' . dol_escape_htmltag($textfortitle) . '"' : '') . ' id="' . $htmlname . '" class="' . $htmlname . ($morecss ? ' ' . $morecss : '') . '"' . ($moreparam ? ' ' . $moreparam : '') . ' name="' . $htmlname . '"><option></option></select>';
257
258
        $formattedarrayresult = array();
259
260
        foreach ($array as $key => $value) {
261
            $o = new stdClass();
262
            $o->id = $key;
263
            $o->text = $value['text'];
264
            $o->url = $value['url'];
265
            $formattedarrayresult[] = $o;
266
        }
267
268
        $outdelayed = '';
269
        if (!empty($conf->use_javascript_ajax)) {
270
            $tmpplugin = 'select2';
271
            $outdelayed = "\n" . '<!-- JS CODE TO ENABLE ' . $tmpplugin . ' for id ' . $htmlname . ' -->
272
				<script nonce="' . getNonce() . '">
273
				$(document).ready(function () {
274
					var data = ' . json_encode($formattedarrayresult) . ';
275
276
					' . ($callurlonselect ? 'var saveRemoteData = ' . json_encode($array) . ';' : '') . '
277
278
					$(".' . $htmlname . '").select2({
279
						data: data,
280
						language: select2arrayoflanguage,
281
						containerCssClass: \':all:\',					/* Line to add class of origin SELECT propagated to the new <span class="select2-selection...> tag */
282
						placeholder: "' . dol_escape_js($placeholder) . '",
283
						escapeMarkup: function (markup) { return markup; }, 	// let our custom formatter work
284
						minimumInputLength: ' . $minimumInputLength . ',
285
						formatResult: function (result, container, query, escapeMarkup) {
286
							return escapeMarkup(result.text);
287
						},
288
						matcher: function (params, data) {
289
290
							if(! data.id) return null;';
291
292
            if ($callurlonselect) {
293
                // We forge the url with 'sall='
294
                $outdelayed .= '
295
296
							var urlBase = data.url;
297
							var separ = urlBase.indexOf("?") >= 0 ? "&" : "?";
298
							/* console.log("params.term="+params.term); */
299
							/* console.log("params.term encoded="+encodeURIComponent(params.term)); */
300
							saveRemoteData[data.id].url = urlBase + separ + "search_all=" + encodeURIComponent(params.term.replace(/\"/g, ""));';
301
            }
302
303
            if (!$disableFiltering) {
304
                $outdelayed .= '
305
306
							if(data.text.match(new RegExp(params.term))) {
307
								return data;
308
							}
309
310
							return null;';
311
            } else {
312
                $outdelayed .= '
313
314
							return data;';
315
            }
316
317
            $outdelayed .= '
318
						}
319
					});
320
321
					' . ($callurlonselect ? '
322
					/* Code to execute a GET when we select a value */
323
					$(".' . $htmlname . '").change(function() {
324
						var selected = $(".' . $htmlname . '").val();
325
						console.log("We select "+selected)
326
327
						$(".' . $htmlname . '").val("");  /* reset visible combo value */
328
						$.each( saveRemoteData, function( key, value ) {
329
							if (key == selected)
330
							{
331
								console.log("selectArrayFilter - Do a redirect to "+value.url)
332
								location.assign(value.url);
333
							}
334
						});
335
					});' : '') . '
336
337
				});
338
				</script>';
339
        }
340
341
        if ($acceptdelayedhtml) {
342
            $delayedhtmlcontent .= $outdelayed;
343
        } else {
344
            $out .= $outdelayed;
345
        }
346
        return $out;
347
    }
348
349
    /**
350
     * Show a multiselect dropbox from an array.
351
     * If a saved selection of fields exists for user (into $user->conf->MAIN_SELECTEDFIELDS_contextofpage), we use this one instead of default.
352
     *
353
     * @param string $htmlname Name of HTML field
354
     * @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.
355
     * @param string $varpage Id of context for page. Can be set by caller with $varpage=(empty($contextpage)?$_SERVER["PHP_SELF"]:$contextpage);
356
     * @param string $pos Position colon on liste value 'left' or '' (meaning 'right').
357
     * @return string               HTML multiselect string
358
     * @see selectarray()
359
     */
360
    public static function multiSelectArrayWithCheckbox($htmlname, &$array, $varpage, $pos = '')
361
    {
362
        global $langs, $user;
363
364
        if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
365
            return '';
366
        }
367
        if (empty($array)) {
368
            return '';
369
        }
370
371
        $tmpvar = "MAIN_SELECTEDFIELDS_" . $varpage; // To get list of saved selected fields to show
372
373
        if (!empty($user->conf->$tmpvar)) {        // A list of fields was already customized for user
374
            $tmparray = explode(',', $user->conf->$tmpvar);
375
            foreach ($array as $key => $val) {
376
                //var_dump($key);
377
                //var_dump($tmparray);
378
                if (in_array($key, $tmparray)) {
379
                    $array[$key]['checked'] = 1;
380
                } else {
381
                    $array[$key]['checked'] = 0;
382
                }
383
            }
384
        } else {                                // There is no list of fields already customized for user
385
            foreach ($array as $key => $val) {
386
                if (!empty($array[$key]['checked']) && $array[$key]['checked'] < 0) {
387
                    $array[$key]['checked'] = 0;
388
                }
389
            }
390
        }
391
392
        $listoffieldsforselection = '';
393
        $listcheckedstring = '';
394
395
        foreach ($array as $key => $val) {
396
            // var_dump($val);
397
            // var_dump(array_key_exists('enabled', $val));
398
            // var_dump(!$val['enabled']);
399
            if (array_key_exists('enabled', $val) && isset($val['enabled']) && !$val['enabled']) {
400
                unset($array[$key]); // We don't want this field
401
                continue;
402
            }
403
            if (!empty($val['type']) && $val['type'] == 'separate') {
404
                // Field remains in array but we don't add it into $listoffieldsforselection
405
                //$listoffieldsforselection .= '<li>-----</li>';
406
                continue;
407
            }
408
            if (!empty($val['label']) && $val['label']) {
409
                if (!empty($val['langfile']) && is_object($langs)) {
410
                    $langs->load($val['langfile']);
411
                }
412
413
                // Note: $val['checked'] <> 0 means we must show the field into the combo list  @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
414
                $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>';
415
                $listcheckedstring .= (empty($val['checked']) ? '' : $key . ',');
416
            }
417
        }
418
419
        $out = '<!-- Component multiSelectArrayWithCheckbox ' . $htmlname . ' -->
420
421
        <dl class="dropdown">
422
            <dt>
423
            <a href="#' . $htmlname . '">
424
              ' . img_picto('', 'list') . '
425
            </a>
426
            <input type="hidden" class="' . $htmlname . '" name="' . $htmlname . '" value="' . $listcheckedstring . '">
427
            </dt>
428
            <dd class="dropdowndd">
429
                <div class="multiselectcheckbox' . $htmlname . '">
430
                    <ul class="' . $htmlname . ($pos == '1' ? 'left' : '') . '">
431
                    <li><input class="inputsearch_dropdownselectedfields width90p minwidth200imp" style="width:90%;" type="text" placeholder="' . $langs->trans('Search') . '"></li>
432
                    ' . $listoffieldsforselection . '
433
                    </ul>
434
                </div>
435
            </dd>
436
        </dl>
437
438
        <script nonce="' . getNonce() . '" type="text/javascript">
439
          jQuery(document).ready(function () {
440
              $(\'.multiselectcheckbox' . $htmlname . ' input[type="checkbox"]\').on(\'click\', function () {
441
                  console.log("A new field was added/removed, we edit field input[name=formfilteraction]");
442
443
                  $("input:hidden[name=formfilteraction]").val(\'listafterchangingselectedfields\');	// Update field so we know we changed something on selected fields after POST
444
445
                  var title = $(this).val() + ",";
446
                  if ($(this).is(\':checked\')) {
447
                      $(\'.' . $htmlname . '\').val(title + $(\'.' . $htmlname . '\').val());
448
                  }
449
                  else {
450
                      $(\'.' . $htmlname . '\').val( $(\'.' . $htmlname . '\').val().replace(title, \'\') )
451
                  }
452
                  // Now, we submit page
453
                  //$(this).parents(\'form:first\').submit();
454
              });
455
              $("input.inputsearch_dropdownselectedfields").on("keyup", function() {
456
			    var value = $(this).val().toLowerCase();
457
			    $(\'.multiselectcheckbox' . $htmlname . ' li > label\').filter(function() {
458
			      $(this).parent().toggle($(this).text().toLowerCase().indexOf(value) > -1)
459
			    });
460
			  });
461
462
463
           });
464
        </script>
465
466
        ';
467
        return $out;
468
    }
469
470
    /**
471
     * Return HTML code to output a photo
472
     *
473
     * @param string $modulepart Key to define module concerned ('societe', 'userphoto', 'memberphoto')
474
     * @param Societe|Adherent|Contact|User|CommonObject $object Object containing data to retrieve file name
475
     * @param int $width Width of photo
476
     * @param int $height Height of photo (auto if 0)
477
     * @param int<0,1> $caneditfield Add edit fields
478
     * @param string $cssclass CSS name to use on img for photo
479
     * @param string $imagesize 'mini', 'small' or '' (original)
480
     * @param int<0,1> $addlinktofullsize Add link to fullsize image
481
     * @param int<0,1> $cache 1=Accept to use image in cache
482
     * @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 ''.
483
     * @param int<0,1> $noexternsourceoverwrite No overwrite image with extern source (like 'gravatar' or other module)
484
     * @return string                               HTML code to output photo
485
     * @see getImagePublicURLOfObject()
486
     */
487
    public static function showphoto($modulepart, $object, $width = 100, $height = 0, $caneditfield = 0, $cssclass = 'photowithmargin', $imagesize = '', $addlinktofullsize = 1, $cache = 0, $forcecapture = '', $noexternsourceoverwrite = 0)
488
    {
489
        global $conf, $langs;
490
491
        $entity = (empty($object->entity) ? $conf->entity : $object->entity);
492
        $id = (empty($object->id) ? $object->rowid : $object->id);  // @phan-suppress-current-line PhanUndeclaredProperty (->rowid)
0 ignored issues
show
Bug Best Practice introduced by
The property rowid does not exist on Dolibarr\Code\Contact\Classes\Contact. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property rowid does not exist on Dolibarr\Code\Adherents\Classes\Adherent. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property rowid does not exist on Dolibarr\Code\User\Classes\User. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property rowid does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property rowid does not exist on Dolibarr\Code\Societe\Classes\Societe. Since you implemented __get, consider adding a @property annotation.
Loading history...
493
494
        $dir = '';
495
        $file = '';
496
        $originalfile = '';
497
        $altfile = '';
498
        $email = '';
499
        $capture = '';
500
        if ($modulepart == 'societe') {
501
            $dir = $conf->societe->multidir_output[$entity];
502
            if (!empty($object->logo)) {
0 ignored issues
show
Bug Best Practice introduced by
The property logo does not exist on Dolibarr\Code\Contact\Classes\Contact. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property logo does not exist on Dolibarr\Code\Adherents\Classes\Adherent. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property logo does not exist on Dolibarr\Code\User\Classes\User. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property logo does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
503
                if (dolIsAllowedForPreview($object->logo)) {
504
                    if ((string)$imagesize == 'mini') {
505
                        $file = get_exdir(0, 0, 0, 0, $object, 'thirdparty') . 'logos/' . getImageFileNameForSize($object->logo, '_mini'); // getImageFileNameForSize include the thumbs
506
                    } elseif ((string)$imagesize == 'small') {
507
                        $file = get_exdir(0, 0, 0, 0, $object, 'thirdparty') . 'logos/' . getImageFileNameForSize($object->logo, '_small');
508
                    } else {
509
                        $file = get_exdir(0, 0, 0, 0, $object, 'thirdparty') . 'logos/' . $object->logo;
510
                    }
511
                    $originalfile = get_exdir(0, 0, 0, 0, $object, 'thirdparty') . 'logos/' . $object->logo;
512
                }
513
            }
514
            $email = $object->email;
0 ignored issues
show
Bug Best Practice introduced by
The property email does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
515
        } elseif ($modulepart == 'contact') {
516
            $dir = $conf->societe->multidir_output[$entity] . '/contact';
517
            if (!empty($object->photo)) {
0 ignored issues
show
Bug Best Practice introduced by
The property photo does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property photo does not exist on Dolibarr\Code\Societe\Classes\Societe. Since you implemented __get, consider adding a @property annotation.
Loading history...
518
                if (dolIsAllowedForPreview($object->photo)) {
519
                    if ((string)$imagesize == 'mini') {
520
                        $file = get_exdir(0, 0, 0, 0, $object, 'contact') . 'photos/' . getImageFileNameForSize($object->photo, '_mini');
521
                    } elseif ((string)$imagesize == 'small') {
522
                        $file = get_exdir(0, 0, 0, 0, $object, 'contact') . 'photos/' . getImageFileNameForSize($object->photo, '_small');
523
                    } else {
524
                        $file = get_exdir(0, 0, 0, 0, $object, 'contact') . 'photos/' . $object->photo;
525
                    }
526
                    $originalfile = get_exdir(0, 0, 0, 0, $object, 'contact') . 'photos/' . $object->photo;
527
                }
528
            }
529
            $email = $object->email;
530
            $capture = 'user';
531
        } elseif ($modulepart == 'userphoto') {
532
            $dir = $conf->user->dir_output;
533
            if (!empty($object->photo)) {
534
                if (dolIsAllowedForPreview($object->photo)) {
535
                    if ((string)$imagesize == 'mini') {
536
                        $file = get_exdir(0, 0, 0, 0, $object, 'user') . 'photos/' . getImageFileNameForSize($object->photo, '_mini');
537
                    } elseif ((string)$imagesize == 'small') {
538
                        $file = get_exdir(0, 0, 0, 0, $object, 'user') . 'photos/' . getImageFileNameForSize($object->photo, '_small');
539
                    } else {
540
                        $file = get_exdir(0, 0, 0, 0, $object, 'user') . 'photos/' . $object->photo;
541
                    }
542
                    $originalfile = get_exdir(0, 0, 0, 0, $object, 'user') . 'photos/' . $object->photo;
543
                }
544
            }
545
            if (getDolGlobalString('MAIN_OLD_IMAGE_LINKS')) {
546
                $altfile = $object->id . ".jpg"; // For backward compatibility
547
            }
548
            $email = $object->email;
549
            $capture = 'user';
550
        } elseif ($modulepart == 'memberphoto') {
551
            $dir = $conf->adherent->dir_output;
552
            if (!empty($object->photo)) {
553
                if (dolIsAllowedForPreview($object->photo)) {
554
                    if ((string)$imagesize == 'mini') {
555
                        $file = get_exdir(0, 0, 0, 0, $object, 'member') . 'photos/' . getImageFileNameForSize($object->photo, '_mini');
556
                    } elseif ((string)$imagesize == 'small') {
557
                        $file = get_exdir(0, 0, 0, 0, $object, 'member') . 'photos/' . getImageFileNameForSize($object->photo, '_small');
558
                    } else {
559
                        $file = get_exdir(0, 0, 0, 0, $object, 'member') . 'photos/' . $object->photo;
560
                    }
561
                    $originalfile = get_exdir(0, 0, 0, 0, $object, 'member') . 'photos/' . $object->photo;
562
                }
563
            }
564
            if (getDolGlobalString('MAIN_OLD_IMAGE_LINKS')) {
565
                $altfile = $object->id . ".jpg"; // For backward compatibility
566
            }
567
            $email = $object->email;
568
            $capture = 'user';
569
        } else {
570
            // Generic case to show photos
571
            // TODO Implement this method in previous objects so we can always use this generic method.
572
            if ($modulepart != "unknown" && method_exists($object, 'getDataToShowPhoto')) {
573
                $tmpdata = $object->getDataToShowPhoto($modulepart, $imagesize);
574
575
                $dir = $tmpdata['dir'];
576
                $file = $tmpdata['file'];
577
                $originalfile = $tmpdata['originalfile'];
578
                $altfile = $tmpdata['altfile'];
579
                $email = $tmpdata['email'];
580
                $capture = $tmpdata['capture'];
581
            }
582
        }
583
584
        if ($forcecapture) {
585
            $capture = $forcecapture;
586
        }
587
588
        $ret = '';
589
590
        if ($dir) {
591
            if ($file && file_exists($dir . "/" . $file)) {
592
                if ($addlinktofullsize) {
593
                    $urladvanced = getAdvancedPreviewUrl($modulepart, $originalfile, 0, '&entity=' . $entity);
594
                    if ($urladvanced) {
595
                        $ret .= '<a href="' . $urladvanced . '">';
596
                    } else {
597
                        $ret .= '<a href="' . constant('BASE_URL') . '/viewimage.php?modulepart=' . $modulepart . '&entity=' . $entity . '&file=' . urlencode($originalfile) . '&cache=' . $cache . '">';
598
                    }
599
                }
600
                $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 . '">';
601
                if ($addlinktofullsize) {
602
                    $ret .= '</a>';
603
                }
604
            } elseif ($altfile && file_exists($dir . "/" . $altfile)) {
605
                if ($addlinktofullsize) {
606
                    $urladvanced = getAdvancedPreviewUrl($modulepart, $originalfile, 0, '&entity=' . $entity);
607
                    if ($urladvanced) {
608
                        $ret .= '<a href="' . $urladvanced . '">';
609
                    } else {
610
                        $ret .= '<a href="' . constant('BASE_URL') . '/viewimage.php?modulepart=' . $modulepart . '&entity=' . $entity . '&file=' . urlencode($originalfile) . '&cache=' . $cache . '">';
611
                    }
612
                }
613
                $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 . '">';
614
                if ($addlinktofullsize) {
615
                    $ret .= '</a>';
616
                }
617
            } else {
618
                $nophoto = '/public/theme/common/nophoto.png';
619
                $defaultimg = 'identicon';        // For gravatar
620
                if (in_array($modulepart, array('societe', 'userphoto', 'contact', 'memberphoto'))) {    // For modules that need a special image when photo not found
621
                    if ($modulepart == 'societe' || ($modulepart == 'memberphoto' && !empty($object->morphy) && strpos($object->morphy, 'mor') !== false)) {
0 ignored issues
show
Bug Best Practice introduced by
The property morphy does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property morphy does not exist on Dolibarr\Code\Contact\Classes\Contact. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property morphy does not exist on Dolibarr\Code\Societe\Classes\Societe. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property morphy does not exist on Dolibarr\Code\User\Classes\User. Since you implemented __get, consider adding a @property annotation.
Loading history...
622
                        $nophoto = 'company';
623
                    } else {
624
                        $nophoto = '/public/theme/common/user_anonymous.png';
625
                        if (!empty($object->gender) && $object->gender == 'man') {
0 ignored issues
show
Bug Best Practice introduced by
The property gender does not exist on Dolibarr\Code\Societe\Classes\Societe. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property gender does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
626
                            $nophoto = '/public/theme/common/user_man.png';
627
                        }
628
                        if (!empty($object->gender) && $object->gender == 'woman') {
629
                            $nophoto = '/public/theme/common/user_woman.png';
630
                        }
631
                    }
632
                }
633
634
                if (isModEnabled('gravatar') && $email && empty($noexternsourceoverwrite)) {
635
                    // see https://gravatar.com/site/implement/images/php/
636
                    $ret .= '<!-- Put link to gravatar -->';
637
                    $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
638
                } else {
639
                    if ($nophoto == 'company') {
640
                        $ret .= '<div class="divforspanimg valignmiddle center photo' . $modulepart . ($cssclass ? ' ' . $cssclass : '') . '" alt="" ' . ($width ? ' width="' . $width . '"' : '') . ($height ? ' height="' . $height . '"' : '') . '>' . img_picto('', 'company') . '</div>';
641
                        //$ret .= '<div class="difforspanimgright"></div>';
642
                    } else {
643
                        $ret .= '<img class="photo' . $modulepart . ($cssclass ? ' ' . $cssclass : '') . '" alt="" ' . ($width ? ' width="' . $width . '"' : '') . ($height ? ' height="' . $height . '"' : '') . ' src="' . constant('DOL_URL_ROOT') . $nophoto . '">';
644
                    }
645
                }
646
            }
647
648
            if ($caneditfield) {
649
                if ($object->photo) {
650
                    $ret .= "<br>\n";
651
                }
652
                $ret .= '<table class="nobordernopadding centpercent">';
653
                if ($object->photo) {
654
                    $ret .= '<tr><td><input type="checkbox" class="flat photodelete" name="deletephoto" id="photodelete"> <label for="photodelete">' . $langs->trans("Delete") . '</label><br><br></td></tr>';
655
                }
656
                $ret .= '<tr><td class="tdoverflow">';
657
                $maxfilesizearray = getMaxFileSizeArray();
658
                $maxmin = $maxfilesizearray['maxmin'];
659
                if ($maxmin > 0) {
660
                    $ret .= '<input type="hidden" name="MAX_FILE_SIZE" value="' . ($maxmin * 1024) . '">';    // MAX_FILE_SIZE must precede the field type=file
661
                }
662
                $ret .= '<input type="file" class="flat maxwidth200onsmartphone" name="photo" id="photoinput" accept="image/*"' . ($capture ? ' capture="' . $capture . '"' : '') . '>';
663
                $ret .= '</td></tr>';
664
                $ret .= '</table>';
665
            }
666
        }
667
668
        return $ret;
669
    }
670
671
    /**
672
     * Output key field for an editable field
673
     *
674
     * @param string $text Text of label or key to translate
675
     * @param string $htmlname Name of select field ('edit' prefix will be added)
676
     * @param string $preselected Value to show/edit (not used in this function)
677
     * @param object $object Object (on the page we show)
678
     * @param boolean $perm Permission to allow button to edit parameter. Set it to 0 to have a not edited field.
679
     * @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]'...)
680
     * @param string $moreparam More param to add on a href URL.
681
     * @param int $fieldrequired 1 if we want to show field as mandatory using the "fieldrequired" CSS.
682
     * @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 ' '
683
     * @param string $paramid Key of parameter for id ('id', 'socid')
684
     * @param string $help Tooltip help
685
     * @return  string                  HTML edit field
686
     */
687
    public function editfieldkey($text, $htmlname, $preselected, $object, $perm, $typeofdata = 'string', $moreparam = '', $fieldrequired = 0, $notabletag = 0, $paramid = 'id', $help = '')
688
    {
689
        global $langs;
690
691
        $ret = '';
692
693
        // TODO change for compatibility
694
        if (getDolGlobalString('MAIN_USE_JQUERY_JEDITABLE') && !preg_match('/^select;/', $typeofdata)) {
695
            if (!empty($perm)) {
696
                $tmp = explode(':', $typeofdata);
697
                $ret .= '<div class="editkey_' . $tmp[0] . (!empty($tmp[1]) ? ' ' . $tmp[1] : '') . '" id="' . $htmlname . '">';
698
                if ($fieldrequired) {
699
                    $ret .= '<span class="fieldrequired">';
700
                }
701
                if ($help) {
702
                    $ret .= $this->textwithpicto($langs->trans($text), $help);
703
                } else {
704
                    $ret .= $langs->trans($text);
705
                }
706
                if ($fieldrequired) {
707
                    $ret .= '</span>';
708
                }
709
                $ret .= '</div>' . "\n";
710
            } else {
711
                if ($fieldrequired) {
712
                    $ret .= '<span class="fieldrequired">';
713
                }
714
                if ($help) {
715
                    $ret .= $this->textwithpicto($langs->trans($text), $help);
716
                } else {
717
                    $ret .= $langs->trans($text);
718
                }
719
                if ($fieldrequired) {
720
                    $ret .= '</span>';
721
                }
722
            }
723
        } else {
724
            if (empty($notabletag) && $perm) {
725
                $ret .= '<table class="nobordernopadding centpercent"><tr><td class="nowrap">';
726
            }
727
            if ($fieldrequired) {
728
                $ret .= '<span class="fieldrequired">';
729
            }
730
            if ($help) {
731
                $ret .= $this->textwithpicto($langs->trans($text), $help);
732
            } else {
733
                $ret .= $langs->trans($text);
734
            }
735
            if ($fieldrequired) {
736
                $ret .= '</span>';
737
            }
738
            if (!empty($notabletag)) {
739
                $ret .= ' ';
740
            }
741
            if (empty($notabletag) && $perm) {
742
                $ret .= '</td>';
743
            }
744
            if (empty($notabletag) && $perm) {
745
                $ret .= '<td class="right">';
746
            }
747
            if ($htmlname && GETPOST('action', 'aZ09') != 'edit' . $htmlname && $perm) {
748
                $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>';
749
            }
750
            if (!empty($notabletag) && $notabletag == 1) {
751
                if ($text) {
752
                    $ret .= ' : ';
753
                } else {
754
                    $ret .= ' ';
755
                }
756
            }
757
            if (!empty($notabletag) && $notabletag == 3) {
758
                $ret .= ' ';
759
            }
760
            if (empty($notabletag) && $perm) {
761
                $ret .= '</td>';
762
            }
763
            if (empty($notabletag) && $perm) {
764
                $ret .= '</tr></table>';
765
            }
766
        }
767
768
        return $ret;
769
    }
770
771
    /**
772
     * Show a text with a picto and a tooltip on picto
773
     *
774
     * @param string $text Text to show
775
     * @param string $htmltext Content of tooltip
776
     * @param int $direction 1=Icon is after text, -1=Icon is before text, 0=no icon
777
     * @param string $type Type of picto ('info', 'infoclickable', 'help', 'helpclickable', 'warning', 'superadmin', 'mypicto@mymodule', ...) or image filepath or 'none'
778
     * @param string $extracss Add a CSS style to td, div or span tag
779
     * @param int $noencodehtmltext Do not encode into html entity the htmltext
780
     * @param int $notabs 0=Include table and tr tags, 1=Do not include table and tr tags, 2=use div, 3=use span
781
     * @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')
782
     * @param int $forcenowrap Force no wrap between text and picto (works with notabs=2 only)
783
     * @return  string                          HTML code of text, picto, tooltip
784
     */
785
    public function textwithpicto($text, $htmltext, $direction = 1, $type = 'help', $extracss = '', $noencodehtmltext = 0, $notabs = 3, $tooltiptrigger = '', $forcenowrap = 0)
786
    {
787
        global $conf, $langs;
788
789
        //For backwards compatibility
790
        if ($type == '0') {
791
            $type = 'info';
792
        } elseif ($type == '1') {
793
            $type = 'help';
794
        }
795
        // Clean parameters
796
        $tooltiptrigger = preg_replace('/[^a-z0-9]/i', '', $tooltiptrigger);
797
798
        if (preg_match('/onsmartphone$/', $tooltiptrigger) && empty($conf->dol_no_mouse_hover)) {
799
            $tooltiptrigger = preg_replace('/^.*onsmartphone$/', '', $tooltiptrigger);
800
        }
801
        $alt = '';
802
        if ($tooltiptrigger) {
803
            $alt = $langs->transnoentitiesnoconv("ClickToShowHelp");
804
        }
805
806
        // If info or help with no javascript, show only text
807
        if (empty($conf->use_javascript_ajax)) {
808
            if ($type == 'info' || $type == 'infoclickable' || $type == 'help' || $type == 'helpclickable') {
809
                return $text;
810
            } else {
811
                $alt = $htmltext;
812
                $htmltext = '';
813
            }
814
        }
815
816
        // If info or help with smartphone, show only text (tooltip hover can't works)
817
        if (!empty($conf->dol_no_mouse_hover) && empty($tooltiptrigger)) {
818
            if ($type == 'info' || $type == 'infoclickable' || $type == 'help' || $type == 'helpclickable') {
819
                return $text;
820
            }
821
        }
822
        // If info or help with smartphone, show only text (tooltip on click does not works with dialog on smaprtphone)
823
        //if (!empty($conf->dol_no_mouse_hover) && !empty($tooltiptrigger))
824
        //{
825
        //if ($type == 'info' || $type == 'help') return '<a href="'..'">'.$text.'</a>';
826
        //}
827
828
        $img = '';
829
        if ($type == 'info') {
830
            $img = img_help(0, $alt);
831
        } elseif ($type == 'help') {
832
            $img = img_help(($tooltiptrigger != '' ? 2 : 1), $alt);
833
        } elseif ($type == 'helpclickable') {
834
            $img = img_help(($tooltiptrigger != '' ? 2 : 1), $alt);
835
        } elseif ($type == 'superadmin') {
836
            // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
837
            $img = img_picto($alt, 'redstar');
838
        } elseif ($type == 'admin') {
839
            // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
840
            $img = img_picto($alt, 'star');
841
        } elseif ($type == 'warning') {
842
            $img = img_warning($alt);
843
        } elseif ($type != 'none') {
844
            // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
845
            $img = img_picto($alt, $type); // $type can be an image path
846
        }
847
848
        return $this->textwithtooltip($text, $htmltext, ((($tooltiptrigger && !$img) || strpos($type, 'clickable')) ? 3 : 2), $direction, $img, $extracss, $notabs, '', $noencodehtmltext, $tooltiptrigger, $forcenowrap);
849
    }
850
851
    /**
852
     *  Show a text and picto with tooltip on text or picto.
853
     *  Can be called by an instancied $form->textwithtooltip or by a static call Form::textwithtooltip
854
     *
855
     * @param string $text Text to show
856
     * @param string $htmltext HTML content of tooltip. Must be HTML/UTF8 encoded.
857
     * @param int $tooltipon 1=tooltip on text, 2=tooltip on image, 3=tooltip on both
858
     * @param int $direction -1=image is before, 0=no image, 1=image is after
859
     * @param string $img Html code for image (use img_xxx() function to get it)
860
     * @param string $extracss Add a CSS style to td tags
861
     * @param int $notabs 0=Include table and tr tags, 1=Do not include table and tr tags, 2=use div, 3=use span
862
     * @param string $incbefore Include code before the text
863
     * @param int $noencodehtmltext Do not encode into html entity the htmltext
864
     * @param string $tooltiptrigger ''=Tooltip on hover, 'abc'=Tooltip on click (abc is a unique key)
865
     * @param int $forcenowrap Force no wrap between text and picto (works with notabs=2 only)
866
     * @return string                      Code html du tooltip (texte+picto)
867
     * @see    textwithpicto()             Use textwithpicto() instead of textwithtooltip if you can.
868
     */
869
    public function textwithtooltip($text, $htmltext, $tooltipon = 1, $direction = 0, $img = '', $extracss = '', $notabs = 3, $incbefore = '', $noencodehtmltext = 0, $tooltiptrigger = '', $forcenowrap = 0)
870
    {
871
        if ($incbefore) {
872
            $text = $incbefore . $text;
873
        }
874
        if (!$htmltext) {
875
            return $text;
876
        }
877
        $direction = (int)$direction;    // For backward compatibility when $direction was set to '' instead of 0
878
879
        $tag = 'td';
880
        if ($notabs == 2) {
881
            $tag = 'div';
882
        }
883
        if ($notabs == 3) {
884
            $tag = 'span';
885
        }
886
        // Sanitize tooltip
887
        $htmltext = str_replace(array("\r", "\n"), '', $htmltext);
888
889
        $extrastyle = '';
890
        if ($direction < 0) {
891
            $extracss = ($extracss ? $extracss . ' ' : '') . ($notabs != 3 ? 'inline-block' : '');
892
            $extrastyle = 'padding: 0px; padding-left: 3px;';
893
        }
894
        if ($direction > 0) {
895
            $extracss = ($extracss ? $extracss . ' ' : '') . ($notabs != 3 ? 'inline-block' : '');
896
            $extrastyle = 'padding: 0px; padding-right: 3px;';
897
        }
898
899
        $classfortooltip = 'classfortooltip';
900
901
        $s = '';
902
        $textfordialog = '';
903
904
        if ($tooltiptrigger == '') {
905
            $htmltext = str_replace('"', '&quot;', $htmltext);
906
        } else {
907
            $classfortooltip = 'classfortooltiponclick';
908
            $textfordialog .= '<div style="display: none;" id="idfortooltiponclick_' . $tooltiptrigger . '" class="classfortooltiponclicktext">' . $htmltext . '</div>';
909
        }
910
        if ($tooltipon == 2 || $tooltipon == 3) {
911
            $paramfortooltipimg = ' class="' . $classfortooltip . ($notabs != 3 ? ' inline-block' : '') . ($extracss ? ' ' . $extracss : '') . '" style="padding: 0px;' . ($extrastyle ? ' ' . $extrastyle : '') . '"';
912
            if ($tooltiptrigger == '') {
913
                $paramfortooltipimg .= ' title="' . ($noencodehtmltext ? $htmltext : dol_escape_htmltag($htmltext, 1)) . '"'; // Attribute to put on img tag to store tooltip
914
            } else {
915
                $paramfortooltipimg .= ' dolid="' . $tooltiptrigger . '"';
916
            }
917
        } else {
918
            $paramfortooltipimg = ($extracss ? ' class="' . $extracss . '"' : '') . ($extrastyle ? ' style="' . $extrastyle . '"' : ''); // Attribute to put on td text tag
919
        }
920
        if ($tooltipon == 1 || $tooltipon == 3) {
921
            $paramfortooltiptd = ' class="' . ($tooltipon == 3 ? 'cursorpointer ' : '') . $classfortooltip . ' inline-block' . ($extracss ? ' ' . $extracss : '') . '" style="padding: 0px;' . ($extrastyle ? ' ' . $extrastyle : '') . '" ';
922
            if ($tooltiptrigger == '') {
923
                $paramfortooltiptd .= ' title="' . ($noencodehtmltext ? $htmltext : dol_escape_htmltag($htmltext, 1)) . '"'; // Attribute to put on td tag to store tooltip
924
            } else {
925
                $paramfortooltiptd .= ' dolid="' . $tooltiptrigger . '"';
926
            }
927
        } else {
928
            $paramfortooltiptd = ($extracss ? ' class="' . $extracss . '"' : '') . ($extrastyle ? ' style="' . $extrastyle . '"' : ''); // Attribute to put on td text tag
929
        }
930
        if (empty($notabs)) {
931
            $s .= '<table class="nobordernopadding"><tr style="height: auto;">';
932
        } elseif ($notabs == 2) {
933
            $s .= '<div class="inline-block' . ($forcenowrap ? ' nowrap' : '') . '">';
934
        }
935
        // Define value if value is before
936
        if ($direction < 0) {
937
            $s .= '<' . $tag . $paramfortooltipimg;
938
            if ($tag == 'td') {
939
                $s .= ' class="valigntop" width="14"';
940
            }
941
            $s .= '>' . $textfordialog . $img . '</' . $tag . '>';
942
        }
943
        // Use another method to help avoid having a space in value in order to use this value with jquery
944
        // Define label
945
        if ((string)$text != '') {
946
            $s .= '<' . $tag . $paramfortooltiptd . '>' . $text . '</' . $tag . '>';
947
        }
948
        // Define value if value is after
949
        if ($direction > 0) {
950
            $s .= '<' . $tag . $paramfortooltipimg;
951
            if ($tag == 'td') {
952
                $s .= ' class="valignmiddle" width="14"';
953
            }
954
            $s .= '>' . $textfordialog . $img . '</' . $tag . '>';
955
        }
956
        if (empty($notabs)) {
957
            $s .= '</tr></table>';
958
        } elseif ($notabs == 2) {
959
            $s .= '</div>';
960
        }
961
962
        return $s;
963
    }
964
965
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
966
967
    /**
968
     * Output value of a field for an editable field
969
     *
970
     * @param string $text Text of label (not used in this function)
971
     * @param string $htmlname Name of select field
972
     * @param string $value Value to show/edit
973
     * @param CommonObject $object Object (that we want to show)
974
     * @param boolean $perm Permission to allow button to edit parameter
975
     * @param string $typeofdata Type of data ('string' by default, 'checkbox', 'email', 'phone', 'amount:99', 'numeric:99',
976
     *                                  'text' or 'textarea:rows:cols%', 'safehtmlstring', 'restricthtml',
977
     *                                  '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,...')
978
     * @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
979
     * @param ?CommonObject $extObject External object ???
980
     * @param mixed $custommsg String or Array of custom messages : eg array('success' => 'MyMessage', 'error' => 'MyMessage')
981
     * @param string $moreparam More param to add on the form on action href URL parameter
982
     * @param int $notabletag Do no output table tags
983
     * @param string $formatfunc Call a specific method of $object->$formatfunc to output field in view mode (For example: 'dol_print_email')
984
     * @param string $paramid Key of parameter for id ('id', 'socid')
985
     * @param string $gm 'auto' or 'tzuser' or 'tzuserrel' or 'tzserver' (when $typeofdata is a date)
986
     * @param array<string,int> $moreoptions Array with more options. For example array('addnowlink'=>1), array('valuealreadyhtmlescaped'=>1)
987
     * @param string $editaction [=''] use GETPOST default action or set action to edit mode
988
     * @return string                   HTML edit field
989
     */
990
    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 = '')
991
    {
992
        global $conf, $langs;
993
994
        $ret = '';
995
996
        // Check parameters
997
        if (empty($typeofdata)) {
998
            return 'ErrorBadParameter typeofdata is empty';
999
        }
1000
        // Clean parameter $typeofdata
1001
        if ($typeofdata == 'datetime') {
1002
            $typeofdata = 'dayhour';
1003
        }
1004
        $reg = array();
1005
        if (preg_match('/^(\w+)\((\d+)\)$/', $typeofdata, $reg)) {
1006
            if ($reg[1] == 'varchar') {
1007
                $typeofdata = 'string';
1008
            } elseif ($reg[1] == 'int') {
1009
                $typeofdata = 'numeric';
1010
            } else {
1011
                return 'ErrorBadParameter ' . $typeofdata;
1012
            }
1013
        }
1014
1015
        // When option to edit inline is activated
1016
        if (getDolGlobalString('MAIN_USE_JQUERY_JEDITABLE') && !preg_match('/^select;|day|datepicker|dayhour|datehourpicker/', $typeofdata)) { // TODO add jquery timepicker and support select
1017
            $ret .= $this->editInPlace($object, $value, $htmlname, $perm, $typeofdata, $editvalue, $extObject, $custommsg);
1018
        } else {
1019
            if ($editaction == '') {
1020
                $editaction = GETPOST('action', 'aZ09');
1021
            }
1022
            $editmode = ($editaction == 'edit' . $htmlname);
1023
            if ($editmode) {    // edit mode
1024
                $ret .= "\n";
1025
                $ret .= '<form method="post" action="' . $_SERVER["PHP_SELF"] . ($moreparam ? '?' . $moreparam : '') . '">';
1026
                $ret .= '<input type="hidden" name="action" value="set' . $htmlname . '">';
1027
                $ret .= '<input type="hidden" name="token" value="' . newToken() . '">';
1028
                $ret .= '<input type="hidden" name="' . $paramid . '" value="' . $object->id . '">';
1029
                if (empty($notabletag)) {
1030
                    $ret .= '<table class="nobordernopadding centpercent">';
1031
                }
1032
                if (empty($notabletag)) {
1033
                    $ret .= '<tr><td>';
1034
                }
1035
                if (preg_match('/^(string|safehtmlstring|email|phone|url)/', $typeofdata)) {
1036
                    $tmp = explode(':', $typeofdata);
1037
                    $ret .= '<input type="text" id="' . $htmlname . '" name="' . $htmlname . '" value="' . ($editvalue ? $editvalue : $value) . '"' . (empty($tmp[1]) ? '' : ' size="' . $tmp[1] . '"') . ' autofocus>';
1038
                } elseif (preg_match('/^(integer)/', $typeofdata)) {
1039
                    $tmp = explode(':', $typeofdata);
1040
                    $valuetoshow = price2num($editvalue ? $editvalue : $value, 0);
1041
                    $ret .= '<input type="text" id="' . $htmlname . '" name="' . $htmlname . '" value="' . $valuetoshow . '"' . (empty($tmp[1]) ? '' : ' size="' . $tmp[1] . '"') . ' autofocus>';
1042
                } elseif (preg_match('/^(numeric|amount)/', $typeofdata)) {
1043
                    $tmp = explode(':', $typeofdata);
1044
                    $valuetoshow = price2num($editvalue ? $editvalue : $value);
1045
                    $ret .= '<input type="text" id="' . $htmlname . '" name="' . $htmlname . '" value="' . ($valuetoshow != '' ? price($valuetoshow) : '') . '"' . (empty($tmp[1]) ? '' : ' size="' . $tmp[1] . '"') . ' autofocus>';
1046
                } elseif (preg_match('/^(checkbox)/', $typeofdata)) {
1047
                    $tmp = explode(':', $typeofdata);
1048
                    $ret .= '<input type="checkbox" id="' . $htmlname . '" name="' . $htmlname . '" value="' . ($value ? $value : 'on') . '"' . ($value ? ' checked' : '') . (empty($tmp[1]) ? '' : $tmp[1]) . '/>';
1049
                } elseif (preg_match('/^text/', $typeofdata) || preg_match('/^note/', $typeofdata)) {    // if wysiwyg is enabled $typeofdata = 'ckeditor'
1050
                    $tmp = explode(':', $typeofdata);
1051
                    $cols = (empty($tmp[2]) ? '' : $tmp[2]);
1052
                    $morealt = '';
1053
                    if (preg_match('/%/', $cols)) {
1054
                        $morealt = ' style="width: ' . $cols . '"';
1055
                        $cols = '';
1056
                    }
1057
                    $valuetoshow = ($editvalue ? $editvalue : $value);
1058
                    $ret .= '<textarea id="' . $htmlname . '" name="' . $htmlname . '" wrap="soft" rows="' . (empty($tmp[1]) ? '20' : $tmp[1]) . '"' . ($cols ? ' cols="' . $cols . '"' : 'class="quatrevingtpercent"') . $morealt . '" autofocus>';
1059
                    // textarea convert automatically entities chars into simple chars.
1060
                    // 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.
1061
                    $valuetoshow = str_replace('&', '&amp;', $valuetoshow);
1062
                    $ret .= dol_htmlwithnojs(dol_string_neverthesehtmltags($valuetoshow, array('textarea')));
1063
                    $ret .= '</textarea>';
1064
                } elseif ($typeofdata == 'day' || $typeofdata == 'datepicker') {
1065
                    $addnowlink = empty($moreoptions['addnowlink']) ? 0 : $moreoptions['addnowlink'];
1066
                    $adddateof = empty($moreoptions['adddateof']) ? '' : $moreoptions['adddateof'];
1067
                    $labeladddateof = empty($moreoptions['labeladddateof']) ? '' : $moreoptions['labeladddateof'];
1068
                    $ret .= $this->selectDate($value, $htmlname, 0, 0, 1, 'form' . $htmlname, 1, $addnowlink, 0, '', '', $adddateof, '', 1, $labeladddateof, '', $gm);
1069
                } elseif ($typeofdata == 'dayhour' || $typeofdata == 'datehourpicker') {
1070
                    $addnowlink = empty($moreoptions['addnowlink']) ? 0 : $moreoptions['addnowlink'];
1071
                    $adddateof = empty($moreoptions['adddateof']) ? '' : $moreoptions['adddateof'];
1072
                    $labeladddateof = empty($moreoptions['labeladddateof']) ? '' : $moreoptions['labeladddateof'];
1073
                    $ret .= $this->selectDate($value, $htmlname, 1, 1, 1, 'form' . $htmlname, 1, $addnowlink, 0, '', '', $adddateof, '', 1, $labeladddateof, '', $gm);
1074
                } elseif (preg_match('/^select;/', $typeofdata)) {
1075
                    $arraydata = explode(',', preg_replace('/^select;/', '', $typeofdata));
1076
                    $arraylist = array();
1077
                    foreach ($arraydata as $val) {
1078
                        $tmp = explode(':', $val);
1079
                        $tmpkey = str_replace('|', ':', $tmp[0]);
1080
                        $arraylist[$tmpkey] = $tmp[1];
1081
                    }
1082
                    $ret .= $this->selectarray($htmlname, $arraylist, $value);
1083
                } elseif (preg_match('/^link/', $typeofdata)) {
1084
                    // TODO Not yet implemented. See code for extrafields
1085
                } elseif (preg_match('/^ckeditor/', $typeofdata)) {
1086
                    $tmp = explode(':', $typeofdata); // Example: ckeditor:dolibarr_zzz:width:height:savemethod:toolbarstartexpanded:rows:cols:uselocalbrowser
1087
                    $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]));
1088
                    $ret .= $doleditor->Create(1);
1089
                } elseif ($typeofdata == 'asis') {
1090
                    $ret .= ($editvalue ? $editvalue : $value);
1091
                }
1092
                if (empty($notabletag)) {
1093
                    $ret .= '</td>';
1094
                }
1095
1096
                // Button save-cancel
1097
                if (empty($notabletag)) {
1098
                    $ret .= '<td>';
1099
                }
1100
                //else $ret.='<div class="clearboth"></div>';
1101
                $ret .= '<input type="submit" class="smallpaddingimp button' . (empty($notabletag) ? '' : ' ') . '" name="modify" value="' . $langs->trans("Modify") . '">';
1102
                if (preg_match('/ckeditor|textarea/', $typeofdata) && empty($notabletag)) {
1103
                    $ret .= '<br>' . "\n";
1104
                }
1105
                $ret .= '<input type="submit" class="smallpaddingimp button button-cancel' . (empty($notabletag) ? '' : ' ') . '" name="cancel" value="' . $langs->trans("Cancel") . '">';
1106
                if (empty($notabletag)) {
1107
                    $ret .= '</td>';
1108
                }
1109
1110
                if (empty($notabletag)) {
1111
                    $ret .= '</tr></table>' . "\n";
1112
                }
1113
                $ret .= '</form>' . "\n";
1114
            } else {        // view mode
1115
                if (preg_match('/^email/', $typeofdata)) {
1116
                    $ret .= dol_print_email($value, 0, 0, 0, 0, 1);
1117
                } elseif (preg_match('/^phone/', $typeofdata)) {
1118
                    $ret .= dol_print_phone($value, '_blank', 32, 1);
1119
                } elseif (preg_match('/^url/', $typeofdata)) {
1120
                    $ret .= dol_print_url($value, '_blank', 32, 1);
1121
                } elseif (preg_match('/^(amount|numeric)/', $typeofdata)) {
1122
                    $ret .= ($value != '' ? price($value, 0, $langs, 0, -1, -1, $conf->currency) : '');
1123
                } elseif (preg_match('/^checkbox/', $typeofdata)) {
1124
                    $tmp = explode(':', $typeofdata);
1125
                    $ret .= '<input type="checkbox" disabled id="' . $htmlname . '" name="' . $htmlname . '" value="' . $value . '"' . ($value ? ' checked' : '') . ($tmp[1] ? $tmp[1] : '') . '/>';
1126
                } elseif (preg_match('/^text/', $typeofdata) || preg_match('/^note/', $typeofdata)) {
1127
                    $ret .= dol_htmlwithnojs(dol_string_onlythesehtmltags(dol_htmlentitiesbr($value), 1, 1, 1));
1128
                } elseif (preg_match('/^(safehtmlstring|restricthtml)/', $typeofdata)) {    // 'restricthtml' is not an allowed type for editfieldval. Value is 'safehtmlstring'
1129
                    $ret .= dol_htmlwithnojs(dol_string_onlythesehtmltags($value));
1130
                } elseif ($typeofdata == 'day' || $typeofdata == 'datepicker') {
1131
                    $ret .= '<span class="valuedate">' . dol_print_date($value, 'day', $gm) . '</span>';
1132
                } elseif ($typeofdata == 'dayhour' || $typeofdata == 'datehourpicker') {
1133
                    $ret .= '<span class="valuedate">' . dol_print_date($value, 'dayhour', $gm) . '</span>';
1134
                } elseif (preg_match('/^select;/', $typeofdata)) {
1135
                    $arraydata = explode(',', preg_replace('/^select;/', '', $typeofdata));
1136
                    $arraylist = array();
1137
                    foreach ($arraydata as $val) {
1138
                        $tmp = explode(':', $val);
1139
                        $arraylist[$tmp[0]] = $tmp[1];
1140
                    }
1141
                    $ret .= $arraylist[$value];
1142
                    if ($htmlname == 'fk_product_type') {
1143
                        if ($value == 0) {
1144
                            $ret = img_picto($langs->trans("Product"), 'product', 'class="paddingleftonly paddingrightonly colorgrey"') . $ret;
1145
                        } else {
1146
                            $ret = img_picto($langs->trans("Service"), 'service', 'class="paddingleftonly paddingrightonly colorgrey"') . $ret;
1147
                        }
1148
                    }
1149
                } elseif (preg_match('/^ckeditor/', $typeofdata)) {
1150
                    $tmpcontent = dol_htmlentitiesbr($value);
1151
                    if (getDolGlobalString('MAIN_DISABLE_NOTES_TAB')) {
1152
                        $firstline = preg_replace('/<br>.*/', '', $tmpcontent);
1153
                        $firstline = preg_replace('/[\n\r].*/', '', $firstline);
1154
                        $tmpcontent = $firstline . ((strlen($firstline) != strlen($tmpcontent)) ? '...' : '');
1155
                    }
1156
                    // We don't use dol_escape_htmltag to get the html formatting active, but this need we must also
1157
                    // clean data from some dangerous html
1158
                    $ret .= dol_string_onlythesehtmltags(dol_htmlentitiesbr($tmpcontent));
1159
                } else {
1160
                    if (empty($moreoptions['valuealreadyhtmlescaped'])) {
1161
                        $ret .= dol_escape_htmltag($value);
1162
                    } else {
1163
                        $ret .= $value;        // $value must be already html escaped.
1164
                    }
1165
                }
1166
1167
                // Custom format if parameter $formatfunc has been provided
1168
                if ($formatfunc && method_exists($object, $formatfunc)) {
1169
                    $ret = $object->$formatfunc($ret);
1170
                }
1171
            }
1172
        }
1173
        return $ret;
1174
    }
1175
1176
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1177
1178
    /**
1179
     * Output edit in place form
1180
     *
1181
     * @param CommonObject $object Object
1182
     * @param string $value Value to show/edit
1183
     * @param string $htmlname DIV ID (field name)
1184
     * @param int $condition Condition to edit
1185
     * @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')
1186
     * @param string $editvalue When in edit mode, use this value as $value instead of value
1187
     * @param   ?CommonObject $extObject External object
1188
     * @param mixed $custommsg String or Array of custom messages : eg array('success' => 'MyMessage', 'error' => 'MyMessage')
1189
     * @return  string              HTML edit in place
1190
     */
1191
    protected function editInPlace($object, $value, $htmlname, $condition, $inputType = 'textarea', $editvalue = null, $extObject = null, $custommsg = null)
1192
    {
1193
        $out = '';
1194
1195
        // Check parameters
1196
        if (preg_match('/^text/', $inputType)) {
1197
            $value = dol_nl2br($value);
1198
        } elseif (preg_match('/^numeric/', $inputType)) {
1199
            $value = price($value);
1200
        } elseif ($inputType == 'day' || $inputType == 'datepicker') {
1201
            $value = dol_print_date($value, 'day');
1202
        }
1203
1204
        if ($condition) {
1205
            $element = false;
1206
            $table_element = false;
1207
            $fk_element = false;
1208
            $loadmethod = false;
1209
            $savemethod = false;
1210
            $ext_element = false;
1211
            $button_only = false;
1212
            $inputOption = '';
1213
            $rows = '';
1214
            $cols = '';
1215
1216
            if (is_object($object)) {
1217
                $element = $object->element;
1218
                $table_element = $object->table_element;
1219
                $fk_element = $object->id;
1220
            }
1221
1222
            if (is_object($extObject)) {
1223
                $ext_element = $extObject->element;
1224
            }
1225
1226
            if (preg_match('/^(string|email|numeric)/', $inputType)) {
1227
                $tmp = explode(':', $inputType);
1228
                $inputType = $tmp[0];
1229
                if (!empty($tmp[1])) {
1230
                    $inputOption = $tmp[1];
1231
                }
1232
                if (!empty($tmp[2])) {
1233
                    $savemethod = $tmp[2];
1234
                }
1235
                $out .= '<input id="width_' . $htmlname . '" value="' . $inputOption . '" type="hidden"/>' . "\n";
1236
            } elseif ((preg_match('/^day$/', $inputType)) || (preg_match('/^datepicker/', $inputType)) || (preg_match('/^datehourpicker/', $inputType))) {
1237
                $tmp = explode(':', $inputType);
1238
                $inputType = $tmp[0];
1239
                if (!empty($tmp[1])) {
1240
                    $inputOption = $tmp[1];
1241
                }
1242
                if (!empty($tmp[2])) {
1243
                    $savemethod = $tmp[2];
1244
                }
1245
1246
                $out .= '<input id="timestamp" type="hidden"/>' . "\n"; // Use for timestamp format
1247
            } elseif (preg_match('/^(select|autocomplete)/', $inputType)) {
1248
                $tmp = explode(':', $inputType);
1249
                $inputType = $tmp[0];
1250
                $loadmethod = $tmp[1];
1251
                if (!empty($tmp[2])) {
1252
                    $savemethod = $tmp[2];
1253
                }
1254
                if (!empty($tmp[3])) {
1255
                    $button_only = true;
1256
                }
1257
            } elseif (preg_match('/^textarea/', $inputType)) {
1258
                $tmp = explode(':', $inputType);
1259
                $inputType = $tmp[0];
1260
                $rows = (empty($tmp[1]) ? '8' : $tmp[1]);
1261
                $cols = (empty($tmp[2]) ? '80' : $tmp[2]);
1262
            } elseif (preg_match('/^ckeditor/', $inputType)) {
1263
                $tmp = explode(':', $inputType);
1264
                $inputType = $tmp[0];
1265
                $toolbar = $tmp[1];
1266
                if (!empty($tmp[2])) {
1267
                    $width = $tmp[2];
1268
                }
1269
                if (!empty($tmp[3])) {
1270
                    $height = $tmp[3];
1271
                }
1272
                if (!empty($tmp[4])) {
1273
                    $savemethod = $tmp[4];
1274
                }
1275
1276
                if (isModEnabled('fckeditor')) {
1277
                    $out .= '<input id="ckeditor_toolbar" value="' . $toolbar . '" type="hidden"/>' . "\n";
1278
                } else {
1279
                    $inputType = 'textarea';
1280
                }
1281
            }
1282
1283
            $out .= '<input id="element_' . $htmlname . '" value="' . $element . '" type="hidden"/>' . "\n";
1284
            $out .= '<input id="table_element_' . $htmlname . '" value="' . $table_element . '" type="hidden"/>' . "\n";
1285
            $out .= '<input id="fk_element_' . $htmlname . '" value="' . $fk_element . '" type="hidden"/>' . "\n";
1286
            $out .= '<input id="loadmethod_' . $htmlname . '" value="' . $loadmethod . '" type="hidden"/>' . "\n";
1287
            if (!empty($savemethod)) {
1288
                $out .= '<input id="savemethod_' . $htmlname . '" value="' . $savemethod . '" type="hidden"/>' . "\n";
1289
            }
1290
            if (!empty($ext_element)) {
1291
                $out .= '<input id="ext_element_' . $htmlname . '" value="' . $ext_element . '" type="hidden"/>' . "\n";
1292
            }
1293
            if (!empty($custommsg)) {
1294
                if (is_array($custommsg)) {
1295
                    if (!empty($custommsg['success'])) {
1296
                        $out .= '<input id="successmsg_' . $htmlname . '" value="' . $custommsg['success'] . '" type="hidden"/>' . "\n";
1297
                    }
1298
                    if (!empty($custommsg['error'])) {
1299
                        $out .= '<input id="errormsg_' . $htmlname . '" value="' . $custommsg['error'] . '" type="hidden"/>' . "\n";
1300
                    }
1301
                } else {
1302
                    $out .= '<input id="successmsg_' . $htmlname . '" value="' . $custommsg . '" type="hidden"/>' . "\n";
1303
                }
1304
            }
1305
            if ($inputType == 'textarea') {
1306
                $out .= '<input id="textarea_' . $htmlname . '_rows" value="' . $rows . '" type="hidden"/>' . "\n";
1307
                $out .= '<input id="textarea_' . $htmlname . '_cols" value="' . $cols . '" type="hidden"/>' . "\n";
1308
            }
1309
            $out .= '<span id="viewval_' . $htmlname . '" class="viewval_' . $inputType . ($button_only ? ' inactive' : ' active') . '">' . $value . '</span>' . "\n";
1310
            $out .= '<span id="editval_' . $htmlname . '" class="editval_' . $inputType . ($button_only ? ' inactive' : ' active') . ' hideobject">' . (!empty($editvalue) ? $editvalue : $value) . '</span>' . "\n";
1311
        } else {
1312
            $out = $value;
1313
        }
1314
1315
        return $out;
1316
    }
1317
1318
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1319
1320
    /**
1321
     *  Show a HTML widget to input a date or combo list for day, month, years and optionally hours and minutes.
1322
     *  Fields are preselected with :
1323
     *              - set_time date (must be a local PHP server timestamp or string date with format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM')
1324
     *              - local date in user area, if set_time is '' (so if set_time is '', output may differs when done from two different location)
1325
     *              - Empty (fields empty), if set_time is -1 (in this case, parameter empty must also have value 1)
1326
     *
1327
     * @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).
1328
     * @param string $prefix Prefix for fields name
1329
     * @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
1330
     * @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
1331
     * @param int $empty 0=Fields required, 1=Empty inputs are allowed, 2=Empty inputs are allowed for hours only
1332
     * @param string $form_name Not used
1333
     * @param int<0,1> $d 1=Show days, month, years
1334
     * @param int<0,2> $addnowlink Add a link "Now", 1 with server time, 2 with local computer time
1335
     * @param int<0,1> $disabled Disable input fields
1336
     * @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')
1337
     * @param string $addplusone Add a link "+1 hour". Value must be name of another selectDate field.
1338
     * @param int|string|array $adddateof Add a link "Date of ..." using the following date. Must be array(array('adddateof'=>..., 'labeladddateof'=>...))
1339
     * @param string $openinghours Specify hour start and hour end for the select ex 8,20
1340
     * @param int $stepminutes Specify step for minutes between 1 and 30
1341
     * @param string $labeladddateof Label to use for the $adddateof parameter. Deprecated. Used only when $adddateof is not an array.
1342
     * @param string $placeholder Placeholder
1343
     * @param mixed $gm 'auto' (for backward compatibility, avoid this), 'gmt' or 'tzserver' or 'tzuserrel'
1344
     * @return string                               Html for selectDate
1345
     * @see    form_date(), select_month(), select_year(), select_dayofweek()
1346
     */
1347
    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')
1348
    {
1349
        global $conf, $langs;
1350
1351
        if ($gm === 'auto') {
1352
            $gm = (empty($conf) ? 'tzserver' : $conf->tzuserinputkey);
1353
        }
1354
1355
        $retstring = '';
1356
1357
        if ($prefix == '') {
1358
            $prefix = 're';
1359
        }
1360
        if ($h == '') {
1361
            $h = 0;
1362
        }
1363
        if ($m == '') {
1364
            $m = 0;
1365
        }
1366
        $emptydate = 0;
1367
        $emptyhours = 0;
1368
        if ($stepminutes <= 0 || $stepminutes > 30) {
1369
            $stepminutes = 1;
1370
        }
1371
        if ($empty == 1) {
1372
            $emptydate = 1;
1373
            $emptyhours = 1;
1374
        }
1375
        if ($empty == 2) {
1376
            $emptydate = 0;
1377
            $emptyhours = 1;
1378
        }
1379
        $orig_set_time = $set_time;
1380
1381
        if ($set_time === '' && $emptydate == 0) {
1382
            include_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php';
1383
            if ($gm == 'tzuser' || $gm == 'tzuserrel') {
1384
                $set_time = dol_now($gm);
1385
            } else {
1386
                $set_time = dol_now('tzuser') - (getServerTimeZoneInt('now') * 3600); // set_time must be relative to PHP server timezone
1387
            }
1388
        }
1389
1390
        // Analysis of the pre-selection date
1391
        $reg = array();
1392
        $shour = '';
1393
        $smin = '';
1394
        $ssec = '';
1395
        if (preg_match('/^([0-9]+)\-([0-9]+)\-([0-9]+)\s?([0-9]+)?:?([0-9]+)?/', $set_time, $reg)) {    // deprecated usage
1396
            // Date format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'
1397
            $syear = (!empty($reg[1]) ? $reg[1] : '');
1398
            $smonth = (!empty($reg[2]) ? $reg[2] : '');
1399
            $sday = (!empty($reg[3]) ? $reg[3] : '');
1400
            $shour = (!empty($reg[4]) ? $reg[4] : '');
1401
            $smin = (!empty($reg[5]) ? $reg[5] : '');
1402
        } elseif (strval($set_time) != '' && $set_time != -1) {
1403
            // set_time est un timestamps (0 possible)
1404
            $syear = dol_print_date($set_time, "%Y", $gm);
1405
            $smonth = dol_print_date($set_time, "%m", $gm);
1406
            $sday = dol_print_date($set_time, "%d", $gm);
1407
            if ($orig_set_time != '') {
1408
                $shour = dol_print_date($set_time, "%H", $gm);
1409
                $smin = dol_print_date($set_time, "%M", $gm);
1410
                $ssec = dol_print_date($set_time, "%S", $gm);
1411
            }
1412
        } else {
1413
            // Date est '' ou vaut -1
1414
            $syear = '';
1415
            $smonth = '';
1416
            $sday = '';
1417
            $shour = getDolGlobalString('MAIN_DEFAULT_DATE_HOUR', ($h == -1 ? '23' : ''));
1418
            $smin = getDolGlobalString('MAIN_DEFAULT_DATE_MIN', ($h == -1 ? '59' : ''));
1419
            $ssec = getDolGlobalString('MAIN_DEFAULT_DATE_SEC', ($h == -1 ? '59' : ''));
1420
        }
1421
        if ($h == 3 || $h == 4) {
1422
            $shour = '';
1423
        }
1424
        if ($m == 3) {
1425
            $smin = '';
1426
        }
1427
1428
        $nowgmt = dol_now('gmt');
1429
        //var_dump(dol_print_date($nowgmt, 'dayhourinputnoreduce', 'tzuserrel'));
1430
1431
        // You can set MAIN_POPUP_CALENDAR to 'eldy' or 'jquery'
1432
        $usecalendar = 'combo';
1433
        if (!empty($conf->use_javascript_ajax) && (!getDolGlobalString('MAIN_POPUP_CALENDAR') || getDolGlobalString('MAIN_POPUP_CALENDAR') != "none")) {
1434
            $usecalendar = ((!getDolGlobalString('MAIN_POPUP_CALENDAR') || getDolGlobalString('MAIN_POPUP_CALENDAR') == 'eldy') ? 'jquery' : $conf->global->MAIN_POPUP_CALENDAR);
1435
        }
1436
        if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1437
            // If we use a text browser or screen reader, we use the 'combo' date selector
1438
            $usecalendar = 'html';
1439
        }
1440
1441
        if ($d) {
1442
            // Show date with popup
1443
            if ($usecalendar != 'combo') {
1444
                $formated_date = '';
1445
                //print "e".$set_time." t ".$conf->format_date_short;
1446
                if (strval($set_time) != '' && $set_time != -1) {
1447
                    //$formated_date=dol_print_date($set_time,$conf->format_date_short);
1448
                    $formated_date = dol_print_date($set_time, $langs->trans("FormatDateShortInput"), $gm); // FormatDateShortInput for dol_print_date / FormatDateShortJavaInput that is same for javascript
1449
                }
1450
1451
                // Calendrier popup version eldy
1452
                if ($usecalendar == "eldy") {
1453
                    // Input area to enter date manually
1454
                    $retstring .= '<input id="' . $prefix . '" name="' . $prefix . '" type="text" class="maxwidthdate center" maxlength="11" value="' . $formated_date . '"';
1455
                    $retstring .= ($disabled ? ' disabled' : '');
1456
                    $retstring .= ' onChange="dpChangeDay(\'' . $prefix . '\',\'' . $langs->trans("FormatDateShortJavaInput") . '\'); "'; // FormatDateShortInput for dol_print_date / FormatDateShortJavaInput that is same for javascript
1457
                    $retstring .= ' autocomplete="off">';
1458
1459
                    // Icon calendar
1460
                    $retstringbuttom = '';
1461
                    if (!$disabled) {
1462
                        $retstringbuttom = '<button id="' . $prefix . 'Button" type="button" class="dpInvisibleButtons"';
1463
                        $base = constant('BASE_URL') . '/core/';
1464
                        $retstringbuttom .= ' onClick="showDP(\'' . $base . '\',\'' . $prefix . '\',\'' . $langs->trans("FormatDateShortJavaInput") . '\',\'' . $langs->defaultlang . '\');"';
1465
                        $retstringbuttom .= '>' . img_object($langs->trans("SelectDate"), 'calendarday', 'class="datecallink"') . '</button>';
1466
                    } else {
1467
                        $retstringbuttom = '<button id="' . $prefix . 'Button" type="button" class="dpInvisibleButtons">' . img_object($langs->trans("Disabled"), 'calendarday', 'class="datecallink"') . '</button>';
1468
                    }
1469
                    $retstring = $retstringbuttom . $retstring;
1470
1471
                    $retstring .= '<input type="hidden" id="' . $prefix . 'day"   name="' . $prefix . 'day"   value="' . $sday . '">' . "\n";
1472
                    $retstring .= '<input type="hidden" id="' . $prefix . 'month" name="' . $prefix . 'month" value="' . $smonth . '">' . "\n";
1473
                    $retstring .= '<input type="hidden" id="' . $prefix . 'year"  name="' . $prefix . 'year"  value="' . $syear . '">' . "\n";
1474
                } elseif ($usecalendar == 'jquery' || $usecalendar == 'html') {
1475
                    if (!$disabled && $usecalendar != 'html') {
1476
                        // Output javascript for datepicker
1477
                        $minYear = getDolGlobalInt('MIN_YEAR_SELECT_DATE', (idate('Y') - 100));
1478
                        $maxYear = getDolGlobalInt('MAX_YEAR_SELECT_DATE', (idate('Y') + 100));
1479
1480
                        $retstring .= '<script nonce="' . getNonce() . '" type="text/javascript">';
1481
                        $retstring .= "$(function(){ $('#" . $prefix . "').datepicker({
1482
							dateFormat: '" . $langs->trans("FormatDateShortJQueryInput") . "',
1483
							autoclose: true,
1484
							todayHighlight: true,
1485
							yearRange: '" . $minYear . ":" . $maxYear . "',";
1486
                        if (!empty($conf->dol_use_jmobile)) {
1487
                            $retstring .= "
1488
								beforeShow: function (input, datePicker) {
1489
									input.disabled = true;
1490
								},
1491
								onClose: function (dateText, datePicker) {
1492
									this.disabled = false;
1493
								},
1494
								";
1495
                        }
1496
                        // Note: We don't need monthNames, monthNamesShort, dayNames, dayNamesShort, dayNamesMin, they are set globally on datepicker component in lib_head.js.php
1497
                        if (!getDolGlobalString('MAIN_POPUP_CALENDAR_ON_FOCUS')) {
1498
                            $retstring .= "
1499
								showOn: 'button',	/* both has problem with autocompletion */
1500
								buttonImage: '" . constant('DOL_URL_ROOT') . "/theme/" . dol_escape_js($conf->theme) . "/img/object_calendarday.png',
1501
								buttonImageOnly: true";
1502
                        }
1503
                        $retstring .= "
1504
							}) });";
1505
                        $retstring .= "</script>";
1506
                    }
1507
1508
                    // Input area to enter date manually
1509
                    $retstring .= '<div class="nowraponall inline-block divfordateinput">';
1510
                    $retstring .= '<input id="' . $prefix . '" name="' . $prefix . '" type="text" class="maxwidthdate center" maxlength="11" value="' . $formated_date . '"';
1511
                    $retstring .= ($disabled ? ' disabled' : '');
1512
                    $retstring .= ($placeholder ? ' placeholder="' . dol_escape_htmltag($placeholder) . '"' : '');
1513
                    $retstring .= ' onChange="dpChangeDay(\'' . dol_escape_js($prefix) . '\',\'' . dol_escape_js($langs->trans("FormatDateShortJavaInput")) . '\'); "'; // FormatDateShortInput for dol_print_date / FormatDateShortJavaInput that is same for javascript
1514
                    $retstring .= ' autocomplete="off">';
1515
1516
                    // Icone calendrier
1517
                    if ($disabled) {
1518
                        $retstringbutton = '<button id="' . $prefix . 'Button" type="button" class="dpInvisibleButtons">' . img_object($langs->trans("Disabled"), 'calendarday', 'class="datecallink"') . '</button>';
1519
                        $retstring = $retstringbutton . $retstring;
1520
                    }
1521
1522
                    $retstring .= '</div>';
1523
                    $retstring .= '<input type="hidden" id="' . $prefix . 'day"   name="' . $prefix . 'day"   value="' . $sday . '">' . "\n";
1524
                    $retstring .= '<input type="hidden" id="' . $prefix . 'month" name="' . $prefix . 'month" value="' . $smonth . '">' . "\n";
1525
                    $retstring .= '<input type="hidden" id="' . $prefix . 'year"  name="' . $prefix . 'year"  value="' . $syear . '">' . "\n";
1526
                } else {
1527
                    $retstring .= "Bad value of MAIN_POPUP_CALENDAR";
1528
                }
1529
            } else {
1530
                // Show date with combo selects
1531
                // Day
1532
                $retstring .= '<select' . ($disabled ? ' disabled' : '') . ' class="flat valignmiddle maxwidth50imp" id="' . $prefix . 'day" name="' . $prefix . 'day">';
1533
1534
                if ($emptydate || $set_time == -1) {
1535
                    $retstring .= '<option value="0" selected>&nbsp;</option>';
1536
                }
1537
1538
                for ($day = 1; $day <= 31; $day++) {
1539
                    $retstring .= '<option value="' . $day . '"' . ($day == $sday ? ' selected' : '') . '>' . $day . '</option>';
1540
                }
1541
1542
                $retstring .= "</select>";
1543
1544
                $retstring .= '<select' . ($disabled ? ' disabled' : '') . ' class="flat valignmiddle maxwidth75imp" id="' . $prefix . 'month" name="' . $prefix . 'month">';
1545
                if ($emptydate || $set_time == -1) {
1546
                    $retstring .= '<option value="0" selected>&nbsp;</option>';
1547
                }
1548
1549
                // Month
1550
                for ($month = 1; $month <= 12; $month++) {
1551
                    $retstring .= '<option value="' . $month . '"' . ($month == $smonth ? ' selected' : '') . '>';
1552
                    $retstring .= dol_print_date(mktime(12, 0, 0, $month, 1, 2000), "%b");
1553
                    $retstring .= "</option>";
1554
                }
1555
                $retstring .= "</select>";
1556
1557
                // Year
1558
                if ($emptydate || $set_time == -1) {
1559
                    $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 . '">';
1560
                } else {
1561
                    $retstring .= '<select' . ($disabled ? ' disabled' : '') . ' class="flat valignmiddle maxwidth75imp" id="' . $prefix . 'year" name="' . $prefix . 'year">';
1562
1563
                    $syear = (int)$syear;
1564
                    for ($year = $syear - 10; $year < (int)$syear + 10; $year++) {
1565
                        $retstring .= '<option value="' . $year . '"' . ($year == $syear ? ' selected' : '') . '>' . $year . '</option>';
1566
                    }
1567
                    $retstring .= "</select>\n";
1568
                }
1569
            }
1570
        }
1571
1572
        if ($d && $h) {
1573
            $retstring .= (($h == 2 || $h == 4) ? '<br>' : ' ');
1574
            $retstring .= '<span class="nowraponall">';
1575
        }
1576
1577
        if ($h) {
1578
            $hourstart = 0;
1579
            $hourend = 24;
1580
            if ($openinghours != '') {
1581
                $openinghours = explode(',', $openinghours);
1582
                $hourstart = $openinghours[0];
1583
                $hourend = $openinghours[1];
1584
                if ($hourend < $hourstart) {
1585
                    $hourend = $hourstart;
1586
                }
1587
            }
1588
            // Show hour
1589
            $retstring .= '<select' . ($disabled ? ' disabled' : '') . ' class="flat valignmiddle maxwidth50 ' . ($fullday ? $fullday . 'hour' : '') . '" id="' . $prefix . 'hour" name="' . $prefix . 'hour">';
1590
            if ($emptyhours) {
1591
                $retstring .= '<option value="-1">&nbsp;</option>';
1592
            }
1593
            for ($hour = $hourstart; $hour < $hourend; $hour++) {
1594
                if (strlen($hour) < 2) {
1595
                    $hour = "0" . $hour;
1596
                }
1597
                $retstring .= '<option value="' . $hour . '"' . (($hour == $shour) ? ' selected' : '') . '>' . $hour;
1598
                //$retstring .= (empty($conf->dol_optimize_smallscreen) ? '' : 'H');
1599
                $retstring .= '</option>';
1600
            }
1601
            $retstring .= '</select>';
1602
            //if ($m && empty($conf->dol_optimize_smallscreen)) $retstring .= ":";
1603
            if ($m) {
1604
                $retstring .= ":";
1605
            }
1606
        }
1607
1608
        if ($m) {
1609
            // Show minutes
1610
            $retstring .= '<select' . ($disabled ? ' disabled' : '') . ' class="flat valignmiddle maxwidth50 ' . ($fullday ? $fullday . 'min' : '') . '" id="' . $prefix . 'min" name="' . $prefix . 'min">';
1611
            if ($emptyhours) {
1612
                $retstring .= '<option value="-1">&nbsp;</option>';
1613
            }
1614
            for ($min = 0; $min < 60; $min += $stepminutes) {
1615
                $min_str = sprintf("%02d", $min);
1616
                $retstring .= '<option value="' . $min_str . '"' . (($min_str == $smin) ? ' selected' : '') . '>' . $min_str . '</option>';
1617
            }
1618
            $retstring .= '</select>';
1619
1620
            $retstring .= '<input type="hidden" name="' . $prefix . 'sec" value="' . $ssec . '">';
1621
        }
1622
1623
        if ($d && $h) {
1624
            $retstring .= '</span>';
1625
        }
1626
1627
        // Add a "Now" link
1628
        if (!empty($conf->use_javascript_ajax) && $addnowlink) {
1629
            // Script which will be inserted in the onClick of the "Now" link
1630
            $reset_scripts = "";
1631
            if ($addnowlink == 2) { // local computer time
1632
                // pad add leading 0 on numbers
1633
                $reset_scripts .= "Number.prototype.pad = function(size) {
1634
                        var s = String(this);
1635
                        while (s.length < (size || 2)) {s = '0' + s;}
1636
                        return s;
1637
                    };
1638
                    var d = new Date();";
1639
            }
1640
1641
            // Generate the date part, depending on the use or not of the javascript calendar
1642
            if ($addnowlink == 1) { // server time expressed in user time setup
1643
                $reset_scripts .= 'jQuery(\'#' . $prefix . '\').val(\'' . dol_print_date($nowgmt, 'day', 'tzuserrel') . '\');';
1644
                $reset_scripts .= 'jQuery(\'#' . $prefix . 'day\').val(\'' . dol_print_date($nowgmt, '%d', 'tzuserrel') . '\');';
1645
                $reset_scripts .= 'jQuery(\'#' . $prefix . 'month\').val(\'' . dol_print_date($nowgmt, '%m', 'tzuserrel') . '\');';
1646
                $reset_scripts .= 'jQuery(\'#' . $prefix . 'year\').val(\'' . dol_print_date($nowgmt, '%Y', 'tzuserrel') . '\');';
1647
            } elseif ($addnowlink == 2) {
1648
                /* Disabled because the output does not use the string format defined by FormatDateShort key to forge the value into #prefix.
1649
                 * This break application for foreign languages.
1650
                $reset_scripts .= 'jQuery(\'#'.$prefix.'\').val(d.toLocaleDateString(\''.str_replace('_', '-', $langs->defaultlang).'\'));';
1651
                $reset_scripts .= 'jQuery(\'#'.$prefix.'day\').val(d.getDate().pad());';
1652
                $reset_scripts .= 'jQuery(\'#'.$prefix.'month\').val(parseInt(d.getMonth().pad()) + 1);';
1653
                $reset_scripts .= 'jQuery(\'#'.$prefix.'year\').val(d.getFullYear());';
1654
                */
1655
                $reset_scripts .= 'jQuery(\'#' . $prefix . '\').val(\'' . dol_print_date($nowgmt, 'day', 'tzuserrel') . '\');';
1656
                $reset_scripts .= 'jQuery(\'#' . $prefix . 'day\').val(\'' . dol_print_date($nowgmt, '%d', 'tzuserrel') . '\');';
1657
                $reset_scripts .= 'jQuery(\'#' . $prefix . 'month\').val(\'' . dol_print_date($nowgmt, '%m', 'tzuserrel') . '\');';
1658
                $reset_scripts .= 'jQuery(\'#' . $prefix . 'year\').val(\'' . dol_print_date($nowgmt, '%Y', 'tzuserrel') . '\');';
1659
            }
1660
            /*if ($usecalendar == "eldy")
1661
            {
1662
                $base=DOL_URL_ROOT.'/core/';
1663
                $reset_scripts .= 'resetDP(\''.$base.'\',\''.$prefix.'\',\''.$langs->trans("FormatDateShortJavaInput").'\',\''.$langs->defaultlang.'\');';
1664
            }
1665
            else
1666
            {
1667
                $reset_scripts .= 'this.form.elements[\''.$prefix.'day\'].value=formatDate(new Date(), \'d\'); ';
1668
                $reset_scripts .= 'this.form.elements[\''.$prefix.'month\'].value=formatDate(new Date(), \'M\'); ';
1669
                $reset_scripts .= 'this.form.elements[\''.$prefix.'year\'].value=formatDate(new Date(), \'yyyy\'); ';
1670
            }*/
1671
            // Update the hour part
1672
            if ($h) {
1673
                if ($fullday) {
1674
                    $reset_scripts .= " if (jQuery('#fullday:checked').val() == null) {";
1675
                }
1676
                //$reset_scripts .= 'this.form.elements[\''.$prefix.'hour\'].value=formatDate(new Date(), \'HH\'); ';
1677
                if ($addnowlink == 1) {
1678
                    $reset_scripts .= 'jQuery(\'#' . $prefix . 'hour\').val(\'' . dol_print_date($nowgmt, '%H', 'tzuserrel') . '\');';
1679
                    $reset_scripts .= 'jQuery(\'#' . $prefix . 'hour\').change();';
1680
                } elseif ($addnowlink == 2) {
1681
                    $reset_scripts .= 'jQuery(\'#' . $prefix . 'hour\').val(d.getHours().pad());';
1682
                    $reset_scripts .= 'jQuery(\'#' . $prefix . 'hour\').change();';
1683
                }
1684
1685
                if ($fullday) {
1686
                    $reset_scripts .= ' } ';
1687
                }
1688
            }
1689
            // Update the minute part
1690
            if ($m) {
1691
                if ($fullday) {
1692
                    $reset_scripts .= " if (jQuery('#fullday:checked').val() == null) {";
1693
                }
1694
                //$reset_scripts .= 'this.form.elements[\''.$prefix.'min\'].value=formatDate(new Date(), \'mm\'); ';
1695
                if ($addnowlink == 1) {
1696
                    $reset_scripts .= 'jQuery(\'#' . $prefix . 'min\').val(\'' . dol_print_date($nowgmt, '%M', 'tzuserrel') . '\');';
1697
                    $reset_scripts .= 'jQuery(\'#' . $prefix . 'min\').change();';
1698
                } elseif ($addnowlink == 2) {
1699
                    $reset_scripts .= 'jQuery(\'#' . $prefix . 'min\').val(d.getMinutes().pad());';
1700
                    $reset_scripts .= 'jQuery(\'#' . $prefix . 'min\').change();';
1701
                }
1702
                if ($fullday) {
1703
                    $reset_scripts .= ' } ';
1704
                }
1705
            }
1706
            // If reset_scripts is not empty, print the link with the reset_scripts in the onClick
1707
            if ($reset_scripts && !getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1708
                $retstring .= ' <button class="dpInvisibleButtons datenowlink" id="' . $prefix . 'ButtonNow" type="button" name="_useless" value="now" onClick="' . $reset_scripts . '">';
1709
                $retstring .= $langs->trans("Now");
1710
                $retstring .= '</button> ';
1711
            }
1712
        }
1713
1714
        // Add a "Plus one hour" link
1715
        if ($conf->use_javascript_ajax && $addplusone) {
1716
            // Script which will be inserted in the onClick of the "Add plusone" link
1717
            $reset_scripts = "";
1718
1719
            // Generate the date part, depending on the use or not of the javascript calendar
1720
            $reset_scripts .= 'jQuery(\'#' . $prefix . '\').val(\'' . dol_print_date($nowgmt, 'dayinputnoreduce', 'tzuserrel') . '\');';
1721
            $reset_scripts .= 'jQuery(\'#' . $prefix . 'day\').val(\'' . dol_print_date($nowgmt, '%d', 'tzuserrel') . '\');';
1722
            $reset_scripts .= 'jQuery(\'#' . $prefix . 'month\').val(\'' . dol_print_date($nowgmt, '%m', 'tzuserrel') . '\');';
1723
            $reset_scripts .= 'jQuery(\'#' . $prefix . 'year\').val(\'' . dol_print_date($nowgmt, '%Y', 'tzuserrel') . '\');';
1724
            // Update the hour part
1725
            if ($h) {
1726
                if ($fullday) {
1727
                    $reset_scripts .= " if (jQuery('#fullday:checked').val() == null) {";
1728
                }
1729
                $reset_scripts .= 'jQuery(\'#' . $prefix . 'hour\').val(\'' . dol_print_date($nowgmt, '%H', 'tzuserrel') . '\');';
1730
                if ($fullday) {
1731
                    $reset_scripts .= ' } ';
1732
                }
1733
            }
1734
            // Update the minute part
1735
            if ($m) {
1736
                if ($fullday) {
1737
                    $reset_scripts .= " if (jQuery('#fullday:checked').val() == null) {";
1738
                }
1739
                $reset_scripts .= 'jQuery(\'#' . $prefix . 'min\').val(\'' . dol_print_date($nowgmt, '%M', 'tzuserrel') . '\');';
1740
                if ($fullday) {
1741
                    $reset_scripts .= ' } ';
1742
                }
1743
            }
1744
            // If reset_scripts is not empty, print the link with the reset_scripts in the onClick
1745
            if ($reset_scripts && empty($conf->dol_optimize_smallscreen)) {
1746
                $retstring .= ' <button class="dpInvisibleButtons datenowlink" id="' . $prefix . 'ButtonPlusOne" type="button" name="_useless2" value="plusone" onClick="' . $reset_scripts . '">';
1747
                $retstring .= $langs->trans("DateStartPlusOne");
1748
                $retstring .= '</button> ';
1749
            }
1750
        }
1751
1752
        // Add a link to set data
1753
        if ($conf->use_javascript_ajax && !empty($adddateof)) {
1754
            if (!is_array($adddateof)) {
1755
                $arrayofdateof = array(array('adddateof' => $adddateof, 'labeladddateof' => $labeladddateof));
1756
            } else {
1757
                $arrayofdateof = $adddateof;
1758
            }
1759
            foreach ($arrayofdateof as $valuedateof) {
1760
                $tmpadddateof = empty($valuedateof['adddateof']) ? 0 : $valuedateof['adddateof'];
1761
                $tmplabeladddateof = empty($valuedateof['labeladddateof']) ? '' : $valuedateof['labeladddateof'];
1762
                $tmparray = dol_getdate($tmpadddateof);
1763
                if (empty($tmplabeladddateof)) {
1764
                    $tmplabeladddateof = $langs->trans("DateInvoice");
1765
                }
1766
                $reset_scripts = 'console.log(\'Click on now link\'); ';
1767
                $reset_scripts .= 'jQuery(\'#' . $prefix . '\').val(\'' . dol_print_date($tmpadddateof, 'dayinputnoreduce') . '\');';
1768
                $reset_scripts .= 'jQuery(\'#' . $prefix . 'day\').val(\'' . $tmparray['mday'] . '\');';
1769
                $reset_scripts .= 'jQuery(\'#' . $prefix . 'month\').val(\'' . $tmparray['mon'] . '\');';
1770
                $reset_scripts .= 'jQuery(\'#' . $prefix . 'year\').val(\'' . $tmparray['year'] . '\');';
1771
                $retstring .= ' - <button class="dpInvisibleButtons datenowlink" id="dateofinvoice" type="button" name="_dateofinvoice" value="now" onclick="' . $reset_scripts . '">' . $tmplabeladddateof . '</button>';
1772
            }
1773
        }
1774
1775
        return $retstring;
1776
    }
1777
1778
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1779
1780
    /**
1781
     *  Return a HTML select string, built from an array of key+value.
1782
     *  Note: Do not apply langs->trans function on returned content, content may be entity encoded twice.
1783
     *
1784
     * @param string $htmlname Name of html select area. Try to start name with "multi" or "search_multi" if this is a multiselect
1785
     * @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'=>...))
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{label:string,data-...?:int<0,1>,css?:string} at position 12 could not be parsed: Expected '}' at position 12, but found 'int'.
Loading history...
1786
     * @param string|string[] $id Preselected key or array of preselected keys for multiselect. Use 'ifone' to autoselect record if there is only one record.
1787
     * @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.
1788
     * @param int<0,1> $key_in_label 1 to show key into label with format "[key] value"
1789
     * @param int<0,1> $value_as_key 1 to use value as key
1790
     * @param string $moreparam Add more parameters onto the select tag. For example 'style="width: 95%"' to avoid select2 component to go over parent container
1791
     * @param int<0,1> $translate 1=Translate and encode value
1792
     * @param int $maxlen Length maximum for labels
1793
     * @param int<0,1> $disabled Html select box is disabled
1794
     * @param string $sort 'ASC' or 'DESC' = Sort on label, '' or 'NONE' or 'POS' = Do not sort, we keep original order
1795
     * @param string $morecss Add more class to css styles
1796
     * @param int $addjscombo Add js combo
1797
     * @param string $moreparamonempty Add more param on the empty option line. Not used if show_empty not set
1798
     * @param int $disablebademail 1=Check if a not valid email, 2=Check string '---', and if found into value, disable and colorize entry
1799
     * @param int $nohtmlescape No html escaping (not recommended, use 'data-html' if you need to use label with HTML content).
1800
     * @return string                           HTML select string.
1801
     * @see multiselectarray(), selectArrayAjax(), selectArrayFilter()
1802
     */
1803
    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)
1804
    {
1805
        global $conf, $langs;
1806
1807
        // Do we want a multiselect ?
1808
        //$jsbeautify = 0;
1809
        //if (preg_match('/^multi/',$htmlname)) $jsbeautify = 1;
1810
        $jsbeautify = 1;
1811
1812
        if ($value_as_key) {
1813
            $array = array_combine($array, $array);
1814
        }
1815
1816
        '@phan-var-force array{label:string,data-html:string,disable?:int<0,1>,css?:string}	$array'; // Array combine breaks information
1817
1818
        $out = '';
1819
1820
        if ($addjscombo < 0) {
1821
            if (!getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1822
                $addjscombo = 1;
1823
            } else {
1824
                $addjscombo = 0;
1825
            }
1826
        }
1827
        $idname = str_replace(array('[', ']'), array('', ''), $htmlname);
1828
        $out .= '<select id="' . preg_replace('/^\./', '', $idname) . '" ' . ($disabled ? 'disabled="disabled" ' : '') . 'class="flat ' . (preg_replace('/^\./', '', $htmlname)) . ($morecss ? ' ' . $morecss : '') . ' selectformat"';
1829
        $out .= ' name="' . preg_replace('/^\./', '', $htmlname) . '" ' . ($moreparam ? $moreparam : '');
1830
        $out .= '>' . "\n";
1831
1832
        if ($show_empty) {
1833
            $textforempty = ' ';
1834
            if (!empty($conf->use_javascript_ajax)) {
1835
                $textforempty = '&nbsp;'; // If we use ajaxcombo, we need &nbsp; here to avoid to have an empty element that is too small.
1836
            }
1837
            if (!is_numeric($show_empty)) {
1838
                $textforempty = $show_empty;
1839
            }
1840
            $out .= '<option class="optiongrey" ' . ($moreparamonempty ? $moreparamonempty . ' ' : '') . 'value="' . (((int)$show_empty) < 0 ? $show_empty : -1) . '"' . ($id == $show_empty ? ' selected' : '') . '>' . $textforempty . '</option>' . "\n";
1841
        }
1842
        if (is_array($array)) {
1843
            // Translate
1844
            if ($translate) {
1845
                foreach ($array as $key => $value) {
1846
                    if (!is_array($value)) {
1847
                        $array[$key] = $langs->trans($value);
1848
                    } else {
1849
                        $array[$key]['label'] = $langs->trans($value['label']);
1850
                    }
1851
                }
1852
            }
1853
            // Sort
1854
            if ($sort == 'ASC') {
1855
                asort($array);
1856
            } elseif ($sort == 'DESC') {
1857
                arsort($array);
1858
            }
1859
1860
            foreach ($array as $key => $tmpvalue) {
1861
                if (is_array($tmpvalue)) {
1862
                    $value = $tmpvalue['label'];
1863
                    //$valuehtml = empty($tmpvalue['data-html']) ? $value : $tmpvalue['data-html'];
1864
                    $disabled = empty($tmpvalue['disabled']) ? '' : ' disabled';
1865
                    $style = empty($tmpvalue['css']) ? '' : ' class="' . $tmpvalue['css'] . '"';
1866
                } else {
1867
                    $value = $tmpvalue;
1868
                    //$valuehtml = $tmpvalue;
1869
                    $disabled = '';
1870
                    $style = '';
1871
                }
1872
                if (!empty($disablebademail)) {
1873
                    if (
1874
                        ($disablebademail == 1 && !preg_match('/&lt;.+@.+&gt;/', $value))
1875
                        || ($disablebademail == 2 && preg_match('/---/', $value))
1876
                    ) {
1877
                        $disabled = ' disabled';
1878
                        $style = ' class="warning"';
1879
                    }
1880
                }
1881
                if ($key_in_label) {
1882
                    if (empty($nohtmlescape)) {
1883
                        $selectOptionValue = dol_escape_htmltag($key . ' - ' . ($maxlen ? dol_trunc($value, $maxlen) : $value));
1884
                    } else {
1885
                        $selectOptionValue = $key . ' - ' . ($maxlen ? dol_trunc($value, $maxlen) : $value);
1886
                    }
1887
                } else {
1888
                    if (empty($nohtmlescape)) {
1889
                        $selectOptionValue = dol_escape_htmltag($maxlen ? dol_trunc($value, $maxlen) : $value);
1890
                    } else {
1891
                        $selectOptionValue = $maxlen ? dol_trunc($value, $maxlen) : $value;
1892
                    }
1893
                    if ($value == '' || $value == '-') {
1894
                        $selectOptionValue = '&nbsp;';
1895
                    }
1896
                }
1897
                $out .= '<option value="' . $key . '"';
1898
                $out .= $style . $disabled;
1899
                if (is_array($id)) {
1900
                    if (in_array($key, $id) && !$disabled) {
1901
                        $out .= ' selected'; // To preselect a value
1902
                    }
1903
                } else {
1904
                    $id = (string)$id; // if $id = 0, then $id = '0'
1905
                    if ($id != '' && ($id == $key || ($id == 'ifone' && count($array) == 1)) && !$disabled) {
1906
                        $out .= ' selected'; // To preselect a value
1907
                    }
1908
                }
1909
                if (!empty($nohtmlescape)) {    // deprecated. Use instead the key 'data-html' into input $array, managed at next step to use HTML content.
1910
                    $out .= ' data-html="' . dol_escape_htmltag($selectOptionValue) . '"';
1911
                }
1912
1913
                if (is_array($tmpvalue)) {
1914
                    foreach ($tmpvalue as $keyforvalue => $valueforvalue) {
1915
                        if (preg_match('/^data-/', $keyforvalue)) { // The best solution if you want to use HTML values into the list is to use data-html.
1916
                            $out .= ' ' . dol_escape_htmltag($keyforvalue) . '="' . dol_escape_htmltag($valueforvalue) . '"';
1917
                        }
1918
                    }
1919
                }
1920
                $out .= '>';
1921
                $out .= $selectOptionValue;
1922
                $out .= "</option>\n";
1923
            }
1924
        }
1925
        $out .= "</select>";
1926
1927
        // Add code for jquery to use multiselect
1928
        if ($addjscombo && $jsbeautify) {
1929
            // Enhance with select2
1930
            include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
1931
            $out .= ajax_combobox($idname, array(), 0, 0, 'resolve', (((int)$show_empty) < 0 ? (string)$show_empty : '-1'), $morecss);
1932
        }
1933
1934
        return $out;
1935
    }
1936
1937
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1938
1939
    /**
1940
     * Output edit in place form
1941
     *
1942
     * @param string $fieldname Name of the field
1943
     * @param CommonObject $object Object
1944
     * @param boolean $perm Permission to allow button to edit parameter. Set it to 0 to have a not edited field.
1945
     * @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]'...)
1946
     * @param string $check Same coe than $check parameter of GETPOST()
1947
     * @param string $morecss More CSS
1948
     * @return  string              HTML code for the edit of alternative language
1949
     */
1950
    public function widgetForTranslation($fieldname, $object, $perm, $typeofdata = 'string', $check = '', $morecss = '')
1951
    {
1952
        global $conf, $langs, $extralanguages;
1953
1954
        $result = '';
1955
1956
        // List of extra languages
1957
        $arrayoflangcode = array();
1958
        if (getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE')) {
1959
            $arrayoflangcode[] = getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE');
1960
        }
1961
1962
        if (is_array($arrayoflangcode) && count($arrayoflangcode)) {
1963
            if (!is_object($extralanguages)) {
1964
                include_once DOL_DOCUMENT_ROOT . '/core/class/extralanguages.class.php';
1965
                $extralanguages = new ExtraLanguages($this->db);
1966
            }
1967
            $extralanguages->fetch_name_extralanguages('societe');
1968
1969
            if (!is_array($extralanguages->attributes[$object->element]) || empty($extralanguages->attributes[$object->element][$fieldname])) {
1970
                return ''; // No extralang field to show
1971
            }
1972
1973
            $result .= '<!-- Widget for translation -->' . "\n";
1974
            $result .= '<div class="inline-block paddingleft image-' . $object->element . '-' . $fieldname . '">';
1975
            $s = img_picto($langs->trans("ShowOtherLanguages"), 'language', '', false, 0, 0, '', 'fa-15 editfieldlang');
1976
            $result .= $s;
1977
            $result .= '</div>';
1978
1979
            $result .= '<div class="inline-block hidden field-' . $object->element . '-' . $fieldname . '">';
1980
1981
            $resultforextrlang = '';
1982
            foreach ($arrayoflangcode as $langcode) {
1983
                $valuetoshow = GETPOSTISSET('field-' . $object->element . "-" . $fieldname . "-" . $langcode) ? GETPOST('field-' . $object->element . '-' . $fieldname . "-" . $langcode, $check) : '';
1984
                if (empty($valuetoshow)) {
1985
                    $object->fetchValuesForExtraLanguages();
1986
                    //var_dump($object->array_languages);
1987
                    $valuetoshow = $object->array_languages[$fieldname][$langcode];
1988
                }
1989
1990
                $s = picto_from_langcode($langcode, 'class="pictoforlang paddingright"');
1991
                $resultforextrlang .= $s;
1992
1993
                // TODO Use the showInputField() method of ExtraLanguages object
1994
                if ($typeofdata == 'textarea') {
1995
                    $resultforextrlang .= '<textarea name="field-' . $object->element . "-" . $fieldname . "-" . $langcode . '" id="' . $fieldname . "-" . $langcode . '" class="' . $morecss . '" rows="' . ROWS_2 . '" wrap="soft">';
1996
                    $resultforextrlang .= $valuetoshow;
1997
                    $resultforextrlang .= '</textarea>';
1998
                } else {
1999
                    $resultforextrlang .= '<input type="text" class="inputfieldforlang ' . ($morecss ? ' ' . $morecss : '') . '" name="field-' . $object->element . '-' . $fieldname . '-' . $langcode . '" value="' . $valuetoshow . '">';
2000
                }
2001
            }
2002
            $result .= $resultforextrlang;
2003
2004
            $result .= '</div>';
2005
            $result .= '<script nonce="' . getNonce() . '">$(".image-' . $object->element . '-' . $fieldname . '").click(function() { console.log("Toggle lang widget"); jQuery(".field-' . $object->element . '-' . $fieldname . '").toggle(); });</script>';
2006
        }
2007
2008
        return $result;
2009
    }
2010
2011
2012
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2013
2014
    /**
2015
     * Generate select HTML to choose massaction
2016
     *
2017
     * @param string $selected Value auto selected when at least one record is selected. Not a preselected value. Use '0' by default.
2018
     * @param array<string,string> $arrayofaction array('code'=>'label', ...). The code is the key stored into the GETPOST('massaction') when submitting action.
2019
     * @param int $alwaysvisible 1=select button always visible
2020
     * @param string $name Name for massaction
2021
     * @param string $cssclass CSS class used to check for select
2022
     * @return string|void              Select list
2023
     */
2024
    public function selectMassAction($selected, $arrayofaction, $alwaysvisible = 0, $name = 'massaction', $cssclass = 'checkforselect')
2025
    {
2026
        global $conf, $langs, $hookmanager;
2027
2028
        $disabled = 0;
2029
        $ret = '<div class="centpercent center">';
2030
        $ret .= '<select class="flat' . (empty($conf->use_javascript_ajax) ? '' : ' hideobject') . ' ' . $name . ' ' . $name . 'select valignmiddle alignstart" id="' . $name . '" name="' . $name . '"' . ($disabled ? ' disabled="disabled"' : '') . '>';
2031
2032
        // 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.
2033
        $parameters = array();
2034
        $reshook = $hookmanager->executeHooks('addMoreMassActions', $parameters); // Note that $action and $object may have been modified by hook
2035
        // check if there is a mass action
2036
2037
        if (is_array($arrayofaction) && count($arrayofaction) == 0 && empty($hookmanager->resPrint)) {
2038
            return;
2039
        }
2040
        if (empty($reshook)) {
2041
            $ret .= '<option value="0"' . ($disabled ? ' disabled="disabled"' : '') . '>-- ' . $langs->trans("SelectAction") . ' --</option>';
2042
            if (is_array($arrayofaction)) {
2043
                foreach ($arrayofaction as $code => $label) {
2044
                    $ret .= '<option value="' . $code . '"' . ($disabled ? ' disabled="disabled"' : '') . ' data-html="' . dol_escape_htmltag($label) . '">' . $label . '</option>';
2045
                }
2046
            }
2047
        }
2048
        $ret .= $hookmanager->resPrint;
2049
2050
        $ret .= '</select>';
2051
2052
        if (empty($conf->dol_optimize_smallscreen)) {
2053
            $ret .= ajax_combobox('.' . $name . 'select');
2054
        }
2055
2056
        // 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
2057
        $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.
2058
        $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")) . '">';
2059
        $ret .= '</div>';
2060
2061
        if (!empty($conf->use_javascript_ajax)) {
2062
            $ret .= '<!-- JS CODE TO ENABLE mass action select -->
2063
    		<script nonce="' . getNonce() . '">
2064
                        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 */
2065
        		{
2066
        			atleastoneselected=0;
2067
                                jQuery("."+cssclass).each(function( index ) {
2068
    	  				/* console.log( index + ": " + $( this ).text() ); */
2069
    	  				if ($(this).is(\':checked\')) atleastoneselected++;
2070
    	  			});
2071
2072
					console.log("initCheckForSelect mode="+mode+" name="+name+" cssclass="+cssclass+" atleastoneselected="+atleastoneselected);
2073
2074
    	  			if (atleastoneselected || ' . $alwaysvisible . ')
2075
    	  			{
2076
                                    jQuery("."+name).show();
2077
        			    ' . ($selected ? 'if (atleastoneselected) { jQuery("."+name+"select").val("' . $selected . '").trigger(\'change\'); jQuery("."+name+"confirmed").prop(\'disabled\', false); }' : '') . '
2078
        			    ' . ($selected ? 'if (! atleastoneselected) { jQuery("."+name+"select").val("0").trigger(\'change\'); jQuery("."+name+"confirmed").prop(\'disabled\', true); } ' : '') . '
2079
    	  			}
2080
    	  			else
2081
    	  			{
2082
                                    jQuery("."+name).hide();
2083
                                    jQuery("."+name+"other").hide();
2084
    	            }
2085
        		}
2086
2087
        	jQuery(document).ready(function () {
2088
                    initCheckForSelect(0, "' . $name . '", "' . $cssclass . '");
2089
                    jQuery(".' . $cssclass . '").click(function() {
2090
                        initCheckForSelect(1, "' . $name . '", "' . $cssclass . '");
2091
                    });
2092
                        jQuery(".' . $name . 'select").change(function() {
2093
        			var massaction = $( this ).val();
2094
        			var urlform = $( this ).closest("form").attr("action").replace("#show_files","");
2095
        			if (massaction == "builddoc")
2096
                    {
2097
                        urlform = urlform + "#show_files";
2098
    	            }
2099
        			$( this ).closest("form").attr("action", urlform);
2100
                    console.log("we select a mass action name=' . $name . ' massaction="+massaction+" - "+urlform);
2101
        	        /* Warning: if you set submit button to disabled, post using Enter will no more work if there is no other button */
2102
        			if ($(this).val() != \'0\')
2103
    	  			{
2104
                                        jQuery(".' . $name . 'confirmed").prop(\'disabled\', false);
2105
										jQuery(".' . $name . 'other").hide();	/* To disable if another div was open */
2106
                                        jQuery(".' . $name . '"+massaction).show();
2107
    	  			}
2108
    	  			else
2109
    	  			{
2110
                                        jQuery(".' . $name . 'confirmed").prop(\'disabled\', true);
2111
										jQuery(".' . $name . 'other").hide();	/* To disable any div open */
2112
    	  			}
2113
    	        });
2114
        	});
2115
    		</script>
2116
        	';
2117
        }
2118
2119
        return $ret;
2120
    }
2121
2122
2123
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2124
2125
    /**
2126
     *  Return combo list of activated countries, into language of user
2127
     *
2128
     * @param string $selected Id or Code or Label of preselected country
2129
     * @param string $htmlname Name of html select object
2130
     * @param string $htmloption More html options on select object
2131
     * @param integer $maxlength Max length for labels (0=no limit)
2132
     * @param string $morecss More css class
2133
     * @param string $usecodeaskey ''=Use id as key (default), 'code3'=Use code on 3 alpha as key, 'code2"=Use code on 2 alpha as key
2134
     * @param int<0,1>|string $showempty Show empty choice
2135
     * @param int<0,1> $disablefavorites 1=Disable favorites,
2136
     * @param int<0,1> $addspecialentries 1=Add dedicated entries for group of countries (like 'European Economic Community', ...)
2137
     * @param string[] $exclude_country_code Array of country code (iso2) to exclude
2138
     * @param int<0,1> $hideflags Hide flags
2139
     * @return string                               HTML string with select
2140
     */
2141
    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)
2142
    {
2143
        // phpcs:enable
2144
        global $conf, $langs, $mysoc;
2145
2146
        $langs->load("dict");
2147
2148
        $out = '';
2149
        $countryArray = array();
2150
        $favorite = array();
2151
        $label = array();
2152
        $atleastonefavorite = 0;
2153
2154
        $sql = "SELECT rowid, code as code_iso, code_iso as code_iso3, label, favorite, eec";
2155
        $sql .= " FROM " . $this->db->prefix() . "c_country";
2156
        $sql .= " WHERE active > 0";
2157
        //$sql.= " ORDER BY code ASC";
2158
2159
        dol_syslog(get_only_class($this) . "::select_country", LOG_DEBUG);
2160
        $resql = $this->db->query($sql);
2161
        if ($resql) {
2162
            $out .= '<select id="select' . $htmlname . '" class="flat maxwidth200onsmartphone selectcountry' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" ' . $htmloption . '>';
2163
            $num = $this->db->num_rows($resql);
2164
            $i = 0;
2165
            if ($num) {
2166
                while ($i < $num) {
2167
                    $obj = $this->db->fetch_object($resql);
2168
2169
                    $countryArray[$i]['rowid'] = $obj->rowid;
2170
                    $countryArray[$i]['code_iso'] = $obj->code_iso;
2171
                    $countryArray[$i]['code_iso3'] = $obj->code_iso3;
2172
                    $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 : ''));
2173
                    $countryArray[$i]['favorite'] = $obj->favorite;
2174
                    $countryArray[$i]['eec'] = $obj->eec;
2175
                    $favorite[$i] = $obj->favorite;
2176
                    $label[$i] = dol_string_unaccent($countryArray[$i]['label']);
2177
                    $i++;
2178
                }
2179
2180
                if (empty($disablefavorites)) {
2181
                    $array1_sort_order = SORT_DESC;
2182
                    $array2_sort_order = SORT_ASC;
2183
                    array_multisort($favorite, $array1_sort_order, $label, $array2_sort_order, $countryArray);
2184
                } else {
2185
                    $countryArray = dol_sort_array($countryArray, 'label');
2186
                }
2187
2188
                if ($showempty) {
2189
                    if (is_numeric($showempty)) {
2190
                        $out .= '<option value="">&nbsp;</option>' . "\n";
2191
                    } else {
2192
                        $out .= '<option value="-1">' . $langs->trans($showempty) . '</option>' . "\n";
2193
                    }
2194
                }
2195
2196
                if ($addspecialentries) {    // Add dedicated entries for groups of countries
2197
                    //if ($showempty) $out.= '<option value="" disabled class="selectoptiondisabledwhite">--------------</option>';
2198
                    $out .= '<option value="special_allnotme"' . ($selected == 'special_allnotme' ? ' selected' : '') . '>' . $langs->trans("CountriesExceptMe", $langs->transnoentitiesnoconv("Country" . $mysoc->country_code)) . '</option>';
2199
                    $out .= '<option value="special_eec"' . ($selected == 'special_eec' ? ' selected' : '') . '>' . $langs->trans("CountriesInEEC") . '</option>';
2200
                    if ($mysoc->isInEEC()) {
2201
                        $out .= '<option value="special_eecnotme"' . ($selected == 'special_eecnotme' ? ' selected' : '') . '>' . $langs->trans("CountriesInEECExceptMe", $langs->transnoentitiesnoconv("Country" . $mysoc->country_code)) . '</option>';
2202
                    }
2203
                    $out .= '<option value="special_noteec"' . ($selected == 'special_noteec' ? ' selected' : '') . '>' . $langs->trans("CountriesNotInEEC") . '</option>';
2204
                    $out .= '<option value="" disabled class="selectoptiondisabledwhite">------------</option>';
2205
                }
2206
2207
                foreach ($countryArray as $row) {
2208
                    //if (empty($showempty) && empty($row['rowid'])) continue;
2209
                    if (empty($row['rowid'])) {
2210
                        continue;
2211
                    }
2212
                    if (is_array($exclude_country_code) && count($exclude_country_code) && in_array($row['code_iso'], $exclude_country_code)) {
2213
                        continue; // exclude some countries
2214
                    }
2215
2216
                    if (empty($disablefavorites) && $row['favorite'] && $row['code_iso']) {
2217
                        $atleastonefavorite++;
2218
                    }
2219
                    if (empty($row['favorite']) && $atleastonefavorite) {
2220
                        $atleastonefavorite = 0;
2221
                        $out .= '<option value="" disabled class="selectoptiondisabledwhite">------------</option>';
2222
                    }
2223
2224
                    $labeltoshow = '';
2225
                    if ($row['label']) {
2226
                        $labeltoshow .= dol_trunc($row['label'], $maxlength, 'middle');
2227
                    } else {
2228
                        $labeltoshow .= '&nbsp;';
2229
                    }
2230
                    if ($row['code_iso']) {
2231
                        $labeltoshow .= ' <span class="opacitymedium">(' . $row['code_iso'] . ')</span>';
2232
                        if (empty($hideflags)) {
2233
                            $tmpflag = picto_from_langcode($row['code_iso'], 'class="saturatemedium paddingrightonly"', 1);
2234
                            $labeltoshow = $tmpflag . ' ' . $labeltoshow;
2235
                        }
2236
                    }
2237
2238
                    if ($selected && $selected != '-1' && ($selected == $row['rowid'] || $selected == $row['code_iso'] || $selected == $row['code_iso3'] || $selected == $row['label'])) {
2239
                        $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']) . '">';
2240
                    } else {
2241
                        $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']) . '">';
2242
                    }
2243
                    $out .= $labeltoshow;
2244
                    $out .= '</option>' . "\n";
2245
                }
2246
            }
2247
            $out .= '</select>';
2248
        } else {
2249
            dol_print_error($this->db);
2250
        }
2251
2252
        // Make select dynamic
2253
        include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
2254
        $out .= ajax_combobox('select' . $htmlname, array(), 0, 0, 'resolve');
2255
2256
        return $out;
2257
    }
2258
2259
2260
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2261
2262
    /**
2263
     *  Return select list of incoterms
2264
     *
2265
     * @param string $selected Id or Code of preselected incoterm
2266
     * @param string $location_incoterms Value of input location
2267
     * @param string $page Defined the form action
2268
     * @param string $htmlname Name of html select object
2269
     * @param string $htmloption Options html on select object
2270
     * @param int<0,1> $forcecombo Force to load all values and output a standard combobox (with no beautification)
2271
     * @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')))
2272
     * @param int<0,1> $disableautocomplete Disable autocomplete
2273
     * @return  string                          HTML string with select and input
2274
     */
2275
    public function select_incoterms($selected = '', $location_incoterms = '', $page = '', $htmlname = 'incoterm_id', $htmloption = '', $forcecombo = 1, $events = array(), $disableautocomplete = 0)
2276
    {
2277
        // phpcs:enable
2278
        global $conf, $langs;
2279
2280
        $langs->load("dict");
2281
2282
        $out = '';
2283
        $moreattrib = '';
2284
        $incotermArray = array();
2285
2286
        $sql = "SELECT rowid, code";
2287
        $sql .= " FROM " . $this->db->prefix() . "c_incoterms";
2288
        $sql .= " WHERE active > 0";
2289
        $sql .= " ORDER BY code ASC";
2290
2291
        dol_syslog(get_only_class($this) . "::select_incoterm", LOG_DEBUG);
2292
        $resql = $this->db->query($sql);
2293
        if ($resql) {
2294
            if ($conf->use_javascript_ajax && !$forcecombo) {
2295
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
2296
                $out .= ajax_combobox($htmlname, $events);
2297
            }
2298
2299
            if (!empty($page)) {
2300
                $out .= '<form method="post" action="' . $page . '">';
2301
                $out .= '<input type="hidden" name="action" value="set_incoterms">';
2302
                $out .= '<input type="hidden" name="token" value="' . newToken() . '">';
2303
            }
2304
2305
            $out .= '<select id="' . $htmlname . '" class="flat selectincoterm width75" name="' . $htmlname . '" ' . $htmloption . '>';
2306
            $out .= '<option value="0">&nbsp;</option>';
2307
            $num = $this->db->num_rows($resql);
2308
            $i = 0;
2309
            if ($num) {
2310
                while ($i < $num) {
2311
                    $obj = $this->db->fetch_object($resql);
2312
                    $incotermArray[$i]['rowid'] = $obj->rowid;
2313
                    $incotermArray[$i]['code'] = $obj->code;
2314
                    $i++;
2315
                }
2316
2317
                foreach ($incotermArray as $row) {
2318
                    if ($selected && ($selected == $row['rowid'] || $selected == $row['code'])) {
2319
                        $out .= '<option value="' . $row['rowid'] . '" selected>';
2320
                    } else {
2321
                        $out .= '<option value="' . $row['rowid'] . '">';
2322
                    }
2323
2324
                    if ($row['code']) {
2325
                        $out .= $row['code'];
2326
                    }
2327
2328
                    $out .= '</option>';
2329
                }
2330
            }
2331
            $out .= '</select>';
2332
2333
            if ($conf->use_javascript_ajax && empty($disableautocomplete)) {
2334
                $out .= ajax_multiautocompleter('location_incoterms', array(), constant('BASE_URL') . '/core/ajax/locationincoterms.php') . "\n";
2335
                $moreattrib .= ' autocomplete="off"';
2336
            }
2337
            $out .= '<input id="location_incoterms" class="maxwidthonsmartphone type="text" name="location_incoterms" value="' . $location_incoterms . '">' . "\n";
2338
2339
            if (!empty($page)) {
2340
                $out .= '<input type="submit" class="button valignmiddle smallpaddingimp nomargintop nomarginbottom" value="' . $langs->trans("Modify") . '"></form>';
2341
            }
2342
        } else {
2343
            dol_print_error($this->db);
2344
        }
2345
2346
        return $out;
2347
    }
2348
2349
    /**
2350
     * Return list of types of lines (product or service)
2351
     * Example: 0=product, 1=service, 9=other (for external module)
2352
     *
2353
     * @param string $selected Preselected type
2354
     * @param string $htmlname Name of field in html form
2355
     * @param int<0,1>|string $showempty Add an empty field
2356
     * @param int $hidetext Do not show label 'Type' before combo box (used only if there is at least 2 choices to select)
2357
     * @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')
2358
     * @param string $morecss More css
2359
     * @return  void
2360
     */
2361
    public function select_type_of_lines($selected = '', $htmlname = 'type', $showempty = 0, $hidetext = 0, $forceall = 0, $morecss = "")
2362
    {
2363
        // phpcs:enable
2364
        global $langs;
2365
2366
        // If product & services are enabled or both disabled.
2367
        if (
2368
            $forceall == 1 || (empty($forceall) && isModEnabled("product") && isModEnabled("service"))
2369
            || (empty($forceall) && !isModEnabled('product') && !isModEnabled('service'))
2370
        ) {
2371
            if (empty($hidetext)) {
2372
                print $langs->trans("Type") . ': ';
2373
            }
2374
            print '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" id="select_' . $htmlname . '" name="' . $htmlname . '">';
2375
            if ($showempty) {
2376
                print '<option value="-1"';
2377
                if ($selected == -1) {
2378
                    print ' selected';
2379
                }
2380
                print '>';
2381
                if (is_numeric($showempty)) {
2382
                    print '&nbsp;';
2383
                } else {
2384
                    print $showempty;
2385
                }
2386
                print '</option>';
2387
            }
2388
2389
            print '<option value="0"';
2390
            if (0 == $selected || ($selected == -1 && getDolGlobalString('MAIN_FREE_PRODUCT_CHECKED_BY_DEFAULT') == 'product')) {
2391
                print ' selected';
2392
            }
2393
            print '>' . $langs->trans("Product");
2394
2395
            print '<option value="1"';
2396
            if (1 == $selected || ($selected == -1 && getDolGlobalString('MAIN_FREE_PRODUCT_CHECKED_BY_DEFAULT') == 'service')) {
2397
                print ' selected';
2398
            }
2399
            print '>' . $langs->trans("Service");
2400
2401
            print '</select>';
2402
            print ajax_combobox('select_' . $htmlname);
2403
            //if ($user->admin) print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"),1);
2404
        }
2405
        if ((empty($forceall) && !isModEnabled('product') && isModEnabled("service")) || $forceall == 3) {
2406
            print $langs->trans("Service");
2407
            print '<input type="hidden" name="' . $htmlname . '" value="1">';
2408
        }
2409
        if ((empty($forceall) && isModEnabled("product") && !isModEnabled('service')) || $forceall == 2) {
2410
            print $langs->trans("Product");
2411
            print '<input type="hidden" name="' . $htmlname . '" value="0">';
2412
        }
2413
        if ($forceall < 0) {    // This should happened only for contracts when both predefined product and service are disabled.
2414
            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
2415
        }
2416
    }
2417
2418
2419
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2420
2421
    /**
2422
     *    Return list of types of notes
2423
     *
2424
     * @param string $selected Preselected type
2425
     * @param string $htmlname Name of field in form
2426
     * @param int $showempty Add an empty field
2427
     * @return    void
2428
     */
2429
    public function select_type_fees($selected = '', $htmlname = 'type', $showempty = 0)
2430
    {
2431
        // phpcs:enable
2432
        global $user, $langs;
2433
2434
        dol_syslog(__METHOD__ . " selected=" . $selected . ", htmlname=" . $htmlname, LOG_DEBUG);
2435
2436
        $this->load_cache_types_fees();
2437
2438
        print '<select id="select_' . $htmlname . '" class="flat" name="' . $htmlname . '">';
2439
        if ($showempty) {
2440
            print '<option value="-1"';
2441
            if ($selected == -1) {
2442
                print ' selected';
2443
            }
2444
            print '>&nbsp;</option>';
2445
        }
2446
2447
        foreach ($this->cache_types_fees as $key => $value) {
2448
            print '<option value="' . $key . '"';
2449
            if ($key == $selected) {
2450
                print ' selected';
2451
            }
2452
            print '>';
2453
            print $value;
2454
            print '</option>';
2455
        }
2456
2457
        print '</select>';
2458
        if ($user->admin) {
2459
            print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
2460
        }
2461
    }
2462
2463
2464
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2465
2466
    /**
2467
     *    Load into cache cache_types_fees, array of types of fees
2468
     *
2469
     * @return     int             Nb of lines loaded, <0 if KO
2470
     */
2471
    public function load_cache_types_fees()
2472
    {
2473
        // phpcs:enable
2474
        global $langs;
2475
2476
        $num = count($this->cache_types_fees);
2477
        if ($num > 0) {
2478
            return 0; // Cache already loaded
2479
        }
2480
2481
        dol_syslog(__METHOD__, LOG_DEBUG);
2482
2483
        $langs->load("trips");
2484
2485
        $sql = "SELECT c.code, c.label";
2486
        $sql .= " FROM " . $this->db->prefix() . "c_type_fees as c";
2487
        $sql .= " WHERE active > 0";
2488
2489
        $resql = $this->db->query($sql);
2490
        if ($resql) {
2491
            $num = $this->db->num_rows($resql);
2492
            $i = 0;
2493
2494
            while ($i < $num) {
2495
                $obj = $this->db->fetch_object($resql);
2496
2497
                // Si traduction existe, on l'utilise, sinon on prend le libelle par default
2498
                $label = ($obj->code != $langs->trans($obj->code) ? $langs->trans($obj->code) : $langs->trans($obj->label));
2499
                $this->cache_types_fees[$obj->code] = $label;
2500
                $i++;
2501
            }
2502
2503
            asort($this->cache_types_fees);
2504
2505
            return $num;
2506
        } else {
2507
            dol_print_error($this->db);
2508
            return -1;
2509
        }
2510
    }
2511
2512
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2513
2514
    /**
2515
     * Output html form to select a contact
2516
     * This call select_contacts() or ajax depending on setup. This component is not able to support multiple select.
2517
     *
2518
     * Return HTML code of the SELECT of list of all contacts (for a third party or all).
2519
     * This also set the number of contacts found into $this->num
2520
     *
2521
     * @param int $socid Id of third party or 0 for all or -1 for empty list
2522
     * @param int|string $selected ID of preselected contact id
2523
     * @param string $htmlname Name of HTML field ('none' for a not editable field)
2524
     * @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
2525
     * @param string $exclude List of contacts id to exclude
2526
     * @param string $limitto Not used
2527
     * @param integer $showfunction Add function into label
2528
     * @param string $morecss Add more class to class style
2529
     * @param bool $nokeyifsocid When 1, we force the option "Press a key to show list" to 0 if there is a value for $socid
2530
     * @param integer $showsoc Add company into label
2531
     * @param int $forcecombo 1=Force to use combo box (so no ajax beautify effect)
2532
     * @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')))
2533
     * @param string $moreparam Add more parameters onto the select tag. For example 'style="width: 95%"' to avoid select2 component to go over parent container
2534
     * @param string $htmlid Html id to use instead of htmlname
2535
     * @param string $selected_input_value Value of preselected input text (for use with ajax)
2536
     * @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.
2537
     * @return  int|string                          Return integer <0 if KO, HTML with select string if OK.
2538
     */
2539
    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 = '')
2540
    {
2541
        // phpcs:enable
2542
2543
        global $conf, $langs;
2544
2545
        $out = '';
2546
2547
        $sav = getDolGlobalString('CONTACT_USE_SEARCH_TO_SELECT');
2548
        if ($nokeyifsocid && $socid > 0) {
2549
            $conf->global->CONTACT_USE_SEARCH_TO_SELECT = 0;
2550
        }
2551
2552
        if (!empty($conf->use_javascript_ajax) && getDolGlobalString('CONTACT_USE_SEARCH_TO_SELECT') && !$forcecombo) {
2553
            if (is_null($events)) {
2554
                $events = array();
2555
            }
2556
2557
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/ajax.lib.php';
2558
2559
            // No immediate load of all database
2560
            $placeholder = '';
2561
            if ($selected && empty($selected_input_value)) {
2562
                $contacttmp = new Contact($this->db);
2563
                $contacttmp->fetch($selected);
2564
                $selected_input_value = $contacttmp->getFullName($langs);
2565
                unset($contacttmp);
2566
            }
2567
            if (!is_numeric($showempty)) {
2568
                $placeholder = $showempty;
2569
            }
2570
2571
            // mode 1
2572
            $urloption = 'htmlname=' . urlencode((string)(str_replace('.', '_', $htmlname))) . '&outjson=1&filter=' . urlencode((string)($filter)) . (empty($exclude) ? '' : '&exclude=' . urlencode($exclude)) . ($showsoc ? '&showsoc=' . urlencode((string)($showsoc)) : '');
2573
2574
            $out .= '<!-- force css to be higher than dialog popup --><style type="text/css">.ui-autocomplete { z-index: 1010; }</style>';
2575
2576
            $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' : '') . ' />';
2577
2578
            $out .= ajax_event($htmlname, $events);
2579
2580
            $out .= ajax_autocompleter($selected, $htmlname, constant('BASE_URL') . '/contact/ajax/contact.php', $urloption, getDolGlobalString('CONTACT_USE_SEARCH_TO_SELECT'), 0, $events);
2581
        } else {
2582
            // Immediate load of all database
2583
            $multiple = false;
2584
            $disableifempty = 0;
2585
            $options_only = false;
2586
            $limitto = '';
2587
2588
            $out .= $this->selectcontacts($socid, $selected, $htmlname, $showempty, $exclude, $limitto, $showfunction, $morecss, $options_only, $showsoc, $forcecombo, $events, $moreparam, $htmlid, $multiple, $disableifempty);
2589
        }
2590
2591
        $conf->global->CONTACT_USE_SEARCH_TO_SELECT = $sav;
2592
2593
        return $out;
2594
    }
2595
2596
2597
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2598
2599
    /**
2600
     * Return HTML code of the SELECT of list of all contacts (for a third party or all).
2601
     * This also set the number of contacts found into $this->num
2602
     * Note: you must use the select_contact() to get the component to select a contact. This function must only be called by select_contact.
2603
     *
2604
     * @param int $socid Id of third party or 0 for all or -1 for empty list
2605
     * @param array|int|string $selected Array of ID of preselected contact id
2606
     * @param string $htmlname Name of HTML field ('none' for a not editable field)
2607
     * @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
2608
     * @param string $exclude List of contacts id to exclude
2609
     * @param string $limitto Disable answers that are not id in this array list
2610
     * @param integer $showfunction Add function into label
2611
     * @param string $morecss Add more class to class style
2612
     * @param int $options_only 1=Return options only (for ajax treatment), 2=Return array
2613
     * @param integer $showsoc Add company into label
2614
     * @param int $forcecombo Force to use combo box (so no ajax beautify effect)
2615
     * @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')))
2616
     * @param string $moreparam Add more parameters onto the select tag. For example 'style="width: 95%"' to avoid select2 component to go over parent container
2617
     * @param string $htmlid Html id to use instead of htmlname
2618
     * @param bool $multiple add [] in the name of element and add 'multiple' attribute
2619
     * @param integer $disableifempty Set tag 'disabled' on select if there is no choice
2620
     * @param string $filter Optional filters criteras. WARNING: To avoid SQL injection, only few chars [.a-z0-9 =<>] are allowed here, example: 's.rowid <> x'
2621
     *                                                  If you need parenthesis, use the Universal Filter Syntax, example: '(s.client:in:1,3)'
2622
     *                                                  Do not use a filter coming from input of users.
2623
     * @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.
2624
     */
2625
    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 = '')
2626
    {
2627
        global $conf, $langs, $hookmanager, $action;
2628
2629
        $langs->load('companies');
2630
2631
        if (empty($htmlid)) {
2632
            $htmlid = $htmlname;
2633
        }
2634
        $num = 0;
2635
        $out = '';
2636
        $outarray = array();
2637
2638
        if ($selected === '') {
2639
            $selected = array();
2640
        } elseif (!is_array($selected)) {
2641
            $selected = array((int)$selected);
2642
        }
2643
2644
        // Clean $filter that may contains sql conditions so sql code
2645
        if (Filters::testSqlAndScriptInject($filter, 3) > 0) {
2646
            $filter = '';
2647
            return 'SQLInjectionTryDetected';
2648
        }
2649
2650
        if ($filter != '') {    // If a filter was provided
2651
            if (preg_match('/[\(\)]/', $filter)) {
2652
                // If there is one parenthesis inside the criteria, we assume it is an Universal Filter Syntax.
2653
                $errormsg = '';
2654
                $filter = forgeSQLFromUniversalSearchCriteria($filter, $errormsg, 1);
2655
2656
                // Redo clean $filter that may contains sql conditions so sql code
2657
                if (Filters::testSqlAndScriptInject($filter, 3) > 0) {
2658
                    $filter = '';
2659
                    return 'SQLInjectionTryDetected';
2660
                }
2661
            } else {
2662
                // If not, we do nothing. We already know that there is no parenthesis
2663
                // TODO Disallow this case in a future.
2664
                dol_syslog("Warning, select_thirdparty_list was called with a filter criteria not using the Universal Search Syntax.", LOG_WARNING);
2665
            }
2666
        }
2667
2668
        if (!is_object($hookmanager)) {
2669
            $hookmanager = new HookManager($this->db);
2670
        }
2671
2672
        // We search third parties
2673
        $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";
2674
        if ($showsoc > 0 || getDolGlobalString('CONTACT_SHOW_EMAIL_PHONE_TOWN_SELECTLIST')) {
2675
            $sql .= ", s.nom as company, s.town AS company_town";
2676
        }
2677
        $sql .= " FROM " . $this->db->prefix() . "socpeople as sp";
2678
        if ($showsoc > 0 || getDolGlobalString('CONTACT_SHOW_EMAIL_PHONE_TOWN_SELECTLIST')) {
2679
            $sql .= " LEFT OUTER JOIN  " . $this->db->prefix() . "societe as s ON s.rowid=sp.fk_soc";
2680
        }
2681
        $sql .= " WHERE sp.entity IN (" . getEntity('contact') . ")";
2682
        if ($socid > 0 || $socid == -1) {
2683
            $sql .= " AND sp.fk_soc = " . ((int)$socid);
2684
        }
2685
        if (getDolGlobalString('CONTACT_HIDE_INACTIVE_IN_COMBOBOX')) {
2686
            $sql .= " AND sp.statut <> 0";
2687
        }
2688
        if ($filter) {
2689
            // $filter is safe because, if it contains '(' or ')', it has been sanitized by testSqlAndScriptInject() and forgeSQLFromUniversalSearchCriteria()
2690
            // if not, by testSqlAndScriptInject() only.
2691
            $sql .= " AND (" . $filter . ")";
2692
        }
2693
        // Add where from hooks
2694
        $parameters = array();
2695
        $reshook = $hookmanager->executeHooks('selectContactListWhere', $parameters); // Note that $action and $object may have been modified by hook
2696
        $sql .= $hookmanager->resPrint;
2697
        $sql .= " ORDER BY sp.lastname ASC";
2698
2699
        dol_syslog(get_only_class($this) . "::selectcontacts", LOG_DEBUG);
2700
        $resql = $this->db->query($sql);
2701
        if ($resql) {
2702
            $num = $this->db->num_rows($resql);
2703
2704
            if ($htmlname != 'none' && !$options_only) {
2705
                $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" id="' . $htmlid . '" name="' . $htmlname . ($multiple ? '[]' : '') . '" ' . (($num || empty($disableifempty)) ? '' : ' disabled') . ($multiple ? 'multiple' : '') . ' ' . (!empty($moreparam) ? $moreparam : '') . '>';
2706
            }
2707
2708
            if ($showempty && !is_numeric($showempty)) {
2709
                $textforempty = $showempty;
2710
                $out .= '<option class="optiongrey" value="-1"' . (in_array(-1, $selected) ? ' selected' : '') . '>' . $textforempty . '</option>';
2711
            } else {
2712
                if (($showempty == 1 || ($showempty == 3 && $num > 1)) && !$multiple) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($showempty == 1 || $sho...num > 1) && ! $multiple, Probably Intended Meaning: $showempty == 1 || ($sho...num > 1 && ! $multiple)
Loading history...
2713
                    $out .= '<option value="0"' . (in_array(0, $selected) ? ' selected' : '') . '>&nbsp;</option>';
2714
                }
2715
                if ($showempty == 2) {
2716
                    $out .= '<option value="0"' . (in_array(0, $selected) ? ' selected' : '') . '>-- ' . $langs->trans("Internal") . ' --</option>';
2717
                }
2718
            }
2719
2720
            $i = 0;
2721
            if ($num) {
2722
                $contactstatic = new Contact($this->db);
2723
2724
                while ($i < $num) {
2725
                    $obj = $this->db->fetch_object($resql);
2726
2727
                    // Set email (or phones) and town extended infos
2728
                    $extendedInfos = '';
2729
                    if (getDolGlobalString('CONTACT_SHOW_EMAIL_PHONE_TOWN_SELECTLIST')) {
2730
                        $extendedInfos = array();
2731
                        $email = trim($obj->email);
2732
                        if (!empty($email)) {
2733
                            $extendedInfos[] = $email;
2734
                        } else {
2735
                            $phone = trim($obj->phone);
2736
                            $phone_perso = trim($obj->phone_perso);
2737
                            $phone_mobile = trim($obj->phone_mobile);
2738
                            if (!empty($phone)) {
2739
                                $extendedInfos[] = $phone;
2740
                            }
2741
                            if (!empty($phone_perso)) {
2742
                                $extendedInfos[] = $phone_perso;
2743
                            }
2744
                            if (!empty($phone_mobile)) {
2745
                                $extendedInfos[] = $phone_mobile;
2746
                            }
2747
                        }
2748
                        $contact_town = trim($obj->contact_town);
2749
                        $company_town = trim($obj->company_town);
2750
                        if (!empty($contact_town)) {
2751
                            $extendedInfos[] = $contact_town;
2752
                        } elseif (!empty($company_town)) {
2753
                            $extendedInfos[] = $company_town;
2754
                        }
2755
                        $extendedInfos = implode(' - ', $extendedInfos);
2756
                        if (!empty($extendedInfos)) {
2757
                            $extendedInfos = ' - ' . $extendedInfos;
2758
                        }
2759
                    }
2760
2761
                    $contactstatic->id = $obj->rowid;
2762
                    $contactstatic->lastname = $obj->lastname;
2763
                    $contactstatic->firstname = $obj->firstname;
2764
                    if ($obj->statut == 1) {
2765
                        $tmplabel = '';
2766
                        if ($htmlname != 'none') {
2767
                            $disabled = 0;
2768
                            if (is_array($exclude) && count($exclude) && in_array($obj->rowid, $exclude)) {
2769
                                $disabled = 1;
2770
                            }
2771
                            if (is_array($limitto) && count($limitto) && !in_array($obj->rowid, $limitto)) {
2772
                                $disabled = 1;
2773
                            }
2774
                            if (!empty($selected) && in_array($obj->rowid, $selected)) {
2775
                                $out .= '<option value="' . $obj->rowid . '"';
2776
                                if ($disabled) {
2777
                                    $out .= ' disabled';
2778
                                }
2779
                                $out .= ' selected>';
2780
2781
                                $tmplabel = $contactstatic->getFullName($langs) . $extendedInfos;
2782
                                if ($showfunction && $obj->poste) {
2783
                                    $tmplabel .= ' (' . $obj->poste . ')';
2784
                                }
2785
                                if (($showsoc > 0) && $obj->company) {
2786
                                    $tmplabel .= ' - (' . $obj->company . ')';
2787
                                }
2788
2789
                                $out .= $tmplabel;
2790
                                $out .= '</option>';
2791
                            } else {
2792
                                $out .= '<option value="' . $obj->rowid . '"';
2793
                                if ($disabled) {
2794
                                    $out .= ' disabled';
2795
                                }
2796
                                $out .= '>';
2797
2798
                                $tmplabel = $contactstatic->getFullName($langs) . $extendedInfos;
2799
                                if ($showfunction && $obj->poste) {
2800
                                    $tmplabel .= ' (' . $obj->poste . ')';
2801
                                }
2802
                                if (($showsoc > 0) && $obj->company) {
2803
                                    $tmplabel .= ' - (' . $obj->company . ')';
2804
                                }
2805
2806
                                $out .= $tmplabel;
2807
                                $out .= '</option>';
2808
                            }
2809
                        } else {
2810
                            if (in_array($obj->rowid, $selected)) {
2811
                                $tmplabel = $contactstatic->getFullName($langs) . $extendedInfos;
2812
                                if ($showfunction && $obj->poste) {
2813
                                    $tmplabel .= ' (' . $obj->poste . ')';
2814
                                }
2815
                                if (($showsoc > 0) && $obj->company) {
2816
                                    $tmplabel .= ' - (' . $obj->company . ')';
2817
                                }
2818
2819
                                $out .= $tmplabel;
2820
                            }
2821
                        }
2822
2823
                        if ($tmplabel != '') {
2824
                            array_push($outarray, array('key' => $obj->rowid, 'value' => $tmplabel, 'label' => $tmplabel, 'labelhtml' => $tmplabel));
2825
                        }
2826
                    }
2827
                    $i++;
2828
                }
2829
            } else {
2830
                $labeltoshow = ($socid != -1) ? ($langs->trans($socid ? "NoContactDefinedForThirdParty" : "NoContactDefined")) : $langs->trans('SelectAThirdPartyFirst');
2831
                $out .= '<option class="disabled" value="-1"' . (($showempty == 2 || $multiple) ? '' : ' selected') . ' disabled="disabled">';
2832
                $out .= $labeltoshow;
2833
                $out .= '</option>';
2834
            }
2835
2836
            $parameters = array(
2837
                'socid' => $socid,
2838
                'htmlname' => $htmlname,
2839
                'resql' => $resql,
2840
                'out' => &$out,
2841
                'showfunction' => $showfunction,
2842
                'showsoc' => $showsoc,
2843
            );
2844
2845
            $reshook = $hookmanager->executeHooks('afterSelectContactOptions', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2846
2847
            if ($htmlname != 'none' && !$options_only) {
2848
                $out .= '</select>';
2849
            }
2850
2851
            if ($conf->use_javascript_ajax && !$forcecombo && !$options_only) {
2852
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
2853
                $out .= ajax_combobox($htmlid, $events, getDolGlobalInt("CONTACT_USE_SEARCH_TO_SELECT"));
2854
            }
2855
2856
            $this->num = $num;
2857
2858
            if ($options_only === 2) {
2859
                // Return array of options
2860
                return $outarray;
2861
            } else {
2862
                return $out;
2863
            }
2864
        } else {
2865
            dol_print_error($this->db);
2866
            return -1;
2867
        }
2868
    }
2869
2870
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2871
2872
    /**
2873
     * Return the HTML select list of users
2874
     *
2875
     * @param string $selected Id user preselected
2876
     * @param string $htmlname Field name in form
2877
     * @param int<0,1> $show_empty 0=liste sans valeur nulle, 1=ajoute valeur inconnue
2878
     * @param int[] $exclude Array list of users id to exclude
2879
     * @param int<0,1> $disabled If select list must be disabled
2880
     * @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
2881
     * @param int[]|int $enableonly Array list of users id to be enabled. All other must be disabled
2882
     * @param string $force_entity '0' or Ids of environment to force
2883
     * @return    void
2884
     * @deprecated        Use select_dolusers instead
2885
     * @see select_dolusers()
2886
     */
2887
    public function select_users($selected = '', $htmlname = 'userid', $show_empty = 0, $exclude = null, $disabled = 0, $include = '', $enableonly = array(), $force_entity = '0')
2888
    {
2889
        // phpcs:enable
2890
        print $this->select_dolusers($selected, $htmlname, $show_empty, $exclude, $disabled, $include, $enableonly, $force_entity);
2891
    }
2892
2893
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2894
2895
    /**
2896
     * Return select list of users
2897
     *
2898
     * @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)
2899
     * @param string $htmlname Field name in form
2900
     * @param int<0,1>|string $show_empty 0=list with no empty value, 1=add also an empty value into list
2901
     * @param int[]|null $exclude Array list of users id to exclude
2902
     * @param int $disabled If select list must be disabled
2903
     * @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
2904
     * @param array|string $enableonly Array list of users id to be enabled. If defined, it means that others will be disabled
2905
     * @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.
2906
     * @param int $maxlength Maximum length of string into list (0=no limit)
2907
     * @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
2908
     * @param string $morefilter Add more filters into sql request (Example: 'employee = 1'). This value must not come from user input.
2909
     * @param integer $show_every 0=default list, 1=add also a value "Everybody" at beginning of list
2910
     * @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.
2911
     * @param string $morecss More css
2912
     * @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).
2913
     * @param int<0,2> $outputmode 0=HTML select string, 1=Array, 2=Detailed array
2914
     * @param bool $multiple add [] in the name of element and add 'multiple' attribute
2915
     * @param int<0,1> $forcecombo Force the component to be a simple combo box without ajax
2916
     * @return string|array<int,string|array{id:int,label:string,labelhtml:string,color:string,picto:string}>   HTML select string
2917
     * @see select_dolgroups()
2918
     */
2919
    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)
2920
    {
2921
        // phpcs:enable
2922
        global $conf, $user, $langs, $hookmanager;
2923
        global $action;
2924
2925
        // If no preselected user defined, we take current user
2926
        if ((is_numeric($selected) && ($selected < -2 || empty($selected))) && !getDolGlobalString('SOCIETE_DISABLE_DEFAULT_SALESREPRESENTATIVE')) {
2927
            $selected = $user->id;
2928
        }
2929
2930
        if ($selected === '') {
2931
            $selected = array();
2932
        } elseif (!is_array($selected)) {
2933
            $selected = array($selected);
2934
        }
2935
2936
        $excludeUsers = null;
2937
        $includeUsers = null;
2938
2939
        // Exclude some users
2940
        if (is_array($exclude)) {
2941
            $excludeUsers = implode(",", $exclude);
2942
        }
2943
        // Include some uses
2944
        if (is_array($include)) {
2945
            $includeUsers = implode(",", $include);
2946
        } elseif ($include == 'hierarchy') {
2947
            // Build list includeUsers to have only hierarchy
2948
            $includeUsers = implode(",", $user->getAllChildIds(0));
2949
        } elseif ($include == 'hierarchyme') {
2950
            // Build list includeUsers to have only hierarchy and current user
2951
            $includeUsers = implode(",", $user->getAllChildIds(1));
2952
        }
2953
2954
        $num = 0;
2955
2956
        $out = '';
2957
        $outarray = array();
2958
        $outarray2 = array();
2959
2960
        // Do we want to show the label of entity into the combo list ?
2961
        $showlabelofentity = isModEnabled('multicompany') && !getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE') && $conf->entity == 1 && !empty($user->admin) && empty($user->entity);
2962
        $userissuperadminentityone = isModEnabled('multicompany') && $conf->entity == 1 && $user->admin && empty($user->entity);
2963
2964
        // Forge request to select users
2965
        $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";
2966
        if ($showlabelofentity) {
2967
            $sql .= ", e.label";
2968
        }
2969
        $sql .= " FROM " . $this->db->prefix() . "user as u";
2970
        if ($showlabelofentity) {
2971
            $sql .= " LEFT JOIN " . $this->db->prefix() . "entity as e ON e.rowid = u.entity";
2972
        }
2973
        // Condition here should be the same than into societe->getSalesRepresentatives().
2974
        if ($userissuperadminentityone && $force_entity != 'default') {
2975
            if (!empty($force_entity)) {
2976
                $sql .= " WHERE u.entity IN (0, " . $this->db->sanitize($force_entity) . ")";
2977
            } else {
2978
                $sql .= " WHERE u.entity IS NOT NULL";
2979
            }
2980
        } else {
2981
            if (isModEnabled('multicompany') && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2982
                $sql .= " WHERE u.rowid IN (SELECT ug.fk_user FROM " . $this->db->prefix() . "usergroup_user as ug WHERE ug.entity IN (" . getEntity('usergroup') . "))";
2983
            } else {
2984
                $sql .= " WHERE u.entity IN (" . getEntity('user') . ")";
2985
            }
2986
        }
2987
2988
        if (!empty($user->socid)) {
2989
            $sql .= " AND u.fk_soc = " . ((int)$user->socid);
2990
        }
2991
        if (is_array($exclude) && $excludeUsers) {
2992
            $sql .= " AND u.rowid NOT IN (" . $this->db->sanitize($excludeUsers) . ")";
2993
        }
2994
        if ($includeUsers) {
2995
            $sql .= " AND u.rowid IN (" . $this->db->sanitize($includeUsers) . ")";
2996
        }
2997
        if (getDolGlobalString('USER_HIDE_INACTIVE_IN_COMBOBOX') || $notdisabled) {
2998
            $sql .= " AND u.statut <> 0";
2999
        }
3000
        if (getDolGlobalString('USER_HIDE_NONEMPLOYEE_IN_COMBOBOX') || $notdisabled) {
3001
            $sql .= " AND u.employee <> 0";
3002
        }
3003
        if (getDolGlobalString('USER_HIDE_EXTERNAL_IN_COMBOBOX') || $notdisabled) {
3004
            $sql .= " AND u.fk_soc IS NULL";
3005
        }
3006
        if (!empty($morefilter)) {
3007
            $sql .= " " . $morefilter;
3008
        }
3009
3010
        //Add hook to filter on user (for example on usergroup define in custom modules)
3011
        $reshook = $hookmanager->executeHooks('addSQLWhereFilterOnSelectUsers', array(), $this, $action);
3012
        if (!empty($reshook)) {
3013
            $sql .= $hookmanager->resPrint;
3014
        }
3015
3016
        if (!getDolGlobalString('MAIN_FIRSTNAME_NAME_POSITION')) {    // MAIN_FIRSTNAME_NAME_POSITION is 0 means firstname+lastname
3017
            $sql .= " ORDER BY u.statut DESC, u.firstname ASC, u.lastname ASC";
3018
        } else {
3019
            $sql .= " ORDER BY u.statut DESC, u.lastname ASC, u.firstname ASC";
3020
        }
3021
3022
        dol_syslog(get_only_class($this) . "::select_dolusers", LOG_DEBUG);
3023
3024
        $resql = $this->db->query($sql);
3025
        if ($resql) {
3026
            $num = $this->db->num_rows($resql);
3027
            $i = 0;
3028
            if ($num) {
3029
                // do not use maxwidthonsmartphone by default. Set it by caller so auto size to 100% will work when not defined
3030
                $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : ' minwidth200') . '" id="' . $htmlname . '" name="' . $htmlname . ($multiple ? '[]' : '') . '" ' . ($multiple ? 'multiple' : '') . ' ' . ($disabled ? ' disabled' : '') . '>';
3031
                if ($show_empty && !$multiple) {
3032
                    $textforempty = ' ';
3033
                    if (!empty($conf->use_javascript_ajax)) {
3034
                        $textforempty = '&nbsp;'; // If we use ajaxcombo, we need &nbsp; here to avoid to have an empty element that is too small.
3035
                    }
3036
                    if (!is_numeric($show_empty)) {
3037
                        $textforempty = $show_empty;
3038
                    }
3039
                    $out .= '<option class="optiongrey" value="' . ($show_empty < 0 ? $show_empty : -1) . '"' . ((empty($selected) || in_array(-1, $selected)) ? ' selected' : '') . '>' . $textforempty . '</option>' . "\n";
3040
                }
3041
                if ($show_every) {
3042
                    $out .= '<option value="-2"' . ((in_array(-2, $selected)) ? ' selected' : '') . '>-- ' . $langs->trans("Everybody") . ' --</option>' . "\n";
3043
                }
3044
3045
                $userstatic = new User($this->db);
3046
3047
                while ($i < $num) {
3048
                    $obj = $this->db->fetch_object($resql);
3049
3050
                    $userstatic->id = $obj->rowid;
3051
                    $userstatic->lastname = $obj->lastname;
3052
                    $userstatic->firstname = $obj->firstname;
3053
                    $userstatic->photo = $obj->photo;
3054
                    $userstatic->status = $obj->status;
3055
                    $userstatic->entity = $obj->entity;
3056
                    $userstatic->admin = $obj->admin;
3057
                    $userstatic->gender = $obj->gender;
3058
3059
                    $disableline = '';
3060
                    if (is_array($enableonly) && count($enableonly) && !in_array($obj->rowid, $enableonly)) {
3061
                        $disableline = ($enableonlytext ? $enableonlytext : '1');
3062
                    }
3063
3064
                    $labeltoshow = '';
3065
                    $labeltoshowhtml = '';
3066
3067
                    // $fullNameMode is 0=Lastname+Firstname (MAIN_FIRSTNAME_NAME_POSITION=1), 1=Firstname+Lastname (MAIN_FIRSTNAME_NAME_POSITION=0)
3068
                    $fullNameMode = 0;
3069
                    if (!getDolGlobalString('MAIN_FIRSTNAME_NAME_POSITION')) {
3070
                        $fullNameMode = 1; //Firstname+lastname
3071
                    }
3072
                    $labeltoshow .= $userstatic->getFullName($langs, $fullNameMode, -1, $maxlength);
3073
                    $labeltoshowhtml .= $userstatic->getFullName($langs, $fullNameMode, -1, $maxlength);
3074
                    if (empty($obj->firstname) && empty($obj->lastname)) {
3075
                        $labeltoshow .= $obj->login;
3076
                        $labeltoshowhtml .= $obj->login;
3077
                    }
3078
3079
                    // Complete name with a more info string like: ' (info1 - info2 - ...)'
3080
                    $moreinfo = '';
3081
                    $moreinfohtml = '';
3082
                    if (getDolGlobalString('MAIN_SHOW_LOGIN')) {
3083
                        $moreinfo .= ($moreinfo ? ' - ' : ' (');
3084
                        $moreinfohtml .= ($moreinfohtml ? ' - ' : ' <span class="opacitymedium">(');
3085
                        $moreinfo .= $obj->login;
3086
                        $moreinfohtml .= $obj->login;
3087
                    }
3088
                    if ($showstatus >= 0) {
3089
                        if ($obj->status == 1 && $showstatus == 1) {
3090
                            $moreinfo .= ($moreinfo ? ' - ' : ' (') . $langs->trans('Enabled');
3091
                            $moreinfohtml .= ($moreinfohtml ? ' - ' : ' <span class="opacitymedium">(') . $langs->trans('Enabled');
3092
                        }
3093
                        if ($obj->status == 0 && $showstatus == 1) {
3094
                            $moreinfo .= ($moreinfo ? ' - ' : ' (') . $langs->trans('Disabled');
3095
                            $moreinfohtml .= ($moreinfohtml ? ' - ' : ' <span class="opacitymedium">(') . $langs->trans('Disabled');
3096
                        }
3097
                    }
3098
                    if ($showlabelofentity) {
3099
                        if (empty($obj->entity)) {
3100
                            $moreinfo .= ($moreinfo ? ' - ' : ' (') . $langs->trans("AllEntities");
3101
                            $moreinfohtml .= ($moreinfohtml ? ' - ' : ' <span class="opacitymedium">(') . $langs->trans("AllEntities");
3102
                        } else {
3103
                            if ($obj->entity != $conf->entity) {
3104
                                $moreinfo .= ($moreinfo ? ' - ' : ' (') . ($obj->label ? $obj->label : $langs->trans("EntityNameNotDefined"));
3105
                                $moreinfohtml .= ($moreinfohtml ? ' - ' : ' <span class="opacitymedium">(') . ($obj->label ? $obj->label : $langs->trans("EntityNameNotDefined"));
3106
                            }
3107
                        }
3108
                    }
3109
                    $moreinfo .= (!empty($moreinfo) ? ')' : '');
3110
                    $moreinfohtml .= (!empty($moreinfohtml) ? ')</span>' : '');
3111
                    if (!empty($disableline) && $disableline != '1') {
3112
                        // Add text from $enableonlytext parameter
3113
                        $moreinfo .= ' - ' . $disableline;
3114
                        $moreinfohtml .= ' - ' . $disableline;
3115
                    }
3116
                    $labeltoshow .= $moreinfo;
3117
                    $labeltoshowhtml .= $moreinfohtml;
3118
3119
                    $out .= '<option value="' . $obj->rowid . '"';
3120
                    if (!empty($disableline)) {
3121
                        $out .= ' disabled';
3122
                    }
3123
                    if ((!empty($selected[0]) && is_object($selected[0])) ? $selected[0]->id == $obj->rowid : in_array($obj->rowid, $selected)) {
3124
                        $out .= ' selected';
3125
                    }
3126
                    $out .= ' data-html="';
3127
3128
                    $outhtml = $userstatic->getNomUrl(-3, '', 0, 1, 24, 1, 'login', '', 1) . ' ';
3129
                    if ($showstatus >= 0 && $obj->status == 0) {
3130
                        $outhtml .= '<strike class="opacitymediumxxx">';
3131
                    }
3132
                    $outhtml .= $labeltoshowhtml;
3133
                    if ($showstatus >= 0 && $obj->status == 0) {
3134
                        $outhtml .= '</strike>';
3135
                    }
3136
                    $labeltoshowhtml = $outhtml;
3137
3138
                    $out .= dol_escape_htmltag($outhtml);
3139
                    $out .= '">';
3140
                    $out .= $labeltoshow;
3141
                    $out .= '</option>';
3142
3143
                    $outarray[$userstatic->id] = $userstatic->getFullName($langs, $fullNameMode, -1, $maxlength) . $moreinfo;
3144
                    $outarray2[$userstatic->id] = array(
3145
                        'id' => $userstatic->id,
3146
                        'label' => $labeltoshow,
3147
                        'labelhtml' => $labeltoshowhtml,
3148
                        'color' => '',
3149
                        'picto' => ''
3150
                    );
3151
3152
                    $i++;
3153
                }
3154
            } else {
3155
                $out .= '<select class="flat" id="' . $htmlname . '" name="' . $htmlname . '" disabled>';
3156
                $out .= '<option value="">' . $langs->trans("None") . '</option>';
3157
            }
3158
            $out .= '</select>';
3159
3160
            if ($num && !$forcecombo) {
3161
                // Enhance with select2
3162
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
3163
                $out .= ajax_combobox($htmlname);
3164
            }
3165
        } else {
3166
            dol_print_error($this->db);
3167
        }
3168
3169
        $this->num = $num;
3170
3171
        if ($outputmode == 2) {
3172
            return $outarray2;
3173
        } elseif ($outputmode) {
3174
            return $outarray;
3175
        }
3176
3177
        return $out;
3178
    }
3179
3180
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3181
3182
    /**
3183
     * Return select list of users. Selected users are stored into session.
3184
     * List of users are provided into $_SESSION['assignedtouser'].
3185
     *
3186
     * @param string $action Value for $action
3187
     * @param string $htmlname Field name in form
3188
     * @param int<0,1> $show_empty 0=list without the empty value, 1=add empty value
3189
     * @param int[] $exclude Array list of users id to exclude
3190
     * @param int<0,1> $disabled If select list must be disabled
3191
     * @param int[]|string $include Array list of users id to include or 'hierarchy' to have only supervised users
3192
     * @param int[]|int $enableonly Array list of users id to be enabled. All other must be disabled
3193
     * @param string $force_entity '0' or Ids of environment to force
3194
     * @param int $maxlength Maximum length of string into list (0=no limit)
3195
     * @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
3196
     * @param string $morefilter Add more filters into sql request
3197
     * @param int $showproperties Show properties of each attendees
3198
     * @param int[] $listofuserid Array with properties of each user
3199
     * @param int[] $listofcontactid Array with properties of each contact
3200
     * @param int[] $listofotherid Array with properties of each other contact
3201
     * @return    string                    HTML select string
3202
     * @see select_dolgroups()
3203
     */
3204
    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())
3205
    {
3206
        // phpcs:enable
3207
        global $langs;
3208
3209
        $userstatic = new User($this->db);
3210
        $out = '';
3211
3212
        if (!empty($_SESSION['assignedtouser'])) {
3213
            $assignedtouser = json_decode($_SESSION['assignedtouser'], true);
3214
            if (!is_array($assignedtouser)) {
3215
                $assignedtouser = array();
3216
            }
3217
        } else {
3218
            $assignedtouser = array();
3219
        }
3220
        $nbassignetouser = count($assignedtouser);
3221
3222
        //if ($nbassignetouser && $action != 'view') $out .= '<br>';
3223
        if ($nbassignetouser) {
3224
            $out .= '<ul class="attendees">';
3225
        }
3226
        $i = 0;
3227
        $ownerid = 0;
3228
        foreach ($assignedtouser as $key => $value) {
3229
            if ($value['id'] == $ownerid) {
3230
                continue;
3231
            }
3232
3233
            $out .= '<li>';
3234
            $userstatic->fetch($value['id']);
3235
            $out .= $userstatic->getNomUrl(-1);
3236
            if ($i == 0) {
3237
                $ownerid = $value['id'];
3238
                $out .= ' (' . $langs->trans("Owner") . ')';
3239
            }
3240
            if ($nbassignetouser > 1 && $action != 'view') {
3241
                $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 . '">';
3242
            }
3243
            // Show my availability
3244
            if ($showproperties) {
3245
                if ($ownerid == $value['id'] && is_array($listofuserid) && count($listofuserid) && in_array($ownerid, array_keys($listofuserid))) {
3246
                    $out .= '<div class="myavailability inline-block">';
3247
                    $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>';
3248
                    $out .= '</div>';
3249
                }
3250
            }
3251
            //$out.=' '.($value['mandatory']?$langs->trans("Mandatory"):$langs->trans("Optional"));
3252
            //$out.=' '.($value['transparency']?$langs->trans("Busy"):$langs->trans("NotBusy"));
3253
3254
            $out .= '</li>';
3255
            $i++;
3256
        }
3257
        if ($nbassignetouser) {
3258
            $out .= '</ul>';
3259
        }
3260
3261
        // Method with no ajax
3262
        if ($action != 'view') {
3263
            $out .= '<input type="hidden" class="removedassignedhidden" name="removedassigned" value="">';
3264
            $out .= '<script nonce="' . getNonce() . '" type="text/javascript">jQuery(document).ready(function () {';
3265
            $out .= 'jQuery(".removedassigned").click(function() { jQuery(".removedassignedhidden").val(jQuery(this).val()); });';
3266
            $out .= 'jQuery(".assignedtouser").change(function() { console.log(jQuery(".assignedtouser option:selected").val());';
3267
            $out .= ' if (jQuery(".assignedtouser option:selected").val() > 0) { jQuery("#' . $action . 'assignedtouser").attr("disabled", false); }';
3268
            $out .= ' else { jQuery("#' . $action . 'assignedtouser").attr("disabled", true); }';
3269
            $out .= '});';
3270
            $out .= '})</script>';
3271
            $out .= $this->select_dolusers('', $htmlname, $show_empty, $exclude, $disabled, $include, $enableonly, $force_entity, $maxlength, $showstatus, $morefilter);
3272
            $out .= ' <input type="submit" disabled class="button valignmiddle smallpaddingimp reposition" id="' . $action . 'assignedtouser" name="' . $action . 'assignedtouser" value="' . dol_escape_htmltag($langs->trans("Add")) . '">';
3273
            $out .= '<br>';
3274
        }
3275
3276
        return $out;
3277
    }
3278
3279
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3280
3281
    /**
3282
     * Return select list of resources. Selected resources are stored into session.
3283
     * List of resources are provided into $_SESSION['assignedtoresource'].
3284
     *
3285
     * @param string $action Value for $action
3286
     * @param string $htmlname Field name in form
3287
     * @param int $show_empty 0=list without the empty value, 1=add empty value
3288
     * @param int[] $exclude Array list of users id to exclude
3289
     * @param int<0,1> $disabled If select list must be disabled
3290
     * @param int[]|string $include Array list of users id to include or 'hierarchy' to have only supervised users
3291
     * @param int[] $enableonly Array list of users id to be enabled. All other must be disabled
3292
     * @param string $force_entity '0' or Ids of environment to force
3293
     * @param int $maxlength Maximum length of string into list (0=no limit)
3294
     * @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
3295
     * @param string $morefilter Add more filters into sql request
3296
     * @param int $showproperties Show properties of each attendees
3297
     * @param array<int,array{transparency:bool|int<0,1>}> $listofresourceid Array with properties of each resource
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<int,array{transparency:bool|int<0,1>}> at position 10 could not be parsed: Expected '}' at position 10, but found 'int'.
Loading history...
3298
     * @return    string                    HTML select string
3299
     */
3300
    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())
3301
    {
3302
        // phpcs:enable
3303
        global $langs;
3304
3305
        $formresources = new FormResource($this->db);
3306
        $resourcestatic = new Dolresource($this->db);
3307
3308
        $out = '';
3309
        if (!empty($_SESSION['assignedtoresource'])) {
3310
            $assignedtoresource = json_decode($_SESSION['assignedtoresource'], true);
3311
            if (!is_array($assignedtoresource)) {
3312
                $assignedtoresource = array();
3313
            }
3314
        } else {
3315
            $assignedtoresource = array();
3316
        }
3317
        $nbassignetoresource = count($assignedtoresource);
3318
3319
        //if ($nbassignetoresource && $action != 'view') $out .= '<br>';
3320
        if ($nbassignetoresource) {
3321
            $out .= '<ul class="attendees">';
3322
        }
3323
        $i = 0;
3324
3325
        foreach ($assignedtoresource as $key => $value) {
3326
            $out .= '<li>';
3327
            $resourcestatic->fetch($value['id']);
3328
            $out .= $resourcestatic->getNomUrl(-1);
3329
            if ($nbassignetoresource > 1 && $action != 'view') {
3330
                $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 . '">';
3331
            }
3332
            // Show my availability
3333
            if ($showproperties) {
3334
                if (is_array($listofresourceid) && count($listofresourceid)) {
3335
                    $out .= '<div class="myavailability inline-block">';
3336
                    $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>';
3337
                    $out .= '</div>';
3338
                }
3339
            }
3340
            //$out.=' '.($value['mandatory']?$langs->trans("Mandatory"):$langs->trans("Optional"));
3341
            //$out.=' '.($value['transparency']?$langs->trans("Busy"):$langs->trans("NotBusy"));
3342
3343
            $out .= '</li>';
3344
            $i++;
3345
        }
3346
        if ($nbassignetoresource) {
3347
            $out .= '</ul>';
3348
        }
3349
3350
        // Method with no ajax
3351
        if ($action != 'view') {
3352
            $out .= '<input type="hidden" class="removedassignedhidden" name="removedassignedresource" value="">';
3353
            $out .= '<script nonce="' . getNonce() . '" type="text/javascript">jQuery(document).ready(function () {';
3354
            $out .= 'jQuery(".removedassignedresource").click(function() { jQuery(".removedassignedresourcehidden").val(jQuery(this).val()); });';
3355
            $out .= 'jQuery(".assignedtoresource").change(function() { console.log(jQuery(".assignedtoresource option:selected").val());';
3356
            $out .= ' if (jQuery(".assignedtoresource option:selected").val() > 0) { jQuery("#' . $action . 'assignedtoresource").attr("disabled", false); }';
3357
            $out .= ' else { jQuery("#' . $action . 'assignedtoresource").attr("disabled", true); }';
3358
            $out .= '});';
3359
            $out .= '})</script>';
3360
3361
            $events = array();
3362
            $out .= img_picto('', 'resource', 'class="pictofixedwidth"');
3363
            $out .= $formresources->select_resource_list(0, $htmlname, [], 1, 1, 0, $events, array(), 2, 0);
3364
            //$out .= $this->select_dolusers('', $htmlname, $show_empty, $exclude, $disabled, $include, $enableonly, $force_entity, $maxlength, $showstatus, $morefilter);
3365
            $out .= ' <input type="submit" disabled class="button valignmiddle smallpaddingimp reposition" id="' . $action . 'assignedtoresource" name="' . $action . 'assignedtoresource" value="' . dol_escape_htmltag($langs->trans("Add")) . '">';
3366
            $out .= '<br>';
3367
        }
3368
3369
        return $out;
3370
    }
3371
3372
    /**
3373
     *  Return list of products for customer.
3374
     *  Use Ajax if Ajax activated or go to select_produits_list
3375
     *
3376
     * @param int $selected Preselected products
3377
     * @param string $htmlname Name of HTML select field (must be unique in page).
3378
     * @param int|string $filtertype Filter on product type (''=nofilter, 0=product, 1=service)
3379
     * @param int $limit Limit on number of returned lines
3380
     * @param int $price_level Level of price to show
3381
     * @param int $status Sell status: -1=No filter on sell status, 0=Products not on sell, 1=Products on sell
3382
     * @param int $finished 2=all, 1=finished, 0=raw material
3383
     * @param string $selected_input_value Value of preselected input text (for use with ajax)
3384
     * @param int $hidelabel Hide label (0=no, 1=yes, 2=show search icon (before) and placeholder, 3 search icon after)
3385
     * @param array<string,string|string[]> $ajaxoptions Options for ajax_autocompleter
3386
     * @param int $socid Thirdparty Id (to get also price dedicated to this customer)
3387
     * @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.
3388
     * @param int $forcecombo Force to use combo box
3389
     * @param string $morecss Add more css on select
3390
     * @param int<0,1> $hidepriceinlabel 1=Hide prices in label
3391
     * @param string $warehouseStatus Warehouse status filter to count the quantity in stock. Following comma separated filter options can be used
3392
     *                                                  'warehouseopen' = count products from open warehouses,
3393
     *                                                  'warehouseclosed' = count products from closed warehouses,
3394
     *                                                  'warehouseinternal' = count products from warehouses for internal correct/transfer only
3395
     * @param      ?mixed[] $selected_combinations Selected combinations. Format: array([attrid] => attrval, [...])
3396
     * @param int<0,1> $nooutput No print if 1, return the output into a string
3397
     * @param int<-1,1> $status_purchase Purchase status: -1=No filter on purchase status, 0=Products not on purchase, 1=Products on purchase
3398
     * @return     void|string
3399
     */
3400
    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)
3401
    {
3402
        // phpcs:enable
3403
        global $langs, $conf;
3404
3405
        $out = '';
3406
3407
        // check parameters
3408
        $price_level = (!empty($price_level) ? $price_level : 0);
3409
        if (is_null($ajaxoptions)) {
3410
            $ajaxoptions = array();
3411
        }
3412
3413
        if (strval($filtertype) === '' && (isModEnabled("product") || isModEnabled("service"))) {
3414
            if (isModEnabled("product") && !isModEnabled('service')) {
3415
                $filtertype = '0';
3416
            } elseif (!isModEnabled('product') && isModEnabled("service")) {
3417
                $filtertype = '1';
3418
            }
3419
        }
3420
3421
        if (!empty($conf->use_javascript_ajax) && getDolGlobalString('PRODUIT_USE_SEARCH_TO_SELECT')) {
3422
            $placeholder = '';
3423
3424
            if ($selected && empty($selected_input_value)) {
3425
                $producttmpselect = new Product($this->db);
3426
                $producttmpselect->fetch($selected);
3427
                $selected_input_value = $producttmpselect->ref;
3428
                unset($producttmpselect);
3429
            }
3430
            // handle case where product or service module is disabled + no filter specified
3431
            if ($filtertype == '') {
3432
                if (!isModEnabled('product')) { // when product module is disabled, show services only
3433
                    $filtertype = 1;
3434
                } elseif (!isModEnabled('service')) { // when service module is disabled, show products only
3435
                    $filtertype = 0;
3436
                }
3437
            }
3438
            // mode=1 means customers products
3439
            $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;
3440
            $out .= ajax_autocompleter($selected, $htmlname, constant('BASE_URL') . '/product/ajax/products.php', $urloption, $conf->global->PRODUIT_USE_SEARCH_TO_SELECT, 1, $ajaxoptions);
3441
3442
            if (isModEnabled('variants') && is_array($selected_combinations)) {
3443
                // Code to automatically insert with javascript the select of attributes under the select of product
3444
                // when a parent of variant has been selected.
3445
                $out .= '
3446
				<!-- script to auto show attributes select tags if a variant was selected -->
3447
				<script nonce="' . getNonce() . '">
3448
					// auto show attributes fields
3449
					selected = ' . json_encode($selected_combinations) . ';
3450
					combvalues = {};
3451
3452
					jQuery(document).ready(function () {
3453
3454
						jQuery("input[name=\'prod_entry_mode\']").change(function () {
3455
							if (jQuery(this).val() == \'free\') {
3456
								jQuery(\'div#attributes_box\').empty();
3457
							}
3458
						});
3459
3460
						jQuery("input#' . $htmlname . '").change(function () {
3461
3462
							if (!jQuery(this).val()) {
3463
								jQuery(\'div#attributes_box\').empty();
3464
								return;
3465
							}
3466
3467
							console.log("A change has started. We get variants fields to inject html select");
3468
3469
							jQuery.getJSON("' . constant('BASE_URL') . '/variants/ajax/getCombinations.php", {
3470
								id: jQuery(this).val()
3471
							}, function (data) {
3472
								jQuery(\'div#attributes_box\').empty();
3473
3474
								jQuery.each(data, function (key, val) {
3475
3476
									combvalues[val.id] = val.values;
3477
3478
									var span = jQuery(document.createElement(\'div\')).css({
3479
										\'display\': \'table-row\'
3480
									});
3481
3482
									span.append(
3483
										jQuery(document.createElement(\'div\')).text(val.label).css({
3484
											\'font-weight\': \'bold\',
3485
											\'display\': \'table-cell\'
3486
										})
3487
									);
3488
3489
									var html = jQuery(document.createElement(\'select\')).attr(\'name\', \'combinations[\' + val.id + \']\').css({
3490
										\'margin-left\': \'15px\',
3491
										\'white-space\': \'pre\'
3492
									}).append(
3493
										jQuery(document.createElement(\'option\')).val(\'\')
3494
									);
3495
3496
									jQuery.each(combvalues[val.id], function (key, val) {
3497
										var tag = jQuery(document.createElement(\'option\')).val(val.id).html(val.value);
3498
3499
										if (selected[val.fk_product_attribute] == val.id) {
3500
											tag.attr(\'selected\', \'selected\');
3501
										}
3502
3503
										html.append(tag);
3504
									});
3505
3506
									span.append(html);
3507
									jQuery(\'div#attributes_box\').append(span);
3508
								});
3509
							})
3510
						});
3511
3512
						' . ($selected ? 'jQuery("input#' . $htmlname . '").change();' : '') . '
3513
					});
3514
				</script>
3515
                ';
3516
            }
3517
3518
            if (empty($hidelabel)) {
3519
                $out .= $langs->trans("RefOrLabel") . ' : ';
3520
            } elseif ($hidelabel > 1) {
3521
                $placeholder = ' placeholder="' . $langs->trans("RefOrLabel") . '"';
3522
                if ($hidelabel == 2) {
3523
                    $out .= img_picto($langs->trans("Search"), 'search');
3524
                }
3525
            }
3526
            $out .= '<input type="text" class="minwidth100' . ($morecss ? ' ' . $morecss : '') . '" name="search_' . $htmlname . '" id="search_' . $htmlname . '" value="' . $selected_input_value . '"' . $placeholder . ' ' . (getDolGlobalString('PRODUCT_SEARCH_AUTOFOCUS') ? 'autofocus' : '') . ' />';
3527
            if ($hidelabel == 3) {
3528
                $out .= img_picto($langs->trans("Search"), 'search');
3529
            }
3530
        } else {
3531
            $out .= $this->select_produits_list($selected, $htmlname, $filtertype, $limit, $price_level, '', $status, $finished, 0, $socid, $showempty, $forcecombo, $morecss, $hidepriceinlabel, $warehouseStatus, $status_purchase);
3532
        }
3533
3534
        if (empty($nooutput)) {
3535
            print $out;
3536
        } else {
3537
            return $out;
3538
        }
3539
    }
3540
3541
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3542
3543
    /**
3544
     * Return list of products for a customer.
3545
     * Called by select_produits.
3546
     *
3547
     * @param int $selected Preselected product
3548
     * @param string $htmlname Name of select html
3549
     * @param string $filtertype Filter on product type (''=nofilter, 0=product, 1=service)
3550
     * @param int $limit Limit on number of returned lines
3551
     * @param int $price_level Level of price to show
3552
     * @param string $filterkey Filter on product
3553
     * @param int $status -1=Return all products, 0=Products not on sell, 1=Products on sell
3554
     * @param int $finished Filter on finished field: 2=No filter
3555
     * @param int $outputmode 0=HTML select string, 1=Array
3556
     * @param int $socid Thirdparty Id (to get also price dedicated to this customer)
3557
     * @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.
3558
     * @param int $forcecombo Force to use combo box
3559
     * @param string $morecss Add more css on select
3560
     * @param int $hidepriceinlabel 1=Hide prices in label
3561
     * @param string $warehouseStatus Warehouse status filter to group/count stock. Following comma separated filter options can be used.
3562
     *                                              'warehouseopen' = count products from open warehouses,
3563
     *                                              'warehouseclosed' = count products from closed warehouses,
3564
     *                                              'warehouseinternal' = count products from warehouses for internal correct/transfer only
3565
     * @param int $status_purchase Purchase status -1=Return all products, 0=Products not on purchase, 1=Products on purchase
3566
     * @return  array|string                        Array of keys for json
3567
     */
3568
    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)
3569
    {
3570
        // phpcs:enable
3571
        global $langs;
3572
        global $hookmanager;
3573
3574
        $out = '';
3575
        $outarray = array();
3576
3577
        // Units
3578
        if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
3579
            $langs->load('other');
3580
        }
3581
3582
        $warehouseStatusArray = array();
3583
        if (!empty($warehouseStatus)) {
3584
            if (preg_match('/warehouseclosed/', $warehouseStatus)) {
3585
                $warehouseStatusArray[] = Entrepot::STATUS_CLOSED;
3586
            }
3587
            if (preg_match('/warehouseopen/', $warehouseStatus)) {
3588
                $warehouseStatusArray[] = Entrepot::STATUS_OPEN_ALL;
3589
            }
3590
            if (preg_match('/warehouseinternal/', $warehouseStatus)) {
3591
                $warehouseStatusArray[] = Entrepot::STATUS_OPEN_INTERNAL;
3592
            }
3593
        }
3594
3595
        $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";
3596
        if (count($warehouseStatusArray)) {
3597
            $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
3598
        } else {
3599
            $selectFieldsGrouped = ", " . $this->db->ifsql("p.stock IS NULL", 0, "p.stock") . " AS stock";
3600
        }
3601
3602
        $sql = "SELECT ";
3603
3604
        // Add select from hooks
3605
        $parameters = array();
3606
        $reshook = $hookmanager->executeHooks('selectProductsListSelect', $parameters); // Note that $action and $object may have been modified by hook
3607
        if (empty($reshook)) {
3608
            $sql .= $selectFields . $selectFieldsGrouped . $hookmanager->resPrint;
3609
        } else {
3610
            $sql .= $hookmanager->resPrint;
3611
        }
3612
3613
        if (getDolGlobalString('PRODUCT_SORT_BY_CATEGORY')) {
3614
            //Product category
3615
            $sql .= ", (SELECT " . $this->db->prefix() . "categorie_product.fk_categorie
3616
						FROM " . $this->db->prefix() . "categorie_product
3617
						WHERE " . $this->db->prefix() . "categorie_product.fk_product=p.rowid
3618
						LIMIT 1
3619
				) AS categorie_product_id ";
3620
        }
3621
3622
        //Price by customer
3623
        if (getDolGlobalString('PRODUIT_CUSTOMER_PRICES') && !empty($socid)) {
3624
            $sql .= ', pcp.rowid as idprodcustprice, pcp.price as custprice, pcp.price_ttc as custprice_ttc,';
3625
            $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';
3626
            $selectFields .= ", idprodcustprice, custprice, custprice_ttc, custprice_base_type, custtva_tx, custdefault_vat_code, custref";
3627
        }
3628
        // Units
3629
        if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
3630
            $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";
3631
            $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';
3632
        }
3633
3634
        // Multilang : we add translation
3635
        if (getDolGlobalInt('MAIN_MULTILANGS')) {
3636
            $sql .= ", pl.label as label_translated";
3637
            $sql .= ", pl.description as description_translated";
3638
            $selectFields .= ", label_translated";
3639
            $selectFields .= ", description_translated";
3640
        }
3641
        // Price by quantity
3642
        if (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES')) {
3643
            $sql .= ", (SELECT pp.rowid FROM " . $this->db->prefix() . "product_price as pp WHERE pp.fk_product = p.rowid";
3644
            if ($price_level >= 1 && getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES')) {
3645
                $sql .= " AND price_level = " . ((int)$price_level);
3646
            }
3647
            $sql .= " ORDER BY date_price";
3648
            $sql .= " DESC LIMIT 1) as price_rowid";
3649
            $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
3650
            if ($price_level >= 1 && getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES')) {
3651
                $sql .= " AND price_level = " . ((int)$price_level);
3652
            }
3653
            $sql .= " ORDER BY date_price";
3654
            $sql .= " DESC LIMIT 1) as price_by_qty";
3655
            $selectFields .= ", price_rowid, price_by_qty";
3656
        }
3657
3658
        $sql .= " FROM " . $this->db->prefix() . "product as p";
3659
3660
        if (getDolGlobalString('MAIN_SEARCH_PRODUCT_FORCE_INDEX')) {
3661
            $sql .= " USE INDEX (" . $this->db->sanitize(getDolGlobalString('MAIN_PRODUCT_FORCE_INDEX')) . ")";
3662
        }
3663
3664
        // Add from (left join) from hooks
3665
        $parameters = array();
3666
        $reshook = $hookmanager->executeHooks('selectProductsListFrom', $parameters); // Note that $action and $object may have been modified by hook
3667
        $sql .= $hookmanager->resPrint;
3668
3669
        if (count($warehouseStatusArray)) {
3670
            $sql .= " LEFT JOIN " . $this->db->prefix() . "product_stock as ps on ps.fk_product = p.rowid";
3671
            $sql .= " LEFT JOIN " . $this->db->prefix() . "entrepot as e on ps.fk_entrepot = e.rowid AND e.entity IN (" . getEntity('stock') . ")";
3672
            $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.
3673
        }
3674
3675
        // include search in supplier ref
3676
        if (getDolGlobalString('MAIN_SEARCH_PRODUCT_BY_FOURN_REF')) {
3677
            $sql .= " LEFT JOIN " . $this->db->prefix() . "product_fournisseur_price as pfp ON p.rowid = pfp.fk_product";
3678
        }
3679
3680
        //Price by customer
3681
        if (getDolGlobalString('PRODUIT_CUSTOMER_PRICES') && !empty($socid)) {
3682
            $sql .= " LEFT JOIN  " . $this->db->prefix() . "product_customer_price as pcp ON pcp.fk_soc=" . ((int)$socid) . " AND pcp.fk_product=p.rowid";
3683
        }
3684
        // Units
3685
        if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
3686
            $sql .= " LEFT JOIN " . $this->db->prefix() . "c_units u ON u.rowid = p.fk_unit";
3687
        }
3688
        // Multilang : we add translation
3689
        if (getDolGlobalInt('MAIN_MULTILANGS')) {
3690
            $sql .= " LEFT JOIN " . $this->db->prefix() . "product_lang as pl ON pl.fk_product = p.rowid ";
3691
            if (getDolGlobalString('PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE') && !empty($socid)) {
3692
                $soc = new Societe($this->db);
3693
                $result = $soc->fetch($socid);
3694
                if ($result > 0 && !empty($soc->default_lang)) {
3695
                    $sql .= " AND pl.lang = '" . $this->db->escape($soc->default_lang) . "'";
3696
                } else {
3697
                    $sql .= " AND pl.lang = '" . $this->db->escape($langs->getDefaultLang()) . "'";
3698
                }
3699
            } else {
3700
                $sql .= " AND pl.lang = '" . $this->db->escape($langs->getDefaultLang()) . "'";
3701
            }
3702
        }
3703
3704
        if (getDolGlobalString('PRODUIT_ATTRIBUTES_HIDECHILD')) {
3705
            $sql .= " LEFT JOIN " . $this->db->prefix() . "product_attribute_combination pac ON pac.fk_product_child = p.rowid";
3706
        }
3707
3708
        $sql .= ' WHERE p.entity IN (' . getEntity('product') . ')';
3709
3710
        if (getDolGlobalString('PRODUIT_ATTRIBUTES_HIDECHILD')) {
3711
            $sql .= " AND pac.rowid IS NULL";
3712
        }
3713
3714
        if ($finished == 0) {
3715
            $sql .= " AND p.finished = " . ((int)$finished);
3716
        } elseif ($finished == 1) {
3717
            $sql .= " AND p.finished = " . ((int)$finished);
3718
        }
3719
        if ($status >= 0) {
3720
            $sql .= " AND p.tosell = " . ((int)$status);
3721
        }
3722
        if ($status_purchase >= 0) {
3723
            $sql .= " AND p.tobuy = " . ((int)$status_purchase);
3724
        }
3725
        // Filter by product type
3726
        if (strval($filtertype) != '') {
3727
            $sql .= " AND p.fk_product_type = " . ((int)$filtertype);
3728
        } elseif (!isModEnabled('product')) { // when product module is disabled, show services only
3729
            $sql .= " AND p.fk_product_type = 1";
3730
        } elseif (!isModEnabled('service')) { // when service module is disabled, show products only
3731
            $sql .= " AND p.fk_product_type = 0";
3732
        }
3733
        // Add where from hooks
3734
        $parameters = array();
3735
        $reshook = $hookmanager->executeHooks('selectProductsListWhere', $parameters); // Note that $action and $object may have been modified by hook
3736
        $sql .= $hookmanager->resPrint;
3737
        // Add criteria on ref/label
3738
        if ($filterkey != '') {
3739
            $sql .= ' AND (';
3740
            $prefix = !getDolGlobalString('PRODUCT_DONOTSEARCH_ANYWHERE') ? '%' : ''; // Can use index if PRODUCT_DONOTSEARCH_ANYWHERE is on
3741
            // For natural search
3742
            $search_crit = explode(' ', $filterkey);
3743
            $i = 0;
3744
            if (count($search_crit) > 1) {
3745
                $sql .= "(";
3746
            }
3747
            foreach ($search_crit as $crit) {
3748
                if ($i > 0) {
3749
                    $sql .= " AND ";
3750
                }
3751
                $sql .= "(p.ref LIKE '" . $this->db->escape($prefix . $crit) . "%' OR p.label LIKE '" . $this->db->escape($prefix . $crit) . "%'";
3752
                if (getDolGlobalInt('MAIN_MULTILANGS')) {
3753
                    $sql .= " OR pl.label LIKE '" . $this->db->escape($prefix . $crit) . "%'";
3754
                }
3755
                if (getDolGlobalString('PRODUIT_CUSTOMER_PRICES') && !empty($socid)) {
3756
                    $sql .= " OR pcp.ref_customer LIKE '" . $this->db->escape($prefix . $crit) . "%'";
3757
                }
3758
                if (getDolGlobalString('PRODUCT_AJAX_SEARCH_ON_DESCRIPTION')) {
3759
                    $sql .= " OR p.description LIKE '" . $this->db->escape($prefix . $crit) . "%'";
3760
                    if (getDolGlobalInt('MAIN_MULTILANGS')) {
3761
                        $sql .= " OR pl.description LIKE '" . $this->db->escape($prefix . $crit) . "%'";
3762
                    }
3763
                }
3764
                if (getDolGlobalString('MAIN_SEARCH_PRODUCT_BY_FOURN_REF')) {
3765
                    $sql .= " OR pfp.ref_fourn LIKE '" . $this->db->escape($prefix . $crit) . "%'";
3766
                }
3767
                $sql .= ")";
3768
                $i++;
3769
            }
3770
            if (count($search_crit) > 1) {
3771
                $sql .= ")";
3772
            }
3773
            if (isModEnabled('barcode')) {
3774
                $sql .= " OR p.barcode LIKE '" . $this->db->escape($prefix . $filterkey) . "%'";
3775
            }
3776
            $sql .= ')';
3777
        }
3778
        if (count($warehouseStatusArray)) {
3779
            $sql .= " GROUP BY " . $selectFields;
3780
        }
3781
3782
        //Sort by category
3783
        if (getDolGlobalString('PRODUCT_SORT_BY_CATEGORY')) {
3784
            $sql .= " ORDER BY categorie_product_id ";
3785
            //ASC OR DESC order
3786
            (getDolGlobalInt('PRODUCT_SORT_BY_CATEGORY') == 1) ? $sql .= "ASC" : $sql .= "DESC";
3787
        } else {
3788
            $sql .= $this->db->order("p.ref");
3789
        }
3790
3791
        $sql .= $this->db->plimit($limit, 0);
3792
3793
        // Build output string
3794
        dol_syslog(get_only_class($this) . "::select_produits_list search products", LOG_DEBUG);
3795
        $result = $this->db->query($sql);
3796
        if ($result) {
3797
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/product.lib.php';
3798
3799
            $num = $this->db->num_rows($result);
3800
3801
            $events = array();
3802
3803
            if (!$forcecombo) {
3804
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
3805
                $out .= ajax_combobox($htmlname, $events, getDolGlobalInt("PRODUIT_USE_SEARCH_TO_SELECT"));
3806
            }
3807
3808
            $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" id="' . $htmlname . '">';
3809
3810
            $textifempty = '';
3811
            // Do not use textifempty = ' ' or '&nbsp;' here, or search on key will search on ' key'.
3812
            //if (!empty($conf->use_javascript_ajax) || $forcecombo) $textifempty='';
3813
            if (getDolGlobalString('PRODUIT_USE_SEARCH_TO_SELECT')) {
3814
                if ($showempty && !is_numeric($showempty)) {
3815
                    $textifempty = $langs->trans($showempty);
3816
                } else {
3817
                    $textifempty .= $langs->trans("All");
3818
                }
3819
            } else {
3820
                if ($showempty && !is_numeric($showempty)) {
3821
                    $textifempty = $langs->trans($showempty);
3822
                }
3823
            }
3824
            if ($showempty) {
3825
                $out .= '<option value="-1" selected>' . ($textifempty ? $textifempty : '&nbsp;') . '</option>';
3826
            }
3827
3828
            $i = 0;
3829
            while ($num && $i < $num) {
3830
                $opt = '';
3831
                $optJson = array();
3832
                $objp = $this->db->fetch_object($result);
3833
3834
                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
3835
                    $sql = "SELECT rowid, quantity, price, unitprice, remise_percent, remise, price_base_type";
3836
                    $sql .= " FROM " . $this->db->prefix() . "product_price_by_qty";
3837
                    $sql .= " WHERE fk_product_price = " . ((int)$objp->price_rowid);
3838
                    $sql .= " ORDER BY quantity ASC";
3839
3840
                    dol_syslog(get_only_class($this) . "::select_produits_list search prices by qty", LOG_DEBUG);
3841
                    $result2 = $this->db->query($sql);
3842
                    if ($result2) {
3843
                        $nb_prices = $this->db->num_rows($result2);
3844
                        $j = 0;
3845
                        while ($nb_prices && $j < $nb_prices) {
3846
                            $objp2 = $this->db->fetch_object($result2);
3847
3848
                            $objp->price_by_qty_rowid = $objp2->rowid;
3849
                            $objp->price_by_qty_price_base_type = $objp2->price_base_type;
3850
                            $objp->price_by_qty_quantity = $objp2->quantity;
3851
                            $objp->price_by_qty_unitprice = $objp2->unitprice;
3852
                            $objp->price_by_qty_remise_percent = $objp2->remise_percent;
3853
                            // For backward compatibility
3854
                            $objp->quantity = $objp2->quantity;
3855
                            $objp->price = $objp2->price;
3856
                            $objp->unitprice = $objp2->unitprice;
3857
                            $objp->remise_percent = $objp2->remise_percent;
3858
3859
                            //$objp->tva_tx is not overwritten by $objp2 value
3860
                            //$objp->default_vat_code is not overwritten by $objp2 value
3861
3862
                            $this->constructProductListOption($objp, $opt, $optJson, 0, $selected, $hidepriceinlabel, $filterkey);
3863
3864
                            $j++;
3865
3866
                            // Add new entry
3867
                            // "key" value of json key array is used by jQuery automatically as selected value
3868
                            // "label" value of json key array is used by jQuery automatically as text for combo box
3869
                            $out .= $opt;
3870
                            array_push($outarray, $optJson);
3871
                        }
3872
                    }
3873
                } else {
3874
                    if (isModEnabled('dynamicprices') && !empty($objp->fk_price_expression)) {
3875
                        $price_product = new Product($this->db);
3876
                        $price_product->fetch($objp->rowid, '', '', 1);
3877
3878
                        $priceparser = new PriceParser($this->db);
3879
                        $price_result = $priceparser->parseProduct($price_product);
3880
                        if ($price_result >= 0) {
3881
                            $objp->price = $price_result;
3882
                            $objp->unitprice = $price_result;
3883
                            //Calculate the VAT
3884
                            $objp->price_ttc = (float)price2num($objp->price) * (1 + ($objp->tva_tx / 100));
3885
                            $objp->price_ttc = price2num($objp->price_ttc, 'MU');
3886
                        }
3887
                    }
3888
3889
                    $this->constructProductListOption($objp, $opt, $optJson, $price_level, $selected, $hidepriceinlabel, $filterkey);
3890
                    // Add new entry
3891
                    // "key" value of json key array is used by jQuery automatically as selected value
3892
                    // "label" value of json key array is used by jQuery automatically as text for combo box
3893
                    $out .= $opt;
3894
                    array_push($outarray, $optJson);
3895
                }
3896
3897
                $i++;
3898
            }
3899
3900
            $out .= '</select>';
3901
3902
            $this->db->free($result);
3903
3904
            if (empty($outputmode)) {
3905
                return $out;
3906
            }
3907
3908
            return $outarray;
3909
        } else {
3910
            dol_print_error($this->db);
3911
        }
3912
3913
        return '';
3914
    }
3915
3916
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3917
3918
    /**
3919
     * Function to forge the string with OPTIONs of SELECT.
3920
     * This define value for &$opt and &$optJson.
3921
     * This function is called by select_produits_list().
3922
     *
3923
     * @param stdClass $objp Resultset of fetch
3924
     * @param string $opt Option (var used for returned value in string option format)
3925
     * @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)
3926
     * @param int $price_level Price level
3927
     * @param int $selected Preselected value
3928
     * @param int<0,1> $hidepriceinlabel Hide price in label
3929
     * @param string $filterkey Filter key to highlight
3930
     * @param int<0,1> $novirtualstock Do not load virtual stock, even if slow option STOCK_SHOW_VIRTUAL_STOCK_IN_PRODUCTS_COMBO is on.
3931
     * @return    void
3932
     */
3933
    protected function constructProductListOption(&$objp, &$opt, &$optJson, $price_level, $selected, $hidepriceinlabel = 0, $filterkey = '', $novirtualstock = 0)
3934
    {
3935
        global $langs, $conf, $user;
3936
        global $hookmanager;
3937
3938
        $outkey = '';
3939
        $outval = '';
3940
        $outref = '';
3941
        $outlabel = '';
3942
        $outlabel_translated = '';
3943
        $outdesc = '';
3944
        $outdesc_translated = '';
3945
        $outbarcode = '';
3946
        $outorigin = '';
3947
        $outtype = '';
3948
        $outprice_ht = '';
3949
        $outprice_ttc = '';
3950
        $outpricebasetype = '';
3951
        $outtva_tx = '';
3952
        $outdefault_vat_code = '';
3953
        $outqty = 1;
3954
        $outdiscount = 0;
3955
3956
        $maxlengtharticle = (!getDolGlobalString('PRODUCT_MAX_LENGTH_COMBO') ? 48 : $conf->global->PRODUCT_MAX_LENGTH_COMBO);
3957
3958
        $label = $objp->label;
3959
        if (!empty($objp->label_translated)) {
3960
            $label = $objp->label_translated;
3961
        }
3962
        if (!empty($filterkey) && $filterkey != '') {
3963
            $label = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', $label, 1);
3964
        }
3965
3966
        $outkey = $objp->rowid;
3967
        $outref = $objp->ref;
3968
        $outrefcust = empty($objp->custref) ? '' : $objp->custref;
3969
        $outlabel = $objp->label;
3970
        $outdesc = $objp->description;
3971
        if (getDolGlobalInt('MAIN_MULTILANGS')) {
3972
            $outlabel_translated = $objp->label_translated;
3973
            $outdesc_translated = $objp->description_translated;
3974
        }
3975
        $outbarcode = $objp->barcode;
3976
        $outorigin = $objp->fk_country;
3977
        $outpbq = empty($objp->price_by_qty_rowid) ? '' : $objp->price_by_qty_rowid;
3978
3979
        $outtype = $objp->fk_product_type;
3980
        $outdurationvalue = $outtype == Product::TYPE_SERVICE ? substr($objp->duration, 0, dol_strlen($objp->duration) - 1) : '';
3981
        $outdurationunit = $outtype == Product::TYPE_SERVICE ? substr($objp->duration, -1) : '';
3982
3983
        if ($outorigin && getDolGlobalString('PRODUCT_SHOW_ORIGIN_IN_COMBO')) {
3984
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/company.lib.php';
3985
        }
3986
3987
        // Units
3988
        $outvalUnits = '';
3989
        if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
3990
            if (!empty($objp->unit_short)) {
3991
                $outvalUnits .= ' - ' . $objp->unit_short;
3992
            }
3993
        }
3994
        if (getDolGlobalString('PRODUCT_SHOW_DIMENSIONS_IN_COMBO')) {
3995
            if (!empty($objp->weight) && $objp->weight_units !== null) {
3996
                $unitToShow = showDimensionInBestUnit($objp->weight, $objp->weight_units, 'weight', $langs);
3997
                $outvalUnits .= ' - ' . $unitToShow;
3998
            }
3999
            if ((!empty($objp->length) || !empty($objp->width) || !empty($objp->height)) && $objp->length_units !== null) {
4000
                $unitToShow = $objp->length . ' x ' . $objp->width . ' x ' . $objp->height . ' ' . measuringUnitString(0, 'size', $objp->length_units);
4001
                $outvalUnits .= ' - ' . $unitToShow;
4002
            }
4003
            if (!empty($objp->surface) && $objp->surface_units !== null) {
4004
                $unitToShow = showDimensionInBestUnit($objp->surface, $objp->surface_units, 'surface', $langs);
4005
                $outvalUnits .= ' - ' . $unitToShow;
4006
            }
4007
            if (!empty($objp->volume) && $objp->volume_units !== null) {
4008
                $unitToShow = showDimensionInBestUnit($objp->volume, $objp->volume_units, 'volume', $langs);
4009
                $outvalUnits .= ' - ' . $unitToShow;
4010
            }
4011
        }
4012
        if ($outdurationvalue && $outdurationunit) {
4013
            $da = array(
4014
                'h' => $langs->trans('Hour'),
4015
                'd' => $langs->trans('Day'),
4016
                'w' => $langs->trans('Week'),
4017
                'm' => $langs->trans('Month'),
4018
                'y' => $langs->trans('Year')
4019
            );
4020
            if (isset($da[$outdurationunit])) {
4021
                $outvalUnits .= ' - ' . $outdurationvalue . ' ' . $langs->transnoentities($da[$outdurationunit] . ($outdurationvalue > 1 ? 's' : ''));
4022
            }
4023
        }
4024
4025
        $opt = '<option value="' . $objp->rowid . '"';
4026
        $opt .= ($objp->rowid == $selected) ? ' selected' : '';
4027
        if (!empty($objp->price_by_qty_rowid) && $objp->price_by_qty_rowid > 0) {
4028
            $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 . '"';
4029
        }
4030
        if (isModEnabled('stock') && isset($objp->stock) && ($objp->fk_product_type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) {
4031
            if ($user->hasRight('stock', 'lire')) {
4032
                if ($objp->stock > 0) {
4033
                    $opt .= ' class="product_line_stock_ok"';
4034
                } elseif ($objp->stock <= 0) {
4035
                    $opt .= ' class="product_line_stock_too_low"';
4036
                }
4037
            }
4038
        }
4039
        if (getDolGlobalString('PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE')) {
4040
            $opt .= ' data-labeltrans="' . $outlabel_translated . '"';
4041
            $opt .= ' data-desctrans="' . dol_escape_htmltag($outdesc_translated) . '"';
4042
        }
4043
        $opt .= '>';
4044
        $opt .= $objp->ref;
4045
        if (!empty($objp->custref)) {
4046
            $opt .= ' (' . $objp->custref . ')';
4047
        }
4048
        if ($outbarcode) {
4049
            $opt .= ' (' . $outbarcode . ')';
4050
        }
4051
        $opt .= ' - ' . dol_trunc($label, $maxlengtharticle);
4052
        if ($outorigin && getDolGlobalString('PRODUCT_SHOW_ORIGIN_IN_COMBO')) {
4053
            $opt .= ' (' . getCountry($outorigin, 1) . ')';
4054
        }
4055
4056
        $objRef = $objp->ref;
4057
        if (!empty($objp->custref)) {
4058
            $objRef .= ' (' . $objp->custref . ')';
4059
        }
4060
        if (!empty($filterkey) && $filterkey != '') {
4061
            $objRef = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', $objRef, 1);
4062
        }
4063
        $outval .= $objRef;
4064
        if ($outbarcode) {
4065
            $outval .= ' (' . $outbarcode . ')';
4066
        }
4067
        $outval .= ' - ' . dol_trunc($label, $maxlengtharticle);
4068
        if ($outorigin && getDolGlobalString('PRODUCT_SHOW_ORIGIN_IN_COMBO')) {
4069
            $outval .= ' (' . getCountry($outorigin, 1) . ')';
4070
        }
4071
4072
        // Units
4073
        $opt .= $outvalUnits;
4074
        $outval .= $outvalUnits;
4075
4076
        $found = 0;
4077
4078
        // Multiprice
4079
        // If we need a particular price level (from 1 to n)
4080
        if (empty($hidepriceinlabel) && $price_level >= 1 && (getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES'))) {
4081
            $sql = "SELECT price, price_ttc, price_base_type, tva_tx, default_vat_code";
4082
            $sql .= " FROM " . $this->db->prefix() . "product_price";
4083
            $sql .= " WHERE fk_product = " . ((int)$objp->rowid);
4084
            $sql .= " AND entity IN (" . getEntity('productprice') . ")";
4085
            $sql .= " AND price_level = " . ((int)$price_level);
4086
            $sql .= " ORDER BY date_price DESC, rowid DESC"; // Warning DESC must be both on date_price and rowid.
4087
            $sql .= " LIMIT 1";
4088
4089
            dol_syslog(get_only_class($this) . '::constructProductListOption search price for product ' . $objp->rowid . ' AND level ' . $price_level, LOG_DEBUG);
4090
            $result2 = $this->db->query($sql);
4091
            if ($result2) {
4092
                $objp2 = $this->db->fetch_object($result2);
4093
                if ($objp2) {
4094
                    $found = 1;
4095
                    if ($objp2->price_base_type == 'HT') {
4096
                        $opt .= ' - ' . price($objp2->price, 1, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->trans("HT");
4097
                        $outval .= ' - ' . price($objp2->price, 0, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->transnoentities("HT");
4098
                    } else {
4099
                        $opt .= ' - ' . price($objp2->price_ttc, 1, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->trans("TTC");
4100
                        $outval .= ' - ' . price($objp2->price_ttc, 0, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->transnoentities("TTC");
4101
                    }
4102
                    $outprice_ht = price($objp2->price);
4103
                    $outprice_ttc = price($objp2->price_ttc);
4104
                    $outpricebasetype = $objp2->price_base_type;
4105
                    if (getDolGlobalString('PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL')) {  // using this option is a bug. kept for backward compatibility
4106
                        $outtva_tx = $objp2->tva_tx;                        // We use the vat rate on line of multiprice
4107
                        $outdefault_vat_code = $objp2->default_vat_code;    // We use the vat code on line of multiprice
4108
                    } else {
4109
                        $outtva_tx = $objp->tva_tx;                            // We use the vat rate of product, not the one on line of multiprice
4110
                        $outdefault_vat_code = $objp->default_vat_code;        // We use the vat code or product, not the one on line of multiprice
4111
                    }
4112
                }
4113
            } else {
4114
                dol_print_error($this->db);
4115
            }
4116
        }
4117
4118
        // Price by quantity
4119
        if (empty($hidepriceinlabel) && !empty($objp->quantity) && $objp->quantity >= 1 && (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES'))) {
4120
            $found = 1;
4121
            $outqty = $objp->quantity;
4122
            $outdiscount = $objp->remise_percent;
4123
            if ($objp->quantity == 1) {
4124
                $opt .= ' - ' . price($objp->unitprice, 1, $langs, 0, 0, -1, $conf->currency) . "/";
4125
                $outval .= ' - ' . price($objp->unitprice, 0, $langs, 0, 0, -1, $conf->currency) . "/";
4126
                $opt .= $langs->trans("Unit"); // Do not use strtolower because it breaks utf8 encoding
4127
                $outval .= $langs->transnoentities("Unit");
4128
            } else {
4129
                $opt .= ' - ' . price($objp->price, 1, $langs, 0, 0, -1, $conf->currency) . "/" . $objp->quantity;
4130
                $outval .= ' - ' . price($objp->price, 0, $langs, 0, 0, -1, $conf->currency) . "/" . $objp->quantity;
4131
                $opt .= $langs->trans("Units"); // Do not use strtolower because it breaks utf8 encoding
4132
                $outval .= $langs->transnoentities("Units");
4133
            }
4134
4135
            $outprice_ht = price($objp->unitprice);
4136
            $outprice_ttc = price($objp->unitprice * (1 + ($objp->tva_tx / 100)));
4137
            $outpricebasetype = $objp->price_base_type;
4138
            $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
4139
            $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
4140
        }
4141
        if (empty($hidepriceinlabel) && !empty($objp->quantity) && $objp->quantity >= 1) {
4142
            $opt .= " (" . price($objp->unitprice, 1, $langs, 0, 0, -1, $conf->currency) . "/" . $langs->trans("Unit") . ")"; // Do not use strtolower because it breaks utf8 encoding
4143
            $outval .= " (" . price($objp->unitprice, 0, $langs, 0, 0, -1, $conf->currency) . "/" . $langs->transnoentities("Unit") . ")"; // Do not use strtolower because it breaks utf8 encoding
4144
        }
4145
        if (empty($hidepriceinlabel) && !empty($objp->remise_percent) && $objp->remise_percent >= 1) {
4146
            $opt .= " - " . $langs->trans("Discount") . " : " . vatrate($objp->remise_percent) . ' %';
4147
            $outval .= " - " . $langs->transnoentities("Discount") . " : " . vatrate($objp->remise_percent) . ' %';
4148
        }
4149
4150
        // Price by customer
4151
        if (empty($hidepriceinlabel) && getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
4152
            if (!empty($objp->idprodcustprice)) {
4153
                $found = 1;
4154
4155
                if ($objp->custprice_base_type == 'HT') {
4156
                    $opt .= ' - ' . price($objp->custprice, 1, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->trans("HT");
4157
                    $outval .= ' - ' . price($objp->custprice, 0, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->transnoentities("HT");
4158
                } else {
4159
                    $opt .= ' - ' . price($objp->custprice_ttc, 1, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->trans("TTC");
4160
                    $outval .= ' - ' . price($objp->custprice_ttc, 0, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->transnoentities("TTC");
4161
                }
4162
4163
                $outprice_ht = price($objp->custprice);
4164
                $outprice_ttc = price($objp->custprice_ttc);
4165
                $outpricebasetype = $objp->custprice_base_type;
4166
                $outtva_tx = $objp->custtva_tx;
4167
                $outdefault_vat_code = $objp->custdefault_vat_code;
4168
            }
4169
        }
4170
4171
        // If level no defined or multiprice not found, we used the default price
4172
        if (empty($hidepriceinlabel) && !$found) {
4173
            if ($objp->price_base_type == 'HT') {
4174
                $opt .= ' - ' . price($objp->price, 1, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->trans("HT");
4175
                $outval .= ' - ' . price($objp->price, 0, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->transnoentities("HT");
4176
            } else {
4177
                $opt .= ' - ' . price($objp->price_ttc, 1, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->trans("TTC");
4178
                $outval .= ' - ' . price($objp->price_ttc, 0, $langs, 0, 0, -1, $conf->currency) . ' ' . $langs->transnoentities("TTC");
4179
            }
4180
            $outprice_ht = price($objp->price);
4181
            $outprice_ttc = price($objp->price_ttc);
4182
            $outpricebasetype = $objp->price_base_type;
4183
            $outtva_tx = $objp->tva_tx;
4184
            $outdefault_vat_code = $objp->default_vat_code;
4185
        }
4186
4187
        if (isModEnabled('stock') && isset($objp->stock) && ($objp->fk_product_type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) {
4188
            if ($user->hasRight('stock', 'lire')) {
4189
                $opt .= ' - ' . $langs->trans("Stock") . ': ' . price(price2num($objp->stock, 'MS'));
4190
4191
                if ($objp->stock > 0) {
4192
                    $outval .= ' - <span class="product_line_stock_ok">';
4193
                } elseif ($objp->stock <= 0) {
4194
                    $outval .= ' - <span class="product_line_stock_too_low">';
4195
                }
4196
                $outval .= $langs->transnoentities("Stock") . ': ' . price(price2num($objp->stock, 'MS'));
4197
                $outval .= '</span>';
4198
                if (empty($novirtualstock) && getDolGlobalString('STOCK_SHOW_VIRTUAL_STOCK_IN_PRODUCTS_COMBO')) {  // Warning, this option may slow down combo list generation
4199
                    $langs->load("stocks");
4200
4201
                    $tmpproduct = new Product($this->db);
4202
                    $tmpproduct->fetch($objp->rowid, '', '', '', 1, 1, 1); // Load product without lang and prices arrays (we just need to make ->virtual_stock() after)
4203
                    $tmpproduct->load_virtual_stock();
4204
                    $virtualstock = $tmpproduct->stock_theorique;
4205
4206
                    $opt .= ' - ' . $langs->trans("VirtualStock") . ':' . $virtualstock;
4207
4208
                    $outval .= ' - ' . $langs->transnoentities("VirtualStock") . ':';
4209
                    if ($virtualstock > 0) {
4210
                        $outval .= '<span class="product_line_stock_ok">';
4211
                    } elseif ($virtualstock <= 0) {
4212
                        $outval .= '<span class="product_line_stock_too_low">';
4213
                    }
4214
                    $outval .= $virtualstock;
4215
                    $outval .= '</span>';
4216
4217
                    unset($tmpproduct);
4218
                }
4219
            }
4220
        }
4221
4222
        $parameters = array('objp' => $objp);
4223
        $reshook = $hookmanager->executeHooks('constructProductListOption', $parameters); // Note that $action and $object may have been modified by hook
4224
        if (empty($reshook)) {
4225
            $opt .= $hookmanager->resPrint;
4226
        } else {
4227
            $opt = $hookmanager->resPrint;
4228
        }
4229
4230
        $opt .= "</option>\n";
4231
        $optJson = array(
4232
            'key' => $outkey,
4233
            'value' => $outref,
4234
            'label' => $outval,
4235
            'label2' => $outlabel,
4236
            'desc' => $outdesc,
4237
            'type' => $outtype,
4238
            'price_ht' => price2num($outprice_ht),
4239
            'price_ttc' => price2num($outprice_ttc),
4240
            'price_ht_locale' => price(price2num($outprice_ht)),
4241
            'price_ttc_locale' => price(price2num($outprice_ttc)),
4242
            'pricebasetype' => $outpricebasetype,
4243
            'tva_tx' => $outtva_tx,
4244
            'default_vat_code' => $outdefault_vat_code,
4245
            'qty' => $outqty,
4246
            'discount' => $outdiscount,
4247
            'duration_value' => $outdurationvalue,
4248
            'duration_unit' => $outdurationunit,
4249
            'pbq' => $outpbq,
4250
            'labeltrans' => $outlabel_translated,
4251
            'desctrans' => $outdesc_translated,
4252
            'ref_customer' => $outrefcust
4253
        );
4254
    }
4255
4256
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4257
4258
    /**
4259
     *  Return list of BOM for customer in Ajax if Ajax activated or go to select_produits_list
4260
     *
4261
     * @param string $selected Preselected BOM id
4262
     * @param string $htmlname Name of HTML select field (must be unique in page).
4263
     * @param int $limit Limit on number of returned lines
4264
     * @param int $status Sell status -1=Return all bom, 0=Draft BOM, 1=Validated BOM
4265
     * @param int $type type of the BOM (-1=Return all BOM, 0=Return disassemble BOM, 1=Return manufacturing BOM)
4266
     * @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.
4267
     * @param string $morecss Add more css on select
4268
     * @param string $nooutput No print, return the output into a string
4269
     * @param int $forcecombo Force to use combo box
4270
     * @param string[] $TProducts Add filter on a defined product
4271
     * @return void|string
4272
     */
4273
    public function select_bom($selected = '', $htmlname = 'bom_id', $limit = 0, $status = 1, $type = 0, $showempty = '1', $morecss = '', $nooutput = '', $forcecombo = 0, $TProducts = [])
4274
    {
4275
        // phpcs:enable
4276
        global $db;
4277
4278
4279
        $error = 0;
4280
        $out = '';
4281
4282
        if (!$forcecombo) {
4283
            include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
4284
            $events = array();
4285
            $out .= ajax_combobox($htmlname, $events, getDolGlobalInt("PRODUIT_USE_SEARCH_TO_SELECT"));
4286
        }
4287
4288
        $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" id="' . $htmlname . '">';
4289
4290
        $sql = 'SELECT b.rowid, b.ref, b.label, b.fk_product';
4291
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'bom_bom as b';
4292
        $sql .= ' WHERE b.entity IN (' . getEntity('bom') . ')';
4293
        if (!empty($status)) {
4294
            $sql .= ' AND status = ' . (int)$status;
4295
        }
4296
        if (!empty($type)) {
4297
            $sql .= ' AND bomtype = ' . (int)$type;
4298
        }
4299
        if (!empty($TProducts)) {
4300
            $sql .= ' AND fk_product IN (' . $this->db->sanitize(implode(',', $TProducts)) . ')';
4301
        }
4302
        if (!empty($limit)) {
4303
            $sql .= ' LIMIT ' . (int)$limit;
4304
        }
4305
        $resql = $db->query($sql);
4306
        if ($resql) {
4307
            if ($showempty) {
4308
                $out .= '<option value="-1"';
4309
                if (empty($selected)) {
4310
                    $out .= ' selected';
4311
                }
4312
                $out .= '>&nbsp;</option>';
4313
            }
4314
            while ($obj = $db->fetch_object($resql)) {
4315
                $product = new Product($db);
4316
                $res = $product->fetch($obj->fk_product);
4317
                $out .= '<option value="' . $obj->rowid . '"';
4318
                if ($obj->rowid == $selected) {
4319
                    $out .= 'selected';
4320
                }
4321
                $out .= '>' . $obj->ref . ' - ' . $product->label . ' - ' . $obj->label . '</option>';
4322
            }
4323
        } else {
4324
            $error++;
4325
            dol_print_error($db);
4326
        }
4327
        $out .= '</select>';
4328
        if (empty($nooutput)) {
4329
            print $out;
4330
        } else {
4331
            return $out;
4332
        }
4333
    }
4334
4335
4336
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4337
4338
    /**
4339
     * Return list of products for customer (in Ajax if Ajax activated or go to select_produits_fournisseurs_list)
4340
     *
4341
     * @param int $socid Id third party
4342
     * @param string $selected Preselected product
4343
     * @param string $htmlname Name of HTML Select
4344
     * @param string $filtertype Filter on product type (''=nofilter, 0=product, 1=service)
4345
     * @param string $filtre For a SQL filter
4346
     * @param array<string,string|string[]> $ajaxoptions Options for ajax_autocompleter
4347
     * @param int<0,1> $hidelabel Hide label (0=no, 1=yes)
4348
     * @param int<0,1> $alsoproductwithnosupplierprice 1=Add also product without supplier prices
4349
     * @param string $morecss More CSS
4350
     * @param string $placeholder Placeholder
4351
     * @return    void
4352
     */
4353
    public function select_produits_fournisseurs($socid, $selected = '', $htmlname = 'productid', $filtertype = '', $filtre = '', $ajaxoptions = array(), $hidelabel = 0, $alsoproductwithnosupplierprice = 0, $morecss = '', $placeholder = '')
4354
    {
4355
        // phpcs:enable
4356
        global $langs, $conf;
4357
        global $price_level, $status, $finished;
4358
4359
        if (!isset($status)) {
4360
            $status = 1;
4361
        }
4362
4363
        $selected_input_value = '';
4364
        if (!empty($conf->use_javascript_ajax) && getDolGlobalString('PRODUIT_USE_SEARCH_TO_SELECT')) {
4365
            if ($selected > 0) {
4366
                $producttmpselect = new Product($this->db);
4367
                $producttmpselect->fetch($selected);
4368
                $selected_input_value = $producttmpselect->ref;
4369
                unset($producttmpselect);
4370
            }
4371
4372
            // mode=2 means suppliers products
4373
            $urloption = ($socid > 0 ? 'socid=' . $socid . '&' : '') . 'htmlname=' . $htmlname . '&outjson=1&price_level=' . $price_level . '&type=' . $filtertype . '&mode=2&status=' . $status . '&finished=' . $finished . '&alsoproductwithnosupplierprice=' . $alsoproductwithnosupplierprice;
4374
            print ajax_autocompleter($selected, $htmlname, constant('BASE_URL') . '/product/ajax/products.php', $urloption, getDolGlobalString('PRODUIT_USE_SEARCH_TO_SELECT'), 0, $ajaxoptions);
4375
4376
            print($hidelabel ? '' : $langs->trans("RefOrLabel") . ' : ') . '<input type="text" class="' . $morecss . '" name="search_' . $htmlname . '" id="search_' . $htmlname . '" value="' . $selected_input_value . '"' . ($placeholder ? ' placeholder="' . $placeholder . '"' : '') . '>';
4377
        } else {
4378
            print $this->select_produits_fournisseurs_list($socid, $selected, $htmlname, $filtertype, $filtre, '', $status, 0, 0, $alsoproductwithnosupplierprice, $morecss, 0, $placeholder);
4379
        }
4380
    }
4381
4382
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4383
4384
    /**
4385
     *    Return list of suppliers products
4386
     *
4387
     * @param int $socid Id of supplier thirdparty (0 = no filter)
4388
     * @param string $selected Product price preselected (must be 'id' in product_fournisseur_price or 'idprod_IDPROD')
4389
     * @param string $htmlname Name of HTML select
4390
     * @param string $filtertype Filter on product type (''=nofilter, 0=product, 1=service)
4391
     * @param string $filtre Generic filter. Data must not come from user input.
4392
     * @param string $filterkey Filter of produdts
4393
     * @param int $statut -1=Return all products, 0=Products not on buy, 1=Products on buy
4394
     * @param int $outputmode 0=HTML select string, 1=Array
4395
     * @param int $limit Limit of line number
4396
     * @param int $alsoproductwithnosupplierprice 1=Add also product without supplier prices
4397
     * @param string $morecss Add more CSS
4398
     * @param int $showstockinlist Show stock information (slower).
4399
     * @param string $placeholder Placeholder
4400
     * @return array|string                Array of keys for json or HTML component
4401
     */
4402
    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 = '')
4403
    {
4404
        // phpcs:enable
4405
        global $langs, $conf, $user;
4406
        global $hookmanager;
4407
4408
        $out = '';
4409
        $outarray = array();
4410
4411
        $maxlengtharticle = (!getDolGlobalString('PRODUCT_MAX_LENGTH_COMBO') ? 48 : $conf->global->PRODUCT_MAX_LENGTH_COMBO);
4412
4413
        $langs->load('stocks');
4414
        // Units
4415
        if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
4416
            $langs->load('other');
4417
        }
4418
4419
        $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,";
4420
        $sql .= " pfp.ref_fourn, pfp.rowid as idprodfournprice, pfp.price as fprice, pfp.quantity, pfp.remise_percent, pfp.remise, pfp.unitprice, pfp.barcode";
4421
        if (isModEnabled('multicurrency')) {
4422
            $sql .= ", pfp.multicurrency_code, pfp.multicurrency_unitprice";
4423
        }
4424
        $sql .= ", pfp.fk_supplier_price_expression, pfp.fk_product, pfp.tva_tx, pfp.default_vat_code, pfp.fk_soc, s.nom as name";
4425
        $sql .= ", pfp.supplier_reputation";
4426
        // if we use supplier description of the products
4427
        if (getDolGlobalString('PRODUIT_FOURN_TEXTS')) {
4428
            $sql .= ", pfp.desc_fourn as description";
4429
        } else {
4430
            $sql .= ", p.description";
4431
        }
4432
        // Units
4433
        if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
4434
            $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";
4435
        }
4436
        $sql .= " FROM " . $this->db->prefix() . "product as p";
4437
        $sql .= " LEFT JOIN " . $this->db->prefix() . "product_fournisseur_price as pfp ON ( p.rowid = pfp.fk_product AND pfp.entity IN (" . getEntity('product') . ") )";
4438
        if ($socid > 0) {
4439
            $sql .= " AND pfp.fk_soc = " . ((int)$socid);
4440
        }
4441
        $sql .= " LEFT JOIN " . $this->db->prefix() . "societe as s ON pfp.fk_soc = s.rowid";
4442
        // Units
4443
        if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
4444
            $sql .= " LEFT JOIN " . $this->db->prefix() . "c_units u ON u.rowid = p.fk_unit";
4445
        }
4446
        $sql .= " WHERE p.entity IN (" . getEntity('product') . ")";
4447
        if ($statut != -1) {
4448
            $sql .= " AND p.tobuy = " . ((int)$statut);
4449
        }
4450
        if (strval($filtertype) != '') {
4451
            $sql .= " AND p.fk_product_type = " . ((int)$filtertype);
4452
        }
4453
        if (!empty($filtre)) {
4454
            $sql .= " " . $filtre;
4455
        }
4456
        // Add where from hooks
4457
        $parameters = array();
4458
        $reshook = $hookmanager->executeHooks('selectSuppliersProductsListWhere', $parameters); // Note that $action and $object may have been modified by hook
4459
        $sql .= $hookmanager->resPrint;
4460
        // Add criteria on ref/label
4461
        if ($filterkey != '') {
4462
            $sql .= ' AND (';
4463
            $prefix = !getDolGlobalString('PRODUCT_DONOTSEARCH_ANYWHERE') ? '%' : ''; // Can use index if PRODUCT_DONOTSEARCH_ANYWHERE is on
4464
            // For natural search
4465
            $search_crit = explode(' ', $filterkey);
4466
            $i = 0;
4467
            if (count($search_crit) > 1) {
4468
                $sql .= "(";
4469
            }
4470
            foreach ($search_crit as $crit) {
4471
                if ($i > 0) {
4472
                    $sql .= " AND ";
4473
                }
4474
                $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) . "%'";
4475
                if (getDolGlobalString('PRODUIT_FOURN_TEXTS')) {
4476
                    $sql .= " OR pfp.desc_fourn LIKE '" . $this->db->escape($prefix . $crit) . "%'";
4477
                }
4478
                $sql .= ")";
4479
                $i++;
4480
            }
4481
            if (count($search_crit) > 1) {
4482
                $sql .= ")";
4483
            }
4484
            if (isModEnabled('barcode')) {
4485
                $sql .= " OR p.barcode LIKE '" . $this->db->escape($prefix . $filterkey) . "%'";
4486
                $sql .= " OR pfp.barcode LIKE '" . $this->db->escape($prefix . $filterkey) . "%'";
4487
            }
4488
            $sql .= ')';
4489
        }
4490
        $sql .= " ORDER BY pfp.ref_fourn DESC, pfp.quantity ASC";
4491
        $sql .= $this->db->plimit($limit, 0);
4492
4493
        // Build output string
4494
4495
        dol_syslog(get_only_class($this) . "::select_produits_fournisseurs_list", LOG_DEBUG);
4496
        $result = $this->db->query($sql);
4497
        if ($result) {
4498
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/product.lib.php';
4499
4500
            $num = $this->db->num_rows($result);
4501
4502
            //$out.='<select class="flat" id="select'.$htmlname.'" name="'.$htmlname.'">';  // remove select to have id same with combo and ajax
4503
            $out .= '<select class="flat ' . ($morecss ? ' ' . $morecss : '') . '" id="' . $htmlname . '" name="' . $htmlname . '">';
4504
            if (!$selected) {
4505
                $out .= '<option value="-1" selected>' . ($placeholder ? $placeholder : '&nbsp;') . '</option>';
4506
            } else {
4507
                $out .= '<option value="-1">' . ($placeholder ? $placeholder : '&nbsp;') . '</option>';
4508
            }
4509
4510
            $i = 0;
4511
            while ($i < $num) {
4512
                $objp = $this->db->fetch_object($result);
4513
4514
                if (is_null($objp->idprodfournprice)) {
4515
                    // There is no supplier price found, we will use the vat rate for sale
4516
                    $objp->tva_tx = $objp->tva_tx_sale;
4517
                    $objp->default_vat_code = $objp->default_vat_code_sale;
4518
                }
4519
4520
                $outkey = $objp->idprodfournprice; // id in table of price
4521
                if (!$outkey && $alsoproductwithnosupplierprice) {
4522
                    $outkey = 'idprod_' . $objp->rowid; // id of product
4523
                }
4524
4525
                $outref = $objp->ref;
4526
                $outbarcode = $objp->barcode;
4527
                $outqty = 1;
4528
                $outdiscount = 0;
4529
                $outtype = $objp->fk_product_type;
4530
                $outdurationvalue = $outtype == Product::TYPE_SERVICE ? substr($objp->duration, 0, dol_strlen($objp->duration) - 1) : '';
4531
                $outdurationunit = $outtype == Product::TYPE_SERVICE ? substr($objp->duration, -1) : '';
4532
4533
                // Units
4534
                $outvalUnits = '';
4535
                if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
4536
                    if (!empty($objp->unit_short)) {
4537
                        $outvalUnits .= ' - ' . $objp->unit_short;
4538
                    }
4539
                    if (!empty($objp->weight) && $objp->weight_units !== null) {
4540
                        $unitToShow = showDimensionInBestUnit($objp->weight, $objp->weight_units, 'weight', $langs);
4541
                        $outvalUnits .= ' - ' . $unitToShow;
4542
                    }
4543
                    if ((!empty($objp->length) || !empty($objp->width) || !empty($objp->height)) && $objp->length_units !== null) {
4544
                        $unitToShow = $objp->length . ' x ' . $objp->width . ' x ' . $objp->height . ' ' . measuringUnitString(0, 'size', $objp->length_units);
4545
                        $outvalUnits .= ' - ' . $unitToShow;
4546
                    }
4547
                    if (!empty($objp->surface) && $objp->surface_units !== null) {
4548
                        $unitToShow = showDimensionInBestUnit($objp->surface, $objp->surface_units, 'surface', $langs);
4549
                        $outvalUnits .= ' - ' . $unitToShow;
4550
                    }
4551
                    if (!empty($objp->volume) && $objp->volume_units !== null) {
4552
                        $unitToShow = showDimensionInBestUnit($objp->volume, $objp->volume_units, 'volume', $langs);
4553
                        $outvalUnits .= ' - ' . $unitToShow;
4554
                    }
4555
                    if ($outdurationvalue && $outdurationunit) {
4556
                        $da = array(
4557
                            'h' => $langs->trans('Hour'),
4558
                            'd' => $langs->trans('Day'),
4559
                            'w' => $langs->trans('Week'),
4560
                            'm' => $langs->trans('Month'),
4561
                            'y' => $langs->trans('Year')
4562
                        );
4563
                        if (isset($da[$outdurationunit])) {
4564
                            $outvalUnits .= ' - ' . $outdurationvalue . ' ' . $langs->transnoentities($da[$outdurationunit] . ($outdurationvalue > 1 ? 's' : ''));
4565
                        }
4566
                    }
4567
                }
4568
4569
                $objRef = $objp->ref;
4570
                if ($filterkey && $filterkey != '') {
4571
                    $objRef = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', $objRef, 1);
4572
                }
4573
                $objRefFourn = $objp->ref_fourn;
4574
                if ($filterkey && $filterkey != '') {
4575
                    $objRefFourn = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', $objRefFourn, 1);
4576
                }
4577
                $label = $objp->label;
4578
                if ($filterkey && $filterkey != '') {
4579
                    $label = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', $label, 1);
4580
                }
4581
4582
                switch ($objp->fk_product_type) {
4583
                    case Product::TYPE_PRODUCT:
4584
                        $picto = 'product';
4585
                        break;
4586
                    case Product::TYPE_SERVICE:
4587
                        $picto = 'service';
4588
                        break;
4589
                    default:
4590
                        $picto = '';
4591
                        break;
4592
                }
4593
4594
                if (empty($picto)) {
4595
                    $optlabel = '';
4596
                } else {
4597
                    $optlabel = img_object('', $picto, 'class="paddingright classfortooltip"', 0, 0, 1);
4598
                }
4599
4600
                $optlabel .= $objp->ref;
4601
                if (!empty($objp->idprodfournprice) && ($objp->ref != $objp->ref_fourn)) {
4602
                    $optlabel .= ' <span class="opacitymedium">(' . $objp->ref_fourn . ')</span>';
4603
                }
4604
                if (isModEnabled('barcode') && !empty($objp->barcode)) {
4605
                    $optlabel .= ' (' . $outbarcode . ')';
4606
                }
4607
                $optlabel .= ' - ' . dol_trunc($label, $maxlengtharticle);
4608
4609
                $outvallabel = $objRef;
4610
                if (!empty($objp->idprodfournprice) && ($objp->ref != $objp->ref_fourn)) {
4611
                    $outvallabel .= ' (' . $objRefFourn . ')';
4612
                }
4613
                if (isModEnabled('barcode') && !empty($objp->barcode)) {
4614
                    $outvallabel .= ' (' . $outbarcode . ')';
4615
                }
4616
                $outvallabel .= ' - ' . dol_trunc($label, $maxlengtharticle);
4617
4618
                // Units
4619
                $optlabel .= $outvalUnits;
4620
                $outvallabel .= $outvalUnits;
4621
4622
                if (!empty($objp->idprodfournprice)) {
4623
                    $outqty = $objp->quantity;
4624
                    $outdiscount = $objp->remise_percent;
4625
                    if (isModEnabled('dynamicprices') && !empty($objp->fk_supplier_price_expression)) {
4626
                        $prod_supplier = new ProductFournisseur($this->db);
4627
                        $prod_supplier->product_fourn_price_id = $objp->idprodfournprice;
4628
                        $prod_supplier->id = $objp->fk_product;
4629
                        $prod_supplier->fourn_qty = $objp->quantity;
4630
                        $prod_supplier->fourn_tva_tx = $objp->tva_tx;
4631
                        $prod_supplier->fk_supplier_price_expression = $objp->fk_supplier_price_expression;
4632
4633
                        $priceparser = new PriceParser($this->db);
4634
                        $price_result = $priceparser->parseProductSupplier($prod_supplier);
4635
                        if ($price_result >= 0) {
4636
                            $objp->fprice = $price_result;
4637
                            if ($objp->quantity >= 1) {
4638
                                $objp->unitprice = $objp->fprice / $objp->quantity; // Replace dynamically unitprice
4639
                            }
4640
                        }
4641
                    }
4642
                    if ($objp->quantity == 1) {
4643
                        $optlabel .= ' - ' . price($objp->fprice * (getDolGlobalString('DISPLAY_DISCOUNTED_SUPPLIER_PRICE') ? (1 - $objp->remise_percent / 100) : 1), 1, $langs, 0, 0, -1, $conf->currency) . "/";
4644
                        $outvallabel .= ' - ' . price($objp->fprice * (getDolGlobalString('DISPLAY_DISCOUNTED_SUPPLIER_PRICE') ? (1 - $objp->remise_percent / 100) : 1), 0, $langs, 0, 0, -1, $conf->currency) . "/";
4645
                        $optlabel .= $langs->trans("Unit"); // Do not use strtolower because it breaks utf8 encoding
4646
                        $outvallabel .= $langs->transnoentities("Unit");
4647
                    } else {
4648
                        $optlabel .= ' - ' . price($objp->fprice * (getDolGlobalString('DISPLAY_DISCOUNTED_SUPPLIER_PRICE') ? (1 - $objp->remise_percent / 100) : 1), 1, $langs, 0, 0, -1, $conf->currency) . "/" . $objp->quantity;
4649
                        $outvallabel .= ' - ' . price($objp->fprice * (getDolGlobalString('DISPLAY_DISCOUNTED_SUPPLIER_PRICE') ? (1 - $objp->remise_percent / 100) : 1), 0, $langs, 0, 0, -1, $conf->currency) . "/" . $objp->quantity;
4650
                        $optlabel .= ' ' . $langs->trans("Units"); // Do not use strtolower because it breaks utf8 encoding
4651
                        $outvallabel .= ' ' . $langs->transnoentities("Units");
4652
                    }
4653
4654
                    if ($objp->quantity > 1) {
4655
                        $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
4656
                        $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
4657
                    }
4658
                    if ($objp->remise_percent >= 1) {
4659
                        $optlabel .= " - " . $langs->trans("Discount") . " : " . vatrate($objp->remise_percent) . ' %';
4660
                        $outvallabel .= " - " . $langs->transnoentities("Discount") . " : " . vatrate($objp->remise_percent) . ' %';
4661
                    }
4662
                    if ($objp->duration) {
4663
                        $optlabel .= " - " . $objp->duration;
4664
                        $outvallabel .= " - " . $objp->duration;
4665
                    }
4666
                    if (!$socid) {
4667
                        $optlabel .= " - " . dol_trunc($objp->name, 8);
4668
                        $outvallabel .= " - " . dol_trunc($objp->name, 8);
4669
                    }
4670
                    if ($objp->supplier_reputation) {
4671
                        //TODO dictionary
4672
                        $reputations = array('' => $langs->trans('Standard'), 'FAVORITE' => $langs->trans('Favorite'), 'NOTTHGOOD' => $langs->trans('NotTheGoodQualitySupplier'), 'DONOTORDER' => $langs->trans('DoNotOrderThisProductToThisSupplier'));
4673
4674
                        $optlabel .= " - " . $reputations[$objp->supplier_reputation];
4675
                        $outvallabel .= " - " . $reputations[$objp->supplier_reputation];
4676
                    }
4677
                } else {
4678
                    $optlabel .= " - <span class='opacitymedium'>" . $langs->trans("NoPriceDefinedForThisSupplier") . '</span>';
4679
                    $outvallabel .= ' - ' . $langs->transnoentities("NoPriceDefinedForThisSupplier");
4680
                }
4681
4682
                if (isModEnabled('stock') && $showstockinlist && isset($objp->stock) && ($objp->fk_product_type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) {
4683
                    $novirtualstock = ($showstockinlist == 2);
4684
4685
                    if ($user->hasRight('stock', 'lire')) {
4686
                        $outvallabel .= ' - ' . $langs->trans("Stock") . ': ' . price(price2num($objp->stock, 'MS'));
4687
4688
                        if ($objp->stock > 0) {
4689
                            $optlabel .= ' - <span class="product_line_stock_ok">';
4690
                        } elseif ($objp->stock <= 0) {
4691
                            $optlabel .= ' - <span class="product_line_stock_too_low">';
4692
                        }
4693
                        $optlabel .= $langs->transnoentities("Stock") . ':' . price(price2num($objp->stock, 'MS'));
4694
                        $optlabel .= '</span>';
4695
                        if (empty($novirtualstock) && getDolGlobalString('STOCK_SHOW_VIRTUAL_STOCK_IN_PRODUCTS_COMBO')) {  // Warning, this option may slow down combo list generation
4696
                            $langs->load("stocks");
4697
4698
                            $tmpproduct = new Product($this->db);
4699
                            $tmpproduct->fetch($objp->rowid, '', '', '', 1, 1, 1); // Load product without lang and prices arrays (we just need to make ->virtual_stock() after)
4700
                            $tmpproduct->load_virtual_stock();
4701
                            $virtualstock = $tmpproduct->stock_theorique;
4702
4703
                            $outvallabel .= ' - ' . $langs->trans("VirtualStock") . ':' . $virtualstock;
4704
4705
                            $optlabel .= ' - ' . $langs->transnoentities("VirtualStock") . ':';
4706
                            if ($virtualstock > 0) {
4707
                                $optlabel .= '<span class="product_line_stock_ok">';
4708
                            } elseif ($virtualstock <= 0) {
4709
                                $optlabel .= '<span class="product_line_stock_too_low">';
4710
                            }
4711
                            $optlabel .= $virtualstock;
4712
                            $optlabel .= '</span>';
4713
4714
                            unset($tmpproduct);
4715
                        }
4716
                    }
4717
                }
4718
4719
                $optstart = '<option value="' . $outkey . '"';
4720
                if ($selected && $selected == $objp->idprodfournprice) {
4721
                    $optstart .= ' selected';
4722
                }
4723
                if (empty($objp->idprodfournprice) && empty($alsoproductwithnosupplierprice)) {
4724
                    $optstart .= ' disabled';
4725
                }
4726
4727
                if (!empty($objp->idprodfournprice) && $objp->idprodfournprice > 0) {
4728
                    $optstart .= ' data-product-id="' . dol_escape_htmltag($objp->rowid) . '"';
4729
                    $optstart .= ' data-price-id="' . dol_escape_htmltag($objp->idprodfournprice) . '"';
4730
                    $optstart .= ' data-qty="' . dol_escape_htmltag($objp->quantity) . '"';
4731
                    $optstart .= ' data-up="' . dol_escape_htmltag(price2num($objp->unitprice)) . '"';
4732
                    $optstart .= ' data-up-locale="' . dol_escape_htmltag(price($objp->unitprice)) . '"';
4733
                    $optstart .= ' data-discount="' . dol_escape_htmltag($outdiscount) . '"';
4734
                    $optstart .= ' data-tvatx="' . dol_escape_htmltag(price2num($objp->tva_tx)) . '"';
4735
                    $optstart .= ' data-tvatx-formated="' . dol_escape_htmltag(price($objp->tva_tx, 0, $langs, 1, -1, 2)) . '"';
4736
                    $optstart .= ' data-default-vat-code="' . dol_escape_htmltag($objp->default_vat_code) . '"';
4737
                    $optstart .= ' data-supplier-ref="' . dol_escape_htmltag($objp->ref_fourn) . '"';
4738
                    if (isModEnabled('multicurrency')) {
4739
                        $optstart .= ' data-multicurrency-code="' . dol_escape_htmltag($objp->multicurrency_code) . '"';
4740
                        $optstart .= ' data-multicurrency-up="' . dol_escape_htmltag($objp->multicurrency_unitprice) . '"';
4741
                    }
4742
                }
4743
                $optstart .= ' data-description="' . dol_escape_htmltag($objp->description, 0, 1) . '"';
4744
4745
                $outarrayentry = array(
4746
                    'key' => $outkey,
4747
                    'value' => $outref,
4748
                    'label' => $outvallabel,
4749
                    'qty' => $outqty,
4750
                    'price_qty_ht' => price2num($objp->fprice, 'MU'),    // Keep higher resolution for price for the min qty
4751
                    'price_unit_ht' => price2num($objp->unitprice, 'MU'),    // This is used to fill the Unit Price
4752
                    'price_ht' => price2num($objp->unitprice, 'MU'),        // This is used to fill the Unit Price (for compatibility)
4753
                    'tva_tx_formated' => price($objp->tva_tx, 0, $langs, 1, -1, 2),
4754
                    'tva_tx' => price2num($objp->tva_tx),
4755
                    'default_vat_code' => $objp->default_vat_code,
4756
                    'supplier_ref' => $objp->ref_fourn,
4757
                    'discount' => $outdiscount,
4758
                    'type' => $outtype,
4759
                    'duration_value' => $outdurationvalue,
4760
                    'duration_unit' => $outdurationunit,
4761
                    'disabled' => empty($objp->idprodfournprice),
4762
                    'description' => $objp->description
4763
                );
4764
                if (isModEnabled('multicurrency')) {
4765
                    $outarrayentry['multicurrency_code'] = $objp->multicurrency_code;
4766
                    $outarrayentry['multicurrency_unitprice'] = price2num($objp->multicurrency_unitprice, 'MU');
4767
                }
4768
4769
                $parameters = array(
4770
                    'objp' => &$objp,
4771
                    'optstart' => &$optstart,
4772
                    'optlabel' => &$optlabel,
4773
                    'outvallabel' => &$outvallabel,
4774
                    'outarrayentry' => &$outarrayentry
4775
                );
4776
                $reshook = $hookmanager->executeHooks('selectProduitsFournisseurListOption', $parameters, $this);
4777
4778
4779
                // Add new entry
4780
                // "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
4781
                // "label" value of json key array is used by jQuery automatically as text for combo box
4782
                $out .= $optstart . ' data-html="' . dol_escape_htmltag($optlabel) . '">' . $optlabel . "</option>\n";
4783
                $outarraypush = array(
4784
                    'key' => $outkey,
4785
                    'value' => $outref,
4786
                    'label' => $outvallabel,
4787
                    'qty' => $outqty,
4788
                    'price_qty_ht' => price2num($objp->fprice, 'MU'),        // Keep higher resolution for price for the min qty
4789
                    'price_qty_ht_locale' => price($objp->fprice),
4790
                    'price_unit_ht' => price2num($objp->unitprice, 'MU'),    // This is used to fill the Unit Price
4791
                    'price_unit_ht_locale' => price($objp->unitprice),
4792
                    'price_ht' => price2num($objp->unitprice, 'MU'),        // This is used to fill the Unit Price (for compatibility)
4793
                    'tva_tx_formated' => price($objp->tva_tx),
4794
                    'tva_tx' => price2num($objp->tva_tx),
4795
                    'default_vat_code' => $objp->default_vat_code,
4796
                    'supplier_ref' => $objp->ref_fourn,
4797
                    'discount' => $outdiscount,
4798
                    'type' => $outtype,
4799
                    'duration_value' => $outdurationvalue,
4800
                    'duration_unit' => $outdurationunit,
4801
                    'disabled' => empty($objp->idprodfournprice),
4802
                    'description' => $objp->description
4803
                );
4804
                if (isModEnabled('multicurrency')) {
4805
                    $outarraypush['multicurrency_code'] = $objp->multicurrency_code;
4806
                    $outarraypush['multicurrency_unitprice'] = price2num($objp->multicurrency_unitprice, 'MU');
4807
                }
4808
                array_push($outarray, $outarraypush);
4809
4810
                // Example of var_dump $outarray
4811
                // array(1) {[0]=>array(6) {[key"]=>string(1) "2" ["value"]=>string(3) "ppp"
4812
                //           ["label"]=>string(76) "ppp (<strong>f</strong>ff2) - ppp - 20,00 Euros/1unité (20,00 Euros/unité)"
4813
                //           ["qty"]=>string(1) "1" ["discount"]=>string(1) "0" ["disabled"]=>bool(false)
4814
                //}
4815
                //var_dump($outval); var_dump(utf8_check($outval)); var_dump(json_encode($outval));
4816
                //$outval=array('label'=>'ppp (<strong>f</strong>ff2) - ppp - 20,00 Euros/ Unité (20,00 Euros/unité)');
4817
                //var_dump($outval); var_dump(utf8_check($outval)); var_dump(json_encode($outval));
4818
4819
                $i++;
4820
            }
4821
            $out .= '</select>';
4822
4823
            $this->db->free($result);
4824
4825
            include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
4826
            $out .= ajax_combobox($htmlname);
4827
        } else {
4828
            dol_print_error($this->db);
4829
        }
4830
4831
        if (empty($outputmode)) {
4832
            return $out;
4833
        }
4834
        return $outarray;
4835
    }
4836
4837
    /**
4838
     *    Return list of suppliers prices for a product
4839
     *
4840
     * @param int $productid Id of product
4841
     * @param string $htmlname Name of HTML field
4842
     * @param int $selected_supplier Pre-selected supplier if more than 1 result
4843
     * @return        string
4844
     */
4845
    public function select_product_fourn_price($productid, $htmlname = 'productfournpriceid', $selected_supplier = 0)
4846
    {
4847
        // phpcs:enable
4848
        global $langs, $conf;
4849
4850
        $langs->load('stocks');
4851
4852
        $sql = "SELECT p.rowid, p.ref, p.label, p.price, p.duration, pfp.fk_soc,";
4853
        $sql .= " pfp.ref_fourn, pfp.rowid as idprodfournprice, pfp.price as fprice, pfp.remise_percent, pfp.quantity, pfp.unitprice,";
4854
        $sql .= " pfp.fk_supplier_price_expression, pfp.fk_product, pfp.tva_tx, s.nom as name";
4855
        $sql .= " FROM " . $this->db->prefix() . "product as p";
4856
        $sql .= " LEFT JOIN " . $this->db->prefix() . "product_fournisseur_price as pfp ON p.rowid = pfp.fk_product";
4857
        $sql .= " LEFT JOIN " . $this->db->prefix() . "societe as s ON pfp.fk_soc = s.rowid";
4858
        $sql .= " WHERE pfp.entity IN (" . getEntity('productsupplierprice') . ")";
4859
        $sql .= " AND p.tobuy = 1";
4860
        $sql .= " AND s.fournisseur = 1";
4861
        $sql .= " AND p.rowid = " . ((int)$productid);
4862
        if (!getDolGlobalString('PRODUCT_BEST_SUPPLIER_PRICE_PRESELECTED')) {
4863
            $sql .= " ORDER BY s.nom, pfp.ref_fourn DESC";
4864
        } else {
4865
            $sql .= " ORDER BY pfp.unitprice ASC";
4866
        }
4867
4868
        dol_syslog(get_only_class($this) . "::select_product_fourn_price", LOG_DEBUG);
4869
        $result = $this->db->query($sql);
4870
4871
        if ($result) {
4872
            $num = $this->db->num_rows($result);
4873
4874
            $form = '<select class="flat" id="select_' . $htmlname . '" name="' . $htmlname . '">';
4875
4876
            if (!$num) {
4877
                $form .= '<option value="0">-- ' . $langs->trans("NoSupplierPriceDefinedForThisProduct") . ' --</option>';
4878
            } else {
4879
                $form .= '<option value="0">&nbsp;</option>';
4880
4881
                $i = 0;
4882
                while ($i < $num) {
4883
                    $objp = $this->db->fetch_object($result);
4884
4885
                    $opt = '<option value="' . $objp->idprodfournprice . '"';
4886
                    //if there is only one supplier, preselect it
4887
                    if ($num == 1 || ($selected_supplier > 0 && $objp->fk_soc == $selected_supplier) || ($i == 0 && getDolGlobalString('PRODUCT_BEST_SUPPLIER_PRICE_PRESELECTED'))) {
4888
                        $opt .= ' selected';
4889
                    }
4890
                    $opt .= '>' . $objp->name . ' - ' . $objp->ref_fourn . ' - ';
4891
4892
                    if (isModEnabled('dynamicprices') && !empty($objp->fk_supplier_price_expression)) {
4893
                        $prod_supplier = new ProductFournisseur($this->db);
4894
                        $prod_supplier->product_fourn_price_id = $objp->idprodfournprice;
4895
                        $prod_supplier->id = $productid;
4896
                        $prod_supplier->fourn_qty = $objp->quantity;
4897
                        $prod_supplier->fourn_tva_tx = $objp->tva_tx;
4898
                        $prod_supplier->fk_supplier_price_expression = $objp->fk_supplier_price_expression;
4899
4900
                        $priceparser = new PriceParser($this->db);
4901
                        $price_result = $priceparser->parseProductSupplier($prod_supplier);
4902
                        if ($price_result >= 0) {
4903
                            $objp->fprice = $price_result;
4904
                            if ($objp->quantity >= 1) {
4905
                                $objp->unitprice = $objp->fprice / $objp->quantity;
4906
                            }
4907
                        }
4908
                    }
4909
                    if ($objp->quantity == 1) {
4910
                        $opt .= price($objp->fprice * (getDolGlobalString('DISPLAY_DISCOUNTED_SUPPLIER_PRICE') ? (1 - $objp->remise_percent / 100) : 1), 1, $langs, 0, 0, -1, $conf->currency) . "/";
4911
                    }
4912
4913
                    $opt .= $objp->quantity . ' ';
4914
4915
                    if ($objp->quantity == 1) {
4916
                        $opt .= $langs->trans("Unit");
4917
                    } else {
4918
                        $opt .= $langs->trans("Units");
4919
                    }
4920
                    if ($objp->quantity > 1) {
4921
                        $opt .= " - ";
4922
                        $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");
4923
                    }
4924
                    if ($objp->duration) {
4925
                        $opt .= " - " . $objp->duration;
4926
                    }
4927
                    $opt .= "</option>\n";
4928
4929
                    $form .= $opt;
4930
                    $i++;
4931
                }
4932
            }
4933
4934
            $form .= '</select>';
4935
            $this->db->free($result);
4936
            return $form;
4937
        } else {
4938
            dol_print_error($this->db);
4939
            return '';
4940
        }
4941
    }
4942
4943
    /**
4944
     *    print list of payment modes.
4945
     *    Constant MAIN_DEFAULT_PAYMENT_TERM_ID can be used to set default value but scope is all application, probably not what you want.
4946
     *    See instead to force the default value by the caller.
4947
     *
4948
     * @param int $selected Id of payment term to preselect by default
4949
     * @param string $htmlname Nom de la zone select
4950
     * @param int $filtertype If > 0, include payment terms with deposit percentage (for objects other than invoices and invoice templates)
4951
     * @param int $addempty Add an empty entry
4952
     * @param int $noinfoadmin 0=Add admin info, 1=Disable admin info
4953
     * @param string $morecss Add more CSS on select tag
4954
     * @param int $deposit_percent < 0 : deposit_percent input makes no sense (for example, in list filters)
4955
     *                                0 : use default deposit percentage from entry
4956
     *                                > 0 : force deposit percentage (for example, from company object)
4957
     * @param int $noprint if set to one we return the html to print, if 0 (default) we print it
4958
     * @return    void|string
4959
     * @deprecated Use getSelectConditionsPaiements() instead and handle noprint locally.
4960
     */
4961
    public function select_conditions_paiements($selected = 0, $htmlname = 'condid', $filtertype = -1, $addempty = 0, $noinfoadmin = 0, $morecss = '', $deposit_percent = -1, $noprint = 0)
4962
    {
4963
        // phpcs:enable
4964
        $out = $this->getSelectConditionsPaiements($selected, $htmlname, $filtertype, $addempty, $noinfoadmin, $morecss, $deposit_percent);
4965
        if (empty($noprint)) {
4966
            print $out;
4967
        } else {
4968
            return $out;
4969
        }
4970
    }
4971
4972
    /**
4973
     *    Return list of payment modes.
4974
     *    Constant MAIN_DEFAULT_PAYMENT_TERM_ID can be used to set default value but scope is all application, probably not what you want.
4975
     *    See instead to force the default value by the caller.
4976
     *
4977
     * @param int $selected Id of payment term to preselect by default
4978
     * @param string $htmlname Nom de la zone select
4979
     * @param int $filtertype If > 0, include payment terms with deposit percentage (for objects other than invoices and invoice templates)
4980
     * @param int $addempty Add an empty entry
4981
     * @param int $noinfoadmin 0=Add admin info, 1=Disable admin info
4982
     * @param string $morecss Add more CSS on select tag
4983
     * @param int $deposit_percent < 0 : deposit_percent input makes no sense (for example, in list filters)
4984
     *                                0 : use default deposit percentage from entry
4985
     *                                > 0 : force deposit percentage (for example, from company object)
4986
     * @return    string                        String for the HTML select component
4987
     */
4988
    public function getSelectConditionsPaiements($selected = 0, $htmlname = 'condid', $filtertype = -1, $addempty = 0, $noinfoadmin = 0, $morecss = '', $deposit_percent = -1)
4989
    {
4990
        global $langs, $user, $conf;
4991
4992
        $out = '';
4993
        dol_syslog(__METHOD__ . " selected=" . $selected . ", htmlname=" . $htmlname, LOG_DEBUG);
4994
4995
        $this->load_cache_conditions_paiements();
4996
4997
        // Set default value if not already set by caller
4998
        if (empty($selected) && getDolGlobalString('MAIN_DEFAULT_PAYMENT_TERM_ID')) {
4999
            dol_syslog(__METHOD__ . "Using deprecated option MAIN_DEFAULT_PAYMENT_TERM_ID", LOG_NOTICE);
5000
            $selected = getDolGlobalString('MAIN_DEFAULT_PAYMENT_TERM_ID');
5001
        }
5002
5003
        $out .= '<select id="' . $htmlname . '" class="flat selectpaymentterms' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '">';
5004
        if ($addempty) {
5005
            $out .= '<option value="0">&nbsp;</option>';
5006
        }
5007
5008
        $selectedDepositPercent = null;
5009
5010
        foreach ($this->cache_conditions_paiements as $id => $arrayconditions) {
5011
            if ($filtertype <= 0 && !empty($arrayconditions['deposit_percent'])) {
5012
                continue;
5013
            }
5014
5015
            if ($selected == $id) {
5016
                $selectedDepositPercent = $deposit_percent > 0 ? $deposit_percent : $arrayconditions['deposit_percent'];
5017
                $out .= '<option value="' . $id . '" data-deposit_percent="' . $arrayconditions['deposit_percent'] . '" selected>';
5018
            } else {
5019
                $out .= '<option value="' . $id . '" data-deposit_percent="' . $arrayconditions['deposit_percent'] . '">';
5020
            }
5021
            $label = $arrayconditions['label'];
5022
5023
            if (!empty($arrayconditions['deposit_percent'])) {
5024
                $label = str_replace('__DEPOSIT_PERCENT__', $deposit_percent > 0 ? $deposit_percent : $arrayconditions['deposit_percent'], $label);
5025
            }
5026
5027
            $out .= $label;
5028
            $out .= '</option>';
5029
        }
5030
        $out .= '</select>';
5031
        if ($user->admin && empty($noinfoadmin)) {
5032
            $out .= info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
5033
        }
5034
        $out .= ajax_combobox($htmlname);
5035
5036
        if ($deposit_percent >= 0) {
5037
            $out .= ' <span id="' . $htmlname . '_deposit_percent_container"' . (empty($selectedDepositPercent) ? ' style="display: none"' : '') . '>';
5038
            $out .= $langs->trans('DepositPercent') . ' : ';
5039
            $out .= '<input id="' . $htmlname . '_deposit_percent" name="' . $htmlname . '_deposit_percent" class="maxwidth50" value="' . $deposit_percent . '" />';
5040
            $out .= '</span>';
5041
            $out .= '
5042
				<script nonce="' . getNonce() . '">
5043
					$(document).ready(function () {
5044
						$("#' . $htmlname . '").change(function () {
5045
							let $selected = $(this).find("option:selected");
5046
							let depositPercent = $selected.attr("data-deposit_percent");
5047
5048
							if (depositPercent.length > 0) {
5049
								$("#' . $htmlname . '_deposit_percent_container").show().find("#' . $htmlname . '_deposit_percent").val(depositPercent);
5050
							} else {
5051
								$("#' . $htmlname . '_deposit_percent_container").hide();
5052
							}
5053
5054
							return true;
5055
						});
5056
					});
5057
				</script>';
5058
        }
5059
5060
        return $out;
5061
    }
5062
5063
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5064
5065
    /**
5066
     *      Load into cache list of payment terms
5067
     *
5068
     * @return     int             Nb of lines loaded, <0 if KO
5069
     */
5070
    public function load_cache_conditions_paiements()
5071
    {
5072
        // phpcs:enable
5073
        global $langs;
5074
5075
        $num = count($this->cache_conditions_paiements);
5076
        if ($num > 0) {
5077
            return 0; // Cache already loaded
5078
        }
5079
5080
        dol_syslog(__METHOD__, LOG_DEBUG);
5081
5082
        $sql = "SELECT rowid, code, libelle as label, deposit_percent";
5083
        $sql .= " FROM " . $this->db->prefix() . 'c_payment_term';
5084
        $sql .= " WHERE entity IN (" . getEntity('c_payment_term') . ")";
5085
        $sql .= " AND active > 0";
5086
        $sql .= " ORDER BY sortorder";
5087
5088
        $resql = $this->db->query($sql);
5089
        if ($resql) {
5090
            $num = $this->db->num_rows($resql);
5091
            $i = 0;
5092
            while ($i < $num) {
5093
                $obj = $this->db->fetch_object($resql);
5094
5095
                // Si traduction existe, on l'utilise, sinon on prend le libelle par default
5096
                $label = ($langs->trans("PaymentConditionShort" . $obj->code) != "PaymentConditionShort" . $obj->code ? $langs->trans("PaymentConditionShort" . $obj->code) : ($obj->label != '-' ? $obj->label : ''));
5097
                $this->cache_conditions_paiements[$obj->rowid]['code'] = $obj->code;
5098
                $this->cache_conditions_paiements[$obj->rowid]['label'] = $label;
5099
                $this->cache_conditions_paiements[$obj->rowid]['deposit_percent'] = $obj->deposit_percent;
5100
                $i++;
5101
            }
5102
5103
            //$this->cache_conditions_paiements=dol_sort_array($this->cache_conditions_paiements, 'label', 'asc', 0, 0, 1);     // We use the field sortorder of table
5104
5105
            return $num;
5106
        } else {
5107
            dol_print_error($this->db);
5108
            return -1;
5109
        }
5110
    }
5111
5112
5113
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5114
5115
    /**
5116
     *  Selection HT or TTC
5117
     *
5118
     * @param string $selected Id pre-selectionne
5119
     * @param string $htmlname Nom de la zone select
5120
     * @param int $addjscombo Add js combo
5121
     * @return    string                    Code of HTML select to chose tax or not
5122
     */
5123
    public function selectPriceBaseType($selected = '', $htmlname = 'price_base_type', $addjscombo = 0)
5124
    {
5125
        global $langs;
5126
5127
        $return = '<select class="flat maxwidth100" id="select_' . $htmlname . '" name="' . $htmlname . '">';
5128
        $options = array(
5129
            'HT' => $langs->trans("HT"),
5130
            'TTC' => $langs->trans("TTC")
5131
        );
5132
        foreach ($options as $id => $value) {
5133
            if ($selected == $id) {
5134
                $return .= '<option value="' . $id . '" selected>' . $value;
5135
            } else {
5136
                $return .= '<option value="' . $id . '">' . $value;
5137
            }
5138
            $return .= '</option>';
5139
        }
5140
        $return .= '</select>';
5141
        if ($addjscombo) {
5142
            $return .= ajax_combobox('select_' . $htmlname);
5143
        }
5144
5145
        return $return;
5146
    }
5147
5148
    /**
5149
     *    Display form to select shipping mode
5150
     *
5151
     * @param string $page Page
5152
     * @param string $selected Id of shipping mode
5153
     * @param string $htmlname Name of select html field
5154
     * @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.
5155
     * @return    void
5156
     */
5157
    public function formSelectShippingMethod($page, $selected = '', $htmlname = 'shipping_method_id', $addempty = 0)
5158
    {
5159
        global $langs;
5160
5161
        $langs->load("deliveries");
5162
5163
        if ($htmlname != "none") {
5164
            print '<form method="POST" action="' . $page . '">';
5165
            print '<input type="hidden" name="action" value="setshippingmethod">';
5166
            print '<input type="hidden" name="token" value="' . newToken() . '">';
5167
            $this->selectShippingMethod($selected, $htmlname, '', $addempty);
5168
            print '<input type="submit" class="button valignmiddle" value="' . $langs->trans("Modify") . '">';
5169
            print '</form>';
5170
        } else {
5171
            if ($selected) {
5172
                $code = $langs->getLabelFromKey($this->db, $selected, 'c_shipment_mode', 'rowid', 'code');
5173
                print $langs->trans("SendingMethod" . strtoupper($code));
5174
            } else {
5175
                print "&nbsp;";
5176
            }
5177
        }
5178
    }
5179
5180
5181
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5182
5183
    /**
5184
     * Return a HTML select list of shipping mode
5185
     *
5186
     * @param string $selected Id shipping mode preselected
5187
     * @param string $htmlname Name of select zone
5188
     * @param string $filtre To filter list. This parameter must not come from input of users
5189
     * @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.
5190
     * @param string $moreattrib To add more attribute on select
5191
     * @param int $noinfoadmin 0=Add admin info, 1=Disable admin info
5192
     * @param string $morecss More CSS
5193
     * @return void
5194
     */
5195
    public function selectShippingMethod($selected = '', $htmlname = 'shipping_method_id', $filtre = '', $useempty = 0, $moreattrib = '', $noinfoadmin = 0, $morecss = '')
5196
    {
5197
        global $langs, $user;
5198
5199
        $langs->load("admin");
5200
        $langs->load("deliveries");
5201
5202
        $sql = "SELECT rowid, code, libelle as label";
5203
        $sql .= " FROM " . $this->db->prefix() . "c_shipment_mode";
5204
        $sql .= " WHERE active > 0";
5205
        if ($filtre) {
5206
            $sql .= " AND " . $filtre;
5207
        }
5208
        $sql .= " ORDER BY libelle ASC";
5209
5210
        dol_syslog(get_only_class($this) . "::selectShippingMode", LOG_DEBUG);
5211
        $result = $this->db->query($sql);
5212
        if ($result) {
5213
            $num = $this->db->num_rows($result);
5214
            $i = 0;
5215
            if ($num) {
5216
                print '<select id="select' . $htmlname . '" class="flat selectshippingmethod' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '"' . ($moreattrib ? ' ' . $moreattrib : '') . '>';
5217
                if ($useempty == 1 || ($useempty == 2 && $num > 1)) {
5218
                    print '<option value="-1">&nbsp;</option>';
5219
                }
5220
                while ($i < $num) {
5221
                    $obj = $this->db->fetch_object($result);
5222
                    if ($selected == $obj->rowid) {
5223
                        print '<option value="' . $obj->rowid . '" selected>';
5224
                    } else {
5225
                        print '<option value="' . $obj->rowid . '">';
5226
                    }
5227
                    print ($langs->trans("SendingMethod" . strtoupper($obj->code)) != "SendingMethod" . strtoupper($obj->code)) ? $langs->trans("SendingMethod" . strtoupper($obj->code)) : $obj->label;
5228
                    print '</option>';
5229
                    $i++;
5230
                }
5231
                print "</select>";
5232
                if ($user->admin && empty($noinfoadmin)) {
5233
                    print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
5234
                }
5235
5236
                print ajax_combobox('select' . $htmlname);
5237
            } else {
5238
                print $langs->trans("NoShippingMethodDefined");
5239
            }
5240
        } else {
5241
            dol_print_error($this->db);
5242
        }
5243
    }
5244
5245
    /**
5246
     * Creates HTML last in cycle situation invoices selector
5247
     *
5248
     * @param string $selected Preselected ID
5249
     * @param int $socid Company ID
5250
     *
5251
     * @return    string                     HTML select
5252
     */
5253
    public function selectSituationInvoices($selected = '', $socid = 0)
5254
    {
5255
        global $langs;
5256
5257
        $langs->load('bills');
5258
5259
        $opt = '<option value="" selected></option>';
5260
        $sql = "SELECT rowid, ref, situation_cycle_ref, situation_counter, situation_final, fk_soc";
5261
        $sql .= ' FROM ' . $this->db->prefix() . 'facture';
5262
        $sql .= ' WHERE entity IN (' . getEntity('invoice') . ')';
5263
        $sql .= ' AND situation_counter >= 1';
5264
        $sql .= ' AND fk_soc = ' . (int)$socid;
5265
        $sql .= ' AND type <> 2';
5266
        $sql .= ' ORDER by situation_cycle_ref, situation_counter desc';
5267
        $resql = $this->db->query($sql);
5268
5269
        if ($resql && $this->db->num_rows($resql) > 0) {
5270
            // Last seen cycle
5271
            $ref = 0;
5272
            while ($obj = $this->db->fetch_object($resql)) {
5273
                //Same cycle ?
5274
                if ($obj->situation_cycle_ref != $ref) {
5275
                    // Just seen this cycle
5276
                    $ref = $obj->situation_cycle_ref;
5277
                    //not final ?
5278
                    if ($obj->situation_final != 1) {
5279
                        //Not prov?
5280
                        if (substr($obj->ref, 1, 4) != 'PROV') {
5281
                            if ($selected == $obj->rowid) {
5282
                                $opt .= '<option value="' . $obj->rowid . '" selected>' . $obj->ref . '</option>';
5283
                            } else {
5284
                                $opt .= '<option value="' . $obj->rowid . '">' . $obj->ref . '</option>';
5285
                            }
5286
                        }
5287
                    }
5288
                }
5289
            }
5290
        } else {
5291
            dol_syslog("Error sql=" . $sql . ", error=" . $this->error, LOG_ERR);
5292
        }
5293
        if ($opt == '<option value ="" selected></option>') {
5294
            $opt = '<option value ="0" selected>' . $langs->trans('NoSituations') . '</option>';
5295
        }
5296
        return $opt;
5297
    }
5298
5299
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5300
5301
    /**
5302
     * Creates HTML units selector (code => label)
5303
     *
5304
     * @param string $selected Preselected Unit ID
5305
     * @param string $htmlname Select name
5306
     * @param int<0,1> $showempty Add an empty line
5307
     * @param string $unit_type Restrict to one given unit type
5308
     * @return  string                  HTML select
5309
     */
5310
    public function selectUnits($selected = '', $htmlname = 'units', $showempty = 0, $unit_type = '')
5311
    {
5312
        global $langs;
5313
5314
        $langs->load('products');
5315
5316
        $return = '<select class="flat" id="' . $htmlname . '" name="' . $htmlname . '">';
5317
5318
        $sql = "SELECT rowid, label, code FROM " . $this->db->prefix() . "c_units";
5319
        $sql .= ' WHERE active > 0';
5320
        if (!empty($unit_type)) {
5321
            $sql .= " AND unit_type = '" . $this->db->escape($unit_type) . "'";
5322
        }
5323
        $sql .= " ORDER BY sortorder";
5324
5325
        $resql = $this->db->query($sql);
5326
        if ($resql && $this->db->num_rows($resql) > 0) {
5327
            if ($showempty) {
5328
                $return .= '<option value="none"></option>';
5329
            }
5330
5331
            while ($res = $this->db->fetch_object($resql)) {
5332
                $unitLabel = $res->label;
5333
                if (!empty($langs->tab_translate['unit' . $res->code])) {    // check if Translation is available before
5334
                    $unitLabel = $langs->trans('unit' . $res->code) != $res->label ? $langs->trans('unit' . $res->code) : $res->label;
5335
                }
5336
5337
                if ($selected == $res->rowid) {
5338
                    $return .= '<option value="' . $res->rowid . '" selected>' . $unitLabel . '</option>';
5339
                } else {
5340
                    $return .= '<option value="' . $res->rowid . '">' . $unitLabel . '</option>';
5341
                }
5342
            }
5343
            $return .= '</select>';
5344
        }
5345
        return $return;
5346
    }
5347
5348
    /**
5349
     * Return a HTML select list of establishment
5350
     *
5351
     * @param string $selected Id establishment preselected
5352
     * @param string $htmlname Name of select zone
5353
     * @param int $status Status of searched establishment (0=open, 1=closed, 2=both)
5354
     * @param string $filtre To filter list. This parameter must not come from input of users
5355
     * @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.
5356
     * @param string $moreattrib To add more attribute on select
5357
     * @return  int                     Return integer <0 if error, Num of establishment found if OK (0, 1, 2, ...)
5358
     */
5359
    public function selectEstablishments($selected = '', $htmlname = 'entity', $status = 0, $filtre = '', $useempty = 0, $moreattrib = '')
5360
    {
5361
        global $langs;
5362
5363
        $langs->load("admin");
5364
        $num = 0;
5365
5366
        $sql = "SELECT rowid, name, fk_country, status, entity";
5367
        $sql .= " FROM " . $this->db->prefix() . "establishment";
5368
        $sql .= " WHERE 1=1";
5369
        if ($status != 2) {
5370
            $sql .= " AND status = " . (int)$status;
5371
        }
5372
        if ($filtre) {  // TODO Support USF
5373
            $sql .= " AND " . $filtre;
5374
        }
5375
        $sql .= " ORDER BY name";
5376
5377
        dol_syslog(get_only_class($this) . "::select_establishment", LOG_DEBUG);
5378
        $result = $this->db->query($sql);
5379
        if ($result) {
5380
            $num = $this->db->num_rows($result);
5381
            $i = 0;
5382
            if ($num) {
5383
                print '<select id="select' . $htmlname . '" class="flat selectestablishment" name="' . $htmlname . '"' . ($moreattrib ? ' ' . $moreattrib : '') . '>';
5384
                if ($useempty == 1 || ($useempty == 2 && $num > 1)) {
5385
                    print '<option value="-1">&nbsp;</option>';
5386
                }
5387
5388
                while ($i < $num) {
5389
                    $obj = $this->db->fetch_object($result);
5390
                    if ($selected == $obj->rowid) {
5391
                        print '<option value="' . $obj->rowid . '" selected>';
5392
                    } else {
5393
                        print '<option value="' . $obj->rowid . '">';
5394
                    }
5395
                    print trim($obj->name);
5396
                    if ($status == 2 && $obj->status == 1) {
5397
                        print ' (' . $langs->trans("Closed") . ')';
5398
                    }
5399
                    print '</option>';
5400
                    $i++;
5401
                }
5402
                print "</select>";
5403
            } else {
5404
                if ($status == 0) {
5405
                    print '<span class="opacitymedium">' . $langs->trans("NoActiveEstablishmentDefined") . '</span>';
5406
                } else {
5407
                    print '<span class="opacitymedium">' . $langs->trans("NoEstablishmentFound") . '</span>';
5408
                }
5409
            }
5410
5411
            return $num;
5412
        } else {
5413
            dol_print_error($this->db);
5414
            return -1;
5415
        }
5416
    }
5417
5418
    /**
5419
     * Display form to select bank account
5420
     *
5421
     * @param string $page Page
5422
     * @param string $selected Id of bank account
5423
     * @param string $htmlname Name of select html field
5424
     * @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.
5425
     * @return                      void
5426
     */
5427
    public function formSelectAccount($page, $selected = '', $htmlname = 'fk_account', $addempty = 0)
5428
    {
5429
        global $langs;
5430
        if ($htmlname != "none") {
5431
            print '<form method="POST" action="' . $page . '">';
5432
            print '<input type="hidden" name="action" value="setbankaccount">';
5433
            print '<input type="hidden" name="token" value="' . newToken() . '">';
5434
            print img_picto('', 'bank_account', 'class="pictofixedwidth"');
5435
            $nbaccountfound = $this->select_comptes($selected, $htmlname, 0, '', $addempty);
5436
            if ($nbaccountfound > 0) {
5437
                print '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
5438
            }
5439
            print '</form>';
5440
        } else {
5441
            $langs->load('banks');
5442
5443
            if ($selected) {
5444
                $bankstatic = new Account($this->db);
5445
                $result = $bankstatic->fetch($selected);
5446
                if ($result) {
5447
                    print $bankstatic->getNomUrl(1);
5448
                }
5449
            } else {
5450
                print "&nbsp;";
5451
            }
5452
        }
5453
    }
5454
5455
    /**
5456
     *  Return a HTML select list of bank accounts
5457
     *
5458
     * @param int|string $selected Id account preselected
5459
     * @param string $htmlname Name of select zone
5460
     * @param int $status Status of searched accounts (0=open, 1=closed, 2=both)
5461
     * @param string $filtre To filter the list. This parameter must not come from input of users
5462
     * @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.
5463
     * @param string $moreattrib To add more attribute on select
5464
     * @param int $showcurrency Show currency in label
5465
     * @param string $morecss More CSS
5466
     * @param int $nooutput 1=Return string, do not send to output
5467
     * @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.
5468
     */
5469
    public function select_comptes($selected = '', $htmlname = 'accountid', $status = 0, $filtre = '', $useempty = 0, $moreattrib = '', $showcurrency = 0, $morecss = '', $nooutput = 0)
5470
    {
5471
        // phpcs:enable
5472
        global $langs;
5473
5474
        $out = '';
5475
5476
        $langs->load("admin");
5477
        $num = 0;
5478
5479
        $sql = "SELECT rowid, label, bank, clos as status, currency_code";
5480
        $sql .= " FROM " . $this->db->prefix() . "bank_account";
5481
        $sql .= " WHERE entity IN (" . getEntity('bank_account') . ")";
5482
        if ($status != 2) {
5483
            $sql .= " AND clos = " . (int)$status;
5484
        }
5485
        if ($filtre) {  // TODO Support USF
5486
            $sql .= " AND " . $filtre;
5487
        }
5488
        $sql .= " ORDER BY label";
5489
5490
        dol_syslog(get_only_class($this) . "::select_comptes", LOG_DEBUG);
5491
        $result = $this->db->query($sql);
5492
        if ($result) {
5493
            $num = $this->db->num_rows($result);
5494
            $i = 0;
5495
            if ($num) {
5496
                $out .= '<select id="select' . $htmlname . '" class="flat selectbankaccount' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '"' . ($moreattrib ? ' ' . $moreattrib : '') . '>';
5497
5498
                if (!empty($useempty) && !is_numeric($useempty)) {
5499
                    $out .= '<option value="-1">' . $langs->trans($useempty) . '</option>';
5500
                } elseif ($useempty == 1 || ($useempty == 2 && $num > 1)) {
5501
                    $out .= '<option value="-1">&nbsp;</option>';
5502
                }
5503
5504
                while ($i < $num) {
5505
                    $obj = $this->db->fetch_object($result);
5506
                    if ($selected == $obj->rowid || ($useempty == 2 && $num == 1 && empty($selected))) {
5507
                        $out .= '<option value="' . $obj->rowid . '" data-currency-code="' . $obj->currency_code . '" selected>';
5508
                    } else {
5509
                        $out .= '<option value="' . $obj->rowid . '" data-currency-code="' . $obj->currency_code . '">';
5510
                    }
5511
                    $out .= trim($obj->label);
5512
                    if ($showcurrency) {
5513
                        $out .= ' (' . $obj->currency_code . ')';
5514
                    }
5515
                    if ($status == 2 && $obj->status == 1) {
5516
                        $out .= ' (' . $langs->trans("Closed") . ')';
5517
                    }
5518
                    $out .= '</option>';
5519
                    $i++;
5520
                }
5521
                $out .= "</select>";
5522
                $out .= ajax_combobox('select' . $htmlname);
5523
            } else {
5524
                if ($status == 0) {
5525
                    $out .= '<span class="opacitymedium">' . $langs->trans("NoActiveBankAccountDefined") . '</span>';
5526
                } else {
5527
                    $out .= '<span class="opacitymedium">' . $langs->trans("NoBankAccountFound") . '</span>';
5528
                }
5529
            }
5530
        } else {
5531
            dol_print_error($this->db);
5532
        }
5533
5534
        // Output or return
5535
        if (empty($nooutput)) {
5536
            print $out;
5537
        } else {
5538
            return $out;
5539
        }
5540
5541
        return $num;
5542
    }
5543
5544
    /**
5545
     *     Show a confirmation HTML form or AJAX popup
5546
     *
5547
     * @param string $page Url of page to call if confirmation is OK
5548
     * @param string $title Title
5549
     * @param string $question Question
5550
     * @param string $action Action
5551
     * @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=...'))
5552
     * @param string $selectedchoice "" or "no" or "yes"
5553
     * @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
5554
     * @param int $height Force height of box
5555
     * @param int $width Force width of box
5556
     * @return    void
5557
     * @deprecated
5558
     * @see formconfirm()
5559
     */
5560
    public function form_confirm($page, $title, $question, $action, $formquestion = array(), $selectedchoice = "", $useajax = 0, $height = 170, $width = 500)
5561
    {
5562
        // phpcs:enable
5563
        dol_syslog(__METHOD__ . ': using form_confirm is deprecated. Use formconfim instead.', LOG_WARNING);
5564
        print $this->formconfirm($page, $title, $question, $action, $formquestion, $selectedchoice, $useajax, $height, $width);
5565
    }
5566
5567
    /**
5568
     *     Show a confirmation HTML form or AJAX popup.
5569
     *     Easiest way to use this is with useajax=1.
5570
     *     If you use useajax='xxx', you must also add jquery code to trigger opening of box (with correct parameters)
5571
     *     just after calling this method. For example:
5572
     *       print '<script nonce="'.getNonce().'" type="text/javascript">'."\n";
5573
     *       print 'jQuery(document).ready(function() {'."\n";
5574
     *       print 'jQuery(".xxxlink").click(function(e) { jQuery("#aparamid").val(jQuery(this).attr("rel")); jQuery("#dialog-confirm-xxx").dialog("open"); return false; });'."\n";
5575
     *       print '});'."\n";
5576
     *       print '</script>'."\n";
5577
     *
5578
     * @param string $page Url of page to call if confirmation is OK. Can contains parameters (param 'action' and 'confirm' will be reformatted)
5579
     * @param string $title Title
5580
     * @param string $question Question
5581
     * @param string $action Action
5582
     * @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=...'))
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array{name:string,...:int<0,1>}>|string|null at position 48 could not be parsed: Expected '}' at position 48, but found 'int'.
Loading history...
5583
     *                                                                                                                                                                                                                  'type' can be 'text', 'password', 'checkbox', 'radio', 'date', 'datetime', 'select', 'multiselect', 'morecss',
5584
     *                                                                                                                                                                                                                  'other', 'onecolumn' or 'hidden'...
5585
     * @param int<0,1>|''|'no'|'yes'|'1'|'0'    $selectedchoice     '' or 'no', or 'yes' or '1', 1, '0' or 0
5586
     * @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
5587
     * @param int|string $height Force height of box (0 = auto)
5588
     * @param int $width Force width of box ('999' or '90%'). Ignored and forced to 90% on smartphones.
5589
     * @param int $disableformtag 1=Disable form tag. Can be used if we are already inside a <form> section.
5590
     * @param string $labelbuttonyes Label for Yes
5591
     * @param string $labelbuttonno Label for No
5592
     * @return string                           HTML ajax code if a confirm ajax popup is required, Pure HTML code if it's an html form
5593
     */
5594
    public function formconfirm($page, $title, $question, $action, $formquestion = '', $selectedchoice = '', $useajax = 0, $height = 0, $width = 500, $disableformtag = 0, $labelbuttonyes = 'Yes', $labelbuttonno = 'No')
5595
    {
5596
        global $langs, $conf;
5597
5598
        $more = '<!-- formconfirm - before call, page=' . dol_escape_htmltag($page) . ' -->';
5599
        $formconfirm = '';
5600
        $inputok = array();
5601
        $inputko = array();
5602
5603
        // Clean parameters
5604
        $newselectedchoice = empty($selectedchoice) ? "no" : $selectedchoice;
5605
        if ($conf->browser->layout == 'phone') {
5606
            $width = '95%';
5607
        }
5608
5609
        // Set height automatically if not defined
5610
        if (empty($height)) {
5611
            $height = 220;
5612
            if (is_array($formquestion) && count($formquestion) > 2) {
5613
                $height += ((count($formquestion) - 2) * 24);
5614
            }
5615
        }
5616
5617
        if (is_array($formquestion) && !empty($formquestion)) {
5618
            // First add hidden fields and value
5619
            foreach ($formquestion as $key => $input) {
5620
                if (is_array($input) && !empty($input)) {
5621
                    if ($input['type'] == 'hidden') {
5622
                        $moreattr = (!empty($input['moreattr']) ? ' ' . $input['moreattr'] : '');
5623
                        $morecss = (!empty($input['morecss']) ? ' ' . $input['morecss'] : '');
5624
5625
                        $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";
5626
                    }
5627
                }
5628
            }
5629
5630
            // Now add questions
5631
            $moreonecolumn = '';
5632
            $more .= '<div class="tagtable paddingtopbottomonly centpercent noborderspacing">' . "\n";
5633
            foreach ($formquestion as $key => $input) {
5634
                if (is_array($input) && !empty($input)) {
5635
                    $size = (!empty($input['size']) ? ' size="' . $input['size'] . '"' : '');    // deprecated. Use morecss instead.
5636
                    $moreattr = (!empty($input['moreattr']) ? ' ' . $input['moreattr'] : '');
5637
                    $morecss = (!empty($input['morecss']) ? ' ' . $input['morecss'] : '');
5638
5639
                    if ($input['type'] == 'text') {
5640
                        $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";
5641
                    } elseif ($input['type'] == 'password') {
5642
                        $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";
5643
                    } elseif ($input['type'] == 'textarea') {
5644
                        /*$more .= '<div class="tagtr"><div class="tagtd'.(empty($input['tdclass']) ? '' : (' '.$input['tdclass'])).'">'.$input['label'].'</div><div class="tagtd">';
5645
                        $more .= '<textarea name="'.$input['name'].'" class="'.$morecss.'"'.$moreattr.'>';
5646
                        $more .= $input['value'];
5647
                        $more .= '</textarea>';
5648
                        $more .= '</div></div>'."\n";*/
5649
                        $moreonecolumn .= '<div class="margintoponly">';
5650
                        $moreonecolumn .= $input['label'] . '<br>';
5651
                        $moreonecolumn .= '<textarea name="' . dol_escape_htmltag($input['name']) . '" id="' . dol_escape_htmltag($input['name']) . '" class="' . $morecss . '"' . $moreattr . '>';
5652
                        $moreonecolumn .= $input['value'];
5653
                        $moreonecolumn .= '</textarea>';
5654
                        $moreonecolumn .= '</div>';
5655
                    } elseif (in_array($input['type'], ['select', 'multiselect'])) {
5656
                        if (empty($morecss)) {
5657
                            $morecss = 'minwidth100';
5658
                        }
5659
5660
                        $show_empty = isset($input['select_show_empty']) ? $input['select_show_empty'] : 1;
5661
                        $key_in_label = isset($input['select_key_in_label']) ? $input['select_key_in_label'] : 0;
5662
                        $value_as_key = isset($input['select_value_as_key']) ? $input['select_value_as_key'] : 0;
5663
                        $translate = isset($input['select_translate']) ? $input['select_translate'] : 0;
5664
                        $maxlen = isset($input['select_maxlen']) ? $input['select_maxlen'] : 0;
5665
                        $disabled = isset($input['select_disabled']) ? $input['select_disabled'] : 0;
5666
                        $sort = isset($input['select_sort']) ? $input['select_sort'] : '';
5667
5668
                        $more .= '<div class="tagtr"><div class="tagtd' . (empty($input['tdclass']) ? '' : (' ' . $input['tdclass'])) . '">';
5669
                        if (!empty($input['label'])) {
5670
                            $more .= $input['label'] . '</div><div class="tagtd left">';
5671
                        }
5672
                        if ($input['type'] == 'select') {
5673
                            $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);
5674
                        } else {
5675
                            $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);
5676
                        }
5677
                        $more .= '</div></div>' . "\n";
5678
                    } elseif ($input['type'] == 'checkbox') {
5679
                        $more .= '<div class="tagtr">';
5680
                        $more .= '<div class="tagtd' . (empty($input['tdclass']) ? '' : (' ' . $input['tdclass'])) . '"><label for="' . dol_escape_htmltag($input['name']) . '">' . $input['label'] . '</label></div><div class="tagtd">';
5681
                        $more .= '<input type="checkbox" class="flat' . ($morecss ? ' ' . $morecss : '') . '" id="' . dol_escape_htmltag($input['name']) . '" name="' . dol_escape_htmltag($input['name']) . '"' . $moreattr;
5682
                        if (!is_bool($input['value']) && $input['value'] != 'false' && $input['value'] != '0' && $input['value'] != '') {
5683
                            $more .= ' checked';
5684
                        }
5685
                        if (is_bool($input['value']) && $input['value']) {
5686
                            $more .= ' checked';
5687
                        }
5688
                        if (isset($input['disabled'])) {
5689
                            $more .= ' disabled';
5690
                        }
5691
                        $more .= ' /></div>';
5692
                        $more .= '</div>' . "\n";
5693
                    } elseif ($input['type'] == 'radio') {
5694
                        $i = 0;
5695
                        foreach ($input['values'] as $selkey => $selval) {
5696
                            $more .= '<div class="tagtr">';
5697
                            if (isset($input['label'])) {
5698
                                if ($i == 0) {
5699
                                    $more .= '<div class="tagtd' . (empty($input['tdclass']) ? ' tdtop' : (' tdtop ' . $input['tdclass'])) . '">' . $input['label'] . '</div>';
5700
                                } else {
5701
                                    $more .= '<div class="tagtd' . (empty($input['tdclass']) ? '' : (' "' . $input['tdclass'])) . '">&nbsp;</div>';
5702
                                }
5703
                            }
5704
                            $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;
5705
                            if (!empty($input['disabled'])) {
5706
                                $more .= ' disabled';
5707
                            }
5708
                            if (isset($input['default']) && $input['default'] === $selkey) {
5709
                                $more .= ' checked="checked"';
5710
                            }
5711
                            $more .= ' /> ';
5712
                            $more .= '<label for="' . dol_escape_htmltag($input['name'] . $selkey) . '" class="valignmiddle">' . $selval . '</label>';
5713
                            $more .= '</div></div>' . "\n";
5714
                            $i++;
5715
                        }
5716
                    } elseif ($input['type'] == 'date' || $input['type'] == 'datetime') {
5717
                        $more .= '<div class="tagtr"><div class="tagtd' . (empty($input['tdclass']) ? '' : (' ' . $input['tdclass'])) . '">' . $input['label'] . '</div>';
5718
                        $more .= '<div class="tagtd">';
5719
                        $addnowlink = (empty($input['datenow']) ? 0 : 1);
5720
                        $h = $m = 0;
5721
                        if ($input['type'] == 'datetime') {
5722
                            $h = isset($input['hours']) ? $input['hours'] : 1;
5723
                            $m = isset($input['minutes']) ? $input['minutes'] : 1;
5724
                        }
5725
                        $more .= $this->selectDate(isset($input['value']) ? $input['value'] : -1, $input['name'], $h, $m, 0, '', 1, $addnowlink);
5726
                        $more .= '</div></div>' . "\n";
5727
                        $formquestion[] = array('name' => $input['name'] . 'day');
5728
                        $formquestion[] = array('name' => $input['name'] . 'month');
5729
                        $formquestion[] = array('name' => $input['name'] . 'year');
5730
                        $formquestion[] = array('name' => $input['name'] . 'hour');
5731
                        $formquestion[] = array('name' => $input['name'] . 'min');
5732
                    } elseif ($input['type'] == 'other') { // can be 1 column or 2 depending if label is set or not
5733
                        $more .= '<div class="tagtr"><div class="tagtd' . (empty($input['tdclass']) ? '' : (' ' . $input['tdclass'])) . '">';
5734
                        if (!empty($input['label'])) {
5735
                            $more .= $input['label'] . '</div><div class="tagtd">';
5736
                        }
5737
                        $more .= $input['value'];
5738
                        $more .= '</div></div>' . "\n";
5739
                    } elseif ($input['type'] == 'onecolumn') {
5740
                        $moreonecolumn .= '<div class="margintoponly">';
5741
                        $moreonecolumn .= $input['value'];
5742
                        $moreonecolumn .= '</div>' . "\n";
5743
                    } elseif ($input['type'] == 'hidden') {
5744
                        // Do nothing more, already added by a previous loop
5745
                    } elseif ($input['type'] == 'separator') {
5746
                        $more .= '<br>';
5747
                    } else {
5748
                        $more .= 'Error type ' . $input['type'] . ' for the confirm box is not a supported type';
5749
                    }
5750
                }
5751
            }
5752
            $more .= '</div>' . "\n";
5753
            $more .= $moreonecolumn;
5754
        }
5755
5756
        // JQUERY method dialog is broken with smartphone, we use standard HTML.
5757
        // 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
5758
        // See page product/card.php for example
5759
        if (!empty($conf->dol_use_jmobile)) {
5760
            $useajax = 0;
5761
        }
5762
        if (empty($conf->use_javascript_ajax)) {
5763
            $useajax = 0;
5764
        }
5765
5766
        if ($useajax) {
5767
            $autoOpen = true;
5768
            $dialogconfirm = 'dialog-confirm';
5769
            $button = '';
5770
            if (!is_numeric($useajax)) {
5771
                $button = $useajax;
5772
                $useajax = 1;
5773
                $autoOpen = false;
5774
                $dialogconfirm .= '-' . $button;
5775
            }
5776
            $pageyes = $page . (preg_match('/\?/', $page) ? '&' : '?') . 'action=' . urlencode($action) . '&confirm=yes';
5777
            $pageno = ($useajax == 2 ? $page . (preg_match('/\?/', $page) ? '&' : '?') . 'action=' . urlencode($action) . '&confirm=no' : '');
5778
5779
            // Add input fields into list of fields to read during submit (inputok and inputko)
5780
            if (is_array($formquestion)) {
5781
                foreach ($formquestion as $key => $input) {
5782
                    //print "xx ".$key." rr ".is_array($input)."<br>\n";
5783
                    // Add name of fields to propagate with the GET when submitting the form with button OK.
5784
                    if (is_array($input) && isset($input['name'])) {
5785
                        if (strpos($input['name'], ',') > 0) {
5786
                            $inputok = array_merge($inputok, explode(',', $input['name']));
5787
                        } else {
5788
                            array_push($inputok, $input['name']);
5789
                        }
5790
                    }
5791
                    // Add name of fields to propagate with the GET when submitting the form with button KO.
5792
                    // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
5793
                    if (is_array($input) && isset($input['inputko']) && $input['inputko'] == 1 && isset($input['name'])) {
5794
                        array_push($inputko, $input['name']);
5795
                    }
5796
                }
5797
            }
5798
5799
            // Show JQuery confirm box.
5800
            $formconfirm .= '<div id="' . $dialogconfirm . '" title="' . dol_escape_htmltag($title) . '" style="display: none;">';
5801
            if (is_array($formquestion) && array_key_exists('text', $formquestion) && !empty($formquestion['text'])) {
5802
                $formconfirm .= '<div class="confirmtext">' . $formquestion['text'] . '</div>' . "\n";
5803
            }
5804
            if (!empty($more)) {
5805
                $formconfirm .= '<div class="confirmquestions">' . $more . '</div>' . "\n";
5806
            }
5807
            $formconfirm .= ($question ? '<div class="confirmmessage">' . img_help(0, '') . ' ' . $question . '</div>' : '');
5808
            $formconfirm .= '</div>' . "\n";
5809
5810
            $formconfirm .= "\n<!-- begin code of popup for formconfirm page=" . $page . " -->\n";
5811
            $formconfirm .= '<script nonce="' . getNonce() . '" type="text/javascript">' . "\n";
5812
            $formconfirm .= "/* Code for the jQuery('#dialogforpopup').dialog() */\n";
5813
            $formconfirm .= 'jQuery(document).ready(function() {
5814
            $(function() {
5815
            	$( "#' . $dialogconfirm . '" ).dialog(
5816
            	{
5817
                    autoOpen: ' . ($autoOpen ? "true" : "false") . ',';
5818
            if ($newselectedchoice == 'no') {
5819
                $formconfirm .= '
5820
						open: function() {
5821
            				$(this).parent().find("button.ui-button:eq(2)").focus();
5822
						},';
5823
            }
5824
5825
            $jsforcursor = '';
5826
            if ($useajax == 1) {
5827
                $jsforcursor = '// The call to urljump can be slow, so we set the wait cursor' . "\n";
5828
                $jsforcursor .= 'jQuery("html,body,#id-container").addClass("cursorwait");' . "\n";
5829
            }
5830
5831
            $postconfirmas = 'GET';
5832
5833
            $formconfirm .= '
5834
                    resizable: false,
5835
                    height: "' . $height . '",
5836
                    width: "' . $width . '",
5837
                    modal: true,
5838
                    closeOnEscape: false,
5839
                    buttons: {
5840
                        "' . dol_escape_js($langs->transnoentities($labelbuttonyes)) . '": function() {
5841
							var options = "token=' . urlencode(newToken()) . '";
5842
                        	var inputok = ' . json_encode($inputok) . ';	/* List of fields into form */
5843
							var page = "' . dol_escape_js(!empty($page) ? $page : '') . '";
5844
                         	var pageyes = "' . dol_escape_js(!empty($pageyes) ? $pageyes : '') . '";
5845
5846
                         	if (inputok.length > 0) {
5847
                         		$.each(inputok, function(i, inputname) {
5848
                         			var more = "";
5849
									var inputvalue;
5850
                         			if ($("input[name=\'" + inputname + "\']").attr("type") == "radio") {
5851
										inputvalue = $("input[name=\'" + inputname + "\']:checked").val();
5852
									} else {
5853
                         		    	if ($("#" + inputname).attr("type") == "checkbox") { more = ":checked"; }
5854
                         				inputvalue = $("#" + inputname + more).val();
5855
									}
5856
                         			if (typeof inputvalue == "undefined") { inputvalue=""; }
5857
									console.log("formconfirm check inputname="+inputname+" inputvalue="+inputvalue);
5858
                         			options += "&" + inputname + "=" + encodeURIComponent(inputvalue);
5859
                         		});
5860
                         	}
5861
                         	var urljump = pageyes + (pageyes.indexOf("?") < 0 ? "?" : "&") + options;
5862
            				if (pageyes.length > 0) {';
5863
            if ($postconfirmas == 'GET') {
5864
                $formconfirm .= 'location.href = urljump;';
5865
            } else {
5866
                $formconfirm .= $jsforcursor;
5867
                $formconfirm .= 'var post = $.post(
5868
									pageyes,
5869
									options,
5870
									function(data) { $("body").html(data); jQuery("html,body,#id-container").removeClass("cursorwait"); }
5871
								);';
5872
            }
5873
            $formconfirm .= '
5874
								console.log("after post ok");
5875
							}
5876
	                        $(this).dialog("close");
5877
                        },
5878
                        "' . dol_escape_js($langs->transnoentities($labelbuttonno)) . '": function() {
5879
                        	var options = "token=' . urlencode(newToken()) . '";
5880
                         	var inputko = ' . json_encode($inputko) . ';	/* List of fields into form */
5881
							var page = "' . dol_escape_js(!empty($page) ? $page : '') . '";
5882
                         	var pageno="' . dol_escape_js(!empty($pageno) ? $pageno : '') . '";
5883
                         	if (inputko.length > 0) {
5884
                         		$.each(inputko, function(i, inputname) {
5885
                         			var more = "";
5886
                         			if ($("#" + inputname).attr("type") == "checkbox") { more = ":checked"; }
5887
                         			var inputvalue = $("#" + inputname + more).val();
5888
                         			if (typeof inputvalue == "undefined") { inputvalue=""; }
5889
                         			options += "&" + inputname + "=" + encodeURIComponent(inputvalue);
5890
                         		});
5891
                         	}
5892
                         	var urljump=pageno + (pageno.indexOf("?") < 0 ? "?" : "&") + options;
5893
                         	//alert(urljump);
5894
            				if (pageno.length > 0) {';
5895
            if ($postconfirmas == 'GET') {
5896
                $formconfirm .= 'location.href = urljump;';
5897
            } else {
5898
                $formconfirm .= $jsforcursor;
5899
                $formconfirm .= 'var post = $.post(
5900
									pageno,
5901
									options,
5902
									function(data) { $("body").html(data); jQuery("html,body,#id-container").removeClass("cursorwait"); }
5903
								);';
5904
            }
5905
            $formconfirm .= '
5906
								console.log("after post ko");
5907
							}
5908
                            $(this).dialog("close");
5909
                        }
5910
                    }
5911
                }
5912
                );
5913
5914
            	var button = "' . $button . '";
5915
            	if (button.length > 0) {
5916
                	$( "#" + button ).click(function() {
5917
                		$("#' . $dialogconfirm . '").dialog("open");
5918
        			});
5919
                }
5920
            });
5921
            });
5922
            </script>';
5923
            $formconfirm .= "<!-- end ajax formconfirm -->\n";
5924
        } else {
5925
            $formconfirm .= "\n<!-- begin formconfirm page=" . dol_escape_htmltag($page) . " -->\n";
5926
5927
            if (empty($disableformtag)) {
5928
                $formconfirm .= '<form method="POST" action="' . $page . '" class="notoptoleftroright">' . "\n";
5929
            }
5930
5931
            $formconfirm .= '<input type="hidden" name="action" value="' . $action . '">' . "\n";
5932
            $formconfirm .= '<input type="hidden" name="token" value="' . newToken() . '">' . "\n";
5933
5934
            $formconfirm .= '<table class="valid centpercent">' . "\n";
5935
5936
            // Line title
5937
            $formconfirm .= '<tr class="validtitre"><td class="validtitre" colspan="2">';
5938
            $formconfirm .= img_picto('', 'pictoconfirm') . ' ' . $title;
5939
            $formconfirm .= '</td></tr>' . "\n";
5940
5941
            // Line text
5942
            if (is_array($formquestion) && array_key_exists('text', $formquestion) && !empty($formquestion['text'])) {
5943
                $formconfirm .= '<tr class="valid"><td class="valid" colspan="2">' . $formquestion['text'] . '</td></tr>' . "\n";
5944
            }
5945
5946
            // Line form fields
5947
            if ($more) {
5948
                $formconfirm .= '<tr class="valid"><td class="valid" colspan="2">' . "\n";
5949
                $formconfirm .= $more;
5950
                $formconfirm .= '</td></tr>' . "\n";
5951
            }
5952
5953
            // Line with question
5954
            $formconfirm .= '<tr class="valid">';
5955
            $formconfirm .= '<td class="valid">' . $question . '</td>';
5956
            $formconfirm .= '<td class="valid center">';
5957
            $formconfirm .= $this->selectyesno("confirm", $newselectedchoice, 0, false, 0, 0, 'marginleftonly marginrightonly', $labelbuttonyes, $labelbuttonno);
5958
            $formconfirm .= '<input class="button valignmiddle confirmvalidatebutton small" type="submit" value="' . $langs->trans("Validate") . '">';
5959
            $formconfirm .= '</td>';
5960
            $formconfirm .= '</tr>' . "\n";
5961
5962
            $formconfirm .= '</table>' . "\n";
5963
5964
            if (empty($disableformtag)) {
5965
                $formconfirm .= "</form>\n";
5966
            }
5967
            $formconfirm .= '<br>';
5968
5969
            if (!empty($conf->use_javascript_ajax)) {
5970
                $formconfirm .= '<!-- code to disable button to avoid double clic -->';
5971
                $formconfirm .= '<script nonce="' . getNonce() . '" type="text/javascript">' . "\n";
5972
                $formconfirm .= '
5973
				$(document).ready(function () {
5974
					$(".confirmvalidatebutton").on("click", function() {
5975
						console.log("We click on button confirmvalidatebutton");
5976
						$(this).attr("disabled", "disabled");
5977
						setTimeout(\'$(".confirmvalidatebutton").removeAttr("disabled")\', 3000);
5978
						//console.log($(this).closest("form"));
5979
						$(this).closest("form").submit();
5980
					});
5981
				});
5982
				';
5983
                $formconfirm .= '</script>' . "\n";
5984
            }
5985
5986
            $formconfirm .= "<!-- end formconfirm -->\n";
5987
        }
5988
5989
        return $formconfirm;
5990
    }
5991
5992
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5993
5994
    /**
5995
     * Show a multiselect form from an array. WARNING: Use this only for short lists.
5996
     *
5997
     * @param string $htmlname Name of select
5998
     * @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'=> ))
5999
     * @param string[] $selected Array of keys preselected
6000
     * @param int<0,1> $key_in_label 1 to show key like in "[key] value"
6001
     * @param int<0,1> $value_as_key 1 to use value as key
6002
     * @param string $morecss Add more css style
6003
     * @param int<0,1> $translate Translate and encode value
6004
     * @param int|string $width Force width of select box. May be used only when using jquery couch. Example: 250, '95%'
6005
     * @param string $moreattrib Add more options on select component. Example: 'disabled'
6006
     * @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.
6007
     * @param string $placeholder String to use as placeholder
6008
     * @param int<-1,1> $addjscombo Add js combo
6009
     * @return  string                      HTML multiselect string
6010
     * @see selectarray(), selectArrayAjax(), selectArrayFilter()
6011
     */
6012
    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)
6013
    {
6014
        global $conf, $langs;
6015
6016
        $out = '';
6017
6018
        if ($addjscombo < 0) {
6019
            if (!getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
6020
                $addjscombo = 1;
6021
            } else {
6022
                $addjscombo = 0;
6023
            }
6024
        }
6025
6026
        $useenhancedmultiselect = 0;
6027
        if (!empty($conf->use_javascript_ajax) && !defined('MAIN_DO_NOT_USE_JQUERY_MULTISELECT') && (getDolGlobalString('MAIN_USE_JQUERY_MULTISELECT') || defined('REQUIRE_JQUERY_MULTISELECT'))) {
6028
            if ($addjscombo) {
6029
                $useenhancedmultiselect = 1;    // Use the js multiselect in one line. Possible only if $addjscombo not 0.
6030
            }
6031
        }
6032
6033
        // We need a hidden field because when using the multiselect, if we unselect all, there is no
6034
        // variable submitted at all, so no way to make a difference between variable not submitted and variable
6035
        // submitted to nothing.
6036
        $out .= '<input type="hidden" name="' . $htmlname . '_multiselect" value="1">';
6037
        // Output select component
6038
        $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";
6039
        if (is_array($array) && !empty($array)) {
6040
            if ($value_as_key) {
6041
                $array = array_combine($array, $array);
6042
            }
6043
6044
            if (!empty($array)) {
6045
                foreach ($array as $key => $value) {
6046
                    $tmpkey = $key;
6047
                    $tmpvalue = $value;
6048
                    $tmpcolor = '';
6049
                    $tmppicto = '';
6050
                    $tmplabelhtml = '';
6051
                    if (is_array($value) && array_key_exists('id', $value) && array_key_exists('label', $value)) {
6052
                        $tmpkey = $value['id'];
6053
                        $tmpvalue = empty($value['label']) ? '' : $value['label'];
6054
                        $tmpcolor = empty($value['color']) ? '' : $value['color'];
6055
                        $tmppicto = empty($value['picto']) ? '' : $value['picto'];
6056
                        $tmplabelhtml = empty($value['labelhtml']) ? (empty($value['data-html']) ? '' : $value['data-html']) : $value['labelhtml'];
6057
                    }
6058
                    $newval = ($translate ? $langs->trans($tmpvalue) : $tmpvalue);
6059
                    $newval = ($key_in_label ? $tmpkey . ' - ' . $newval : $newval);
6060
6061
                    $out .= '<option value="' . $tmpkey . '"';
6062
                    if (is_array($selected) && !empty($selected) && in_array((string)$tmpkey, $selected) && ((string)$tmpkey != '')) {
6063
                        $out .= ' selected';
6064
                    }
6065
                    if (!empty($tmplabelhtml)) {
6066
                        $out .= ' data-html="' . dol_escape_htmltag($tmplabelhtml, 0, 0, '', 0, 1) . '"';
6067
                    } else {
6068
                        $tmplabelhtml = ($tmppicto ? img_picto('', $tmppicto, 'class="pictofixedwidth" style="color: #' . $tmpcolor . '"') : '') . $newval;
6069
                        $out .= ' data-html="' . dol_escape_htmltag($tmplabelhtml, 0, 0, '', 0, 1) . '"';
6070
                    }
6071
                    $out .= '>';
6072
                    $out .= dol_htmlentitiesbr($newval);
6073
                    $out .= '</option>' . "\n";
6074
                }
6075
            }
6076
        }
6077
        $out .= '</select>' . "\n";
6078
6079
        // Add code for jquery to use multiselect
6080
        if (!empty($conf->use_javascript_ajax) && getDolGlobalString('MAIN_USE_JQUERY_MULTISELECT') || defined('REQUIRE_JQUERY_MULTISELECT')) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (! empty($conf->use_java...RE_JQUERY_MULTISELECT'), Probably Intended Meaning: ! empty($conf->use_javas...E_JQUERY_MULTISELECT'))
Loading history...
6081
            $out .= "\n" . '<!-- JS CODE TO ENABLE select for id ' . $htmlname . ', addjscombo=' . $addjscombo . ' -->';
6082
            $out .= "\n" . '<script nonce="' . getNonce() . '">' . "\n";
6083
            if ($addjscombo == 1) {
6084
                $tmpplugin = !getDolGlobalString('MAIN_USE_JQUERY_MULTISELECT') ? constant('REQUIRE_JQUERY_MULTISELECT') : $conf->global->MAIN_USE_JQUERY_MULTISELECT;
6085
                $out .= 'function formatResult(record, container) {' . "\n";
6086
                // If property data-html set, we decode html entities and use this.
6087
                // Note that HTML content must have been sanitized from js with dol_escape_htmltag(xxx, 0, 0, '', 0, 1) when building the select option.
6088
                $out .= '	if ($(record.element).attr("data-html") != undefined && typeof htmlEntityDecodeJs === "function") {';
6089
                //$out .= '     console.log("aaa");';
6090
                $out .= '		return htmlEntityDecodeJs($(record.element).attr("data-html"));';
6091
                $out .= '	}' . "\n";
6092
                $out .= '	return record.text;';
6093
                $out .= '}' . "\n";
6094
                $out .= 'function formatSelection(record) {' . "\n";
6095
                if ($elemtype == 'category') {
6096
                    $out .= 'return \'<span><img src="' . constant('DOL_URL_ROOT') . '/theme/eldy/img/object_category.png"> \'+record.text+\'</span>\';';
6097
                } else {
6098
                    $out .= 'return record.text;';
6099
                }
6100
                $out .= '}' . "\n";
6101
                $out .= '$(document).ready(function () {
6102
							$(\'#' . $htmlname . '\').' . $tmpplugin . '({';
6103
                if ($placeholder) {
6104
                    $out .= '
6105
								placeholder: {
6106
								    id: \'-1\',
6107
								    text: \'' . dol_escape_js($placeholder) . '\'
6108
								  },';
6109
                }
6110
                $out .= '		dir: \'ltr\',
6111
								containerCssClass: \':all:\',					/* Line to add class of origin SELECT propagated to the new <span class="select2-selection...> tag (ko with multiselect) */
6112
								dropdownCssClass: \'' . $morecss . '\',				/* Line to add class on the new <span class="select2-selection...> tag (ok with multiselect). Need full version of select2. */
6113
								// Specify format function for dropdown item
6114
								formatResult: formatResult,
6115
							 	templateResult: formatResult,		/* For 4.0 */
6116
								escapeMarkup: function (markup) { return markup; }, 	// let our custom formatter work
6117
								// Specify format function for selected item
6118
								formatSelection: formatSelection,
6119
							 	templateSelection: formatSelection		/* For 4.0 */
6120
							});
6121
6122
							/* Add also morecss to the css .select2 that is after the #htmlname, for component that are show dynamically after load, because select2 set
6123
								 the size only if component is not hidden by default on load */
6124
							$(\'#' . $htmlname . ' + .select2\').addClass(\'' . $morecss . '\');
6125
						});' . "\n";
6126
            } elseif ($addjscombo == 2 && !defined('DISABLE_MULTISELECT')) {
6127
                // Add other js lib
6128
                // TODO external lib multiselect/jquery.multi-select.js must have been loaded to use this multiselect plugin
6129
                // ...
6130
                $out .= 'console.log(\'addjscombo=2 for htmlname=' . $htmlname . '\');';
6131
                $out .= '$(document).ready(function () {
6132
							$(\'#' . $htmlname . '\').multiSelect({
6133
								containerHTML: \'<div class="multi-select-container">\',
6134
								menuHTML: \'<div class="multi-select-menu">\',
6135
								buttonHTML: \'<span class="multi-select-button ' . $morecss . '">\',
6136
								menuItemHTML: \'<label class="multi-select-menuitem">\',
6137
								activeClass: \'multi-select-container--open\',
6138
								noneText: \'' . $placeholder . '\'
6139
							});
6140
						})';
6141
            }
6142
            $out .= '</script>';
6143
        }
6144
6145
        return $out;
6146
    }
6147
6148
    /**
6149
     *    Return an html string with a select combo box to choose yes or no
6150
     *
6151
     * @param string $htmlname Name of html select field
6152
     * @param string $value Pre-selected value
6153
     * @param int $option 0 return yes/no, 1 return 1/0
6154
     * @param bool $disabled true or false
6155
     * @param int $useempty 1=Add empty line
6156
     * @param int $addjscombo 1=Add js beautifier on combo box
6157
     * @param string $morecss More CSS
6158
     * @param string $labelyes Label for Yes
6159
     * @param string $labelno Label for No
6160
     * @return    string                See option
6161
     */
6162
    public function selectyesno($htmlname, $value = '', $option = 0, $disabled = false, $useempty = 0, $addjscombo = 0, $morecss = '', $labelyes = 'Yes', $labelno = 'No')
6163
    {
6164
        global $langs;
6165
6166
        $yes = "yes";
6167
        $no = "no";
6168
        if ($option) {
6169
            $yes = "1";
6170
            $no = "0";
6171
        }
6172
6173
        $disabled = ($disabled ? ' disabled' : '');
6174
6175
        $resultyesno = '<select class="flat width75' . ($morecss ? ' ' . $morecss : '') . '" id="' . $htmlname . '" name="' . $htmlname . '"' . $disabled . '>' . "\n";
6176
        if ($useempty) {
6177
            $resultyesno .= '<option value="-1"' . (($value < 0) ? ' selected' : '') . '>&nbsp;</option>' . "\n";
6178
        }
6179
        if (("$value" == 'yes') || ($value == 1)) {
6180
            $resultyesno .= '<option value="' . $yes . '" selected>' . $langs->trans($labelyes) . '</option>' . "\n";
6181
            $resultyesno .= '<option value="' . $no . '">' . $langs->trans($labelno) . '</option>' . "\n";
6182
        } else {
6183
            $selected = (($useempty && $value != '0' && $value != 'no') ? '' : ' selected');
6184
            $resultyesno .= '<option value="' . $yes . '">' . $langs->trans($labelyes) . '</option>' . "\n";
6185
            $resultyesno .= '<option value="' . $no . '"' . $selected . '>' . $langs->trans($labelno) . '</option>' . "\n";
6186
        }
6187
        $resultyesno .= '</select>' . "\n";
6188
6189
        if ($addjscombo) {
6190
            $resultyesno .= ajax_combobox($htmlname, array(), 0, 0, 'resolve', ($useempty < 0 ? (string)$useempty : '-1'), $morecss);
6191
        }
6192
6193
        return $resultyesno;
6194
    }
6195
6196
    /**
6197
     * Show a form to select a project
6198
     *
6199
     * @param int $page Page
6200
     * @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)
6201
     * @param string $selected Id preselected project
6202
     * @param string $htmlname Name of select field
6203
     * @param int $discard_closed Discard closed projects (0=Keep,1=hide completely except $selected,2=Disable)
6204
     * @param int $maxlength Max length
6205
     * @param int $forcefocus Force focus on field (works with javascript only)
6206
     * @param int $nooutput No print is done. String is returned.
6207
     * @param string $textifnoproject Text to show if no project
6208
     * @param string $morecss More CSS
6209
     * @return  string                          Return html content
6210
     */
6211
    public function form_project($page, $socid, $selected = '', $htmlname = 'projectid', $discard_closed = 0, $maxlength = 20, $forcefocus = 0, $nooutput = 0, $textifnoproject = '', $morecss = '')
6212
    {
6213
        // phpcs:enable
6214
        global $langs;
6215
6216
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/project.lib.php';
6217
6218
        $out = '';
6219
6220
        $formproject = new FormProjets($this->db);
6221
6222
        $langs->load("project");
6223
        if ($htmlname != "none") {
6224
            $out .= '<form method="post" action="' . $page . '">';
6225
            $out .= '<input type="hidden" name="action" value="classin">';
6226
            $out .= '<input type="hidden" name="token" value="' . newToken() . '">';
6227
            $out .= $formproject->select_projects($socid, $selected, $htmlname, $maxlength, 0, 1, $discard_closed, $forcefocus, 0, 0, '', 1, 0, $morecss);
6228
            $out .= '<input type="submit" class="button smallpaddingimp" value="' . $langs->trans("Modify") . '">';
6229
            $out .= '</form>';
6230
        } else {
6231
            $out .= '<span class="project_head_block">';
6232
            if ($selected) {
6233
                $projet = new Project($this->db);
6234
                $projet->fetch($selected);
6235
                $out .= $projet->getNomUrl(0, '', 1);
6236
            } else {
6237
                $out .= '<span class="opacitymedium">' . $textifnoproject . '</span>';
6238
            }
6239
            $out .= '</span>';
6240
        }
6241
6242
        if (empty($nooutput)) {
6243
            print $out;
6244
            return '';
6245
        }
6246
        return $out;
6247
    }
6248
6249
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6250
6251
    /**
6252
     * Show a form to select payment conditions
6253
     *
6254
     * @param int $page Page
6255
     * @param string $selected Id condition pre-selectionne
6256
     * @param string $htmlname Name of select html field
6257
     * @param int $addempty Add empty entry
6258
     * @param string $type Type ('direct-debit' or 'bank-transfer')
6259
     * @param int $filtertype If > 0, include payment terms with deposit percentage (for objects other than invoices and invoice templates)
6260
     * @param int $deposit_percent < 0 : deposit_percent input makes no sense (for example, in list filters)
6261
     *                                      0 : use default deposit percentage from entry
6262
     *                                      > 0 : force deposit percentage (for example, from company object)
6263
     * @param int $nooutput No print is done. String is returned.
6264
     * @return string                       HTML output or ''
6265
     */
6266
    public function form_conditions_reglement($page, $selected = '', $htmlname = 'cond_reglement_id', $addempty = 0, $type = '', $filtertype = -1, $deposit_percent = -1, $nooutput = 0)
6267
    {
6268
        // phpcs:enable
6269
        global $langs;
6270
6271
        $out = '';
6272
6273
        if ($htmlname != "none") {
6274
            $out .= '<form method="POST" action="' . $page . '">';
6275
            $out .= '<input type="hidden" name="action" value="setconditions">';
6276
            $out .= '<input type="hidden" name="token" value="' . newToken() . '">';
6277
            if ($type) {
6278
                $out .= '<input type="hidden" name="type" value="' . dol_escape_htmltag($type) . '">';
6279
            }
6280
            $out .= $this->getSelectConditionsPaiements($selected, $htmlname, $filtertype, $addempty, 0, '', $deposit_percent);
6281
            $out .= '<input type="submit" class="button valignmiddle smallpaddingimp" value="' . $langs->trans("Modify") . '">';
6282
            $out .= '</form>';
6283
        } else {
6284
            if ($selected) {
6285
                $this->load_cache_conditions_paiements();
6286
                if (isset($this->cache_conditions_paiements[$selected])) {
6287
                    $label = $this->cache_conditions_paiements[$selected]['label'];
6288
6289
                    if (!empty($this->cache_conditions_paiements[$selected]['deposit_percent'])) {
6290
                        $label = str_replace('__DEPOSIT_PERCENT__', $deposit_percent > 0 ? $deposit_percent : $this->cache_conditions_paiements[$selected]['deposit_percent'], $label);
6291
                    }
6292
6293
                    $out .= $label;
6294
                } else {
6295
                    $langs->load('errors');
6296
                    $out .= $langs->trans('ErrorNotInDictionaryPaymentConditions');
6297
                }
6298
            } else {
6299
                $out .= '&nbsp;';
6300
            }
6301
        }
6302
6303
        if (empty($nooutput)) {
6304
            print $out;
6305
            return '';
6306
        }
6307
        return $out;
6308
    }
6309
6310
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6311
6312
    /**
6313
     *  Show a form to select a delivery delay
6314
     *
6315
     * @param int $page Page
6316
     * @param string $selected Id condition pre-selectionne
6317
     * @param string $htmlname Name of select html field
6318
     * @param int $addempty Add an empty entry
6319
     * @return  void
6320
     */
6321
    public function form_availability($page, $selected = '', $htmlname = 'availability', $addempty = 0)
6322
    {
6323
        // phpcs:enable
6324
        global $langs;
6325
        if ($htmlname != "none") {
6326
            print '<form method="post" action="' . $page . '">';
6327
            print '<input type="hidden" name="action" value="setavailability">';
6328
            print '<input type="hidden" name="token" value="' . newToken() . '">';
6329
            $this->selectAvailabilityDelay($selected, $htmlname, -1, $addempty);
6330
            print '<input type="submit" name="modify" class="button smallpaddingimp" value="' . $langs->trans("Modify") . '">';
6331
            print '<input type="submit" name="cancel" class="button smallpaddingimp" value="' . $langs->trans("Cancel") . '">';
6332
            print '</form>';
6333
        } else {
6334
            if ($selected) {
6335
                $this->load_cache_availability();
6336
                print $this->cache_availability[$selected]['label'];
6337
            } else {
6338
                print "&nbsp;";
6339
            }
6340
        }
6341
    }
6342
6343
    /**
6344
     * Return the list of type of delay available.
6345
     *
6346
     * @param string $selected Id du type de delais pre-selectionne
6347
     * @param string $htmlname Nom de la zone select
6348
     * @param string $filtertype To add a filter
6349
     * @param int $addempty Add empty entry
6350
     * @param string $morecss More CSS
6351
     * @return  void
6352
     */
6353
    public function selectAvailabilityDelay($selected = '', $htmlname = 'availid', $filtertype = '', $addempty = 0, $morecss = '')
6354
    {
6355
        global $langs, $user;
6356
6357
        $this->load_cache_availability();
6358
6359
        dol_syslog(__METHOD__ . " selected=" . $selected . ", htmlname=" . $htmlname, LOG_DEBUG);
6360
6361
        print '<select id="' . $htmlname . '" class="flat' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '">';
6362
        if ($addempty) {
6363
            print '<option value="0">&nbsp;</option>';
6364
        }
6365
        foreach ($this->cache_availability as $id => $arrayavailability) {
6366
            if ($selected == $id) {
6367
                print '<option value="' . $id . '" selected>';
6368
            } else {
6369
                print '<option value="' . $id . '">';
6370
            }
6371
            print dol_escape_htmltag($arrayavailability['label']);
6372
            print '</option>';
6373
        }
6374
        print '</select>';
6375
        if ($user->admin) {
6376
            print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
6377
        }
6378
        print ajax_combobox($htmlname);
6379
    }
6380
6381
6382
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6383
6384
    /**
6385
     *      Load int a cache property th elist of possible delivery delays.
6386
     *
6387
     * @return     int             Nb of lines loaded, <0 if KO
6388
     */
6389
    public function load_cache_availability()
6390
    {
6391
        // phpcs:enable
6392
        global $langs;
6393
6394
        $num = count($this->cache_availability);    // TODO Use $conf->cache['availability'] instead of $this->cache_availability
6395
        if ($num > 0) {
6396
            return 0; // Cache already loaded
6397
        }
6398
6399
        dol_syslog(__METHOD__, LOG_DEBUG);
6400
6401
        $langs->load('propal');
6402
6403
        $sql = "SELECT rowid, code, label, position";
6404
        $sql .= " FROM " . $this->db->prefix() . 'c_availability';
6405
        $sql .= " WHERE active > 0";
6406
6407
        $resql = $this->db->query($sql);
6408
        if ($resql) {
6409
            $num = $this->db->num_rows($resql);
6410
            $i = 0;
6411
            while ($i < $num) {
6412
                $obj = $this->db->fetch_object($resql);
6413
6414
                // Si traduction existe, on l'utilise, sinon on prend le libelle par default
6415
                $label = ($langs->trans("AvailabilityType" . $obj->code) != "AvailabilityType" . $obj->code ? $langs->trans("AvailabilityType" . $obj->code) : ($obj->label != '-' ? $obj->label : ''));
6416
                $this->cache_availability[$obj->rowid]['code'] = $obj->code;
6417
                $this->cache_availability[$obj->rowid]['label'] = $label;
6418
                $this->cache_availability[$obj->rowid]['position'] = $obj->position;
6419
                $i++;
6420
            }
6421
6422
            $this->cache_availability = dol_sort_array($this->cache_availability, 'position', 'asc', 0, 0, 1);
6423
6424
            return $num;
6425
        } else {
6426
            dol_print_error($this->db);
6427
            return -1;
6428
        }
6429
    }
6430
6431
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6432
6433
    /**
6434
     *  Output HTML form to select list of input reason (events that triggered an object creation, like after sending an emailing, making an advert, ...)
6435
     *  List found into table c_input_reason loaded by loadCacheInputReason
6436
     *
6437
     * @param string $page Page
6438
     * @param string $selected Id condition pre-selectionne
6439
     * @param string $htmlname Name of select html field
6440
     * @param int $addempty Add empty entry
6441
     * @return    void
6442
     */
6443
    public function formInputReason($page, $selected = '', $htmlname = 'demandreason', $addempty = 0)
6444
    {
6445
        global $langs;
6446
        if ($htmlname != "none") {
6447
            print '<form method="post" action="' . $page . '">';
6448
            print '<input type="hidden" name="action" value="setdemandreason">';
6449
            print '<input type="hidden" name="token" value="' . newToken() . '">';
6450
            $this->selectInputReason($selected, $htmlname, -1, $addempty);
6451
            print '<input type="submit" class="button smallpaddingimp" value="' . $langs->trans("Modify") . '">';
6452
            print '</form>';
6453
        } else {
6454
            if ($selected) {
6455
                $this->loadCacheInputReason();
6456
                foreach ($this->cache_demand_reason as $key => $val) {
6457
                    if ($val['id'] == $selected) {
6458
                        print $val['label'];
6459
                        break;
6460
                    }
6461
                }
6462
            } else {
6463
                print "&nbsp;";
6464
            }
6465
        }
6466
    }
6467
6468
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6469
6470
    /**
6471
     * Return list of input reason (events that triggered an object creation, like after sending an emailing, making an advert, ...)
6472
     * List found into table c_input_reason loaded by loadCacheInputReason
6473
     *
6474
     * @param string $selected Id or code of type origin to select by default
6475
     * @param string $htmlname Nom de la zone select
6476
     * @param string $exclude To exclude a code value (Example: SRC_PROP)
6477
     * @param int $addempty Add an empty entry
6478
     * @param string $morecss Add more css to the HTML select component
6479
     * @param int $notooltip Do not show the tooltip for admin
6480
     * @return  void
6481
     */
6482
    public function selectInputReason($selected = '', $htmlname = 'demandreasonid', $exclude = '', $addempty = 0, $morecss = '', $notooltip = 0)
6483
    {
6484
        global $langs, $user;
6485
6486
        $this->loadCacheInputReason();
6487
6488
        print '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" id="select_' . $htmlname . '" name="' . $htmlname . '">';
6489
        if ($addempty) {
6490
            print '<option value="0"' . (empty($selected) ? ' selected' : '') . '>&nbsp;</option>';
6491
        }
6492
        foreach ($this->cache_demand_reason as $id => $arraydemandreason) {
6493
            if ($arraydemandreason['code'] == $exclude) {
6494
                continue;
6495
            }
6496
6497
            if ($selected && ($selected == $arraydemandreason['id'] || $selected == $arraydemandreason['code'])) {
6498
                print '<option value="' . $arraydemandreason['id'] . '" selected>';
6499
            } else {
6500
                print '<option value="' . $arraydemandreason['id'] . '">';
6501
            }
6502
            $label = $arraydemandreason['label']; // Translation of label was already done into the ->loadCacheInputReason
6503
            print $langs->trans($label);
6504
            print '</option>';
6505
        }
6506
        print '</select>';
6507
        if ($user->admin && empty($notooltip)) {
6508
            print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
6509
        }
6510
        print ajax_combobox('select_' . $htmlname);
6511
    }
6512
6513
    /**
6514
     * Load into cache cache_demand_reason, array of input reasons
6515
     *
6516
     * @return     int             Nb of lines loaded, <0 if KO
6517
     */
6518
    public function loadCacheInputReason()
6519
    {
6520
        global $langs;
6521
6522
        $num = count($this->cache_demand_reason);    // TODO Use $conf->cache['input_reason'] instead of $this->cache_demand_reason
6523
        if ($num > 0) {
6524
            return 0; // Cache already loaded
6525
        }
6526
6527
        $sql = "SELECT rowid, code, label";
6528
        $sql .= " FROM " . $this->db->prefix() . 'c_input_reason';
6529
        $sql .= " WHERE active > 0";
6530
6531
        $resql = $this->db->query($sql);
6532
        if ($resql) {
6533
            $num = $this->db->num_rows($resql);
6534
            $i = 0;
6535
            $tmparray = array();
6536
            while ($i < $num) {
6537
                $obj = $this->db->fetch_object($resql);
6538
6539
                // Si traduction existe, on l'utilise, sinon on prend le libelle par default
6540
                $label = ($obj->label != '-' ? $obj->label : '');
6541
                if ($langs->trans("DemandReasonType" . $obj->code) != "DemandReasonType" . $obj->code) {
6542
                    $label = $langs->trans("DemandReasonType" . $obj->code); // So translation key DemandReasonTypeSRC_XXX will work
6543
                }
6544
                if ($langs->trans($obj->code) != $obj->code) {
6545
                    $label = $langs->trans($obj->code); // So translation key SRC_XXX will work
6546
                }
6547
6548
                $tmparray[$obj->rowid]['id'] = $obj->rowid;
6549
                $tmparray[$obj->rowid]['code'] = $obj->code;
6550
                $tmparray[$obj->rowid]['label'] = $label;
6551
                $i++;
6552
            }
6553
6554
            $this->cache_demand_reason = dol_sort_array($tmparray, 'label', 'asc', 0, 0, 1);
6555
6556
            unset($tmparray);
6557
            return $num;
6558
        } else {
6559
            dol_print_error($this->db);
6560
            return -1;
6561
        }
6562
    }
6563
6564
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6565
6566
    /**
6567
     *    Show a form + html select a date
6568
     *
6569
     * @param string $page Page
6570
     * @param string $selected Date preselected
6571
     * @param string $htmlname Html name of date input fields or 'none'
6572
     * @param int $displayhour Display hour selector
6573
     * @param int $displaymin Display minutes selector
6574
     * @param int $nooutput 1=No print output, return string
6575
     * @param string $type 'direct-debit' or 'bank-transfer'
6576
     * @return    string
6577
     * @see        selectDate()
6578
     */
6579
    public function form_date($page, $selected, $htmlname, $displayhour = 0, $displaymin = 0, $nooutput = 0, $type = '')
6580
    {
6581
        // phpcs:enable
6582
        global $langs;
6583
6584
        $ret = '';
6585
6586
        if ($htmlname != "none") {
6587
            $ret .= '<form method="POST" action="' . $page . '" name="form' . $htmlname . '">';
6588
            $ret .= '<input type="hidden" name="action" value="set' . $htmlname . '">';
6589
            $ret .= '<input type="hidden" name="token" value="' . newToken() . '">';
6590
            if ($type) {
6591
                $ret .= '<input type="hidden" name="type" value="' . dol_escape_htmltag($type) . '">';
6592
            }
6593
            $ret .= '<table class="nobordernopadding">';
6594
            $ret .= '<tr><td>';
6595
            $ret .= $this->selectDate($selected, $htmlname, $displayhour, $displaymin, 1, 'form' . $htmlname, 1, 0);
6596
            $ret .= '</td>';
6597
            $ret .= '<td class="left"><input type="submit" class="button smallpaddingimp" value="' . $langs->trans("Modify") . '"></td>';
6598
            $ret .= '</tr></table></form>';
6599
        } else {
6600
            if ($displayhour) {
6601
                $ret .= dol_print_date($selected, 'dayhour');
6602
            } else {
6603
                $ret .= dol_print_date($selected, 'day');
6604
            }
6605
        }
6606
6607
        if (empty($nooutput)) {
6608
            print $ret;
6609
        }
6610
        return $ret;
6611
    }
6612
6613
6614
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6615
6616
    /**
6617
     *  Show a select form to choose a user
6618
     *
6619
     * @param string $page Page
6620
     * @param string $selected Id of user preselected
6621
     * @param string $htmlname Name of input html field. If 'none', we just output the user link.
6622
     * @param int[] $exclude List of users id to exclude
6623
     * @param int[] $include List of users id to include
6624
     * @return    void
6625
     */
6626
    public function form_users($page, $selected = '', $htmlname = 'userid', $exclude = array(), $include = array())
6627
    {
6628
        // phpcs:enable
6629
        global $langs;
6630
6631
        if ($htmlname != "none") {
6632
            print '<form method="POST" action="' . $page . '" name="form' . $htmlname . '">';
6633
            print '<input type="hidden" name="action" value="set' . $htmlname . '">';
6634
            print '<input type="hidden" name="token" value="' . newToken() . '">';
6635
            print $this->select_dolusers($selected, $htmlname, 1, $exclude, 0, $include);
6636
            print '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
6637
            print '</form>';
6638
        } else {
6639
            if ($selected) {
6640
                $theuser = new User($this->db);
6641
                $theuser->fetch($selected);
6642
                print $theuser->getNomUrl(1);
6643
            } else {
6644
                print "&nbsp;";
6645
            }
6646
        }
6647
    }
6648
6649
6650
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6651
6652
    /**
6653
     *    Show form with payment mode
6654
     *
6655
     * @param string $page Page
6656
     * @param string $selected Id mode pre-selectionne
6657
     * @param string $htmlname Name of select html field
6658
     * @param string $filtertype To filter on field type in llx_c_paiement ('CRDT' or 'DBIT' or array('code'=>xx,'label'=>zz))
6659
     * @param int $active Active or not, -1 = all
6660
     * @param int $addempty 1=Add empty entry
6661
     * @param string $type Type ('direct-debit' or 'bank-transfer')
6662
     * @param int $nooutput 1=Return string, no output
6663
     * @return    string                    HTML output or ''
6664
     */
6665
    public function form_modes_reglement($page, $selected = '', $htmlname = 'mode_reglement_id', $filtertype = '', $active = 1, $addempty = 0, $type = '', $nooutput = 0)
6666
    {
6667
        // phpcs:enable
6668
        global $langs;
6669
6670
        $out = '';
6671
        if ($htmlname != "none") {
6672
            $out .= '<form method="POST" action="' . $page . '">';
6673
            $out .= '<input type="hidden" name="action" value="setmode">';
6674
            $out .= '<input type="hidden" name="token" value="' . newToken() . '">';
6675
            if ($type) {
6676
                $out .= '<input type="hidden" name="type" value="' . dol_escape_htmltag($type) . '">';
6677
            }
6678
            $out .= $this->select_types_paiements($selected, $htmlname, $filtertype, 0, $addempty, 0, 0, $active, '', 1);
6679
            $out .= '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
6680
            $out .= '</form>';
6681
        } else {
6682
            if ($selected) {
6683
                $this->load_cache_types_paiements();
6684
                $out .= $this->cache_types_paiements[$selected]['label'];
6685
            } else {
6686
                $out .= "&nbsp;";
6687
            }
6688
        }
6689
6690
        if ($nooutput) {
6691
            return $out;
6692
        } else {
6693
            print $out;
6694
        }
6695
        return '';
6696
    }
6697
6698
    /**
6699
     * Return list of payment methods
6700
     * Constant MAIN_DEFAULT_PAYMENT_TYPE_ID can used to set default value but scope is all application, probably not what you want.
6701
     *
6702
     * @param string $selected Id or code or preselected payment mode
6703
     * @param string $htmlname Name of select field
6704
     * @param string $filtertype To filter on field type in llx_c_paiement ('CRDT' or 'DBIT' or array('code'=>xx,'label'=>zz))
6705
     * @param int $format 0=id+label, 1=code+code, 2=code+label, 3=id+code
6706
     * @param int $empty 1=can be empty, 0 otherwise
6707
     * @param int $noadmininfo 0=Add admin info, 1=Disable admin info
6708
     * @param int $maxlength Max length of label
6709
     * @param int $active Active or not, -1 = all
6710
     * @param string $morecss Add more CSS on select tag
6711
     * @param int $nooutput 1=Return string, do not send to output
6712
     * @return  string|void             String for the HTML select component
6713
     */
6714
    public function select_types_paiements($selected = '', $htmlname = 'paiementtype', $filtertype = '', $format = 0, $empty = 1, $noadmininfo = 0, $maxlength = 0, $active = 1, $morecss = '', $nooutput = 0)
6715
    {
6716
        // phpcs:enable
6717
        global $langs, $user, $conf;
6718
6719
        $out = '';
6720
6721
        dol_syslog(__METHOD__ . " " . $selected . ", " . $htmlname . ", " . $filtertype . ", " . $format, LOG_DEBUG);
6722
6723
        $filterarray = array();
6724
        if ($filtertype == 'CRDT') {
6725
            $filterarray = array(0, 2, 3);
6726
        } elseif ($filtertype == 'DBIT') {
6727
            $filterarray = array(1, 2, 3);
6728
        } elseif ($filtertype != '' && $filtertype != '-1') {
6729
            $filterarray = explode(',', $filtertype);
6730
        }
6731
6732
        $this->load_cache_types_paiements();
6733
6734
        // Set default value if not already set by caller
6735
        if (empty($selected) && getDolGlobalString('MAIN_DEFAULT_PAYMENT_TYPE_ID')) {
6736
            dol_syslog(__METHOD__ . "Using deprecated option MAIN_DEFAULT_PAYMENT_TYPE_ID", LOG_NOTICE);
6737
            $selected = getDolGlobalString('MAIN_DEFAULT_PAYMENT_TYPE_ID');
6738
        }
6739
6740
        $out .= '<select id="select' . $htmlname . '" class="flat selectpaymenttypes' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '">';
6741
        if ($empty) {
6742
            $out .= '<option value="">&nbsp;</option>';
6743
        }
6744
        foreach ($this->cache_types_paiements as $id => $arraytypes) {
6745
            // If not good status
6746
            if ($active >= 0 && $arraytypes['active'] != $active) {
6747
                continue;
6748
            }
6749
6750
            // We skip of the user requested to filter on specific payment methods
6751
            if (count($filterarray) && !in_array($arraytypes['type'], $filterarray)) {
6752
                continue;
6753
            }
6754
6755
            // We discard empty lines if showempty is on because an empty line has already been output.
6756
            if ($empty && empty($arraytypes['code'])) {
6757
                continue;
6758
            }
6759
6760
            if ($format == 0) {
6761
                $out .= '<option value="' . $id . '"';
6762
            } elseif ($format == 1) {
6763
                $out .= '<option value="' . $arraytypes['code'] . '"';
6764
            } elseif ($format == 2) {
6765
                $out .= '<option value="' . $arraytypes['code'] . '"';
6766
            } elseif ($format == 3) {
6767
                $out .= '<option value="' . $id . '"';
6768
            }
6769
            // Print attribute selected or not
6770
            if ($format == 1 || $format == 2) {
6771
                if ($selected == $arraytypes['code']) {
6772
                    $out .= ' selected';
6773
                }
6774
            } else {
6775
                if ($selected == $id) {
6776
                    $out .= ' selected';
6777
                }
6778
            }
6779
            $out .= '>';
6780
            $value = '';
6781
            if ($format == 0) {
6782
                $value = ($maxlength ? dol_trunc($arraytypes['label'], $maxlength) : $arraytypes['label']);
6783
            } elseif ($format == 1) {
6784
                $value = $arraytypes['code'];
6785
            } elseif ($format == 2) {
6786
                $value = ($maxlength ? dol_trunc($arraytypes['label'], $maxlength) : $arraytypes['label']);
6787
            } elseif ($format == 3) {
6788
                $value = $arraytypes['code'];
6789
            }
6790
            $out .= $value ? $value : '&nbsp;';
6791
            $out .= '</option>';
6792
        }
6793
        $out .= '</select>';
6794
        if ($user->admin && !$noadmininfo) {
6795
            $out .= info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
6796
        }
6797
        $out .= ajax_combobox('select' . $htmlname);
6798
6799
        if (empty($nooutput)) {
6800
            print $out;
6801
        } else {
6802
            return $out;
6803
        }
6804
    }
6805
6806
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6807
6808
    /**
6809
     *      Charge dans cache la liste des types de paiements possibles
6810
     *
6811
     * @return     int                 Nb of lines loaded, <0 if KO
6812
     */
6813
    public function load_cache_types_paiements()
6814
    {
6815
        // phpcs:enable
6816
        global $langs;
6817
6818
        $num = count($this->cache_types_paiements);        // TODO Use $conf->cache['payment_mode'] instead of $this->cache_types_paiements
6819
        if ($num > 0) {
6820
            return $num; // Cache already loaded
6821
        }
6822
6823
        dol_syslog(__METHOD__, LOG_DEBUG);
6824
6825
        $this->cache_types_paiements = array();
6826
6827
        $sql = "SELECT id, code, libelle as label, type, active";
6828
        $sql .= " FROM " . $this->db->prefix() . "c_paiement";
6829
        $sql .= " WHERE entity IN (" . getEntity('c_paiement') . ")";
6830
6831
        $resql = $this->db->query($sql);
6832
        if ($resql) {
6833
            $num = $this->db->num_rows($resql);
6834
            $i = 0;
6835
            while ($i < $num) {
6836
                $obj = $this->db->fetch_object($resql);
6837
6838
                // Si traduction existe, on l'utilise, sinon on prend le libelle par default
6839
                $label = ($langs->transnoentitiesnoconv("PaymentTypeShort" . $obj->code) != "PaymentTypeShort" . $obj->code ? $langs->transnoentitiesnoconv("PaymentTypeShort" . $obj->code) : ($obj->label != '-' ? $obj->label : ''));
6840
                $this->cache_types_paiements[$obj->id]['id'] = $obj->id;
6841
                $this->cache_types_paiements[$obj->id]['code'] = $obj->code;
6842
                $this->cache_types_paiements[$obj->id]['label'] = $label;
6843
                $this->cache_types_paiements[$obj->id]['type'] = $obj->type;
6844
                $this->cache_types_paiements[$obj->id]['active'] = $obj->active;
6845
                $i++;
6846
            }
6847
6848
            $this->cache_types_paiements = dol_sort_array($this->cache_types_paiements, 'label', 'asc', 0, 0, 1);
6849
6850
            return $num;
6851
        } else {
6852
            dol_print_error($this->db);
6853
            return -1;
6854
        }
6855
    }
6856
6857
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6858
6859
    /**
6860
     *    Show form with transport mode
6861
     *
6862
     * @param string $page Page
6863
     * @param string $selected Id mode pre-select
6864
     * @param string $htmlname Name of select html field
6865
     * @param int $active Active or not, -1 = all
6866
     * @param int $addempty 1=Add empty entry
6867
     * @return    void
6868
     */
6869
    public function formSelectTransportMode($page, $selected = '', $htmlname = 'transport_mode_id', $active = 1, $addempty = 0)
6870
    {
6871
        global $langs;
6872
        if ($htmlname != "none") {
6873
            print '<form method="POST" action="' . $page . '">';
6874
            print '<input type="hidden" name="action" value="settransportmode">';
6875
            print '<input type="hidden" name="token" value="' . newToken() . '">';
6876
            $this->selectTransportMode($selected, $htmlname, 0, $addempty, 0, 0, $active);
6877
            print '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
6878
            print '</form>';
6879
        } else {
6880
            if ($selected) {
6881
                $this->load_cache_transport_mode();
6882
                print $this->cache_transport_mode[$selected]['label'];
6883
            } else {
6884
                print "&nbsp;";
6885
            }
6886
        }
6887
    }
6888
6889
6890
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6891
6892
    /**
6893
     *      Return list of transport mode for intracomm report
6894
     *
6895
     * @param string $selected Id of the transport mode preselected
6896
     * @param string $htmlname Name of the select field
6897
     * @param int $format 0=id+label, 1=code+code, 2=code+label, 3=id+code
6898
     * @param int $empty 1=can be empty, 0 else
6899
     * @param int $noadmininfo 0=Add admin info, 1=Disable admin info
6900
     * @param int $maxlength Max length of label
6901
     * @param int $active Active or not, -1 = all
6902
     * @param string $morecss Add more CSS on select tag
6903
     * @return    void
6904
     */
6905
    public function selectTransportMode($selected = '', $htmlname = 'transportmode', $format = 0, $empty = 1, $noadmininfo = 0, $maxlength = 0, $active = 1, $morecss = '')
6906
    {
6907
        global $langs, $user;
6908
6909
        dol_syslog(__METHOD__ . " " . $selected . ", " . $htmlname . ", " . $format, LOG_DEBUG);
6910
6911
        $this->load_cache_transport_mode();
6912
6913
        print '<select id="select' . $htmlname . '" class="flat selectmodetransport' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '">';
6914
        if ($empty) {
6915
            print '<option value="">&nbsp;</option>';
6916
        }
6917
        foreach ($this->cache_transport_mode as $id => $arraytypes) {
6918
            // If not good status
6919
            if ($active >= 0 && $arraytypes['active'] != $active) {
6920
                continue;
6921
            }
6922
6923
            // We discard empty line if showempty is on because an empty line has already been output.
6924
            if ($empty && empty($arraytypes['code'])) {
6925
                continue;
6926
            }
6927
6928
            if ($format == 0) {
6929
                print '<option value="' . $id . '"';
6930
            } elseif ($format == 1) {
6931
                print '<option value="' . $arraytypes['code'] . '"';
6932
            } elseif ($format == 2) {
6933
                print '<option value="' . $arraytypes['code'] . '"';
6934
            } elseif ($format == 3) {
6935
                print '<option value="' . $id . '"';
6936
            }
6937
            // If text is selected, we compare with code, else with id
6938
            if (preg_match('/[a-z]/i', $selected) && $selected == $arraytypes['code']) {
6939
                print ' selected';
6940
            } elseif ($selected == $id) {
6941
                print ' selected';
6942
            }
6943
            print '>';
6944
            $value = '';
6945
            if ($format == 0) {
6946
                $value = ($maxlength ? dol_trunc($arraytypes['label'], $maxlength) : $arraytypes['label']);
6947
            } elseif ($format == 1) {
6948
                $value = $arraytypes['code'];
6949
            } elseif ($format == 2) {
6950
                $value = ($maxlength ? dol_trunc($arraytypes['label'], $maxlength) : $arraytypes['label']);
6951
            } elseif ($format == 3) {
6952
                $value = $arraytypes['code'];
6953
            }
6954
            print $value ? $value : '&nbsp;';
6955
            print '</option>';
6956
        }
6957
        print '</select>';
6958
        if ($user->admin && !$noadmininfo) {
6959
            print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
6960
        }
6961
    }
6962
6963
6964
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6965
6966
    /**
6967
     *      Load in cache list of transport mode
6968
     *
6969
     * @return     int                 Nb of lines loaded, <0 if KO
6970
     */
6971
    public function load_cache_transport_mode()
6972
    {
6973
        // phpcs:enable
6974
        global $langs;
6975
6976
        $num = count($this->cache_transport_mode);        // TODO Use $conf->cache['payment_mode'] instead of $this->cache_transport_mode
6977
        if ($num > 0) {
6978
            return $num; // Cache already loaded
6979
        }
6980
6981
        dol_syslog(__METHOD__, LOG_DEBUG);
6982
6983
        $this->cache_transport_mode = array();
6984
6985
        $sql = "SELECT rowid, code, label, active";
6986
        $sql .= " FROM " . $this->db->prefix() . "c_transport_mode";
6987
        $sql .= " WHERE entity IN (" . getEntity('c_transport_mode') . ")";
6988
6989
        $resql = $this->db->query($sql);
6990
        if ($resql) {
6991
            $num = $this->db->num_rows($resql);
6992
            $i = 0;
6993
            while ($i < $num) {
6994
                $obj = $this->db->fetch_object($resql);
6995
6996
                // If traduction exist, we use it else we take the default label
6997
                $label = ($langs->transnoentitiesnoconv("PaymentTypeShort" . $obj->code) != "PaymentTypeShort" . $obj->code ? $langs->transnoentitiesnoconv("PaymentTypeShort" . $obj->code) : ($obj->label != '-' ? $obj->label : ''));
6998
                $this->cache_transport_mode[$obj->rowid]['rowid'] = $obj->rowid;
6999
                $this->cache_transport_mode[$obj->rowid]['code'] = $obj->code;
7000
                $this->cache_transport_mode[$obj->rowid]['label'] = $label;
7001
                $this->cache_transport_mode[$obj->rowid]['active'] = $obj->active;
7002
                $i++;
7003
            }
7004
7005
            $this->cache_transport_mode = dol_sort_array($this->cache_transport_mode, 'label', 'asc', 0, 0, 1);
7006
7007
            return $num;
7008
        } else {
7009
            dol_print_error($this->db);
7010
            return -1;
7011
        }
7012
    }
7013
7014
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
7015
7016
    /**
7017
     *    Show form with multicurrency code
7018
     *
7019
     * @param string $page Page
7020
     * @param string $selected code pre-selectionne
7021
     * @param string $htmlname Name of select html field
7022
     * @return    void
7023
     */
7024
    public function form_multicurrency_code($page, $selected = '', $htmlname = 'multicurrency_code')
7025
    {
7026
        // phpcs:enable
7027
        global $langs;
7028
        if ($htmlname != "none") {
7029
            print '<form method="POST" action="' . $page . '">';
7030
            print '<input type="hidden" name="action" value="setmulticurrencycode">';
7031
            print '<input type="hidden" name="token" value="' . newToken() . '">';
7032
            print $this->selectMultiCurrency($selected, $htmlname, 0);
7033
            print '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
7034
            print '</form>';
7035
        } else {
7036
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/company.lib.php';
7037
            print !empty($selected) ? currency_name($selected, 1) : '&nbsp;';
7038
        }
7039
    }
7040
7041
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
7042
7043
    /**
7044
     *    Return array of currencies in user language
7045
     *
7046
     * @param string $selected Preselected currency code
7047
     * @param string $htmlname Name of HTML select list
7048
     * @param integer $useempty 1=Add empty line
7049
     * @param string $filter Optional filters criteras (example: 'code <> x', ' in (1,3)')
7050
     * @param bool $excludeConfCurrency false = If company current currency not in table, we add it into list. Should always be available.
7051
     *                                  true = we are in currency_rate update , we don't want to see conf->currency in select
7052
     * @param string $morecss More css
7053
     * @return    string
7054
     */
7055
    public function selectMultiCurrency($selected = '', $htmlname = 'multicurrency_code', $useempty = 0, $filter = '', $excludeConfCurrency = false, $morecss = '')
7056
    {
7057
        global $conf, $langs;
7058
7059
        $langs->loadCacheCurrencies(''); // Load ->cache_currencies
7060
7061
        $TCurrency = array();
7062
7063
        $sql = "SELECT code FROM " . $this->db->prefix() . "multicurrency";
7064
        $sql .= " WHERE entity IN ('" . getEntity('mutlicurrency') . "')";
7065
        if ($filter) {
7066
            $sql .= " AND " . $filter;
7067
        }
7068
        $resql = $this->db->query($sql);
7069
        if ($resql) {
7070
            while ($obj = $this->db->fetch_object($resql)) {
7071
                $TCurrency[$obj->code] = $obj->code;
7072
            }
7073
        }
7074
7075
        $out = '';
7076
        $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" id="' . $htmlname . '">';
7077
        if ($useempty) {
7078
            $out .= '<option value="">&nbsp;</option>';
7079
        }
7080
        // If company current currency not in table, we add it into list. Should always be available.
7081
        if (!in_array($conf->currency, $TCurrency) && !$excludeConfCurrency) {
7082
            $TCurrency[$conf->currency] = $conf->currency;
7083
        }
7084
        if (count($TCurrency) > 0) {
7085
            foreach ($langs->cache_currencies as $code_iso => $currency) {
7086
                if (isset($TCurrency[$code_iso])) {
7087
                    if (!empty($selected) && $selected == $code_iso) {
7088
                        $out .= '<option value="' . $code_iso . '" selected="selected">';
7089
                    } else {
7090
                        $out .= '<option value="' . $code_iso . '">';
7091
                    }
7092
7093
                    $out .= $currency['label'];
7094
                    $out .= ' (' . $langs->getCurrencySymbol($code_iso) . ')';
7095
                    $out .= '</option>';
7096
                }
7097
            }
7098
        }
7099
7100
        $out .= '</select>';
7101
7102
        // Make select dynamic
7103
        include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
7104
        $out .= ajax_combobox($htmlname);
7105
7106
        return $out;
7107
    }
7108
7109
    /**
7110
     *    Show form with multicurrency rate
7111
     *
7112
     * @param string $page Page
7113
     * @param double $rate Current rate
7114
     * @param string $htmlname Name of select html field
7115
     * @param string $currency Currency code to explain the rate
7116
     * @return    void
7117
     */
7118
    public function form_multicurrency_rate($page, $rate = 0.0, $htmlname = 'multicurrency_tx', $currency = '')
7119
    {
7120
        // phpcs:enable
7121
        global $langs, $mysoc, $conf;
7122
7123
        if ($htmlname != "none") {
7124
            print '<form method="POST" action="' . $page . '">';
7125
            print '<input type="hidden" name="action" value="setmulticurrencyrate">';
7126
            print '<input type="hidden" name="token" value="' . newToken() . '">';
7127
            print '<input type="text" class="maxwidth100" name="' . $htmlname . '" value="' . (!empty($rate) ? price(price2num($rate, 'CU')) : 1) . '" /> ';
7128
            print '<select name="calculation_mode">';
7129
            print '<option value="1">Change ' . $langs->trans("PriceUHT") . ' of lines</option>';
7130
            print '<option value="2">Change ' . $langs->trans("PriceUHTCurrency") . ' of lines</option>';
7131
            print '</select> ';
7132
            print '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
7133
            print '</form>';
7134
        } else {
7135
            if (!empty($rate)) {
7136
                print price($rate, 1, $langs, 0, 0);
7137
                if ($currency && $rate != 1) {
7138
                    print ' &nbsp; (' . price($rate, 1, $langs, 0, 0) . ' ' . $currency . ' = 1 ' . $conf->currency . ')';
7139
                }
7140
            } else {
7141
                print 1;
7142
            }
7143
        }
7144
    }
7145
7146
    /**
7147
     *    Show a select box with available absolute discounts
7148
     *
7149
     * @param string $page Page URL where form is shown
7150
     * @param int $selected Value preselected
7151
     * @param string $htmlname Name of SELECT component. If 'none', not changeable. Example 'remise_id'.
7152
     * @param int $socid Third party id
7153
     * @param float $amount Total amount available
7154
     * @param string $filter SQL filter on discounts
7155
     * @param int $maxvalue Max value for lines that can be selected
7156
     * @param string $more More string to add
7157
     * @param int $hidelist 1=Hide list
7158
     * @param int $discount_type 0 => customer discount, 1 => supplier discount
7159
     * @return    void
7160
     */
7161
    public function form_remise_dispo($page, $selected, $htmlname, $socid, $amount, $filter = '', $maxvalue = 0, $more = '', $hidelist = 0, $discount_type = 0)
7162
    {
7163
        // phpcs:enable
7164
        global $conf, $langs;
7165
        if ($htmlname != "none") {
7166
            print '<form method="post" action="' . $page . '">';
7167
            print '<input type="hidden" name="action" value="setabsolutediscount">';
7168
            print '<input type="hidden" name="token" value="' . newToken() . '">';
7169
            print '<div class="inline-block">';
7170
            if (!empty($discount_type)) {
7171
                if (getDolGlobalString('FACTURE_SUPPLIER_DEPOSITS_ARE_JUST_PAYMENTS')) {
7172
                    if (!$filter || $filter == "fk_invoice_supplier_source IS NULL") {
7173
                        $translationKey = 'HasAbsoluteDiscountFromSupplier'; // If we want deposit to be subtracted to payments only and not to total of final invoice
7174
                    } else {
7175
                        $translationKey = 'HasCreditNoteFromSupplier';
7176
                    }
7177
                } else {
7178
                    if (!$filter || $filter == "fk_invoice_supplier_source IS NULL OR (description LIKE '(DEPOSIT)%' AND description NOT LIKE '(EXCESS PAID)%')") {
7179
                        $translationKey = 'HasAbsoluteDiscountFromSupplier';
7180
                    } else {
7181
                        $translationKey = 'HasCreditNoteFromSupplier';
7182
                    }
7183
                }
7184
            } else {
7185
                if (getDolGlobalString('FACTURE_DEPOSITS_ARE_JUST_PAYMENTS')) {
7186
                    if (!$filter || $filter == "fk_facture_source IS NULL") {
7187
                        $translationKey = 'CompanyHasAbsoluteDiscount'; // If we want deposit to be subtracted to payments only and not to total of final invoice
7188
                    } else {
7189
                        $translationKey = 'CompanyHasCreditNote';
7190
                    }
7191
                } else {
7192
                    if (!$filter || $filter == "fk_facture_source IS NULL OR (description LIKE '(DEPOSIT)%' AND description NOT LIKE '(EXCESS RECEIVED)%')") {
7193
                        $translationKey = 'CompanyHasAbsoluteDiscount';
7194
                    } else {
7195
                        $translationKey = 'CompanyHasCreditNote';
7196
                    }
7197
                }
7198
            }
7199
            print $langs->trans($translationKey, price($amount, 0, $langs, 0, 0, -1, $conf->currency));
7200
            if (empty($hidelist)) {
7201
                print ' ';
7202
            }
7203
            print '</div>';
7204
            if (empty($hidelist)) {
7205
                print '<div class="inline-block" style="padding-right: 10px">';
7206
                $newfilter = 'discount_type=' . intval($discount_type);
7207
                if (!empty($discount_type)) {
7208
                    $newfilter .= ' AND fk_invoice_supplier IS NULL AND fk_invoice_supplier_line IS NULL'; // Supplier discounts available
7209
                } else {
7210
                    $newfilter .= ' AND fk_facture IS NULL AND fk_facture_line IS NULL'; // Customer discounts available
7211
                }
7212
                if ($filter) {
7213
                    $newfilter .= ' AND (' . $filter . ')';
7214
                }
7215
                // output the combo of discounts
7216
                $nbqualifiedlines = $this->select_remises($selected, $htmlname, $newfilter, $socid, $maxvalue);
7217
                if ($nbqualifiedlines > 0) {
7218
                    print ' &nbsp; <input type="submit" class="button smallpaddingimp" value="' . dol_escape_htmltag($langs->trans("UseLine")) . '"';
7219
                    if (!empty($discount_type) && $filter && $filter != "fk_invoice_supplier_source IS NULL OR (description LIKE '(DEPOSIT)%' AND description NOT LIKE '(EXCESS PAID)%')") {
7220
                        print ' title="' . $langs->trans("UseCreditNoteInInvoicePayment") . '"';
7221
                    }
7222
                    if (empty($discount_type) && $filter && $filter != "fk_facture_source IS NULL OR (description LIKE '(DEPOSIT)%' AND description NOT LIKE '(EXCESS RECEIVED)%')") {
7223
                        print ' title="' . $langs->trans("UseCreditNoteInInvoicePayment") . '"';
7224
                    }
7225
7226
                    print '>';
7227
                }
7228
                print '</div>';
7229
            }
7230
            if ($more) {
7231
                print '<div class="inline-block">';
7232
                print $more;
7233
                print '</div>';
7234
            }
7235
            print '</form>';
7236
        } else {
7237
            if ($selected) {
7238
                print $selected;
7239
            } else {
7240
                print "0";
7241
            }
7242
        }
7243
    }
7244
7245
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
7246
7247
    /**
7248
     *  Return HTML combo list of absolute discounts
7249
     *
7250
     * @param string $selected Id Fixed reduction preselected
7251
     * @param string $htmlname Name of the form field
7252
     * @param string $filter Optional filter critreria
7253
     * @param int $socid Id of thirdparty
7254
     * @param int $maxvalue Max value for lines that can be selected
7255
     * @return  int                 Return number of qualifed lines in list
7256
     */
7257
    public function select_remises($selected, $htmlname, $filter, $socid, $maxvalue = 0)
7258
    {
7259
        // phpcs:enable
7260
        global $langs, $conf;
7261
7262
        // On recherche les remises
7263
        $sql = "SELECT re.rowid, re.amount_ht, re.amount_tva, re.amount_ttc,";
7264
        $sql .= " re.description, re.fk_facture_source";
7265
        $sql .= " FROM " . $this->db->prefix() . "societe_remise_except as re";
7266
        $sql .= " WHERE re.fk_soc = " . (int)$socid;
7267
        $sql .= " AND re.entity = " . $conf->entity;
7268
        if ($filter) {
7269
            $sql .= " AND " . $filter;
7270
        }
7271
        $sql .= " ORDER BY re.description ASC";
7272
7273
        dol_syslog(get_only_class($this) . "::select_remises", LOG_DEBUG);
7274
        $resql = $this->db->query($sql);
7275
        if ($resql) {
7276
            print '<select id="select_' . $htmlname . '" class="flat maxwidthonsmartphone" name="' . $htmlname . '">';
7277
            $num = $this->db->num_rows($resql);
7278
7279
            $qualifiedlines = $num;
7280
7281
            $i = 0;
7282
            if ($num) {
7283
                print '<option value="0">&nbsp;</option>';
7284
                while ($i < $num) {
7285
                    $obj = $this->db->fetch_object($resql);
7286
                    $desc = dol_trunc($obj->description, 40);
7287
                    if (preg_match('/\(CREDIT_NOTE\)/', $desc)) {
7288
                        $desc = preg_replace('/\(CREDIT_NOTE\)/', $langs->trans("CreditNote"), $desc);
7289
                    }
7290
                    if (preg_match('/\(DEPOSIT\)/', $desc)) {
7291
                        $desc = preg_replace('/\(DEPOSIT\)/', $langs->trans("Deposit"), $desc);
7292
                    }
7293
                    if (preg_match('/\(EXCESS RECEIVED\)/', $desc)) {
7294
                        $desc = preg_replace('/\(EXCESS RECEIVED\)/', $langs->trans("ExcessReceived"), $desc);
7295
                    }
7296
                    if (preg_match('/\(EXCESS PAID\)/', $desc)) {
7297
                        $desc = preg_replace('/\(EXCESS PAID\)/', $langs->trans("ExcessPaid"), $desc);
7298
                    }
7299
7300
                    $selectstring = '';
7301
                    if ($selected > 0 && $selected == $obj->rowid) {
7302
                        $selectstring = ' selected';
7303
                    }
7304
7305
                    $disabled = '';
7306
                    if ($maxvalue > 0 && $obj->amount_ttc > $maxvalue) {
7307
                        $qualifiedlines--;
7308
                        $disabled = ' disabled';
7309
                    }
7310
7311
                    if (getDolGlobalString('MAIN_SHOW_FACNUMBER_IN_DISCOUNT_LIST') && !empty($obj->fk_facture_source)) {
7312
                        $tmpfac = new Facture($this->db);
7313
                        if ($tmpfac->fetch($obj->fk_facture_source) > 0) {
7314
                            $desc = $desc . ' - ' . $tmpfac->ref;
7315
                        }
7316
                    }
7317
7318
                    print '<option value="' . $obj->rowid . '"' . $selectstring . $disabled . '>' . $desc . ' (' . price($obj->amount_ht) . ' ' . $langs->trans("HT") . ' - ' . price($obj->amount_ttc) . ' ' . $langs->trans("TTC") . ')</option>';
7319
                    $i++;
7320
                }
7321
            }
7322
            print '</select>';
7323
            print ajax_combobox('select_' . $htmlname);
7324
7325
            return $qualifiedlines;
7326
        } else {
7327
            dol_print_error($this->db);
7328
            return -1;
7329
        }
7330
    }
7331
7332
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
7333
7334
    /**
7335
     *  Show forms to select a contact
7336
     *
7337
     * @param string $page Page
7338
     * @param Societe $societe Filter on third party
7339
     * @param string $selected Id contact pre-selectionne
7340
     * @param string $htmlname Name of HTML select. If 'none', we just show contact link.
7341
     * @return    void
7342
     */
7343
    public function form_contacts($page, $societe, $selected = '', $htmlname = 'contactid')
7344
    {
7345
        // phpcs:enable
7346
        global $langs;
7347
7348
        if ($htmlname != "none") {
7349
            print '<form method="post" action="' . $page . '">';
7350
            print '<input type="hidden" name="action" value="set_contact">';
7351
            print '<input type="hidden" name="token" value="' . newToken() . '">';
7352
            print '<table class="nobordernopadding">';
7353
            print '<tr><td>';
7354
            print $this->selectcontacts($societe->id, $selected, $htmlname);
7355
            $num = $this->num;
7356
            if ($num == 0) {
7357
                $addcontact = (getDolGlobalString('SOCIETE_ADDRESSES_MANAGEMENT') ? $langs->trans("AddContact") : $langs->trans("AddContactAddress"));
7358
                print '<a href="' . constant('BASE_URL') . '/contact/card.php?socid=' . $societe->id . '&amp;action=create&amp;backtoreferer=1">' . $addcontact . '</a>';
7359
            }
7360
            print '</td>';
7361
            print '<td class="left"><input type="submit" class="button smallpaddingimp" value="' . $langs->trans("Modify") . '"></td>';
7362
            print '</tr></table></form>';
7363
        } else {
7364
            if ($selected) {
7365
                $contact = new Contact($this->db);
7366
                $contact->fetch($selected);
7367
                print $contact->getFullName($langs);
7368
            } else {
7369
                print "&nbsp;";
7370
            }
7371
        }
7372
    }
7373
7374
7375
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
7376
7377
    /**
7378
     *  Output html select to select thirdparty
7379
     *
7380
     * @param string $page Page
7381
     * @param string $selected Id preselected
7382
     * @param string $htmlname Name of HTML select
7383
     * @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.
7384
     * @param string|int<0,1> $showempty Add an empty field (Can be '1' or text key to use on empty line like 'SelectThirdParty')
7385
     * @param int<0,1> $showtype Show third party type in combolist (customer, prospect or supplier)
7386
     * @param int<0,1> $forcecombo Force to use combo box
7387
     * @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')))
7388
     * @param int $nooutput No print output. Return it only.
7389
     * @param int[] $excludeids Exclude IDs from the select combo
7390
     * @param string $textifnothirdparty Text to show if no thirdparty
7391
     * @return    string                        HTML output or ''
7392
     */
7393
    public function form_thirdparty($page, $selected = '', $htmlname = 'socid', $filter = '', $showempty = 0, $showtype = 0, $forcecombo = 0, $events = array(), $nooutput = 0, $excludeids = array(), $textifnothirdparty = '')
7394
    {
7395
        // phpcs:enable
7396
        global $langs;
7397
7398
        $out = '';
7399
        if ($htmlname != "none") {
7400
            $out .= '<form method="post" action="' . $page . '">';
7401
            $out .= '<input type="hidden" name="action" value="set_thirdparty">';
7402
            $out .= '<input type="hidden" name="token" value="' . newToken() . '">';
7403
            $out .= $this->select_company($selected, $htmlname, $filter, $showempty, $showtype, $forcecombo, $events, 0, 'minwidth100', '', '', 1, array(), false, $excludeids);
7404
            $out .= '<input type="submit" class="button smallpaddingimp valignmiddle" value="' . $langs->trans("Modify") . '">';
7405
            $out .= '</form>';
7406
        } else {
7407
            if ($selected) {
7408
                $soc = new Societe($this->db);
7409
                $soc->fetch($selected);
7410
                $out .= $soc->getNomUrl(0, '');
7411
            } else {
7412
                $out .= '<span class="opacitymedium">' . $textifnothirdparty . '</span>';
7413
            }
7414
        }
7415
7416
        if ($nooutput) {
7417
            return $out;
7418
        } else {
7419
            print $out;
7420
        }
7421
7422
        return '';
7423
    }
7424
7425
    /**
7426
     *  Output html form to select a third party
7427
     *  This call select_thirdparty_list() or ajax depending on setup. This component is not able to support multiple select.
7428
     *
7429
     * @param int|string $selected Preselected ID
7430
     * @param string $htmlname Name of field in form
7431
     * @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.
7432
     * @param string|int<1,1> $showempty Add an empty field (Can be '1' or text key to use on empty line like 'SelectThirdParty')
7433
     * @param int $showtype Show third party type in combolist (customer, prospect or supplier)
7434
     * @param int $forcecombo Force to load all values and output a standard combobox (with no beautification)
7435
     * @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')))
7436
     * @param int $limit Maximum number of elements
7437
     * @param string $morecss Add more css styles to the SELECT component
7438
     * @param string $moreparam Add more parameters onto the select tag. For example 'style="width: 95%"' to avoid select2 component to go over parent container
7439
     * @param string $selected_input_value Value of preselected input text (for use with ajax)
7440
     * @param int<0,3> $hidelabel Hide label (0=no, 1=yes, 2=show search icon (before) and placeholder, 3 search icon after)
7441
     * @param array<string,string|string[]> $ajaxoptions Options for ajax_autocompleter
7442
     * @param bool $multiple add [] in the name of element and add 'multiple' attribute (not working with ajax_autocompleter)
7443
     * @param string[] $excludeids Exclude IDs from the select combo
7444
     * @param int<0,1> $showcode Show code
7445
     * @return string                               HTML string with select box for thirdparty.
7446
     */
7447
    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)
7448
    {
7449
        // phpcs:enable
7450
        global $conf, $langs;
7451
7452
        $out = '';
7453
7454
        if (!empty($conf->use_javascript_ajax) && getDolGlobalString('COMPANY_USE_SEARCH_TO_SELECT') && !$forcecombo) {
7455
            if (is_null($ajaxoptions)) {
7456
                $ajaxoptions = array();
7457
            }
7458
7459
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/ajax.lib.php';
7460
7461
            // No immediate load of all database
7462
            $placeholder = '';
7463
            if ($selected && empty($selected_input_value)) {
7464
                $societetmp = new Societe($this->db);
7465
                $societetmp->fetch($selected);
7466
                $selected_input_value = $societetmp->name;
7467
                unset($societetmp);
7468
            }
7469
7470
            // mode 1
7471
            $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)) : '');
7472
7473
            $out .= '<!-- force css to be higher than dialog popup --><style type="text/css">.ui-autocomplete { z-index: 1010; }</style>';
7474
            if (empty($hidelabel)) {
7475
                print $langs->trans("RefOrLabel") . ' : ';
7476
            } elseif ($hidelabel > 1) {
7477
                $placeholder = $langs->trans("RefOrLabel");
7478
                if ($hidelabel == 2) {
7479
                    $out .= img_picto($langs->trans("Search"), 'search');
7480
                }
7481
            }
7482
            $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' : '') . ' />';
7483
            if ($hidelabel == 3) {
7484
                $out .= img_picto($langs->trans("Search"), 'search');
7485
            }
7486
7487
            $out .= ajax_event($htmlname, $events);
7488
7489
            $out .= ajax_autocompleter($selected, $htmlname, constant('BASE_URL') . '/societe/ajax/company.php', $urloption, getDolGlobalString('COMPANY_USE_SEARCH_TO_SELECT'), 0, $ajaxoptions);
7490
        } else {
7491
            // Immediate load of all database
7492
            $out .= $this->select_thirdparty_list($selected, $htmlname, $filter, $showempty, $showtype, $forcecombo, $events, '', 0, $limit, $morecss, $moreparam, $multiple, $excludeids, $showcode);
7493
        }
7494
7495
        return $out;
7496
    }
7497
7498
    /**
7499
     *  Output html form to select a third party.
7500
     *  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.
7501
     *
7502
     * @param string $selected Preselected type
7503
     * @param string $htmlname Name of field in form
7504
     * @param string $filter Optional filters criteras. WARNING: To avoid SQL injection, only few chars [.a-z0-9 =<>] are allowed here, example: 's.rowid <> x'
7505
     *                                          If you need parenthesis, use the Universal Filter Syntax, example: '(s.client:in:1,3)'
7506
     *                                          Do not use a filter coming from input of users.
7507
     * @param string|int<0,1> $showempty Add an empty field (Can be '1' or text to use on empty line like 'SelectThirdParty')
7508
     * @param int<0,1> $showtype Show third party type in combolist (customer, prospect or supplier)
7509
     * @param int $forcecombo Force to use standard HTML select component without beautification
7510
     * @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')))
7511
     * @param string $filterkey Filter on key value
7512
     * @param int<0,1> $outputmode 0=HTML select string, 1=Array
7513
     * @param int $limit Limit number of answers
7514
     * @param string $morecss Add more css styles to the SELECT component
7515
     * @param string $moreparam Add more parameters onto the select tag. For example 'style="width: 95%"' to avoid select2 component to go over parent container
7516
     * @param bool $multiple add [] in the name of element and add 'multiple' attribute
7517
     * @param string[] $excludeids Exclude IDs from the select combo
7518
     * @param int<0,1> $showcode Show code in list
7519
     * @return array<int,array{key:int,value:string,label:string,labelhtml:string}>|string              HTML string with
7520
     * @see select_company()
7521
     */
7522
    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)
7523
    {
7524
        // phpcs:enable
7525
        global $user, $langs;
7526
        global $hookmanager;
7527
7528
        $out = '';
7529
        $num = 0;
7530
        $outarray = array();
7531
7532
        if ($selected === '') {
7533
            $selected = array();
7534
        } elseif (!is_array($selected)) {
7535
            $selected = array($selected);
7536
        }
7537
7538
        // Clean $filter that may contains sql conditions so sql code
7539
        if (Filters::testSqlAndScriptInject($filter, 3) > 0) {
7540
            $filter = '';
7541
            return 'SQLInjectionTryDetected';
7542
        }
7543
7544
        if ($filter != '') {    // If a filter was provided
7545
            if (preg_match('/[\(\)]/', $filter)) {
7546
                // If there is one parenthesis inside the criteria, we assume it is an Universal Filter Syntax.
7547
                $errormsg = '';
7548
                $filter = forgeSQLFromUniversalSearchCriteria($filter, $errormsg, 1);
7549
7550
                // Redo clean $filter that may contains sql conditions so sql code
7551
                if (Filters::testSqlAndScriptInject($filter, 3) > 0) {
7552
                    $filter = '';
7553
                    return 'SQLInjectionTryDetected';
7554
                }
7555
            } else {
7556
                // If not, we do nothing. We already know that there is no parenthesis
7557
                // TODO Disallow this case in a future.
7558
                dol_syslog("Warning, select_thirdparty_list was called with a filter criteria not using the Universal Search Syntax.", LOG_WARNING);
7559
            }
7560
        }
7561
7562
        // We search companies
7563
        $sql = "SELECT s.rowid, s.nom as name, s.name_alias, s.tva_intra, s.client, s.fournisseur, s.code_client, s.code_fournisseur";
7564
        if (getDolGlobalString('COMPANY_SHOW_ADDRESS_SELECTLIST')) {
7565
            $sql .= ", s.address, s.zip, s.town";
7566
            $sql .= ", dictp.code as country_code";
7567
        }
7568
        $sql .= " FROM " . $this->db->prefix() . "societe as s";
7569
        if (getDolGlobalString('COMPANY_SHOW_ADDRESS_SELECTLIST')) {
7570
            $sql .= " LEFT JOIN " . $this->db->prefix() . "c_country as dictp ON dictp.rowid = s.fk_pays";
7571
        }
7572
        if (!$user->hasRight('societe', 'client', 'voir')) {
7573
            $sql .= ", " . $this->db->prefix() . "societe_commerciaux as sc";
7574
        }
7575
        $sql .= " WHERE s.entity IN (" . getEntity('societe') . ")";
7576
        if (!empty($user->socid)) {
7577
            $sql .= " AND s.rowid = " . ((int)$user->socid);
7578
        }
7579
        if ($filter) {
7580
            // $filter is safe because, if it contains '(' or ')', it has been sanitized by testSqlAndScriptInject() and forgeSQLFromUniversalSearchCriteria()
7581
            // if not, by testSqlAndScriptInject() only.
7582
            $sql .= " AND (" . $filter . ")";
7583
        }
7584
        if (!$user->hasRight('societe', 'client', 'voir')) {
7585
            $sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = " . ((int)$user->id);
7586
        }
7587
        if (getDolGlobalString('COMPANY_HIDE_INACTIVE_IN_COMBOBOX')) {
7588
            $sql .= " AND s.status <> 0";
7589
        }
7590
        if (!empty($excludeids)) {
7591
            $sql .= " AND s.rowid NOT IN (" . $this->db->sanitize(implode(',', $excludeids)) . ")";
7592
        }
7593
        // Add where from hooks
7594
        $parameters = array();
7595
        $reshook = $hookmanager->executeHooks('selectThirdpartyListWhere', $parameters); // Note that $action and $object may have been modified by hook
7596
        $sql .= $hookmanager->resPrint;
7597
        // Add criteria
7598
        if ($filterkey && $filterkey != '') {
7599
            $sql .= " AND (";
7600
            $prefix = !getDolGlobalString('COMPANY_DONOTSEARCH_ANYWHERE') ? '%' : ''; // Can use index if COMPANY_DONOTSEARCH_ANYWHERE is on
7601
            // For natural search
7602
            $search_crit = explode(' ', $filterkey);
7603
            $i = 0;
7604
            if (count($search_crit) > 1) {
7605
                $sql .= "(";
7606
            }
7607
            foreach ($search_crit as $crit) {
7608
                if ($i > 0) {
7609
                    $sql .= " AND ";
7610
                }
7611
                $sql .= "(s.nom LIKE '" . $this->db->escape($prefix . $crit) . "%')";
7612
                $i++;
7613
            }
7614
            if (count($search_crit) > 1) {
7615
                $sql .= ")";
7616
            }
7617
            if (isModEnabled('barcode')) {
7618
                $sql .= " OR s.barcode LIKE '" . $this->db->escape($prefix . $filterkey) . "%'";
7619
            }
7620
            $sql .= " OR s.code_client LIKE '" . $this->db->escape($prefix . $filterkey) . "%' OR s.code_fournisseur LIKE '" . $this->db->escape($prefix . $filterkey) . "%'";
7621
            $sql .= " OR s.name_alias LIKE '" . $this->db->escape($prefix . $filterkey) . "%' OR s.tva_intra LIKE '" . $this->db->escape($prefix . $filterkey) . "%'";
7622
            $sql .= ")";
7623
        }
7624
        $sql .= $this->db->order("nom", "ASC");
7625
        $sql .= $this->db->plimit($limit, 0);
7626
7627
        // Build output string
7628
        dol_syslog(get_only_class($this) . "::select_thirdparty_list", LOG_DEBUG);
7629
        $resql = $this->db->query($sql);
7630
        if ($resql) {
7631
            // Construct $out and $outarray
7632
            $out .= '<select id="' . $htmlname . '" class="flat' . ($morecss ? ' ' . $morecss : '') . '"' . ($moreparam ? ' ' . $moreparam : '') . ' name="' . $htmlname . ($multiple ? '[]' : '') . '" ' . ($multiple ? 'multiple' : '') . '>' . "\n";
7633
7634
            $textifempty = (($showempty && !is_numeric($showempty)) ? $langs->trans($showempty) : '');
7635
            if (getDolGlobalString('COMPANY_USE_SEARCH_TO_SELECT')) {
7636
                // Do not use textifempty = ' ' or '&nbsp;' here, or search on key will search on ' key'.
7637
                //if (!empty($conf->use_javascript_ajax) || $forcecombo) $textifempty='';
7638
                if ($showempty && !is_numeric($showempty)) {
7639
                    $textifempty = $langs->trans($showempty);
7640
                } else {
7641
                    $textifempty .= $langs->trans("All");
7642
                }
7643
            }
7644
            if ($showempty) {
7645
                $out .= '<option value="-1" data-html="' . dol_escape_htmltag('<span class="opacitymedium">' . ($textifempty ? $textifempty : '&nbsp;') . '</span>') . '">' . $textifempty . '</option>' . "\n";
7646
            }
7647
7648
            $companytemp = new Societe($this->db);
7649
7650
            $num = $this->db->num_rows($resql);
7651
            $i = 0;
7652
            if ($num) {
7653
                while ($i < $num) {
7654
                    $obj = $this->db->fetch_object($resql);
7655
                    $label = '';
7656
                    if ($showcode || getDolGlobalString('SOCIETE_ADD_REF_IN_LIST')) {
7657
                        if (($obj->client) && (!empty($obj->code_client))) {
7658
                            $label = $obj->code_client . ' - ';
7659
                        }
7660
                        if (($obj->fournisseur) && (!empty($obj->code_fournisseur))) {
7661
                            $label .= $obj->code_fournisseur . ' - ';
7662
                        }
7663
                        $label .= ' ' . $obj->name;
7664
                    } else {
7665
                        $label = $obj->name;
7666
                    }
7667
7668
                    if (!empty($obj->name_alias)) {
7669
                        $label .= ' (' . $obj->name_alias . ')';
7670
                    }
7671
7672
                    if (getDolGlobalString('SOCIETE_SHOW_VAT_IN_LIST') && !empty($obj->tva_intra)) {
7673
                        $label .= ' - ' . $obj->tva_intra;
7674
                    }
7675
7676
                    $labelhtml = $label;
7677
7678
                    if ($showtype) {
7679
                        $companytemp->id = $obj->rowid;
7680
                        $companytemp->client = $obj->client;
7681
                        $companytemp->fournisseur = $obj->fournisseur;
7682
                        $tmptype = $companytemp->getTypeUrl(1, '', 0, 'span');
7683
                        if ($tmptype) {
7684
                            $labelhtml .= ' ' . $tmptype;
7685
                        }
7686
7687
                        if ($obj->client || $obj->fournisseur) {
7688
                            $label .= ' (';
7689
                        }
7690
                        if ($obj->client == 1 || $obj->client == 3) {
7691
                            $label .= $langs->trans("Customer");
7692
                        }
7693
                        if ($obj->client == 2 || $obj->client == 3) {
7694
                            $label .= ($obj->client == 3 ? ', ' : '') . $langs->trans("Prospect");
7695
                        }
7696
                        if ($obj->fournisseur) {
7697
                            $label .= ($obj->client ? ', ' : '') . $langs->trans("Supplier");
7698
                        }
7699
                        if ($obj->client || $obj->fournisseur) {
7700
                            $label .= ')';
7701
                        }
7702
                    }
7703
7704
                    if (getDolGlobalString('COMPANY_SHOW_ADDRESS_SELECTLIST')) {
7705
                        $s = ($obj->address ? ' - ' . $obj->address : '') . ($obj->zip ? ' - ' . $obj->zip : '') . ($obj->town ? ' ' . $obj->town : '');
7706
                        if (!empty($obj->country_code)) {
7707
                            $s .= ', ' . $langs->trans('Country' . $obj->country_code);
7708
                        }
7709
                        $label .= $s;
7710
                        $labelhtml .= $s;
7711
                    }
7712
7713
                    if (empty($outputmode)) {
7714
                        if (in_array($obj->rowid, $selected)) {
7715
                            $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>';
7716
                        } else {
7717
                            $out .= '<option value="' . $obj->rowid . '" data-html="' . dol_escape_htmltag($labelhtml, 0, 0, '', 0, 1) . '">' . dol_escape_htmltag($label, 0, 0, '', 0, 1) . '</option>';
7718
                        }
7719
                    } else {
7720
                        array_push($outarray, array('key' => $obj->rowid, 'value' => $label, 'label' => $label, 'labelhtml' => $labelhtml));
7721
                    }
7722
7723
                    $i++;
7724
                    if (($i % 10) == 0) {
7725
                        $out .= "\n";
7726
                    }
7727
                }
7728
            }
7729
            $out .= '</select>' . "\n";
7730
            if (!$forcecombo) {
7731
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
7732
                $out .= ajax_combobox($htmlname, $events, getDolGlobalInt("COMPANY_USE_SEARCH_TO_SELECT"));
7733
            }
7734
        } else {
7735
            dol_print_error($this->db);
7736
        }
7737
7738
        $this->result = array('nbofthirdparties' => $num);
7739
7740
        if ($outputmode) {
7741
            return $outarray;
7742
        }
7743
        return $out;
7744
    }
7745
7746
    /**
7747
     *    Retourne la liste des devises, dans la langue de l'utilisateur
7748
     *
7749
     * @param string $selected preselected currency code
7750
     * @param string $htmlname name of HTML select list
7751
     * @return    void
7752
     * @deprecated
7753
     */
7754
    public function select_currency($selected = '', $htmlname = 'currency_id')
7755
    {
7756
        // phpcs:enable
7757
        print $this->selectCurrency($selected, $htmlname);
7758
    }
7759
7760
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
7761
7762
    /**
7763
     *  Retourne la liste des devises, dans la langue de l'utilisateur
7764
     *
7765
     * @param string $selected preselected currency code
7766
     * @param string $htmlname name of HTML select list
7767
     * @param int $mode 0 = Add currency symbol into label, 1 = Add 3 letter iso code
7768
     * @param string $useempty '1'=Allow empty value
7769
     * @return    string
7770
     */
7771
    public function selectCurrency($selected = '', $htmlname = 'currency_id', $mode = 0, $useempty = '')
7772
    {
7773
        global $langs, $user;
7774
7775
        $langs->loadCacheCurrencies('');
7776
7777
        $out = '';
7778
7779
        if ($selected == 'euro' || $selected == 'euros') {
7780
            $selected = 'EUR'; // Pour compatibilite
7781
        }
7782
7783
        $out .= '<select class="flat maxwidth200onsmartphone minwidth300" name="' . $htmlname . '" id="' . $htmlname . '">';
7784
        if ($useempty) {
7785
            $out .= '<option value="-1" selected></option>';
7786
        }
7787
        foreach ($langs->cache_currencies as $code_iso => $currency) {
7788
            $labeltoshow = $currency['label'];
7789
            if ($mode == 1) {
7790
                $labeltoshow .= ' <span class="opacitymedium">(' . $code_iso . ')</span>';
7791
            } else {
7792
                $labeltoshow .= ' <span class="opacitymedium">(' . $langs->getCurrencySymbol($code_iso) . ')</span>';
7793
            }
7794
7795
            if ($selected && $selected == $code_iso) {
7796
                $out .= '<option value="' . $code_iso . '" selected data-html="' . dol_escape_htmltag($labeltoshow) . '">';
7797
            } else {
7798
                $out .= '<option value="' . $code_iso . '" data-html="' . dol_escape_htmltag($labeltoshow) . '">';
7799
            }
7800
            $out .= $labeltoshow;
7801
            $out .= '</option>';
7802
        }
7803
        $out .= '</select>';
7804
        if ($user->admin) {
7805
            $out .= info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
7806
        }
7807
7808
        // Make select dynamic
7809
        include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
7810
        $out .= ajax_combobox($htmlname);
7811
7812
        return $out;
7813
    }
7814
7815
    /**
7816
     *  Output an HTML select vat rate.
7817
     *  The name of this function should be selectVat. We keep bad name for compatibility purpose.
7818
     *
7819
     * @param string $htmlname Name of HTML select field
7820
     * @param float|string $selectedrate Force preselected vat rate. Can be '8.5' or '8.5 (NOO)' for example. Use '' for no forcing.
7821
     * @param Societe $societe_vendeuse Thirdparty seller
7822
     * @param Societe $societe_acheteuse Thirdparty buyer
7823
     * @param int $idprod Id product. O if unknown of NA.
7824
     * @param int $info_bits Miscellaneous information on line (1 for NPR)
7825
     * @param int|string $type ''=Unknown, 0=Product, 1=Service (Used if idprod not defined)
7826
     *                                            If seller not subject to VAT, default VAT=0. End of rule.
7827
     *                                            If (seller country==buyer country), then default VAT=product's VAT. End of rule.
7828
     *                                            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.
7829
     *                                            If (seller and buyer in EU) and buyer=private person, then default VAT=VAT of sold product.  End of rule.
7830
     *                                            If (seller and buyer in EU) and buyer=company then default VAT =0. End of rule.
7831
     *                                            Else, default proposed VAT==0. End of rule.
7832
     * @param bool $options_only Return HTML options lines only (for ajax treatment)
7833
     * @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
7834
     * @param int $type_vat 0=All type, 1=VAT rate sale, 2=VAT rate purchase
7835
     * @return string
7836
     */
7837
    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)
7838
    {
7839
        // phpcs:enable
7840
        global $langs, $mysoc;
7841
7842
        $langs->load('errors');
7843
7844
        $return = '';
7845
7846
        // Define defaultnpr, defaultttx and defaultcode
7847
        $defaultnpr = ($info_bits & 0x01);
7848
        $defaultnpr = (preg_match('/\*/', $selectedrate) ? 1 : $defaultnpr);
7849
        $defaulttx = str_replace('*', '', $selectedrate);
7850
        $defaultcode = '';
7851
        $reg = array();
7852
        if (preg_match('/\((.*)\)/', $defaulttx, $reg)) {
7853
            $defaultcode = $reg[1];
7854
            $defaulttx = preg_replace('/\s*\(.*\)/', '', $defaulttx);
7855
        }
7856
        //var_dump($selectedrate.'-'.$defaulttx.'-'.$defaultnpr.'-'.$defaultcode);
7857
7858
        // Check parameters
7859
        if (is_object($societe_vendeuse) && !$societe_vendeuse->country_code) {
7860
            if ($societe_vendeuse->id == $mysoc->id) {
7861
                $return .= '<span class="error">' . $langs->trans("ErrorYourCountryIsNotDefined") . '</span>';
7862
            } else {
7863
                $return .= '<span class="error">' . $langs->trans("ErrorSupplierCountryIsNotDefined") . '</span>';
7864
            }
7865
            return $return;
7866
        }
7867
7868
        //var_dump($societe_acheteuse);
7869
        //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";
7870
        //exit;
7871
7872
        // Define list of countries to use to search VAT rates to show
7873
        // First we defined code_country to use to find list
7874
        if (is_object($societe_vendeuse)) {
7875
            $code_country = "'" . $societe_vendeuse->country_code . "'";
7876
        } else {
7877
            $code_country = "'" . $mysoc->country_code . "'"; // Pour compatibilite ascendente
7878
        }
7879
        if (getDolGlobalString('SERVICE_ARE_ECOMMERCE_200238EC')) {    // If option to have vat for end customer for services is on
7880
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/company.lib.php';
7881
            // If SERVICE_ARE_ECOMMERCE_200238EC=1 combo list vat rate of purchaser and seller countries
7882
            // If SERVICE_ARE_ECOMMERCE_200238EC=2 combo list only the vat rate of the purchaser country
7883
            $selectVatComboMode = getDolGlobalString('SERVICE_ARE_ECOMMERCE_200238EC');
7884
            if (isInEEC($societe_vendeuse) && isInEEC($societe_acheteuse) && !$societe_acheteuse->isACompany()) {
0 ignored issues
show
Bug introduced by
The method isACompany() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

7884
            if (isInEEC($societe_vendeuse) && isInEEC($societe_acheteuse) && !$societe_acheteuse->/** @scrutinizer ignore-call */ isACompany()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
7885
                // We also add the buyer country code
7886
                if (is_numeric($type)) {
7887
                    if ($type == 1) { // We know product is a service
7888
                        switch ($selectVatComboMode) {
7889
                            case '1':
7890
                                $code_country .= ",'" . $societe_acheteuse->country_code . "'";
7891
                                break;
7892
                            case '2':
7893
                                $code_country = "'" . $societe_acheteuse->country_code . "'";
7894
                                break;
7895
                        }
7896
                    }
7897
                } elseif (!$idprod) {  // We don't know type of product
7898
                    switch ($selectVatComboMode) {
7899
                        case '1':
7900
                            $code_country .= ",'" . $societe_acheteuse->country_code . "'";
7901
                            break;
7902
                        case '2':
7903
                            $code_country = "'" . $societe_acheteuse->country_code . "'";
7904
                            break;
7905
                    }
7906
                } else {
7907
                    $prodstatic = new Product($this->db);
7908
                    $prodstatic->fetch($idprod);
7909
                    if ($prodstatic->type == Product::TYPE_SERVICE) {   // We know product is a service
7910
                        $code_country .= ",'" . $societe_acheteuse->country_code . "'";
7911
                    }
7912
                }
7913
            }
7914
        }
7915
7916
        // Now we load the list of VAT
7917
        $this->load_cache_vatrates($code_country); // If no vat defined, return -1 with message into this->error
7918
7919
        // Keep only the VAT qualified for $type_vat
7920
        $arrayofvatrates = array();
7921
        foreach ($this->cache_vatrates as $cachevalue) {
7922
            if (empty($cachevalue['type_vat']) || $cachevalue['type_vat'] != $type_vat) {
7923
                $arrayofvatrates[] = $cachevalue;
7924
            }
7925
        }
7926
7927
        $num = count($arrayofvatrates);
7928
7929
        if ($num > 0) {
7930
            // Definition du taux a pre-selectionner (si defaulttx non force et donc vaut -1 ou '')
7931
            if ($defaulttx < 0 || dol_strlen($defaulttx) == 0) {
7932
                $tmpthirdparty = new Societe($this->db);
7933
7934
                $defaulttx = get_default_tva($societe_vendeuse, (is_object($societe_acheteuse) ? $societe_acheteuse : $tmpthirdparty), $idprod);
7935
                $defaultnpr = get_default_npr($societe_vendeuse, (is_object($societe_acheteuse) ? $societe_acheteuse : $tmpthirdparty), $idprod);
7936
7937
                if (preg_match('/\((.*)\)/', $defaulttx, $reg)) {
7938
                    $defaultcode = $reg[1];
7939
                    $defaulttx = preg_replace('/\s*\(.*\)/', '', $defaulttx);
7940
                }
7941
                if (empty($defaulttx)) {
7942
                    $defaultnpr = 0;
7943
                }
7944
            }
7945
7946
            // If we fails to find a default vat rate, we take the last one in list
7947
            // Because they are sorted in ascending order, the last one will be the higher one (we suppose the higher one is the current rate)
7948
            if ($defaulttx < 0 || dol_strlen($defaulttx) == 0) {
7949
                if (!getDolGlobalString('MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS')) {
7950
                    // We take the last one found in list
7951
                    $defaulttx = $arrayofvatrates[$num - 1]['txtva'];
7952
                } else {
7953
                    // We will use the rate defined into MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS
7954
                    $defaulttx = '';
7955
                    if (getDolGlobalString('MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS') != 'none') {
7956
                        $defaulttx = getDolGlobalString('MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS');
7957
                    }
7958
                    if (preg_match('/\((.*)\)/', $defaulttx, $reg)) {
7959
                        $defaultcode = $reg[1];
7960
                        $defaulttx = preg_replace('/\s*\(.*\)/', '', $defaulttx);
7961
                    }
7962
                }
7963
            }
7964
7965
            // Disabled if seller is not subject to VAT
7966
            $disabled = false;
7967
            $title = '';
7968
            if (is_object($societe_vendeuse) && $societe_vendeuse->id == $mysoc->id && $societe_vendeuse->tva_assuj == "0") {
7969
                // Override/enable VAT for expense report regardless of global setting - needed if expense report used for business expenses instead
7970
                // of using supplier invoices (this is a very bad idea !)
7971
                if (!getDolGlobalString('EXPENSEREPORT_OVERRIDE_VAT')) {
7972
                    $title = ' title="' . dol_escape_htmltag($langs->trans('VATIsNotUsed')) . '"';
7973
                    $disabled = true;
7974
                }
7975
            }
7976
7977
            if (!$options_only) {
7978
                $return .= '<select class="flat minwidth75imp maxwidth100 right" id="' . $htmlname . '" name="' . $htmlname . '"' . ($disabled ? ' disabled' : '') . $title . '>';
7979
            }
7980
7981
            $selectedfound = false;
7982
            foreach ($arrayofvatrates as $rate) {
7983
                // Keep only 0 if seller is not subject to VAT
7984
                if ($disabled && $rate['txtva'] != 0) {
7985
                    continue;
7986
                }
7987
7988
                // Define key to use into select list
7989
                $key = $rate['txtva'];
7990
                $key .= $rate['nprtva'] ? '*' : '';
7991
                if ($mode > 0 && $rate['code']) {
7992
                    $key .= ' (' . $rate['code'] . ')';
7993
                }
7994
                if ($mode < 0) {
7995
                    $key = $rate['rowid'];
7996
                }
7997
7998
                $return .= '<option value="' . $key . '"';
7999
                if (!$selectedfound) {
8000
                    if ($defaultcode) { // If defaultcode is defined, we used it in priority to select combo option instead of using rate+npr flag
8001
                        if ($defaultcode == $rate['code']) {
8002
                            $return .= ' selected';
8003
                            $selectedfound = true;
8004
                        }
8005
                    } elseif ($rate['txtva'] == $defaulttx && $rate['nprtva'] == $defaultnpr) {
8006
                        $return .= ' selected';
8007
                        $selectedfound = true;
8008
                    }
8009
                }
8010
                $return .= '>';
8011
8012
                // Show label of VAT
8013
                if ($mysoc->country_code == 'IN' || getDolGlobalString('MAIN_VAT_LABEL_IS_POSITIVE_RATES')) {
8014
                    // Label with all localtax and code. For example:  x.y / a.b / c.d (CODE)'
8015
                    $return .= $rate['labelpositiverates'];
8016
                } else {
8017
                    // Simple label
8018
                    $return .= vatrate($rate['label']);
8019
                }
8020
8021
                //$return.=($rate['code']?' '.$rate['code']:'');
8022
                $return .= (empty($rate['code']) && $rate['nprtva']) ? ' *' : ''; // We show the *  (old behaviour only if new vat code is not used)
8023
8024
                $return .= '</option>';
8025
            }
8026
8027
            if (!$options_only) {
8028
                $return .= '</select>';
8029
                //$return .= ajax_combobox($htmlname);      // This break for the moment the dynamic autoselection of a value when selecting a product in object lines
8030
            }
8031
        } else {
8032
            $return .= $this->error;
8033
        }
8034
8035
        $this->num = $num;
8036
        return $return;
8037
    }
8038
8039
    /**
8040
     *  Load into the cache ->cache_vatrates, all the vat rates of a country
8041
     *
8042
     * @param string $country_code Country code with quotes ("'CA'", or "'CA,IN,...'")
8043
     * @return int                         Nb of loaded lines, 0 if already loaded, <0 if KO
8044
     */
8045
    public function load_cache_vatrates($country_code)
8046
    {
8047
        // phpcs:enable
8048
        global $langs, $user;
8049
8050
        $num = count($this->cache_vatrates);
8051
        if ($num > 0) {
8052
            return $num; // Cache already loaded
8053
        }
8054
8055
        dol_syslog(__METHOD__, LOG_DEBUG);
8056
8057
        $sql = "SELECT t.rowid, t.type_vat, t.code, t.taux, t.localtax1, t.localtax1_type, t.localtax2, t.localtax2_type, t.recuperableonly";
8058
        $sql .= " FROM " . $this->db->prefix() . "c_tva as t, " . $this->db->prefix() . "c_country as c";
8059
        $sql .= " WHERE t.fk_pays = c.rowid";
8060
        $sql .= " AND t.active > 0";
8061
        $sql .= " AND t.entity IN (" . getEntity('c_tva') . ")";
8062
        $sql .= " AND c.code IN (" . $this->db->sanitize($country_code, 1) . ")";
8063
        $sql .= " ORDER BY t.code ASC, t.taux ASC, t.recuperableonly ASC";
8064
8065
        $resql = $this->db->query($sql);
8066
        if ($resql) {
8067
            $num = $this->db->num_rows($resql);
8068
            if ($num) {
8069
                for ($i = 0; $i < $num; $i++) {
8070
                    $obj = $this->db->fetch_object($resql);
8071
8072
                    $tmparray = array();
8073
                    $tmparray['rowid'] = $obj->rowid;
8074
                    $tmparray['type_vat'] = $obj->type_vat;
8075
                    $tmparray['code'] = $obj->code;
8076
                    $tmparray['txtva'] = $obj->taux;
8077
                    $tmparray['nprtva'] = $obj->recuperableonly;
8078
                    $tmparray['localtax1'] = $obj->localtax1;
8079
                    $tmparray['localtax1_type'] = $obj->localtax1_type;
8080
                    $tmparray['localtax2'] = $obj->localtax2;
8081
                    $tmparray['localtax2_type'] = $obj->localtax1_type;
8082
                    $tmparray['label'] = $obj->taux . '%' . ($obj->code ? ' (' . $obj->code . ')' : ''); // Label must contains only 0-9 , . % or *
8083
                    $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
8084
                    $positiverates = '';
8085
                    if ($obj->taux) {
8086
                        $positiverates .= ($positiverates ? '/' : '') . $obj->taux;
8087
                    }
8088
                    if ($obj->localtax1) {
8089
                        $positiverates .= ($positiverates ? '/' : '') . $obj->localtax1;
8090
                    }
8091
                    if ($obj->localtax2) {
8092
                        $positiverates .= ($positiverates ? '/' : '') . $obj->localtax2;
8093
                    }
8094
                    if (empty($positiverates)) {
8095
                        $positiverates = '0';
8096
                    }
8097
                    $tmparray['labelpositiverates'] = $positiverates . ($obj->code ? ' (' . $obj->code . ')' : ''); // Must never be used as key, only label
8098
8099
                    $this->cache_vatrates[$obj->rowid] = $tmparray;
8100
                }
8101
8102
                return $num;
8103
            } else {
8104
                $this->error = '<span class="error">';
8105
                $this->error .= $langs->trans("ErrorNoVATRateDefinedForSellerCountry", $country_code);
8106
                $reg = array();
8107
                if (!empty($user) && $user->admin && preg_match('/\'(..)\'/', $country_code, $reg)) {
8108
                    $langs->load("errors");
8109
                    $new_country_code = $reg[1];
8110
                    $country_id = dol_getIdFromCode($this->db, $new_country_code, 'c_pays', 'code', 'rowid');
8111
                    $this->error .= '<br>' . $langs->trans("ErrorFixThisHere", constant('BASE_URL') . '/admin/dict.php?id=10' . ($country_id > 0 ? '&countryidforinsert=' . $country_id : ''));
8112
                }
8113
                $this->error .= '</span>';
8114
                return -1;
8115
            }
8116
        } else {
8117
            $this->error = '<span class="error">' . $this->db->error() . '</span>';
8118
            return -2;
8119
        }
8120
    }
8121
8122
    /**
8123
     *  Show a HTML widget to input a date or combo list for day, month, years and optionally hours and minutes.
8124
     *  Fields are preselected with :
8125
     *                - set_time date (must be a local PHP server timestamp or string date with format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM')
8126
     *                - local date in user area, if set_time is '' (so if set_time is '', output may differs when done from two different location)
8127
     *                - Empty (fields empty), if set_time is -1 (in this case, parameter empty must also have value 1)
8128
     *
8129
     * @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).
8130
     * @param string $prefix Prefix for fields name
8131
     * @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
8132
     * @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
8133
     * @param int $empty 0=Fields required, 1=Empty inputs are allowed, 2=Empty inputs are allowed for hours only
8134
     * @param string $form_name Not used
8135
     * @param int $d 1=Show days, month, years
8136
     * @param int $addnowlink Add a link "Now"
8137
     * @param int $nooutput Do not output html string but return it
8138
     * @param int $disabled Disable input fields
8139
     * @param int $fullday When a checkbox with this html name is on, hour and day are set with 00:00 or 23:59
8140
     * @param string $addplusone Add a link "+1 hour". Value must be name of another select_date field.
8141
     * @param int|string $adddateof Add a link "Date of invoice" using the following date.
8142
     * @return    string                        '' or HTML component string if nooutput is 1
8143
     * @deprecated
8144
     * @see    selectDate(), form_date(), select_month(), select_year(), select_dayofweek()
8145
     */
8146
    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 = '')
8147
    {
8148
        // phpcs:enable
8149
        dol_syslog(__METHOD__ . ': using select_date is deprecated. Use selectDate instead.', LOG_WARNING);
8150
        $retstring = $this->selectDate($set_time, $prefix, $h, $m, $empty, $form_name, $d, $addnowlink, $disabled, $fullday, $addplusone, $adddateof);
8151
        if (!empty($nooutput)) {
8152
            return $retstring;
8153
        }
8154
        print $retstring;
8155
8156
        return '';
8157
    }
8158
8159
    /**
8160
     *  Show 2 HTML widget to input a date or combo list for day, month, years and optionally hours and minutes.
8161
     *  Fields are preselected with :
8162
     *              - set_time date (must be a local PHP server timestamp or string date with format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM')
8163
     *              - local date in user area, if set_time is '' (so if set_time is '', output may differs when done from two different location)
8164
     *              - Empty (fields empty), if set_time is -1 (in this case, parameter empty must also have value 1)
8165
     *
8166
     * @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).
8167
     * @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).
8168
     * @param string $prefix Prefix for fields name
8169
     * @param int $empty 0=Fields required, 1=Empty inputs are allowed, 2=Empty inputs are allowed for hours only
8170
     * @param int $forcenewline Force new line between the 2 dates.
8171
     * @return string                        Html for selectDate
8172
     * @see    form_date(), select_month(), select_year(), select_dayofweek()
8173
     */
8174
    public function selectDateToDate($set_time = '', $set_time_end = '', $prefix = 're', $empty = 0, $forcenewline = 0)
8175
    {
8176
        global $langs;
8177
8178
        $ret = $this->selectDate($set_time, $prefix . '_start', 0, 0, $empty, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("from"), 'tzuserrel');
8179
        if ($forcenewline) {
8180
            $ret .= '<br>';
8181
        }
8182
        $ret .= $this->selectDate($set_time_end, $prefix . '_end', 0, 0, $empty, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("to"), 'tzuserrel');
8183
        return $ret;
8184
    }
8185
8186
    /**
8187
     * selectTypeDuration
8188
     *
8189
     * @param string $prefix Prefix
8190
     * @param string $selected Selected duration type
8191
     * @param string[] $excludetypes Array of duration types to exclude. Example array('y', 'm')
8192
     * @return  string                    HTML select string
8193
     */
8194
    public function selectTypeDuration($prefix, $selected = 'i', $excludetypes = array())
8195
    {
8196
        global $langs;
8197
8198
        $TDurationTypes = array(
8199
            'y' => $langs->trans('Years'),
8200
            'm' => $langs->trans('Month'),
8201
            'w' => $langs->trans('Weeks'),
8202
            'd' => $langs->trans('Days'),
8203
            'h' => $langs->trans('Hours'),
8204
            'i' => $langs->trans('Minutes')
8205
        );
8206
8207
        // Removed undesired duration types
8208
        foreach ($excludetypes as $value) {
8209
            unset($TDurationTypes[$value]);
8210
        }
8211
8212
        $retstring = '<select class="flat minwidth75 maxwidth100" id="select_' . $prefix . 'type_duration" name="' . $prefix . 'type_duration">';
8213
        foreach ($TDurationTypes as $key => $typeduration) {
8214
            $retstring .= '<option value="' . $key . '"';
8215
            if ($key == $selected) {
8216
                $retstring .= " selected";
8217
            }
8218
            $retstring .= ">" . $typeduration . "</option>";
8219
        }
8220
        $retstring .= "</select>";
8221
8222
        $retstring .= ajax_combobox('select_' . $prefix . 'type_duration');
8223
8224
        return $retstring;
8225
    }
8226
8227
    /**
8228
     *  Function to show a form to select a duration on a page
8229
     *
8230
     * @param string $prefix Prefix for input fields
8231
     * @param int|string $iSecond Default preselected duration (number of seconds or '')
8232
     * @param int $disabled Disable the combo box
8233
     * @param string $typehour If 'select' then input hour and input min is a combo,
8234
     *                         If 'text' input hour is in text and input min is a text,
8235
     *                         If 'textselect' input hour is in text and input min is a combo
8236
     * @param integer $minunderhours If 1, show minutes selection under the hours
8237
     * @param int $nooutput Do not output html string but return it
8238
     * @return    string                        HTML component
8239
     */
8240
    public function select_duration($prefix, $iSecond = '', $disabled = 0, $typehour = 'select', $minunderhours = 0, $nooutput = 0)
8241
    {
8242
        // phpcs:enable
8243
        global $langs;
8244
8245
        $retstring = '<span class="nowraponall">';
8246
8247
        $hourSelected = '';
8248
        $minSelected = '';
8249
8250
        // Hours
8251
        if ($iSecond != '') {
8252
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/date.lib.php';
8253
8254
            $hourSelected = convertSecondToTime($iSecond, 'allhour');
8255
            $minSelected = convertSecondToTime($iSecond, 'min');
8256
        }
8257
8258
        if ($typehour == 'select') {
8259
            $retstring .= '<select class="flat" id="select_' . $prefix . 'hour" name="' . $prefix . 'hour"' . ($disabled ? ' disabled' : '') . '>';
8260
            for ($hour = 0; $hour < 25; $hour++) {    // For a duration, we allow 24 hours
8261
                $retstring .= '<option value="' . $hour . '"';
8262
                if (is_numeric($hourSelected) && $hourSelected == $hour) {
8263
                    $retstring .= " selected";
8264
                }
8265
                $retstring .= ">" . $hour . "</option>";
8266
            }
8267
            $retstring .= "</select>";
8268
        } elseif ($typehour == 'text' || $typehour == 'textselect') {
8269
            $retstring .= '<input placeholder="' . $langs->trans('HourShort') . '" type="number" min="0" name="' . $prefix . 'hour"' . ($disabled ? ' disabled' : '') . ' class="flat maxwidth50 inputhour right" value="' . (($hourSelected != '') ? ((int)$hourSelected) : '') . '">';
8270
        } else {
8271
            return 'BadValueForParameterTypeHour';
8272
        }
8273
8274
        if ($typehour != 'text') {
8275
            $retstring .= ' ' . $langs->trans('HourShort');
8276
        } else {
8277
            $retstring .= '<span class="">:</span>';
8278
        }
8279
8280
        // Minutes
8281
        if ($minunderhours) {
8282
            $retstring .= '<br>';
8283
        } else {
8284
            if ($typehour != 'text') {
8285
                $retstring .= '<span class="hideonsmartphone">&nbsp;</span>';
8286
            }
8287
        }
8288
8289
        if ($typehour == 'select' || $typehour == 'textselect') {
8290
            $retstring .= '<select class="flat" id="select_' . $prefix . 'min" name="' . $prefix . 'min"' . ($disabled ? ' disabled' : '') . '>';
8291
            for ($min = 0; $min <= 55; $min += 5) {
8292
                $retstring .= '<option value="' . $min . '"';
8293
                if (is_numeric($minSelected) && $minSelected == $min) {
8294
                    $retstring .= ' selected';
8295
                }
8296
                $retstring .= '>' . $min . '</option>';
8297
            }
8298
            $retstring .= "</select>";
8299
        } elseif ($typehour == 'text') {
8300
            $retstring .= '<input placeholder="' . $langs->trans('MinuteShort') . '" type="number" min="0" name="' . $prefix . 'min"' . ($disabled ? ' disabled' : '') . ' class="flat maxwidth50 inputminute right" value="' . (($minSelected != '') ? ((int)$minSelected) : '') . '">';
8301
        }
8302
8303
        if ($typehour != 'text') {
8304
            $retstring .= ' ' . $langs->trans('MinuteShort');
8305
        }
8306
8307
        $retstring .= "</span>";
8308
8309
        if (!empty($nooutput)) {
8310
            return $retstring;
8311
        }
8312
8313
        print $retstring;
8314
8315
        return '';
8316
    }
8317
8318
    /**
8319
     *  Return list of tickets in Ajax if Ajax activated or go to selectTicketsList
8320
     *
8321
     * @param string $selected Preselected tickets
8322
     * @param string $htmlname Name of HTML select field (must be unique in page).
8323
     * @param string $filtertype To add a filter
8324
     * @param int $limit Limit on number of returned lines
8325
     * @param int $status Ticket status
8326
     * @param string $selected_input_value Value of preselected input text (for use with ajax)
8327
     * @param int<0,3> $hidelabel Hide label (0=no, 1=yes, 2=show search icon (before) and placeholder, 3 search icon after)
8328
     * @param array<string,string|string[]> $ajaxoptions Options for ajax_autocompleter
8329
     * @param int $socid Thirdparty Id (to get also price dedicated to this customer)
8330
     * @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.
8331
     * @param int<0,1> $forcecombo Force to use combo box
8332
     * @param string $morecss Add more css on select
8333
     * @param array<string,string> $selected_combinations Selected combinations. Format: array([attrid] => attrval, [...])
8334
     * @param int<0,1> $nooutput No print, return the output into a string
8335
     * @return  string
8336
     */
8337
    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)
8338
    {
8339
        global $langs, $conf;
8340
8341
        $out = '';
8342
8343
        // check parameters
8344
        if (is_null($ajaxoptions)) {
8345
            $ajaxoptions = array();
8346
        }
8347
8348
        if (!empty($conf->use_javascript_ajax) && getDolGlobalString('TICKET_USE_SEARCH_TO_SELECT')) {
8349
            $placeholder = '';
8350
8351
            if ($selected && empty($selected_input_value)) {
8352
                $tickettmpselect = new Ticket($this->db);
8353
                $tickettmpselect->fetch($selected);
8354
                $selected_input_value = $tickettmpselect->ref;
8355
                unset($tickettmpselect);
8356
            }
8357
8358
            $urloption = '';
8359
            $out .= ajax_autocompleter($selected, $htmlname, constant('BASE_URL') . '/ticket/ajax/tickets.php', $urloption, $conf->global->PRODUIT_USE_SEARCH_TO_SELECT, 1, $ajaxoptions);
8360
8361
            if (empty($hidelabel)) {
8362
                $out .= $langs->trans("RefOrLabel") . ' : ';
8363
            } elseif ($hidelabel > 1) {
8364
                $placeholder = ' placeholder="' . $langs->trans("RefOrLabel") . '"';
8365
                if ($hidelabel == 2) {
8366
                    $out .= img_picto($langs->trans("Search"), 'search');
8367
                }
8368
            }
8369
            $out .= '<input type="text" class="minwidth100" name="search_' . $htmlname . '" id="search_' . $htmlname . '" value="' . $selected_input_value . '"' . $placeholder . ' ' . (getDolGlobalString('PRODUCT_SEARCH_AUTOFOCUS') ? 'autofocus' : '') . ' />';
8370
            if ($hidelabel == 3) {
8371
                $out .= img_picto($langs->trans("Search"), 'search');
8372
            }
8373
        } else {
8374
            $out .= $this->selectTicketsList($selected, $htmlname, $filtertype, $limit, '', $status, 0, $showempty, $forcecombo, $morecss);
8375
        }
8376
8377
        if (empty($nooutput)) {
8378
            print $out;
8379
        } else {
8380
            return $out;
8381
        }
8382
        return '';
8383
    }
8384
8385
    /**
8386
     * Return list of tickets.
8387
     *  Called by selectTickets.
8388
     *
8389
     * @param string $selected Preselected ticket
8390
     * @param string $htmlname Name of select html
8391
     * @param string $filtertype Filter on ticket type
8392
     * @param int $limit Limit on number of returned lines
8393
     * @param string $filterkey Filter on ticket ref or subject
8394
     * @param int $status Ticket status
8395
     * @param int $outputmode 0=HTML select string, 1=Array
8396
     * @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.
8397
     * @param int $forcecombo Force to use combo box
8398
     * @param string $morecss Add more css on select
8399
     * @return  array|string                Array of keys for json or HTML component
8400
     */
8401
    public function selectTicketsList($selected = '', $htmlname = 'ticketid', $filtertype = '', $limit = 20, $filterkey = '', $status = 1, $outputmode = 0, $showempty = '1', $forcecombo = 0, $morecss = '')
8402
    {
8403
        global $langs, $conf;
8404
8405
        $out = '';
8406
        $outarray = array();
8407
8408
        $selectFields = " p.rowid, p.ref, p.message";
8409
8410
        $sql = "SELECT ";
8411
        $sql .= $selectFields;
8412
        $sql .= " FROM " . $this->db->prefix() . "ticket as p";
8413
        $sql .= ' WHERE p.entity IN (' . getEntity('ticket') . ')';
8414
8415
        // Add criteria on ref/label
8416
        if ($filterkey != '') {
8417
            $sql .= ' AND (';
8418
            $prefix = !getDolGlobalString('TICKET_DONOTSEARCH_ANYWHERE') ? '%' : ''; // Can use index if PRODUCT_DONOTSEARCH_ANYWHERE is on
8419
            // For natural search
8420
            $search_crit = explode(' ', $filterkey);
8421
            $i = 0;
8422
            if (count($search_crit) > 1) {
8423
                $sql .= "(";
8424
            }
8425
            foreach ($search_crit as $crit) {
8426
                if ($i > 0) {
8427
                    $sql .= " AND ";
8428
                }
8429
                $sql .= "(p.ref LIKE '" . $this->db->escape($prefix . $crit) . "%' OR p.subject LIKE '" . $this->db->escape($prefix . $crit) . "%'";
8430
                $sql .= ")";
8431
                $i++;
8432
            }
8433
            if (count($search_crit) > 1) {
8434
                $sql .= ")";
8435
            }
8436
            $sql .= ')';
8437
        }
8438
8439
        $sql .= $this->db->plimit($limit, 0);
8440
8441
        // Build output string
8442
        dol_syslog(get_only_class($this) . "::selectTicketsList search tickets", LOG_DEBUG);
8443
        $result = $this->db->query($sql);
8444
        if ($result) {
8445
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/ticket.lib.php';
8446
8447
            $num = $this->db->num_rows($result);
8448
8449
            $events = array();
8450
8451
            if (!$forcecombo) {
8452
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
8453
                $out .= ajax_combobox($htmlname, $events, $conf->global->TICKET_USE_SEARCH_TO_SELECT);
8454
            }
8455
8456
            $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" id="' . $htmlname . '">';
8457
8458
            $textifempty = '';
8459
            // Do not use textifempty = ' ' or '&nbsp;' here, or search on key will search on ' key'.
8460
            //if (!empty($conf->use_javascript_ajax) || $forcecombo) $textifempty='';
8461
            if (getDolGlobalString('TICKET_USE_SEARCH_TO_SELECT')) {
8462
                if ($showempty && !is_numeric($showempty)) {
8463
                    $textifempty = $langs->trans($showempty);
8464
                } else {
8465
                    $textifempty .= $langs->trans("All");
8466
                }
8467
            } else {
8468
                if ($showempty && !is_numeric($showempty)) {
8469
                    $textifempty = $langs->trans($showempty);
8470
                }
8471
            }
8472
            if ($showempty) {
8473
                $out .= '<option value="0" selected>' . $textifempty . '</option>';
8474
            }
8475
8476
            $i = 0;
8477
            while ($num && $i < $num) {
8478
                $opt = '';
8479
                $optJson = array();
8480
                $objp = $this->db->fetch_object($result);
8481
8482
                $this->constructTicketListOption($objp, $opt, $optJson, $selected, $filterkey);
8483
                // Add new entry
8484
                // "key" value of json key array is used by jQuery automatically as selected value
8485
                // "label" value of json key array is used by jQuery automatically as text for combo box
8486
                $out .= $opt;
8487
                array_push($outarray, $optJson);
8488
8489
                $i++;
8490
            }
8491
8492
            $out .= '</select>';
8493
8494
            $this->db->free($result);
8495
8496
            if (empty($outputmode)) {
8497
                return $out;
8498
            }
8499
            return $outarray;
8500
        } else {
8501
            dol_print_error($this->db);
8502
        }
8503
8504
        return array();
8505
    }
8506
8507
    /**
8508
     * constructTicketListOption.
8509
     * This define value for &$opt and &$optJson.
8510
     *
8511
     * @param object $objp Result set of fetch
8512
     * @param string $opt Option (var used for returned value in string option format)
8513
     * @param mixed[] $optJson Option (var used for returned value in json format)
8514
     * @param string $selected Preselected value
8515
     * @param string $filterkey Filter key to highlight
8516
     * @return    void
8517
     */
8518
    protected function constructTicketListOption(&$objp, &$opt, &$optJson, $selected, $filterkey = '')
8519
    {
8520
        $outkey = '';
8521
        $outref = '';
8522
        $outtype = '';
8523
8524
        $outkey = $objp->rowid;
8525
        $outref = $objp->ref;
8526
        $outtype = $objp->fk_product_type;
8527
8528
        $opt = '<option value="' . $objp->rowid . '"';
8529
        $opt .= ($objp->rowid == $selected) ? ' selected' : '';
8530
        $opt .= '>';
8531
        $opt .= $objp->ref;
8532
        $objRef = $objp->ref;
8533
        if (!empty($filterkey) && $filterkey != '') {
8534
            $objRef = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', $objRef, 1);
8535
        }
8536
8537
        $opt .= "</option>\n";
8538
        $optJson = array('key' => $outkey, 'value' => $outref, 'type' => $outtype);
8539
    }
8540
8541
    /**
8542
     *  Return list of projects in Ajax if Ajax activated or go to selectTicketsList
8543
     *
8544
     * @param string $selected Preselected tickets
8545
     * @param string $htmlname Name of HTML select field (must be unique in page).
8546
     * @param string $filtertype To add a filter
8547
     * @param int $limit Limit on number of returned lines
8548
     * @param int $status Ticket status
8549
     * @param string $selected_input_value Value of preselected input text (for use with ajax)
8550
     * @param int<0,3> $hidelabel Hide label (0=no, 1=yes, 2=show search icon (before) and placeholder, 3 search icon after)
8551
     * @param array<string,string|string[]> $ajaxoptions Options for ajax_autocompleter
8552
     * @param int $socid Thirdparty Id (to get also price dedicated to this customer)
8553
     * @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.
8554
     * @param int<0,1> $forcecombo Force to use combo box
8555
     * @param string $morecss Add more css on select
8556
     * @param array<string,string> $selected_combinations Selected combinations. Format: array([attrid] => attrval, [...])
8557
     * @param int<0,1> $nooutput No print, return the output into a string
8558
     * @return  string
8559
     */
8560
    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)
8561
    {
8562
        global $langs, $conf;
8563
8564
        $out = '';
8565
8566
        // check parameters
8567
        if (is_null($ajaxoptions)) {
8568
            $ajaxoptions = array();
8569
        }
8570
8571
        if (!empty($conf->use_javascript_ajax) && getDolGlobalString('TICKET_USE_SEARCH_TO_SELECT')) {
8572
            $placeholder = '';
8573
8574
            if ($selected && empty($selected_input_value)) {
8575
                $projecttmpselect = new Project($this->db);
8576
                $projecttmpselect->fetch($selected);
8577
                $selected_input_value = $projecttmpselect->ref;
8578
                unset($projecttmpselect);
8579
            }
8580
8581
            $urloption = '';
8582
            $out .= ajax_autocompleter($selected, $htmlname, constant('BASE_URL') . '/projet/ajax/projects.php', $urloption, $conf->global->PRODUIT_USE_SEARCH_TO_SELECT, 1, $ajaxoptions);
8583
8584
            if (empty($hidelabel)) {
8585
                $out .= $langs->trans("RefOrLabel") . ' : ';
8586
            } elseif ($hidelabel > 1) {
8587
                $placeholder = ' placeholder="' . $langs->trans("RefOrLabel") . '"';
8588
                if ($hidelabel == 2) {
8589
                    $out .= img_picto($langs->trans("Search"), 'search');
8590
                }
8591
            }
8592
            $out .= '<input type="text" class="minwidth100" name="search_' . $htmlname . '" id="search_' . $htmlname . '" value="' . $selected_input_value . '"' . $placeholder . ' ' . (getDolGlobalString('PRODUCT_SEARCH_AUTOFOCUS') ? 'autofocus' : '') . ' />';
8593
            if ($hidelabel == 3) {
8594
                $out .= img_picto($langs->trans("Search"), 'search');
8595
            }
8596
        } else {
8597
            $out .= $this->selectProjectsList($selected, $htmlname, $filtertype, $limit, '', $status, 0, $showempty, $forcecombo, $morecss);
8598
        }
8599
8600
        if (empty($nooutput)) {
8601
            print $out;
8602
        } else {
8603
            return $out;
8604
        }
8605
        return '';
8606
    }
8607
8608
    /**
8609
     *    Return list of projects.
8610
     *  Called by selectProjects.
8611
     *
8612
     * @param string $selected Preselected project
8613
     * @param string $htmlname Name of select html
8614
     * @param string $filtertype Filter on project type
8615
     * @param int $limit Limit on number of returned lines
8616
     * @param string $filterkey Filter on project ref or subject
8617
     * @param int $status Ticket status
8618
     * @param int $outputmode 0=HTML select string, 1=Array
8619
     * @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.
8620
     * @param int $forcecombo Force to use combo box
8621
     * @param string $morecss Add more css on select
8622
     * @return     array|string                Array of keys for json or HTML component
8623
     */
8624
    public function selectProjectsList($selected = '', $htmlname = 'projectid', $filtertype = '', $limit = 20, $filterkey = '', $status = 1, $outputmode = 0, $showempty = '1', $forcecombo = 0, $morecss = '')
8625
    {
8626
        global $langs, $conf;
8627
8628
        $out = '';
8629
        $outarray = array();
8630
8631
        $selectFields = " p.rowid, p.ref";
8632
8633
        $sql = "SELECT ";
8634
        $sql .= $selectFields;
8635
        $sql .= " FROM " . $this->db->prefix() . "projet as p";
8636
        $sql .= ' WHERE p.entity IN (' . getEntity('project') . ')';
8637
8638
        // Add criteria on ref/label
8639
        if ($filterkey != '') {
8640
            $sql .= ' AND (';
8641
            $prefix = !getDolGlobalString('TICKET_DONOTSEARCH_ANYWHERE') ? '%' : ''; // Can use index if PRODUCT_DONOTSEARCH_ANYWHERE is on
8642
            // For natural search
8643
            $search_crit = explode(' ', $filterkey);
8644
            $i = 0;
8645
            if (count($search_crit) > 1) {
8646
                $sql .= "(";
8647
            }
8648
            foreach ($search_crit as $crit) {
8649
                if ($i > 0) {
8650
                    $sql .= " AND ";
8651
                }
8652
                $sql .= "p.ref LIKE '" . $this->db->escape($prefix . $crit) . "%'";
8653
                $sql .= "";
8654
                $i++;
8655
            }
8656
            if (count($search_crit) > 1) {
8657
                $sql .= ")";
8658
            }
8659
            $sql .= ')';
8660
        }
8661
8662
        $sql .= $this->db->plimit($limit, 0);
8663
8664
        // Build output string
8665
        dol_syslog(get_only_class($this) . "::selectProjectsList search projects", LOG_DEBUG);
8666
        $result = $this->db->query($sql);
8667
        if ($result) {
8668
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/project.lib.php';
8669
8670
            $num = $this->db->num_rows($result);
8671
8672
            $events = array();
8673
8674
            if (!$forcecombo) {
8675
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
8676
                $out .= ajax_combobox($htmlname, $events, $conf->global->PROJECT_USE_SEARCH_TO_SELECT);
8677
            }
8678
8679
            $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" id="' . $htmlname . '">';
8680
8681
            $textifempty = '';
8682
            // Do not use textifempty = ' ' or '&nbsp;' here, or search on key will search on ' key'.
8683
            //if (!empty($conf->use_javascript_ajax) || $forcecombo) $textifempty='';
8684
            if (getDolGlobalString('PROJECT_USE_SEARCH_TO_SELECT')) {
8685
                if ($showempty && !is_numeric($showempty)) {
8686
                    $textifempty = $langs->trans($showempty);
8687
                } else {
8688
                    $textifempty .= $langs->trans("All");
8689
                }
8690
            } else {
8691
                if ($showempty && !is_numeric($showempty)) {
8692
                    $textifempty = $langs->trans($showempty);
8693
                }
8694
            }
8695
            if ($showempty) {
8696
                $out .= '<option value="0" selected>' . $textifempty . '</option>';
8697
            }
8698
8699
            $i = 0;
8700
            while ($num && $i < $num) {
8701
                $opt = '';
8702
                $optJson = array();
8703
                $objp = $this->db->fetch_object($result);
8704
8705
                $this->constructProjectListOption($objp, $opt, $optJson, $selected, $filterkey);
8706
                // Add new entry
8707
                // "key" value of json key array is used by jQuery automatically as selected value
8708
                // "label" value of json key array is used by jQuery automatically as text for combo box
8709
                $out .= $opt;
8710
                array_push($outarray, $optJson);
8711
8712
                $i++;
8713
            }
8714
8715
            $out .= '</select>';
8716
8717
            $this->db->free($result);
8718
8719
            if (empty($outputmode)) {
8720
                return $out;
8721
            }
8722
            return $outarray;
8723
        } else {
8724
            dol_print_error($this->db);
8725
        }
8726
8727
        return array();
8728
    }
8729
8730
    /**
8731
     * constructProjectListOption.
8732
     * This define value for &$opt and &$optJson.
8733
     *
8734
     * @param stdClass $objp Result set of fetch
8735
     * @param string $opt Option (var used for returned value in string option format)
8736
     * @param array{key:string,value:string,type:string} $optJson Option (var used for returned value in json format)
8737
     * @param string $selected Preselected value
8738
     * @param string $filterkey Filter key to highlight
8739
     * @return    void
8740
     */
8741
    protected function constructProjectListOption(&$objp, &$opt, &$optJson, $selected, $filterkey = '')
8742
    {
8743
        $outkey = '';
8744
        $outref = '';
8745
        $outtype = '';
8746
8747
        $label = $objp->label;
8748
8749
        $outkey = $objp->rowid;
8750
        $outref = $objp->ref;
8751
        $outlabel = $objp->label;
8752
        $outtype = $objp->fk_product_type;
8753
8754
        $opt = '<option value="' . $objp->rowid . '"';
8755
        $opt .= ($objp->rowid == $selected) ? ' selected' : '';
8756
        $opt .= '>';
8757
        $opt .= $objp->ref;
8758
        $objRef = $objp->ref;
8759
        if (!empty($filterkey) && $filterkey != '') {
8760
            $objRef = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', $objRef, 1);
8761
        }
8762
8763
        $opt .= "</option>\n";
8764
        $optJson = array('key' => $outkey, 'value' => $outref, 'type' => $outtype);
8765
    }
8766
8767
    /**
8768
     *  Return list of members in Ajax if Ajax activated or go to selectTicketsList
8769
     *
8770
     * @param string $selected Preselected tickets
8771
     * @param string $htmlname Name of HTML select field (must be unique in page).
8772
     * @param string $filtertype To add a filter
8773
     * @param int $limit Limit on number of returned lines
8774
     * @param int $status Ticket status
8775
     * @param string $selected_input_value Value of preselected input text (for use with ajax)
8776
     * @param int<0,3> $hidelabel Hide label (0=no, 1=yes, 2=show search icon before and placeholder, 3 search icon after)
8777
     * @param array<string,string|string[]> $ajaxoptions Options for ajax_autocompleter
8778
     * @param int $socid Thirdparty Id (to get also price dedicated to this customer)
8779
     * @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.
8780
     * @param int $forcecombo Force to use combo box
8781
     * @param string $morecss Add more css on select
8782
     * @param array<string,string> $selected_combinations Selected combinations. Format: array([attrid] => attrval, [...])
8783
     * @param int<0,1> $nooutput No print, return the output into a string
8784
     * @return        string
8785
     */
8786
    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)
8787
    {
8788
        global $langs, $conf;
8789
8790
        $out = '';
8791
8792
        // check parameters
8793
        if (is_null($ajaxoptions)) {
8794
            $ajaxoptions = array();
8795
        }
8796
8797
        if (!empty($conf->use_javascript_ajax) && getDolGlobalString('TICKET_USE_SEARCH_TO_SELECT')) {
8798
            $placeholder = '';
8799
8800
            if ($selected && empty($selected_input_value)) {
8801
                $adherenttmpselect = new Adherent($this->db);
8802
                $adherenttmpselect->fetch($selected);
8803
                $selected_input_value = $adherenttmpselect->ref;
8804
                unset($adherenttmpselect);
8805
            }
8806
8807
            $urloption = '';
8808
8809
            $out .= ajax_autocompleter($selected, $htmlname, constant('BASE_URL') . '/adherents/ajax/adherents.php', $urloption, $conf->global->PRODUIT_USE_SEARCH_TO_SELECT, 1, $ajaxoptions);
8810
8811
            if (empty($hidelabel)) {
8812
                $out .= $langs->trans("RefOrLabel") . ' : ';
8813
            } elseif ($hidelabel > 1) {
8814
                $placeholder = ' placeholder="' . $langs->trans("RefOrLabel") . '"';
8815
                if ($hidelabel == 2) {
8816
                    $out .= img_picto($langs->trans("Search"), 'search');
8817
                }
8818
            }
8819
            $out .= '<input type="text" class="minwidth100" name="search_' . $htmlname . '" id="search_' . $htmlname . '" value="' . $selected_input_value . '"' . $placeholder . ' ' . (getDolGlobalString('PRODUCT_SEARCH_AUTOFOCUS') ? 'autofocus' : '') . ' />';
8820
            if ($hidelabel == 3) {
8821
                $out .= img_picto($langs->trans("Search"), 'search');
8822
            }
8823
        } else {
8824
            $filterkey = '';
8825
8826
            $out .= $this->selectMembersList($selected, $htmlname, $filtertype, $limit, $filterkey, $status, 0, $showempty, $forcecombo, $morecss);
8827
        }
8828
8829
        if (empty($nooutput)) {
8830
            print $out;
8831
        } else {
8832
            return $out;
8833
        }
8834
        return '';
8835
    }
8836
8837
    /**
8838
     *    Return list of adherents.
8839
     *  Called by selectMembers.
8840
     *
8841
     * @param string $selected Preselected adherent
8842
     * @param string $htmlname Name of select html
8843
     * @param string $filtertype Filter on adherent type
8844
     * @param int $limit Limit on number of returned lines
8845
     * @param string $filterkey Filter on member status
8846
     * @param int $status Member status
8847
     * @param int $outputmode 0=HTML select string, 1=Array
8848
     * @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.
8849
     * @param int $forcecombo Force to use combo box
8850
     * @param string $morecss Add more css on select
8851
     * @return     array|string                Array of keys for json or HTML string component
8852
     */
8853
    public function selectMembersList($selected = '', $htmlname = 'adherentid', $filtertype = '', $limit = 20, $filterkey = '', $status = 1, $outputmode = 0, $showempty = '1', $forcecombo = 0, $morecss = '')
8854
    {
8855
        global $langs, $conf;
8856
8857
        $out = '';
8858
        $outarray = array();
8859
8860
        $selectFields = " p.rowid, p.ref, p.firstname, p.lastname, p.fk_adherent_type";
8861
8862
        $sql = "SELECT ";
8863
        $sql .= $selectFields;
8864
        $sql .= " FROM " . $this->db->prefix() . "adherent as p";
8865
        $sql .= ' WHERE p.entity IN (' . getEntity('adherent') . ')';
8866
8867
        // Add criteria on ref/label
8868
        if ($filterkey != '') {
8869
            $sql .= ' AND (';
8870
            $prefix = !getDolGlobalString('MEMBER_DONOTSEARCH_ANYWHERE') ? '%' : ''; // Can use index if PRODUCT_DONOTSEARCH_ANYWHERE is on
8871
            // For natural search
8872
            $search_crit = explode(' ', $filterkey);
8873
            $i = 0;
8874
            if (count($search_crit) > 1) {
8875
                $sql .= "(";
8876
            }
8877
            foreach ($search_crit as $crit) {
8878
                if ($i > 0) {
8879
                    $sql .= " AND ";
8880
                }
8881
                $sql .= "(p.firstname LIKE '" . $this->db->escape($prefix . $crit) . "%'";
8882
                $sql .= " OR p.lastname LIKE '" . $this->db->escape($prefix . $crit) . "%')";
8883
                $i++;
8884
            }
8885
            if (count($search_crit) > 1) {
8886
                $sql .= ")";
8887
            }
8888
            $sql .= ')';
8889
        }
8890
        if ($status != -1) {
8891
            $sql .= ' AND statut = ' . ((int)$status);
8892
        }
8893
        $sql .= $this->db->plimit($limit, 0);
8894
8895
        // Build output string
8896
        dol_syslog(get_only_class($this) . "::selectMembersList search adherents", LOG_DEBUG);
8897
        $result = $this->db->query($sql);
8898
        if ($result) {
8899
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/member.lib.php';
8900
8901
            $num = $this->db->num_rows($result);
8902
8903
            $events = array();
8904
8905
            if (!$forcecombo) {
8906
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
8907
                $out .= ajax_combobox($htmlname, $events, getDolGlobalString('PROJECT_USE_SEARCH_TO_SELECT') ? $conf->global->PROJECT_USE_SEARCH_TO_SELECT : '');
8908
            }
8909
8910
            $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" id="' . $htmlname . '">';
8911
8912
            $textifempty = '';
8913
            // Do not use textifempty = ' ' or '&nbsp;' here, or search on key will search on ' key'.
8914
            //if (!empty($conf->use_javascript_ajax) || $forcecombo) $textifempty='';
8915
            if (getDolGlobalString('PROJECT_USE_SEARCH_TO_SELECT')) {
8916
                if ($showempty && !is_numeric($showempty)) {
8917
                    $textifempty = $langs->trans($showempty);
8918
                } else {
8919
                    $textifempty .= $langs->trans("All");
8920
                }
8921
            } else {
8922
                if ($showempty && !is_numeric($showempty)) {
8923
                    $textifempty = $langs->trans($showempty);
8924
                }
8925
            }
8926
            if ($showempty) {
8927
                $out .= '<option value="-1" selected>' . $textifempty . '</option>';
8928
            }
8929
8930
            $i = 0;
8931
            while ($num && $i < $num) {
8932
                $opt = '';
8933
                $optJson = array();
8934
                $objp = $this->db->fetch_object($result);
8935
8936
                $this->constructMemberListOption($objp, $opt, $optJson, $selected, $filterkey);
8937
8938
                // Add new entry
8939
                // "key" value of json key array is used by jQuery automatically as selected value
8940
                // "label" value of json key array is used by jQuery automatically as text for combo box
8941
                $out .= $opt;
8942
                array_push($outarray, $optJson);
8943
8944
                $i++;
8945
            }
8946
8947
            $out .= '</select>';
8948
8949
            $this->db->free($result);
8950
8951
            if (empty($outputmode)) {
8952
                return $out;
8953
            }
8954
            return $outarray;
8955
        } else {
8956
            dol_print_error($this->db);
8957
        }
8958
8959
        return array();
8960
    }
8961
8962
    /**
8963
     * constructMemberListOption.
8964
     * This define value for &$opt and &$optJson.
8965
     *
8966
     * @param object $objp Result set of fetch
8967
     * @param string $opt Option (var used for returned value in string option format)
8968
     * @param mixed[] $optJson Option (var used for returned value in json format)
8969
     * @param string $selected Preselected value
8970
     * @param string $filterkey Filter key to highlight
8971
     * @return    void
8972
     */
8973
    protected function constructMemberListOption(&$objp, &$opt, &$optJson, $selected, $filterkey = '')
8974
    {
8975
        $outkey = '';
8976
        $outlabel = '';
8977
        $outtype = '';
8978
8979
        $outkey = $objp->rowid;
8980
        $outlabel = dolGetFirstLastname($objp->firstname, $objp->lastname);
8981
        $outtype = $objp->fk_adherent_type;
8982
8983
        $opt = '<option value="' . $objp->rowid . '"';
8984
        $opt .= ($objp->rowid == $selected) ? ' selected' : '';
8985
        $opt .= '>';
8986
        if (!empty($filterkey) && $filterkey != '') {
8987
            $outlabel = preg_replace('/(' . preg_quote($filterkey, '/') . ')/i', '<strong>$1</strong>', $outlabel, 1);
8988
        }
8989
        $opt .= $outlabel;
8990
        $opt .= "</option>\n";
8991
8992
        $optJson = array('key' => $outkey, 'value' => $outlabel, 'type' => $outtype);
8993
    }
8994
8995
    /**
8996
     * Generic method to select a component from a combo list.
8997
     * Can use autocomplete with ajax after x key pressed or a full combo, depending on setup.
8998
     * This is the generic method that will replace all specific existing methods.
8999
     *
9000
     * @param string $objectdesc 'ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'. For hard coded custom needs. Try to prefer method using $objectfield instead of $objectdesc.
9001
     * @param string $htmlname Name of HTML select component
9002
     * @param int $preSelectedValue Preselected value (ID of element)
9003
     * @param string|int<0,1> $showempty ''=empty values not allowed, 'string'=value show if we allow empty values (for example 'All', ...)
9004
     * @param string $searchkey Search criteria
9005
     * @param string $placeholder Place holder
9006
     * @param string $morecss More CSS
9007
     * @param string $moreparams More params provided to ajax call
9008
     * @param int $forcecombo Force to load all values and output a standard combobox (with no beautification)
9009
     * @param int<0,1> $disabled 1=Html component is disabled
9010
     * @param string $selected_input_value Value of preselected input text (for use with ajax)
9011
     * @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')
9012
     * @return  string                              Return HTML string
9013
     * @see selectForFormsList(), select_thirdparty_list()
9014
     */
9015
    public function selectForForms($objectdesc, $htmlname, $preSelectedValue, $showempty = '', $searchkey = '', $placeholder = '', $morecss = '', $moreparams = '', $forcecombo = 0, $disabled = 0, $selected_input_value = '', $objectfield = '')
9016
    {
9017
        global $conf, $extrafields, $user, $db;
9018
9019
        //var_dump($objectdesc); debug_print_backtrace();
9020
9021
        $objectdescorig = $objectdesc;
9022
        $objecttmp = null;
9023
        $InfoFieldList = array();
9024
        $classname = '';
9025
        $filter = '';  // Ensure filter has value (for static analysis)
9026
        $sortfield = '';  // Ensure filter has value (for static analysis)
9027
9028
        if ($objectfield) { // We must retrieve the objectdesc from the field or extrafield
9029
            // Example: $objectfield = 'product:options_package' or 'myobject@mymodule:options_myfield'
9030
            $tmparray = explode(':', $objectfield);
9031
9032
            // Get instance of object from $element
9033
            $objectforfieldstmp = fetchObjectByElement(0, strtolower($tmparray[0]));
9034
9035
            if (is_object($objectforfieldstmp)) {
9036
                $objectdesc = '';
9037
9038
                $reg = array();
9039
                if (preg_match('/^options_(.*)$/', $tmparray[1], $reg)) {
9040
                    // For a property in extrafields
9041
                    $key = $reg[1];
9042
                    // fetch optionals attributes and labels
9043
                    $extrafields->fetch_name_optionals_label($objectforfieldstmp->table_element);
9044
9045
                    if (!empty($extrafields->attributes[$objectforfieldstmp->table_element]['type'][$key]) && $extrafields->attributes[$objectforfieldstmp->table_element]['type'][$key] == 'link') {
9046
                        if (!empty($extrafields->attributes[$objectforfieldstmp->table_element]['param'][$key]['options'])) {
9047
                            $tmpextrafields = array_keys($extrafields->attributes[$objectforfieldstmp->table_element]['param'][$key]['options']);
9048
                            $objectdesc = $tmpextrafields[0];
9049
                        }
9050
                    }
9051
                } else {
9052
                    // For a property in ->fields
9053
                    if (array_key_exists($tmparray[1], $objectforfieldstmp->fields)) {
9054
                        $objectdesc = $objectforfieldstmp->fields[$tmparray[1]]['type'];
9055
                        $objectdesc = preg_replace('/^integer[^:]*:/', '', $objectdesc);
9056
                    }
9057
                }
9058
            }
9059
        }
9060
9061
        if ($objectdesc) {
9062
            // Example of value for $objectdesc:
9063
            // Bom:bom/class/bom.class.php:0:t.status=1
9064
            // Bom:bom/class/bom.class.php:0:t.status=1:ref
9065
            // Bom:bom/class/bom.class.php:0:(t.status:=:1) OR (t.field2:=:2):ref
9066
            $InfoFieldList = explode(":", $objectdesc, 4);
9067
            $vartmp = (empty($InfoFieldList[3]) ? '' : $InfoFieldList[3]);
9068
            $reg = array();
9069
            if (preg_match('/^.*:(\w*)$/', $vartmp, $reg)) {
9070
                $InfoFieldList[4] = $reg[1];    // take the sort field
9071
            }
9072
            $InfoFieldList[3] = preg_replace('/:\w*$/', '', $vartmp);    // take the filter field
9073
9074
            $classname = $InfoFieldList[0];
9075
            $classpath = empty($InfoFieldList[1]) ? '' : $InfoFieldList[1];
9076
            //$addcreatebuttonornot = empty($InfoFieldList[2]) ? 0 : $InfoFieldList[2];
9077
            $filter = empty($InfoFieldList[3]) ? '' : $InfoFieldList[3];
9078
            $sortfield = empty($InfoFieldList[4]) ? '' : $InfoFieldList[4];
9079
9080
9081
            // Load object according to $id and $element
9082
            // $objecttmp = fetchObjectByElement(0, strtolower($InfoFieldList[0]));
9083
            $objecttmp = Misc::loadModel($InfoFieldList[0], $db);
9084
9085
            // Fallback to another solution to get $objecttmp
9086
            if (empty($objecttmp) && !empty($classpath)) {
9087
                dol_include_once($classpath);
9088
9089
                if ($classname && class_exists($classname)) {
9090
                    $objecttmp = new $classname($this->db);
9091
                }
9092
            }
9093
        }
9094
9095
        // Make some replacement in $filter. May not be used if we used the ajax mode with $objectfield. In such a case
9096
        // we propagate the $objectfield and not the filter and replacement is done by the ajax/selectobject.php component.
9097
        $sharedentities = (is_object($objecttmp) && property_exists($objecttmp, 'element')) ? getEntity($objecttmp->element) : strtolower($classname);
9098
        $filter = str_replace(
9099
            array('__ENTITY__', '__SHARED_ENTITIES__', '__USER_ID__'),
9100
            array($conf->entity, $sharedentities, $user->id),
9101
            $filter
9102
        );
9103
9104
        if (!is_object($objecttmp)) {
9105
            dol_syslog('selectForForms: Error bad setup of field objectdescorig=' . $objectdescorig . ', objectfield=' . $objectfield . ', objectdesc=' . $objectdesc, LOG_WARNING);
9106
            return 'selectForForms: Error bad setup of field objectdescorig=' . $objectdescorig . ', objectfield=' . $objectfield . ', objectdesc=' . $objectdesc;
9107
        }
9108
        '@phan-var-force CommonObject $objecttmp';
9109
9110
        //var_dump($filter);
9111
        $prefixforautocompletemode = $objecttmp->element;
9112
        if ($prefixforautocompletemode == 'societe') {
9113
            $prefixforautocompletemode = 'company';
9114
        }
9115
        if ($prefixforautocompletemode == 'product') {
9116
            $prefixforautocompletemode = 'produit';
9117
        }
9118
        $confkeyforautocompletemode = strtoupper($prefixforautocompletemode) . '_USE_SEARCH_TO_SELECT'; // For example COMPANY_USE_SEARCH_TO_SELECT
9119
9120
        dol_syslog(get_only_class($this) . "::selectForForms filter=" . $filter, LOG_DEBUG);
9121
9122
        // Generate the combo HTML component
9123
        $out = '';
9124
        if (!empty($conf->use_javascript_ajax) && getDolGlobalString($confkeyforautocompletemode) && !$forcecombo) {
9125
            // No immediate load of all database
9126
            $placeholder = '';
9127
9128
            if ($preSelectedValue && empty($selected_input_value)) {
9129
                $objecttmp->fetch($preSelectedValue);
9130
                $selected_input_value = ($prefixforautocompletemode == 'company' ? $objecttmp->name : $objecttmp->ref);
9131
9132
                $oldValueForShowOnCombobox = 0;
9133
                foreach ($objecttmp->fields as $fieldK => $fielV) {
9134
                    if (empty($fielV['showoncombobox']) || empty($objecttmp->$fieldK)) {
9135
                        continue;
9136
                    }
9137
9138
                    if (!$oldValueForShowOnCombobox) {
9139
                        $selected_input_value = '';
9140
                    }
9141
9142
                    $selected_input_value .= $oldValueForShowOnCombobox ? ' - ' : '';
9143
                    $selected_input_value .= $objecttmp->$fieldK;
9144
                    $oldValueForShowOnCombobox = empty($fielV['showoncombobox']) ? 0 : $fielV['showoncombobox'];
9145
                }
9146
            }
9147
9148
            // Set url and param to call to get json of the search results
9149
            $urlforajaxcall = constant('BASE_URL') . '/core/ajax/selectobject.php';
9150
            $urloption = 'htmlname=' . urlencode($htmlname) . '&outjson=1&objectdesc=' . urlencode($objectdescorig) . '&objectfield=' . urlencode($objectfield) . ($sortfield ? '&sortfield=' . urlencode($sortfield) : '');
9151
9152
            // Activate the auto complete using ajax call.
9153
            $out .= ajax_autocompleter($preSelectedValue, $htmlname, $urlforajaxcall, $urloption, getDolGlobalString($confkeyforautocompletemode), 0);
9154
            $out .= '<!-- force css to be higher than dialog popup --><style type="text/css">.ui-autocomplete { z-index: 1010; }</style>';
9155
            $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) . '"' : '') . ' />';
9156
        } else {
9157
            // Immediate load of table record.
9158
            $out .= $this->selectForFormsList($objecttmp, $htmlname, $preSelectedValue, $showempty, $searchkey, $placeholder, $morecss, $moreparams, $forcecombo, 0, $disabled, $sortfield, $filter);
9159
        }
9160
9161
        return $out;
9162
    }
9163
9164
    /**
9165
     * Output html form to select an object.
9166
     * Note, this function is called by selectForForms or by ajax selectobject.php
9167
     *
9168
     * @param Object $objecttmp Object to know the table to scan for combo.
9169
     * @param string $htmlname Name of HTML select component
9170
     * @param int $preselectedvalue Preselected value (ID of element)
9171
     * @param string|int<0,1> $showempty ''=empty values not allowed, 'string'=value show if we allow empty values (for example 'All', ...)
9172
     * @param string $searchkey Search value
9173
     * @param string $placeholder Place holder
9174
     * @param string $morecss More CSS
9175
     * @param string $moreparams More params provided to ajax call
9176
     * @param int $forcecombo Force to load all values and output a standard combobox (with no beautification)
9177
     * @param int $outputmode 0=HTML select string, 1=Array
9178
     * @param int $disabled 1=Html component is disabled
9179
     * @param string $sortfield Sort field
9180
     * @param string $filter Add more filter (Universal Search Filter)
9181
     * @return string|array                     Return HTML string
9182
     * @see selectForForms()
9183
     */
9184
    public function selectForFormsList($objecttmp, $htmlname, $preselectedvalue, $showempty = '', $searchkey = '', $placeholder = '', $morecss = '', $moreparams = '', $forcecombo = 0, $outputmode = 0, $disabled = 0, $sortfield = '', $filter = '')
9185
    {
9186
        global $langs, $user, $hookmanager;
9187
9188
        //print "$htmlname, $preselectedvalue, $showempty, $searchkey, $placeholder, $morecss, $moreparams, $forcecombo, $outputmode, $disabled";
9189
9190
        $prefixforautocompletemode = $objecttmp->element;
9191
        if ($prefixforautocompletemode == 'societe') {
9192
            $prefixforautocompletemode = 'company';
9193
        }
9194
        $confkeyforautocompletemode = strtoupper($prefixforautocompletemode) . '_USE_SEARCH_TO_SELECT'; // For example COMPANY_USE_SEARCH_TO_SELECT
9195
9196
        if (!empty($objecttmp->fields)) {    // For object that declare it, it is better to use declared fields (like societe, contact, ...)
9197
            $tmpfieldstoshow = '';
9198
            foreach ($objecttmp->fields as $key => $val) {
9199
                if (!(int)dol_eval($val['enabled'], 1, 1, '1')) {
9200
                    continue;
9201
                }
9202
                if (!empty($val['showoncombobox'])) {
9203
                    $tmpfieldstoshow .= ($tmpfieldstoshow ? ',' : '') . 't.' . $key;
9204
                }
9205
            }
9206
            if ($tmpfieldstoshow) {
9207
                $fieldstoshow = $tmpfieldstoshow;
9208
            }
9209
        } else {
9210
            // For backward compatibility
9211
            $objecttmp->fields['ref'] = array('type' => 'varchar(30)', 'label' => 'Ref', 'showoncombobox' => 1);
9212
        }
9213
9214
        if (empty($fieldstoshow)) {
9215
            if (isset($objecttmp->fields['ref'])) {
9216
                $fieldstoshow = 't.ref';
9217
            } else {
9218
                $langs->load("errors");
9219
                $this->error = $langs->trans("ErrorNoFieldWithAttributeShowoncombobox");
9220
                return $langs->trans('ErrorNoFieldWithAttributeShowoncombobox');
9221
            }
9222
        }
9223
9224
        $out = '';
9225
        $outarray = array();
9226
        $tmparray = array();
9227
9228
        $num = 0;
9229
9230
        // Search data
9231
        $sql = "SELECT t.rowid, " . $fieldstoshow . " FROM " . $this->db->prefix() . $objecttmp->table_element . " as t";
9232
        if (!empty($objecttmp->isextrafieldmanaged)) {
9233
            $sql .= " LEFT JOIN " . $this->db->prefix() . $objecttmp->table_element . "_extrafields as e ON t.rowid=e.fk_object";
9234
        }
9235
        if (isset($objecttmp->ismultientitymanaged)) {
9236
            if (!is_numeric($objecttmp->ismultientitymanaged)) {
9237
                $tmparray = explode('@', $objecttmp->ismultientitymanaged);
9238
                $sql .= " INNER JOIN " . $this->db->prefix() . $tmparray[1] . " as parenttable ON parenttable.rowid = t." . $tmparray[0];
9239
            }
9240
            if ($objecttmp->ismultientitymanaged === 'fk_soc@societe') {
9241
                if (!$user->hasRight('societe', 'client', 'voir')) {
9242
                    $sql .= ", " . $this->db->prefix() . "societe_commerciaux as sc";
9243
                }
9244
            }
9245
        }
9246
9247
        // Add where from hooks
9248
        $parameters = array(
9249
            'object' => $objecttmp,
9250
            'htmlname' => $htmlname,
9251
            'filter' => $filter,
9252
            'searchkey' => $searchkey
9253
        );
9254
9255
        $reshook = $hookmanager->executeHooks('selectForFormsListWhere', $parameters); // Note that $action and $object may have been modified by hook
9256
        if (!empty($hookmanager->resPrint)) {
9257
            $sql .= $hookmanager->resPrint;
9258
        } else {
9259
            $sql .= " WHERE 1=1";
9260
            if (isset($objecttmp->ismultientitymanaged)) {
9261
                if ($objecttmp->ismultientitymanaged == 1) {
9262
                    $sql .= " AND t.entity IN (" . getEntity($objecttmp->table_element) . ")";
9263
                }
9264
                if (!is_numeric($objecttmp->ismultientitymanaged)) {
9265
                    $sql .= " AND parenttable.entity = t." . $tmparray[0];
9266
                }
9267
                if ($objecttmp->ismultientitymanaged == 1 && !empty($user->socid)) {
9268
                    if ($objecttmp->element == 'societe') {
9269
                        $sql .= " AND t.rowid = " . ((int)$user->socid);
9270
                    } else {
9271
                        $sql .= " AND t.fk_soc = " . ((int)$user->socid);
9272
                    }
9273
                }
9274
                if ($objecttmp->ismultientitymanaged === 'fk_soc@societe') {
9275
                    if (!$user->hasRight('societe', 'client', 'voir')) {
9276
                        $sql .= " AND t.rowid = sc.fk_soc AND sc.fk_user = " . ((int)$user->id);
9277
                    }
9278
                }
9279
            }
9280
            if ($searchkey != '') {
9281
                $sql .= natural_search(explode(',', $fieldstoshow), $searchkey);
9282
            }
9283
9284
            if ($filter) {     // Syntax example "(t.ref:like:'SO-%') and (t.date_creation:<:'20160101')"
9285
                $errormessage = '';
9286
                $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
9287
                if ($errormessage) {
9288
                    return 'Error forging a SQL request from an universal criteria: ' . $errormessage;
9289
                }
9290
            }
9291
        }
9292
        $sql .= $this->db->order($sortfield ? $sortfield : $fieldstoshow, "ASC");
9293
        //$sql.=$this->db->plimit($limit, 0);
9294
        //print $sql;
9295
9296
        // Build output string
9297
        $resql = $this->db->query($sql);
9298
        if ($resql) {
9299
            // Construct $out and $outarray
9300
            $out .= '<select id="' . $htmlname . '" class="flat minwidth100' . ($morecss ? ' ' . $morecss : '') . '"' . ($disabled ? ' disabled="disabled"' : '') . ($moreparams ? ' ' . $moreparams : '') . ' name="' . $htmlname . '">' . "\n";
9301
9302
            // 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
9303
            $textifempty = '&nbsp;';
9304
9305
            //if (!empty($conf->use_javascript_ajax) || $forcecombo) $textifempty='';
9306
            if (getDolGlobalInt($confkeyforautocompletemode)) {
9307
                if ($showempty && !is_numeric($showempty)) {
9308
                    $textifempty = $langs->trans($showempty);
9309
                } else {
9310
                    $textifempty .= $langs->trans("All");
9311
                }
9312
            }
9313
            if ($showempty) {
9314
                $out .= '<option value="-1">' . $textifempty . '</option>' . "\n";
9315
            }
9316
9317
            $num = $this->db->num_rows($resql);
9318
            $i = 0;
9319
            if ($num) {
9320
                while ($i < $num) {
9321
                    $obj = $this->db->fetch_object($resql);
9322
                    $label = '';
9323
                    $labelhtml = '';
9324
                    $tmparray = explode(',', $fieldstoshow);
9325
                    $oldvalueforshowoncombobox = 0;
9326
                    foreach ($tmparray as $key => $val) {
9327
                        $val = preg_replace('/t\./', '', $val);
9328
                        $label .= (($label && $obj->$val) ? ($oldvalueforshowoncombobox != $objecttmp->fields[$val]['showoncombobox'] ? ' - ' : ' ') : '');
9329
                        $labelhtml .= (($label && $obj->$val) ? ($oldvalueforshowoncombobox != $objecttmp->fields[$val]['showoncombobox'] ? ' - ' : ' ') : '');
9330
                        $label .= $obj->$val;
9331
                        $labelhtml .= $obj->$val;
9332
9333
                        $oldvalueforshowoncombobox = empty($objecttmp->fields[$val]['showoncombobox']) ? 0 : $objecttmp->fields[$val]['showoncombobox'];
9334
                    }
9335
                    if (empty($outputmode)) {
9336
                        if ($preselectedvalue > 0 && $preselectedvalue == $obj->rowid) {
9337
                            $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>';
9338
                        } else {
9339
                            $out .= '<option value="' . $obj->rowid . '" data-html="' . dol_escape_htmltag($labelhtml, 0, 0, '', 0, 1) . '">' . dol_escape_htmltag($label, 0, 0, '', 0, 1) . '</option>';
9340
                        }
9341
                    } else {
9342
                        array_push($outarray, array('key' => $obj->rowid, 'value' => $label, 'label' => $label));
9343
                    }
9344
9345
                    $i++;
9346
                    if (($i % 10) == 0) {
9347
                        $out .= "\n";
9348
                    }
9349
                }
9350
            }
9351
9352
            $out .= '</select>' . "\n";
9353
9354
            if (!$forcecombo) {
9355
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
9356
                $out .= ajax_combobox($htmlname, array(), getDolGlobalInt($confkeyforautocompletemode, 0));
9357
            }
9358
        } else {
9359
            dol_print_error($this->db);
9360
        }
9361
9362
        $this->result = array('nbofelement' => $num);
9363
9364
        if ($outputmode) {
9365
            return $outarray;
9366
        }
9367
        return $out;
9368
    }
9369
9370
    /**
9371
     * Render list of categories linked to object with id $id and type $type
9372
     *
9373
     * @param int $id Id of object
9374
     * @param string $type Type of category ('member', 'customer', 'supplier', 'product', 'contact'). Old mode (0, 1, 2, ...) is deprecated.
9375
     * @param int<0,1> $rendermode 0=Default, use multiselect. 1=Emulate multiselect (recommended)
9376
     * @param int<0,1> $nolink 1=Do not add html links
9377
     * @return string               String with categories
9378
     */
9379
    public function showCategories($id, $type, $rendermode = 0, $nolink = 0)
9380
    {
9381
        $cat = new Categorie($this->db);
9382
        $categories = $cat->containing($id, $type);
9383
9384
        if ($rendermode == 1) {
9385
            $toprint = array();
9386
            foreach ($categories as $c) {
9387
                $ways = $c->print_all_ways(' &gt;&gt; ', ($nolink ? 'none' : ''), 0, 1); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
9388
                foreach ($ways as $way) {
9389
                    $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #bbb"') . '>' . $way . '</li>';
9390
                }
9391
            }
9392
            return '<div class="select2-container-multi-dolibarr"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
9393
        }
9394
9395
        if ($rendermode == 0) {
9396
            $arrayselected = array();
9397
            $cate_arbo = $this->select_all_categories($type, '', 'parent', 64, 0, 3);
9398
            foreach ($categories as $c) {
9399
                $arrayselected[] = $c->id;
9400
            }
9401
9402
            return $this->multiselectarray('categories', $cate_arbo, $arrayselected, 0, 0, '', 0, '100%', 'disabled', 'category');
9403
        }
9404
9405
        return 'ErrorBadValueForParameterRenderMode'; // Should not happened
9406
    }
9407
9408
    /**
9409
     * Return list of categories having chosen type
9410
     *
9411
     * @param string|int $type Type of category ('customer', 'supplier', 'contact', 'product', 'member'). Old mode (0, 1, 2, ...) is deprecated.
9412
     * @param string $selected Id of category preselected or 'auto' (autoselect category if there is only one element). Not used if $outputmode = 1.
9413
     * @param string $htmlname HTML field name
9414
     * @param int $maxlength Maximum length for labels
9415
     * @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.
9416
     *                                              $fromid can be an :
9417
     *                                              - int (id of category)
9418
     *                                              - string (categories ids separated by comma)
9419
     *                                              - array (list of categories ids)
9420
     * @param int<0,3> $outputmode 0=HTML select string, 1=Array with full label only, 2=Array extended, 3=Array with full picto + label
9421
     * @param int<0,1> $include [=0] Removed or 1=Keep only
9422
     * @param string $morecss More CSS
9423
     * @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.
9424
     * @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
9425
     * @see select_categories()
9426
     */
9427
    public function select_all_categories($type, $selected = '', $htmlname = "parent", $maxlength = 64, $fromid = 0, $outputmode = 0, $include = 0, $morecss = '', $useempty = 1)
9428
    {
9429
        // phpcs:enable
9430
        global $conf, $langs;
9431
        $langs->load("categories");
9432
9433
        // For backward compatibility
9434
        if (is_numeric($type)) {
9435
            dol_syslog(__METHOD__ . ': using numeric value for parameter type is deprecated. Use string code instead.', LOG_WARNING);
9436
        }
9437
9438
        if ($type === Categorie::TYPE_BANK_LINE) {
9439
            // TODO Move this into common category feature
9440
            $cate_arbo = array();
9441
            $sql = "SELECT c.label, c.rowid";
9442
            $sql .= " FROM " . $this->db->prefix() . "bank_categ as c";
9443
            $sql .= " WHERE entity = " . $conf->entity;
9444
            $sql .= " ORDER BY c.label";
9445
            $result = $this->db->query($sql);
9446
            if ($result) {
9447
                $num = $this->db->num_rows($result);
9448
                $i = 0;
9449
                while ($i < $num) {
9450
                    $objp = $this->db->fetch_object($result);
9451
                    if ($objp) {
9452
                        $cate_arbo[$objp->rowid] = array('id' => $objp->rowid, 'fulllabel' => $objp->label, 'color' => '', 'picto' => 'category');
9453
                    }
9454
                    $i++;
9455
                }
9456
                $this->db->free($result);
9457
            } else {
9458
                dol_print_error($this->db);
9459
            }
9460
        } else {
9461
            $cat = new Categorie($this->db);
9462
            $cate_arbo = $cat->get_full_arbo($type, $fromid, $include);
9463
        }
9464
9465
        $outarray = array();
9466
        $outarrayrichhtml = array();
9467
9468
9469
        $output = '<select class="flat minwidth100' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '" id="' . $htmlname . '">';
9470
        if (is_array($cate_arbo)) {
9471
            $num = count($cate_arbo);
9472
9473
            if (!$num) {
9474
                $output .= '<option value="-1" disabled>' . $langs->trans("NoCategoriesDefined") . '</option>';
9475
            } else {
9476
                if ($useempty == 1 || ($useempty == 2 && $num > 1)) {
9477
                    $output .= '<option value="-1">&nbsp;</option>';
9478
                }
9479
                foreach ($cate_arbo as $key => $value) {
9480
                    if ($cate_arbo[$key]['id'] == $selected || ($selected === 'auto' && count($cate_arbo) == 1)) {
9481
                        $add = 'selected ';
9482
                    } else {
9483
                        $add = '';
9484
                    }
9485
9486
                    $labeltoshow = img_picto('', 'category', 'class="pictofixedwidth" style="color: #' . $cate_arbo[$key]['color'] . '"');
9487
                    $labeltoshow .= dol_trunc($cate_arbo[$key]['fulllabel'], $maxlength, 'middle');
9488
9489
                    $outarray[$cate_arbo[$key]['id']] = $cate_arbo[$key]['fulllabel'];
9490
9491
                    $outarrayrichhtml[$cate_arbo[$key]['id']] = $labeltoshow;
9492
9493
                    $output .= '<option ' . $add . 'value="' . $cate_arbo[$key]['id'] . '"';
9494
                    $output .= ' data-html="' . dol_escape_htmltag($labeltoshow) . '"';
9495
                    $output .= '>';
9496
                    $output .= dol_trunc($cate_arbo[$key]['fulllabel'], $maxlength, 'middle');
9497
                    $output .= '</option>';
9498
9499
                    $cate_arbo[$key]['data-html'] = $labeltoshow;
9500
                }
9501
            }
9502
        }
9503
        $output .= '</select>';
9504
        $output .= "\n";
9505
9506
        if ($outputmode == 2) {
9507
            // TODO: handle error when $cate_arbo is not an array
9508
            return $cate_arbo;
9509
        } elseif ($outputmode == 1) {
9510
            return $outarray;
9511
        } elseif ($outputmode == 3) {
9512
            return $outarrayrichhtml;
9513
        }
9514
        return $output;
9515
    }
9516
9517
    /**
9518
     *  Show linked object block.
9519
     *
9520
     * @param CommonObject $object Object we want to show links to
9521
     * @param string $morehtmlright More html to show on right of title
9522
     * @param array<int,string> $compatibleImportElementsList Array of compatibles elements object for "import from" action
9523
     * @param string $title Title
9524
     * @return  int                                             Return Number of different types
9525
     */
9526
    public function showLinkedObjectBlock($object, $morehtmlright = '', $compatibleImportElementsList = array(), $title = 'RelatedObjects')
9527
    {
9528
        global $conf, $langs, $hookmanager;
9529
        global $bc, $action;
9530
9531
        $object->fetchObjectLinked();
9532
9533
        // Bypass the default method
9534
        $hookmanager->initHooks(array('commonobject'));
9535
        $parameters = array(
9536
            'morehtmlright' => $morehtmlright,
9537
            'compatibleImportElementsList' => &$compatibleImportElementsList,
9538
        );
9539
        $reshook = $hookmanager->executeHooks('showLinkedObjectBlock', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
9540
9541
        $nbofdifferenttypes = count($object->linkedObjects);
9542
9543
        if (empty($reshook)) {
9544
            print '<!-- showLinkedObjectBlock -->';
9545
            print load_fiche_titre($langs->trans($title), $morehtmlright, '', 0, 0, 'showlinkedobjectblock');
9546
9547
9548
            print '<div class="div-table-responsive-no-min">';
9549
            print '<table class="noborder allwidth" data-block="showLinkedObject" data-element="' . $object->element . '"  data-elementid="' . $object->id . '"   >';
9550
9551
            print '<tr class="liste_titre">';
9552
            print '<td>' . $langs->trans("Type") . '</td>';
9553
            print '<td>' . $langs->trans("Ref") . '</td>';
9554
            print '<td class="center"></td>';
9555
            print '<td class="center">' . $langs->trans("Date") . '</td>';
9556
            print '<td class="right">' . $langs->trans("AmountHTShort") . '</td>';
9557
            print '<td class="right">' . $langs->trans("Status") . '</td>';
9558
            print '<td></td>';
9559
            print '</tr>';
9560
9561
            $nboftypesoutput = 0;
9562
9563
            foreach ($object->linkedObjects as $objecttype => $objects) {
9564
                $tplpath = $element = $subelement = $objecttype;
9565
9566
                // to display import button on tpl
9567
                $showImportButton = false;
9568
                if (!empty($compatibleImportElementsList) && in_array($element, $compatibleImportElementsList)) {
9569
                    $showImportButton = true;
9570
                }
9571
9572
                $regs = array();
9573
                if ($objecttype != 'supplier_proposal' && preg_match('/^([^_]+)_([^_]+)/i', $objecttype, $regs)) {
9574
                    $element = $regs[1];
9575
                    $subelement = $regs[2];
9576
                    $tplpath = $element . '/' . $subelement;
9577
                }
9578
                $tplname = 'linkedobjectblock';
9579
9580
                // To work with non standard path
9581
                if ($objecttype == 'facture') {
9582
                    $tplpath = 'compta/' . $element;
9583
                    if (!isModEnabled('invoice')) {
9584
                        continue; // Do not show if module disabled
9585
                    }
9586
                } elseif ($objecttype == 'facturerec') {
9587
                    $tplpath = 'compta/facture';
9588
                    $tplname = 'linkedobjectblockForRec';
9589
                    if (!isModEnabled('invoice')) {
9590
                        continue; // Do not show if module disabled
9591
                    }
9592
                } elseif ($objecttype == 'propal') {
9593
                    $tplpath = 'comm/' . $element;
9594
                    if (!isModEnabled('propal')) {
9595
                        continue; // Do not show if module disabled
9596
                    }
9597
                } elseif ($objecttype == 'supplier_proposal') {
9598
                    if (!isModEnabled('supplier_proposal')) {
9599
                        continue; // Do not show if module disabled
9600
                    }
9601
                } elseif ($objecttype == 'shipping' || $objecttype == 'shipment' || $objecttype == 'expedition') {
9602
                    $tplpath = 'expedition';
9603
                    if (!isModEnabled('shipping')) {
9604
                        continue; // Do not show if module disabled
9605
                    }
9606
                } elseif ($objecttype == 'reception') {
9607
                    $tplpath = 'reception';
9608
                    if (!isModEnabled('reception')) {
9609
                        continue; // Do not show if module disabled
9610
                    }
9611
                } elseif ($objecttype == 'delivery') {
9612
                    $tplpath = 'delivery';
9613
                    if (!getDolGlobalInt('MAIN_SUBMODULE_DELIVERY')) {
9614
                        continue; // Do not show if sub module disabled
9615
                    }
9616
                } elseif ($objecttype == 'ficheinter') {
9617
                    $tplpath = 'fichinter';
9618
                    if (!isModEnabled('intervention')) {
9619
                        continue; // Do not show if module disabled
9620
                    }
9621
                } elseif ($objecttype == 'invoice_supplier') {
9622
                    $tplpath = 'fourn/facture';
9623
                } elseif ($objecttype == 'order_supplier') {
9624
                    $tplpath = 'fourn/commande';
9625
                } elseif ($objecttype == 'expensereport') {
9626
                    $tplpath = 'expensereport';
9627
                } elseif ($objecttype == 'subscription') {
9628
                    $tplpath = 'adherents';
9629
                } elseif ($objecttype == 'conferenceorbooth') {
9630
                    $tplpath = 'eventorganization';
9631
                } elseif ($objecttype == 'conferenceorboothattendee') {
9632
                    $tplpath = 'eventorganization';
9633
                } elseif ($objecttype == 'mo') {
9634
                    $tplpath = 'mrp';
9635
                    if (!isModEnabled('mrp')) {
9636
                        continue; // Do not show if module disabled
9637
                    }
9638
                }
9639
9640
                global $linkedObjectBlock;
9641
                $linkedObjectBlock = $objects;
9642
9643
                // Output template part (modules that overwrite templates must declare this into descriptor)
9644
                $dirtpls = array_merge($conf->modules_parts['tpl'], array('/' . $tplpath . '/tpl'));
9645
                foreach ($dirtpls as $reldir) {
9646
                    $reldir = rtrim($reldir, '/');
9647
                    if ($nboftypesoutput == ($nbofdifferenttypes - 1)) {    // No more type to show after
9648
                        global $noMoreLinkedObjectBlockAfter;
9649
                        $noMoreLinkedObjectBlockAfter = 1;
9650
                    }
9651
9652
                    $res = @include dol_buildpath($reldir . '/' . $tplname . '.tpl.php');
9653
                    if ($res) {
9654
                        $nboftypesoutput++;
9655
                        break;
9656
                    }
9657
                }
9658
            }
9659
9660
            if (!$nboftypesoutput) {
9661
                print '<tr><td class="impair" colspan="7"><span class="opacitymedium">' . $langs->trans("None") . '</span></td></tr>';
9662
            }
9663
9664
            print '</table>';
9665
9666
            if (!empty($compatibleImportElementsList)) {
9667
                $res = @include dol_buildpath('core/tpl/objectlinked_lineimport.tpl.php');
9668
            }
9669
9670
            print '</div>';
9671
        }
9672
9673
        return $nbofdifferenttypes;
9674
    }
9675
9676
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
9677
9678
    /**
9679
     *  Show block with links to link to other objects.
9680
     *
9681
     * @param CommonObject $object Object we want to show links to
9682
     * @param string[] $restrictlinksto Restrict links to some elements, for example array('order') or array('supplier_order'). null or array() if no restriction.
9683
     * @param string[] $excludelinksto Do not show links of this type, for example array('order') or array('supplier_order'). null or array() if no exclusion.
9684
     * @return  string                              HTML block
9685
     */
9686
    public function showLinkToObjectBlock($object, $restrictlinksto = array(), $excludelinksto = array())
9687
    {
9688
        global $conf, $langs, $hookmanager;
9689
        global $action;
9690
9691
        $linktoelem = '';
9692
        $linktoelemlist = '';
9693
        $listofidcompanytoscan = '';
9694
9695
        if (!is_object($object->thirdparty)) {
9696
            $object->fetch_thirdparty();
9697
        }
9698
9699
        $possiblelinks = array();
9700
        if (is_object($object->thirdparty) && !empty($object->thirdparty->id) && $object->thirdparty->id > 0) {
9701
            $listofidcompanytoscan = $object->thirdparty->id;
9702
            if (($object->thirdparty->parent > 0) && getDolGlobalString('THIRDPARTY_INCLUDE_PARENT_IN_LINKTO')) {
9703
                $listofidcompanytoscan .= ',' . $object->thirdparty->parent;
9704
            }
9705
            if (($object->fk_project > 0) && getDolGlobalString('THIRDPARTY_INCLUDE_PROJECT_THIRDPARY_IN_LINKTO')) {
9706
                $tmpproject = new Project($this->db);
9707
                $tmpproject->fetch($object->fk_project);
9708
                if ($tmpproject->socid > 0 && ($tmpproject->socid != $object->thirdparty->id)) {
9709
                    $listofidcompanytoscan .= ',' . $tmpproject->socid;
9710
                }
9711
                unset($tmpproject);
9712
            }
9713
9714
            $possiblelinks = array(
9715
                'propal' => array(
9716
                    'enabled' => isModEnabled('propal'),
9717
                    'perms' => 1,
9718
                    'label' => 'LinkToProposal',
9719
                    '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') . ')'),
9720
                'shipping' => array(
9721
                    'enabled' => isModEnabled('shipping'),
9722
                    'perms' => 1,
9723
                    'label' => 'LinkToExpedition',
9724
                    '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') . ')'),
9725
                'order' => array(
9726
                    'enabled' => isModEnabled('order'),
9727
                    'perms' => 1,
9728
                    'label' => 'LinkToOrder',
9729
                    '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') . ')'),
9730
                'invoice' => array(
9731
                    'enabled' => isModEnabled('invoice'),
9732
                    'perms' => 1,
9733
                    'label' => 'LinkToInvoice',
9734
                    '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') . ')'),
9735
                'invoice_template' => array(
9736
                    'enabled' => isModEnabled('invoice'),
9737
                    'perms' => 1,
9738
                    'label' => 'LinkToTemplateInvoice',
9739
                    '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') . ')'),
9740
                'contrat' => array(
9741
                    'enabled' => isModEnabled('contract'),
9742
                    'perms' => 1,
9743
                    'label' => 'LinkToContract',
9744
                    '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
9745
							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'
9746
                ),
9747
                'fichinter' => array(
9748
                    'enabled' => isModEnabled('intervention'),
9749
                    'perms' => 1,
9750
                    'label' => 'LinkToIntervention',
9751
                    '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') . ')'),
9752
                'supplier_proposal' => array(
9753
                    'enabled' => isModEnabled('supplier_proposal'),
9754
                    'perms' => 1,
9755
                    'label' => 'LinkToSupplierProposal',
9756
                    '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') . ')'),
9757
                'order_supplier' => array(
9758
                    'enabled' => isModEnabled("supplier_order"),
9759
                    'perms' => 1,
9760
                    'label' => 'LinkToSupplierOrder',
9761
                    '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') . ')'),
9762
                'invoice_supplier' => array(
9763
                    'enabled' => isModEnabled("supplier_invoice"),
9764
                    'perms' => 1, 'label' => 'LinkToSupplierInvoice',
9765
                    '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') . ')'),
9766
                'ticket' => array(
9767
                    'enabled' => isModEnabled('ticket'),
9768
                    'perms' => 1,
9769
                    'label' => 'LinkToTicket',
9770
                    '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') . ')'),
9771
                'mo' => array(
9772
                    'enabled' => isModEnabled('mrp'),
9773
                    'perms' => 1,
9774
                    'label' => 'LinkToMo',
9775
                    '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') . ')')
9776
            );
9777
        }
9778
9779
        if ($object->table_element == 'commande_fournisseur') {
9780
            $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') . ')';
9781
        } elseif ($object->table_element == 'mrp_mo') {
9782
            $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') . ')';
9783
        }
9784
9785
        $reshook = 0; // Ensure $reshook is defined for static analysis
9786
        if (!empty($listofidcompanytoscan)) {  // If empty, we don't have criteria to scan the object we can link to
9787
            // Can complete the possiblelink array
9788
            $hookmanager->initHooks(array('commonobject'));
9789
            $parameters = array('listofidcompanytoscan' => $listofidcompanytoscan, 'possiblelinks' => $possiblelinks);
9790
            $reshook = $hookmanager->executeHooks('showLinkToObjectBlock', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
9791
        }
9792
9793
        if (empty($reshook)) {
9794
            if (is_array($hookmanager->resArray) && count($hookmanager->resArray)) {
9795
                $possiblelinks = array_merge($possiblelinks, $hookmanager->resArray);
9796
            }
9797
        } elseif ($reshook > 0) {
9798
            if (is_array($hookmanager->resArray) && count($hookmanager->resArray)) {
9799
                $possiblelinks = $hookmanager->resArray;
9800
            }
9801
        }
9802
9803
        foreach ($possiblelinks as $key => $possiblelink) {
9804
            $num = 0;
9805
9806
            if (empty($possiblelink['enabled'])) {
9807
                continue;
9808
            }
9809
9810
            if (!empty($possiblelink['perms']) && (empty($restrictlinksto) || in_array($key, $restrictlinksto)) && (empty($excludelinksto) || !in_array($key, $excludelinksto))) {
9811
                print '<div id="' . $key . 'list"' . (empty($conf->use_javascript_ajax) ? '' : ' style="display:none"') . '>';
9812
9813
                if (getDolGlobalString('MAIN_LINK_BY_REF_IN_LINKTO')) {
9814
                    print '<br>' . "\n";
9815
                    print '<!-- form to add a link from anywhere -->' . "\n";
9816
                    print '<form action="' . $_SERVER["PHP_SELF"] . '" method="POST" name="formlinkedbyref' . $key . '">';
9817
                    print '<input type="hidden" name="id" value="' . $object->id . '">';
9818
                    print '<input type="hidden" name="action" value="addlinkbyref">';
9819
                    print '<input type="hidden" name="token" value="' . newToken() . '">';
9820
                    print '<input type="hidden" name="addlink" value="' . $key . '">';
9821
                    print '<table class="noborder">';
9822
                    print '<tr>';
9823
                    //print '<td>' . $langs->trans("Ref") . '</td>';
9824
                    print '<td class="center"><input type="text" placeholder="' . dol_escape_htmltag($langs->trans("Ref")) . '" name="reftolinkto" value="' . dol_escape_htmltag(GETPOST('reftolinkto', 'alpha')) . '">&nbsp;';
9825
                    print '<input type="submit" class="button small valignmiddle" value="' . $langs->trans('ToLink') . '">&nbsp;';
9826
                    print '<input type="submit" class="button small" name="cancel" value="' . $langs->trans('Cancel') . '"></td>';
9827
                    print '</tr>';
9828
                    print '</table>';
9829
                    print '</form>';
9830
                }
9831
9832
                $sql = $possiblelink['sql'];
9833
9834
                $resqllist = $this->db->query($sql);
9835
                if ($resqllist) {
9836
                    $num = $this->db->num_rows($resqllist);
9837
                    $i = 0;
9838
9839
                    print '<br>';
9840
                    print '<!-- form to add a link from object to same thirdparty -->' . "\n";
9841
                    print '<form action="' . $_SERVER["PHP_SELF"] . '" method="POST" name="formlinked' . $key . '">';
9842
                    print '<input type="hidden" name="action" value="addlink">';
9843
                    print '<input type="hidden" name="token" value="' . newToken() . '">';
9844
                    print '<input type="hidden" name="id" value="' . $object->id . '">';
9845
                    print '<input type="hidden" name="addlink" value="' . $key . '">';
9846
                    print '<table class="noborder">';
9847
                    print '<tr class="liste_titre">';
9848
                    print '<td class="nowrap"></td>';
9849
                    print '<td class="center">' . $langs->trans("Ref") . '</td>';
9850
                    print '<td class="left">' . $langs->trans("RefCustomer") . '</td>';
9851
                    print '<td class="right">' . $langs->trans("AmountHTShort") . '</td>';
9852
                    print '<td class="left">' . $langs->trans("Company") . '</td>';
9853
                    print '</tr>';
9854
                    while ($i < $num) {
9855
                        $objp = $this->db->fetch_object($resqllist);
9856
9857
                        print '<tr class="oddeven">';
9858
                        print '<td class="left">';
9859
                        print '<input type="radio" name="idtolinkto" id="' . $key . '_' . $objp->rowid . '" value="' . $objp->rowid . '">';
9860
                        print '</td>';
9861
                        print '<td class="center"><label for="' . $key . '_' . $objp->rowid . '">' . $objp->ref . '</label></td>';
9862
                        print '<td>' . (!empty($objp->ref_client) ? $objp->ref_client : (!empty($objp->ref_supplier) ? $objp->ref_supplier : '')) . '</td>';
9863
                        print '<td class="right">';
9864
                        if ($possiblelink['label'] == 'LinkToContract') {
9865
                            $form = new Form($this->db);
9866
                            print $form->textwithpicto('', $langs->trans("InformationOnLinkToContract")) . ' ';
9867
                        }
9868
                        print '<span class="amount">' . (isset($objp->total_ht) ? price($objp->total_ht) : '') . '</span>';
9869
                        print '</td>';
9870
                        print '<td>' . $objp->name . '</td>';
9871
                        print '</tr>';
9872
                        $i++;
9873
                    }
9874
                    print '</table>';
9875
                    print '<div class="center">';
9876
                    if ($num) {
9877
                        print '<input type="submit" class="button valignmiddle marginleftonly marginrightonly small" value="' . $langs->trans('ToLink') . '">';
9878
                    }
9879
                    if (empty($conf->use_javascript_ajax)) {
9880
                        print '<input type="submit" class="button button-cancel marginleftonly marginrightonly small" name="cancel" value="' . $langs->trans("Cancel") . '"></div>';
9881
                    } else {
9882
                        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>';
9883
                    }
9884
                    print '</form>';
9885
                    $this->db->free($resqllist);
9886
                } else {
9887
                    dol_print_error($this->db);
9888
                }
9889
                print '</div>';
9890
9891
                //$linktoelem.=($linktoelem?' &nbsp; ':'');
9892
                if ($num > 0 || getDolGlobalString('MAIN_LINK_BY_REF_IN_LINKTO')) {
9893
                    $linktoelemlist .= '<li><a href="#linkto' . $key . '" class="linkto dropdowncloseonclick" rel="' . $key . '">' . $langs->trans($possiblelink['label']) . ' (' . $num . ')</a></li>';
9894
                    // } else $linktoelem.=$langs->trans($possiblelink['label']);
9895
                } else {
9896
                    $linktoelemlist .= '<li><span class="linktodisabled">' . $langs->trans($possiblelink['label']) . ' (0)</span></li>';
9897
                }
9898
            }
9899
        }
9900
9901
        if ($linktoelemlist) {
9902
            $linktoelem = '
9903
    		<dl class="dropdown" id="linktoobjectname">
9904
    		';
9905
            if (!empty($conf->use_javascript_ajax)) {
9906
                $linktoelem .= '<dt><a href="#linktoobjectname"><span class="fas fa-link paddingrightonly"></span>' . $langs->trans("LinkTo") . '...</a></dt>';
9907
            }
9908
            $linktoelem .= '<dd>
9909
    		<div class="multiselectlinkto">
9910
    		<ul class="ulselectedfields">' . $linktoelemlist . '
9911
    		</ul>
9912
    		</div>
9913
    		</dd>
9914
    		</dl>';
9915
        } else {
9916
            $linktoelem = '';
9917
        }
9918
9919
        if (!empty($conf->use_javascript_ajax)) {
9920
            print '<!-- Add js to show linkto box -->
9921
				<script nonce="' . getNonce() . '">
9922
				jQuery(document).ready(function() {
9923
					jQuery(".linkto").click(function() {
9924
						console.log("We choose to show/hide links for rel="+jQuery(this).attr(\'rel\')+" so #"+jQuery(this).attr(\'rel\')+"list");
9925
					    jQuery("#"+jQuery(this).attr(\'rel\')+"list").toggle();
9926
					});
9927
				});
9928
				</script>
9929
		    ';
9930
        }
9931
9932
        return $linktoelem;
9933
    }
9934
9935
    /**
9936
     *  Return list of export templates
9937
     *
9938
     * @param string $selected Id modele pre-selectionne
9939
     * @param string $htmlname Name of HTML select
9940
     * @param string $type Type of searched templates
9941
     * @param int $useempty Affiche valeur vide dans liste
9942
     * @return    void
9943
     */
9944
    public function select_export_model($selected = '', $htmlname = 'exportmodelid', $type = '', $useempty = 0)
9945
    {
9946
        // phpcs:enable
9947
        $sql = "SELECT rowid, label";
9948
        $sql .= " FROM " . $this->db->prefix() . "export_model";
9949
        $sql .= " WHERE type = '" . $this->db->escape($type) . "'";
9950
        $sql .= " ORDER BY rowid";
9951
        $result = $this->db->query($sql);
9952
        if ($result) {
9953
            print '<select class="flat" id="select_' . $htmlname . '" name="' . $htmlname . '">';
9954
            if ($useempty) {
9955
                print '<option value="-1">&nbsp;</option>';
9956
            }
9957
9958
            $num = $this->db->num_rows($result);
9959
            $i = 0;
9960
            while ($i < $num) {
9961
                $obj = $this->db->fetch_object($result);
9962
                if ($selected == $obj->rowid) {
9963
                    print '<option value="' . $obj->rowid . '" selected>';
9964
                } else {
9965
                    print '<option value="' . $obj->rowid . '">';
9966
                }
9967
                print $obj->label;
9968
                print '</option>';
9969
                $i++;
9970
            }
9971
            print "</select>";
9972
        } else {
9973
            dol_print_error($this->db);
9974
        }
9975
    }
9976
9977
    /**
9978
     * Return a HTML area with the reference of object and a navigation bar for a business object
9979
     * Note: To complete search with a particular filter on select, you can set $object->next_prev_filter set to define SQL criteria.
9980
     *
9981
     * @param CommonObject $object Object to show.
9982
     * @param string $paramid Name of parameter to use to name the id into the URL next/previous link.
9983
     * @param string $morehtml More html content to output just before the nav bar.
9984
     * @param int<0,1> $shownav Show Condition (navigation is shown if value is 1).
9985
     * @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.
9986
     * @param string $fieldref Name of field ref of object (object->ref) to show or 'none' to not show ref.
9987
     * @param string $morehtmlref More html to show after ref.
9988
     * @param string $moreparam More param to add in nav link url. Must start with '&...'.
9989
     * @param int<0,1> $nodbprefix Do not include DB prefix to forge table name.
9990
     * @param string $morehtmlleft More html code to show before ref.
9991
     * @param string $morehtmlstatus More html code to show under navigation arrows (status place).
9992
     * @param string $morehtmlright More html code to show after ref.
9993
     * @return string                   Portion HTML with ref + navigation buttons
9994
     */
9995
    public function showrefnav($object, $paramid, $morehtml = '', $shownav = 1, $fieldid = 'rowid', $fieldref = 'ref', $morehtmlref = '', $moreparam = '', $nodbprefix = 0, $morehtmlleft = '', $morehtmlstatus = '', $morehtmlright = '')
9996
    {
9997
        global $conf, $langs, $hookmanager, $extralanguages;
9998
9999
        $ret = '';
10000
        if (empty($fieldid)) {
10001
            $fieldid = 'rowid';
10002
        }
10003
        if (empty($fieldref)) {
10004
            $fieldref = 'ref';
10005
        }
10006
10007
        // Preparing gender's display if there is one
10008
        $addgendertxt = '';
10009
        if (property_exists($object, 'gender') && !empty($object->gender)) {
0 ignored issues
show
Bug Best Practice introduced by
The property gender does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
10010
            $addgendertxt = ' ';
10011
            switch ($object->gender) {
10012
                case 'man':
10013
                    $addgendertxt .= '<i class="fas fa-mars"></i>';
10014
                    break;
10015
                case 'woman':
10016
                    $addgendertxt .= '<i class="fas fa-venus"></i>';
10017
                    break;
10018
                case 'other':
10019
                    $addgendertxt .= '<i class="fas fa-transgender"></i>';
10020
                    break;
10021
            }
10022
        }
10023
10024
        // Add where from hooks
10025
        if (is_object($hookmanager)) {
10026
            $parameters = array('showrefnav' => true);
10027
            $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $object); // Note that $action and $object may have been modified by hook
10028
            $object->next_prev_filter .= $hookmanager->resPrint;
10029
        }
10030
10031
        $previous_ref = $next_ref = '';
10032
        if ($shownav) {
10033
            //print "paramid=$paramid,morehtml=$morehtml,shownav=$shownav,$fieldid,$fieldref,$morehtmlref,$moreparam";
10034
            $object->load_previous_next_ref((isset($object->next_prev_filter) ? $object->next_prev_filter : ''), $fieldid, $nodbprefix);
10035
10036
            $navurl = $_SERVER["PHP_SELF"];
10037
            // Special case for project/task page
10038
            if ($paramid == 'project_ref') {
10039
                if (preg_match('/\/tasks\/(task|contact|note|document)\.php/', $navurl)) {     // TODO Remove this when nav with project_ref on task pages are ok
10040
                    $navurl = preg_replace('/\/tasks\/(task|contact|time|note|document)\.php/', '/tasks.php', $navurl);
10041
                    $paramid = 'ref';
10042
                }
10043
            }
10044
10045
            // accesskey is for Windows or Linux:  ALT + key for chrome, ALT + SHIFT + KEY for firefox
10046
            // accesskey is for Mac:               CTRL + key for all browsers
10047
            $stringforfirstkey = $langs->trans("KeyboardShortcut");
10048
            if ($conf->browser->name == 'chrome') {
10049
                $stringforfirstkey .= ' ALT +';
10050
            } elseif ($conf->browser->name == 'firefox') {
10051
                $stringforfirstkey .= ' ALT + SHIFT +';
10052
            } else {
10053
                $stringforfirstkey .= ' CTL +';
10054
            }
10055
10056
            $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>';
10057
            $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>';
10058
        }
10059
10060
        //print "xx".$previous_ref."x".$next_ref;
10061
        $ret .= '<!-- Start banner content --><div style="vertical-align: middle">';
10062
10063
        // Right part of banner
10064
        if ($morehtmlright) {
10065
            $ret .= '<div class="inline-block floatleft">' . $morehtmlright . '</div>';
10066
        }
10067
10068
        if ($previous_ref || $next_ref || $morehtml) {
10069
            $ret .= '<div class="pagination paginationref"><ul class="right">';
10070
        }
10071
        if ($morehtml && getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2) {
10072
            $ret .= '<!-- morehtml --><li class="noborder litext' . (($shownav && $previous_ref && $next_ref) ? ' clearbothonsmartphone' : '') . '">' . $morehtml . '</li>';
10073
        }
10074
        if ($shownav && ($previous_ref || $next_ref)) {
10075
            $ret .= '<li class="pagination">' . $previous_ref . '</li>';
10076
            $ret .= '<li class="pagination">' . $next_ref . '</li>';
10077
        }
10078
        if ($previous_ref || $next_ref || $morehtml) {
10079
            $ret .= '</ul></div>';
10080
        }
10081
10082
        // Status
10083
        $parameters = array('morehtmlstatus' => $morehtmlstatus);
10084
        $reshook = $hookmanager->executeHooks('moreHtmlStatus', $parameters, $object); // Note that $action and $object may have been modified by hook
10085
        if (empty($reshook)) {
10086
            $morehtmlstatus .= $hookmanager->resPrint;
10087
        } else {
10088
            $morehtmlstatus = $hookmanager->resPrint;
10089
        }
10090
        if ($morehtmlstatus) {
10091
            $ret .= '<div class="statusref">' . $morehtmlstatus . '</div>';
10092
        }
10093
10094
        $parameters = array();
10095
        $reshook = $hookmanager->executeHooks('moreHtmlRef', $parameters, $object); // Note that $action and $object may have been modified by hook
10096
        if (empty($reshook)) {
10097
            $morehtmlref .= $hookmanager->resPrint;
10098
        } elseif ($reshook > 0) {
10099
            $morehtmlref = $hookmanager->resPrint;
10100
        }
10101
10102
        // Left part of banner
10103
        if ($morehtmlleft) {
10104
            if ($conf->browser->layout == 'phone') {
10105
                $ret .= '<!-- morehtmlleft --><div class="floatleft">' . $morehtmlleft . '</div>';
10106
            } else {
10107
                $ret .= '<!-- morehtmlleft --><div class="inline-block floatleft">' . $morehtmlleft . '</div>';
10108
            }
10109
        }
10110
10111
        //if ($conf->browser->layout == 'phone') $ret.='<div class="clearboth"></div>';
10112
        $ret .= '<div class="inline-block floatleft valignmiddle maxwidth750 marginbottomonly refid' . (($shownav && ($previous_ref || $next_ref)) ? ' refidpadding' : '') . '">';
10113
10114
        // For thirdparty, contact, user, member, the ref is the id, so we show something else
10115
        if ($object->element == 'societe') {
10116
            $ret .= dol_htmlentities($object->name);
10117
10118
            // List of extra languages
10119
            $arrayoflangcode = array();
10120
            if (getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE')) {
10121
                $arrayoflangcode[] = getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE');
10122
            }
10123
10124
            if (is_array($arrayoflangcode) && count($arrayoflangcode)) {
10125
                if (!is_object($extralanguages)) {
10126
                    include_once DOL_DOCUMENT_ROOT . '/core/class/extralanguages.class.php';
10127
                    $extralanguages = new ExtraLanguages($this->db);
10128
                }
10129
                $extralanguages->fetch_name_extralanguages('societe');
10130
10131
                if (!empty($extralanguages->attributes['societe']['name'])) {
10132
                    $object->fetchValuesForExtraLanguages();
10133
10134
                    $htmltext = '';
10135
                    // If there is extra languages
10136
                    foreach ($arrayoflangcode as $extralangcode) {
10137
                        $htmltext .= picto_from_langcode($extralangcode, 'class="pictoforlang paddingright"');
10138
                        if ($object->array_languages['name'][$extralangcode]) {
10139
                            $htmltext .= $object->array_languages['name'][$extralangcode];
10140
                        } else {
10141
                            $htmltext .= '<span class="opacitymedium">' . $langs->trans("SwitchInEditModeToAddTranslation") . '</span>';
10142
                        }
10143
                    }
10144
                    $ret .= '<!-- Show translations of name -->' . "\n";
10145
                    $ret .= $this->textwithpicto('', $htmltext, -1, 'language', 'opacitymedium paddingleft');
10146
                }
10147
            }
10148
        } elseif ($object->element == 'member') {
10149
            '@phan-var-force Adherent $object';
10150
            $ret .= $object->ref . '<br>';
10151
            $fullname = $object->getFullName($langs);
0 ignored issues
show
Bug introduced by
The method getFullName() does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

10151
            /** @scrutinizer ignore-call */ 
10152
            $fullname = $object->getFullName($langs);
Loading history...
10152
            if ($object->morphy == 'mor' && $object->societe) {
0 ignored issues
show
Bug Best Practice introduced by
The property societe does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property morphy does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
10153
                $ret .= dol_htmlentities($object->societe) . ((!empty($fullname) && $object->societe != $fullname) ? ' (' . dol_htmlentities($fullname) . $addgendertxt . ')' : '');
10154
            } else {
10155
                $ret .= dol_htmlentities($fullname) . $addgendertxt . ((!empty($object->societe) && $object->societe != $fullname) ? ' (' . dol_htmlentities($object->societe) . ')' : '');
10156
            }
10157
        } elseif (in_array($object->element, array('contact', 'user'))) {
10158
            $ret .= dol_htmlentities($object->getFullName($langs)) . $addgendertxt;
10159
        } elseif ($object->element == 'usergroup') {
10160
            $ret .= dol_htmlentities($object->name);
10161
        } elseif (in_array($object->element, array('action', 'agenda'))) {
10162
            '@phan-var-force ActionComm $object';
10163
            $ret .= $object->ref . '<br>' . $object->label;
10164
        } elseif (in_array($object->element, array('adherent_type'))) {
10165
            $ret .= $object->label;
10166
        } elseif ($object->element == 'ecm_directories') {
10167
            $ret .= '';
10168
        } elseif ($fieldref != 'none') {
10169
            $ret .= dol_htmlentities(!empty($object->$fieldref) ? $object->$fieldref : "");
10170
        }
10171
        if ($morehtmlref) {
10172
            // don't add a additional space, when "$morehtmlref" starts with a HTML div tag
10173
            if (substr($morehtmlref, 0, 4) != '<div') {
10174
                $ret .= ' ';
10175
            }
10176
10177
            $ret .= $morehtmlref;
10178
        }
10179
10180
        $ret .= '</div>';
10181
10182
        $ret .= '</div><!-- End banner content -->';
10183
10184
        return $ret;
10185
    }
10186
10187
    /**
10188
     *  Return HTML code to output a barcode
10189
     *
10190
     * @param CommonObject $object Object containing data to retrieve file name
10191
     * @param int $width Width of photo
10192
     * @param string $morecss More CSS on img of barcode
10193
     * @return string                    HTML code to output barcode
10194
     */
10195
    public function showbarcode(&$object, $width = 100, $morecss = '')
10196
    {
10197
        global $conf;
10198
10199
        //Check if barcode is filled in the card
10200
        if (empty($object->barcode)) {
0 ignored issues
show
Bug Best Practice introduced by
The property barcode does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
10201
            return '';
10202
        }
10203
10204
        // Complete object if not complete
10205
        if (empty($object->barcode_type_code) || empty($object->barcode_type_coder)) {
10206
            // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
10207
            $result = $object->fetch_barcode();
10208
            //Check if fetch_barcode() failed
10209
            if ($result < 1) {
10210
                return '<!-- ErrorFetchBarcode -->';
10211
            }
10212
        }
10213
10214
        // Barcode image  @phan-suppress-next-line PhanUndeclaredProperty
10215
        $url = constant('BASE_URL') . '/viewimage.php?modulepart=barcode&generator=' . urlencode($object->barcode_type_coder) . '&code=' . urlencode($object->barcode) . '&encoding=' . urlencode($object->barcode_type_code);
10216
        $out = '<!-- url barcode = ' . $url . ' -->';
10217
        $out .= '<img src="' . $url . '"' . ($morecss ? ' class="' . $morecss . '"' : '') . '>';
10218
10219
        return $out;
10220
    }
10221
10222
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
10223
10224
    /**
10225
     * Return select list of groups
10226
     *
10227
     * @param int|object|object[] $selected Id group or group(s) preselected
10228
     * @param string $htmlname Field name in form
10229
     * @param int<0,1> $show_empty 0=liste sans valeur nulle, 1=ajoute valeur inconnue
10230
     * @param string|int[] $exclude Array list of groups id to exclude
10231
     * @param int<0,1> $disabled If select list must be disabled
10232
     * @param string|int[] $include Array list of groups id to include
10233
     * @param int[] $enableonly Array list of groups id to be enabled. All other must be disabled
10234
     * @param string $force_entity '0' or Ids of environment to force
10235
     * @param bool $multiple add [] in the name of element and add 'multiple' attribute (not working with ajax_autocompleter)
10236
     * @param string $morecss More css to add to html component
10237
     * @return  string                              HTML Componont to select a group
10238
     * @see select_dolusers()
10239
     */
10240
    public function select_dolgroups($selected = 0, $htmlname = 'groupid', $show_empty = 0, $exclude = '', $disabled = 0, $include = '', $enableonly = array(), $force_entity = '0', $multiple = false, $morecss = 'minwidth200')
10241
    {
10242
        // phpcs:enable
10243
        global $conf, $user, $langs;
10244
10245
        // Allow excluding groups
10246
        $excludeGroups = null;
10247
        if (is_array($exclude)) {
10248
            $excludeGroups = implode(",", $exclude);
10249
        }
10250
        // Allow including groups
10251
        $includeGroups = null;
10252
        if (is_array($include)) {
10253
            $includeGroups = implode(",", $include);
10254
        }
10255
10256
        if (!is_array($selected)) {
10257
            $selected = array($selected);
10258
        }
10259
10260
        $out = '';
10261
10262
        // Build sql to search groups
10263
        $sql = "SELECT ug.rowid, ug.nom as name";
10264
        if (isModEnabled('multicompany') && $conf->entity == 1 && $user->admin && !$user->entity) {
10265
            $sql .= ", e.label";
10266
        }
10267
        $sql .= " FROM " . $this->db->prefix() . "usergroup as ug ";
10268
        if (isModEnabled('multicompany') && $conf->entity == 1 && $user->admin && !$user->entity) {
10269
            $sql .= " LEFT JOIN " . $this->db->prefix() . "entity as e ON e.rowid=ug.entity";
10270
            if ($force_entity) {
10271
                $sql .= " WHERE ug.entity IN (0, " . $force_entity . ")";
10272
            } else {
10273
                $sql .= " WHERE ug.entity IS NOT NULL";
10274
            }
10275
        } else {
10276
            $sql .= " WHERE ug.entity IN (0, " . $conf->entity . ")";
10277
        }
10278
        if (is_array($exclude) && $excludeGroups) {
10279
            $sql .= " AND ug.rowid NOT IN (" . $this->db->sanitize($excludeGroups) . ")";
10280
        }
10281
        if (is_array($include) && $includeGroups) {
10282
            $sql .= " AND ug.rowid IN (" . $this->db->sanitize($includeGroups) . ")";
10283
        }
10284
        $sql .= " ORDER BY ug.nom ASC";
10285
10286
        dol_syslog(get_only_class($this) . "::select_dolgroups", LOG_DEBUG);
10287
        $resql = $this->db->query($sql);
10288
        if ($resql) {
10289
            // Enhance with select2
10290
            include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
10291
10292
            $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" id="' . $htmlname . '" name="' . $htmlname . ($multiple ? '[]' : '') . '" ' . ($multiple ? 'multiple' : '') . ' ' . ($disabled ? ' disabled' : '') . '>';
10293
10294
            $num = $this->db->num_rows($resql);
10295
            $i = 0;
10296
            if ($num) {
10297
                if ($show_empty && !$multiple) {
10298
                    $out .= '<option value="-1"' . (in_array(-1, $selected) ? ' selected' : '') . '>&nbsp;</option>' . "\n";
10299
                }
10300
10301
                while ($i < $num) {
10302
                    $obj = $this->db->fetch_object($resql);
10303
                    $disableline = 0;
10304
                    if (is_array($enableonly) && count($enableonly) && !in_array($obj->rowid, $enableonly)) {
10305
                        $disableline = 1;
10306
                    }
10307
10308
                    $label = $obj->name;
10309
                    $labelhtml = $obj->name;
10310
                    if (isModEnabled('multicompany') && !getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE') && $conf->entity == 1) {
10311
                        $label .= " (" . $obj->label . ")";
10312
                        $labelhtml .= ' <span class="opacitymedium">(' . $obj->label . ')</span>';
10313
                    }
10314
10315
                    $out .= '<option value="' . $obj->rowid . '"';
10316
                    if ($disableline) {
10317
                        $out .= ' disabled';
10318
                    }
10319
                    if (
10320
                        (isset($selected[0]) && is_object($selected[0]) && $selected[0]->id == $obj->rowid)
10321
                        || ((!isset($selected[0]) || !is_object($selected[0])) && !empty($selected) && in_array($obj->rowid, $selected))
10322
                    ) {
10323
                        $out .= ' selected';
10324
                    }
10325
                    $out .= ' data-html="' . dol_escape_htmltag($labelhtml) . '"';
10326
                    $out .= '>';
10327
                    $out .= $label;
10328
                    $out .= '</option>';
10329
                    $i++;
10330
                }
10331
            } else {
10332
                if ($show_empty) {
10333
                    $out .= '<option value="-1"' . (in_array(-1, $selected) ? ' selected' : '') . '></option>' . "\n";
10334
                }
10335
                $out .= '<option value="" disabled>' . $langs->trans("NoUserGroupDefined") . '</option>';
10336
            }
10337
            $out .= '</select>';
10338
10339
            $out .= ajax_combobox($htmlname);
10340
        } else {
10341
            dol_print_error($this->db);
10342
        }
10343
10344
        return $out;
10345
    }
10346
10347
    /**
10348
     *    Return HTML to show the search and clear search button
10349
     *
10350
     * @param int $addcheckuncheckall Add the check all/uncheck all checkbox (use javascript) and code to manage this
10351
     * @param string $cssclass CSS class
10352
     * @param int $calljsfunction 0=default. 1=call function initCheckForSelect() after changing status of checkboxes
10353
     * @param string $massactionname Mass action name
10354
     * @return    string
10355
     */
10356
    public function showFilterAndCheckAddButtons($addcheckuncheckall = 0, $cssclass = 'checkforaction', $calljsfunction = 0, $massactionname = "massaction")
10357
    {
10358
        $out = $this->showFilterButtons();
10359
        if ($addcheckuncheckall) {
10360
            $out .= $this->showCheckAddButtons($cssclass, $calljsfunction, $massactionname);
10361
        }
10362
        return $out;
10363
    }
10364
10365
    /**
10366
     *    Return HTML to show the search and clear search button
10367
     *
10368
     * @param string $pos Position of colon on the list. Value 'left' or 'right'
10369
     * @return    string
10370
     */
10371
    public function showFilterButtons($pos = '')
10372
    {
10373
        $out = '<div class="nowraponall">';
10374
        $out .= '<button type="submit" class="liste_titre button_search reposition" name="button_search_x" value="x"><span class="fas fa-search"></span></button>';
10375
        $out .= '<button type="submit" class="liste_titre button_removefilter reposition" name="button_removefilter_x" value="x"><span class="fas fa-times"></span></button>';
10376
        $out .= '</div>';
10377
10378
        return $out;
10379
    }
10380
10381
    /**
10382
     *    Return HTML to show the search and clear search button
10383
     *
10384
     * @param string $cssclass CSS class
10385
     * @param int $calljsfunction 0=default. 1=call function initCheckForSelect() after changing status of checkboxes
10386
     * @param string $massactionname Mass action button name that will launch an action on the selected items
10387
     * @return    string
10388
     */
10389
    public function showCheckAddButtons($cssclass = 'checkforaction', $calljsfunction = 0, $massactionname = "massaction")
10390
    {
10391
        global $conf;
10392
10393
        $out = '';
10394
10395
        if (!empty($conf->use_javascript_ajax)) {
10396
            $out .= '<div class="inline-block checkallactions"><input type="checkbox" id="' . $cssclass . 's" name="' . $cssclass . 's" class="checkallactions"></div>';
10397
        }
10398
        $out .= '<script nonce="' . getNonce() . '">
10399
            $(document).ready(function() {
10400
                $("#' . $cssclass . 's").click(function() {
10401
                    if($(this).is(\':checked\')){
10402
                        console.log("We check all ' . $cssclass . ' and trigger the change method");
10403
                		$(".' . $cssclass . '").prop(\'checked\', true).trigger(\'change\');
10404
                    }
10405
                    else
10406
                    {
10407
                        console.log("We uncheck all");
10408
                		$(".' . $cssclass . '").prop(\'checked\', false).trigger(\'change\');
10409
                    }' . "\n";
10410
        if ($calljsfunction) {
10411
            $out .= 'if (typeof initCheckForSelect == \'function\') { initCheckForSelect(0, "' . $massactionname . '", "' . $cssclass . '"); } else { console.log("No function initCheckForSelect found. Call won\'t be done."); }';
10412
        }
10413
        $out .= '         });
10414
        	        $(".' . $cssclass . '").change(function() {
10415
					$(this).closest("tr").toggleClass("highlight", this.checked);
10416
				});
10417
		 	});
10418
    	</script>';
10419
10420
        return $out;
10421
    }
10422
10423
    /**
10424
     * Return HTML to show the select of expense categories
10425
     *
10426
     * @param string $selected preselected category
10427
     * @param string $htmlname name of HTML select list
10428
     * @param int<0,1> $useempty 1=Add empty line
10429
     * @param int[] $excludeid id to exclude
10430
     * @param string $target htmlname of target select to bind event
10431
     * @param int $default_selected default category to select if fk_c_type_fees change = EX_KME
10432
     * @param array<string,int|string> $params param to give
10433
     * @param int<0,1> $info_admin Show the tooltip help picto to setup list
10434
     * @return    string
10435
     */
10436
    public function selectExpenseCategories($selected = '', $htmlname = 'fk_c_exp_tax_cat', $useempty = 0, $excludeid = array(), $target = '', $default_selected = 0, $params = array(), $info_admin = 1)
10437
    {
10438
        global $langs, $user;
10439
10440
        $out = '';
10441
        $sql = "SELECT rowid, label FROM " . $this->db->prefix() . "c_exp_tax_cat WHERE active = 1";
10442
        $sql .= " AND entity IN (0," . getEntity('exp_tax_cat') . ")";
10443
        if (!empty($excludeid)) {
10444
            $sql .= " AND rowid NOT IN (" . $this->db->sanitize(implode(',', $excludeid)) . ")";
10445
        }
10446
        $sql .= " ORDER BY label";
10447
10448
        $resql = $this->db->query($sql);
10449
        if ($resql) {
10450
            $out = '<select id="select_' . $htmlname . '" name="' . $htmlname . '" class="' . $htmlname . ' flat minwidth75imp maxwidth200">';
10451
            if ($useempty) {
10452
                $out .= '<option value="0">&nbsp;</option>';
10453
            }
10454
10455
            while ($obj = $this->db->fetch_object($resql)) {
10456
                $out .= '<option ' . ($selected == $obj->rowid ? 'selected="selected"' : '') . ' value="' . $obj->rowid . '">' . $langs->trans($obj->label) . '</option>';
10457
            }
10458
            $out .= '</select>';
10459
            $out .= ajax_combobox('select_' . $htmlname);
10460
10461
            if (!empty($htmlname) && $user->admin && $info_admin) {
10462
                $out .= ' ' . info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
10463
            }
10464
10465
            if (!empty($target)) {
10466
                $sql = "SELECT c.id FROM " . $this->db->prefix() . "c_type_fees as c WHERE c.code = 'EX_KME' AND c.active = 1";
10467
                $resql = $this->db->query($sql);
10468
                if ($resql) {
10469
                    if ($this->db->num_rows($resql) > 0) {
10470
                        $obj = $this->db->fetch_object($resql);
10471
                        $out .= '<script nonce="' . getNonce() . '">
10472
							$(function() {
10473
								$("select[name=' . $target . ']").on("change", function() {
10474
									var current_val = $(this).val();
10475
									if (current_val == ' . $obj->id . ') {';
10476
                        if (!empty($default_selected) || !empty($selected)) {
10477
                            $out .= '$("select[name=' . $htmlname . ']").val("' . ($default_selected > 0 ? $default_selected : $selected) . '");';
10478
                        }
10479
10480
                        $out .= '
10481
										$("select[name=' . $htmlname . ']").change();
10482
									}
10483
								});
10484
10485
								$("select[name=' . $htmlname . ']").change(function() {
10486
10487
									if ($("select[name=' . $target . ']").val() == ' . $obj->id . ') {
10488
										// get price of kilometer to fill the unit price
10489
										$.ajax({
10490
											method: "POST",
10491
											dataType: "json",
10492
											data: { fk_c_exp_tax_cat: $(this).val(), token: \'' . currentToken() . '\' },
10493
											url: "' . (constant('BASE_URL') . '/expensereport/ajax/ajaxik.php?' . implode('&', $params)) . '",
10494
										}).done(function( data, textStatus, jqXHR ) {
10495
											console.log(data);
10496
											if (typeof data.up != "undefined") {
10497
												$("input[name=value_unit]").val(data.up);
10498
												$("select[name=' . $htmlname . ']").attr("title", data.title);
10499
											} else {
10500
												$("input[name=value_unit]").val("");
10501
												$("select[name=' . $htmlname . ']").attr("title", "");
10502
											}
10503
										});
10504
									}
10505
								});
10506
							});
10507
						</script>';
10508
                    }
10509
                }
10510
            }
10511
        } else {
10512
            dol_print_error($this->db);
10513
        }
10514
10515
        return $out;
10516
    }
10517
10518
    /**
10519
     * Return HTML to show the select ranges of expense range
10520
     *
10521
     * @param string $selected preselected category
10522
     * @param string $htmlname name of HTML select list
10523
     * @param integer $useempty 1=Add empty line
10524
     * @return    string
10525
     */
10526
    public function selectExpenseRanges($selected = '', $htmlname = 'fk_range', $useempty = 0)
10527
    {
10528
        global $conf, $langs;
10529
10530
        $out = '';
10531
        $sql = "SELECT rowid, range_ik FROM " . $this->db->prefix() . "c_exp_tax_range";
10532
        $sql .= " WHERE entity = " . $conf->entity . " AND active = 1";
10533
10534
        $resql = $this->db->query($sql);
10535
        if ($resql) {
10536
            $out = '<select id="select_' . $htmlname . '" name="' . $htmlname . '" class="' . $htmlname . ' flat minwidth75imp">';
10537
            if ($useempty) {
10538
                $out .= '<option value="0"></option>';
10539
            }
10540
10541
            while ($obj = $this->db->fetch_object($resql)) {
10542
                $out .= '<option ' . ($selected == $obj->rowid ? 'selected="selected"' : '') . ' value="' . $obj->rowid . '">' . price($obj->range_ik, 0, $langs, 1, 0) . '</option>';
10543
            }
10544
            $out .= '</select>';
10545
        } else {
10546
            dol_print_error($this->db);
10547
        }
10548
10549
        return $out;
10550
    }
10551
10552
    /**
10553
     * Return HTML to show a select of expense
10554
     *
10555
     * @param string $selected preselected category
10556
     * @param string $htmlname name of HTML select list
10557
     * @param integer $useempty 1=Add empty choice
10558
     * @param integer $allchoice 1=Add all choice
10559
     * @param integer $useid 0=use 'code' as key, 1=use 'id' as key
10560
     * @return    string
10561
     */
10562
    public function selectExpense($selected = '', $htmlname = 'fk_c_type_fees', $useempty = 0, $allchoice = 1, $useid = 0)
10563
    {
10564
        global $langs;
10565
10566
        $out = '';
10567
        $sql = "SELECT id, code, label";
10568
        $sql .= " FROM " . $this->db->prefix() . "c_type_fees";
10569
        $sql .= " WHERE active = 1";
10570
10571
        $resql = $this->db->query($sql);
10572
        if ($resql) {
10573
            $out = '<select id="select_' . $htmlname . '" name="' . $htmlname . '" class="' . $htmlname . ' flat minwidth75imp">';
10574
            if ($useempty) {
10575
                $out .= '<option value="0"></option>';
10576
            }
10577
            if ($allchoice) {
10578
                $out .= '<option value="-1">' . $langs->trans('AllExpenseReport') . '</option>';
10579
            }
10580
10581
            $field = 'code';
10582
            if ($useid) {
10583
                $field = 'id';
10584
            }
10585
10586
            while ($obj = $this->db->fetch_object($resql)) {
10587
                $key = $langs->trans($obj->code);
10588
                $out .= '<option ' . ($selected == $obj->{$field} ? 'selected="selected"' : '') . ' value="' . $obj->{$field} . '">' . ($key != $obj->code ? $key : $obj->label) . '</option>';
10589
            }
10590
            $out .= '</select>';
10591
10592
            $out .= ajax_combobox('select_' . $htmlname);
10593
        } else {
10594
            dol_print_error($this->db);
10595
        }
10596
10597
        return $out;
10598
    }
10599
10600
    /**
10601
     *  Output a combo list with invoices qualified for a third party
10602
     *
10603
     * @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)
10604
     * @param string $selected Id invoice preselected
10605
     * @param string $htmlname Name of HTML select
10606
     * @param int $maxlength Maximum length of label
10607
     * @param int $option_only Return only html options lines without the select tag
10608
     * @param string $show_empty Add an empty line ('1' or string to show for empty line)
10609
     * @param int $discard_closed Discard closed projects (0=Keep,1=hide completely,2=Disable)
10610
     * @param int $forcefocus Force focus on field (works with javascript only)
10611
     * @param int $disabled Disabled
10612
     * @param string $morecss More css added to the select component
10613
     * @param string $projectsListId ''=Automatic filter on project allowed. List of id=Filter on project ids.
10614
     * @param string $showproject 'all' = Show project info, ''=Hide project info
10615
     * @param User $usertofilter User object to use for filtering
10616
     * @return string            HTML Select Invoice
10617
     */
10618
    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)
10619
    {
10620
        global $user, $conf, $langs;
10621
10622
10623
        if (is_null($usertofilter)) {
10624
            $usertofilter = $user;
10625
        }
10626
10627
        $out = '';
10628
10629
        $hideunselectables = false;
10630
        if (getDolGlobalString('PROJECT_HIDE_UNSELECTABLES')) {
10631
            $hideunselectables = true;
10632
        }
10633
10634
        if (empty($projectsListId)) {
10635
            if (!$usertofilter->hasRight('projet', 'all', 'lire')) {
0 ignored issues
show
Bug introduced by
The method hasRight() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

10635
            if (!$usertofilter->/** @scrutinizer ignore-call */ hasRight('projet', 'all', 'lire')) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
10636
                $projectstatic = new Project($this->db);
10637
                $projectsListId = $projectstatic->getProjectsAuthorizedForUser($usertofilter, 0, 1);
10638
            }
10639
        }
10640
10641
        // Search all projects
10642
        $sql = "SELECT f.rowid, f.ref as fref, 'nolabel' as flabel, p.rowid as pid, f.ref,
10643
            p.title, p.fk_soc, p.fk_statut, p.public,";
10644
        $sql .= ' s.nom as name';
10645
        $sql .= ' FROM ' . $this->db->prefix() . 'projet as p';
10646
        $sql .= ' LEFT JOIN ' . $this->db->prefix() . 'societe as s ON s.rowid = p.fk_soc,';
10647
        $sql .= ' ' . $this->db->prefix() . 'facture as f';
10648
        $sql .= " WHERE p.entity IN (" . getEntity('project') . ")";
10649
        $sql .= " AND f.fk_projet = p.rowid AND f.fk_statut=0"; //Brouillons seulement
10650
        //if ($projectsListId) $sql.= " AND p.rowid IN (".$this->db->sanitize($projectsListId).")";
10651
        //if ($socid == 0) $sql.= " AND (p.fk_soc=0 OR p.fk_soc IS NULL)";
10652
        //if ($socid > 0)  $sql.= " AND (p.fk_soc=".((int) $socid)." OR p.fk_soc IS NULL)";
10653
        $sql .= " ORDER BY p.ref, f.ref ASC";
10654
10655
        $resql = $this->db->query($sql);
10656
        if ($resql) {
10657
            // Use select2 selector
10658
            if (!empty($conf->use_javascript_ajax)) {
10659
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
10660
                $comboenhancement = ajax_combobox($htmlname, array(), 0, $forcefocus);
10661
                $out .= $comboenhancement;
10662
                $morecss = 'minwidth200imp maxwidth500';
10663
            }
10664
10665
            if (empty($option_only)) {
10666
                $out .= '<select class="valignmiddle flat' . ($morecss ? ' ' . $morecss : '') . '"' . ($disabled ? ' disabled="disabled"' : '') . ' id="' . $htmlname . '" name="' . $htmlname . '">';
10667
            }
10668
            if (!empty($show_empty)) {
10669
                $out .= '<option value="0" class="optiongrey">';
10670
                if (!is_numeric($show_empty)) {
10671
                    $out .= $show_empty;
10672
                } else {
10673
                    $out .= '&nbsp;';
10674
                }
10675
                $out .= '</option>';
10676
            }
10677
            $num = $this->db->num_rows($resql);
10678
            $i = 0;
10679
            if ($num) {
10680
                while ($i < $num) {
10681
                    $obj = $this->db->fetch_object($resql);
10682
                    // 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.
10683
                    if ($socid > 0 && (empty($obj->fk_soc) || $obj->fk_soc == $socid) && !$usertofilter->hasRight('societe', 'lire')) {
10684
                        // Do nothing
10685
                    } else {
10686
                        if ($discard_closed == 1 && $obj->fk_statut == Project::STATUS_CLOSED) {
10687
                            $i++;
10688
                            continue;
10689
                        }
10690
10691
                        $labeltoshow = '';
10692
10693
                        if ($showproject == 'all') {
10694
                            $labeltoshow .= dol_trunc($obj->ref, 18); // Invoice ref
10695
                            if ($obj->name) {
10696
                                $labeltoshow .= ' - ' . $obj->name; // Soc name
10697
                            }
10698
10699
                            $disabled = 0;
10700
                            if ($obj->fk_statut == Project::STATUS_DRAFT) {
10701
                                $disabled = 1;
10702
                                $labeltoshow .= ' - ' . $langs->trans("Draft");
10703
                            } elseif ($obj->fk_statut == Project::STATUS_CLOSED) {
10704
                                if ($discard_closed == 2) {
10705
                                    $disabled = 1;
10706
                                }
10707
                                $labeltoshow .= ' - ' . $langs->trans("Closed");
10708
                            } elseif ($socid > 0 && (!empty($obj->fk_soc) && $obj->fk_soc != $socid)) {
10709
                                $disabled = 1;
10710
                                $labeltoshow .= ' - ' . $langs->trans("LinkedToAnotherCompany");
10711
                            }
10712
                        }
10713
10714
                        if (!empty($selected) && $selected == $obj->rowid) {
10715
                            $out .= '<option value="' . $obj->rowid . '" selected';
10716
                            //if ($disabled) $out.=' disabled';                     // with select2, field can't be preselected if disabled
10717
                            $out .= '>' . $labeltoshow . '</option>';
10718
                        } else {
10719
                            if ($hideunselectables && $disabled && ($selected != $obj->rowid)) {
10720
                                $resultat = '';
10721
                            } else {
10722
                                $resultat = '<option value="' . $obj->rowid . '"';
10723
                                if ($disabled) {
10724
                                    $resultat .= ' disabled';
10725
                                }
10726
                                //if ($obj->public) $labeltoshow.=' ('.$langs->trans("Public").')';
10727
                                //else $labeltoshow.=' ('.$langs->trans("Private").')';
10728
                                $resultat .= '>';
10729
                                $resultat .= $labeltoshow;
10730
                                $resultat .= '</option>';
10731
                            }
10732
                            $out .= $resultat;
10733
                        }
10734
                    }
10735
                    $i++;
10736
                }
10737
            }
10738
            if (empty($option_only)) {
10739
                $out .= '</select>';
10740
            }
10741
10742
            $this->db->free($resql);
10743
10744
            return $out;
10745
        } else {
10746
            dol_print_error($this->db);
10747
            return '';
10748
        }
10749
    }
10750
10751
    /**
10752
     *  Output a combo list with invoices qualified for a third party
10753
     *
10754
     * @param string $selected Id invoice preselected
10755
     * @param string $htmlname Name of HTML select
10756
     * @param int $maxlength Maximum length of label
10757
     * @param int $option_only Return only html options lines without the select tag
10758
     * @param string $show_empty Add an empty line ('1' or string to show for empty line)
10759
     * @param int $forcefocus Force focus on field (works with javascript only)
10760
     * @param int $disabled Disabled
10761
     * @param string $morecss More css added to the select component
10762
     * @return int                    Nbr of project if OK, <0 if KO
10763
     */
10764
    public function selectInvoiceRec($selected = '', $htmlname = 'facrecid', $maxlength = 24, $option_only = 0, $show_empty = '1', $forcefocus = 0, $disabled = 0, $morecss = 'maxwidth500')
10765
    {
10766
        global $conf, $langs;
10767
10768
        $out = '';
10769
10770
        dol_syslog('FactureRec::fetch', LOG_DEBUG);
10771
10772
        $sql = 'SELECT f.rowid, f.entity, f.titre as title, f.suspended, f.fk_soc';
10773
        //$sql.= ', el.fk_source';
10774
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'facture_rec as f';
10775
        $sql .= " WHERE f.entity IN (" . getEntity('invoice') . ")";
10776
        $sql .= " ORDER BY f.titre ASC";
10777
10778
        $resql = $this->db->query($sql);
10779
        if ($resql) {
10780
            // Use select2 selector
10781
            if (!empty($conf->use_javascript_ajax)) {
10782
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
10783
                $comboenhancement = ajax_combobox($htmlname, array(), 0, $forcefocus);
10784
                $out .= $comboenhancement;
10785
                $morecss = 'minwidth200imp maxwidth500';
10786
            }
10787
10788
            if (empty($option_only)) {
10789
                $out .= '<select class="valignmiddle flat' . ($morecss ? ' ' . $morecss : '') . '"' . ($disabled ? ' disabled="disabled"' : '') . ' id="' . $htmlname . '" name="' . $htmlname . '">';
10790
            }
10791
            if (!empty($show_empty)) {
10792
                $out .= '<option value="0" class="optiongrey">';
10793
                if (!is_numeric($show_empty)) {
10794
                    $out .= $show_empty;
10795
                } else {
10796
                    $out .= '&nbsp;';
10797
                }
10798
                $out .= '</option>';
10799
            }
10800
            $num = $this->db->num_rows($resql);
10801
            if ($num) {
10802
                while ($obj = $this->db->fetch_object($resql)) {
10803
                    $labeltoshow = dol_trunc($obj->title, 18); // Invoice ref
10804
10805
                    $disabled = 0;
10806
                    if (!empty($obj->suspended)) {
10807
                        $disabled = 1;
10808
                        $labeltoshow .= ' - ' . $langs->trans("Closed");
10809
                    }
10810
10811
10812
                    if (!empty($selected) && $selected == $obj->rowid) {
10813
                        $out .= '<option value="' . $obj->rowid . '" selected';
10814
                        //if ($disabled) $out.=' disabled';                     // with select2, field can't be preselected if disabled
10815
                        $out .= '>' . $labeltoshow . '</option>';
10816
                    } else {
10817
                        if ($disabled && ($selected != $obj->rowid)) {
10818
                            $resultat = '';
10819
                        } else {
10820
                            $resultat = '<option value="' . $obj->rowid . '"';
10821
                            if ($disabled) {
10822
                                $resultat .= ' disabled';
10823
                            }
10824
                            $resultat .= '>';
10825
                            $resultat .= $labeltoshow;
10826
                            $resultat .= '</option>';
10827
                        }
10828
                        $out .= $resultat;
10829
                    }
10830
                }
10831
            }
10832
            if (empty($option_only)) {
10833
                $out .= '</select>';
10834
            }
10835
10836
            print $out;
10837
10838
            $this->db->free($resql);
10839
            return $num;
10840
        } else {
10841
            $this->errors[] = $this->db->lasterror;
10842
            return -1;
10843
        }
10844
    }
10845
10846
    /**
10847
     * Output the component to make advanced search criteries
10848
     *
10849
     * @param array<array<string,array{type:string}>> $arrayofcriterias Array of available search criteria. Example: array($object->element => $object->fields, 'otherfamily' => otherarrayoffields, ...)
10850
     * @param array<int,string> $search_component_params Array of selected search criteria
10851
     * @param string[] $arrayofinputfieldsalreadyoutput Array of input fields already inform. The component will not generate a hidden input field if it is in this list.
10852
     * @param string $search_component_params_hidden String with $search_component_params criteria
10853
     * @return  string                                                                      HTML component for advanced search
10854
     */
10855
    public function searchComponent($arrayofcriterias, $search_component_params, $arrayofinputfieldsalreadyoutput = array(), $search_component_params_hidden = '')
10856
    {
10857
        global $langs;
10858
10859
        if ($search_component_params_hidden != '' && !preg_match('/^\(.*\)$/', $search_component_params_hidden)) {    // If $search_component_params_hidden does not start and end with ()
10860
            $search_component_params_hidden = '(' . $search_component_params_hidden . ')';
10861
        }
10862
10863
        $ret = '';
10864
10865
        $ret .= '<div class="divadvancedsearchfieldcomp centpercent inline-block">';
10866
        $ret .= '<a href="#" class="dropdownsearch-toggle unsetcolor">';
10867
        $ret .= '<span class="fas fa-filter linkobject boxfilter paddingright pictofixedwidth" title="' . dol_escape_htmltag($langs->trans("Filters")) . '" id="idsubimgproductdistribution"></span>';
10868
        $ret .= '</a>';
10869
10870
        $ret .= '<div class="divadvancedsearchfieldcompinput inline-block minwidth500 maxwidth300onsmartphone">';
10871
10872
        // Show select fields as tags.
10873
        $ret .= '<div id="divsearch_component_params" name="divsearch_component_params" class="noborderbottom search_component_params inline-block valignmiddle">';
10874
10875
        if ($search_component_params_hidden) {
10876
            // Split the criteria on each AND
10877
            //var_dump($search_component_params_hidden);
10878
10879
            $arrayofandtags = dolForgeExplodeAnd($search_component_params_hidden);
10880
10881
            // $arrayofandtags is now array( '...' , '...', ...)
10882
            // Show each AND part
10883
            foreach ($arrayofandtags as $tmpkey => $tmpval) {
10884
                $errormessage = '';
10885
                $searchtags = forgeSQLFromUniversalSearchCriteria($tmpval, $errormessage, 1, 1);
10886
                if ($errormessage) {
10887
                    $this->error = 'ERROR in parsing search string: ' . $errormessage;
10888
                }
10889
                // Remove first and last parenthesis but only if first is the opening and last the closing of the same group
10890
                include_once DOL_DOCUMENT_ROOT . '/core/lib/functions2.lib.php';
10891
                $searchtags = removeGlobalParenthesis($searchtags);
10892
10893
                $ret .= '<span class="marginleftonlyshort valignmiddle tagsearch" data-ufilterid="' . ($tmpkey + 1) . '" data-ufilter="' . dol_escape_htmltag($tmpval) . '">';
10894
                $ret .= '<span class="tagsearchdelete select2-selection__choice__remove" data-ufilterid="' . ($tmpkey + 1) . '">x</span> ';
10895
                $ret .= dol_escape_htmltag($searchtags);
10896
                $ret .= '</span>';
10897
            }
10898
        }
10899
10900
        //$ret .= '<button type="submit" class="liste_titre button_search paddingleftonly" name="button_search_x" value="x"><span class="fa fa-search"></span></button>';
10901
10902
        //$ret .= search_component_params
10903
        //$texttoshow = '<div class="opacitymedium inline-block search_component_searchtext">'.$langs->trans("Search").'</div>';
10904
        //$ret .= '<div class="search_component inline-block valignmiddle">'.$texttoshow.'</div>';
10905
10906
        $show_search_component_params_hidden = 1;
10907
        if ($show_search_component_params_hidden) {
10908
            $ret .= '<input type="hidden" name="show_search_component_params_hidden" value="1">';
10909
        }
10910
        $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%')) -->";
10911
        $ret .= '<input type="hidden" id="search_component_params_hidden" name="search_component_params_hidden" value="' . dol_escape_htmltag($search_component_params_hidden) . '">';
10912
        // $ret .= "<!-- sql= ".forgeSQLFromUniversalSearchCriteria($search_component_params_hidden, $errormessage)." -->";
10913
10914
        // For compatibility with forms that show themself the search criteria in addition of this component, we output these fields
10915
        foreach ($arrayofcriterias as $criteria) {
10916
            foreach ($criteria as $criteriafamilykey => $criteriafamilyval) {
10917
                if (in_array('search_' . $criteriafamilykey, $arrayofinputfieldsalreadyoutput)) {
10918
                    continue;
10919
                }
10920
                if (in_array($criteriafamilykey, array('rowid', 'ref_ext', 'entity', 'extraparams'))) {
10921
                    continue;
10922
                }
10923
                if (in_array($criteriafamilyval['type'], array('date', 'datetime', 'timestamp'))) {
10924
                    $ret .= '<input type="hidden" name="search_' . $criteriafamilykey . '_start">';
10925
                    $ret .= '<input type="hidden" name="search_' . $criteriafamilykey . '_startyear">';
10926
                    $ret .= '<input type="hidden" name="search_' . $criteriafamilykey . '_startmonth">';
10927
                    $ret .= '<input type="hidden" name="search_' . $criteriafamilykey . '_startday">';
10928
                    $ret .= '<input type="hidden" name="search_' . $criteriafamilykey . '_end">';
10929
                    $ret .= '<input type="hidden" name="search_' . $criteriafamilykey . '_endyear">';
10930
                    $ret .= '<input type="hidden" name="search_' . $criteriafamilykey . '_endmonth">';
10931
                    $ret .= '<input type="hidden" name="search_' . $criteriafamilykey . '_endday">';
10932
                } else {
10933
                    $ret .= '<input type="hidden" name="search_' . $criteriafamilykey . '">';
10934
                }
10935
            }
10936
        }
10937
10938
        $ret .= '</div>';
10939
10940
        $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";
10941
        $ret .= '<input type="text" placeholder="' . $langs->trans("Filters") . '" id="search_component_params_input" name="search_component_params_input" class="noborderbottom search_component_input" value="">';
10942
10943
        $ret .= '</div>';
10944
        $ret .= '</div>';
10945
10946
        $ret .= '<script>
10947
		jQuery(".tagsearchdelete").click(function(e) {
10948
			var filterid = $(this).parents().attr("data-ufilterid");
10949
			console.log("We click to delete the criteria nb "+filterid);
10950
10951
			// Regenerate the search_component_params_hidden with all data-ufilter except the one to delete, and post the page
10952
			var newparamstring = \'\';
10953
			$(\'.tagsearch\').each(function(index, element) {
10954
				tmpfilterid = $(this).attr("data-ufilterid");
10955
				if (tmpfilterid != filterid) {
10956
					// We keep this criteria
10957
					if (newparamstring == \'\') {
10958
						newparamstring = $(this).attr("data-ufilter");
10959
					} else {
10960
						newparamstring = newparamstring + \' AND \' + $(this).attr("data-ufilter");
10961
					}
10962
				}
10963
			});
10964
			console.log("newparamstring = "+newparamstring);
10965
10966
			jQuery("#search_component_params_hidden").val(newparamstring);
10967
10968
			// We repost the form
10969
			$(this).closest(\'form\').submit();
10970
		});
10971
10972
		jQuery("#search_component_params_input").keydown(function(e) {
10973
			console.log("We press a key on the filter field that is "+jQuery("#search_component_params_input").val());
10974
			console.log(e.which);
10975
			if (jQuery("#search_component_params_input").val() == "" && e.which == 8) {
10976
				/* We click on back when the input field is already empty */
10977
			   	event.preventDefault();
10978
				jQuery("#divsearch_component_params .tagsearch").last().remove();
10979
				/* Regenerate content of search_component_params_hidden from remaining .tagsearch */
10980
				var s = "";
10981
				jQuery("#divsearch_component_params .tagsearch").each(function( index ) {
10982
					if (s != "") {
10983
						s = s + " AND ";
10984
					}
10985
					s = s + $(this).attr("data-ufilter");
10986
				});
10987
				console.log("New value for search_component_params_hidden = "+s);
10988
				jQuery("#search_component_params_hidden").val(s);
10989
			}
10990
		});
10991
10992
		</script>
10993
		';
10994
10995
        return $ret;
10996
    }
10997
10998
    /**
10999
     * selectModelMail
11000
     *
11001
     * @param string $prefix Prefix
11002
     * @param string $modelType Model type
11003
     * @param int<0,1> $default 1=Show also Default mail template
11004
     * @param int<0,1> $addjscombo Add js combobox
11005
     * @return  string                      HTML select string
11006
     */
11007
    public function selectModelMail($prefix, $modelType = '', $default = 0, $addjscombo = 0)
11008
    {
11009
        global $langs, $user;
11010
11011
        $retstring = '';
11012
11013
        $TModels = array();
11014
11015
        $formmail = new FormMail($this->db);
11016
        $result = $formmail->fetchAllEMailTemplate($modelType, $user, $langs);
11017
11018
        if ($default) {
11019
            $TModels[0] = $langs->trans('DefaultMailModel');
11020
        }
11021
        if ($result > 0) {
11022
            foreach ($formmail->lines_model as $model) {
11023
                $TModels[$model->id] = $model->label;
11024
            }
11025
        }
11026
11027
        $retstring .= '<select class="flat" id="select_' . $prefix . 'model_mail" name="' . $prefix . 'model_mail">';
11028
11029
        foreach ($TModels as $id_model => $label_model) {
11030
            $retstring .= '<option value="' . $id_model . '"';
11031
            $retstring .= ">" . $label_model . "</option>";
11032
        }
11033
11034
        $retstring .= "</select>";
11035
11036
        if ($addjscombo) {
11037
            $retstring .= ajax_combobox('select_' . $prefix . 'model_mail');
11038
        }
11039
11040
        return $retstring;
11041
    }
11042
11043
    /**
11044
     * Output the buttons to submit a creation/edit form
11045
     *
11046
     * @param string $save_label Alternative label for save button
11047
     * @param string $cancel_label Alternative label for cancel button
11048
     * @param array<array{addclass?:string,name?:string,label_key?:string}> $morebuttons Add additional buttons between save and cancel
11049
     * @param bool $withoutdiv Option to remove enclosing centered div
11050
     * @param string $morecss More CSS
11051
     * @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.
11052
     * @return  string                      Html code with the buttons
11053
     */
11054
    public function buttonsSaveCancel($save_label = 'Save', $cancel_label = 'Cancel', $morebuttons = array(), $withoutdiv = false, $morecss = '', $dol_openinpopup = '')
11055
    {
11056
        global $langs;
11057
11058
        $buttons = array();
11059
11060
        $save = array(
11061
            'name' => 'save',
11062
            'label_key' => $save_label,
11063
        );
11064
11065
        if ($save_label == 'Create' || $save_label == 'Add') {
11066
            $save['name'] = 'add';
11067
        } elseif ($save_label == 'Modify') {
11068
            $save['name'] = 'edit';
11069
        }
11070
11071
        $cancel = array(
11072
            'name' => 'cancel',
11073
            'label_key' => 'Cancel',
11074
        );
11075
11076
        !empty($save_label) ? $buttons[] = $save : '';
11077
11078
        if (!empty($morebuttons)) {
11079
            $buttons[] = $morebuttons;
11080
        }
11081
11082
        !empty($cancel_label) ? $buttons[] = $cancel : '';
11083
11084
        $retstring = $withoutdiv ? '' : '<div class="center">';
11085
11086
        foreach ($buttons as $button) {
11087
            $addclass = empty($button['addclass']) ? '' : $button['addclass'];
11088
            $retstring .= '<input type="submit" class="button button-' . $button['name'] . ($morecss ? ' ' . $morecss : '') . ' ' . $addclass . '" name="' . $button['name'] . '" value="' . dol_escape_htmltag($langs->trans($button['label_key'])) . '">';
11089
        }
11090
        $retstring .= $withoutdiv ? '' : '</div>';
11091
11092
        if ($dol_openinpopup) {
11093
            $retstring .= '<!-- buttons are shown into a $dol_openinpopup=' . $dol_openinpopup . ' context, so we enable the close of dialog on cancel -->' . "\n";
11094
            $retstring .= '<script nonce="' . getNonce() . '">';
11095
            $retstring .= 'jQuery(".button-cancel").click(function(e) {
11096
				e.preventDefault(); console.log(\'We click on cancel in iframe popup ' . $dol_openinpopup . '\');
11097
				window.parent.jQuery(\'#idfordialog' . $dol_openinpopup . '\').dialog(\'close\');
11098
				 });';
11099
            $retstring .= '</script>';
11100
        }
11101
11102
        return $retstring;
11103
    }
11104
11105
11106
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
11107
11108
    /**
11109
     * Return list of invoice subtypes.
11110
     *
11111
     * @param int $selected Id of invoice subtype to preselect by default
11112
     * @param string $htmlname Select field name
11113
     * @param int<0,1> $addempty Add an empty entry
11114
     * @param int<0,1> $noinfoadmin 0=Add admin info, 1=Disable admin info
11115
     * @param string $morecss Add more CSS on select tag
11116
     * @return string                String for the HTML select component
11117
     */
11118
    public function getSelectInvoiceSubtype($selected = 0, $htmlname = 'subtypeid', $addempty = 0, $noinfoadmin = 0, $morecss = '')
11119
    {
11120
        global $langs, $user;
11121
11122
        $out = '';
11123
        dol_syslog(__METHOD__ . " selected=" . $selected . ", htmlname=" . $htmlname, LOG_DEBUG);
11124
11125
        $this->load_cache_invoice_subtype();
11126
11127
        $out .= '<select id="' . $htmlname . '" class="flat selectsubtype' . ($morecss ? ' ' . $morecss : '') . '" name="' . $htmlname . '">';
11128
        if ($addempty) {
11129
            $out .= '<option value="0">&nbsp;</option>';
11130
        }
11131
11132
        foreach ($this->cache_invoice_subtype as $rowid => $subtype) {
11133
            $label = $subtype['label'];
11134
            $out .= '<option value="' . $subtype['rowid'] . '"';
11135
            if ($selected == $subtype['rowid']) {
11136
                $out .= ' selected="selected"';
11137
            }
11138
            $out .= '>';
11139
            $out .= $label;
11140
            $out .= '</option>';
11141
        }
11142
11143
        $out .= '</select>';
11144
        if ($user->admin && empty($noinfoadmin)) {
11145
            $out .= info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
11146
        }
11147
        $out .= ajax_combobox($htmlname);
11148
11149
        return $out;
11150
    }
11151
11152
    /**
11153
     * Load into cache list of invoice subtypes
11154
     *
11155
     * @return int             Nb of lines loaded, <0 if KO
11156
     */
11157
    public function load_cache_invoice_subtype()
11158
    {
11159
        // phpcs:enable
11160
        global $langs;
11161
11162
        $num = count($this->cache_invoice_subtype);
11163
        if ($num > 0) {
11164
            return 0; // Cache already loaded
11165
        }
11166
11167
        dol_syslog(__METHOD__, LOG_DEBUG);
11168
11169
        $sql = "SELECT rowid, code, label as label";
11170
        $sql .= " FROM " . MAIN_DB_PREFIX . 'c_invoice_subtype';
11171
        $sql .= " WHERE active = 1";
11172
11173
        $resql = $this->db->query($sql);
11174
        if ($resql) {
11175
            $num = $this->db->num_rows($resql);
11176
            $i = 0;
11177
            while ($i < $num) {
11178
                $obj = $this->db->fetch_object($resql);
11179
11180
                // If translation exists, we use it, otherwise we take the default wording
11181
                $label = ($langs->trans("InvoiceSubtype" . $obj->rowid) != "InvoiceSubtype" . $obj->rowid) ? $langs->trans("InvoiceSubtype" . $obj->rowid) : (($obj->label != '-') ? $obj->label : '');
11182
                $this->cache_invoice_subtype[$obj->rowid]['rowid'] = $obj->rowid;
11183
                $this->cache_invoice_subtype[$obj->rowid]['code'] = $obj->code;
11184
                $this->cache_invoice_subtype[$obj->rowid]['label'] = $label;
11185
                $i++;
11186
            }
11187
11188
            $this->cache_invoice_subtype = dol_sort_array($this->cache_invoice_subtype, 'code', 'asc', 0, 0, 1);
11189
11190
            return $num;
11191
        } else {
11192
            dol_print_error($this->db);
11193
            return -1;
11194
        }
11195
    }
11196
}
11197