Passed
Push — EXTRACT_CLASSES ( 0382f2...c25e41 )
by Rafael
52:18
created

ExtraFields::create_label()   F

Complexity

Conditions 36
Paths > 20000

Size

Total Lines 124
Code Lines 99

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 36
eloc 99
nc 201330688
nop 21
dl 0
loc 124
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/* Copyright (C) 2002-2003  Rodolphe Quiedeville        <[email protected]>
4
 * Copyright (C) 2002-2003  Jean-Louis Bergamo          <[email protected]>
5
 * Copyright (C) 2004       Sebastien Di Cintio         <[email protected]>
6
 * Copyright (C) 2004       Benoit Mortier              <[email protected]>
7
 * Copyright (C) 2009-2012  Laurent Destailleur         <[email protected]>
8
 * Copyright (C) 2009-2012  Regis Houssin               <[email protected]>
9
 * Copyright (C) 2013       Florian Henry               <[email protected]>
10
 * Copyright (C) 2015-2023  Charlene BENKE              <[email protected]>
11
 * Copyright (C) 2016       Raphaël Doursenaud          <[email protected]>
12
 * Copyright (C) 2017       Nicolas ZABOURI             <[email protected]>
13
 * Copyright (C) 2018-2024  Frédéric France             <[email protected]>
14
 * Copyright (C) 2022 		Antonin MARCHAL             <[email protected]>
15
 * Copyright (C) 2024		MDW							<[email protected]>
16
 * Copyright (C) 2024       Rafael San José             <[email protected]>
17
 *
18
 * This program is free software; you can redistribute it and/or modify
19
 * it under the terms of the GNU General Public License as published by
20
 * the Free Software Foundation; either version 3 of the License, or
21
 * (at your option) any later version.
22
 *
23
 * This program is distributed in the hope that it will be useful,
24
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
26
 * GNU General Public License for more details.
27
 *
28
 * You should have received a copy of the GNU General Public License
29
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
30
 */
31
32
namespace Dolibarr\Code\Core\Classes;
33
34
use DoliDB;
35
36
/**
37
 *  \file       htdocs/core/class/extrafields.class.php
38
 *  \ingroup    core
39
 *  \brief      File of class to manage extra fields
40
 */
41
42
43
/**
44
 *  Class to manage standard extra fields
45
 */
46
class ExtraFields
47
{
48
    /**
49
     * @var DoliDB Database handler.
50
     */
51
    public $db;
52
53
    /**
54
     * @var array<string,array{label:array<string,string>,type:array<string,string>,size:array<string,string>,default:array<string,string>,computed:array<string,string>,unique:array<string,int>,required:array<string,int>,param:array<string,mixed>,perms:array<string,mixed[]>,list:array<string,int|string>,pos:array<string,int>,totalizable:array<string,int>,help:array<string,string>,printable:array<string,int>,enabled:array<string,int>,langfile:array<string,string>,css:array<string,string>,csslist:array<string,string>,hidden:array<string,int>,mandatoryfieldsofotherentities:array<string,string>,loaded?:int,count:int}> New array to store extrafields definition  Note: count set as present to avoid static analysis notices
55
     */
56
    public $attributes = array();
57
58
    /**
59
     * @var array<string,bool>  Array with boolean of status of groups
60
     */
61
    public $expand_display;
62
63
    /**
64
     * @var string Error code (or message)
65
     */
66
    public $error = '';
67
68
    /**
69
     * @var string[] Array of Error code (or message)
70
     */
71
    public $errors = array();
72
73
    /**
74
     * @var string  DB Error number
75
     */
76
    public $errno;
77
78
    /**
79
     * @var array<string,string>    Array of type to label
80
     */
81
    public static $type2label = array(
82
        'varchar' => 'String1Line',
83
        'text' => 'TextLongNLines',
84
        'html' => 'HtmlText',
85
        'int' => 'Int',
86
        'double' => 'Float',
87
        'date' => 'Date',
88
        'datetime' => 'DateAndTime',
89
        //'datetimegmt'=>'DateAndTimeUTC',
90
        'boolean' => 'Boolean',
91
        'price' => 'ExtrafieldPrice',
92
        'pricecy' => 'ExtrafieldPriceWithCurrency',
93
        'phone' => 'ExtrafieldPhone',
94
        'mail' => 'ExtrafieldMail',
95
        'url' => 'ExtrafieldUrl',
96
        'ip' => 'ExtrafieldIP',
97
        'icon' => 'Icon',
98
        'password' => 'ExtrafieldPassword',
99
        'select' => 'ExtrafieldSelect',
100
        'sellist' => 'ExtrafieldSelectList',
101
        'radio' => 'ExtrafieldRadio',
102
        'checkbox' => 'ExtrafieldCheckBox',
103
        'chkbxlst' => 'ExtrafieldCheckBoxFromList',
104
        'link' => 'ExtrafieldLink',
105
        'point' => 'ExtrafieldPointGeo',
106
        'multipts' => 'ExtrafieldMultiPointGeo',
107
        'linestrg' => 'ExtrafieldLinestringGeo',
108
        'polygon' => 'ExtrafieldPolygonGeo',
109
        'separate' => 'ExtrafieldSeparator',
110
    );
111
112
    /**
113
     *  Constructor
114
     *
115
     *  @param      DoliDB      $db      Database handler
116
     */
117
    public function __construct($db)
118
    {
119
        $this->db = $db;
120
    }
121
122
    /**
123
     *  Add a new extra field parameter
124
     *
125
     *  @param  string          $attrname           Code of attribute
126
     *  @param  string          $label              label of attribute
127
     *  @param  string          $type               Type of attribute ('boolean','int','varchar','text','html','date','datetime','price', 'pricecy', 'phone','mail','password','url','select','checkbox','separate',...)
128
     *  @param  int             $pos                Position of attribute
129
     *  @param  string          $size               Size/length definition of attribute ('5', '24,8', ...). For float, it contains 2 numeric separated with a comma.
130
     *  @param  string          $elementtype        Element type. Same value than object->table_element (Example 'member', 'product', 'thirdparty', ...)
131
     *  @param  int             $unique             Is field unique or not
132
     *  @param  int             $required           Is field required or not
133
     *  @param  string          $default_value      Defaulted value (In database. use the default_value feature for default value on screen. Example: '', '0', 'null', 'avalue')
134
     *  @param  array|string    $param              Params for field (ex for select list : array('options' => array(value'=>'label of option')) )
135
     *  @param  int             $alwayseditable     Is attribute always editable regardless of the document status
136
     *  @param  string          $perms              Permission to check
137
     *  @param  string          $list               Visibility ('0'=never visible, '1'=visible on list+forms, '2'=list only, '3'=form only or 'eval string')
138
     *  @param  string          $help               Text with help tooltip
139
     *  @param  string          $computed           Computed value
140
     *  @param  string          $entity             Entity of extrafields (for multicompany modules)
141
     *  @param  string          $langfile           Language file
142
     *  @param  string          $enabled            Condition to have the field enabled or not
143
     *  @param  int             $totalizable        Is a measure. Must show a total on lists
144
     *  @param  int             $printable          Is extrafield displayed on PDF
145
     *  @param  array           $moreparams         More parameters. Example: array('css'=>, 'csslist'=>Css on list, 'cssview'=>...)
146
     *  @return int                                 Return integer <=0 if KO, >0 if OK
147
     */
148
    public function addExtraField($attrname, $label, $type, $pos, $size, $elementtype, $unique = 0, $required = 0, $default_value = '', $param = '', $alwayseditable = 0, $perms = '', $list = '-1', $help = '', $computed = '', $entity = '', $langfile = '', $enabled = '1', $totalizable = 0, $printable = 0, $moreparams = array())
149
    {
150
        if (empty($attrname)) {
151
            return -1;
152
        }
153
        if (empty($label)) {
154
            return -1;
155
        }
156
157
        $result = 0;
158
159
        if ($type == 'separator' || $type == 'separate') {
160
            $type = 'separate';
161
            $unique = 0;
162
            $required = 0;
163
        }   // Force unique and not required if this is a separator field to avoid troubles.
164
        if ($elementtype == 'thirdparty') {
165
            $elementtype = 'societe';
166
        }
167
        if ($elementtype == 'contact') {
168
            $elementtype = 'socpeople';
169
        }
170
171
        // Create field into database except for separator type which is not stored in database
172
        if ($type != 'separate') {
173
            $result = $this->create($attrname, $type, $size, $elementtype, $unique, $required, $default_value, $param, $perms, $list, $computed, $help, $moreparams);
174
        }
175
        $err1 = $this->errno;
176
        if ($result > 0 || $err1 == 'DB_ERROR_COLUMN_ALREADY_EXISTS' || $type == 'separate') {
177
            // Add declaration of field into table
178
            $result2 = $this->create_label($attrname, $label, $type, $pos, $size, $elementtype, $unique, $required, $param, $alwayseditable, $perms, $list, $help, $default_value, $computed, $entity, $langfile, $enabled, $totalizable, $printable, $moreparams);
179
            $err2 = $this->errno;
180
            if ($result2 > 0 || ($err1 == 'DB_ERROR_COLUMN_ALREADY_EXISTS' && $err2 == 'DB_ERROR_RECORD_ALREADY_EXISTS')) {
181
                $this->error = '';
182
                $this->errno = '0';
183
                return 1;
184
            } else {
185
                return -2;
186
            }
187
        } else {
188
            return -1;
189
        }
190
    }
191
192
    /**
193
     *  Update an existing extra field parameter
194
     *
195
     *  @param  string          $attrname           Code of attribute
196
     *  @param  string          $label              label of attribute
197
     *  @param  string          $type               Type of attribute ('boolean','int','varchar','text','html','date','datetime','price', 'pricecy', 'phone','mail','password','url','select','checkbox','separate',...)
198
     *  @param  int             $pos                Position of attribute
199
     *  @param  string          $size               Size/length definition of attribute ('5', '24,8', ...). For float, it contains 2 numeric separated with a comma.
200
     *  @param  string          $elementtype        Element type. Same value than object->table_element (Example 'member', 'product', 'thirdparty', ...)
201
     *  @param  int             $unique             Is field unique or not
202
     *  @param  int             $required           Is field required or not
203
     *  @param  string          $default_value      Defaulted value (In database. use the default_value feature for default value on screen. Example: '', '0', 'null', 'avalue')
204
     *  @param  array|string    $param              Params for field (ex for select list : array('options' => array(value'=>'label of option')) )
205
     *  @param  int             $alwayseditable     Is attribute always editable regardless of the document status
206
     *  @param  string          $perms              Permission to check
207
     *  @param  string          $list               Visibility ('0'=never visible, '1'=visible on list+forms, '2'=list only, '3'=form only or 'eval string')
208
     *  @param  string          $help               Text with help tooltip
209
     *  @param  string          $computed           Computed value
210
     *  @param  string          $entity             Entity of extrafields (for multicompany modules)
211
     *  @param  string          $langfile           Language file
212
     *  @param  string          $enabled            Condition to have the field enabled or not
213
     *  @param  int             $totalizable        Is a measure. Must show a total on lists
214
     *  @param  int             $printable          Is extrafield displayed on PDF
215
     *  @param  array           $moreparams         More parameters. Example: array('css'=>, 'csslist'=>Css on list, 'cssview'=>...)
216
     *  @return int                                 Return integer <=0 if KO, >0 if OK
217
     */
218
    public function updateExtraField($attrname, $label, $type, $pos, $size, $elementtype, $unique = 0, $required = 0, $default_value = '', $param = '', $alwayseditable = 0, $perms = '', $list = '-1', $help = '', $computed = '', $entity = '', $langfile = '', $enabled = '1', $totalizable = 0, $printable = 0, $moreparams = array())
219
    {
220
        if (empty($attrname)) {
221
            return -1;
222
        }
223
        if (empty($label)) {
224
            return -1;
225
        }
226
227
        $result = 0;
228
229
        if ($type == 'separator' || $type == 'separate') {
230
            $type = 'separate';
231
            $unique = 0;
232
            $required = 0;
233
        }   // Force unique and not required if this is a separator field to avoid troubles.
234
        if ($elementtype == 'thirdparty') {
235
            $elementtype = 'societe';
236
        }
237
        if ($elementtype == 'contact') {
238
            $elementtype = 'socpeople';
239
        }
240
241
        // Create field into database except for separator type which is not stored in database
242
        if ($type != 'separate') {
243
            dol_syslog(get_class($this) . '::thisupdate', LOG_DEBUG);
244
            $result = $this->update($attrname, $label, $type, $size, $elementtype, $unique, $required, $pos, $param, $alwayseditable, $perms, $list, $help, $default_value, $computed, $entity, $langfile, $enabled, $totalizable, $printable, $moreparams);
245
        }
246
        $err1 = $this->errno;
247
        if ($result > 0 || $err1 == 'DB_ERROR_COLUMN_ALREADY_EXISTS' || $type == 'separate') {
248
            // Add declaration of field into table
249
            dol_syslog(get_class($this) . '::thislabel', LOG_DEBUG);
250
            $result2 = $this->update_label($attrname, $label, $type, $size, $elementtype, $unique, $required, $pos, $param, $alwayseditable, $perms, $list, $help, $default_value, $computed, $entity, $langfile, $enabled, $totalizable, $printable, $moreparams);
251
            $err2 = $this->errno;
252
            if ($result2 > 0 || ($err1 == 'DB_ERROR_COLUMN_ALREADY_EXISTS' && $err2 == 'DB_ERROR_RECORD_ALREADY_EXISTS')) {
253
                $this->error = '';
254
                $this->errno = '0';
255
                return 1;
256
            } else {
257
                return -2;
258
            }
259
        } else {
260
            return -1;
261
        }
262
    }
263
264
    /**
265
     *  Add a new optional attribute.
266
     *  This is a private method. For public method, use addExtraField.
267
     *
268
     *  @param  string  $attrname           code of attribute
269
     *  @param  string  $type               Type of attribute ('boolean', 'int', 'varchar', 'text', 'html', 'date', 'datetime', 'price', 'pricecy', 'phone', 'mail', 'password', 'url', 'select', 'checkbox', ...)
270
     *  @param  string  $length             Size/length of attribute ('5', '24,8', ...)
271
     *  @param  string  $elementtype        Element type ('member', 'product', 'thirdparty', 'contact', ...)
272
     *  @param  int     $unique             Is field unique or not
273
     *  @param  int     $required           Is field required or not
274
     *  @param  string  $default_value      Default value for field (in database)
275
     *  @param  array   $param              Params for field  (ex for select list : array('options'=>array('value'=>'label of option'))
276
     *  @param  string  $perms              Permission
277
     *  @param  string  $list               Into list view by default
278
     *  @param  string  $computed           Computed value
279
     *  @param  string  $help               Help on tooltip
280
     *  @param  array   $moreparams         More parameters. Example: array('css'=>, 'csslist'=>, 'cssview'=>...)
281
     *  @return int                         Return integer <=0 if KO, >0 if OK
282
     */
283
    private function create($attrname, $type = 'varchar', $length = '255', $elementtype = '', $unique = 0, $required = 0, $default_value = '', $param = array(), $perms = '', $list = '0', $computed = '', $help = '', $moreparams = array())
284
    {
285
        if ($elementtype == 'thirdparty') {
286
            $elementtype = 'societe';
287
        }
288
        if ($elementtype == 'contact') {
289
            $elementtype = 'socpeople';
290
        }
291
292
        $table = $elementtype . '_extrafields';
293
        if ($elementtype == 'categorie') {
294
            $table = 'categories_extrafields';
295
        }
296
297
        if (!empty($attrname) && preg_match("/^\w[a-zA-Z0-9_]*$/", $attrname) && !is_numeric($attrname)) {
298
            if ($type == 'boolean') {
299
                $typedb = 'int';
300
                $lengthdb = '1';
301
            } elseif ($type == 'price') {
302
                $typedb = 'double';
303
                $lengthdb = '24,8';
304
            } elseif ($type == 'pricecy') {
305
                $typedb = 'varchar';
306
                $lengthdb = '64';
307
            } elseif ($type == 'phone') {
308
                $typedb = 'varchar';
309
                $lengthdb = '20';
310
            } elseif ($type == 'mail' || $type == 'ip' || $type == 'icon') {
311
                $typedb = 'varchar';
312
                $lengthdb = '128';
313
            } elseif ($type == 'url') {
314
                $typedb = 'varchar';
315
                $lengthdb = '255';
316
            } elseif (($type == 'select') || ($type == 'sellist') || ($type == 'radio') || ($type == 'checkbox') || ($type == 'chkbxlst')) {
317
                $typedb = 'varchar';
318
                $lengthdb = '255';
319
            } elseif ($type == 'link') {
320
                $typedb = 'int';
321
                $lengthdb = '11';
322
            } elseif ($type == 'point') {
323
                $typedb = 'point';
324
                $lengthdb = '';
325
            } elseif ($type == 'multipts') {
326
                $typedb = 'multipoint';
327
                $lengthdb = '';
328
            } elseif ($type == 'linestrg') {
329
                $typedb = 'linestring';
330
                $lengthdb = '';
331
            } elseif ($type == 'polygon') {
332
                $typedb = 'polygon';
333
                $lengthdb = '';
334
            } elseif ($type == 'html') {
335
                $typedb = 'text';
336
                $lengthdb = $length;
337
            } elseif ($type == 'password') {
338
                $typedb = 'varchar';
339
                $lengthdb = '128';
340
            } else {
341
                $typedb = $type;
342
                $lengthdb = $length;
343
                if ($type == 'varchar' && empty($lengthdb)) {
344
                    $lengthdb = '255';
345
                }
346
            }
347
            $field_desc = array(
348
                'type' => $typedb,
349
                'value' => $lengthdb,
350
                'null' => ($required ? 'NOT NULL' : 'NULL'),
351
                'default' => $default_value
352
            );
353
354
            $result = $this->db->DDLAddField($this->db->prefix() . $table, $attrname, $field_desc);
355
            if ($result > 0) {
356
                if ($unique) {
357
                    $sql = "ALTER TABLE " . $this->db->prefix() . $table . " ADD UNIQUE INDEX uk_" . $table . "_" . $attrname . " (" . $attrname . ")";
358
                    $resql = $this->db->query($sql, 1, 'dml');
359
                }
360
                return 1;
361
            } else {
362
                $this->error = $this->db->lasterror();
363
                $this->errno = $this->db->lasterrno();
364
                return -1;
365
            }
366
        } else {
367
            return 0;
368
        }
369
    }
370
371
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
372
    /**
373
     *  Add description of a new optional attribute
374
     *
375
     *  @param  string          $attrname       code of attribute
376
     *  @param  string          $label          label of attribute
377
     *  @param  string          $type           Type of attribute ('int', 'varchar', 'text', 'html', 'date', 'datehour', 'float')
378
     *  @param  int             $pos            Position of attribute
379
     *  @param  string          $size           Size/length of attribute ('5', '24,8', ...)
380
     *  @param  string          $elementtype    Element type ('member', 'product', 'thirdparty', ...)
381
     *  @param  int             $unique         Is field unique or not
382
     *  @param  int             $required       Is field required or not
383
     *  @param  array|string    $param          Params for field  (ex for select list : array('options' => array(value'=>'label of option')) )
384
     *  @param  int             $alwayseditable Is attribute always editable regardless of the document status
385
     *  @param  string          $perms          Permission to check
386
     *  @param  string          $list           Visibility
387
     *  @param  string          $help           Help on tooltip
388
     *  @param  string          $default        Default value (in database. use the default_value feature for default value on screen).
389
     *  @param  string          $computed       Computed value
390
     *  @param  string          $entity         Entity of extrafields
391
     *  @param  string          $langfile       Language file
392
     *  @param  string          $enabled        Condition to have the field enabled or not
393
     *  @param  int             $totalizable    Is a measure. Must show a total on lists
394
     *  @param  int             $printable      Is extrafield displayed on PDF
395
     *  @param  array           $moreparams     More parameters. Example: array('css'=>, 'csslist'=>, 'cssview'=>...)
396
     *  @return int                             Return integer <=0 if KO, >0 if OK
397
     *  @throws Exception
398
     */
399
    private function create_label($attrname, $label = '', $type = '', $pos = 0, $size = '', $elementtype = '', $unique = 0, $required = 0, $param = '', $alwayseditable = 0, $perms = '', $list = '-1', $help = '', $default = '', $computed = '', $entity = '', $langfile = '', $enabled = '1', $totalizable = 0, $printable = 0, $moreparams = array())
400
    {
401
		// phpcs:enable
402
        global $conf, $user;
403
404
        if ($elementtype == 'thirdparty') {
405
            $elementtype = 'societe';
406
        }
407
        if ($elementtype == 'contact') {
408
            $elementtype = 'socpeople';
409
        }
410
411
        // Clean parameters
412
        if (empty($pos)) {
413
            $pos = 0;
414
        }
415
        if (empty($list)) {
416
            $list = '0';
417
        }
418
        if (empty($required)) {
419
            $required = 0;
420
        }
421
        if (empty($unique)) {
422
            $unique = 0;
423
        }
424
        if (empty($printable)) {
425
            $printable = 0;
426
        }
427
        if (empty($alwayseditable)) {
428
            $alwayseditable = 0;
429
        }
430
        if (empty($totalizable)) {
431
            $totalizable = 0;
432
        }
433
434
        $css = '';
435
        if (!empty($moreparams) && !empty($moreparams['css'])) {
436
            $css = $moreparams['css'];
437
        }
438
        $csslist = '';
439
        if (!empty($moreparams) && !empty($moreparams['csslist'])) {
440
            $csslist = $moreparams['csslist'];
441
        }
442
        $cssview = '';
443
        if (!empty($moreparams) && !empty($moreparams['cssview'])) {
444
            $cssview = $moreparams['cssview'];
445
        }
446
447
        if (!empty($attrname) && preg_match("/^\w[a-zA-Z0-9-_]*$/", $attrname) && !is_numeric($attrname)) {
448
            if (is_array($param) && count($param) > 0) {
449
                $params = serialize($param);
450
            } elseif (strlen($param) > 0) {
451
                $params = trim($param);
452
            } else {
453
                $params = '';
454
            }
455
456
            $sql = "INSERT INTO " . $this->db->prefix() . "extrafields(";
457
            $sql .= " name,";
458
            $sql .= " label,";
459
            $sql .= " type,";
460
            $sql .= " pos,";
461
            $sql .= " size,";
462
            $sql .= " entity,";
463
            $sql .= " elementtype,";
464
            $sql .= " fieldunique,";
465
            $sql .= " fieldrequired,";
466
            $sql .= " param,";
467
            $sql .= " alwayseditable,";
468
            $sql .= " perms,";
469
            $sql .= " langs,";
470
            $sql .= " list,";
471
            $sql .= " printable,";
472
            $sql .= " fielddefault,";
473
            $sql .= " fieldcomputed,";
474
            $sql .= " fk_user_author,";
475
            $sql .= " fk_user_modif,";
476
            $sql .= " datec,";
477
            $sql .= " enabled,";
478
            $sql .= " help,";
479
            $sql .= " totalizable,";
480
            $sql .= " css,";
481
            $sql .= " csslist,";
482
            $sql .= " cssview";
483
            $sql .= " )";
484
            $sql .= " VALUES('" . $this->db->escape($attrname) . "',";
485
            $sql .= " '" . $this->db->escape($label) . "',";
486
            $sql .= " '" . $this->db->escape($type) . "',";
487
            $sql .= " " . ((int) $pos) . ",";
488
            $sql .= " '" . $this->db->escape($size) . "',";
489
            $sql .= " " . ($entity === '' ? $conf->entity : $entity) . ",";
490
            $sql .= " '" . $this->db->escape($elementtype) . "',";
491
            $sql .= " " . ((int) $unique) . ",";
492
            $sql .= " " . ((int) $required) . ",";
493
            $sql .= " '" . $this->db->escape($params) . "',";
494
            $sql .= " " . ((int) $alwayseditable) . ",";
495
            $sql .= " " . ($perms ? "'" . $this->db->escape($perms) . "'" : "null") . ",";
496
            $sql .= " " . ($langfile ? "'" . $this->db->escape($langfile) . "'" : "null") . ",";
497
            $sql .= " '" . $this->db->escape($list) . "',";
498
            $sql .= " '" . $this->db->escape($printable) . "',";
499
            $sql .= " " . ($default ? "'" . $this->db->escape($default) . "'" : "null") . ",";
500
            $sql .= " " . ($computed ? "'" . $this->db->escape($computed) . "'" : "null") . ",";
501
            $sql .= " " . (is_object($user) ? $user->id : 0) . ",";
502
            $sql .= " " . (is_object($user) ? $user->id : 0) . ",";
503
            $sql .= "'" . $this->db->idate(dol_now()) . "',";
504
            $sql .= " " . ($enabled ? "'" . $this->db->escape($enabled) . "'" : "1") . ",";
505
            $sql .= " " . ($help ? "'" . $this->db->escape($help) . "'" : "null") . ",";
506
            $sql .= " " . ($totalizable ? 'TRUE' : 'FALSE') . ",";
507
            $sql .= " " . ($css ? "'" . $this->db->escape($css) . "'" : "null") . ",";
508
            $sql .= " " . ($csslist ? "'" . $this->db->escape($csslist) . "'" : "null") . ",";
509
            $sql .= " " . ($cssview ? "'" . $this->db->escape($cssview) . "'" : "null");
510
            $sql .= ')';
511
512
            if ($this->db->query($sql)) {
513
                dol_syslog(get_class($this) . "::create_label_success", LOG_DEBUG);
514
                return 1;
515
            } else {
516
                dol_syslog(get_class($this) . "::create_label_error", LOG_DEBUG);
517
                $this->error = $this->db->lasterror();
518
                $this->errno = $this->db->lasterrno();
519
                return -1;
520
            }
521
        }
522
        return -1;
523
    }
524
525
    /**
526
     *  Delete an optional attribute
527
     *
528
     *  @param  string  $attrname       Code of attribute to delete
529
     *  @param  string  $elementtype    Element type ('member', 'product', 'thirdparty', 'contact', ...)
530
     *  @return int                     Return integer < 0 if KO, 0 if nothing is done, 1 if OK
531
     */
532
    public function delete($attrname, $elementtype = '')
533
    {
534
        if ($elementtype == 'thirdparty') {
535
            $elementtype = 'societe';
536
        }
537
        if ($elementtype == 'contact') {
538
            $elementtype = 'socpeople';
539
        }
540
541
        $table = $elementtype . '_extrafields';
542
        if ($elementtype == 'categorie') {
543
            $table = 'categories_extrafields';
544
        }
545
546
        $error = 0;
547
548
        if (!empty($attrname) && preg_match("/^\w[a-zA-Z0-9-_]*$/", $attrname)) {
549
            $result = $this->delete_label($attrname, $elementtype);
550
            if ($result < 0) {
551
                $this->error = $this->db->lasterror();
552
                $this->errors[] = $this->db->lasterror();
553
                $error++;
554
            }
555
556
            if (!$error) {
557
                $sql = "SELECT COUNT(rowid) as nb";
558
                $sql .= " FROM " . $this->db->prefix() . "extrafields";
559
                $sql .= " WHERE elementtype = '" . $this->db->escape($elementtype) . "'";
560
                $sql .= " AND name = '" . $this->db->escape($attrname) . "'";
561
                //$sql.= " AND entity IN (0,".$conf->entity.")";      Do not test on entity here. We want to see if there is still on field remaining in other entities before deleting field in table
562
                $resql = $this->db->query($sql);
563
                if ($resql) {
564
                    $obj = $this->db->fetch_object($resql);
565
                    if ($obj->nb <= 0) {
566
                        $result = $this->db->DDLDropField($this->db->prefix() . $table, $attrname); // This also drop the unique key
567
                        if ($result < 0) {
568
                            $this->error = $this->db->lasterror();
569
                            $this->errors[] = $this->db->lasterror();
570
                            $error++;
571
                        }
572
                    }
573
                }
574
            }
575
576
            return $result;
577
        } else {
578
            return 0;
579
        }
580
    }
581
582
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
583
    /**
584
     *  Delete description of an optional attribute
585
     *
586
     *  @param  string  $attrname           Code of attribute to delete
587
     *  @param  string  $elementtype        Element type ('member', 'product', 'thirdparty', ...)
588
     *  @return int                         Return integer < 0 if KO, 0 if nothing is done, 1 if OK
589
     */
590
    private function delete_label($attrname, $elementtype = '')
591
    {
592
		// phpcs:enable
593
        global $conf;
594
595
        if ($elementtype == 'thirdparty') {
596
            $elementtype = 'societe';
597
        }
598
        if ($elementtype == 'contact') {
599
            $elementtype = 'socpeople';
600
        }
601
602
        if (isset($attrname) && $attrname != '' && preg_match("/^\w[a-zA-Z0-9-_]*$/", $attrname)) {
603
            $sql = "DELETE FROM " . $this->db->prefix() . "extrafields";
604
            $sql .= " WHERE name = '" . $this->db->escape($attrname) . "'";
605
            $sql .= " AND entity IN  (0," . $conf->entity . ')';
606
            $sql .= " AND elementtype = '" . $this->db->escape($elementtype) . "'";
607
608
            dol_syslog(get_class($this) . "::delete_label", LOG_DEBUG);
609
            $resql = $this->db->query($sql);
610
            if ($resql) {
611
                return 1;
612
            } else {
613
                dol_print_error($this->db);
614
                return -1;
615
            }
616
        } else {
617
            return 0;
618
        }
619
    }
620
621
    /**
622
     *  Modify type of a personalized attribute
623
     *
624
     *  @param  string  $attrname           Name of attribute
625
     *  @param  string  $label              Label of attribute
626
     *  @param  string  $type               Type of attribute ('boolean', 'int', 'varchar', 'text', 'html', 'date', 'datetime','price','phone','mail','password','url','select','checkbox', ...)
627
     *  @param  int     $length             Length of attribute
628
     *  @param  string  $elementtype        Element type ('member', 'product', 'thirdparty', 'contact', ...)
629
     *  @param  int     $unique             Is field unique or not
630
     *  @param  int     $required           Is field required or not
631
     *  @param  int     $pos                Position of attribute
632
     *  @param  array   $param              Params for field (ex for select list : array('options' => array(value'=>'label of option')) )
633
     *  @param  int     $alwayseditable     Is attribute always editable regardless of the document status
634
     *  @param  string  $perms              Permission to check
635
     *  @param  string  $list               Visibility
636
     *  @param  string  $help               Help on tooltip
637
     *  @param  string  $default            Default value (in database. use the default_value feature for default value on screen).
638
     *  @param  string  $computed           Computed value
639
     *  @param  string  $entity             Entity of extrafields
640
     *  @param  string  $langfile           Language file
641
     *  @param  string  $enabled            Condition to have the field enabled or not
642
     *  @param  int     $totalizable        Is extrafield totalizable on list
643
     *  @param  int     $printable          Is extrafield displayed on PDF
644
     *  @param  array   $moreparams         More parameters. Example: array('css'=>, 'csslist'=>, 'cssview'=>...)
645
     *  @return int                         >0 if OK, <=0 if KO
646
     *  @throws Exception
647
     */
648
    public function update($attrname, $label, $type, $length, $elementtype, $unique = 0, $required = 0, $pos = 0, $param = array(), $alwayseditable = 0, $perms = '', $list = '', $help = '', $default = '', $computed = '', $entity = '', $langfile = '', $enabled = '1', $totalizable = 0, $printable = 0, $moreparams = array())
649
    {
650
        global $action, $hookmanager;
651
652
        if ($elementtype == 'thirdparty') {
653
            $elementtype = 'societe';
654
        }
655
        if ($elementtype == 'contact') {
656
            $elementtype = 'socpeople';
657
        }
658
659
        $table = $elementtype . '_extrafields';
660
        if ($elementtype == 'categorie') {
661
            $table = 'categories_extrafields';
662
        }
663
664
        if (isset($attrname) && $attrname != '' && preg_match("/^\w[a-zA-Z0-9-_]*$/", $attrname)) {
665
            if ($type == 'boolean') {
666
                $typedb = 'int';
667
                $lengthdb = '1';
668
            } elseif ($type == 'price') {
669
                $typedb = 'double';
670
                $lengthdb = '24,8';
671
            } elseif ($type == 'pricecy') {
672
                $typedb = 'varchar';
673
                $lengthdb = '64';
674
            } elseif ($type == 'phone') {
675
                $typedb = 'varchar';
676
                $lengthdb = '20';
677
            } elseif ($type == 'mail' || $type == 'ip' || $type == 'icon') {
678
                $typedb = 'varchar';
679
                $lengthdb = '128';
680
            } elseif ($type == 'url') {
681
                $typedb = 'varchar';
682
                $lengthdb = '255';
683
            } elseif (($type == 'select') || ($type == 'sellist') || ($type == 'radio') || ($type == 'checkbox') || ($type == 'chkbxlst')) {
684
                $typedb = 'varchar';
685
                $lengthdb = '255';
686
            } elseif ($type == 'html') {
687
                $typedb = 'text';
688
                $lengthdb = $length;
689
            } elseif ($type == 'link') {
690
                $typedb = 'int';
691
                $lengthdb = '11';
692
            } elseif ($type == 'point') {
693
                $typedb = 'point';
694
                $lengthdb = '';
695
            } elseif ($type == 'multipts') {
696
                $typedb = 'multipoint';
697
                $lengthdb = '';
698
            } elseif ($type == 'linestrg') {
699
                $typedb = 'linestring';
700
                $lengthdb = '';
701
            } elseif ($type == 'polygon') {
702
                $typedb = 'polygon';
703
                $lengthdb = '';
704
            } elseif ($type == 'password') {
705
                $typedb = 'varchar';
706
                $lengthdb = '128';
707
            } else {
708
                $typedb = $type;
709
                $lengthdb = $length;
710
            }
711
            $field_desc = array('type' => $typedb, 'value' => $lengthdb, 'null' => ($required ? 'NOT NULL' : 'NULL'), 'default' => $default);
712
713
            if (is_object($hookmanager)) {
714
                $hookmanager->initHooks(array('extrafieldsdao'));
715
                $parameters = array('field_desc' => &$field_desc, 'table' => $table, 'attr_name' => $attrname, 'label' => $label, 'type' => $type, 'length' => $length, 'unique' => $unique, 'required' => $required, 'pos' => $pos, 'param' => $param, 'alwayseditable' => $alwayseditable, 'perms' => $perms, 'list' => $list, 'help' => $help, 'default' => $default, 'computed' => $computed, 'entity' => $entity, 'langfile' => $langfile, 'enabled' => $enabled, 'totalizable' => $totalizable, 'printable' => $printable);
716
                $reshook = $hookmanager->executeHooks('updateExtrafields', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
717
718
                if ($reshook < 0) {
719
                    $this->error = $this->db->lasterror();
720
                    return -1;
721
                }
722
            }
723
724
            dol_syslog(get_class($this) . '::DDLUpdateField', LOG_DEBUG);
725
            if ($type != 'separate') { // No table update when separate type
726
                $result = $this->db->DDLUpdateField($this->db->prefix() . $table, $attrname, $field_desc);
727
            }
728
            if ($result > 0 || $type == 'separate') {
729
                if ($label) {
730
                    dol_syslog(get_class($this) . '::update_label', LOG_DEBUG);
731
                    $result = $this->update_label($attrname, $label, $type, $length, $elementtype, $unique, $required, $pos, $param, $alwayseditable, $perms, $list, $help, $default, $computed, $entity, $langfile, $enabled, $totalizable, $printable, $moreparams);
732
                }
733
                if ($result > 0) {
734
                    $sql = '';
735
                    if ($unique) {
736
                        dol_syslog(get_class($this) . '::update_unique', LOG_DEBUG);
737
                        $sql = "ALTER TABLE " . $this->db->prefix() . $table . " ADD UNIQUE INDEX uk_" . $table . "_" . $this->db->sanitize($attrname) . " (" . $this->db->sanitize($attrname) . ")";
738
                    } else {
739
                        dol_syslog(get_class($this) . '::update_common', LOG_DEBUG);
740
                        $sql = "ALTER TABLE " . $this->db->prefix() . $table . " DROP INDEX IF EXISTS uk_" . $table . "_" . $this->db->sanitize($attrname);
741
                    }
742
                    dol_syslog(get_class($this) . '::update', LOG_DEBUG);
743
                    $resql = $this->db->query($sql, 1, 'dml');
744
                    /*if ($resql < 0) {
745
                        $this->error = $this->db->lasterror();
746
                        return -1;
747
                    }*/
748
                    return 1;
749
                } else {
750
                    $this->error = $this->db->lasterror();
751
                    return -1;
752
                }
753
            } else {
754
                $this->error = $this->db->lasterror();
755
                return -1;
756
            }
757
        } else {
758
            return 0;
759
        }
760
    }
761
762
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
763
    /**
764
     *  Modify description of personalized attribute
765
     *  This is a private method. For public method, use updateExtraField.
766
     *
767
     *  @param  string  $attrname           Name of attribute
768
     *  @param  string  $label              Label of attribute
769
     *  @param  string  $type               Type of attribute
770
     *  @param  int     $size               Length of attribute
771
     *  @param  string  $elementtype        Element type ('member', 'product', 'thirdparty', ...)
772
     *  @param  int     $unique             Is field unique or not
773
     *  @param  int     $required           Is field required or not
774
     *  @param  int     $pos                Position of attribute
775
     *  @param  array   $param              Params for field  (ex for select list : array('options' => array(value'=>'label of option')) )
776
     *  @param  int     $alwayseditable     Is attribute always editable regardless of the document status
777
     *  @param  string  $perms              Permission to check
778
     *  @param  string  $list               Visibility
779
     *  @param  string  $help               Help on tooltip.
780
     *  @param  string  $default            Default value (in database. use the default_value feature for default value on screen).
781
     *  @param  string  $computed           Computed value
782
     *  @param  string  $entity             Entity of extrafields
783
     *  @param  string  $langfile           Language file
784
     *  @param  string  $enabled            Condition to have the field enabled or not
785
     *  @param  int     $totalizable        Is extrafield totalizable on list
786
     *  @param  int     $printable          Is extrafield displayed on PDF
787
     *  @param  array   $moreparams         More parameters. Example: array('css'=>, 'csslist'=>, 'cssview'=>...)
788
     *  @return int                         Return integer <=0 if KO, >0 if OK
789
     *  @throws Exception
790
     */
791
    private function update_label($attrname, $label, $type, $size, $elementtype, $unique = 0, $required = 0, $pos = 0, $param = array(), $alwayseditable = 0, $perms = '', $list = '0', $help = '', $default = '', $computed = '', $entity = '', $langfile = '', $enabled = '1', $totalizable = 0, $printable = 0, $moreparams = array())
792
    {
793
		// phpcs:enable
794
        global $conf, $user;
795
        dol_syslog(get_class($this) . "::update_label " . $attrname . ", " . $label . ", " . $type . ", " . $size . ", " . $elementtype . ", " . $unique . ", " . $required . ", " . $pos . ", " . $alwayseditable . ", " . $perms . ", " . $list . ", " . $default . ", " . $computed . ", " . $entity . ", " . $langfile . ", " . $enabled . ", " . $totalizable . ", " . $printable);
796
797
        // Clean parameters
798
        if ($elementtype == 'thirdparty') {
799
            $elementtype = 'societe';
800
        }
801
        if ($elementtype == 'contact') {
802
            $elementtype = 'socpeople';
803
        }
804
805
        if (empty($pos)) {
806
            $pos = 0;
807
        }
808
        if (empty($list)) {
809
            $list = '0';
810
        }
811
        if (empty($totalizable)) {
812
            $totalizable = 0;
813
        }
814
        if (empty($required)) {
815
            $required = 0;
816
        }
817
        if (empty($unique)) {
818
            $unique = 0;
819
        }
820
        if (empty($alwayseditable)) {
821
            $alwayseditable = 0;
822
        }
823
824
        $css = '';
825
        if (!empty($moreparams) && !empty($moreparams['css'])) {
826
            $css = $moreparams['css'];
827
        }
828
        $csslist = '';
829
        if (!empty($moreparams) && !empty($moreparams['csslist'])) {
830
            $csslist = $moreparams['csslist'];
831
        }
832
        $cssview = '';
833
        if (!empty($moreparams) && !empty($moreparams['cssview'])) {
834
            $cssview = $moreparams['cssview'];
835
        }
836
837
        if (isset($attrname) && $attrname != '' && preg_match("/^\w[a-zA-Z0-9-_]*$/", $attrname)) {
838
            $this->db->begin();
839
840
            if (is_array($param) && count($param) > 0) {
841
                $params = serialize($param);
842
            } elseif (is_array($param)) {
843
                $params = '';
844
            } elseif (strlen($param) > 0) {
845
                $params = trim($param);
846
            } else {
847
                $params = '';
848
            }
849
850
            if ($entity === '' || $entity != '0') {
851
                // We don't want on all entities, we delete all and current
852
                $sql_del = "DELETE FROM " . $this->db->prefix() . "extrafields";
853
                $sql_del .= " WHERE name = '" . $this->db->escape($attrname) . "'";
854
                $sql_del .= " AND entity IN (0, " . ($entity === '' ? $conf->entity : $entity) . ")";
855
                $sql_del .= " AND elementtype = '" . $this->db->escape($elementtype) . "'";
856
            } else {
857
                // We want on all entities ($entities = '0'), we delete on all only (we keep setup specific to each entity)
858
                $sql_del = "DELETE FROM " . $this->db->prefix() . "extrafields";
859
                $sql_del .= " WHERE name = '" . $this->db->escape($attrname) . "'";
860
                $sql_del .= " AND entity = 0";
861
                $sql_del .= " AND elementtype = '" . $this->db->escape($elementtype) . "'";
862
            }
863
            $resql1 = $this->db->query($sql_del);
864
865
            $sql = "INSERT INTO " . $this->db->prefix() . "extrafields(";
866
            $sql .= " name,"; // This is code
867
            $sql .= " entity,";
868
            $sql .= " label,";
869
            $sql .= " type,";
870
            $sql .= " size,";
871
            $sql .= " elementtype,";
872
            $sql .= " fieldunique,";
873
            $sql .= " fieldrequired,";
874
            $sql .= " perms,";
875
            $sql .= " langs,";
876
            $sql .= " pos,";
877
            $sql .= " alwayseditable,";
878
            $sql .= " param,";
879
            $sql .= " list,";
880
            $sql .= " printable,";
881
            $sql .= " totalizable,";
882
            $sql .= " fielddefault,";
883
            $sql .= " fieldcomputed,";
884
            $sql .= " fk_user_author,";
885
            $sql .= " fk_user_modif,";
886
            $sql .= " datec,";
887
            $sql .= " enabled,";
888
            $sql .= " help,";
889
            $sql .= " css,";
890
            $sql .= " csslist,";
891
            $sql .= " cssview";
892
            $sql .= ") VALUES (";
893
            $sql .= "'" . $this->db->escape($attrname) . "',";
894
            $sql .= " " . ($entity === '' ? $conf->entity : $entity) . ",";
895
            $sql .= " '" . $this->db->escape($label) . "',";
896
            $sql .= " '" . $this->db->escape($type) . "',";
897
            $sql .= " '" . $this->db->escape($size) . "',";
898
            $sql .= " '" . $this->db->escape($elementtype) . "',";
899
            $sql .= " " . $unique . ",";
900
            $sql .= " " . $required . ",";
901
            $sql .= " " . ($perms ? "'" . $this->db->escape($perms) . "'" : "null") . ",";
902
            $sql .= " " . ($langfile ? "'" . $this->db->escape($langfile) . "'" : "null") . ",";
903
            $sql .= " " . $pos . ",";
904
            $sql .= " '" . $this->db->escape($alwayseditable) . "',";
905
            $sql .= " '" . $this->db->escape($params) . "',";
906
            $sql .= " '" . $this->db->escape($list) . "',";
907
            $sql .= " " . ((int) $printable) . ",";
908
            $sql .= " " . ($totalizable ? 'TRUE' : 'FALSE') . ",";
909
            $sql .= " " . (($default != '') ? "'" . $this->db->escape($default) . "'" : "null") . ",";
910
            $sql .= " " . ($computed ? "'" . $this->db->escape($computed) . "'" : "null") . ",";
911
            $sql .= " " . $user->id . ",";
912
            $sql .= " " . $user->id . ",";
913
            $sql .= "'" . $this->db->idate(dol_now()) . "',";
914
            $sql .= "'" . $this->db->escape($enabled) . "',";
915
            $sql .= " " . ($help ? "'" . $this->db->escape($help) . "'" : "null") . ",";
916
            $sql .= " " . ($css ? "'" . $this->db->escape($css) . "'" : "null") . ",";
917
            $sql .= " " . ($csslist ? "'" . $this->db->escape($csslist) . "'" : "null") . ",";
918
            $sql .= " " . ($cssview ? "'" . $this->db->escape($cssview) . "'" : "null");
919
            $sql .= ")";
920
921
            $resql2 = $this->db->query($sql);
922
923
            if ($resql1 && $resql2) {
924
                $this->db->commit();
925
                return 1;
926
            } else {
927
                $this->db->rollback();
928
                dol_print_error($this->db);
929
                return -1;
930
            }
931
        } else {
932
            return 0;
933
        }
934
    }
935
936
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
937
    /**
938
     *  Load the array of extrafields definition $this->attributes
939
     *
940
     *  @param  string      $elementtype        Type of element ('all' = all or $object->table_element like 'adherent', 'commande', 'thirdparty', 'facture', 'propal', 'product', ...).
941
     *  @param  boolean     $forceload          Force load of extra fields whatever is status of cache.
942
     *  @param  string      $attrname           The name of the attribute.
943
     *  @return array{}|array{label:array<string,string>,type:array<string,string>,size:array<string,string>,default:array<string,string>,computed:array<string,string>,unique:array<string,int>,required:array<string,int>,param:array<string,mixed>,perms:array<string,mixed[]>,list:array<string,int>|array<string,string>,pos:array<string,int>,totalizable:array<string,int>,help:array<string,string>,printable:array<string,int>,enabled:array<string,int>,langfile:array<string,string>,css:array<string,string>,csslist:array<string,string>,hidden:array<string,int>,mandatoryfieldsofotherentities?:array<string,string>,loaded?:int,count:int}      Array of attributes keys+label for all extra fields.  Note: count set as present to avoid static analysis notices
944
     */
945
    public function fetch_name_optionals_label($elementtype, $forceload = false, $attrname = '')
946
    {
947
		// phpcs:enable
948
        global $conf;
949
950
        if (empty($elementtype)) {
951
            return array();
952
        }
953
954
        if ($elementtype == 'thirdparty') {
955
            $elementtype = 'societe';
956
        }
957
        if ($elementtype == 'contact') {
958
            $elementtype = 'socpeople';
959
        }
960
        if ($elementtype == 'order_supplier') {
961
            $elementtype = 'commande_fournisseur';
962
        }
963
964
        // Test cache $this->attributes[$elementtype]['loaded'] to see if we must do something
965
        // TODO
966
967
        $array_name_label = array();
968
969
        // We should not have several time this request. If we have, there is some optimization to do by calling a simple $extrafields->fetch_optionals() in top of code and not into subcode
970
        $sql = "SELECT rowid, name, label, type, size, elementtype, fieldunique, fieldrequired, param, pos, alwayseditable, perms, langs, list, printable, totalizable, fielddefault, fieldcomputed, entity, enabled, help,";
971
        $sql .= " css, cssview, csslist";
972
        $sql .= " FROM " . $this->db->prefix() . "extrafields";
973
        //$sql.= " WHERE entity IN (0,".$conf->entity.")";    // Filter is done later
974
        if ($elementtype && $elementtype != 'all') {
975
            $sql .= " WHERE elementtype = '" . $this->db->escape($elementtype) . "'"; // Filed with object->table_element
976
        }
977
        if ($attrname && $elementtype && $elementtype != 'all') {
978
            $sql .= " AND name = '" . $this->db->escape($attrname) . "'";
979
        }
980
        $sql .= " ORDER BY pos";
981
982
        $resql = $this->db->query($sql);
983
        if ($resql) {
984
            $count = 0;
985
            if ($this->db->num_rows($resql)) {
986
                while ($tab = $this->db->fetch_object($resql)) {
987
                    if ($tab->entity != 0 && $tab->entity != $conf->entity) {
988
                        // This field is not in current entity. We discard but before we save it into the array of mandatory fields if it is a mandatory field without default value
989
                        if ($tab->fieldrequired && is_null($tab->fielddefault)) {
990
                            $this->attributes[$tab->elementtype]['mandatoryfieldsofotherentities'][$tab->name] = $tab->type;
991
                        }
992
                        continue;
993
                    }
994
995
                    // We can add this attribute to object. TODO Remove this and return $this->attributes[$elementtype]['label']
996
                    if ($tab->type != 'separate') {
997
                        $array_name_label[$tab->name] = $tab->label;
998
                    }
999
1000
1001
                    $this->attributes[$tab->elementtype]['type'][$tab->name] = $tab->type;
1002
                    $this->attributes[$tab->elementtype]['label'][$tab->name] = $tab->label;
1003
                    $this->attributes[$tab->elementtype]['size'][$tab->name] = $tab->size;
1004
                    $this->attributes[$tab->elementtype]['elementtype'][$tab->name] = $tab->elementtype;
1005
                    $this->attributes[$tab->elementtype]['default'][$tab->name] = $tab->fielddefault;
1006
                    $this->attributes[$tab->elementtype]['computed'][$tab->name] = $tab->fieldcomputed;
1007
                    $this->attributes[$tab->elementtype]['unique'][$tab->name] = $tab->fieldunique;
1008
                    $this->attributes[$tab->elementtype]['required'][$tab->name] = $tab->fieldrequired;
1009
                    $this->attributes[$tab->elementtype]['param'][$tab->name] = ($tab->param ? jsonOrUnserialize($tab->param) : '');
1010
                    $this->attributes[$tab->elementtype]['pos'][$tab->name] = $tab->pos;
1011
                    $this->attributes[$tab->elementtype]['alwayseditable'][$tab->name] = $tab->alwayseditable;
1012
                    $this->attributes[$tab->elementtype]['perms'][$tab->name] = ((is_null($tab->perms) || strlen($tab->perms) == 0) ? 1 : $tab->perms);
1013
                    $this->attributes[$tab->elementtype]['langfile'][$tab->name] = $tab->langs;
1014
                    $this->attributes[$tab->elementtype]['list'][$tab->name] = $tab->list;
1015
                    $this->attributes[$tab->elementtype]['printable'][$tab->name] = $tab->printable;
1016
                    $this->attributes[$tab->elementtype]['totalizable'][$tab->name] = ($tab->totalizable ? 1 : 0);
1017
                    $this->attributes[$tab->elementtype]['entityid'][$tab->name] = $tab->entity;
1018
                    $this->attributes[$tab->elementtype]['enabled'][$tab->name] = $tab->enabled;
1019
                    $this->attributes[$tab->elementtype]['help'][$tab->name] = $tab->help;
1020
                    $this->attributes[$tab->elementtype]['css'][$tab->name] = $tab->css;
1021
                    $this->attributes[$tab->elementtype]['cssview'][$tab->name] = $tab->cssview;
1022
                    $this->attributes[$tab->elementtype]['csslist'][$tab->name] = $tab->csslist;
1023
1024
                    $this->attributes[$tab->elementtype]['loaded'] = 1;
1025
                    $count++;
1026
                }
1027
            }
1028
            if ($elementtype) {
1029
                $this->attributes[$elementtype]['loaded'] = 1; // Note: If nothing is found, we also set the key 'loaded' to 1.
1030
                $this->attributes[$elementtype]['count'] = $count;
1031
            }
1032
        } else {
1033
            $this->error = $this->db->lasterror();
1034
            dol_syslog(get_class($this) . "::fetch_name_optionals_label " . $this->error, LOG_ERR);
1035
        }
1036
1037
        return $array_name_label;
1038
    }
1039
1040
1041
    /**
1042
     * Return HTML string to put an input field into a page
1043
     * Code very similar with showInputField of common object
1044
     *
1045
     * @param  string        $key                   Key of attribute
1046
     * @param  string|array  $value                 Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value); for dates in filter mode, a range array('start'=><timestamp>, 'end'=><timestamp>) should be provided
1047
     * @param  string        $moreparam             To add more parameters on html input tag
1048
     * @param  string        $keysuffix             Suffix string to add after name and id of field (can be used to avoid duplicate names)
1049
     * @param  string        $keyprefix             Prefix string to add before name and id of field (can be used to avoid duplicate names)
1050
     * @param  string        $morecss               More css (to defined size of field. Old behaviour: may also be a numeric)
1051
     * @param  int           $objectid              Current object id
1052
     * @param  string        $extrafieldsobjectkey  The key to use to store retrieved data (commonly $object->table_element)
1053
     * @param  int           $mode                  1=Used for search filters
1054
     * @return string
1055
     */
1056
    public function showInputField($key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = '', $objectid = 0, $extrafieldsobjectkey = '', $mode = 0)
1057
    {
1058
        global $conf, $langs, $form;
1059
1060
        if (!is_object($form)) {
1061
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/class/html.form.class.php';
1062
            $form = new Form($this->db);
1063
        }
1064
1065
        $out = '';
1066
1067
        if (!preg_match('/options_$/', $keyprefix)) {   // Because we work on extrafields, we add 'options_' to prefix if not already added
1068
            $keyprefix = $keyprefix . 'options_';
1069
        }
1070
1071
        if (empty($extrafieldsobjectkey)) {
1072
            dol_syslog(get_class($this) . '::showInputField extrafieldsobjectkey required', LOG_ERR);
1073
            return 'BadValueForParamExtraFieldsObjectKey';
1074
        }
1075
1076
        $label = $this->attributes[$extrafieldsobjectkey]['label'][$key];
1077
        $type = $this->attributes[$extrafieldsobjectkey]['type'][$key];
1078
        $size = $this->attributes[$extrafieldsobjectkey]['size'][$key];
1079
        $default = $this->attributes[$extrafieldsobjectkey]['default'][$key];
1080
        $computed = $this->attributes[$extrafieldsobjectkey]['computed'][$key];
1081
        $unique = $this->attributes[$extrafieldsobjectkey]['unique'][$key];
1082
        $required = $this->attributes[$extrafieldsobjectkey]['required'][$key];
1083
        $param = $this->attributes[$extrafieldsobjectkey]['param'][$key];
1084
        $perms = (int) dol_eval($this->attributes[$extrafieldsobjectkey]['perms'][$key], 1, 1, '2');
1085
        $langfile = $this->attributes[$extrafieldsobjectkey]['langfile'][$key];
1086
        $list = (string) dol_eval($this->attributes[$extrafieldsobjectkey]['list'][$key], 1, 1, '2');
1087
        $totalizable = $this->attributes[$extrafieldsobjectkey]['totalizable'][$key];
1088
        $help = $this->attributes[$extrafieldsobjectkey]['help'][$key];
1089
        $hidden = (empty($list) ? 1 : 0); // If empty, we are sure it is hidden, otherwise we show. If it depends on mode (view/create/edit form or list, this must be filtered by caller)
1090
1091
        //var_dump('key='.$key.' '.$value.' '.$moreparam.' '.$keysuffix.' '.$keyprefix.' '.$objectid.' '.$extrafieldsobjectkey.' '.$mode);
1092
        //var_dump('label='.$label.' type='.$type.' param='.var_export($param, 1));
1093
1094
        if ($computed) {
1095
            if (!preg_match('/^search_/', $keyprefix)) {
1096
                return '<span class="opacitymedium">' . $langs->trans("AutomaticallyCalculated") . '</span>';
1097
            } else {
1098
                return '';
1099
            }
1100
        }
1101
1102
        //
1103
        // 'css' is used in creation and update. 'cssview' is used in view mode. 'csslist' is used for columns in lists. For example: 'css'=>'minwidth300 maxwidth500 widthcentpercentminusx', 'cssview'=>'wordbreak', 'csslist'=>'tdoverflowmax200'
1104
        if (empty($morecss)) {
1105
            // Add automatic css
1106
            if ($type == 'date') {
1107
                $morecss = 'minwidth100imp';
1108
            } elseif ($type == 'datetime' || $type == 'datetimegmt' || $type == 'link') {
1109
                $morecss = 'minwidth200imp';
1110
            } elseif (in_array($type, array('int', 'integer', 'double', 'price'))) {
1111
                $morecss = 'maxwidth75';
1112
            } elseif ($type == 'password') {
1113
                $morecss = 'maxwidth100';
1114
            } elseif ($type == 'url') {
1115
                $morecss = 'minwidth400';
1116
            } elseif ($type == 'boolean') {
1117
                $morecss = '';
1118
            } elseif ($type == 'radio') {
1119
                $morecss = 'width25';
1120
            } else {
1121
                if (empty($size) || round((float) $size) < 12) {
1122
                    $morecss = 'minwidth100';
1123
                } elseif (round((float) $size) <= 48) {
1124
                    $morecss = 'minwidth200';
1125
                } else {
1126
                    $morecss = 'minwidth400';
1127
                }
1128
            }
1129
            // If css forced in attribute, we use this one
1130
            if (!empty($this->attributes[$extrafieldsobjectkey]['css'][$key])) {
1131
                $morecss = $this->attributes[$extrafieldsobjectkey]['css'][$key];
1132
            }
1133
        }
1134
1135
        if (in_array($type, array('date'))) {
1136
            $tmp = explode(',', $size);
1137
            $newsize = $tmp[0];
1138
            $showtime = 0;
1139
1140
            // Do not show current date when field not required (see selectDate() method)
1141
            if (!$required && $value == '') {
1142
                $value = '-1';
1143
            }
1144
1145
            if ($mode == 1) {
1146
                // search filter on a date extrafield shows two inputs to select a date range
1147
                $prefill = array(
1148
                    'start' => isset($value['start']) ? $value['start'] : '',
1149
                    'end'   => isset($value['end']) ? $value['end'] : ''
1150
                );
1151
                $out = '<div ' . ($moreparam ? $moreparam : '') . '><div class="nowrap">';
1152
                $out .= $form->selectDate($prefill['start'], $keyprefix . $key . $keysuffix . '_start', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("From"));
1153
                $out .= '</div><div class="nowrap">';
1154
                $out .= $form->selectDate($prefill['end'], $keyprefix . $key . $keysuffix . '_end', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("to"));
1155
                $out .= '</div></div>';
1156
            } else {
1157
                // TODO Must also support $moreparam
1158
                $out = $form->selectDate($value, $keyprefix . $key . $keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1);
1159
            }
1160
        } elseif (in_array($type, array('datetime', 'datetimegmt'))) {
1161
            $tmp = explode(',', $size);
1162
            $newsize = $tmp[0];
1163
            $showtime = 1;
1164
1165
            // Do not show current date when field not required (see selectDate() method)
1166
            if (!$required && $value == '') {
1167
                $value = '-1';
1168
            }
1169
1170
            if ($mode == 1) {
1171
                // search filter on a date extrafield shows two inputs to select a date range
1172
                $prefill = array(
1173
                    'start' => isset($value['start']) ? $value['start'] : '',
1174
                    'end'   => isset($value['end']) ? $value['end'] : ''
1175
                );
1176
                $out = '<div ' . ($moreparam ? $moreparam : '') . '><div class="nowrap">';
1177
                $out .= $form->selectDate($prefill['start'], $keyprefix . $key . $keysuffix . '_start', 1, 1, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("From"), 'tzuserrel');
1178
                $out .= '</div><div class="nowrap">';
1179
                $out .= $form->selectDate($prefill['end'], $keyprefix . $key . $keysuffix . '_end', 1, 1, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("to"), 'tzuserrel');
1180
                $out .= '</div></div>';
1181
            } else {
1182
                // TODO Must also support $moreparam
1183
                $out = $form->selectDate($value, $keyprefix . $key . $keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1, '', '', '', 1, '', '', 'tzuserrel');
1184
            }
1185
        } elseif (in_array($type, array('int', 'integer'))) {
1186
            $tmp = explode(',', $size);
1187
            $newsize = $tmp[0];
1188
            $out = '<input type="text" class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" maxlength="' . $newsize . '" value="' . dol_escape_htmltag($value) . '"' . ($moreparam ? $moreparam : '') . '>';
1189
        } elseif (preg_match('/varchar/', $type)) {
1190
            $out = '<input type="text" class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" maxlength="' . $size . '" value="' . dol_escape_htmltag($value) . '"' . ($moreparam ? $moreparam : '') . '>';
1191
        } elseif (in_array($type, array('mail', 'ip', 'phone', 'url'))) {
1192
            $out = '<input type="text" class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" value="' . dol_escape_htmltag($value) . '" ' . ($moreparam ? $moreparam : '') . '>';
1193
        } elseif ($type == 'icon') {
1194
            /* External lib inclusion are not allowed in backoffice. Also lib is included several time if there is several icon file.
1195
            Some code must be added into main when MAIN_ADD_ICONPICKER_JS is set to add of lib in html header
1196
            $out ='<link rel="stylesheet" href="'.dol_buildpath('/myfield/css/fontawesome-iconpicker.min.css', 1).'">';
1197
            $out.='<script src="'.dol_buildpath('/myfield/js/fontawesome-iconpicker.min.js', 1).'"></script>';
1198
            */
1199
            $out .= '<input type="text" class="form-control icp icp-auto iconpicker-element iconpicker-input flat ' . $morecss . ' maxwidthonsmartphone"';
1200
            $out .= ' name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" value="' . dol_escape_htmltag($value) . '" ' . ($moreparam ? $moreparam : '') . '>';
1201
            if (getDolGlobalInt('MAIN_ADD_ICONPICKER_JS')) {
1202
                $out .= '<script>';
1203
                $options = "{ title: '<b>" . $langs->trans("IconFieldSelector") . "</b>', placement: 'right', showFooter: false, templates: {";
1204
                $options .= "iconpicker: '<div class=\"iconpicker\"><div style=\"background-color:#EFEFEF;\" class=\"iconpicker-items\"></div></div>',";
1205
                $options .= "iconpickerItem: '<a role=\"button\" href=\"#\" class=\"iconpicker-item\" style=\"background-color:#DDDDDD;\"><i></i></a>',";
1206
                // $options.="buttons: '<button style=\"background-color:#FFFFFF;\" class=\"iconpicker-btn iconpicker-btn-cancel btn btn-default btn-sm\">".$langs->trans("Cancel")."</button>";
1207
                // $options.="<button style=\"background-color:#FFFFFF;\" class=\"iconpicker-btn iconpicker-btn-accept btn btn-primary btn-sm\">".$langs->trans("Save")."</button>',";
1208
                $options .= "footer: '<div class=\"popover-footer\" style=\"background-color:#EFEFEF;\"></div>',";
1209
                $options .= "search: '<input type=\"search\" class\"form-control iconpicker-search\" placeholder=\"" . $langs->trans("TypeToFilter") . "\" />',";
1210
                $options .= "popover: '<div class=\"iconpicker-popover popover\">";
1211
                $options .= "   <div class=\"arrow\" ></div>";
1212
                $options .= "   <div class=\"popover-title\" style=\"text-align:center;background-color:#EFEFEF;\"></div>";
1213
                $options .= "   <div class=\"popover-content \" ></div>";
1214
                $options .= "</div>'}}";
1215
                $out .= "$('#" . $keyprefix . $key . $keysuffix . "').iconpicker(" . $options . ");";
1216
                $out .= '</script>';
1217
            }
1218
        } elseif ($type == 'text') {
1219
            if (!preg_match('/search_/', $keyprefix)) {     // If keyprefix is search_ or search_options_, we must just use a simple text field
1220
                require_once constant('DOL_DOCUMENT_ROOT') . '/core/class/doleditor.class.php';
1221
                $doleditor = new DolEditor($keyprefix . $key . $keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, false, ROWS_5, '90%');
1222
                $out = (string) $doleditor->Create(1);
1223
            } else {
1224
                $out = '<input type="text" class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" value="' . dol_escape_htmltag($value) . '" ' . ($moreparam ? $moreparam : '') . '>';
1225
            }
1226
        } elseif ($type == 'html') {
1227
            if (!preg_match('/search_/', $keyprefix)) {     // If keyprefix is search_ or search_options_, we must just use a simple text field
1228
                require_once constant('DOL_DOCUMENT_ROOT') . '/core/class/doleditor.class.php';
1229
                $doleditor = new DolEditor($keyprefix . $key . $keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, isModEnabled('fckeditor') && getDolGlobalInt('FCKEDITOR_ENABLE_SOCIETE'), ROWS_5, '90%');
1230
                $out = (string) $doleditor->Create(1);
1231
            } else {
1232
                $out = '<input type="text" class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" value="' . dol_escape_htmltag($value) . '" ' . ($moreparam ? $moreparam : '') . '>';
1233
            }
1234
        } elseif ($type == 'boolean') {
1235
            if (empty($mode)) {
1236
                $checked = '';
1237
                if (!empty($value)) {
1238
                    $checked = ' checked value="1" ';
1239
                } else {
1240
                    $checked = ' value="1" ';
1241
                }
1242
                $out = '<input type="checkbox" class="flat valignmiddle' . ($morecss ? ' ' . $morecss : '') . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" ' . $checked . ' ' . ($moreparam ? $moreparam : '') . '>';
1243
            } else {
1244
                $out = $form->selectyesno($keyprefix . $key . $keysuffix, $value, 1, false, 1);
1245
            }
1246
            $out .= '<input type="hidden" name="' . $keyprefix . $key . $keysuffix . '_boolean" value="1">';    // A hidden field ending with "_boolean" that is always set to 1.
1247
        } elseif ($type == 'price') {
1248
            if (!empty($value)) {       // $value in memory is a php numeric, we format it into user number format.
1249
                $value = price($value);
1250
            }
1251
            $out = '<input type="text" class="flat ' . $morecss . ' maxwidthonsmartphone right" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" value="' . $value . '" ' . ($moreparam ? $moreparam : '') . ' placeholder="' . $langs->getCurrencySymbol($conf->currency) . '">';
1252
        } elseif ($type == 'pricecy') {
1253
            $currency = $conf->currency;
1254
            if (!empty($value)) {
1255
                // $value in memory is a php string like '10.01:USD'
1256
                $pricetmp = explode(':', $value);
1257
                $currency = !empty($pricetmp[1]) ? $pricetmp[1] : $conf->currency;
1258
                $value = price($pricetmp[0]);
1259
            }
1260
            $out = '<input type="text" class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" value="' . $value . '" ' . ($moreparam ? $moreparam : '') . '> ';
1261
            $out .= $form->selectCurrency($currency, $keyprefix . $key . $keysuffix . 'currency_id');
1262
        } elseif ($type == 'double') {
1263
            if (!empty($value)) {       // $value in memory is a php numeric, we format it into user number format.
1264
                $value = price($value);
1265
            }
1266
            $out = '<input type="text" class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" value="' . $value . '" ' . ($moreparam ? $moreparam : '') . '> ';
1267
        } elseif ($type == 'select') {
1268
            $out = '';
1269
            if ($mode) {
1270
                $options = array();
1271
                foreach ($param['options'] as $okey => $val) {
1272
                    if ((string) $okey == '') {
1273
                        continue;
1274
                    }
1275
1276
                    $valarray = explode('|', $val);
1277
                    $val = $valarray[0];
1278
1279
                    if ($langfile && $val) {
1280
                        $options[$okey] = $langs->trans($val);
1281
                    } else {
1282
                        $options[$okey] = $val;
1283
                    }
1284
                }
1285
                $selected = array();
1286
                if (!is_array($value)) {
1287
                    $selected = explode(',', $value);
1288
                }
1289
1290
                $out .= $form->multiselectarray($keyprefix . $key . $keysuffix, $options, $selected, 0, 0, $morecss, 0, 0, '', '', '', !empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2'));
1291
            } else {
1292
                if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
1293
                    include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
1294
                    $out .= ajax_combobox($keyprefix . $key . $keysuffix, array(), 0);
1295
                }
1296
1297
                $out .= '<select class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" ' . ($moreparam ? $moreparam : '') . '>';
1298
                $out .= '<option value="0">&nbsp;</option>';
1299
                foreach ($param['options'] as $key2 => $val2) {
1300
                    if ((string) $key2 == '') {
1301
                        continue;
1302
                    }
1303
                    $valarray = explode('|', $val2);
1304
                    $val2 = $valarray[0];
1305
                    $parent = '';
1306
                    if (!empty($valarray[1])) {
1307
                        $parent = $valarray[1];
1308
                    }
1309
                    $out .= '<option value="' . $key2 . '"';
1310
                    $out .= (((string) $value == (string) $key2) ? ' selected' : '');
1311
                    $out .= (!empty($parent) ? ' parent="' . $parent . '"' : '');
1312
                    $out .= '>';
1313
                    if ($langfile && $val2) {
1314
                        $out .= $langs->trans($val2);
1315
                    } else {
1316
                        $out .= $val2;
1317
                    }
1318
                    $out .= '</option>';
1319
                }
1320
                $out .= '</select>';
1321
            }
1322
        } elseif ($type == 'sellist') {
1323
            $out = '';
1324
            if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
1325
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
1326
                $out .= ajax_combobox($keyprefix . $key . $keysuffix, array(), 0);
1327
            }
1328
1329
            $out .= '<select class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" ' . ($moreparam ? $moreparam : '') . '>';
1330
            if (is_array($param['options'])) {
1331
                $param_list = array_keys($param['options']);
1332
                $InfoFieldList = explode(":", $param_list[0]);
1333
                $parentName = '';
1334
                $parentField = '';
1335
                // 0 : tableName
1336
                // 1 : label field name
1337
                // 2 : key fields name (if differ of rowid)
1338
                // 3 : key field parent (for dependent lists)
1339
                // 4 : where clause filter on column or table extrafield, syntax field='value' or extra.field=value
1340
                // 5 : id category type
1341
                // 6 : ids categories list separated by comma for category root
1342
                // 7 : sort by (to be close to common object)
1343
                $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2] . ' as rowid');
1344
1345
1346
                if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
1347
                    if (strpos($InfoFieldList[4], 'extra.') !== false) {
1348
                        $keyList = 'main.' . $InfoFieldList[2] . ' as rowid';
1349
                    } else {
1350
                        $keyList = $InfoFieldList[2] . ' as rowid';
1351
                    }
1352
                }
1353
                if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
1354
                    list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
1355
                    $keyList .= ', ' . $parentField;
1356
                }
1357
1358
                $filter_categorie = false;
1359
                if (count($InfoFieldList) > 5) {
1360
                    if ($InfoFieldList[0] == 'categorie') {
1361
                        $filter_categorie = true;
1362
                    }
1363
                }
1364
1365
                if ($filter_categorie === false) {
1366
                    $fields_label = explode('|', $InfoFieldList[1]);
1367
                    if (is_array($fields_label)) {
1368
                        $keyList .= ', ';
1369
                        $keyList .= implode(', ', $fields_label);
1370
                    }
1371
1372
                    $sqlwhere = '';
1373
                    $sql = "SELECT " . $keyList;
1374
                    $sql .= ' FROM ' . $this->db->prefix() . $InfoFieldList[0];
1375
                    if (!empty($InfoFieldList[4])) {
1376
                        // can use current entity filter
1377
                        if (strpos($InfoFieldList[4], '$ENTITY$') !== false) {
1378
                            $InfoFieldList[4] = str_replace('$ENTITY$', (string) $conf->entity, $InfoFieldList[4]);
1379
                        }
1380
                        // can use SELECT request
1381
                        if (strpos($InfoFieldList[4], '$SEL$') !== false) {
1382
                            $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
1383
                        }
1384
1385
                        // current object id can be use into filter
1386
                        if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
1387
                            $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]);
1388
                        } else {
1389
                            $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
1390
                        }
1391
                        //We have to join on extrafield table
1392
                        if (strpos($InfoFieldList[4], 'extra.') !== false) {
1393
                            $sql .= ' as main, ' . $this->db->prefix() . $InfoFieldList[0] . '_extrafields as extra';
1394
                            $sqlwhere .= " WHERE extra.fk_object=main." . $InfoFieldList[2] . " AND " . $InfoFieldList[4];
1395
                        } else {
1396
                            $sqlwhere .= " WHERE " . $InfoFieldList[4];
1397
                        }
1398
                    } else {
1399
                        $sqlwhere .= ' WHERE 1=1';
1400
                    }
1401
                    // Some tables may have field, some other not. For the moment we disable it.
1402
                    if (in_array($InfoFieldList[0], array('tablewithentity'))) {
1403
                        $sqlwhere .= ' AND entity = ' . ((int) $conf->entity);
1404
                    }
1405
                    $sql .= $sqlwhere;
1406
                    //print $sql;
1407
1408
                    $sql .= ' ORDER BY ' . implode(', ', $fields_label);
1409
1410
                    dol_syslog(get_class($this) . '::showInputField type=sellist', LOG_DEBUG);
1411
                    $resql = $this->db->query($sql);
1412
                    if ($resql) {
1413
                        $out .= '<option value="0">&nbsp;</option>';
1414
                        $num = $this->db->num_rows($resql);
1415
                        $i = 0;
1416
                        while ($i < $num) {
1417
                            $labeltoshow = '';
1418
                            $obj = $this->db->fetch_object($resql);
1419
1420
                            // Several field into label (eq table:code|label:rowid)
1421
                            $notrans = false;
1422
                            $fields_label = explode('|', $InfoFieldList[1]);
1423
                            if (is_array($fields_label) && count($fields_label) > 1) {
1424
                                $notrans = true;
1425
                                foreach ($fields_label as $field_toshow) {
1426
                                    $labeltoshow .= $obj->$field_toshow . ' ';
1427
                                }
1428
                            } else {
1429
                                $labeltoshow = $obj->{$InfoFieldList[1]};
1430
                            }
1431
1432
                            if ($value == $obj->rowid) {
1433
                                if (!$notrans) {
1434
                                    foreach ($fields_label as $field_toshow) {
1435
                                        $translabel = $langs->trans($obj->$field_toshow);
1436
                                        $labeltoshow = $translabel . ' ';
1437
                                    }
1438
                                }
1439
                                $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
1440
                            } else {
1441
                                if (!$notrans) {
1442
                                    $translabel = $langs->trans($obj->{$InfoFieldList[1]});
1443
                                    $labeltoshow = $translabel;
1444
                                }
1445
                                if (empty($labeltoshow)) {
1446
                                    $labeltoshow = '(not defined)';
1447
                                }
1448
1449
                                if (!empty($InfoFieldList[3]) && $parentField) {
1450
                                    $parent = $parentName . ':' . $obj->{$parentField};
1451
                                }
1452
1453
                                $out .= '<option value="' . $obj->rowid . '"';
1454
                                $out .= ($value == $obj->rowid ? ' selected' : '');
1455
                                $out .= (!empty($parent) ? ' parent="' . $parent . '"' : '');
1456
                                $out .= '>' . $labeltoshow . '</option>';
1457
                            }
1458
1459
                            $i++;
1460
                        }
1461
                        $this->db->free($resql);
1462
                    } else {
1463
                        print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
1464
                    }
1465
                } else {
1466
                    require_once constant('DOL_DOCUMENT_ROOT') . '/categories/class/categorie.class.php';
1467
                    $data = $form->select_all_categories(Categorie::$MAP_ID_TO_CODE[$InfoFieldList[5]], '', 'parent', 64, $InfoFieldList[6], 1, 1);
1468
                    $out .= '<option value="0">&nbsp;</option>';
1469
                    if (is_array($data)) {
1470
                        foreach ($data as $data_key => $data_value) {
1471
                            $out .= '<option value="' . $data_key . '"';
1472
                            $out .= ($value == $data_key ? ' selected' : '');
1473
                            $out .= '>' . $data_value . '</option>';
1474
                        }
1475
                    }
1476
                }
1477
            }
1478
            $out .= '</select>';
1479
        } elseif ($type == 'checkbox') {
1480
            $value_arr = $value;
1481
            if (!is_array($value)) {
1482
                $value_arr = explode(',', $value);
1483
            }
1484
            $out = $form->multiselectarray($keyprefix . $key . $keysuffix, (empty($param['options']) ? null : $param['options']), $value_arr, '', 0, '', 0, '100%');
1485
        } elseif ($type == 'radio') {
1486
            $out = '';
1487
            foreach ($param['options'] as $keyopt => $val) {
1488
                $out .= '<input class="flat ' . $morecss . '" type="radio" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" ' . ($moreparam ? $moreparam : '');
1489
                $out .= ' value="' . $keyopt . '"';
1490
                $out .= ' id="' . $keyprefix . $key . $keysuffix . '_' . $keyopt . '"';
1491
                $out .= ($value == $keyopt ? 'checked' : '');
1492
                $out .= '/><label for="' . $keyprefix . $key . $keysuffix . '_' . $keyopt . '">' . $langs->trans($val) . '</label><br>';
1493
            }
1494
        } elseif ($type == 'chkbxlst') {
1495
            if (is_array($value)) {
1496
                $value_arr = $value;
1497
            } else {
1498
                $value_arr = explode(',', $value);
1499
            }
1500
1501
            if (is_array($param['options'])) {
1502
                $param_list = array_keys($param['options']);
1503
                $InfoFieldList = explode(":", $param_list[0]);
1504
                $parentName = '';
1505
                $parentField = '';
1506
                // 0 : tableName
1507
                // 1 : label field name
1508
                // 2 : key fields name (if differ of rowid)
1509
                // 3 : key field parent (for dependent lists)
1510
                // 4 : where clause filter on column or table extrafield, syntax field='value' or extra.field=value
1511
                // 5 : id category type
1512
                // 6 : ids categories list separated by comma for category root
1513
                // 7 : sort by (to be close to common object)
1514
                $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2] . ' as rowid');
1515
1516
                if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
1517
                    list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
1518
                    $keyList .= ', ' . $parentField;
1519
                }
1520
                if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
1521
                    if (strpos($InfoFieldList[4], 'extra.') !== false) {
1522
                        $keyList = 'main.' . $InfoFieldList[2] . ' as rowid';
1523
                    } else {
1524
                        $keyList = $InfoFieldList[2] . ' as rowid';
1525
                    }
1526
                }
1527
1528
                $filter_categorie = false;
1529
                if (count($InfoFieldList) > 5) {
1530
                    if ($InfoFieldList[0] == 'categorie') {
1531
                        $filter_categorie = true;
1532
                    }
1533
                }
1534
1535
                if ($filter_categorie === false) {
1536
                    $fields_label = explode('|', $InfoFieldList[1]);
1537
                    if (is_array($fields_label)) {
1538
                        $keyList .= ', ';
1539
                        $keyList .= implode(', ', $fields_label);
1540
                    }
1541
1542
                    $sqlwhere = '';
1543
                    $sql = "SELECT " . $keyList;
1544
                    $sql .= ' FROM ' . $this->db->prefix() . $InfoFieldList[0];
1545
                    if (!empty($InfoFieldList[4])) {
1546
                        // can use current entity filter
1547
                        if (strpos($InfoFieldList[4], '$ENTITY$') !== false) {
1548
                            $InfoFieldList[4] = str_replace('$ENTITY$', (string) $conf->entity, $InfoFieldList[4]);
1549
                        }
1550
                        // can use SELECT request
1551
                        if (strpos($InfoFieldList[4], '$SEL$') !== false) {
1552
                            $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
1553
                        }
1554
1555
                        // current object id can be use into filter
1556
                        if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
1557
                            $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]);
1558
                        } elseif (preg_match("#^.*list.php$#", $_SERVER["PHP_SELF"])) {
1559
                            // Pattern for word=$ID$
1560
                            $word = '\b[a-zA-Z0-9-\.-_]+\b=\$ID\$';
1561
1562
                            // Removing spaces around =, ( and )
1563
                            $InfoFieldList[4] = preg_replace('# *(=|\(|\)) *#', '$1', $InfoFieldList[4]);
1564
1565
                            $nbPreg = 1;
1566
                            // While we have parenthesis
1567
                            while ($nbPreg != 0) {
1568
                                // Initialise counters
1569
                                $nbPregRepl = $nbPregSel = 0;
1570
                                // Remove all parenthesis not preceded with '=' sign
1571
                                $InfoFieldList[4] = preg_replace('#([^=])(\([^)^(]*(' . $word . ')[^)^(]*\))#', '$1 $3 ', $InfoFieldList[4], -1, $nbPregRepl);
1572
                                // Remove all escape characters around '=' and parenthesis
1573
                                $InfoFieldList[4] = preg_replace('# *(=|\(|\)) *#', '$1', $InfoFieldList[4]);
1574
                                // Remove all parentheses preceded with '='
1575
                                $InfoFieldList[4] = preg_replace('#\b[a-zA-Z0-9-\.-_]+\b=\([^)^(]*(' . $word . ')[^)^(]*\)#', '$1 ', $InfoFieldList[4], -1, $nbPregSel);
1576
                                // On retire les escapes autour des = et parenthèses
1577
                                $InfoFieldList[4] = preg_replace('# *(=|\(|\)) *#', '$1', $InfoFieldList[4]);
1578
1579
                                // UPdate the totals counter for the loop
1580
                                $nbPreg = $nbPregRepl + $nbPregSel;
1581
                            }
1582
1583
                            // In case there is AND ou OR, before or after
1584
                            $matchCondition = array();
1585
                            preg_match('#(AND|OR|) *(' . $word . ') *(AND|OR|)#', $InfoFieldList[4], $matchCondition);
1586
                            while (!empty($matchCondition[0])) {
1587
                                // If the two sides differ but are not empty
1588
                                if (!empty($matchCondition[1]) && !empty($matchCondition[3]) && $matchCondition[1] != $matchCondition[3]) {
1589
                                    // Nobody sain would do that without parentheses
1590
                                    $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
1591
                                } else {
1592
                                    if (!empty($matchCondition[1])) {
1593
                                        $boolCond = (($matchCondition[1] == "AND") ? ' AND TRUE ' : ' OR FALSE ');
1594
                                        $InfoFieldList[4] = str_replace($matchCondition[0], $boolCond . $matchCondition[3], $InfoFieldList[4]);
1595
                                    } elseif (!empty($matchCondition[3])) {
1596
                                        $boolCond = (($matchCondition[3] == "AND") ? ' TRUE AND ' : ' FALSE OR');
1597
                                        $InfoFieldList[4] = str_replace($matchCondition[0], $boolCond, $InfoFieldList[4]);
1598
                                    } else {
1599
                                        $InfoFieldList[4] = " TRUE ";
1600
                                    }
1601
                                }
1602
1603
                                // In case there is AND ou OR, before or after
1604
                                preg_match('#(AND|OR|) *(' . $word . ') *(AND|OR|)#', $InfoFieldList[4], $matchCondition);
1605
                            }
1606
                        } else {
1607
                            $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
1608
                        }
1609
1610
                        // We have to join on extrafield table
1611
                        if (strpos($InfoFieldList[4], 'extra.') !== false) {
1612
                            $sql .= ' as main, ' . $this->db->prefix() . $InfoFieldList[0] . '_extrafields as extra';
1613
                            $sqlwhere .= " WHERE extra.fk_object=main." . $InfoFieldList[2] . " AND " . $InfoFieldList[4];
1614
                        } else {
1615
                            $sqlwhere .= " WHERE " . $InfoFieldList[4];
1616
                        }
1617
                    } else {
1618
                        $sqlwhere .= ' WHERE 1=1';
1619
                    }
1620
                    // Some tables may have field, some other not. For the moment we disable it.
1621
                    if (in_array($InfoFieldList[0], array('tablewithentity'))) {
1622
                        $sqlwhere .= " AND entity = " . ((int) $conf->entity);
1623
                    }
1624
                    // $sql.=preg_replace('/^ AND /','',$sqlwhere);
1625
                    // print $sql;
1626
1627
                    $sql .= $sqlwhere;
1628
                    dol_syslog(get_class($this) . '::showInputField type=chkbxlst', LOG_DEBUG);
1629
                    $resql = $this->db->query($sql);
1630
                    if ($resql) {
1631
                        $num = $this->db->num_rows($resql);
1632
                        $i = 0;
1633
1634
                        $data = array();
1635
1636
                        while ($i < $num) {
1637
                            $labeltoshow = '';
1638
                            $obj = $this->db->fetch_object($resql);
1639
1640
                            $notrans = false;
1641
                            // Several field into label (eq table:code|label:rowid)
1642
                            $fields_label = explode('|', $InfoFieldList[1]);
1643
                            if (is_array($fields_label)) {
1644
                                $notrans = true;
1645
                                foreach ($fields_label as $field_toshow) {
1646
                                    $labeltoshow .= $obj->$field_toshow . ' ';
1647
                                }
1648
                            } else {
1649
                                $labeltoshow = $obj->{$InfoFieldList[1]};
1650
                            }
1651
                            $labeltoshow = dol_trunc($labeltoshow, 45);
1652
1653
                            if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
1654
                                $labeltoshow = '';
1655
                                foreach ($fields_label as $field_toshow) {
1656
                                    $translabel = $langs->trans($obj->$field_toshow);
1657
                                    if ($translabel != $obj->$field_toshow) {
1658
                                        $labeltoshow .= ' ' . dol_trunc($translabel, 18) . ' ';
1659
                                    } else {
1660
                                        $labeltoshow .= ' ' . dol_trunc($obj->$field_toshow, 18) . ' ';
1661
                                    }
1662
                                }
1663
                                $data[$obj->rowid] = $labeltoshow;
1664
                            } else {
1665
                                if (!$notrans) {
1666
                                    $translabel = $langs->trans($obj->{$InfoFieldList[1]});
1667
                                    if ($translabel != $obj->{$InfoFieldList[1]}) {
1668
                                        $labeltoshow = dol_trunc($translabel, 18);
1669
                                    } else {
1670
                                        $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]}, 18);
1671
                                    }
1672
                                }
1673
                                if (empty($labeltoshow)) {
1674
                                    $labeltoshow = '(not defined)';
1675
                                }
1676
1677
                                if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
1678
                                    $data[$obj->rowid] = $labeltoshow;
1679
                                }
1680
1681
                                if (!empty($InfoFieldList[3]) && $parentField) {
1682
                                    $parent = $parentName . ':' . $obj->{$parentField};
1683
                                }
1684
1685
                                $data[$obj->rowid] = $labeltoshow;
1686
                            }
1687
1688
                            $i++;
1689
                        }
1690
                        $this->db->free($resql);
1691
1692
                        $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, '', 0, '', 0, '100%');
1693
                    } else {
1694
                        print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
1695
                    }
1696
                } else {
1697
                    require_once constant('DOL_DOCUMENT_ROOT') . '/categories/class/categorie.class.php';
1698
                    $data = $form->select_all_categories(Categorie::$MAP_ID_TO_CODE[$InfoFieldList[5]], '', 'parent', 64, $InfoFieldList[6], 1, 1);
1699
                    $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, '', 0, '', 0, '100%');
1700
                }
1701
            }
1702
        } elseif ($type == 'link') {
1703
            $param_list = array_keys($param['options']); // $param_list[0] = 'ObjectName:classPath' but can also be 'ObjectName:classPath:1:(status:=:1)'
1704
            /* Removed.
1705
            The selectForForms is called with parameter $objectfield defined, so the app can retrieve the filter inside the ajax component instead of being provided as parameters. The
1706
            filter was used to pass SQL requests leading to serious SQL injection problem. This should not be possible. Also the call of the ajax was broken by some WAF.
1707
            if (strpos($param_list[0], '$ID$') !== false && !empty($objectid)) {
1708
                $param_list[0] = str_replace('$ID$', $objectid, $param_list[0]);
1709
            }*/
1710
            $showempty = (($required && $default != '') ? 0 : 1);
1711
1712
            $tmparray = explode(':', $param_list[0]);
1713
1714
            $element = $extrafieldsobjectkey;       // $extrafieldsobjectkey comes from $object->table_element but we need $object->element
1715
            if ($element == 'socpeople') {
1716
                $element = 'contact';
1717
            } elseif ($element == 'projet') {
1718
                $element = 'project';
1719
            }
1720
1721
            //$objectdesc = $param_list[0];             // Example: 'ObjectName:classPath:1:(status:=:1)'   Replaced by next line: this was propagated also a filter by ajax call that was blocked by some WAF
1722
            $objectdesc = $tmparray[0];                 // Example: 'ObjectName:classPath'                  To not propagate any filter (selectForForms do ajax call and propagating SQL filter is blocked by some WAF). Also we should use the one into the definition in the ->fields of $elem if found.
1723
            $objectfield = $element . ':options_' . $key;   // Example: 'actioncomm:options_fff'                To be used in priority to know object linked with all its definition (including filters)
1724
1725
            $out = $form->selectForForms($objectdesc, $keyprefix . $key . $keysuffix, $value, $showempty, '', '', $morecss, '', 0, 0, '', $objectfield);
1726
        } elseif (in_array($type, ['point', 'multipts', 'linestrg', 'polygon'])) {
1727
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/class/dolgeophp.class.php';
1728
            $dolgeophp = new DolGeoPHP($this->db);
1729
            $geojson = '{}';
1730
            $centroidjson = getDolGlobalString('MAIN_INFO_SOCIETE_GEO_COORDINATES', '{}');
1731
            if (!empty($value)) {
1732
                $tmparray = $dolgeophp->parseGeoString($value);
1733
                $geojson = $tmparray['geojson'];
1734
                $centroidjson = $tmparray['centroidjson'];
1735
            }
1736
            if (!preg_match('/search_/', $keyprefix)) {
1737
                require_once constant('DOL_DOCUMENT_ROOT') . '/core/class/geomapeditor.class.php';
1738
                $geomapeditor = new GeoMapEditor();
1739
                $out .= $geomapeditor->getHtml($keyprefix . $key . $keysuffix, $geojson, $centroidjson, $type);
1740
            } else {
1741
                // If keyprefix is search_ or search_options_, we must just use a simple text field
1742
                $out = '';
1743
            }
1744
        } elseif ($type == 'password') {
1745
            // If prefix is 'search_', field is used as a filter, we use a common text field.
1746
            $out = '<input style="display:none" type="text" name="fakeusernameremembered">'; // Hidden field to reduce impact of evil Google Chrome autopopulate bug.
1747
            $out .= '<input autocomplete="new-password" type="' . ($keyprefix == 'search_' ? 'text' : 'password') . '" class="flat ' . $morecss . '" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" value="' . $value . '" ' . ($moreparam ? $moreparam : '') . '>';
1748
        }
1749
        if (!empty($hidden)) {
1750
            $out = '<input type="hidden" value="' . $value . '" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '"/>';
1751
        }
1752
        /* Add comments
1753
         if ($type == 'date') $out.=' (YYYY-MM-DD)';
1754
         elseif ($type == 'datetime') $out.=' (YYYY-MM-DD HH:MM:SS)';
1755
         */
1756
        /*if (!empty($help) && $keyprefix != 'search_options_') {
1757
            $out .= $form->textwithpicto('', $help, 1, 'help', '', 0, 3);
1758
        }*/
1759
        return $out;
1760
    }
1761
1762
1763
    /**
1764
     * Return HTML string to put an output field into a page
1765
     *
1766
     * @param   string  $key                    Key of attribute
1767
     * @param   string  $value                  Value to show
1768
     * @param   string  $moreparam              To add more parameters on html input tag (only checkbox use html input for output rendering)
1769
     * @param   string  $extrafieldsobjectkey   Required (for example $object->table_element).
1770
     * @param   Translate $outputlangs          Output language
1771
     * @return  string                          Formatted value
1772
     */
1773
    public function showOutputField($key, $value, $moreparam = '', $extrafieldsobjectkey = '', $outputlangs = null)
1774
    {
1775
        global $conf, $langs;
1776
1777
        if (is_null($outputlangs) || !is_object($outputlangs)) {
1778
            $outputlangs = $langs;
1779
        }
1780
1781
        if (empty($extrafieldsobjectkey)) {
1782
            dol_syslog(get_class($this) . '::showOutputField extrafieldsobjectkey required', LOG_ERR);
1783
            return 'BadValueForParamExtraFieldsObjectKey';
1784
        }
1785
1786
        $label = $this->attributes[$extrafieldsobjectkey]['label'][$key];
1787
        $type = $this->attributes[$extrafieldsobjectkey]['type'][$key];
1788
        $size = $this->attributes[$extrafieldsobjectkey]['size'][$key];         // Can be '255', '24,8'...
1789
        $default = $this->attributes[$extrafieldsobjectkey]['default'][$key];
1790
        $computed = $this->attributes[$extrafieldsobjectkey]['computed'][$key];
1791
        $unique = $this->attributes[$extrafieldsobjectkey]['unique'][$key];
1792
        $required = $this->attributes[$extrafieldsobjectkey]['required'][$key];
1793
        $param = $this->attributes[$extrafieldsobjectkey]['param'][$key];
1794
        $perms = (int) dol_eval($this->attributes[$extrafieldsobjectkey]['perms'][$key], 1, 1, '2');
1795
        $langfile = $this->attributes[$extrafieldsobjectkey]['langfile'][$key];
1796
        $list = (string) dol_eval($this->attributes[$extrafieldsobjectkey]['list'][$key], 1, 1, '2');
1797
        $help = $this->attributes[$extrafieldsobjectkey]['help'][$key];
1798
        $hidden = (empty($list) ? 1 : 0); // If $list empty, we are sure it is hidden, otherwise we show. If it depends on mode (view/create/edit form or list, this must be filtered by caller)
1799
1800
        if ($hidden) {
1801
            return ''; // This is a protection. If field is hidden, we should just not call this method.
1802
        }
1803
1804
        //if ($computed) $value =       // $value is already calculated into $value before calling this method
1805
1806
        $showsize = 0;
1807
        if ($type == 'date') {
1808
            $showsize = 10;
1809
            if ($value !== '') {
1810
                $value = dol_print_date($value, 'day'); // For date without hour, date is always GMT for storage and output
1811
            }
1812
        } elseif ($type == 'datetime') {
1813
            $showsize = 19;
1814
            if ($value !== '') {
1815
                $value = dol_print_date($value, 'dayhour', 'tzuserrel');
1816
            }
1817
        } elseif ($type == 'datetimegmt') {
1818
            $showsize = 19;
1819
            if ($value !== '') {
1820
                $value = dol_print_date($value, 'dayhour', 'gmt');
1821
            }
1822
        } elseif ($type == 'int') {
1823
            $showsize = 10;
1824
        } elseif ($type == 'double') {
1825
            if (!empty($value)) {
1826
                //$value=price($value);
1827
                $sizeparts = explode(",", $size);
1828
                $number_decimals = array_key_exists(1, $sizeparts) ? $sizeparts[1] : 0;
1829
                $value = price($value, 0, $outputlangs, 0, 0, $number_decimals, '');
1830
            }
1831
        } elseif ($type == 'boolean') {
1832
            $checked = '';
1833
            if (!empty($value)) {
1834
                $checked = ' checked ';
1835
            }
1836
            if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2) {
1837
                $value = '<input type="checkbox" ' . $checked . ' ' . ($moreparam ? $moreparam : '') . ' readonly disabled>';
1838
            } else {
1839
                $value = yn($value ? 1 : 0);
1840
            }
1841
        } elseif ($type == 'mail') {
1842
            $value = dol_print_email($value, 0, 0, 0, 64, 1, 1);
1843
        } elseif ($type == 'ip') {
1844
            $value = dol_print_ip($value, 0);
1845
        } elseif ($type == 'icon') {
1846
            $value = '<span class="' . $value . '"></span>';
1847
        } elseif ($type == 'url') {
1848
            $value = dol_print_url($value, '_blank', 32, 1);
1849
        } elseif ($type == 'phone') {
1850
            $value = dol_print_phone($value, '', 0, 0, '', '&nbsp;', 'phone');
1851
        } elseif ($type == 'price') {
1852
            //$value = price($value, 0, $langs, 0, 0, -1, $conf->currency);
1853
            if ($value || $value == '0') {
1854
                $value = price($value, 0, $outputlangs, 0, $conf->global->MAIN_MAX_DECIMALS_TOT, -1) . ' ' . $outputlangs->getCurrencySymbol($conf->currency);
1855
            }
1856
        } elseif ($type == 'pricecy') {
1857
            $currency = $conf->currency;
1858
            if (!empty($value)) {
1859
                // $value in memory is a php string like '0.01:EUR'
1860
                $pricetmp = explode(':', $value);
1861
                $currency = !empty($pricetmp[1]) ? $pricetmp[1] : $conf->currency;
1862
                $value = $pricetmp[0];
1863
            }
1864
            if ($value || $value == '0') {
1865
                $value = price($value, 0, $outputlangs, 0, $conf->global->MAIN_MAX_DECIMALS_TOT, -1, $currency);
1866
            }
1867
        } elseif ($type == 'select') {
1868
            $valstr = (!empty($param['options'][$value]) ? $param['options'][$value] : '');
1869
            if (($pos = strpos($valstr, "|")) !== false) {
1870
                $valstr = substr($valstr, 0, $pos);
1871
            }
1872
            if ($langfile && $valstr) {
1873
                $value = $outputlangs->trans($valstr);
1874
            } else {
1875
                $value = $valstr;
1876
            }
1877
        } elseif ($type == 'sellist') {
1878
            $param_list = array_keys($param['options']);
1879
            $InfoFieldList = explode(":", $param_list[0]);
1880
1881
            $selectkey = "rowid";
1882
            $keyList = 'rowid';
1883
1884
            if (count($InfoFieldList) >= 3) {
1885
                $selectkey = $InfoFieldList[2];
1886
                $keyList = $InfoFieldList[2] . ' as rowid';
1887
            }
1888
1889
            $fields_label = explode('|', $InfoFieldList[1]);
1890
            if (is_array($fields_label)) {
1891
                $keyList .= ', ';
1892
                $keyList .= implode(', ', $fields_label);
1893
            }
1894
1895
            $filter_categorie = false;
1896
            if (count($InfoFieldList) > 5) {
1897
                if ($InfoFieldList[0] == 'categorie') {
1898
                    $filter_categorie = true;
1899
                }
1900
            }
1901
1902
            $sql = "SELECT " . $keyList;
1903
            $sql .= ' FROM ' . $this->db->prefix() . $InfoFieldList[0];
1904
            if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) {
1905
                $sql .= ' as main';
1906
            }
1907
            if ($selectkey == 'rowid' && empty($value)) {
1908
                $sql .= " WHERE " . $selectkey . " = 0";
1909
            } elseif ($selectkey == 'rowid') {
1910
                $sql .= " WHERE " . $selectkey . " = " . ((int) $value);
1911
            } else {
1912
                $sql .= " WHERE " . $selectkey . " = '" . $this->db->escape($value) . "'";
1913
            }
1914
1915
            //$sql.= ' AND entity = '.$conf->entity;
1916
1917
            dol_syslog(get_class($this) . ':showOutputField:$type=sellist', LOG_DEBUG);
1918
            $resql = $this->db->query($sql);
1919
            if ($resql) {
1920
                if ($filter_categorie === false) {
1921
                    $value = ''; // value was used, so now we reste it to use it to build final output
1922
1923
                    $obj = $this->db->fetch_object($resql);
1924
1925
                    // Several field into label (eq table:code|label:rowid)
1926
                    $fields_label = explode('|', $InfoFieldList[1]);
1927
1928
                    if (is_array($fields_label) && count($fields_label) > 1) {
1929
                        foreach ($fields_label as $field_toshow) {
1930
                            $translabel = '';
1931
                            if (!empty($obj->$field_toshow)) {
1932
                                $translabel = $outputlangs->trans($obj->$field_toshow);
1933
1934
                                if ($translabel != $obj->$field_toshow) {
1935
                                    $value .= dol_trunc($translabel, 24) . ' ';
1936
                                } else {
1937
                                    $value .= $obj->$field_toshow . ' ';
1938
                                }
1939
                            }
1940
                        }
1941
                    } else {
1942
                        $translabel = '';
1943
                        $tmppropname = $InfoFieldList[1];
1944
                        //$obj->$tmppropname = '';
1945
                        if (!empty(isset($obj->$tmppropname) ? $obj->$tmppropname : '')) {
1946
                            $translabel = $outputlangs->trans($obj->$tmppropname);
1947
                        }
1948
                        if ($translabel != (isset($obj->$tmppropname) ? $obj->$tmppropname : '')) {
1949
                            $value = dol_trunc($translabel, 18);
1950
                        } else {
1951
                            $value = isset($obj->$tmppropname) ? $obj->$tmppropname : '';
1952
                        }
1953
                    }
1954
                } else {
1955
                    $toprint = array();
1956
                    $obj = $this->db->fetch_object($resql);
1957
                    if ($obj->rowid) {
1958
                        require_once constant('DOL_DOCUMENT_ROOT') . '/categories/class/categorie.class.php';
1959
                        $c = new Categorie($this->db);
1960
                        $result = $c->fetch($obj->rowid);
1961
                        if ($result > 0) {
1962
                            $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
1963
                            foreach ($ways as $way) {
1964
                                $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #bbb"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
1965
                            }
1966
                        }
1967
                    }
1968
                    $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
1969
                }
1970
            } else {
1971
                dol_syslog(get_class($this) . '::showOutputField error ' . $this->db->lasterror(), LOG_WARNING);
1972
            }
1973
        } elseif ($type == 'radio') {
1974
            if (!isset($param['options'][$value])) {
1975
                $outputlangs->load('errors');
1976
                $value = $outputlangs->trans('ErrorNoValueForRadioType');
1977
            } else {
1978
                $value = $outputlangs->trans($param['options'][$value]);
1979
            }
1980
        } elseif ($type == 'checkbox') {
1981
            $value_arr = explode(',', $value);
1982
            $value = '';
1983
            $toprint = array();
1984
            if (is_array($value_arr)) {
1985
                foreach ($value_arr as $keyval => $valueval) {
1986
                    if (!empty($valueval)) {
1987
                        $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $param['options'][$valueval] . '</li>';
1988
                    }
1989
                }
1990
            }
1991
            $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
1992
        } elseif ($type == 'chkbxlst') {
1993
            $value_arr = explode(',', $value);
1994
1995
            $param_list = array_keys($param['options']);
1996
            $InfoFieldList = explode(":", $param_list[0]);
1997
1998
            $selectkey = "rowid";
1999
            $keyList = 'rowid';
2000
2001
            if (count($InfoFieldList) >= 3) {
2002
                $selectkey = $InfoFieldList[2];
2003
                $keyList = $InfoFieldList[2] . ' as rowid';
2004
            }
2005
2006
            $fields_label = explode('|', $InfoFieldList[1]);
2007
            if (is_array($fields_label)) {
2008
                $keyList .= ', ';
2009
                $keyList .= implode(', ', $fields_label);
2010
            }
2011
2012
            $filter_categorie = false;
2013
            if (count($InfoFieldList) > 5) {
2014
                if ($InfoFieldList[0] == 'categorie') {
2015
                    $filter_categorie = true;
2016
                }
2017
            }
2018
2019
            $sql = "SELECT " . $keyList;
2020
            $sql .= " FROM " . $this->db->prefix() . $InfoFieldList[0];
2021
            if (strpos($InfoFieldList[4], 'extra.') !== false) {
2022
                $sql .= ' as main';
2023
            }
2024
            // $sql.= " WHERE ".$selectkey."='".$this->db->escape($value)."'";
2025
            // $sql.= ' AND entity = '.$conf->entity;
2026
2027
            dol_syslog(get_class($this) . ':showOutputField:$type=chkbxlst', LOG_DEBUG);
2028
            $resql = $this->db->query($sql);
2029
            if ($resql) {
2030
                if ($filter_categorie === false) {
2031
                    $value = ''; // value was used, so now we reste it to use it to build final output
2032
                    $toprint = array();
2033
                    while ($obj = $this->db->fetch_object($resql)) {
2034
                        // Several field into label (eq table:code|label:rowid)
2035
                        $fields_label = explode('|', $InfoFieldList[1]);
2036
                        if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
2037
                            if (is_array($fields_label) && count($fields_label) > 1) {
2038
                                $label = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">';
2039
                                foreach ($fields_label as $field_toshow) {
2040
                                    $translabel = '';
2041
                                    if (!empty($obj->$field_toshow)) {
2042
                                        $translabel = $outputlangs->trans($obj->$field_toshow);
2043
                                    }
2044
                                    if ($translabel != $field_toshow) {
2045
                                        $label .= ' ' . dol_trunc($translabel, 18);
2046
                                    } else {
2047
                                        $label .= ' ' . $obj->$field_toshow;
2048
                                    }
2049
                                }
2050
                                $label .= '</li>';
2051
                                $toprint[] = $label;
2052
                            } else {
2053
                                $translabel = '';
2054
                                if (!empty($obj->{$InfoFieldList[1]})) {
2055
                                    $translabel = $outputlangs->trans($obj->{$InfoFieldList[1]});
2056
                                }
2057
                                if ($translabel != $obj->{$InfoFieldList[1]}) {
2058
                                    $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
2059
                                } else {
2060
                                    $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->{$InfoFieldList[1]} . '</li>';
2061
                                }
2062
                            }
2063
                        }
2064
                    }
2065
                } else {
2066
                    require_once constant('DOL_DOCUMENT_ROOT') . '/categories/class/categorie.class.php';
2067
2068
                    $toprint = array();
2069
                    while ($obj = $this->db->fetch_object($resql)) {
2070
                        if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
2071
                            $c = new Categorie($this->db);
2072
                            $c->fetch($obj->rowid);
2073
                            $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
2074
                            foreach ($ways as $way) {
2075
                                $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #bbb"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
2076
                            }
2077
                        }
2078
                    }
2079
                }
2080
                if (!empty($toprint)) {
2081
                    $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
2082
                }
2083
            } else {
2084
                dol_syslog(get_class($this) . '::showOutputField error ' . $this->db->lasterror(), LOG_WARNING);
2085
            }
2086
        } elseif ($type == 'link') {
2087
            $out = '';
2088
2089
            // Only if something to display (perf)
2090
            if ($value) {       // If we have -1 here, pb is into insert, not into output (fix insert instead of changing code here to compensate)
2091
                $param_list = array_keys($param['options']); // $param_list='ObjectName:classPath'
2092
2093
                $InfoFieldList = explode(":", $param_list[0]);
2094
                $classname = $InfoFieldList[0];
2095
                $classpath = $InfoFieldList[1];
2096
                if (!empty($classpath)) {
2097
                    dol_include_once($InfoFieldList[1]);
2098
                    if ($classname && class_exists($classname)) {
2099
                        $object = new $classname($this->db);
2100
                        $object->fetch($value);
2101
                        $value = $object->getNomUrl(3);
2102
                    }
2103
                } else {
2104
                    dol_syslog('Error bad setup of extrafield', LOG_WARNING);
2105
                    return 'Error bad setup of extrafield';
2106
                }
2107
            }
2108
        } elseif ($type == 'point') {
2109
            if (!empty($value)) {
2110
                require_once constant('DOL_DOCUMENT_ROOT') . '/core/class/dolgeophp.class.php';
2111
                $dolgeophp = new DolGeoPHP($this->db);
2112
                $value = $dolgeophp->getXYString($value);
2113
            } else {
2114
                $value = '';
2115
            }
2116
        } elseif (in_array($type, ['multipts','linestrg', 'polygon'])) {
2117
            if (!empty($value)) {
2118
                require_once constant('DOL_DOCUMENT_ROOT') . '/core/class/dolgeophp.class.php';
2119
                $dolgeophp = new DolGeoPHP($this->db);
2120
                $value = $dolgeophp->getPointString($value);
2121
            } else {
2122
                $value = '';
2123
            }
2124
        } elseif ($type == 'text') {
2125
            $value = dol_htmlentitiesbr($value);
2126
        } elseif ($type == 'html') {
2127
            $value = dol_htmlentitiesbr($value);
2128
        } elseif ($type == 'password') {
2129
            $value = dol_trunc(preg_replace('/./i', '*', $value), 8, 'right', 'UTF-8', 1);
2130
        } else {
2131
            $showsize = round((float) $size);
2132
            if ($showsize > 48) {
2133
                $showsize = 48;
2134
            }
2135
        }
2136
2137
        //print $type.'-'.$size;
2138
        $out = $value;
2139
2140
        return $out;
2141
    }
2142
2143
    /**
2144
     * Return the CSS to use for this extrafield into list
2145
     *
2146
     * @param   string  $key                    Key of attribute
2147
     * @param   string  $extrafieldsobjectkey   If defined, use the new method to get extrafields data
2148
     * @return  string                          Formatted value
2149
     */
2150
    public function getAlignFlag($key, $extrafieldsobjectkey = '')
2151
    {
2152
        global $conf, $langs;
2153
2154
        $type = 'varchar';
2155
        if (!empty($extrafieldsobjectkey)) {
2156
            $type = $this->attributes[$extrafieldsobjectkey]['type'][$key];
2157
        }
2158
2159
        $cssstring = '';
2160
2161
        if (in_array($type, array('date', 'datetime', 'datetimegmt',))) {
2162
            $cssstring = "center";
2163
        } elseif (in_array($type, array('int', 'price', 'double'))) {
2164
            $cssstring = "right";
2165
        } elseif (in_array($type, array('boolean', 'radio', 'checkbox', 'ip', 'icon'))) {
2166
            $cssstring = "center";
2167
        }
2168
2169
        if (!empty($this->attributes[$extrafieldsobjectkey]['csslist'][$key])) {
2170
            $cssstring .= ($cssstring ? ' ' : '') . $this->attributes[$extrafieldsobjectkey]['csslist'][$key];
2171
        } else {
2172
            if (in_array($type, array('ip'))) {
2173
                $cssstring .= ($cssstring ? ' ' : '') . 'tdoverflowmax150';
2174
            }
2175
        }
2176
2177
        return $cssstring;
2178
    }
2179
2180
    /**
2181
     * Return HTML string to print separator extrafield
2182
     *
2183
     * @param   string  $key            Key of attribute
2184
     * @param   object  $object         Object
2185
     * @param   int     $colspan        Value of colspan to use (it must includes the first column with title)
2186
     * @param   string  $display_type   "card" for form display, "line" for document line display (extrafields on propal line, order line, etc...)
2187
     * @param   string  $mode           Show output ('view') or input ('create' or 'edit') for extrafield
2188
     * @return  string                  HTML code with line for separator
2189
     */
2190
    public function showSeparator($key, $object, $colspan = 2, $display_type = 'card', $mode = '')
2191
    {
2192
        global $conf, $langs;
2193
2194
        $tagtype = 'tr';
2195
        $tagtype_dyn = 'td';
2196
2197
        if ($display_type == 'line') {
2198
            $tagtype = 'div';
2199
            $tagtype_dyn = 'span';
2200
            $colspan = 0;
2201
        }
2202
2203
        $extrafield_param = $this->attributes[$object->table_element]['param'][$key];
2204
        $extrafield_param_list = array();
2205
        if (!empty($extrafield_param) && is_array($extrafield_param)) {
2206
            $extrafield_param_list = array_keys($extrafield_param['options']);
2207
        }
2208
2209
        // Set $extrafield_collapse_display_value (do we have to collapse/expand the group after the separator)
2210
        $extrafield_collapse_display_value = -1;
2211
        $expand_display = false;
2212
        if (is_array($extrafield_param_list) && count($extrafield_param_list) > 0) {
2213
            $extrafield_collapse_display_value = intval($extrafield_param_list[0]);
2214
            $expand_display = ((isset($_COOKIE['DOLCOLLAPSE_' . $object->table_element . '_extrafields_' . $key]) || GETPOSTINT('ignorecollapsesetup')) ? (empty($_COOKIE['DOLCOLLAPSE_' . $object->table_element . '_extrafields_' . $key]) ? false : true) : ($extrafield_collapse_display_value == 2 ? false : true));
2215
        }
2216
        $disabledcookiewrite = 0;
2217
        if ($mode == 'create') {
2218
            // On create mode, force separator group to not be collapsible
2219
            $extrafield_collapse_display_value = 1;
2220
            $expand_display = true; // We force group to be shown expanded
2221
            $disabledcookiewrite = 1; // We keep status of group unchanged into the cookie
2222
        }
2223
2224
        $out = '<' . $tagtype . ' id="trextrafieldseparator' . $key . (!empty($object->id) ? '_' . $object->id : '') . '" class="trextrafieldseparator trextrafieldseparator' . $key . (!empty($object->id) ? '_' . $object->id : '') . '">';
2225
        $out .= '<' . $tagtype_dyn . ' ' . (!empty($colspan) ? 'colspan="' . $colspan . '"' : '') . '>';
2226
        // Some js code will be injected here to manage the collapsing of extrafields
2227
        // Output the picto
2228
        $out .= '<span class="' . ($extrafield_collapse_display_value ? 'cursorpointer ' : '') . ($extrafield_collapse_display_value == 0 ? 'fas fa-square opacitymedium' : 'far fa-' . (($expand_display ? 'minus' : 'plus') . '-square')) . '"></span>';
2229
        $out .= '&nbsp;';
2230
        $out .= '<strong>';
2231
        $out .= $langs->trans($this->attributes[$object->table_element]['label'][$key]);
2232
        $out .= '</strong>';
2233
        $out .= '</' . $tagtype_dyn . '>';
2234
        $out .= '</' . $tagtype . '>';
2235
2236
        $collapse_group = $key . (!empty($object->id) ? '_' . $object->id : '');
2237
        //$extrafields_collapse_num = $this->attributes[$object->table_element]['pos'][$key].(!empty($object->id)?'_'.$object->id:'');
2238
2239
        if ($extrafield_collapse_display_value == 1 || $extrafield_collapse_display_value == 2) {
2240
            // Set the collapse_display status to cookie in priority or if ignorecollapsesetup is 1, if cookie and ignorecollapsesetup not defined, use the setup.
2241
            $this->expand_display[$collapse_group] = $expand_display;
2242
2243
            if (!empty($conf->use_javascript_ajax)) {
2244
                $out .= '<!-- Add js script to manage the collapse/uncollapse of extrafields separators ' . $key . ' -->' . "\n";
2245
                $out .= '<script nonce="' . getNonce() . '" type="text/javascript">' . "\n";
2246
                $out .= 'jQuery(document).ready(function(){' . "\n";
2247
                if (empty($disabledcookiewrite)) {
2248
                    if ($expand_display === false) {
2249
                        $out .= '   console.log("Inject js for the collapsing of extrafield ' . $key . ' - hide");' . "\n";
2250
                        $out .= '   jQuery(".trextrafields_collapse' . $collapse_group . '").hide();' . "\n";
2251
                    } else {
2252
                        $out .= '   console.log("Inject js for collapsing of extrafield ' . $key . ' - keep visible and set cookie");' . "\n";
2253
                        $out .= '   document.cookie = "DOLCOLLAPSE_' . $object->table_element . '_extrafields_' . $key . '=1; path=' . $_SERVER["PHP_SELF"] . '"' . "\n";
2254
                    }
2255
                }
2256
                $out .= '   jQuery("#trextrafieldseparator' . $key . (!empty($object->id) ? '_' . $object->id : '') . '").click(function(){' . "\n";
2257
                $out .= '       console.log("We click on collapse/uncollapse to hide/show .trextrafields_collapse' . $collapse_group . '");' . "\n";
2258
                $out .= '       jQuery(".trextrafields_collapse' . $collapse_group . '").toggle(100, function(){' . "\n";
2259
                $out .= '           if (jQuery(".trextrafields_collapse' . $collapse_group . '").is(":hidden")) {' . "\n";
2260
                $out .= '               jQuery("#trextrafieldseparator' . $key . (!empty($object->id) ? '_' . $object->id : '') . ' ' . $tagtype_dyn . ' span").addClass("fa-plus-square").removeClass("fa-minus-square");' . "\n";
2261
                $out .= '               document.cookie = "DOLCOLLAPSE_' . $object->table_element . '_extrafields_' . $key . '=0; path=' . $_SERVER["PHP_SELF"] . '"' . "\n";
2262
                $out .= '           } else {' . "\n";
2263
                $out .= '               jQuery("#trextrafieldseparator' . $key . (!empty($object->id) ? '_' . $object->id : '') . ' ' . $tagtype_dyn . ' span").addClass("fa-minus-square").removeClass("fa-plus-square");' . "\n";
2264
                $out .= '               document.cookie = "DOLCOLLAPSE_' . $object->table_element . '_extrafields_' . $key . '=1; path=' . $_SERVER["PHP_SELF"] . '"' . "\n";
2265
                $out .= '           }' . "\n";
2266
                $out .= '       });' . "\n";
2267
                $out .= '   });' . "\n";
2268
                $out .= '});' . "\n";
2269
                $out .= '</script>' . "\n";
2270
            }
2271
        } else {
2272
            $this->expand_display[$collapse_group] = 1;
2273
        }
2274
2275
        return $out;
2276
    }
2277
2278
    /**
2279
     * Fill array_options property of object by extrafields value (using for data sent by forms)
2280
     *
2281
     * @param   array|null  $extralabels        Deprecated (old $array of extrafields, now set this to null)
2282
     * @param   object      $object             Object
2283
     * @param   string      $onlykey            Only some keys are filled:
2284
     *                                          'string' => When we make update of only one extrafield ($action = 'update_extras'), calling page can set this to avoid to have other extrafields being reset.
2285
     *                                          '@GETPOSTISSET' => When we make update of several extrafields ($action = 'update'), calling page can set this to avoid to have fields not into POST being reset.
2286
     * @param   int         $todefaultifmissing 1=Set value to the default value in database if value is mandatory and missing
2287
     * @return  int                             1 if array_options set, 0 if no value, -1 if error (field required missing for example)
2288
     */
2289
    public function setOptionalsFromPost($extralabels, &$object, $onlykey = '', $todefaultifmissing = 0)
2290
    {
2291
        global $langs;
2292
2293
        $nofillrequired = 0; // For error when required field left blank
2294
        $error_field_required = array();
2295
2296
        if (isset($this->attributes[$object->table_element]['label']) && is_array($this->attributes[$object->table_element]['label'])) {
2297
            $extralabels = $this->attributes[$object->table_element]['label'];
2298
        }
2299
2300
        if (is_array($extralabels)) {
2301
            // Get extra fields
2302
            foreach ($extralabels as $key => $value) {
2303
                if (!empty($onlykey) && $onlykey != '@GETPOSTISSET' && $key != $onlykey) {
2304
                    continue;
2305
                }
2306
2307
                if (!empty($onlykey) && $onlykey == '@GETPOSTISSET' && !GETPOSTISSET('options_' . $key) && (! in_array($this->attributes[$object->table_element]['type'][$key], array('boolean', 'checkbox', 'chkbxlst', 'point', 'multipts', 'linestrg', 'polygon')))) {
2308
                    //when unticking boolean field, it's not set in POST
2309
                    continue;
2310
                }
2311
2312
                $key_type = $this->attributes[$object->table_element]['type'][$key];
2313
                if ($key_type == 'separate') {
2314
                    continue;
2315
                }
2316
2317
                $enabled = 1;
2318
                if (isset($this->attributes[$object->table_element]['enabled'][$key])) {    // 'enabled' is often a condition on module enabled or not
2319
                    $enabled = (int) dol_eval($this->attributes[$object->table_element]['enabled'][$key], 1, 1, '2');
2320
                }
2321
2322
                $visibility = 1;
2323
                if (isset($this->attributes[$object->table_element]['list'][$key])) {       // 'list' is option for visibility
2324
                    $visibility = (int) dol_eval($this->attributes[$object->table_element]['list'][$key], 1, 1, '2');
2325
                }
2326
2327
                $perms = 1;
2328
                if (isset($this->attributes[$object->table_element]['perms'][$key])) {
2329
                    $perms = (int) dol_eval($this->attributes[$object->table_element]['perms'][$key], 1, 1, '2');
2330
                }
2331
                if (
2332
                    empty($enabled)
2333
                    || (
2334
                        $onlykey === '@GETPOSTISSET'
2335
                        && in_array($this->attributes[$object->table_element]['type'][$key], array('boolean', 'checkbox', 'chkbxlst'))
2336
                        && in_array(abs($enabled), array(2, 5))
2337
                        && ! GETPOSTISSET('options_' . $key) // Update hidden checkboxes and multiselect only if they are provided
2338
                    )
2339
                ) {
2340
                    continue;
2341
                }
2342
2343
                $visibility_abs = abs($visibility);
2344
                // not modify if extra field is not in update form (0 : never, 2 or -2 : list only, 5 or - 5 : list and view only)
2345
                if (empty($visibility_abs) || $visibility_abs == 2 || $visibility_abs == 5) {
2346
                    continue;
2347
                }
2348
                if (empty($perms)) {
2349
                    continue;
2350
                }
2351
2352
                if ($this->attributes[$object->table_element]['required'][$key]) {  // Value is required
2353
                    // Check if functionally empty without using GETPOST (depending on the type of extrafield, a
2354
                    // technically non-empty value may be treated as empty functionally).
2355
                    // value can be alpha, int, array, etc...
2356
                    $v = $_POST["options_" . $key] ?? null;
2357
                    $type = $this->attributes[$object->table_element]['type'][$key];
2358
                    if (self::isEmptyValue($v, $type)) {
2359
                        //print 'ccc'.$value.'-'.$this->attributes[$object->table_element]['required'][$key];
2360
2361
                        // Field is not defined. We mark this as an error. We may fix it later if there is a default value and $todefaultifmissing is set.
2362
2363
                        $nofillrequired++;
2364
                        if (!empty($this->attributes[$object->table_element]['langfile'][$key])) {
2365
                            $langs->load($this->attributes[$object->table_element]['langfile'][$key]);
2366
                        }
2367
                        $error_field_required[$key] = $langs->transnoentitiesnoconv($value);
2368
                    }
2369
                }
2370
2371
                if (in_array($key_type, array('date'))) {
2372
                    // Clean parameters
2373
                    $value_key = dol_mktime(12, 0, 0, GETPOSTINT("options_" . $key . "month"), GETPOSTINT("options_" . $key . "day"), GETPOSTINT("options_" . $key . "year"));
2374
                } elseif (in_array($key_type, array('datetime'))) {
2375
                    // Clean parameters
2376
                    $value_key = dol_mktime(GETPOSTINT("options_" . $key . "hour"), GETPOSTINT("options_" . $key . "min"), GETPOSTINT("options_" . $key . "sec"), GETPOSTINT("options_" . $key . "month"), GETPOSTINT("options_" . $key . "day"), GETPOSTINT("options_" . $key . "year"), 'tzuserrel');
2377
                } elseif (in_array($key_type, array('datetimegmt'))) {
2378
                    // Clean parameters
2379
                    $value_key = dol_mktime(GETPOSTINT("options_" . $key . "hour"), GETPOSTINT("options_" . $key . "min"), GETPOSTINT("options_" . $key . "sec"), GETPOSTINT("options_" . $key . "month"), GETPOSTINT("options_" . $key . "day"), GETPOSTINT("options_" . $key . "year"), 'gmt');
2380
                } elseif (in_array($key_type, array('checkbox', 'chkbxlst'))) {
2381
                    $value_arr = GETPOST("options_" . $key, 'array'); // check if an array
2382
                    if (!empty($value_arr)) {
2383
                        $value_key = implode(',', $value_arr);
2384
                    } else {
2385
                        $value_key = '';
2386
                    }
2387
                } elseif (in_array($key_type, array('price', 'double'))) {
2388
                    $value_arr = GETPOST("options_" . $key, 'alpha');
2389
                    $value_key = price2num($value_arr);
2390
                } elseif (in_array($key_type, array('pricecy', 'double'))) {
2391
                    $value_key = price2num(GETPOST("options_" . $key, 'alpha')) . ':' . GETPOST("options_" . $key . "currency_id", 'alpha');
2392
                } elseif (in_array($key_type, array('html'))) {
2393
                    $value_key = GETPOST("options_" . $key, 'restricthtml');
2394
                } elseif (in_array($key_type, ['point', 'multipts', 'linestrg', 'polygon'])) {
2395
                    // construct point
2396
                    require_once constant('DOL_DOCUMENT_ROOT') . '/core/class/dolgeophp.class.php';
2397
                    $geojson = GETPOST("options_" . $key, 'restricthtml');
2398
                    if ($geojson != '{}') {
2399
                        $dolgeophp = new DolGeoPHP($this->db);
2400
                        $value_key = $dolgeophp->getWkt($geojson);
2401
                    } else {
2402
                        $value_key = '';
2403
                    }
2404
                } elseif (in_array($key_type, array('text'))) {
2405
                    $label_security_check = 'alphanohtml';
2406
                    // by default 'alphanohtml' (better security); hidden conf MAIN_SECURITY_ALLOW_UNSECURED_LABELS_WITH_HTML allows basic html
2407
                    if (getDolGlobalString('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
2408
                        $label_security_check = 'nohtml';
2409
                    } else {
2410
                        $label_security_check = !getDolGlobalString('MAIN_SECURITY_ALLOW_UNSECURED_LABELS_WITH_HTML') ? 'alphanohtml' : 'restricthtml';
2411
                    }
2412
                    $value_key = GETPOST("options_" . $key, $label_security_check);
2413
                } else {
2414
                    $value_key = GETPOST("options_" . $key);
2415
                    if (in_array($key_type, array('link')) && $value_key == '-1') {
2416
                        $value_key = '';
2417
                    }
2418
                }
2419
2420
                if (!empty($error_field_required[$key]) && $todefaultifmissing) {
2421
                    // Value is required but we have a default value and we asked to set empty value to the default value
2422
                    if (!empty($this->attributes[$object->table_element]['default']) && !is_null($this->attributes[$object->table_element]['default'][$key])) {
2423
                        $value_key = $this->attributes[$object->table_element]['default'][$key];
2424
                        unset($error_field_required[$key]);
2425
                        $nofillrequired--;
2426
                    }
2427
                }
2428
2429
                $object->array_options["options_" . $key] = $value_key;
2430
            }
2431
2432
            if ($nofillrequired) {
2433
                $langs->load('errors');
2434
                $this->error = $langs->trans('ErrorFieldsRequired') . ' : ' . implode(', ', $error_field_required);
2435
                setEventMessages($this->error, null, 'errors');
2436
                return -1;
2437
            } else {
2438
                return 1;
2439
            }
2440
        } else {
2441
            return 0;
2442
        }
2443
    }
2444
2445
    /**
2446
     * return array_options array of data of extrafields value of object sent by a search form
2447
     *
2448
     * @param  array|string     $extrafieldsobjectkey   array of extrafields (old usage) or value of object->table_element (new usage)
2449
     * @param  string           $keysuffix              Suffix string to add into name and id of field (can be used to avoid duplicate names)
2450
     * @param  string           $keyprefix              Prefix string to add into name and id of field (can be used to avoid duplicate names)
2451
     * @return array|int                                array_options set or 0 if no value
2452
     */
2453
    public function getOptionalsFromPost($extrafieldsobjectkey, $keysuffix = '', $keyprefix = '')
2454
    {
2455
        global $_POST;
2456
2457
        if (is_string($extrafieldsobjectkey) && !empty($this->attributes[$extrafieldsobjectkey]['label']) && is_array($this->attributes[$extrafieldsobjectkey]['label'])) {
2458
            $extralabels = $this->attributes[$extrafieldsobjectkey]['label'];
2459
        } else {
2460
            $extralabels = $extrafieldsobjectkey;
2461
        }
2462
2463
        if (is_array($extralabels)) {
2464
            $array_options = array();
2465
2466
            // Get extra fields
2467
            foreach ($extralabels as $key => $value) {
2468
                $key_type = '';
2469
                if (is_string($extrafieldsobjectkey)) {
2470
                    $key_type = $this->attributes[$extrafieldsobjectkey]['type'][$key];
2471
                }
2472
2473
                if (in_array($key_type, array('date'))) {
2474
                    $dateparamname_start = $keyprefix . 'options_' . $key . $keysuffix . '_start';
2475
                    $dateparamname_end   = $keyprefix . 'options_' . $key . $keysuffix . '_end';
2476
                    if (GETPOST($dateparamname_start . 'year') || GETPOST($dateparamname_end . 'year')) {
2477
                        $value_key = array();
2478
                        // values provided as a component year, month, day, etc.
2479
                        if (GETPOST($dateparamname_start . 'year')) {
2480
                            $value_key['start'] = dol_mktime(0, 0, 0, GETPOSTINT($dateparamname_start . 'month'), GETPOSTINT($dateparamname_start . 'day'), GETPOSTINT($dateparamname_start . 'year'));
2481
                        }
2482
                        if (GETPOST($dateparamname_start . 'year')) {
2483
                            $value_key['end'] = dol_mktime(23, 59, 59, GETPOSTINT($dateparamname_end . 'month'), GETPOSTINT($dateparamname_end . 'day'), GETPOSTINT($dateparamname_end . 'year'));
2484
                        }
2485
                    } elseif (GETPOST($keyprefix . "options_" . $key . $keysuffix . "year")) {
2486
                        // Clean parameters
2487
                        $value_key = dol_mktime(12, 0, 0, GETPOSTINT($keyprefix . "options_" . $key . $keysuffix . "month"), GETPOSTINT($keyprefix . "options_" . $key . $keysuffix . "day"), GETPOSTINT($keyprefix . "options_" . $key . $keysuffix . "year"));
2488
                    } else {
2489
                        continue; // Value was not provided, we should not set it.
2490
                    }
2491
                } elseif (in_array($key_type, array('datetime', 'datetimegmt'))) {
2492
                    $dateparamname_start = $keyprefix . 'options_' . $key . $keysuffix . '_start';
2493
                    $dateparamname_end   = $keyprefix . 'options_' . $key . $keysuffix . '_end';
2494
2495
                    if (GETPOST($dateparamname_start . 'year') && GETPOST($dateparamname_end . 'year')) {
2496
                        // values provided as a date pair (start date + end date), each date being broken down as year, month, day, etc.
2497
                        $dateparamname_end_hour = GETPOSTINT($dateparamname_end . 'hour') != '-1' ? GETPOSTINT($dateparamname_end . 'hour') : '23';
2498
                        $dateparamname_end_min = GETPOSTINT($dateparamname_end . 'min') != '-1' ? GETPOSTINT($dateparamname_end . 'min') : '59';
2499
                        $dateparamname_end_sec = GETPOSTINT($dateparamname_end . 'sec') != '-1' ? GETPOSTINT($dateparamname_end . 'sec') : '59';
2500
                        if ($key_type == 'datetimegmt') {
2501
                            $value_key = array(
2502
                            'start' => dol_mktime(GETPOSTINT($dateparamname_start . 'hour'), GETPOSTINT($dateparamname_start . 'min'), GETPOSTINT($dateparamname_start . 'sec'), GETPOSTINT($dateparamname_start . 'month'), GETPOSTINT($dateparamname_start . 'day'), GETPOSTINT($dateparamname_start . 'year'), 'gmt'),
2503
                            'end' => dol_mktime($dateparamname_end_hour, $dateparamname_end_min, $dateparamname_end_sec, GETPOSTINT($dateparamname_end . 'month'), GETPOSTINT($dateparamname_end . 'day'), GETPOSTINT($dateparamname_end . 'year'), 'gmt')
2504
                            );
2505
                        } else {
2506
                            $value_key = array(
2507
                            'start' => dol_mktime(GETPOSTINT($dateparamname_start . 'hour'), GETPOSTINT($dateparamname_start . 'min'), GETPOSTINT($dateparamname_start . 'sec'), GETPOSTINT($dateparamname_start . 'month'), GETPOSTINT($dateparamname_start . 'day'), GETPOSTINT($dateparamname_start . 'year'), 'tzuserrel'),
2508
                            'end' => dol_mktime($dateparamname_end_hour, $dateparamname_end_min, $dateparamname_end_sec, GETPOSTINT($dateparamname_end . 'month'), GETPOSTINT($dateparamname_end . 'day'), GETPOSTINT($dateparamname_end . 'year'), 'tzuserrel')
2509
                            );
2510
                        }
2511
                    } elseif (GETPOST($keyprefix . "options_" . $key . $keysuffix . "year")) {
2512
                        // Clean parameters
2513
                        if ($key_type == 'datetimegmt') {
2514
                            $value_key = dol_mktime(GETPOSTINT($keyprefix . "options_" . $key . $keysuffix . "hour"), GETPOSTINT($keyprefix . "options_" . $key . $keysuffix . "min"), GETPOSTINT($keyprefix . "options_" . $key . $keysuffix . "sec"), GETPOSTINT($keyprefix . "options_" . $key . $keysuffix . "month"), GETPOSTINT($keyprefix . "options_" . $key . $keysuffix . "day"), GETPOSTINT($keyprefix . "options_" . $key . $keysuffix . "year"), 'gmt');
2515
                        } else {
2516
                            $value_key = dol_mktime(GETPOSTINT($keyprefix . "options_" . $key . $keysuffix . "hour"), GETPOSTINT($keyprefix . "options_" . $key . $keysuffix . "min"), GETPOSTINT($keyprefix . "options_" . $key . $keysuffix . "sec"), GETPOSTINT($keyprefix . "options_" . $key . $keysuffix . "month"), GETPOSTINT($keyprefix . "options_" . $key . $keysuffix . "day"), GETPOSTINT($keyprefix . "options_" . $key . $keysuffix . "year"), 'tzuserrel');
2517
                        }
2518
                    } else {
2519
                        continue; // Value was not provided, we should not set it.
2520
                    }
2521
                } elseif ($key_type == 'select') {
2522
                    // to detect if we are in search context
2523
                    if (GETPOSTISARRAY($keyprefix . "options_" . $key . $keysuffix)) {
2524
                        $value_arr = GETPOST($keyprefix . "options_" . $key . $keysuffix, 'array:aZ09');
2525
                        // Make sure we get an array even if there's only one selected
2526
                        $value_arr = (array) $value_arr;
2527
                        $value_key = implode(',', $value_arr);
2528
                    } else {
2529
                        $value_key = GETPOST($keyprefix . "options_" . $key . $keysuffix);
2530
                    }
2531
                } elseif (in_array($key_type, array('checkbox', 'chkbxlst'))) {
2532
                    // We test on a hidden field named "..._multiselect" that is always set to 1 if param is in form so
2533
                    // when nothing is provided we can make a difference between noparam in the form and param was set to nothing.
2534
                    if (!GETPOSTISSET($keyprefix . "options_" . $key . $keysuffix . '_multiselect')) {
2535
                        continue; // Value was not provided, we should not set it.
2536
                    }
2537
                    $value_arr = GETPOST($keyprefix . "options_" . $key . $keysuffix);
2538
                    // Make sure we get an array even if there's only one checkbox
2539
                    $value_arr = (array) $value_arr;
2540
                    $value_key = implode(',', $value_arr);
2541
                } elseif (in_array($key_type, array('price', 'double', 'int'))) {
2542
                    if (!GETPOSTISSET($keyprefix . "options_" . $key . $keysuffix)) {
2543
                        continue; // Value was not provided, we should not set it.
2544
                    }
2545
                    $value_arr = GETPOST($keyprefix . "options_" . $key . $keysuffix);
2546
                    if ($keyprefix != 'search_') {    // If value is for a search, we must keep complex string like '>100 <=150'
2547
                        $value_key = price2num($value_arr);
2548
                    } else {
2549
                        $value_key = $value_arr;
2550
                    }
2551
                } elseif (in_array($key_type, array('boolean'))) {
2552
                    // We test on a hidden field named "..._boolean" that is always set to 1 if param is in form so
2553
                    // when nothing is provided we can make a difference between noparam in the form and param was set to nothing.
2554
                    if (!GETPOSTISSET($keyprefix . "options_" . $key . $keysuffix . "_boolean")) {
2555
                        $value_key = '';
2556
                    } else {
2557
                        $value_arr = GETPOST($keyprefix . "options_" . $key . $keysuffix);
2558
                        $value_key = $value_arr;
2559
                    }
2560
                } elseif (in_array($key_type, array('html'))) {
2561
                    if (!GETPOSTISSET($keyprefix . "options_" . $key . $keysuffix)) {
2562
                        continue; // Value was not provided, we should not set it.
2563
                    }
2564
                    $value_key = dol_htmlcleanlastbr(GETPOST($keyprefix . "options_" . $key . $keysuffix, 'restricthtml'));
2565
                } else {
2566
                    if (!GETPOSTISSET($keyprefix . "options_" . $key . $keysuffix)) {
2567
                        continue; // Value was not provided, we should not set it.
2568
                    }
2569
                    $value_key = GETPOST($keyprefix . "options_" . $key . $keysuffix);
2570
                }
2571
2572
                $array_options[$keyprefix . "options_" . $key] = $value_key; // No keyprefix here. keyprefix is used only for read.
2573
            }
2574
2575
            return $array_options;
2576
        }
2577
2578
        return 0;
2579
    }
2580
2581
    /**
2582
     * Return array with all possible types and labels of extrafields
2583
     *
2584
     * @return string[]
2585
     */
2586
    public static function getListOfTypesLabels()
2587
    {
2588
        global $langs;
2589
2590
        $tmptype2label = ExtraFields::$type2label;
2591
        $type2label = array('');
2592
        foreach ($tmptype2label as $key => $val) {
2593
            $type2label[$key] = $langs->transnoentitiesnoconv($val);
2594
        }
2595
2596
        if (!getDolGlobalString('MAIN_USE_EXTRAFIELDS_ICON')) {
2597
            unset($type2label['icon']);
2598
        }
2599
        if (!getDolGlobalString('MAIN_USE_GEOPHP')) {
2600
            unset($type2label['point']);
2601
            unset($type2label['multipts']);
2602
            unset($type2label['linestrg']);
2603
            unset($type2label['polygon']);
2604
        }
2605
2606
        return $type2label;
2607
    }
2608
2609
    /**
2610
     * Return if a value is "empty" for a mandatory vision.
2611
     *
2612
     * @param   mixed   $v      Value to test
2613
     * @param   string  $type   Type of extrafield 'sellist', 'link', 'select', ...
2614
     * @return  boolean         True is value is an empty value, not allowed for a mandatory field.
2615
     */
2616
    public static function isEmptyValue($v, string $type)
2617
    {
2618
        if ($v === null || $v === '') {
2619
            return true;
2620
        }
2621
        if (is_array($v) || $type == 'select') {
2622
            return empty($v);
2623
        }
2624
        if ($type == 'link') {
2625
            return ($v == '-1');
2626
        }
2627
        if ($type == 'sellist') {
2628
            return ($v == '0');
2629
        }
2630
        return (empty($v) && $v != '0');
2631
    }
2632
}
2633