Form   F
last analyzed

Complexity

Total Complexity 2455

Size/Duplication

Total Lines 11115
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 5769
dl 0
loc 11115
rs 0.8
c 0
b 0
f 0
wmc 2455

112 Methods

Rating   Name   Duplication   Size   Complexity  
A constructProjectListOption() 0 24 4
A form_project() 0 36 4
A form_multicurrency_code() 0 14 3
A showbarcode() 0 25 6
B select_produits_fournisseurs() 0 26 8
A formSelectAccount() 0 24 5
C selectyesno() 0 32 13
A formSelectShippingMethod() 0 19 3
A showFilterButtons() 0 8 1
A select_users() 0 4 1
F constructProductListOption() 0 320 89
F selectcontacts() 0 242 82
A constructTicketListOption() 0 21 4
A select_date() 0 11 2
B load_cache_conditions_paiements() 0 39 6
D selectMembersList() 0 107 21
B load_cache_invoice_subtype() 0 37 6
F select_dolusers() 0 259 98
C getSelectConditionsPaiements() 0 73 16
F editfieldkey() 0 82 34
A __construct() 0 3 1
F textwithpicto() 0 64 29
A showCheckAddButtons() 0 32 3
D select_country() 0 116 40
C select_bom() 0 59 13
A selectTypeDuration() 0 31 4
F selectDate() 0 429 123
A select_currency() 0 4 1
F editfieldval() 0 184 92
B load_cache_types_paiements() 0 41 6
C select_dolresources_forevent() 0 70 14
F selectArrayFilter() 0 101 13
F load_tva() 0 200 57
A form_modes_reglement() 0 31 5
D select_dolgroups() 0 105 43
F select_produits_fournisseurs_list() 0 433 102
C selectProjects() 0 46 12
F textwithtooltip() 0 94 40
D selectTicketsList() 0 104 19
A showFilterAndCheckAddButtons() 0 7 2
B form_conditions_reglement() 0 42 8
D select_produits() 0 138 28
C selectShippingMethod() 0 47 14
D selectProjectsList() 0 104 19
B loadCacheInputReason() 0 43 7
A load_cache_types_fees() 0 38 5
D select_all_categories() 0 88 19
A constructMemberListOption() 0 20 4
C select_contact() 0 55 14
D load_cache_vatrates() 0 74 21
A select_export_model() 0 30 5
A form_confirm() 0 5 1
B selectExpense() 0 36 8
F editInPlace() 0 125 37
F formconfirm() 0 396 112
A selectPriceBaseType() 0 23 4
B showCategories() 0 27 8
A selectDateToDate() 0 10 2
B selectSituationInvoices() 0 44 9
F select_thirdparty_list() 0 222 69
C select_remises() 0 72 16
F showLinkToObjectBlock() 0 247 42
A formSelectTransportMode() 0 16 3
D selectForForms() 0 147 38
D select_duration() 0 76 25
B selectUnits() 0 36 9
C selectExpenseCategories() 0 80 15
F showphoto() 0 182 72
D selectTransportMode() 0 55 24
B getSelectInvoiceSubtype() 0 32 7
A form_contacts() 0 27 5
C buttonsSaveCancel() 0 49 13
A form_multicurrency_rate() 0 24 6
C selectEstablishments() 0 56 14
C select_incoterms() 0 72 15
A form_users() 0 19 3
C selectTickets() 0 46 12
C selectMultiCurrency() 0 52 13
B load_cache_availability() 0 39 6
F showrefnav() 0 190 65
F multiSelectArrayWithCheckbox() 0 108 24
A form_availability() 0 18 3
D select_product_fourn_price() 0 95 20
C selectMassAction() 0 96 17
C searchComponent() 0 141 12
A selectAvailabilityDelay() 0 26 6
C select_company() 0 49 16
F selectForFormsList() 0 184 52
D selectarray() 0 132 52
A select_type_fees() 0 31 6
D select_type_of_lines() 0 54 28
D select_comptes() 0 73 22
D form_remise_dispo() 0 80 26
B selectCurrency() 0 42 9
F select_produits_list() 0 346 78
D multiselectarray() 0 134 43
F showLinkedObjectBlock() 0 148 38
D select_types_paiements() 0 89 34
A formInputReason() 0 21 5
A form_thirdparty() 0 30 4
A selectExpenseRanges() 0 24 5
C selectMembers() 0 49 12
A selectModelMail() 0 34 6
D select_dolusers_forevent() 0 73 18
C selectInvoiceRec() 0 79 17
A select_conditions_paiements() 0 8 2
A form_date() 0 32 5
B load_cache_transport_mode() 0 40 6
B selectInputReason() 0 29 11
D selectInvoice() 0 130 35
C widgetForTranslation() 0 59 12
B selectArrayAjax() 0 84 9

How to fix   Complexity   

Complex Class

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

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

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

1
<?php
2
3
/* Copyright (c) 2002-2007  Rodolphe Quiedeville        <[email protected]>
4
 * Copyright (C) 2004-2012  Laurent Destailleur         <[email protected]>
5
 * Copyright (C) 2004       Benoit Mortier              <[email protected]>
6
 * Copyright (C) 2004       Sebastien Di Cintio         <[email protected]>
7
 * Copyright (C) 2004       Eric Seigne                 <[email protected]>
8
 * Copyright (C) 2005-2017  Regis Houssin               <[email protected]>
9
 * Copyright (C) 2006       Andre Cianfarani            <[email protected]>
10
 * Copyright (C) 2006       Marc Barilley/Ocebo         <[email protected]>
11
 * Copyright (C) 2007       Franky Van Liedekerke       <[email protected]>
12
 * Copyright (C) 2007       Patrick Raguin              <[email protected]>
13
 * Copyright (C) 2010       Juanjo Menent               <[email protected]>
14
 * Copyright (C) 2010-2021  Philippe Grand              <[email protected]>
15
 * Copyright (C) 2011       Herve Prot                  <[email protected]>
16
 * Copyright (C) 2012-2016  Marcos García               <[email protected]>
17
 * Copyright (C) 2012       Cedric Salvador             <[email protected]>
18
 * Copyright (C) 2012-2015  Raphaël Doursenaud          <[email protected]>
19
 * Copyright (C) 2014-2023  Alexandre Spangaro          <[email protected]>
20
 * Copyright (C) 2018-2022  Ferran Marcet               <[email protected]>
21
 * Copyright (C) 2018-2024  Frédéric France             <[email protected]>
22
 * Copyright (C) 2018       Nicolas ZABOURI	            <[email protected]>
23
 * Copyright (C) 2018       Christophe Battarel         <[email protected]>
24
 * Copyright (C) 2018       Josep Lluis Amador          <[email protected]>
25
 * Copyright (C) 2023		Joachim Kueter			    <[email protected]>
26
 * Copyright (C) 2023		Nick Fragoulis
27
 * Copyright (C) 2024		MDW							<[email protected]>
28
 * Copyright (C) 2024       Rafael San José             <[email protected]>
29
 *
30
 * This program is free software; you can redistribute it and/or modify
31
 * it under the terms of the GNU General Public License as published by
32
 * the Free Software Foundation; either version 3 of the License, or
33
 * (at your option) any later version.
34
 *
35
 * This program is distributed in the hope that it will be useful,
36
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
37
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
38
 * GNU General Public License for more details.
39
 *
40
 * You should have received a copy of the GNU General Public License
41
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
42
 */
43
44
namespace Dolibarr\Code\Core\Classes;
45
46
use 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