Passed
Push — EXTRACT_CLASSES ( 231cec )
by Rafael
70:48
created

CommonObject::setStatut()   F

Complexity

Conditions 40
Paths > 20000

Size

Total Lines 118
Code Lines 73

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 40
eloc 73
nc 8036352
nop 5
dl 0
loc 118
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

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:

1
<?php
2
3
/* Copyright (C) 2006-2015  Laurent Destailleur         <[email protected]>
4
 * Copyright (C) 2005-2013  Regis Houssin               <[email protected]>
5
 * Copyright (C) 2010-2020  Juanjo Menent               <[email protected]>
6
 * Copyright (C) 2012-2013  Christophe Battarel         <[email protected]>
7
 * Copyright (C) 2011-2022  Philippe Grand              <[email protected]>
8
 * Copyright (C) 2012-2015  Marcos García               <[email protected]>
9
 * Copyright (C) 2012-2015  Raphaël Doursenaud          <[email protected]>
10
 * Copyright (C) 2012       Cedric Salvador             <[email protected]>
11
 * Copyright (C) 2015-2022  Alexandre Spangaro          <[email protected]>
12
 * Copyright (C) 2016       Bahfir abbes                <[email protected]>
13
 * Copyright (C) 2017       ATM Consulting              <[email protected]>
14
 * Copyright (C) 2017-2019  Nicolas ZABOURI             <[email protected]>
15
 * Copyright (C) 2017       Rui Strecht                 <[email protected]>
16
 * Copyright (C) 2018-2024  Frédéric France             <[email protected]>
17
 * Copyright (C) 2018       Josep Lluís Amador          <[email protected]>
18
 * Copyright (C) 2023       Gauthier VERDOL             <[email protected]>
19
 * Copyright (C) 2021       Grégory Blémand             <[email protected]>
20
 * Copyright (C) 2023       Lenin Rivas      	        <[email protected]>
21
 * Copyright (C) 2024		MDW							<[email protected]>
22
 * Copyright (C) 2024       Rafael San José             <[email protected]>
23
 *
24
 * This program is free software; you can redistribute it and/or modify
25
 * it under the terms of the GNU General Public License as published by
26
 * the Free Software Foundation; either version 3 of the License, or
27
 * (at your option) any later version.
28
 *
29
 * This program is distributed in the hope that it will be useful,
30
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
31
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
32
 * GNU General Public License for more details.
33
 *
34
 * You should have received a copy of the GNU General Public License
35
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
36
 */
37
38
namespace Dolibarr\Core\Base;
39
40
use DolEditor;
41
use Dolibarr\Code\MultiCurrency\Classes\MultiCurrency;
42
use Dolibarr\Core\Trait\DolDeprecationHandler;
43
use ExtraFields;
44
45
/**
46
 *  \file       htdocs/core/class/commonobject.class.php
47
 *  \ingroup    core
48
 *  \brief      File of parent class of all other business classes (invoices, contracts, proposals, orders, ...)
49
 */
50
51
require_once constant('DOL_DOCUMENT_ROOT') . '/core/class/doldeprecationhandler.class.php';
52
53
/**
54
 *  Parent class of all other business classes (invoices, contracts, proposals, orders, ...)
55
 *
56
 * @phan-forbid-undeclared-magic-properties
57
 */
58
abstract class CommonObject
59
{
60
    use DolDeprecationHandler;
0 ignored issues
show
introduced by
The trait Dolibarr\Core\Trait\DolDeprecationHandler requires some properties which are not provided by Dolibarr\Core\Base\CommonObject: $enableDeprecatedReporting, $enableDynamicProperties
Loading history...
61
62
    const TRIGGER_PREFIX = ''; // to be overridden in child class implementations, i.e. 'BILL', 'TASK', 'PROPAL', etc.
63
64
    /**
65
     * @var string      ID of module.
66
     */
67
    public $module;
68
69
    /**
70
     * @var DoliDB      Database handler (result of a new DoliDB)
0 ignored issues
show
Bug introduced by
The type Dolibarr\Core\Base\DoliDB was not found. Did you mean DoliDB? If so, make sure to prefix the type with \.
Loading history...
71
     */
72
    public $db;
73
74
    /**
75
     * @var int         The object identifier
76
     */
77
    public $id;
78
79
    /**
80
     * @var int         The environment ID when using a multicompany module
81
     */
82
    public $entity;
83
84
    /**
85
     * @var string      Error string
86
     * @see             $errors
87
     */
88
    public $error;
89
90
    /**
91
     * @var string      Error string that is hidden but can be used to store additional technical code
92
     */
93
    public $errorhidden;
94
95
    /**
96
     * @var string[]    Array of error strings
97
     */
98
    public $errors = array();
99
    /**
100
     * @var string      ID to identify managed object
101
     */
102
    public $element;
103
    /**
104
     * @var string|int  Field with ID of parent key if this field has a parent (a string). For example 'fk_product'.
105
     *                  ID of parent key itself (an int). For example in few classes like 'Comment', 'ActionComm' or 'AdvanceTargetingMailing'.
106
     */
107
    public $fk_element;
108
    /**
109
     * @var string      Name to use for 'features' parameter to check module permissions user->rights->feature with restrictedArea().
110
     *                  Undefined means same value than $element.
111
     *                  Can be use to force a check on another element (for example for class of a line, we mention here its parent element).
112
     */
113
    public $element_for_permission;
114
    /**
115
     * @var string      Name of table without prefix where object is stored
116
     */
117
    public $table_element;
118
    /**
119
     * @var string      Name of subtable line
120
     */
121
    public $table_element_line = '';
122
    /**
123
     * @var int<0,1>|string     Does this object support multicompany module ?
124
     *                          0=No test on entity, 1=Test with field entity, 'field@table'=Test with link by field@table (example 'fk_soc@societe')
125
     */
126
    public $ismultientitymanaged;
127
    /**
128
     * @var string      Key value used to track if data is coming from import wizard
129
     */
130
    public $import_key;
131
    /**
132
     * @var array<string,mixed> Contains data to manage extrafields
133
     */
134
    public $array_options = array();
135
    /**
136
     * @var array<string,array{type:string,label:string,enabled:int<0,2>|string,position:int,notnull?:int,visible:int,noteditable?:int,default?:string,index?:int,foreignkey?:string,searchall?:int,isameasure?:int,css?:string,csslist?:string,help?:string,showoncombobox?:int,disabled?:int,arrayofkeyval?:array<int,string>,comment?:string}>  Array with all fields and their property. Do not use it as a static var. It may be modified by constructor.
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string,array{type:...ring>,comment?:string}> at position 16 could not be parsed: Expected '}' at position 16, but found 'int'.
Loading history...
137
     */
138
    public $fields = array();
139
/**
140
     * @var array<string,array<string,string>>  Array to store alternative languages values of object
141
     *                                          Note: call fetchValuesForExtraLanguages() before using this
142
     */
143
    public $array_languages = null;
144
    /**
145
     * @var array<int,array{parentId:int,source:string,socid:int,id:int,nom:string,civility:string,lastname:string,firstname:string,email:string,login:string,photo:string,statuscontact:int,rowid:int,code:string,libelle:string,status:string,fk_c_type_contact:int}>     To store result of ->liste_contact()
146
     */
147
    public $contacts_ids; // Value is array() when load already tried
148
    /**
149
     * @var mixed       Array of linked objects, set and used when calling ->create() to be able to create links during the creation of object
150
     */
151
    public $linked_objects;
152
    /**
153
     * @var int[][]     Array of linked objects ids. Loaded by ->fetchObjectLinked
154
     */
155
    public $linkedObjectsIds;
156
    /**
157
     * @var mixed       Array of linked objects. Loaded by ->fetchObjectLinked
158
     */
159
    public $linkedObjects;
160
    /**
161
     * @var ?static     To store a cloned copy of the object before editing it (to keep track of its former properties)
162
     */
163
    public $oldcopy;
164
    /**
165
     * @var string      To store the old value of a modified reference
166
     */
167
    public $oldref;
168
    /**
169
     * @var integer     0=Default, 1=View may be restricted to sales representative only if no permission to see all or to company of external user if external user
170
     */
171
    public $restrictiononfksoc = 0;
172
    /**
173
     * @var array<string,mixed>     Can be used to pass information when only the object is provided to the method
174
     */
175
    public $context = array();
176
    /**
177
     * @var string  Properties set and used by Agenda trigger
178
     */
179
    public $actionmsg;
180
    /**
181
     * @var string  Properties set and used by Agenda trigger
182
     */
183
    public $actionmsg2;
184
185
186
    // The following vars are used by some objects only.
187
    // We keep these properties in CommonObject in order to provide common methods using them.
188
    /**
189
     * @var string          Contains canvas name if record is an alternative canvas record
190
     */
191
    public $canvas;
192
    /**
193
     * @var Project|null    The related project object
0 ignored issues
show
Bug introduced by
The type Dolibarr\Core\Base\Project was not found. Did you mean Project? If so, make sure to prefix the type with \.
Loading history...
194
     * @see fetch_projet()
195
     */
196
    public $project;
197
    /**
198
     * @var int             The related project ID
199
     * @see setProject(), project
200
     */
201
    public $fk_project;
202
    /**
203
     * @var int
204
     * @deprecated          Use $fk_project instead.
205
     * @see $fk_project
206
     */
207
    public $fk_projet;
208
    /**
209
     * @var Contact|null    A related contact object
0 ignored issues
show
Bug introduced by
The type Dolibarr\Core\Base\Contact was not found. Did you mean Contact? If so, make sure to prefix the type with \.
Loading history...
210
     * @see fetch_contact()
211
     */
212
    public $contact;
213
    /**
214
     * @var int             The related contact ID
215
     * @see fetch_contact()
216
     */
217
    public $contact_id;
218
    /**
219
     * @var Societe|null    A related thirdparty object
0 ignored issues
show
Bug introduced by
The type Dolibarr\Core\Base\Societe was not found. Did you mean Societe? If so, make sure to prefix the type with \.
Loading history...
220
     * @see fetch_thirdparty()
221
     */
222
    public $thirdparty;
223
    /**
224
     * @var User            A related user
0 ignored issues
show
Bug introduced by
The type Dolibarr\Core\Base\User was not found. Did you mean User? If so, make sure to prefix the type with \.
Loading history...
225
     * @see fetch_user()
226
     */
227
    public $user;
228
    /**
229
     * @var string      The type of originating object. Combined with $origin_id, it allows to reload $origin_object
230
     * @see fetch_origin()
231
     */
232
    public $origin_type;
233
    /**
234
     * @var int         The id of originating object. Combined with $origin_type, it allows to reload $origin_object
235
     * @see fetch_origin()
236
     */
237
    public $origin_id;
238
    /**
239
     * @var ?CommonObject   Origin object. This is set by fetch_origin() from this->origin_type and this->origin_id.
240
     */
241
    public $origin_object;
242
    /**
243
     * @var CommonObject|string|null    Sometimes the type of the originating object ('commande', 'facture', ...), sometimes the object (as with MouvementStock)
244
     * @deprecated                      Use $origin_type and $origin_id instead.
245
     * @see fetch_origin()
246
     */
247
    public $origin;
248
    /**
249
     * @var string      The object's reference
250
     */
251
    public $ref;
252
    /**
253
     * @var string      An external reference to the object
254
     */
255
    public $ref_ext;
256
    /**
257
     * @var string      The object's previous reference
258
     */
259
    public $ref_previous;
260
    /**
261
     * @var string      The object's next reference
262
     */
263
    public $ref_next;
264
    /**
265
     * @var string      Ref to store on object to save the new ref to use for example when making a validate() of an object
266
     */
267
    public $newref;
268
    /**
269
     * @var int|array<int, string>      The object's status. Use status instead.
270
     * @deprecated  Use $status instead.
271
     * @see $status
272
     * @see setStatut(), $status
273
     */
274
    public $statut;
275
    /**
276
     * @var int|array<int, string>   The object's status (an int).
277
     *                                      Or an array listing all the potential status of the object:
278
     *                                      array: int of the status => translated label of the status
279
     *                                      See for example the Account class.
280
     * @see setStatut()
281
     */
282
    public $status;
283
    /**
284
     * @var string      Country name
285
     * @see getFullAddress()
286
     */
287
    public $country;
288
    /**
289
     * @var int         Country ID
290
     * @see getFullAddress(), country
291
     */
292
    public $country_id;
293
    /**
294
     * @var string      ISO country code on 2 chars
295
     * @see getFullAddress(), isInEEC(), country
296
     */
297
    public $country_code;
298
    /**
299
     * @var string      State name
300
     * @see getFullAddress()
301
     */
302
    public $state;
303
    /**
304
     * @var int         State ID
305
     * @see getFullAddress(), state
306
     */
307
    public $state_id;
308
    /**
309
     * @var int         State ID
310
     * @deprecated  Use $state_id. We can remove this property when the field 'fk_departement' have been renamed into 'state_id' in all tables
311
     */
312
    public $fk_departement;
313
    /**
314
     * @var string      State code
315
     * @see getFullAddress(), $state
316
     */
317
    public $state_code;
318
    /**
319
     * @var int         Region ID
320
     * @see getFullAddress(), $region_code, $region
321
     */
322
    public $region_id;
323
    /**
324
     * @var string      Region code
325
     * @see getFullAddress(), $region_id, $region
326
     */
327
    public $region_code;
328
    /**
329
     * @var string      Region name
330
     * @see getFullAddress(), $region_id, $region_code
331
     */
332
    public $region;
333
    /**
334
     * @var int         Barcode type
335
     * @see fetch_barcode()
336
     */
337
    public $barcode_type;
338
    /**
339
     * @var string      Code of the barcode type
340
     * @see fetch_barcode(), barcode_type
341
     */
342
    public $barcode_type_code;
343
    /**
344
     * @var string      Label of the barcode type
345
     * @see fetch_barcode(), barcode_type
346
     */
347
    public $barcode_type_label;
348
    /**
349
     * @var string
350
     * @see fetch_barcode(), barcode_type
351
     */
352
    public $barcode_type_coder;
353
    /**
354
     * @var int         Payment method ID (cheque, cash, ...)
355
     * @see setPaymentMethods()
356
     */
357
    public $mode_reglement_id;
358
    /**
359
     * @var int         Payment terms ID
360
     * @see setPaymentTerms()
361
     */
362
    public $cond_reglement_id;
363
    /**
364
     * @var int         Demand reason ID
365
     */
366
    public $demand_reason_id;
367
    /**
368
     * @var int         Transport mode ID (For module intracomm report)
369
     * @see setTransportMode()
370
     */
371
    public $transport_mode_id;
372
    /**
373
     * @var int         Delivery address ID
374
     * @see setDeliveryAddress()
375
     * @deprecated
376
     */
377
    public $fk_delivery_address;
378
    /**
379
     * @var int         Shipping method ID
380
     * @see setShippingMethod()
381
     */
382
    public $shipping_method_id;
383
    /**
384
     * @var string      Shipping method label
385
     * @see setShippingMethod()
386
     */
387
    public $shipping_method;
388
    /**
389
     * @var int ID
390
     */
391
    public $fk_multicurrency;
392
    /**
393
     * @var string|string[]             Multicurrency code
394
     *                                  Or, just for the Paiement object, an array: invoice ID => currency code for that invoice.
395
     */
396
    public $multicurrency_code;
397
    /**
398
     * @var float|float[]               Multicurrency rate ("tx" = "taux" in French)
399
     *                                  Or, just for the Paiement object, an array: invoice ID => currency rate for that invoice.
400
     */
401
    public $multicurrency_tx;
402
    /**
403
     * @var float       Multicurrency total amount excluding taxes (HT = "Hors Taxe" in French)
404
     */
405
    public $multicurrency_total_ht;
406
    /**
407
     * @var float       Multicurrency total VAT amount (TVA = "Taxe sur la Valeur Ajoutée" in French)
408
     */
409
    public $multicurrency_total_tva;  // Private to call DolDeprecationHandler
410
        /**
411
     * @var float       Multicurrency total amount including taxes (TTC = "Toutes Taxes Comprises" in French)
412
     */
413
    public $multicurrency_total_ttc;  // Internal value for deprecation
414
/**
415
     * @var float Multicurrency total localta1
416
     */
417
    public $multicurrency_total_localtax1;
418
/**
419
     * @var float Multicurrency total localtax2
420
     */
421
    public $multicurrency_total_localtax2;
422
    /**
423
     * @var string
424
     * @see SetDocModel()
425
     */
426
    public $model_pdf;
427
428
    // Multicurrency
429
    /**
430
     * @var string
431
     * Contains relative path of last generated main file
432
     */
433
    public $last_main_doc;
434
    /**
435
     * @var int         Bank account ID sometimes, ID of record into llx_bank sometimes
436
     * @deprecated
437
     * @see $fk_account
438
     */
439
    public $fk_bank;
440
    /**
441
     * @var int         Bank account ID
442
     * @see SetBankAccount()
443
     */
444
    public $fk_account;
445
    /**
446
     * @var string      Public note
447
     * @see update_note()
448
     */
449
    public $note_public;
450
    /**
451
     * @var string      Private note
452
     * @see update_note()
453
     */
454
    public $note_private;
455
    /**
456
     * @var string
457
     * @deprecated Use $note_private instead.
458
     * @see $note_private
459
     */
460
    public $note;
461
    /**
462
     * @var float       Total amount excluding taxes (HT = "Hors Taxe" in French)
463
     * @see update_price()
464
     */
465
    public $total_ht;  // not in database
466
    /**
467
     * @var float       Total VAT amount (TVA = "Taxe sur la Valeur Ajoutée" in French)
468
     * @see update_price()
469
     */
470
    public $total_tva;  // not in database
471
    /**
472
     * @var float       Total local tax 1 amount
473
     * @see update_price()
474
     */
475
    public $total_localtax1;
476
    /**
477
     * @var float       Total local tax 2 amount
478
     * @see update_price()
479
     */
480
    public $total_localtax2;
481
    /**
482
     * @var float       Total amount including taxes (TTC = "Toutes Taxes Comprises" in French)
483
     * @see update_price()
484
     */
485
    public $total_ttc;
486
    /**
487
     * @var CommonObjectLine[]|CommonObject[]|stdClass[]
488
     */
489
    public $lines;
490
    /**
491
     * @var string  Action type code to use to record auto event in agenda. For example 'AC_OTH_AUTO'
492
     */
493
    public $actiontypecode;
494
    /**
495
     * @var mixed       Comments
496
     * @see fetchComments()
497
     */
498
    public $comments = array();
499
    /**
500
     * @var string      The name
501
     */
502
    public $name;
503
    /**
504
     * @var string      The lastname
505
     */
506
    public $lastname;
507
    /**
508
     * @var string      The firstname
509
     */
510
    public $firstname;
511
    /**
512
     * @var string      The civility code, not an integer
513
     */
514
    public $civility_id;
515
    /**
516
     * @var integer|string|null     Object creation date
517
     */
518
    public $date_creation;
519
    /**
520
     * @var integer|string|null     Object last validation date
521
     */
522
    public $date_validation;
523
    /**
524
     * @var integer|string|null     Object last modification date
525
     */
526
    public $date_modification;
527
    /**
528
     * Update timestamp record (tms)
529
     * @var integer
530
     * @deprecated                  Use $date_modification
531
     */
532
    public $tms;
533
    /**
534
     * @var integer|string|null     Object closing date
535
     */
536
    public $date_cloture;
537
    /**
538
     * @var User        User author/creation
539
     * @deprecated      Store only id in user_creation_id
540
     */
541
    public $user_author;
542
    /**
543
     * @var User        User author/creation
544
     * @deprecated
545
     */
546
    public $user_creation;
547
    /**
548
     * @var int         User id author/creation
549
     */
550
    public $user_creation_id;
551
    /**
552
     * @var User        User of validation
553
     * @deprecated
554
     */
555
    public $user_valid;
556
557
    // Dates
558
    /**
559
     * @var User        User of validation
560
     * @deprecated
561
     */
562
    public $user_validation;
563
    /**
564
     * @var int|null        User id of validation
565
     */
566
    public $user_validation_id;
567
    /**
568
     * @var int         User id closing object
569
     */
570
    public $user_closing_id;
571
    /**
572
     * @var User    User last modifier
573
     * @deprecated
574
     */
575
    public $user_modification;
576
    /**
577
     * @var int         User ID who last modified the object
578
     */
579
    public $user_modification_id;
580
    /**
581
     * @var int ID
582
     * @deprecated  Use $user_creation_id
583
     */
584
    public $fk_user_creat;
585
    /**
586
     * @var int ID
587
     * @deprecated  Use $user_modification_id
588
     */
589
    public $fk_user_modif;
590
    /**
591
     * @var string XX
592
     */
593
    public $next_prev_filter;
594
    /**
595
     * @var int<0,1> 1 if object is specimen
596
     */
597
    public $specimen = 0;
598
    /**
599
     * @var int[]       Id of contacts to send objects (mails, etc.)
600
     */
601
    public $sendtoid;
602
    /**
603
     * @var float       Amount already paid from getSommePaiement() (used to show correct status)
604
     */
605
    public $totalpaid;
606
    /**
607
     * @var array<int,string>       Array with labels of status
608
     */
609
    public $labelStatus = array();
610
    /**
611
     * @var array<int,string>   Array with short labels of status
612
     */
613
    public $labelStatusShort = array();
614
    /**
615
     * @var array<string,int|string>    Array to store lists of tpl
616
     */
617
    public $tpl;
618
    /**
619
     * @var int         show photo on popup
620
     */
621
    public $showphoto_on_popup;
622
    /**
623
     * @var array{actionscomm?:int,banklines?:int,cheques?:int,contacts?:int,contracts?:int,customers?:int,dolresource?:int,donations?:int,expensereports?:int,holidays?:int,interventions?:int,invoices?:int,members?:int,orders?:int,products?:int,projects?:int,proposals?:int,prospects?:int,services?:int,supplier_invoices?:int,supplier_orders?:int,supplier_proposals?:int,suppliers?:int,tasks?:int,ticket?:int,users?:int}        nb used in load_stateboard
624
     */
625
    public $nb = array();
626
    /**
627
     * @var int         used for the return of show_photos()
628
     */
629
    public $nbphoto;
630
    /**
631
     * @var string      output content. Used topropagate information by cron jobs.
632
     */
633
    public $output;
634
    /**
635
     * @var array|string    extra parameters. Try to store here the array of parameters. Old code is sometimes storing a string.
636
     */
637
    public $extraparams = array();
638
    /**
639
     * @var Product     Populated by fetch_product()
0 ignored issues
show
Bug introduced by
The type Dolibarr\Core\Base\Product was not found. Did you mean Product? If so, make sure to prefix the type with \.
Loading history...
640
     */
641
    public $product;
642
    /**
643
     * @var int         Populated by setPaymentTerms()
644
     */
645
    public $cond_reglement_supplier_id;
646
    /**
647
     * @var float       Deposit percent for payment terms.
648
     *                  Populated by setPaymentTerms().
649
     * @see setPaymentTerms()
650
     */
651
    public $deposit_percent;
652
    /**
653
     * @var int         Populated by setRetainedWarrantyPaymentTerms()
654
     */
655
    public $retained_warranty_fk_cond_reglement;
656
    /**
657
     * @var int         Populated by setWarehouse()
658
     */
659
    public $warehouse_id;
660
    /**
661
     * @var int<0,1>    Does object support extrafields ? 0=No, 1=Yes
662
     */
663
    public $isextrafieldmanaged = 0;
664
    /**
665
     * @var string      Column name of the ref field.
666
     */
667
    protected $table_ref_field = '';
668
/**
669
     * @var int|string Internal to detect deprecated access
670
     */
671
    protected $depr_cond_reglement;
672
    /**
673
     * @var string[]|array<string,string[]|array{parent:string,parentkey:string}>   List of child tables. To test if we can delete object.
674
     */
675
    protected $childtables = array();
676
    /**
677
     * @var string[]    List of child tables. To know object to delete on cascade.
678
     *               If name is like '@ClassName:FilePathClass:ParentFkFieldName', it will
679
     *               call method deleteByParentField(parentId, ParentFkFieldName) to fetch and delete child object.
680
     */
681
    protected $childtablesoncascade = array();
682
    /**
683
     * @var array<string,string>    To store error results of ->validateField()
684
     */
685
    private $validateFieldsErrors = array();
686
    /**
687
     * @var boolean[]   Array of boolean with object id as key and value as true if linkedObjects full loaded for object id. Loaded by ->fetchObjectLinked. Important for pdf generation time reduction.
688
     */
689
    private $linkedObjectsFullLoaded = array();
690
    /**
691
     * @var ?Project        The related project object
692
     * @deprecated  Use $project instead.
693
     * @see $project
694
     */
695
    private $projet;
696
    /**
697
     * TODO Remove this. Has been replaced with ->origin_object.
698
     * This is set by fetch_origin() from this->origin and this->origin_id
699
     *
700
     * @var CommonObject
701
     * @deprecated Use $origin_object instead.
702
     * @see $origin_object
703
     */
704
    private $expedition;
705
    /**
706
     * @var CommonObject
707
     * @deprecated Use $origin_object instead.
708
     * @see $origin_object
709
     */
710
    private $livraison;
711
    /**
712
     * @var CommonObject
713
     * @deprecated Use $origin_object instead.
714
     * @see $origin_object
715
     */
716
    private $commandeFournisseur;
717
/**
718
     * @var int|string      Payment terms ID
719
     * @deprecated  Use $cond_reglement_id instead - Kept for compatibility
720
     * @see $cond_reglement_id
721
     *
722
     * Note: cond_reglement can not be aliased to cond_reglement!!!
723
     */
724
    private $cond_reglement;
725
    /**
726
     * @var int|string|null
727
     * @deprecated Use $date_modification instead.
728
     * @see $date_modification
729
     */
730
    private $date_update;
731
    /**
732
     * @var float       Amount already paid from getSommePaiement() (used to show correct status)
733
     * @deprecated      Use $totalpaid instead
734
     * @see $totalpaid
735
     */
736
    private $alreadypaid;
737
738
739
    // No constructor as it is an abstract class
740
741
    /**
742
     * Check if an object id or ref exists
743
     * If you don't need or want to instantiate the object and just need to know if the object exists, use this method instead of fetch
744
     *
745
     * @param string $element String of element ('product', 'facture', ...)
746
     * @param int $id Id of object
747
     * @param string $ref Ref of object to check
748
     * @param string $ref_ext Ref ext of object to check
749
     * @return int                 Return integer <0 if KO, 0 if OK but not found, >0 if OK and exists
750
     */
751
    public static function isExistingObject($element, $id, $ref = '', $ref_ext = '')
752
    {
753
        global $db, $conf;
754
755
        $sql = "SELECT rowid, ref, ref_ext";
756
        $sql .= " FROM " . $db->prefix() . $element;
757
        $sql .= " WHERE entity IN (" . getEntity($element) . ")";
758
759
        if ($id > 0) {
760
            $sql .= " AND rowid = " . ((int)$id);
761
        } elseif ($ref) {
762
            $sql .= " AND ref = '" . $db->escape($ref) . "'";
763
        } elseif ($ref_ext) {
764
            $sql .= " AND ref_ext = '" . $db->escape($ref_ext) . "'";
765
        } else {
766
            $error = 'ErrorWrongParameters';
767
            dol_syslog(get_class() . "::isExistingObject " . $error, LOG_ERR);
768
            return -1;
769
        }
770
        if ($ref || $ref_ext) {     // Because the same ref can exists in 2 different entities, we force the current one in priority
771
            $sql .= " AND entity = " . ((int)$conf->entity);
772
        }
773
774
        dol_syslog(get_class() . "::isExistingObject", LOG_DEBUG);
775
        $resql = $db->query($sql);
776
        if ($resql) {
777
            $num = $db->num_rows($resql);
778
            if ($num > 0) {
779
                return 1;
780
            } else {
781
                return 0;
782
            }
783
        }
784
        return -1;
785
    }
786
787
    /**
788
     * Function used to get an array with all items linked to an object id in association table
789
     *
790
     * @param int $fk_object_where id of object we need to get linked items
791
     * @param string $field_select name of field we need to get a list
792
     * @param string $field_where name of field of object we need to get linked items
793
     * @param string $table_element name of association table
794
     * @return  array|int                       Array of record, -1 if empty
795
     */
796
    public static function getAllItemsLinkedByObjectID($fk_object_where, $field_select, $field_where, $table_element)
797
    {
798
        if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
799
            return -1;
800
        }
801
        if (!preg_match('/^[_a-zA-Z0-9]+$/', $field_select)) {
802
            dol_syslog('Invalid value $field_select for parameter ' . $field_select . ' in call to getAllItemsLinkedByObjectID(). Must be a single field name.', LOG_ERR);
803
        }
804
805
        global $db;
806
807
        $sql = "SELECT " . $field_select . " FROM " . $db->prefix() . $table_element . " WHERE " . $field_where . " = " . ((int)$fk_object_where);
808
        $resql = $db->query($sql);
809
810
        $TRes = array();
811
        if (!empty($resql)) {
812
            while ($res = $db->fetch_object($resql)) {
813
                $TRes[] = $res->{$field_select};
814
            }
815
        }
816
817
        return $TRes;
818
    }
819
820
    /**
821
     * Count items linked to an object id in association table
822
     *
823
     * @param int $fk_object_where id of object we need to get linked items
824
     * @param string $field_where name of field of object we need to get linked items
825
     * @param string $table_element name of association table
826
     * @return  array|int                       Array of record, -1 if empty
827
     */
828
    public static function getCountOfItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
829
    {
830
        if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
831
            return -1;
832
        }
833
834
        global $db;
835
836
        $sql = "SELECT COUNT(*) as nb FROM " . $db->prefix() . $table_element . " WHERE " . $field_where . " = " . ((int)$fk_object_where);
837
        $resql = $db->query($sql);
838
        $n = 0;
839
        if ($resql) {
840
            $res = $db->fetch_object($resql);
841
            if ($res) {
842
                $n = $res->nb;
843
            }
844
        }
845
846
        return $n;
847
    }
848
849
    /**
850
     * Function used to remove all items linked to an object id in association table
851
     *
852
     * @param int $fk_object_where id of object we need to remove linked items
853
     * @param string $field_where name of field of object we need to delete linked items
854
     * @param string $table_element name of association table
855
     * @return  int                             Return integer <0 if KO, 0 if nothing done, >0 if OK and something done
856
     */
857
    public static function deleteAllItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
858
    {
859
        if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
860
            return -1;
861
        }
862
863
        global $db;
864
865
        $sql = "DELETE FROM " . $db->prefix() . $table_element . " WHERE " . $field_where . " = " . ((int)$fk_object_where);
866
        $resql = $db->query($sql);
867
868
        if (empty($resql)) {
869
            return 0;
870
        }
871
872
        return 1;
873
    }
874
875
    /**
876
     * Function used to replace a thirdparty id with another one.
877
     * This function is meant to be called from replaceThirdparty with the appropriate tables
878
     * Column name fk_soc MUST exist.
879
     *
880
     * @param DoliDB $dbs Database handler
881
     * @param int $origin_id Old thirdparty id (the thirdparty to delete)
882
     * @param int $dest_id New thirdparty id (the thirdparty that will received element of the other)
883
     * @param string[] $tables Tables that need to be changed
884
     * @param int<0,1> $ignoreerrors Ignore errors. Return true even if errors. We need this when replacement can fails like for categories (categorie of old thirdparty may already exists on new one)
885
     * @return bool                     True if success, False if error
886
     */
887
    public static function commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
888
    {
889
        foreach ($tables as $table) {
890
            $sql = 'UPDATE ' . $dbs->prefix() . $table . ' SET fk_soc = ' . ((int)$dest_id) . ' WHERE fk_soc = ' . ((int)$origin_id);
891
892
            if (!$dbs->query($sql)) {
893
                if ($ignoreerrors) {
894
                    return true; // FIXME Not enough. If there is A-B on the kept thirdparty and B-C on the old one, we must get A-B-C after merge. Not A-B.
895
                }
896
                //$this->errors = $db->lasterror();
897
                return false;
898
            }
899
        }
900
901
        return true;
902
    }
903
904
    /**
905
     * Function used to replace a product id with another one.
906
     * This function is meant to be called from replaceProduct with the appropriate tables
907
     * Column name fk_product MUST be used to identify products
908
     *
909
     * @param DoliDB $dbs Database handler
910
     * @param int $origin_id Old product id (the product to delete)
911
     * @param int $dest_id New product id (the product that will received element of the other)
912
     * @param string[] $tables Tables that need to be changed
913
     * @param int<0,1> $ignoreerrors Ignore errors. Return true even if errors. We need this when replacement can fails like for categories (categorie of old product may already exists on new one)
914
     * @return bool                         True if success, False if error
915
     */
916
    public static function commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
917
    {
918
        foreach ($tables as $table) {
919
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . $table . ' SET fk_product = ' . ((int)$dest_id) . ' WHERE fk_product = ' . ((int)$origin_id);
920
921
            if (!$dbs->query($sql)) {
922
                if ($ignoreerrors) {
923
                    return true; // TODO Not enough. If there is A-B on kept product and B-C on old one, we must get A-B-C after merge. Not A-B.
924
                }
925
                //$this->errors = $db->lasterror();
926
                return false;
927
            }
928
        }
929
930
        return true;
931
    }
932
933
    /**
934
     * isEmpty We consider CommonObject isEmpty if this->id is empty
935
     *
936
     * @return bool
937
     */
938
    public function isEmpty()
939
    {
940
        return (empty($this->id));
941
    }
942
943
    /**
944
     * setErrorsFromObject
945
     *
946
     * @param CommonObject $object commonobject
947
     * @return void
948
     */
949
    public function setErrorsFromObject($object)
950
    {
951
        if (!empty($object->error)) {
952
            $this->error = $object->error;
953
        }
954
        if (!empty($object->errors)) {
955
            $this->errors = array_merge($this->errors, $object->errors);
956
        }
957
    }
958
959
    /**
960
     * getTooltipContent
961
     *
962
     * @param array<string,mixed> $params params
963
     * @return string
964
     * @since v18
965
     */
966
    public function getTooltipContent($params)
967
    {
968
        global $action, $extrafields, $langs, $hookmanager;
969
970
        // If there is too much extrafields, we do not include them into tooltip
971
        $MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP = getDolGlobalInt('MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP', 3);
972
973
        $data = $this->getTooltipContentArray($params);
974
        $count = 0;
975
976
        // Add extrafields
977
        if (!empty($extrafields->attributes[$this->table_element]['label'])) {
978
            $data['opendivextra'] = '<div class="centpercent wordbreak divtooltipextra">';
979
            foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
980
                if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
981
                    continue;
982
                }
983
                if ($count >= abs($MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP)) {
984
                    $data['more_extrafields'] = '<br>...';
985
                    break;
986
                }
987
                $enabled = 1;
988
                if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
989
                    $enabled = (int)dol_eval($extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
990
                }
991
                if ($enabled && isset($extrafields->attributes[$this->table_element]['list'][$key])) {
992
                    $enabled = (int)dol_eval($extrafields->attributes[$this->table_element]['list'][$key], 1, 1, '2');
993
                }
994
                $perms = 1;
995
                if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
996
                    $perms = (int)dol_eval($extrafields->attributes[$this->table_element]['perms'][$key], 1, 1, '2');
997
                }
998
                if (empty($enabled)) {
999
                    continue; // 0 = Never visible field
1000
                }
1001
                if (abs($enabled) != 1 && abs($enabled) != 3 && abs($enabled) != 5 && abs($enabled) != 4) {
1002
                    continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list <> 4 = not visible at the creation
1003
                }
1004
                if (empty($perms)) {
1005
                    continue; // 0 = Not visible
1006
                }
1007
                if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
1008
                    $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
1009
                }
1010
                $labelextra = $langs->trans((string)$extrafields->attributes[$this->table_element]['label'][$key]);
1011
                if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
1012
                    $data[$key] = '<br><b><u>' . $labelextra . '</u></b>';
1013
                } else {
1014
                    $value = (empty($this->array_options['options_' . $key]) ? '' : $this->array_options['options_' . $key]);
1015
                    $data[$key] = '<br><b>' . $labelextra . ':</b> ' . $extrafields->showOutputField($key, $value, '', $this->table_element);
1016
                    $count++;
1017
                }
1018
            }
1019
            $data['closedivextra'] = '</div>';
1020
        }
1021
1022
        $hookmanager->initHooks(array($this->element . 'dao'));
1023
        $parameters = array(
1024
            'tooltipcontentarray' => &$data,
1025
            'params' => $params,
1026
        );
1027
        // Note that $action and $object may have been modified by some hooks
1028
        $hookmanager->executeHooks('getTooltipContent', $parameters, $this, $action);
1029
1030
        //var_dump($data);
1031
        $label = implode($data);
1032
1033
        return $label;
1034
    }
1035
1036
    /**
1037
     * Return array of data to show into a tooltip. This method must be implemented in each object class.
1038
     *
1039
     * @param array<string,mixed> $params params to construct tooltip data
1040
     * @return array<string,string> Data to show in tooltip
1041
     * @since v18
1042
     */
1043
    public function getTooltipContentArray($params)
1044
    {
1045
        return [];
1046
    }
1047
1048
    /**
1049
     * Return HTML string to show a field into a page
1050
     * Code very similar with showOutputField of extra fields
1051
     *
1052
     * @param array{type:string,label:string,enabled:int<0,2>|string,position:int,notnull?:int,visible:int,noteditable?:int,default?:string,index?:int,foreignkey?:string,searchall?:int,isameasure?:int,css?:string,csslist?:string,help?:string,showoncombobox?:int,disabled?:int,arrayofkeyval?:array<int,string>,comment?:string} $val Array of properties of field to show
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{type:string,label:...tring>,comment?:string} at position 12 could not be parsed: Expected '}' at position 12, but found 'int'.
Loading history...
1053
     * @param string $key Key of attribute
1054
     * @param string $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)
1055
     * @param string $moreparam To add more parameters on html tag
1056
     * @param string $keysuffix Prefix string to add into name and id of field (can be used to avoid duplicate names)
1057
     * @param string $keyprefix Suffix string to add into name and id of field (can be used to avoid duplicate names)
1058
     * @param mixed $morecss Value for CSS to use (Old usage: May also be a numeric to define a size).
1059
     * @return string
1060
     */
1061
    public function showOutputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = '')
1062
    {
1063
        global $conf, $langs, $form;
1064
1065
        if (!is_object($form)) {
1066
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/class/html.form.class.php';
1067
            $form = new Form($this->db);
0 ignored issues
show
Bug introduced by
The type Dolibarr\Core\Base\Form was not found. Did you mean Form? If so, make sure to prefix the type with \.
Loading history...
1068
        }
1069
1070
        //$label = empty($val['label']) ? '' : $val['label'];
1071
        $type = empty($val['type']) ? '' : $val['type'];
1072
        $size = empty($val['css']) ? '' : $val['css'];
1073
        $reg = array();
1074
1075
        // Convert var to be able to share same code than showOutputField of extrafields
1076
        if (preg_match('/varchar\((\d+)\)/', $type, $reg)) {
1077
            $type = 'varchar'; // convert varchar(xx) int varchar
1078
            $size = $reg[1];
1079
        } elseif (preg_match('/varchar/', $type)) {
1080
            $type = 'varchar'; // convert varchar(xx) int varchar
1081
        }
1082
        if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
1083
            if (empty($this->fields[$key]['multiinput'])) {
1084
                $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
1085
            }
1086
        }
1087
        if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
1088
            $type = 'link';
1089
        }
1090
1091
        $default = empty($val['default']) ? '' : $val['default'];
1092
        $computed = empty($val['computed']) ? '' : $val['computed'];
1093
        $unique = empty($val['unique']) ? '' : $val['unique'];
1094
        $required = empty($val['required']) ? '' : $val['required'];
1095
        $param = array();
1096
        $param['options'] = array();
1097
1098
        if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
1099
            $param['options'] = $val['arrayofkeyval'];
1100
        }
1101
        if (preg_match('/^integer:([^:]*):([^:]*)/i', $val['type'], $reg)) {    // ex: integer:User:user/class/user.class.php
1102
            $type = 'link';
1103
            $stringforoptions = $reg[1] . ':' . $reg[2];
1104
            // Special case: Force addition of getnomurlparam1 to -1 for users
1105
            if ($reg[1] == 'User') {
1106
                $stringforoptions .= ':#getnomurlparam1=-1';
1107
            }
1108
            $param['options'] = array($stringforoptions => $stringforoptions);
1109
        } elseif (preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
1110
            $param['options'] = array($reg[1] . ':' . $reg[2] . ':' . $reg[3] . ':' . $reg[4] => 'N');
1111
            $type = 'sellist';
1112
        } elseif (preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
1113
            $param['options'] = array($reg[1] . ':' . $reg[2] . ':' . $reg[3] => 'N');
1114
            $type = 'sellist';
1115
        } elseif (preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
1116
            $param['options'] = array($reg[1] . ':' . $reg[2] => 'N');
1117
            $type = 'sellist';
1118
        } elseif (preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
1119
            $param['options'] = array($reg[1] => 'N');
1120
            $type = 'chkbxlst';
1121
        }
1122
1123
        $langfile = empty($val['langfile']) ? '' : $val['langfile'];
1124
        $list = (empty($val['list']) ? '' : $val['list']);
1125
        $help = (empty($val['help']) ? '' : $val['help']);
1126
        $hidden = (($val['visible'] == 0) ? 1 : 0); // If zero, 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)
1127
1128
        if ($hidden) {
1129
            return '';
1130
        }
1131
1132
        // If field is a computed field, value must become result of compute
1133
        if ($computed) {
1134
            // Make the eval of compute string
1135
            //var_dump($computed);
1136
            $value = dol_eval($computed, 1, 0, '2');
1137
        }
1138
1139
        if (empty($morecss)) {
1140
            if ($type == 'date') {
1141
                $morecss = 'minwidth100imp';
1142
            } elseif ($type == 'datetime' || $type == 'timestamp') {
1143
                $morecss = 'minwidth200imp';
1144
            } elseif (in_array($type, array('int', 'double', 'price'))) {
1145
                $morecss = 'maxwidth75';
1146
            } elseif ($type == 'url') {
1147
                $morecss = 'minwidth400';
1148
            } elseif ($type == 'boolean') {
1149
                $morecss = '';
1150
            } else {
1151
                if (is_numeric($size) && round((float)$size) < 12) {
1152
                    $morecss = 'minwidth100';
1153
                } elseif (is_numeric($size) && round((float)$size) <= 48) {
1154
                    $morecss = 'minwidth200';
1155
                } else {
1156
                    $morecss = 'minwidth400';
1157
                }
1158
            }
1159
        }
1160
1161
        // Format output value differently according to properties of field
1162
        if (in_array($key, array('rowid', 'ref')) && method_exists($this, 'getNomUrl')) {
1163
            if ($key != 'rowid' || empty($this->fields['ref'])) {   // If we want ref field or if we want ID and there is no ref field, we show the link.
1164
                $value = $this->getNomUrl(1, '', 0, '', 1);
0 ignored issues
show
Bug introduced by
The method getNomUrl() does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

1164
                /** @scrutinizer ignore-call */ 
1165
                $value = $this->getNomUrl(1, '', 0, '', 1);
Loading history...
1165
            }
1166
        } elseif ($key == 'status' && method_exists($this, 'getLibStatut')) {
1167
            $value = $this->getLibStatut(3);
0 ignored issues
show
Bug introduced by
The method getLibStatut() does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

1167
            /** @scrutinizer ignore-call */ 
1168
            $value = $this->getLibStatut(3);
Loading history...
1168
        } elseif ($type == 'date') {
1169
            if (!empty($value)) {
1170
                $value = dol_print_date($value, 'day'); // We suppose dates without time are always gmt (storage of course + output)
1171
            } else {
1172
                $value = '';
1173
            }
1174
        } elseif ($type == 'datetime' || $type == 'timestamp') {
1175
            if (!empty($value)) {
1176
                $value = dol_print_date($value, 'dayhour', 'tzuserrel');
1177
            } else {
1178
                $value = '';
1179
            }
1180
        } elseif ($type == 'duration') {
1181
            include_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php';
1182
            if (!is_null($value) && $value !== '') {
1183
                $value = convertSecondToTime($value, 'allhourmin');
1184
            }
1185
        } elseif ($type == 'double' || $type == 'real') {
1186
            if (!is_null($value) && $value !== '') {
1187
                $value = price($value);
1188
            }
1189
        } elseif ($type == 'boolean') {
1190
            $checked = '';
1191
            if (!empty($value)) {
1192
                $checked = ' checked ';
1193
            }
1194
            if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2) {
1195
                $value = '<input type="checkbox" ' . $checked . ' ' . ($moreparam ? $moreparam : '') . ' readonly disabled>';
1196
            } else {
1197
                $value = yn($value ? 1 : 0);
1198
            }
1199
        } elseif ($type == 'mail' || $type == 'email') {
1200
            $value = dol_print_email($value, 0, 0, 0, 64, 1, 1);
1201
        } elseif ($type == 'url') {
1202
            $value = dol_print_url($value, '_blank', 32, 1);
1203
        } elseif ($type == 'phone') {
1204
            $value = dol_print_phone($value, '', 0, 0, '', '&nbsp;', 'phone');
1205
        } elseif ($type == 'ip') {
1206
            $value = dol_print_ip($value, 0);
1207
        } elseif ($type == 'price') {
1208
            if (!is_null($value) && $value !== '') {
1209
                $value = price($value, 0, $langs, 0, 0, -1, $conf->currency);
1210
            }
1211
        } elseif ($type == 'select') {
1212
            $value = isset($param['options'][(string)$value]) ? $param['options'][(string)$value] : '';
1213
            if (strpos($value, "|") !== false) {
1214
                $value = $langs->trans(explode('|', $value)[0]);
1215
            } elseif (!is_numeric($value)) {
1216
                $value = $langs->trans($value);
1217
            }
1218
        } elseif ($type == 'sellist') {
1219
            $param_list = array_keys($param['options']);
1220
            $InfoFieldList = explode(":", $param_list[0]);
1221
1222
            $selectkey = "rowid";
1223
            $keyList = 'rowid';
1224
1225
            if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
1226
                $selectkey = $InfoFieldList[2];
1227
                $keyList = $InfoFieldList[2] . ' as rowid';
1228
            }
1229
1230
            $fields_label = explode('|', $InfoFieldList[1]);
1231
            if (is_array($fields_label)) {
1232
                $keyList .= ', ';
1233
                $keyList .= implode(', ', $fields_label);
1234
            }
1235
1236
            $filter_categorie = false;
1237
            if (count($InfoFieldList) > 5) {
1238
                if ($InfoFieldList[0] == 'categorie') {
1239
                    $filter_categorie = true;
1240
                }
1241
            }
1242
1243
            $sql = "SELECT " . $keyList;
1244
            $sql .= ' FROM ' . $this->db->prefix() . $InfoFieldList[0];
1245
            if (strpos($InfoFieldList[4], 'extra') !== false) {
1246
                $sql .= ' as main';
1247
            }
1248
            if ($selectkey == 'rowid' && empty($value)) {
1249
                $sql .= " WHERE " . $selectkey . " = 0";
1250
            } elseif ($selectkey == 'rowid') {
1251
                $sql .= " WHERE " . $selectkey . " = " . ((int)$value);
1252
            } else {
1253
                $sql .= " WHERE " . $selectkey . " = '" . $this->db->escape($value) . "'";
1254
            }
1255
1256
            //$sql.= ' AND entity = '.$conf->entity;
1257
1258
            dol_syslog(get_class($this) . ':showOutputField:$type=sellist', LOG_DEBUG);
1259
            $resql = $this->db->query($sql);
1260
            if ($resql) {
1261
                if (!$filter_categorie) {
1262
                    $value = ''; // value was used, so now we reste it to use it to build final output
1263
                    $numrows = $this->db->num_rows($resql);
1264
                    if ($numrows) {
1265
                        $obj = $this->db->fetch_object($resql);
1266
1267
                        // Several field into label (eq table:code|libelle:rowid)
1268
                        $fields_label = explode('|', $InfoFieldList[1]);
1269
1270
                        if (is_array($fields_label) && count($fields_label) > 1) {
1271
                            foreach ($fields_label as $field_toshow) {
1272
                                $translabel = '';
1273
                                if (!empty($obj->$field_toshow)) {
1274
                                    $translabel = $langs->trans($obj->$field_toshow);
1275
                                }
1276
                                if ($translabel != $field_toshow) {
1277
                                    $value .= dol_trunc($translabel, 18) . ' ';
1278
                                } else {
1279
                                    $value .= $obj->$field_toshow . ' ';
1280
                                }
1281
                            }
1282
                        } else {
1283
                            $translabel = '';
1284
                            if (!empty($obj->{$InfoFieldList[1]})) {
1285
                                $translabel = $langs->trans($obj->{$InfoFieldList[1]});
1286
                            }
1287
                            if ($translabel != $obj->{$InfoFieldList[1]}) {
1288
                                $value = dol_trunc($translabel, 18);
1289
                            } else {
1290
                                $value = $obj->{$InfoFieldList[1]};
1291
                            }
1292
                        }
1293
                    }
1294
                } else {
1295
                    require_once constant('DOL_DOCUMENT_ROOT') . '/categories/class/categorie.class.php';
1296
1297
                    $toprint = array();
1298
                    $obj = $this->db->fetch_object($resql);
1299
                    $c = new Categorie($this->db);
0 ignored issues
show
Bug introduced by
The type Dolibarr\Core\Base\Categorie was not found. Did you mean Categorie? If so, make sure to prefix the type with \.
Loading history...
1300
                    $c->fetch($obj->rowid);
1301
                    $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
1302
                    foreach ($ways as $way) {
1303
                        $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
1304
                    }
1305
                    $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
1306
                }
1307
            } else {
1308
                dol_syslog(get_class($this) . '::showOutputField error ' . $this->db->lasterror(), LOG_WARNING);
1309
            }
1310
        } elseif ($type == 'radio') {
1311
            $value = $param['options'][(string)$value];
1312
        } elseif ($type == 'checkbox') {
1313
            $value_arr = explode(',', (string)$value);
1314
            $value = '';
1315
            if (is_array($value_arr) && count($value_arr) > 0) {
1316
                $toprint = array();
1317
                foreach ($value_arr as $keyval => $valueval) {
1318
                    if (!empty($valueval)) {
1319
                        $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $param['options'][$valueval] . '</li>';
1320
                    }
1321
                }
1322
                if (!empty($toprint)) {
1323
                    $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
1324
                }
1325
            }
1326
        } elseif ($type == 'chkbxlst') {
1327
            $value_arr = (isset($value) ? explode(',', $value) : array());
1328
1329
            $param_list = array_keys($param['options']);
1330
            $InfoFieldList = explode(":", $param_list[0]);
1331
1332
            $selectkey = "rowid";
1333
            $keyList = 'rowid';
1334
1335
            if (count($InfoFieldList) >= 3) {
1336
                $selectkey = $InfoFieldList[2];
1337
                $keyList = $InfoFieldList[2] . ' as rowid';
1338
            }
1339
1340
            $fields_label = explode('|', $InfoFieldList[1]);
1341
            if (is_array($fields_label)) {
1342
                $keyList .= ', ';
1343
                $keyList .= implode(', ', $fields_label);
1344
            }
1345
1346
            $filter_categorie = false;
1347
            if (count($InfoFieldList) > 5) {
1348
                if ($InfoFieldList[0] == 'categorie') {
1349
                    $filter_categorie = true;
1350
                }
1351
            }
1352
1353
            $sql = "SELECT " . $keyList;
1354
            $sql .= ' FROM ' . $this->db->prefix() . $InfoFieldList[0];
1355
            if (strpos($InfoFieldList[4], 'extra') !== false) {
1356
                $sql .= ' as main';
1357
            }
1358
            // $sql.= " WHERE ".$selectkey."='".$this->db->escape($value)."'";
1359
            // $sql.= ' AND entity = '.$conf->entity;
1360
1361
            dol_syslog(get_class($this) . ':showOutputField:$type=chkbxlst', LOG_DEBUG);
1362
            $resql = $this->db->query($sql);
1363
            if ($resql) {
1364
                if (!$filter_categorie) {
1365
                    $value = ''; // value was used, so now we reset it to use it to build final output
1366
                    $toprint = array();
1367
                    while ($obj = $this->db->fetch_object($resql)) {
1368
                        // Several field into label (eq table:code|libelle:rowid)
1369
                        $fields_label = explode('|', $InfoFieldList[1]);
1370
                        if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
1371
                            if (is_array($fields_label) && count($fields_label) > 1) {
1372
                                foreach ($fields_label as $field_toshow) {
1373
                                    $translabel = '';
1374
                                    if (!empty($obj->$field_toshow)) {
1375
                                        $translabel = $langs->trans($obj->$field_toshow);
1376
                                    }
1377
                                    if ($translabel != $field_toshow) {
1378
                                        $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
1379
                                    } else {
1380
                                        $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->$field_toshow . '</li>';
1381
                                    }
1382
                                }
1383
                            } else {
1384
                                $translabel = '';
1385
                                if (!empty($obj->{$InfoFieldList[1]})) {
1386
                                    $translabel = $langs->trans($obj->{$InfoFieldList[1]});
1387
                                }
1388
                                if ($translabel != $obj->{$InfoFieldList[1]}) {
1389
                                    $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
1390
                                } else {
1391
                                    $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->{$InfoFieldList[1]} . '</li>';
1392
                                }
1393
                            }
1394
                        }
1395
                    }
1396
                } else {
1397
                    require_once constant('DOL_DOCUMENT_ROOT') . '/categories/class/categorie.class.php';
1398
1399
                    $toprint = array();
1400
                    while ($obj = $this->db->fetch_object($resql)) {
1401
                        if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
1402
                            $c = new Categorie($this->db);
1403
                            $c->fetch($obj->rowid);
1404
                            $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
1405
                            foreach ($ways as $way) {
1406
                                $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
1407
                            }
1408
                        }
1409
                    }
1410
                }
1411
                $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
1412
            } else {
1413
                dol_syslog(get_class($this) . '::showOutputField error ' . $this->db->lasterror(), LOG_WARNING);
1414
            }
1415
        } elseif ($type == 'link') {
1416
            $out = '';
1417
1418
            // only if something to display (perf)
1419
            if ($value) {
1420
                $param_list = array_keys($param['options']);
1421
                // Example: $param_list='ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
1422
                // Example: $param_list='ObjectClass:PathToClass:#getnomurlparam1=-1#getnomurlparam2=customer'
1423
1424
                $InfoFieldList = explode(":", $param_list[0]);
1425
1426
                $classname = $InfoFieldList[0];
1427
                $classpath = $InfoFieldList[1];
1428
1429
                // Set $getnomurlparam1 et getnomurlparam2
1430
                $getnomurlparam = 3;
1431
                $getnomurlparam2 = '';
1432
                $regtmp = array();
1433
                if (preg_match('/#getnomurlparam1=([^#]*)/', $param_list[0], $regtmp)) {
1434
                    $getnomurlparam = $regtmp[1];
1435
                }
1436
                if (preg_match('/#getnomurlparam2=([^#]*)/', $param_list[0], $regtmp)) {
1437
                    $getnomurlparam2 = $regtmp[1];
1438
                }
1439
1440
                if (!empty($classpath)) {
1441
                    dol_include_once($InfoFieldList[1]);
1442
                    if ($classname && class_exists($classname)) {
1443
                        $object = new $classname($this->db);
1444
                        if ($object->element === 'product') {   // Special case for product because default valut of fetch are wrong
1445
                            $result = $object->fetch($value, '', '', '', 0, 1, 1);
1446
                        } else {
1447
                            $result = $object->fetch($value);
1448
                        }
1449
                        if ($result > 0) {
1450
                            if ($object->element === 'product') {
1451
                                $get_name_url_param_arr = array($getnomurlparam, $getnomurlparam2, 0, -1, 0, '', 0);
1452
                                if (isset($val['get_name_url_params'])) {
1453
                                    $get_name_url_params = explode(':', $val['get_name_url_params']);
1454
                                    if (!empty($get_name_url_params)) {
1455
                                        $param_num_max = count($get_name_url_param_arr) - 1;
1456
                                        foreach ($get_name_url_params as $param_num => $param_value) {
1457
                                            if ($param_num > $param_num_max) {
1458
                                                break;
1459
                                            }
1460
                                            $get_name_url_param_arr[$param_num] = $param_value;
1461
                                        }
1462
                                    }
1463
                                }
1464
1465
                                /**
1466
                                 * @var Product $object
1467
                                 */
1468
                                $value = $object->getNomUrl($get_name_url_param_arr[0], $get_name_url_param_arr[1], $get_name_url_param_arr[2], $get_name_url_param_arr[3], $get_name_url_param_arr[4], $get_name_url_param_arr[5], $get_name_url_param_arr[6]);
1469
                            } else {
1470
                                $value = $object->getNomUrl($getnomurlparam, $getnomurlparam2);
1471
                            }
1472
                        } else {
1473
                            $value = '';
1474
                        }
1475
                    }
1476
                } else {
1477
                    dol_syslog('Error bad setup of extrafield', LOG_WARNING);
1478
                    return 'Error bad setup of extrafield';
1479
                }
1480
            } else {
1481
                $value = '';
1482
            }
1483
        } elseif ($type == 'password') {
1484
            $value = '<span class="opacitymedium">' . $langs->trans("Encrypted") . '</span>';
1485
            //$value = preg_replace('/./i', '*', $value);
1486
        } elseif ($type == 'array') {
1487
            if (is_array($value)) {
1488
                $value = implode('<br>', $value);
1489
            } else {
1490
                dol_syslog(__METHOD__ . ' Expected array from dol_eval, but got ' . gettype($value), LOG_ERR);
1491
                return 'Error unexpected result from code evaluation';
1492
            }
1493
        } else {    // text|html|varchar
1494
            $value = dol_htmlentitiesbr($value);
1495
        }
1496
1497
        //print $type.'-'.$size.'-'.$value;
1498
        $out = $value;
1499
1500
        return is_null($out) ? '' : $out;
1501
    }
1502
1503
1504
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1505
1506
    /**
1507
     * Method to output saved errors
1508
     *
1509
     * @return  string      String with errors
1510
     */
1511
    public function errorsToString()
1512
    {
1513
        return $this->error . (is_array($this->errors) ? (($this->error != '' ? ', ' : '') . implode(', ', $this->errors)) : '');
1514
    }
1515
1516
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1517
1518
    /**
1519
     * Return customer ref for screen output.
1520
     *
1521
     * @param string $objref Customer ref
1522
     * @return string                     Customer ref formatted
1523
     */
1524
    public function getFormatedCustomerRef($objref)
1525
    {
1526
        global $hookmanager;
1527
1528
        $parameters = array('objref' => $objref);
1529
        $action = '';
1530
        $reshook = $hookmanager->executeHooks('getFormatedCustomerRef', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1531
        if ($reshook > 0) {
1532
            return $hookmanager->resArray['objref'];
1533
        }
1534
        return $objref . (isset($hookmanager->resArray['objref']) ? $hookmanager->resArray['objref'] : '');
1535
    }
1536
1537
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1538
1539
    /**
1540
     * Return supplier ref for screen output.
1541
     *
1542
     * @param string $objref Supplier ref
1543
     * @return string                     Supplier ref formatted
1544
     */
1545
    public function getFormatedSupplierRef($objref)
1546
    {
1547
        global $hookmanager;
1548
1549
        $parameters = array('objref' => $objref);
1550
        $action = '';
1551
        $reshook = $hookmanager->executeHooks('getFormatedSupplierRef', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1552
        if ($reshook > 0) {
1553
            return $hookmanager->resArray['objref'];
1554
        }
1555
        return $objref . (isset($hookmanager->resArray['objref']) ? $hookmanager->resArray['objref'] : '');
1556
    }
1557
1558
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1559
1560
    /**
1561
     *  Return full address of contact
1562
     *
1563
     * @param int<0,1> $withcountry 1=Add country into address string
1564
     * @param string $sep Separator to use to build string
1565
     * @param int<0,1> $withregion 1=Add region into address string
1566
     * @param string $extralangcode User extralanguages as value
1567
     * @return     string                          Full address string
1568
     */
1569
    public function getFullAddress($withcountry = 0, $sep = "\n", $withregion = 0, $extralangcode = '')
1570
    {
1571
        if ($withcountry && $this->country_id && (empty($this->country_code) || empty($this->country))) {
1572
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/company.lib.php';
1573
            $tmparray = getCountry($this->country_id, 'all');
1574
            $this->country_code = $tmparray['code'];
1575
            $this->country = $tmparray['label'];
1576
        }
1577
1578
        if ($withregion && $this->state_id && (empty($this->state_code) || empty($this->state) || empty($this->region) || empty($this->region_code))) {
1579
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/company.lib.php';
1580
            $tmparray = getState($this->state_id, 'all', null, 1);
1581
            $this->state_code = $tmparray['code'];
1582
            $this->state = $tmparray['label'];
1583
            $this->region_code = $tmparray['region_code'];
1584
            $this->region = $tmparray['region'];
1585
        }
1586
1587
        return dol_format_address($this, $withcountry, $sep, null, 0, $extralangcode);
1588
    }
1589
1590
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1591
1592
    /**
1593
     * Return the link of last main doc file for direct public download.
1594
     *
1595
     * @param string $modulepart Module related to document
1596
     * @param int $initsharekey Init the share key if it was not yet defined
1597
     * @param int $relativelink 0=Return full external link, 1=Return link relative to root of file
1598
     * @return  string|-1                   Returns the link, or an empty string if no link was found, or -1 if error.
0 ignored issues
show
Documentation Bug introduced by
The doc comment string|-1 at position 2 could not be parsed: Unknown type name '-1' at position 2 in string|-1.
Loading history...
1599
     */
1600
    public function getLastMainDocLink($modulepart, $initsharekey = 0, $relativelink = 0)
1601
    {
1602
        global $user, $dolibarr_main_url_root;
1603
1604
        if (empty($this->last_main_doc)) {
1605
            return ''; // No way to known which document name to use
1606
        }
1607
1608
        include_once DOL_DOCUMENT_ROOT . '/ecm/class/ecmfiles.class.php';
1609
        $ecmfile = new EcmFiles($this->db);
0 ignored issues
show
Bug introduced by
The type Dolibarr\Core\Base\EcmFiles was not found. Did you mean EcmFiles? If so, make sure to prefix the type with \.
Loading history...
1610
        $result = $ecmfile->fetch(0, '', $this->last_main_doc);
1611
        if ($result < 0) {
1612
            $this->error = $ecmfile->error;
1613
            $this->errors = $ecmfile->errors;
1614
            return -1;
1615
        }
1616
1617
        if (empty($ecmfile->id)) {  // No entry into file index already exists, we should initialize the shared key manually.
1618
            // Add entry into index
1619
            if ($initsharekey) {
1620
                require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/security2.lib.php';
1621
1622
                // TODO We can't, we don't have full path of file, only last_main_doc and ->element, so we must first rebuild full path $destfull
1623
                /*
1624
                $ecmfile->filepath = $rel_dir;
1625
                $ecmfile->filename = $filename;
1626
                $ecmfile->label = md5_file(dol_osencode($destfull));    // hash of file content
1627
                $ecmfile->fullpath_orig = '';
1628
                $ecmfile->gen_or_uploaded = 'generated';
1629
                $ecmfile->description = '';    // indexed content
1630
                $ecmfile->keywords = '';        // keyword content
1631
                $ecmfile->share = getRandomPassword(true);
1632
                $result = $ecmfile->create($user);
1633
                if ($result < 0)
1634
                {
1635
                    $this->error = $ecmfile->error;
1636
                    $this->errors = $ecmfile->errors;
1637
                }
1638
                */
1639
            } else {
1640
                return '';
1641
            }
1642
        } elseif (empty($ecmfile->share)) { // Entry into file index already exists but no share key is defined.
1643
            // Add entry into index
1644
            if ($initsharekey) {
1645
                require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/security2.lib.php';
1646
                $ecmfile->share = getRandomPassword(true);
1647
                $ecmfile->update($user);
1648
            } else {
1649
                return '';
1650
            }
1651
        }
1652
        // Define $urlwithroot
1653
        $urlwithouturlroot = preg_replace('/' . preg_quote(DOL_URL_ROOT, '/') . '$/i', '', trim($dolibarr_main_url_root));
1654
        // This is to use external domain name found into config file
1655
        //if (DOL_URL_ROOT && ! preg_match('/\/$/', $urlwithouturlroot) && ! preg_match('/^\//', DOL_URL_ROOT)) $urlwithroot=$urlwithouturlroot.'/'.DOL_URL_ROOT;
1656
        //else
1657
        $urlwithroot = $urlwithouturlroot . DOL_URL_ROOT;
1658
        //$urlwithroot=DOL_MAIN_URL_ROOT;                   // This is to use same domain name than current
1659
1660
        $forcedownload = 0;
1661
1662
        $paramlink = '';
1663
        //if (!empty($modulepart)) $paramlink.=($paramlink?'&':'').'modulepart='.$modulepart;       // For sharing with hash (so public files), modulepart is not required.
1664
        //if (!empty($ecmfile->entity)) $paramlink.='&entity='.$ecmfile->entity;                    // For sharing with hash (so public files), entity is not required.
1665
        //$paramlink.=($paramlink?'&':'').'file='.urlencode($filepath);                             // No need of name of file for public link, we will use the hash
1666
        if (!empty($ecmfile->share)) {
1667
            $paramlink .= ($paramlink ? '&' : '') . 'hashp=' . $ecmfile->share; // Hash for public share
1668
        }
1669
        if ($forcedownload) {
1670
            $paramlink .= ($paramlink ? '&' : '') . 'attachment=1';
1671
        }
1672
1673
        if ($relativelink) {
1674
            $linktoreturn = 'document.php' . ($paramlink ? '?' . $paramlink : '');
1675
        } else {
1676
            $linktoreturn = $urlwithroot . '/document.php' . ($paramlink ? '?' . $paramlink : '');
1677
        }
1678
1679
        // Here $ecmfile->share is defined
1680
        return $linktoreturn;
1681
    }
1682
1683
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1684
1685
    /**
1686
     *    Copy contact from one element to current
1687
     *
1688
     * @param CommonObject $objFrom Source element
1689
     * @param 'internal'|'external' $source Nature of contact ('internal' or 'external')
0 ignored issues
show
Documentation Bug introduced by
The doc comment 'internal'|'external' at position 0 could not be parsed: Unknown type name ''internal'' at position 0 in 'internal'|'external'.
Loading history...
1690
     * @return   int                         >0 if OK, <0 if KO
1691
     */
1692
    public function copy_linked_contact($objFrom, $source = 'internal')
1693
    {
1694
        // phpcs:enable
1695
        $contacts = $objFrom->liste_contact(-1, $source);
1696
        foreach ($contacts as $contact) {
1697
            if ($this->add_contact($contact['id'], $contact['fk_c_type_contact'], $contact['source']) < 0) {
1698
                return -1;
1699
            }
1700
        }
1701
        return 1;
1702
    }
1703
1704
    /**
1705
     *    Get array of all contacts for an object
1706
     *
1707
     * @param int $statusoflink Status of links to get (-1=all). Not used.
1708
     * @param 'external'|'thirdparty'|'internal' $source Source of contact: 'external' or 'thirdparty' (llx_socpeople) or 'internal' (llx_user)
0 ignored issues
show
Documentation Bug introduced by
The doc comment 'external'|'thirdparty'|'internal' at position 0 could not be parsed: Unknown type name ''external'' at position 0 in 'external'|'thirdparty'|'internal'.
Loading history...
1709
     * @param int<0,1> $list 0:Returned array contains all properties, 1:Return array contains just id
1710
     * @param string $code Filter on this code of contact type ('SHIPPING', 'BILLING', ...)
1711
     * @param int $status Status of user or company
1712
     * @param int[] $arrayoftcids Array with ID of type of contacts. If we provide this, we can make a ec.fk_c_type_contact in ($arrayoftcids) to avoid link on tc table. TODO Not implemented.
1713
     * @return array<int,array{parentId:int,source:string,socid:int,id:int,nom:string,civility:string,lastname:string,firstname:string,email:string,login:string,photo:string,gender:string,statuscontact:int,rowid:int,code:string,libelle:string,status:string,fk_c_type_contact:int}>|int<-1,-1>          Array of contacts, -1 if error
1714
     */
1715
    public function liste_contact($statusoflink = -1, $source = 'external', $list = 0, $code = '', $status = -1, $arrayoftcids = array())
1716
    {
1717
        // phpcs:enable
1718
        global $langs;
1719
1720
        $tab = array();
1721
1722
        $sql = "SELECT ec.rowid, ec.statut as statuslink, ec.fk_socpeople as id, ec.fk_c_type_contact"; // This field contains id of llx_socpeople or id of llx_user
1723
        if ($source == 'internal') {
1724
            $sql .= ", '-1' as socid, t.statut as statuscontact, t.login, t.photo, t.gender";
1725
        }
1726
        if ($source == 'external' || $source == 'thirdparty') {
1727
            $sql .= ", t.fk_soc as socid, t.statut as statuscontact";
1728
        }
1729
        $sql .= ", t.civility as civility, t.lastname as lastname, t.firstname, t.email";
1730
        $sql .= ", tc.source, tc.element, tc.code, tc.libelle as type_label";
1731
        $sql .= " FROM " . $this->db->prefix() . "c_type_contact tc,";
1732
        $sql .= " " . $this->db->prefix() . "element_contact ec";
1733
        if ($source == 'internal') {    // internal contact (user)
1734
            $sql .= " LEFT JOIN " . $this->db->prefix() . "user t on ec.fk_socpeople = t.rowid";
1735
        }
1736
        if ($source == 'external' || $source == 'thirdparty') { // external contact (socpeople)
1737
            $sql .= " LEFT JOIN " . $this->db->prefix() . "socpeople t on ec.fk_socpeople = t.rowid";
1738
        }
1739
        $sql .= " WHERE ec.element_id = " . ((int)$this->id);
1740
        $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1741
        $sql .= " AND tc.element = '" . $this->db->escape($this->element) . "'";
1742
        if ($code) {
1743
            $sql .= " AND tc.code = '" . $this->db->escape($code) . "'";
1744
        }
1745
        if ($source == 'internal') {
1746
            $sql .= " AND tc.source = 'internal'";
1747
            if ($status >= 0) {
1748
                $sql .= " AND t.statut = " . ((int)$status);
1749
            }
1750
        }
1751
        if ($source == 'external' || $source == 'thirdparty') {
1752
            $sql .= " AND tc.source = 'external'";
1753
            if ($status >= 0) {
1754
                $sql .= " AND t.statut = " . ((int)$status); // t is llx_socpeople
1755
            }
1756
        }
1757
        $sql .= " AND tc.active = 1";
1758
        if ($statusoflink >= 0) {
1759
            $sql .= " AND ec.statut = " . ((int)$statusoflink);
1760
        }
1761
        $sql .= " ORDER BY t.lastname ASC";
1762
1763
        dol_syslog(get_class($this) . "::liste_contact", LOG_DEBUG);
1764
        $resql = $this->db->query($sql);
1765
        if ($resql) {
1766
            $num = $this->db->num_rows($resql);
1767
            $i = 0;
1768
            while ($i < $num) {
1769
                $obj = $this->db->fetch_object($resql);
1770
1771
                if (!$list) {
1772
                    $transkey = "TypeContact_" . $obj->element . "_" . $obj->source . "_" . $obj->code;
1773
                    $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1774
                    $tab[$i] = array(
1775
                        'parentId' => $this->id,
1776
                        'source' => $obj->source,
1777
                        'socid' => $obj->socid,
1778
                        'id' => $obj->id,
1779
                        'nom' => $obj->lastname, // For backward compatibility
1780
                        'civility' => $obj->civility,
1781
                        'lastname' => $obj->lastname,
1782
                        'firstname' => $obj->firstname,
1783
                        'email' => $obj->email,
1784
                        'login' => (empty($obj->login) ? '' : $obj->login),
1785
                        'photo' => (empty($obj->photo) ? '' : $obj->photo),
1786
                        'gender' => (empty($obj->gender) ? '' : $obj->gender),
1787
                        'statuscontact' => $obj->statuscontact,
1788
                        'rowid' => $obj->rowid,
1789
                        'code' => $obj->code,
1790
                        'libelle' => $libelle_type,
1791
                        'status' => $obj->statuslink,
1792
                        'fk_c_type_contact' => $obj->fk_c_type_contact
1793
                    );
1794
                } else {
1795
                    $tab[$i] = $obj->id;
1796
                }
1797
1798
                $i++;
1799
            }
1800
1801
            return $tab;
1802
        } else {
1803
            $this->error = $this->db->lasterror();
1804
            dol_print_error($this->db);
1805
            return -1;
1806
        }
1807
    }
1808
1809
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1810
1811
    /**
1812
     *  Add a link between element $this->element and a contact
1813
     *
1814
     * @param int $fk_socpeople Id of thirdparty contact (if source = 'external') or id of user (if source = 'internal') to link
1815
     * @param int|string $type_contact Type of contact (code or id). Must be id or code found into table llx_c_type_contact. For example: SALESREPFOLL
1816
     * @param string $source external=Contact extern (llx_socpeople), internal=Contact intern (llx_user)
1817
     * @param int $notrigger Disable all triggers
1818
     * @return int                             Return integer <0 if KO, 0 if already added or code not valid, >0 if OK
1819
     */
1820
    public function add_contact($fk_socpeople, $type_contact, $source = 'external', $notrigger = 0)
1821
    {
1822
        // phpcs:enable
1823
        global $user, $langs;
1824
1825
1826
        dol_syslog(get_class($this) . "::add_contact $fk_socpeople, $type_contact, $source, $notrigger");
1827
1828
        // Check parameters
1829
        if ($fk_socpeople <= 0) {
1830
            $langs->load("errors");
1831
            $this->error = $langs->trans("ErrorWrongValueForParameterX", "1");
1832
            dol_syslog(get_class($this) . "::add_contact " . $this->error, LOG_ERR);
1833
            return -1;
1834
        }
1835
        if (!$type_contact) {
1836
            $langs->load("errors");
1837
            $this->error = $langs->trans("ErrorWrongValueForParameterX", "2");
1838
            dol_syslog(get_class($this) . "::add_contact " . $this->error, LOG_ERR);
1839
            return -2;
1840
        }
1841
1842
        $id_type_contact = 0;
1843
        if (is_numeric($type_contact)) {
1844
            $id_type_contact = $type_contact;
1845
        } else {
1846
            // We look for id type_contact
1847
            $sql = "SELECT tc.rowid";
1848
            $sql .= " FROM " . $this->db->prefix() . "c_type_contact as tc";
1849
            $sql .= " WHERE tc.element='" . $this->db->escape($this->element) . "'";
1850
            $sql .= " AND tc.source='" . $this->db->escape($source) . "'";
1851
            $sql .= " AND tc.code='" . $this->db->escape($type_contact) . "' AND tc.active=1";
1852
            //print $sql;
1853
            $resql = $this->db->query($sql);
1854
            if ($resql) {
1855
                $obj = $this->db->fetch_object($resql);
1856
                if ($obj) {
1857
                    $id_type_contact = $obj->rowid;
1858
                }
1859
            }
1860
        }
1861
1862
        if ($id_type_contact == 0) {
1863
            dol_syslog("CODE_NOT_VALID_FOR_THIS_ELEMENT: Code type of contact '" . $type_contact . "' does not exists or is not active for element " . $this->element . ", we can ignore it");
1864
            return 0;
1865
        }
1866
1867
        $datecreate = dol_now();
1868
1869
        // Socpeople must have already been added by some trigger, then we have to check it to avoid DB_ERROR_RECORD_ALREADY_EXISTS error
1870
        $TListeContacts = $this->liste_contact(-1, $source);
1871
        $already_added = false;
1872
        if (is_array($TListeContacts) && !empty($TListeContacts)) {
1873
            foreach ($TListeContacts as $array_contact) {
1874
                if ($array_contact['status'] == 4 && $array_contact['id'] == $fk_socpeople && $array_contact['fk_c_type_contact'] == $id_type_contact) {
1875
                    $already_added = true;
1876
                    break;
1877
                }
1878
            }
1879
        }
1880
1881
        if (!$already_added) {
1882
            $this->db->begin();
1883
1884
            // Insert into database
1885
            $sql = "INSERT INTO " . $this->db->prefix() . "element_contact";
1886
            $sql .= " (element_id, fk_socpeople, datecreate, statut, fk_c_type_contact) ";
1887
            $sql .= " VALUES (" . $this->id . ", " . ((int)$fk_socpeople) . " , ";
1888
            $sql .= "'" . $this->db->idate($datecreate) . "'";
1889
            $sql .= ", 4, " . ((int)$id_type_contact);
1890
            $sql .= ")";
1891
1892
            $resql = $this->db->query($sql);
1893
            if ($resql) {
1894
                if (!$notrigger) {
1895
                    $result = $this->call_trigger(strtoupper($this->element) . '_ADD_CONTACT', $user);
1896
                    if ($result < 0) {
1897
                        $this->db->rollback();
1898
                        return -1;
1899
                    }
1900
                }
1901
1902
                $this->db->commit();
1903
                return 1;
1904
            } else {
1905
                if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1906
                    $this->error = $this->db->errno();
1907
                    $this->db->rollback();
1908
                    return -2;
1909
                } else {
1910
                    $this->error = $this->db->lasterror();
1911
                    $this->db->rollback();
1912
                    return -1;
1913
                }
1914
            }
1915
        } else {
1916
            return 0;
1917
        }
1918
    }
1919
1920
    /**
1921
     * Call trigger based on this instance.
1922
     * Some context information may also be provided into array property this->context.
1923
     * NB:  Error from trigger are stacked in interface->errors
1924
     * NB2: If return code of triggers are < 0, action calling trigger should cancel all transaction.
1925
     *
1926
     * @param string $triggerName trigger's name to execute
1927
     * @param User $user Object user
1928
     * @return  int                       Result of run_triggers
1929
     */
1930
    public function call_trigger($triggerName, $user)
1931
    {
1932
        // phpcs:enable
1933
        global $langs, $conf;
1934
1935
        if (!empty(self::TRIGGER_PREFIX) && strpos($triggerName, self::TRIGGER_PREFIX . '_') !== 0) {
1936
            dol_print_error(null, 'The trigger "' . $triggerName . '" does not start with "' . self::TRIGGER_PREFIX . '_" as required.');
1937
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1938
        }
1939
        if (!is_object($langs)) {   // If lang was not defined, we set it. It is required by run_triggers().
1940
            include_once DOL_DOCUMENT_ROOT . '/core/class/translate.class.php';
1941
            $langs = new Translate('', $conf);
0 ignored issues
show
Bug introduced by
The type Dolibarr\Core\Base\Translate was not found. Did you mean Translate? If so, make sure to prefix the type with \.
Loading history...
1942
        }
1943
1944
        include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
1945
        $interface = new Interfaces($this->db);
0 ignored issues
show
Bug introduced by
The type Dolibarr\Core\Base\Interfaces was not found. Did you mean Interfaces? If so, make sure to prefix the type with \.
Loading history...
1946
        $result = $interface->run_triggers($triggerName, $this, $user, $langs, $conf);
1947
1948
        if ($result < 0) {
1949
            if (!empty($this->errors)) {
1950
                $this->errors = array_unique(array_merge($this->errors, $interface->errors)); // We use array_unique because when a trigger call another trigger on same object, this->errors is added twice.
1951
            } else {
1952
                $this->errors = $interface->errors;
1953
            }
1954
        }
1955
        return $result;
1956
    }
1957
1958
    /**
1959
     *    Delete a link to contact line
1960
     *
1961
     * @param int $rowid Id of contact link line to delete
1962
     * @param int $notrigger Disable all triggers
1963
     * @return   int                     >0 if OK, <0 if KO
1964
     */
1965
    public function delete_contact($rowid, $notrigger = 0)
1966
    {
1967
        // phpcs:enable
1968
        global $user;
1969
1970
        $error = 0;
1971
1972
        $this->db->begin();
1973
1974
        if (!$error && empty($notrigger)) {
1975
            // Call trigger
1976
            $this->context['contact_id'] = ((int)$rowid);
1977
            $result = $this->call_trigger(strtoupper($this->element) . '_DELETE_CONTACT', $user);
1978
            if ($result < 0) {
1979
                $error++;
1980
            }
1981
            // End call triggers
1982
        }
1983
1984
        if (!$error) {
1985
            dol_syslog(get_class($this) . "::delete_contact", LOG_DEBUG);
1986
1987
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . "element_contact";
1988
            $sql .= " WHERE rowid = " . ((int)$rowid);
1989
1990
            $result = $this->db->query($sql);
1991
            if (!$result) {
1992
                $error++;
1993
                $this->errors[] = $this->db->lasterror();
1994
            }
1995
        }
1996
1997
        if (!$error) {
1998
            $this->db->commit();
1999
            return 1;
2000
        } else {
2001
            $this->error = $this->db->lasterror();
2002
            $this->db->rollback();
2003
            return -1;
2004
        }
2005
    }
2006
2007
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2008
2009
    /**
2010
     *    Delete all links between an object $this and all its contacts in llx_element_contact
2011
     *
2012
     * @param string $source '' or 'internal' or 'external'
2013
     * @param string $code Type of contact (code or id)
2014
     * @return   int                 Return integer <0 if KO, 0=Nothing done, >0 if OK
2015
     */
2016
    public function delete_linked_contact($source = '', $code = '')
2017
    {
2018
        // phpcs:enable
2019
        $listId = '';
2020
        $temp = array();
2021
        $typeContact = $this->liste_type_contact($source, '', 0, 0, $code);
2022
2023
        if (!empty($typeContact)) {
2024
            foreach ($typeContact as $key => $value) {
2025
                array_push($temp, $key);
2026
            }
2027
            $listId = implode(",", $temp);
2028
        }
2029
2030
        // If $listId is empty, we have not criteria on fk_c_type_contact so we will delete record on element_id for
2031
        // any type or record instead of only the ones of the current object. So we do nothing in such a case.
2032
        if (empty($listId)) {
2033
            return 0;
2034
        }
2035
2036
        $sql = "DELETE FROM " . $this->db->prefix() . "element_contact";
2037
        $sql .= " WHERE element_id = " . ((int)$this->id);
2038
        $sql .= " AND fk_c_type_contact IN (" . $this->db->sanitize($listId) . ")";
2039
2040
        dol_syslog(get_class($this) . "::delete_linked_contact", LOG_DEBUG);
2041
        if ($this->db->query($sql)) {
2042
            return 1;
2043
        } else {
2044
            $this->error = $this->db->lasterror();
2045
            return -1;
2046
        }
2047
    }
2048
2049
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2050
2051
    /**
2052
     *      Return array with list of possible values for type of contacts
2053
     *
2054
     * @param 'internal'|'external'|'all' $source 'internal', 'external' or 'all'
0 ignored issues
show
Documentation Bug introduced by
The doc comment 'internal'|'external'|'all' at position 0 could not be parsed: Unknown type name ''internal'' at position 0 in 'internal'|'external'|'all'.
Loading history...
2055
     * @param string $order Sort order by : 'position', 'code', 'rowid'...
2056
     * @param int<0,1> $option 0=Return array id->label, 1=Return array code->label
2057
     * @param int<0,1> $activeonly 0=all status of contact, 1=only the active
2058
     * @param string $code Type of contact (Example: 'CUSTOMER', 'SERVICE')
2059
     * @return array<int,string>|array<string,string>|null Array list of type of contacts (id->label if option=0, code->label if option=1), or null if error
2060
     */
2061
    public function liste_type_contact($source = 'internal', $order = 'position', $option = 0, $activeonly = 0, $code = '')
2062
    {
2063
        // phpcs:enable
2064
        global $langs;
2065
2066
        if (empty($order)) {
2067
            $order = 'position';
2068
        }
2069
        if ($order == 'position') {
2070
            $order .= ',code';
2071
        }
2072
2073
        $tab = array();
2074
        $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position";
2075
        $sql .= " FROM " . $this->db->prefix() . "c_type_contact as tc";
2076
        $sql .= " WHERE tc.element='" . $this->db->escape($this->element) . "'";
2077
        if ($activeonly == 1) {
2078
            $sql .= " AND tc.active=1"; // only the active types
2079
        }
2080
        if (!empty($source) && $source != 'all') {
2081
            $sql .= " AND tc.source='" . $this->db->escape($source) . "'";
2082
        }
2083
        if (!empty($code)) {
2084
            $sql .= " AND tc.code='" . $this->db->escape($code) . "'";
2085
        }
2086
        $sql .= $this->db->order($order, 'ASC');
2087
2088
        //print "sql=".$sql;
2089
        $resql = $this->db->query($sql);
2090
        if ($resql) {
2091
            $num = $this->db->num_rows($resql);
2092
            $i = 0;
2093
            while ($i < $num) {
2094
                $obj = $this->db->fetch_object($resql);
2095
2096
                $transkey = "TypeContact_" . $this->element . "_" . $source . "_" . $obj->code;
2097
                $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
2098
                if (empty($option)) {
2099
                    $tab[$obj->rowid] = $libelle_type;
2100
                } else {
2101
                    $tab[$obj->code] = $libelle_type;
2102
                }
2103
                $i++;
2104
            }
2105
            return $tab;
2106
        } else {
2107
            $this->error = $this->db->lasterror();
2108
            //dol_print_error($this->db);
2109
            return null;
2110
        }
2111
    }
2112
2113
    /**
2114
     *      Update status of a contact linked to object
2115
     *
2116
     * @param int $rowid Id of link between object and contact
2117
     * @return int                 Return integer <0 if KO, >=0 if OK
2118
     */
2119
    public function swapContactStatus($rowid)
2120
    {
2121
        $sql = "SELECT ec.datecreate, ec.statut, ec.fk_socpeople, ec.fk_c_type_contact,";
2122
        $sql .= " tc.code, tc.libelle as type_label";
2123
        $sql .= " FROM (" . $this->db->prefix() . "element_contact as ec, " . $this->db->prefix() . "c_type_contact as tc)";
2124
        $sql .= " WHERE ec.rowid =" . ((int)$rowid);
2125
        $sql .= " AND ec.fk_c_type_contact = tc.rowid";
2126
        $sql .= " AND tc.element = '" . $this->db->escape($this->element) . "'";
2127
2128
        dol_syslog(get_class($this) . "::swapContactStatus", LOG_DEBUG);
2129
        $resql = $this->db->query($sql);
2130
        if ($resql) {
2131
            $obj = $this->db->fetch_object($resql);
2132
            $newstatut = ($obj->statut == 4) ? 5 : 4;
2133
            $result = $this->update_contact($rowid, $newstatut);
2134
            $this->db->free($resql);
2135
            return $result;
2136
        } else {
2137
            $this->error = $this->db->error();
2138
            dol_print_error($this->db);
2139
            return -1;
2140
        }
2141
    }
2142
2143
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2144
2145
    /**
2146
     *      Update a link to contact line
2147
     *
2148
     * @param int $rowid Id of line contact-element
2149
     * @param int $statut New status of link
2150
     * @param int $type_contact_id Id of contact type (not modified if 0)
2151
     * @param int $fk_socpeople Id of soc_people to update (not modified if 0)
2152
     * @return int                         Return integer <0 if KO, >= 0 if OK
2153
     */
2154
    public function update_contact($rowid, $statut, $type_contact_id = 0, $fk_socpeople = 0)
2155
    {
2156
        // phpcs:enable
2157
        // Insert into database
2158
        $sql = "UPDATE " . $this->db->prefix() . "element_contact set";
2159
        $sql .= " statut = " . $statut;
2160
        if ($type_contact_id) {
2161
            $sql .= ", fk_c_type_contact = " . ((int)$type_contact_id);
2162
        }
2163
        if ($fk_socpeople) {
2164
            $sql .= ", fk_socpeople = " . ((int)$fk_socpeople);
2165
        }
2166
        $sql .= " where rowid = " . ((int)$rowid);
2167
        $resql = $this->db->query($sql);
2168
        if ($resql) {
2169
            return 0;
2170
        } else {
2171
            $this->error = $this->db->lasterror();
2172
            return -1;
2173
        }
2174
    }
2175
2176
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2177
2178
    /**
2179
     *      Return array with list of possible values for type of contacts
2180
     *
2181
     * @param string $source 'internal', 'external' or 'all'
2182
     * @param int<0,1> $option 0=Return array id->label, 1=Return array code->label
2183
     * @param int<0,1> $activeonly 0=all status of contact, 1=only the active
2184
     * @param string $code Type of contact (Example: 'CUSTOMER', 'SERVICE')
2185
     * @param string $element Filter on 1 element type
2186
     * @param string $excludeelement Exclude 1 element type. Example: 'agenda'
2187
     * @return array<int,string>|array<string,string>|null Array list of type of contacts (id->label if option=0, code->label if option=1), or null if error
2188
     */
2189
    public function listeTypeContacts($source = 'internal', $option = 0, $activeonly = 0, $code = '', $element = '', $excludeelement = '')
2190
    {
2191
        global $langs, $conf;
2192
2193
        $langs->loadLangs(array('bills', 'contracts', 'interventions', 'orders', 'projects', 'propal', 'ticket', 'agenda'));
2194
2195
        $tab = array();
2196
2197
        $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position, tc.element";
2198
        $sql .= " FROM " . $this->db->prefix() . "c_type_contact as tc";
2199
2200
        $sqlWhere = array();
2201
        if (!empty($element)) {
2202
            $sqlWhere[] = " tc.element='" . $this->db->escape($element) . "'";
2203
        }
2204
        if (!empty($excludeelement)) {
2205
            $sqlWhere[] = " tc.element <> '" . $this->db->escape($excludeelement) . "'";
2206
        }
2207
2208
        if ($activeonly == 1) {
2209
            $sqlWhere[] = " tc.active=1"; // only the active types
2210
        }
2211
2212
        if (!empty($source) && $source != 'all') {
2213
            $sqlWhere[] = " tc.source='" . $this->db->escape($source) . "'";
2214
        }
2215
2216
        if (!empty($code)) {
2217
            $sqlWhere[] = " tc.code='" . $this->db->escape($code) . "'";
2218
        }
2219
2220
        if (count($sqlWhere) > 0) {
2221
            $sql .= " WHERE " . implode(' AND ', $sqlWhere);
2222
        }
2223
2224
        $sql .= $this->db->order('tc.element, tc.position', 'ASC');
2225
2226
        dol_syslog(__METHOD__, LOG_DEBUG);
2227
        $resql = $this->db->query($sql);
2228
        if ($resql) {
2229
            $num = $this->db->num_rows($resql);
2230
            if ($num > 0) {
2231
                $langs->loadLangs(array("propal", "orders", "bills", "suppliers", "contracts", "supplier_proposal"));
2232
2233
                while ($obj = $this->db->fetch_object($resql)) {
2234
                    $modulename = $obj->element;
2235
                    if (strpos($obj->element, 'project') !== false) {
2236
                        $modulename = 'projet';
2237
                    } elseif ($obj->element == 'contrat') {
2238
                        $element = 'contract';
2239
                    } elseif ($obj->element == 'action') {
2240
                        $modulename = 'agenda';
2241
                    } elseif (strpos($obj->element, 'supplier') !== false && $obj->element != 'supplier_proposal') {
2242
                        $modulename = 'fournisseur';
2243
                    }
2244
                    if (!empty($conf->{$modulename}->enabled)) {
2245
                        $libelle_element = $langs->trans('ContactDefault_' . $obj->element);
2246
                        $tmpelement = $obj->element;
2247
                        $transkey = "TypeContact_" . $tmpelement . "_" . $source . "_" . $obj->code;
2248
                        $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
2249
                        $tab[$obj->rowid] = $libelle_element . ' - ' . $libelle_type;
2250
                    }
2251
                }
2252
            }
2253
            return $tab;
2254
        } else {
2255
            $this->error = $this->db->lasterror();
2256
            return null;
2257
        }
2258
    }
2259
2260
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2261
2262
    /**
2263
     *      Return id of contacts for a source and a contact code.
2264
     *      Example: contact client de facturation ('external', 'BILLING')
2265
     *      Example: contact client de livraison ('external', 'SHIPPING')
2266
     *      Example: contact interne suivi paiement ('internal', 'SALESREPFOLL')
2267
     *
2268
     * @param string $source 'external' or 'internal'
2269
     * @param string $code 'BILLING', 'SHIPPING', 'SALESREPFOLL', ...
2270
     * @param int $status limited to a certain status
2271
     * @return int[]|null          List of id for such contacts, or null if error
2272
     */
2273
    public function getIdContact($source, $code, $status = 0)
2274
    {
2275
        global $conf;
2276
2277
        $result = array();
2278
        $i = 0;
2279
        // Particular case for shipping
2280
        if ($this->element == 'shipping' && $this->origin_id != 0) {
2281
            $id = $this->origin_id;
2282
            $element = 'commande';
2283
        } elseif ($this->element == 'reception' && $this->origin_id != 0) {
2284
            $id = $this->origin_id;
2285
            $element = 'order_supplier';
2286
        } else {
2287
            $id = $this->id;
2288
            $element = $this->element;
2289
        }
2290
2291
        $sql = "SELECT ec.fk_socpeople";
2292
        $sql .= " FROM " . $this->db->prefix() . "element_contact as ec,";
2293
        if ($source == 'internal') {
2294
            $sql .= " " . $this->db->prefix() . "user as c,";
2295
        }
2296
        if ($source == 'external') {
2297
            $sql .= " " . $this->db->prefix() . "socpeople as c,";
2298
        }
2299
        $sql .= " " . $this->db->prefix() . "c_type_contact as tc";
2300
        $sql .= " WHERE ec.element_id = " . ((int)$id);
2301
        $sql .= " AND ec.fk_socpeople = c.rowid";
2302
        if ($source == 'internal') {
2303
            $sql .= " AND c.entity IN (" . getEntity('user') . ")";
2304
        }
2305
        if ($source == 'external') {
2306
            $sql .= " AND c.entity IN (" . getEntity('societe') . ")";
2307
        }
2308
        $sql .= " AND ec.fk_c_type_contact = tc.rowid";
2309
        $sql .= " AND tc.element = '" . $this->db->escape($element) . "'";
2310
        $sql .= " AND tc.source = '" . $this->db->escape($source) . "'";
2311
        if ($code) {
2312
            $sql .= " AND tc.code = '" . $this->db->escape($code) . "'";
2313
        }
2314
        $sql .= " AND tc.active = 1";
2315
        if ($status) {
2316
            $sql .= " AND ec.statut = " . ((int)$status);
2317
        }
2318
2319
        dol_syslog(get_class($this) . "::getIdContact", LOG_DEBUG);
2320
        $resql = $this->db->query($sql);
2321
        if ($resql) {
2322
            while ($obj = $this->db->fetch_object($resql)) {
2323
                $result[$i] = $obj->fk_socpeople;
2324
                $i++;
2325
            }
2326
        } else {
2327
            $this->error = $this->db->error();
2328
            return null;
2329
        }
2330
2331
        return $result;
2332
    }
2333
2334
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2335
2336
    /**
2337
     *      Load object contact with id=$this->contact_id into $this->contact
2338
     *
2339
     * @param  ?int $contactid Id du contact. Use this->contact_id if empty.
2340
     * @return int<-1,1>               Return integer <0 if KO, >0 if OK
2341
     */
2342
    public function fetch_contact($contactid = null)
2343
    {
2344
        // phpcs:enable
2345
        if (empty($contactid)) {
2346
            $contactid = $this->contact_id;
2347
        }
2348
2349
        if (empty($contactid)) {
2350
            return 0;
2351
        }
2352
2353
        require_once constant('DOL_DOCUMENT_ROOT') . '/contact/class/contact.class.php';
2354
        $contact = new Contact($this->db);
2355
        $result = $contact->fetch($contactid);
2356
        $this->contact = $contact;
2357
        return $result;
2358
    }
2359
2360
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2361
2362
    /**
2363
     *      Load the third party of object, from id $this->socid or $this->fk_soc, into this->thirdparty
2364
     *
2365
     * @param int<0,1> $force_thirdparty_id Force thirdparty id
2366
     * @return     int<-1,1>                       Return integer <0 if KO, >0 if OK
2367
     * @phan-suppress PhanUndeclaredProperty
2368
     */
2369
    public function fetch_thirdparty($force_thirdparty_id = 0)
2370
    {
2371
        // phpcs:enable
2372
        if (empty($this->socid) && empty($this->fk_soc) && empty($force_thirdparty_id)) {
2373
            return 0;
2374
        }
2375
2376
        require_once constant('DOL_DOCUMENT_ROOT') . '/societe/class/societe.class.php';
2377
2378
        $idtofetch = isset($this->socid) ? $this->socid : (isset($this->fk_soc) ? $this->fk_soc : 0);
2379
        if ($force_thirdparty_id) {
2380
            $idtofetch = $force_thirdparty_id;
2381
        }
2382
2383
        if ($idtofetch) {
2384
            $thirdparty = new Societe($this->db);
2385
            $result = $thirdparty->fetch($idtofetch);
2386
            if ($result < 0) {
2387
                $this->errors = array_merge($this->errors, $thirdparty->errors);
2388
            }
2389
            $this->thirdparty = $thirdparty;
2390
2391
            // Use first price level if level not defined for third party
2392
            if (getDolGlobalString('PRODUIT_MULTIPRICES') && empty($this->thirdparty->price_level)) {
2393
                $this->thirdparty->price_level = 1;
2394
            }
2395
2396
            return $result;
2397
        } else {
2398
            return -1;
2399
        }
2400
    }
2401
2402
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2403
2404
    /**
2405
     * Looks for an object with ref matching the wildcard provided
2406
     * It does only work when $this->table_ref_field is set
2407
     *
2408
     * @param string $ref Wildcard
2409
     * @return  int<-1,1>       >1 = OK, 0 = Not found or table_ref_field not defined, <0 = KO
2410
     */
2411
    public function fetchOneLike($ref)
2412
    {
2413
        if (!$this->table_ref_field) {
2414
            return 0;
2415
        }
2416
2417
        $sql = "SELECT rowid FROM " . $this->db->prefix() . $this->table_element;
2418
        $sql .= " WHERE " . $this->table_ref_field . " LIKE '" . $this->db->escape($ref) . "'"; // no escapeforlike here
2419
        $sql .= " LIMIT 1";
2420
2421
        $query = $this->db->query($sql);
2422
2423
        if (!$this->db->num_rows($query)) {
2424
            return 0;
2425
        }
2426
2427
        $result = $this->db->fetch_object($query);
2428
2429
        if (method_exists($this, 'fetch')) {
2430
            return $this->fetch($result->rowid);
0 ignored issues
show
Bug introduced by
The method fetch() does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

2430
            return $this->/** @scrutinizer ignore-call */ fetch($result->rowid);
Loading history...
2431
        } else {
2432
            $this->error = 'Fetch method not implemented on ' . get_class($this);
2433
            dol_syslog(get_class($this) . '::fetchOneLike Error=' . $this->error, LOG_ERR);
2434
            array_push($this->errors, $this->error);
2435
            return -1;
2436
        }
2437
    }
2438
2439
    /**
2440
     *  Load data for barcode into properties ->barcode_type*
2441
     *  Properties ->barcode_type that is id of barcode. Type is used to find other properties, but
2442
     *  if it is not defined, ->element must be defined to know default barcode type.
2443
     *
2444
     * @return     int<-1,1>       Return integer <0 if KO, 0 if can't guess type of barcode (ISBN, EAN13...), >0 if OK (all barcode properties loaded)
2445
     */
2446
    public function fetch_barcode()
2447
    {
2448
        // phpcs:enable
2449
        global $conf;
2450
2451
        dol_syslog(get_class($this) . '::fetch_barcode this->element=' . $this->element . ' this->barcode_type=' . $this->barcode_type);
2452
2453
        $idtype = $this->barcode_type;
2454
        if (empty($idtype) && $idtype != '0') { // If type of barcode no set, we try to guess. If set to '0' it means we forced to have type remain not defined
2455
            if ($this->element == 'product' && getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE')) {
2456
                $idtype = getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE');
2457
            } elseif ($this->element == 'societe') {
2458
                $idtype = getDolGlobalString('GENBARCODE_BARCODETYPE_THIRDPARTY');
2459
            } else {
2460
                dol_syslog('Call fetch_barcode with barcode_type not defined and cannot be guessed', LOG_WARNING);
2461
            }
2462
        }
2463
2464
        if ($idtype > 0) {
2465
            if (empty($this->barcode_type) || empty($this->barcode_type_code) || empty($this->barcode_type_label) || empty($this->barcode_type_coder)) {    // If data not already loaded
2466
                $sql = "SELECT rowid, code, libelle as label, coder";
2467
                $sql .= " FROM " . $this->db->prefix() . "c_barcode_type";
2468
                $sql .= " WHERE rowid = " . ((int)$idtype);
2469
                dol_syslog(get_class($this) . '::fetch_barcode', LOG_DEBUG);
2470
                $resql = $this->db->query($sql);
2471
                if ($resql) {
2472
                    $obj = $this->db->fetch_object($resql);
2473
                    $this->barcode_type = $obj->rowid;
2474
                    $this->barcode_type_code = $obj->code;
2475
                    $this->barcode_type_label = $obj->label;
2476
                    $this->barcode_type_coder = $obj->coder;
2477
                    return 1;
2478
                } else {
2479
                    dol_print_error($this->db);
2480
                    return -1;
2481
                }
2482
            }
2483
        }
2484
        return 0;
2485
    }
2486
2487
    /**
2488
     *      Load the project with id $this->fk_project into this->project
2489
     *
2490
     * @return     int<-1,1>       Return integer <0 if KO, >=0 if OK
2491
     */
2492
    public function fetch_project()
2493
    {
2494
        // phpcs:enable
2495
        return $this->fetch_projet();
2496
    }
2497
2498
    /**
2499
     *      Load the project with id $this->fk_project into this->project
2500
     *
2501
     * @return     int         Return integer <0 if KO, >=0 if OK
2502
     */
2503
    public function fetch_projet()
2504
    {
2505
        // phpcs:enable
2506
        include_once DOL_DOCUMENT_ROOT . '/projet/class/project.class.php';
2507
2508
        if (empty($this->fk_project) && !empty($this->fk_projet)) {
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$fk_projet has been deprecated: Use $fk_project instead. ( Ignorable by Annotation )

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

2508
        if (empty($this->fk_project) && !empty(/** @scrutinizer ignore-deprecated */ $this->fk_projet)) {

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
2509
            $this->fk_project = $this->fk_projet; // For backward compatibility
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$fk_projet has been deprecated: Use $fk_project instead. ( Ignorable by Annotation )

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

2509
            $this->fk_project = /** @scrutinizer ignore-deprecated */ $this->fk_projet; // For backward compatibility

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
2510
        }
2511
        if (empty($this->fk_project)) {
2512
            return 0;
2513
        }
2514
2515
        $project = new Project($this->db);
2516
        $result = $project->fetch($this->fk_project);
2517
2518
        $this->projet = $project; // deprecated
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$projet has been deprecated: Use $project instead. ( Ignorable by Annotation )

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

2518
        /** @scrutinizer ignore-deprecated */ $this->projet = $project; // deprecated

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
2519
        $this->project = $project;
2520
        return $result;
2521
    }
2522
2523
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2524
2525
    /**
2526
     *      Load the user with id $userid into this->user
2527
     *
2528
     * @param int $userid Id du contact
2529
     * @return int<-1,1>               Return integer <0 if KO, >0 if OK
2530
     */
2531
    public function fetch_user($userid)
2532
    {
2533
        // phpcs:enable
2534
        $user = new User($this->db);
2535
        $result = $user->fetch($userid);
2536
        $this->user = $user;
2537
        return $result;
2538
    }
2539
2540
    /**
2541
     *  Read linked origin object.
2542
     *  Set ->origin_object
2543
     *
2544
     * @return     void
2545
     */
2546
    public function fetch_origin()
2547
    {
2548
        // phpcs:enable
2549
        $origin = $this->origin ? $this->origin : $this->origin_type;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

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

2549
        $origin = $this->origin ? /** @scrutinizer ignore-deprecated */ $this->origin : $this->origin_type;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
2550
2551
        // Manage classes with non standard name
2552
        if ($origin == 'shipping') {
2553
            $origin = 'expedition';
2554
        }
2555
        if ($origin == 'delivery') {
2556
            $origin = 'livraison';
2557
        }
2558
        if ($origin == 'order_supplier' || $origin == 'supplier_order') {
2559
            $origin = 'commandeFournisseur';
2560
        }
2561
2562
        $classname = ucfirst($origin);
2563
        $this->origin_object = new $classname($this->db);
2564
        // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
2565
        $this->origin_object->fetch($this->origin_id);
2566
    }
2567
2568
    /**
2569
     *  Load object from specific field
2570
     *
2571
     * @param string $table Table element or element line
2572
     * @param string $field Field selected
2573
     * @param string $key Import key
2574
     * @param string $element Element name
2575
     * @return int<-1,1>|false     Return -1 or false if KO, >0 if OK
2576
     */
2577
    public function fetchObjectFrom($table, $field, $key, $element = null)
2578
    {
2579
        global $conf;
2580
2581
        $result = false;
2582
2583
        $sql = "SELECT rowid FROM " . $this->db->prefix() . $table;
2584
        $sql .= " WHERE " . $field . " = '" . $this->db->escape($key) . "'";
2585
        if (!empty($element)) {
2586
            $sql .= " AND entity IN (" . getEntity($element) . ")";
2587
        } else {
2588
            $sql .= " AND entity = " . ((int)$conf->entity);
2589
        }
2590
2591
        dol_syslog(get_class($this) . '::fetchObjectFrom', LOG_DEBUG);
2592
        $resql = $this->db->query($sql);
2593
        if ($resql) {
2594
            $obj = $this->db->fetch_object($resql);
2595
            // Test for avoid error -1
2596
            if ($obj) {
2597
                if (method_exists($this, 'fetch')) {
2598
                    $result = $this->fetch($obj->rowid);
2599
                } else {
2600
                    $this->error = 'fetch() method not implemented on ' . get_class($this);
2601
                    dol_syslog(get_class($this) . '::fetchOneLike Error=' . $this->error, LOG_ERR);
2602
                    array_push($this->errors, $this->error);
2603
                    $result = -1;
2604
                }
2605
            }
2606
        }
2607
2608
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result could also return false which is incompatible with the documented return type integer. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
2609
    }
2610
2611
    /**
2612
     *  Getter generic. Load value from a specific field
2613
     *
2614
     * @param string $table Table of element or element line
2615
     * @param int $id Element id
2616
     * @param string $field Field selected
2617
     * @return int<-1,1>           Return integer <0 if KO, >0 if OK
2618
     */
2619
    public function getValueFrom($table, $id, $field)
2620
    {
2621
        $result = false;
2622
        if (!empty($id) && !empty($field) && !empty($table)) {
2623
            $sql = "SELECT " . $field . " FROM " . $this->db->prefix() . $table;
2624
            $sql .= " WHERE rowid = " . ((int)$id);
2625
2626
            dol_syslog(get_class($this) . '::getValueFrom', LOG_DEBUG);
2627
            $resql = $this->db->query($sql);
2628
            if ($resql) {
2629
                $row = $this->db->fetch_row($resql);
2630
                $result = $row[0];
2631
            }
2632
        }
2633
        return $result;
2634
    }
2635
2636
    /**
2637
     *  Setter generic. Update a specific field into database.
2638
     *  Warning: Trigger is run only if param trigkey is provided.
2639
     *
2640
     * @param string $field Field to update
2641
     * @param mixed $value New value
2642
     * @param string $table To force other table element or element line (should not be used)
2643
     * @param int $id To force other object id (should not be used)
2644
     * @param string $format Data format ('text', 'int', 'date'). 'text' is used if not defined
2645
     * @param string $id_field To force rowid field name. 'rowid' is used if not defined
2646
     * @param User|string $fuser Update the user of last update field with this user. If not provided, current user is used except if value is 'none'
2647
     * @param string $trigkey Trigger key to run (in most cases something like 'XXX_MODIFY')
2648
     * @param string $fk_user_field Name of field to save user id making change
2649
     * @return int<-2,1>                   Return integer <0 if KO, >0 if OK
2650
     * @see updateExtraField()
2651
     */
2652
    public function setValueFrom($field, $value, $table = '', $id = null, $format = '', $id_field = '', $fuser = null, $trigkey = '', $fk_user_field = 'fk_user_modif')
2653
    {
2654
        global $user;
2655
2656
        if (empty($table)) {
2657
            $table = $this->table_element;
2658
        }
2659
        if (empty($id)) {
2660
            $id = $this->id;
2661
        }
2662
        if (empty($format)) {
2663
            $format = 'text';
2664
        }
2665
        if (empty($id_field)) {
2666
            $id_field = 'rowid';
2667
        }
2668
2669
        // Special case
2670
        if ($table == 'product' && $field == 'note_private') {
2671
            $field = 'note';
2672
        }
2673
2674
        if (in_array($table, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
2675
            $fk_user_field = 'fk_user_mod';
2676
        }
2677
        if (in_array($table, array('prelevement_bons'))) {  // TODO Add a field fk_user_modif into llx_prelevement_bons
2678
            $fk_user_field = '';
2679
        }
2680
2681
        $oldvalue = null;
2682
        if ($trigkey) {
2683
            $sql = "SELECT " . $field;
2684
            $sql .= " FROM " . MAIN_DB_PREFIX . $table;
2685
            $sql .= " WHERE " . $id_field . " = " . ((int)$id);
2686
2687
            $resql = $this->db->query($sql);
2688
            if ($resql) {
2689
                if ($obj = $this->db->fetch_object($resql)) {
2690
                    if ($format == 'date') {
2691
                        $oldvalue = $this->db->jdate($obj->$field);
2692
                    } else {
2693
                        $oldvalue = $obj->$field;
2694
                    }
2695
                }
2696
            } else {
2697
                $this->error = $this->db->lasterror();
2698
                return -1;
2699
            }
2700
        }
2701
2702
        $error = 0;
2703
2704
        dol_syslog(__METHOD__, LOG_DEBUG);
2705
2706
        $this->db->begin();
2707
2708
        $sql = "UPDATE " . $this->db->prefix() . $table . " SET ";
2709
2710
        if ($format == 'text') {
2711
            $sql .= $field . " = '" . $this->db->escape($value) . "'";
2712
        } elseif ($format == 'int') {
2713
            $sql .= $field . " = " . ((int)$value);
2714
        } elseif ($format == 'date') {
2715
            $sql .= $field . " = " . ($value ? "'" . $this->db->idate($value) . "'" : "null");
2716
        } elseif ($format == 'dategmt') {
2717
            $sql .= $field . " = " . ($value ? "'" . $this->db->idate($value, 'gmt') . "'" : "null");
2718
        }
2719
2720
        if ($fk_user_field) {
2721
            if (!empty($fuser) && is_object($fuser)) {
2722
                $sql .= ", " . $fk_user_field . " = " . ((int)$fuser->id);
2723
            } elseif (empty($fuser) || $fuser != 'none') {
2724
                $sql .= ", " . $fk_user_field . " = " . ((int)$user->id);
2725
            }
2726
        }
2727
2728
        $sql .= " WHERE " . $id_field . " = " . ((int)$id);
2729
2730
        $resql = $this->db->query($sql);
2731
        if ($resql) {
2732
            if ($trigkey) {
2733
                // call trigger with updated object values
2734
                if (method_exists($this, 'fetch')) {
2735
                    $result = $this->fetch($id);
2736
                } else {
2737
                    $result = $this->fetchCommon($id);
2738
                }
2739
                $this->oldcopy = clone $this;
2740
                if (property_exists($this->oldcopy, $field)) {
2741
                    $this->oldcopy->$field = $oldvalue;
2742
                }
2743
2744
                if ($result >= 0) {
2745
                    $result = $this->call_trigger($trigkey, (!empty($fuser) && is_object($fuser)) ? $fuser : $user); // This may set this->errors
2746
                }
2747
                if ($result < 0) {
2748
                    $error++;
2749
                }
2750
            }
2751
2752
            if (!$error) {
2753
                if (property_exists($this, $field)) {
2754
                    $this->$field = $value;
2755
                }
2756
                $this->db->commit();
2757
                return 1;
2758
            } else {
2759
                $this->db->rollback();
2760
                return -2;
2761
            }
2762
        } else {
2763
            if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
2764
                $this->error = 'DB_ERROR_RECORD_ALREADY_EXISTS';
2765
            } else {
2766
                $this->error = $this->db->lasterror();
2767
            }
2768
            $this->db->rollback();
2769
            return -1;
2770
        }
2771
    }
2772
2773
    /**
2774
     * Load object in memory from the database. This does not load line. This is done by parent fetch() that call fetchCommon
2775
     *
2776
     * @param int $id Id object
2777
     * @param string $ref Ref
2778
     * @param string $morewhere More SQL filters (' AND ...')
2779
     * @param int<0,1> $noextrafields 0=Default to load extrafields, 1=No extrafields
2780
     * @return  int<-4,1>                   Return integer <0 if KO, 0 if not found, >0 if OK
2781
     */
2782
    public function fetchCommon($id, $ref = null, $morewhere = '', $noextrafields = 0)
2783
    {
2784
        if (empty($id) && empty($ref) && empty($morewhere)) {
2785
            return -1;
2786
        }
2787
2788
        $fieldlist = $this->getFieldList('t');
2789
        if (empty($fieldlist)) {
2790
            return 0;
2791
        }
2792
2793
        $sql = "SELECT " . $fieldlist;
2794
        $sql .= " FROM " . $this->db->prefix() . $this->table_element . ' as t';
2795
2796
        if (!empty($id)) {
2797
            $sql .= ' WHERE t.rowid = ' . ((int)$id);
2798
        } elseif (!empty($ref)) {
2799
            $sql .= " WHERE t.ref = '" . $this->db->escape($ref) . "'";
2800
        } else {
2801
            $sql .= ' WHERE 1 = 1'; // usage with empty id and empty ref is very rare
2802
        }
2803
        if (empty($id) && isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2804
            $sql .= ' AND t.entity IN (' . getEntity($this->element) . ')';
2805
        }
2806
        if ($morewhere) {
2807
            $sql .= $morewhere;
2808
        }
2809
        $sql .= ' LIMIT 1'; // This is a fetch, to be certain to get only one record
2810
2811
        $res = $this->db->query($sql);
2812
        if ($res) {
2813
            $obj = $this->db->fetch_object($res);
2814
            if ($obj) {
2815
                $this->setVarsFromFetchObj($obj);
2816
2817
                // Retrieve all extrafield
2818
                // fetch optionals attributes and labels
2819
                if (empty($noextrafields)) {
2820
                    $result = $this->fetch_optionals();
2821
                    if ($result < 0) {
2822
                        $this->error = $this->db->lasterror();
2823
                        $this->errors[] = $this->error;
2824
                        return -4;
2825
                    }
2826
                }
2827
2828
                return $this->id;
2829
            } else {
2830
                return 0;
2831
            }
2832
        } else {
2833
            $this->error = $this->db->lasterror();
2834
            $this->errors[] = $this->error;
2835
            return -1;
2836
        }
2837
    }
2838
2839
    /**
2840
     * Function to concat keys of fields
2841
     *
2842
     * @param string $alias String of alias of table for fields. For example 't'. It is recommended to use '' and set alias into fields definition.
2843
     * @param string[] $excludefields Array of fields to exclude
2844
     * @return  string                      List of alias fields
2845
     */
2846
    public function getFieldList($alias = '', $excludefields = array())
2847
    {
2848
        $keys = array_keys($this->fields);
2849
        if (!empty($alias)) {
2850
            $keys_with_alias = array();
2851
            foreach ($keys as $fieldname) {
2852
                if (!empty($excludefields)) {
2853
                    if (in_array($fieldname, $excludefields)) { // The field is excluded and must not be in output
2854
                        continue;
2855
                    }
2856
                }
2857
                $keys_with_alias[] = $alias . '.' . $fieldname;
2858
            }
2859
            return implode(',', $keys_with_alias);
2860
        } else {
2861
            return implode(',', $keys);
2862
        }
2863
    }
2864
2865
    /**
2866
     * Function to load data from a SQL pointer into properties of current object $this
2867
     *
2868
     * @param stdClass $obj Contain data of object from database
0 ignored issues
show
Bug introduced by
The type Dolibarr\Core\Base\stdClass was not found. Did you mean stdClass? If so, make sure to prefix the type with \.
Loading history...
2869
     * @return void
2870
     */
2871
    public function setVarsFromFetchObj(&$obj)
2872
    {
2873
        global $db;
2874
2875
        foreach ($this->fields as $field => $info) {
2876
            if ($this->isDate($info)) {
2877
                if (!isset($obj->$field) || is_null($obj->$field) || $obj->$field === '' || $obj->$field === '0000-00-00 00:00:00' || $obj->$field === '1000-01-01 00:00:00') {
2878
                    $this->$field = '';
2879
                } else {
2880
                    $this->$field = $db->jdate($obj->$field);
2881
                }
2882
            } elseif ($this->isInt($info)) {
2883
                if ($field == 'rowid') {
2884
                    $this->id = (int)$obj->$field;
2885
                } else {
2886
                    if ($this->isForcedToNullIfZero($info)) {
2887
                        if (empty($obj->$field)) {
2888
                            $this->$field = null;
2889
                        } else {
2890
                            $this->$field = (int)$obj->$field;
2891
                        }
2892
                    } else {
2893
                        if (isset($obj->$field) && (!is_null($obj->$field) || (array_key_exists('notnull', $info) && $info['notnull'] == 1))) {
2894
                            $this->$field = (int)$obj->$field;
2895
                        } else {
2896
                            $this->$field = null;
2897
                        }
2898
                    }
2899
                }
2900
            } elseif ($this->isFloat($info)) {
2901
                if ($this->isForcedToNullIfZero($info)) {
2902
                    if (empty($obj->$field)) {
2903
                        $this->$field = null;
2904
                    } else {
2905
                        $this->$field = (float)$obj->$field;
2906
                    }
2907
                } else {
2908
                    if (isset($obj->$field) && (!is_null($obj->$field) || (array_key_exists('notnull', $info) && $info['notnull'] == 1))) {
2909
                        $this->$field = (float)$obj->$field;
2910
                    } else {
2911
                        $this->$field = null;
2912
                    }
2913
                }
2914
            } else {
2915
                $this->$field = isset($obj->$field) ? $obj->$field : null;
2916
            }
2917
        }
2918
2919
        // If there is no 'ref' field, we force property ->ref to ->id for a better compatibility with common functions.
2920
        if (!isset($this->fields['ref']) && isset($this->id)) {
2921
            $this->ref = (string)$this->id;
2922
        }
2923
    }
2924
2925
    /**
2926
     * Function test if type is date
2927
     *
2928
     * @param array{type:string,label:string,enabled:int<0,2>|string,position:int,notnull?:int,visible:int,noteditable?:int,default?:string,index?:int,foreignkey?:string,searchall?:int,isameasure?:int,css?:string,csslist?:string,help?:string,showoncombobox?:int,disabled?:int,arrayofkeyval?:array<int,string>,comment?:string} $info content information of field
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{type:string,label:...tring>,comment?:string} at position 12 could not be parsed: Expected '}' at position 12, but found 'int'.
Loading history...
2929
     * @return  bool            true if date
2930
     */
2931
    public function isDate($info)
2932
    {
2933
        if (isset($info['type']) && ($info['type'] == 'date' || $info['type'] == 'datetime' || $info['type'] == 'timestamp')) {
2934
            return true;
2935
        }
2936
        return false;
2937
    }
2938
2939
    /**
2940
     * Function test if type is integer
2941
     *
2942
     * @param array{type:string,label:string,enabled:int<0,2>|string,position:int,notnull?:int,visible:int,noteditable?:int,default?:string,index?:int,foreignkey?:string,searchall?:int,isameasure?:int,css?:string,csslist?:string,help?:string,showoncombobox?:int,disabled?:int,arrayofkeyval?:array<int,string>,comment?:string} $info Properties of field
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{type:string,label:...tring>,comment?:string} at position 12 could not be parsed: Expected '}' at position 12, but found 'int'.
Loading history...
2943
     * @return  bool            true if integer
2944
     */
2945
    public function isInt($info)
2946
    {
2947
        if (is_array($info)) {
2948
            if (isset($info['type']) && (preg_match('/(^int|int$)/i', $info['type']))) {
2949
                return true;
2950
            } else {
2951
                return false;
2952
            }
2953
        } else {
2954
            return false;
2955
        }
2956
    }
2957
2958
    /**
2959
     * Function test if field is forced to null if zero or empty
2960
     *
2961
     * @param array{type:string,label:string,enabled:int<0,2>|string,position:int,notnull?:int,visible:int,noteditable?:int,default?:string,index?:int,foreignkey?:string,searchall?:int,isameasure?:int,css?:string,csslist?:string,help?:string,showoncombobox?:int,disabled?:int,arrayofkeyval?:array<int,string>,comment?:string} $info content information of field
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{type:string,label:...tring>,comment?:string} at position 12 could not be parsed: Expected '}' at position 12, but found 'int'.
Loading history...
2962
     * @return  bool            true if forced to null
2963
     */
2964
    protected function isForcedToNullIfZero($info)
2965
    {
2966
        if (is_array($info)) {
2967
            if (array_key_exists('notnull', $info) && $info['notnull'] == '-1') {
2968
                return true;
2969
            } else {
2970
                return false;
2971
            }
2972
        }
2973
        return false;
2974
    }
2975
2976
    /**
2977
     * Function test if type is float
2978
     *
2979
     * @param array{type:string,label:string,enabled:int<0,2>|string,position:int,notnull?:int,visible:int,noteditable?:int,default?:string,index?:int,foreignkey?:string,searchall?:int,isameasure?:int,css?:string,csslist?:string,help?:string,showoncombobox?:int,disabled?:int,arrayofkeyval?:array<int,string>,comment?:string} $info content information of field
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{type:string,label:...tring>,comment?:string} at position 12 could not be parsed: Expected '}' at position 12, but found 'int'.
Loading history...
2980
     * @return  bool            true if float
2981
     */
2982
    public function isFloat($info)
2983
    {
2984
        if (is_array($info)) {
2985
            if (isset($info['type']) && (preg_match('/^(double|real|price)/i', $info['type']))) {
2986
                return true;
2987
            } else {
2988
                return false;
2989
            }
2990
        }
2991
        return false;
2992
    }
2993
2994
    /**
2995
     *  Function to get extra fields of an object into $this->array_options
2996
     *  This method is in most cases called by method fetch of objects but you can call it separately.
2997
     *
2998
     * @param int $rowid Id of line. Use the id of object if not defined. Deprecated. Function must be called without parameters.
2999
     * @param 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} $optionsArray Array resulting of call of extrafields->fetch_name_optionals_label(). Deprecated. Function must be called without parameters.
3000
     * @return int<-1,1>               Return integer <0 if error, 0 if no values of extrafield to find nor found, 1 if an attribute is found and value loaded
3001
     * @see fetchValuesForExtraLanguages()
3002
     */
3003
    public function fetch_optionals($rowid = null, $optionsArray = null)
3004
    {
3005
        // phpcs:enable
3006
        global $conf, $extrafields;
3007
3008
        if (empty($rowid)) {
3009
            $rowid = $this->id;
3010
        }
3011
        if (empty($rowid) && isset($this->rowid)) {
0 ignored issues
show
Bug Best Practice introduced by
The property rowid does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
3012
            $rowid = $this->rowid; // deprecated
3013
        }
3014
3015
        // To avoid SQL errors. Probably not the better solution though
3016
        if (!$this->table_element) {
3017
            return 0;
3018
        }
3019
3020
        $this->array_options = array();
3021
3022
        if (!is_array($optionsArray)) {
3023
            // If $extrafields is not a known object, we initialize it. Best practice is to have $extrafields defined into card.php or list.php page.
3024
            if (!isset($extrafields) || !is_object($extrafields)) {
3025
                require_once constant('DOL_DOCUMENT_ROOT') . '/core/class/extrafields.class.php';
3026
                $extrafields = new ExtraFields($this->db);
3027
            }
3028
3029
            // Load array of extrafields for elementype = $this->table_element
3030
            if (empty($extrafields->attributes[$this->table_element]['loaded'])) {
3031
                $extrafields->fetch_name_optionals_label($this->table_element);
3032
            }
3033
            $optionsArray = (!empty($extrafields->attributes[$this->table_element]['label']) ? $extrafields->attributes[$this->table_element]['label'] : null);
3034
        } else {
3035
            global $extrafields;
3036
            dol_syslog("Warning: fetch_optionals was called with param optionsArray defined when you should pass null now", LOG_WARNING);
3037
        }
3038
3039
        $table_element = $this->table_element;
3040
        if ($table_element == 'categorie') {
3041
            $table_element = 'categories'; // For compatibility
3042
        }
3043
3044
        // Request to get complementary values
3045
        if (is_array($optionsArray) && count($optionsArray) > 0) {
3046
            $sql = "SELECT rowid";
3047
            foreach ($optionsArray as $name => $label) {
3048
                if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || (!in_array($extrafields->attributes[$this->table_element]['type'][$name], ['separate', 'point', 'multipts', 'linestrg', 'polygon']))) {
3049
                    $sql .= ", " . $name;
3050
                }
3051
                // use geo sql fonction to read as text
3052
                if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || $extrafields->attributes[$this->table_element]['type'][$name] == 'point') {
3053
                    $sql .= ", ST_AsWKT(" . $name . ") as " . $name;
3054
                }
3055
                if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || $extrafields->attributes[$this->table_element]['type'][$name] == 'multipts') {
3056
                    $sql .= ", ST_AsWKT(" . $name . ") as " . $name;
3057
                }
3058
                if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || $extrafields->attributes[$this->table_element]['type'][$name] == 'linestrg') {
3059
                    $sql .= ", ST_AsWKT(" . $name . ") as " . $name;
3060
                }
3061
                if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || $extrafields->attributes[$this->table_element]['type'][$name] == 'polygon') {
3062
                    $sql .= ", ST_AsWKT(" . $name . ") as " . $name;
3063
                }
3064
            }
3065
            $sql .= " FROM " . $this->db->prefix() . $table_element . "_extrafields";
3066
            $sql .= " WHERE fk_object = " . ((int)$rowid);
3067
3068
            //dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG);       // Too verbose
3069
            $resql = $this->db->query($sql);
3070
            if ($resql) {
3071
                $numrows = $this->db->num_rows($resql);
3072
                if ($numrows) {
3073
                    $tab = $this->db->fetch_array($resql);
3074
3075
                    foreach ($tab as $key => $value) {
3076
                        // Test fetch_array ! is_int($key) because fetch_array result is a mix table with Key as alpha and Key as int (depend db engine)
3077
                        if ($key != 'rowid' && $key != 'tms' && $key != 'fk_member' && !is_int($key)) {
3078
                            // we can add this attribute to object
3079
                            if (!empty($extrafields->attributes[$this->table_element]) && in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date', 'datetime'))) {
3080
                                //var_dump($extrafields->attributes[$this->table_element]['type'][$key]);
3081
                                $this->array_options["options_" . $key] = $this->db->jdate($value);
3082
                            } else {
3083
                                $this->array_options["options_" . $key] = $value;
3084
                            }
3085
3086
                            //var_dump('key '.$key.' '.$value.' type='.$extrafields->attributes[$this->table_element]['type'][$key].' '.$this->array_options["options_".$key]);
3087
                        }
3088
                        if (!empty($extrafields->attributes[$this->table_element]['type'][$key]) && $extrafields->attributes[$this->table_element]['type'][$key] == 'password') {
3089
                            if (!empty($value) && preg_match('/^dolcrypt:/', $value)) {
3090
                                $this->array_options["options_" . $key] = dolDecrypt($value);
3091
                            }
3092
                        }
3093
                    }
3094
                } else {
3095
                    /**
3096
                     * We are in a situation where the current object has no values in its extra fields.
3097
                     * We want to initialize all the values to null so that the array_option is accessible in other contexts (especially in document generation).
3098
                     **/
3099
                    if (is_array($extrafields->attributes[$this->table_element]['label'])) {
3100
                        foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
3101
                            $this->array_options['options_' . $key] = null;
3102
                        }
3103
                    }
3104
                }
3105
3106
                // If field is a computed field, value must become result of compute (regardless of whether a row exists
3107
                // in the element's extrafields table)
3108
                if (is_array($extrafields->attributes[$this->table_element]['label'])) {
3109
                    foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
3110
                        if (!empty($extrafields->attributes[$this->table_element]) && !empty($extrafields->attributes[$this->table_element]['computed'][$key])) {
3111
                            //var_dump($conf->disable_compute);
3112
                            if (empty($conf->disable_compute)) {
3113
                                global $objectoffield;        // We set a global variable to $objectoffield so
3114
                                $objectoffield = $this;        // we can use it inside computed formula
3115
                                $this->array_options['options_' . $key] = dol_eval($extrafields->attributes[$this->table_element]['computed'][$key], 1, 0, '2');
3116
                            }
3117
                        }
3118
                    }
3119
                }
3120
3121
                $this->db->free($resql);
3122
3123
                if ($numrows) {
3124
                    return $numrows;
3125
                } else {
3126
                    return 0;
3127
                }
3128
            } else {
3129
                $this->errors[] = $this->db->lasterror;
3130
                return -1;
3131
            }
3132
        }
3133
        return 0;
3134
    }
3135
3136
    /**
3137
     *      Load properties id_previous and id_next by comparing $fieldid with $this->ref
3138
     *
3139
     * @param string $filter Optional SQL filter. Use SQL or Universal Search Filter.
3140
     *                                  Example: "(t.field1 = 'aa' OR t.field2 = 'bb')". Do not allow user input data here with this syntax.
3141
     *                                  Example: "((t.field1:=:'aa') OR (t.field2:=:'bb'))".
3142
     * @param string $fieldid Name of field to use for the select MAX and MIN
3143
     * @param int<0,1> $nodbprefix Do not include DB prefix to forge table name
3144
     * @return int<-2,1>           Return integer <0 if KO, >0 if OK
3145
     */
3146
    public function load_previous_next_ref($filter, $fieldid, $nodbprefix = 0)
3147
    {
3148
        // phpcs:enable
3149
        global $conf, $user;
3150
3151
        if (!$this->table_element) {
3152
            dol_print_error(null, get_class($this) . "::load_previous_next_ref was called on object with property table_element not defined");
3153
            return -1;
3154
        }
3155
        if ($fieldid == 'none') {
3156
            return 1;
3157
        }
3158
3159
        // For backward compatibility
3160
        if (in_array($this->table_element, array('facture_rec', 'facture_fourn_rec')) && $fieldid == 'title') {
3161
            $fieldid = 'titre';
3162
        }
3163
3164
        // Security on socid
3165
        $socid = 0;
3166
        if ($user->socid > 0) {
3167
            $socid = $user->socid;
3168
        }
3169
3170
        // this->ismultientitymanaged contains
3171
        // 0=No test on entity, 1=Test with field entity, 'field@table'=Test with link by field@table
3172
        $aliastablesociete = 's';
3173
        if ($this->element == 'societe') {
3174
            $aliastablesociete = 'te'; // te as table_element
3175
        }
3176
        $restrictiononfksoc = empty($this->restrictiononfksoc) ? 0 : $this->restrictiononfksoc;
3177
        $sql = "SELECT MAX(te." . $fieldid . ")";
3178
        $sql .= " FROM " . (empty($nodbprefix) ? $this->db->prefix() : '') . $this->table_element . " as te";
3179
        if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
3180
            $tmparray = explode('@', $this->ismultientitymanaged);
3181
            $sql .= ", " . $this->db->prefix() . $tmparray[1] . " as " . ($tmparray[1] == 'societe' ? 's' : 'parenttable'); // If we need to link to this table to limit select to entity
3182
        } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
3183
            $sql .= ", " . $this->db->prefix() . "societe as s"; // If we need to link to societe to limit select to socid
3184
        } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
3185
            $sql .= " LEFT JOIN " . $this->db->prefix() . "societe as s ON te.fk_soc = s.rowid"; // If we need to link to societe to limit select to socid
3186
        }
3187
        if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
3188
            $sql .= " LEFT JOIN " . $this->db->prefix() . "societe_commerciaux as sc ON " . $aliastablesociete . ".rowid = sc.fk_soc";
3189
        }
3190
        if ($fieldid == 'rowid') {
3191
            $sql .= " WHERE te." . $fieldid . " < " . ((int)$this->id);
3192
        } else {
3193
            $sql .= " WHERE te." . $fieldid . " < '" . $this->db->escape($this->ref) . "'"; // ->ref must always be defined (set to id if field does not exists)
3194
        }
3195
        if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
3196
            $sql .= " AND sc.fk_user = " . ((int)$user->id);
3197
        }
3198
        if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
3199
            $sql .= " AND (sc.fk_user = " . ((int)$user->id) . ' OR te.fk_soc IS NULL)';
3200
        }
3201
3202
        $filtermax = $filter;
3203
3204
        // Manage filter
3205
        $errormessage = '';
3206
        $tmpsql = forgeSQLFromUniversalSearchCriteria($filtermax, $errormessage);
3207
        if ($errormessage) {
3208
            if (!preg_match('/^\s*AND/i', $filtermax)) {
3209
                $sql .= " AND ";
3210
            }
3211
            $sql .= $filtermax;
3212
        } else {
3213
            $sql .= $tmpsql;
3214
        }
3215
3216
        if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
3217
            $tmparray = explode('@', $this->ismultientitymanaged);
3218
            $sql .= " AND te." . $tmparray[0] . " = " . ($tmparray[1] == "societe" ? "s" : "parenttable") . ".rowid"; // If we need to link to this table to limit select to entity
3219
        } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
3220
            $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
3221
        }
3222
        if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
3223
            if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
3224
                if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
3225
                    $sql .= " AND te.entity IS NOT NULL"; // Show all users
3226
                } else {
3227
                    $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM " . $this->db->prefix() . "usergroup_user as ug WHERE ug.entity IN (" . getEntity('usergroup') . "))";
3228
                }
3229
            } else {
3230
                $sql .= ' AND te.entity IN (' . getEntity($this->element) . ')';
3231
            }
3232
        }
3233
        if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
3234
            $tmparray = explode('@', $this->ismultientitymanaged);
3235
            $sql .= ' AND parenttable.entity IN (' . getEntity($tmparray[1]) . ')';
3236
        }
3237
        if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
3238
            $sql .= ' AND te.fk_soc = ' . ((int)$socid);
3239
        }
3240
        if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
3241
            $sql .= ' AND (te.fk_soc = ' . ((int)$socid) . ' OR te.fk_soc IS NULL)';
3242
        }
3243
        if ($restrictiononfksoc && $socid && $this->element == 'societe') {
3244
            $sql .= ' AND te.rowid = ' . ((int)$socid);
3245
        }
3246
        //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
3247
3248
        $result = $this->db->query($sql);
3249
        if (!$result) {
3250
            $this->error = $this->db->lasterror();
3251
            return -1;
3252
        }
3253
        $row = $this->db->fetch_row($result);
3254
        $this->ref_previous = $row[0];
3255
3256
        $sql = "SELECT MIN(te." . $fieldid . ")";
3257
        $sql .= " FROM " . (empty($nodbprefix) ? $this->db->prefix() : '') . $this->table_element . " as te";
3258
        if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
3259
            $tmparray = explode('@', $this->ismultientitymanaged);
3260
            $sql .= ", " . $this->db->prefix() . $tmparray[1] . " as " . ($tmparray[1] == 'societe' ? 's' : 'parenttable'); // If we need to link to this table to limit select to entity
3261
        } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
3262
            $sql .= ", " . $this->db->prefix() . "societe as s"; // If we need to link to societe to limit select to socid
3263
        } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
3264
            $sql .= " LEFT JOIN " . $this->db->prefix() . "societe as s ON te.fk_soc = s.rowid"; // If we need to link to societe to limit select to socid
3265
        }
3266
        if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
3267
            $sql .= " LEFT JOIN " . $this->db->prefix() . "societe_commerciaux as sc ON " . $aliastablesociete . ".rowid = sc.fk_soc";
3268
        }
3269
        if ($fieldid == 'rowid') {
3270
            $sql .= " WHERE te." . $fieldid . " > " . ((int)$this->id);
3271
        } else {
3272
            $sql .= " WHERE te." . $fieldid . " > '" . $this->db->escape($this->ref) . "'"; // ->ref must always be defined (set to id if field does not exists)
3273
        }
3274
        if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
3275
            $sql .= " AND (sc.fk_user = " . ((int)$user->id);
3276
            if (getDolGlobalInt('MAIN_SEE_SUBORDINATES')) {
3277
                $userschilds = $user->getAllChildIds();
3278
                $sql .= " OR sc.fk_user IN (" . $this->db->sanitize(implode(',', $userschilds)) . ")";
3279
            }
3280
            $sql .= ')';
3281
        }
3282
        if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
3283
            $sql .= " AND (sc.fk_user = " . ((int)$user->id) . ' OR te.fk_soc IS NULL)';
3284
        }
3285
3286
        $filtermin = $filter;
3287
3288
        // Manage filter
3289
        $errormessage = '';
3290
        $tmpsql = forgeSQLFromUniversalSearchCriteria($filtermin, $errormessage);
3291
        if ($errormessage) {
3292
            if (!preg_match('/^\s*AND/i', $filtermin)) {
3293
                $sql .= " AND ";
3294
            }
3295
            $sql .= $filtermin;
3296
3297
            $filtermin = '';
3298
        } else {
3299
            $sql .= $tmpsql;
3300
        }
3301
3302
        if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
3303
            $tmparray = explode('@', $this->ismultientitymanaged);
3304
            $sql .= " AND te." . $tmparray[0] . " = " . ($tmparray[1] == "societe" ? "s" : "parenttable") . ".rowid"; // If we need to link to this table to limit select to entity
3305
        } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
3306
            $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
3307
        }
3308
        if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
3309
            if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
3310
                if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
3311
                    $sql .= " AND te.entity IS NOT NULL"; // Show all users
3312
                } else {
3313
                    $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM " . $this->db->prefix() . "usergroup_user as ug WHERE ug.entity IN (" . getEntity('usergroup') . "))";
3314
                }
3315
            } else {
3316
                $sql .= ' AND te.entity IN (' . getEntity($this->element) . ')';
3317
            }
3318
        }
3319
        if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
3320
            $tmparray = explode('@', $this->ismultientitymanaged);
3321
            $sql .= ' AND parenttable.entity IN (' . getEntity($tmparray[1]) . ')';
3322
        }
3323
        if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
3324
            $sql .= ' AND te.fk_soc = ' . ((int)$socid);
3325
        }
3326
        if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
3327
            $sql .= ' AND (te.fk_soc = ' . ((int)$socid) . ' OR te.fk_soc IS NULL)';
3328
        }
3329
        if ($restrictiononfksoc && $socid && $this->element == 'societe') {
3330
            $sql .= ' AND te.rowid = ' . ((int)$socid);
3331
        }
3332
        //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
3333
        // Rem: Bug in some mysql version: SELECT MIN(rowid) FROM llx_socpeople WHERE rowid > 1 when one row in database with rowid=1, returns 1 instead of null
3334
3335
        $result = $this->db->query($sql);
3336
        if (!$result) {
3337
            $this->error = $this->db->lasterror();
3338
            return -2;
3339
        }
3340
        $row = $this->db->fetch_row($result);
3341
        $this->ref_next = $row[0];
3342
3343
        return 1;
3344
    }
3345
3346
3347
    // TODO: Move line related operations to CommonObjectLine?
3348
3349
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3350
3351
    /**
3352
     *      Return list of id of contacts of object
3353
     *
3354
     * @param string $source Source of contact: external (llx_socpeople) or internal (llx_user) or thirdparty (llx_societe)
3355
     * @return int[]               Array of id of contacts (if source=external or internal)
3356
     *                                  Array of id of third parties with at least one contact on object (if source=thirdparty)
3357
     */
3358
    public function getListContactId($source = 'external')
3359
    {
3360
        $contactAlreadySelected = array();
3361
        $tab = $this->liste_contact(-1, $source);
3362
        $num = count($tab);
3363
        $i = 0;
3364
        while ($i < $num) {
3365
            if ($source == 'thirdparty') {
3366
                $contactAlreadySelected[$i] = $tab[$i]['socid'];
3367
            } else {
3368
                $contactAlreadySelected[$i] = $tab[$i]['id'];
3369
            }
3370
            $i++;
3371
        }
3372
        return $contactAlreadySelected;
3373
    }
3374
3375
    /**
3376
     *  Link element with a project
3377
     *
3378
     * @param int $projectid Project id to link element to
3379
     * @param int<0,1> $notrigger Disable the trigger
3380
     * @return     int<-1,1>               Return integer <0 if KO, >0 if OK
3381
     */
3382
    public function setProject($projectid, $notrigger = 0)
3383
    {
3384
        global $user;
3385
        $error = 0;
3386
3387
        if (!$this->table_element) {
3388
            dol_syslog(get_class($this) . "::setProject was called on object with property table_element not defined", LOG_ERR);
3389
            return -1;
3390
        }
3391
3392
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
3393
        if (!empty($this->fields['fk_project'])) {      // Common case
3394
            if ($projectid) {
3395
                $sql .= " SET fk_project = " . ((int)$projectid);
3396
            } else {
3397
                $sql .= " SET fk_project = NULL";
3398
            }
3399
            $sql .= ' WHERE rowid = ' . ((int)$this->id);
3400
        } elseif ($this->table_element == 'actioncomm') {   // Special case for actioncomm
3401
            if ($projectid) {
3402
                $sql .= " SET fk_project = " . ((int)$projectid);
3403
            } else {
3404
                $sql .= " SET fk_project = NULL";
3405
            }
3406
            $sql .= ' WHERE id = ' . ((int)$this->id);
3407
        } else { // Special case for old architecture objects
3408
            if ($projectid) {
3409
                $sql .= ' SET fk_projet = ' . ((int)$projectid);
3410
            } else {
3411
                $sql .= ' SET fk_projet = NULL';
3412
            }
3413
            $sql .= " WHERE rowid = " . ((int)$this->id);
3414
        }
3415
3416
        $this->db->begin();
3417
3418
        dol_syslog(get_class($this) . "::setProject", LOG_DEBUG);
3419
        if ($this->db->query($sql)) {
3420
            $this->fk_project = ((int)$projectid);
3421
        } else {
3422
            dol_print_error($this->db);
3423
            $error++;
3424
        }
3425
3426
        // Triggers
3427
        if (!$error && !$notrigger) {
3428
            // Call triggers
3429
            $result = $this->call_trigger(strtoupper($this->element) . '_MODIFY', $user);
3430
            if ($result < 0) {
3431
                $error++;
3432
            } //Do also here what you must do to rollback action if trigger fail
3433
            // End call triggers
3434
        }
3435
3436
        // Commit or rollback
3437
        if ($error) {
3438
            $this->db->rollback();
3439
            return -1;
3440
        } else {
3441
            $this->db->commit();
3442
            return 1;
3443
        }
3444
    }
3445
3446
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3447
3448
    /**
3449
     *  Change the payments methods
3450
     *
3451
     * @param int $id Id of new payment method
3452
     * @return     int             >0 if OK, <0 if KO
3453
     */
3454
    public function setPaymentMethods($id)
3455
    {
3456
        global $user;
3457
3458
        $error = 0;
3459
        $notrigger = 0;
3460
3461
        dol_syslog(get_class($this) . '::setPaymentMethods(' . $id . ')');
3462
3463
        if ($this->status >= 0 || $this->element == 'societe') {
3464
            // TODO uniformize field name
3465
            $fieldname = 'fk_mode_reglement';
3466
            if ($this->element == 'societe') {
3467
                $fieldname = 'mode_reglement';
3468
            }
3469
            if (get_class($this) == 'Fournisseur') {
3470
                $fieldname = 'mode_reglement_supplier';
3471
            }
3472
            if (get_class($this) == 'Tva') {
3473
                $fieldname = 'fk_typepayment';
3474
            }
3475
            if (get_class($this) == 'Salary') {
3476
                $fieldname = 'fk_typepayment';
3477
            }
3478
3479
            $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
3480
            $sql .= " SET " . $fieldname . " = " . (($id > 0 || $id == '0') ? ((int)$id) : 'NULL');
3481
            $sql .= ' WHERE rowid=' . ((int)$this->id);
3482
3483
            if ($this->db->query($sql)) {
3484
                $this->mode_reglement_id = $id;
3485
                // for supplier
3486
                if (get_class($this) == 'Fournisseur') {
3487
                    $this->mode_reglement_supplier_id = $id;
3488
                }
3489
                // Triggers
3490
                if (!$error && !$notrigger) {
3491
                    // Call triggers
3492
                    if (get_class($this) == 'Commande') {
3493
                        $result = $this->call_trigger('ORDER_MODIFY', $user);
3494
                    } else {
3495
                        $result = $this->call_trigger(strtoupper(get_class($this)) . '_MODIFY', $user);
3496
                    }
3497
                    if ($result < 0) {
3498
                        $error++;
3499
                    }
3500
                    // End call triggers
3501
                }
3502
                return 1;
3503
            } else {
3504
                dol_syslog(get_class($this) . '::setPaymentMethods Error ' . $this->db->error());
3505
                $this->error = $this->db->error();
3506
                return -1;
3507
            }
3508
        } else {
3509
            dol_syslog(get_class($this) . '::setPaymentMethods, status of the object is incompatible');
3510
            $this->error = 'Status of the object is incompatible ' . $this->status;
3511
            return -2;
3512
        }
3513
    }
3514
3515
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3516
3517
    /**
3518
     *  Change the multicurrency code
3519
     *
3520
     * @param string $code multicurrency code
3521
     * @return     int             >0 if OK, <0 if KO
3522
     */
3523
    public function setMulticurrencyCode($code)
3524
    {
3525
        dol_syslog(get_class($this) . '::setMulticurrencyCode(' . $code . ')');
3526
        if ($this->status >= 0 || $this->element == 'societe') {
3527
            $fieldname = 'multicurrency_code';
3528
3529
            $sql = 'UPDATE ' . $this->db->prefix() . $this->table_element;
3530
            $sql .= " SET " . $fieldname . " = '" . $this->db->escape($code) . "'";
3531
            $sql .= ' WHERE rowid=' . ((int)$this->id);
3532
3533
            if ($this->db->query($sql)) {
3534
                $this->multicurrency_code = $code;
3535
3536
                list($fk_multicurrency, $rate) = MultiCurrency::getIdAndTxFromCode($this->db, $code);
3537
                if ($rate) {
3538
                    $this->setMulticurrencyRate($rate, 2);
3539
                }
3540
3541
                return 1;
3542
            } else {
3543
                dol_syslog(get_class($this) . '::setMulticurrencyCode Error ' . $sql . ' - ' . $this->db->error());
3544
                $this->error = $this->db->error();
3545
                return -1;
3546
            }
3547
        } else {
3548
            dol_syslog(get_class($this) . '::setMulticurrencyCode, status of the object is incompatible');
3549
            $this->error = 'Status of the object is incompatible ' . $this->status;
3550
            return -2;
3551
        }
3552
    }
3553
3554
    /**
3555
     *  Change the multicurrency rate
3556
     *
3557
     * @param double $rate multicurrency rate
3558
     * @param int $mode mode 1 : amounts in company currency will be recalculated, mode 2 : amounts in foreign currency will be recalculated
3559
     * @return     int             >0 if OK, <0 if KO
3560
     */
3561
    public function setMulticurrencyRate($rate, $mode = 1)
3562
    {
3563
        dol_syslog(get_class($this) . '::setMulticurrencyRate(' . $rate . ', ' . $mode . ')');
3564
        if ($this->status >= 0 || $this->element == 'societe') {
3565
            $fieldname = 'multicurrency_tx';
3566
3567
            $sql = 'UPDATE ' . $this->db->prefix() . $this->table_element;
3568
            $sql .= " SET " . $fieldname . " = " . ((float)$rate);
3569
            $sql .= ' WHERE rowid=' . ((int)$this->id);
3570
3571
            if ($this->db->query($sql)) {
3572
                $this->multicurrency_tx = $rate;
3573
3574
                // Update line price
3575
                if (!empty($this->lines)) {
3576
                    foreach ($this->lines as &$line) {
3577
                        // Amounts in company currency will be recalculated
3578
                        if ($mode == 1) {
3579
                            $line->subprice = 0;
3580
                        }
3581
3582
                        // Amounts in foreign currency will be recalculated
3583
                        if ($mode == 2) {
3584
                            $line->multicurrency_subprice = 0;
3585
                        }
3586
3587
                        switch ($this->element) {
3588
                            case 'propal':
3589
                                /** @var Propal $this */
3590
                                /** @var PropaleLigne $line */
3591
                                $this->updateline(
0 ignored issues
show
Bug introduced by
The method updateline() does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

3591
                                $this->/** @scrutinizer ignore-call */ 
3592
                                       updateline(
Loading history...
3592
                                    $line->id,
3593
                                    $line->subprice,
3594
                                    $line->qty,
3595
                                    $line->remise_percent,
3596
                                    $line->tva_tx,
3597
                                    $line->localtax1_tx,
3598
                                    $line->localtax2_tx,
3599
                                    ($line->description ? $line->description : $line->desc),
3600
                                    'HT',
3601
                                    $line->info_bits,
3602
                                    $line->special_code,
3603
                                    $line->fk_parent_line,
3604
                                    $line->skip_update_total,
3605
                                    $line->fk_fournprice,
3606
                                    $line->pa_ht,
3607
                                    $line->label,
3608
                                    $line->product_type,
3609
                                    $line->date_start,
3610
                                    $line->date_end,
3611
                                    $line->array_options,
3612
                                    $line->fk_unit,
3613
                                    $line->multicurrency_subprice
3614
                                );
3615
                                break;
3616
                            case 'commande':
3617
                                /** @var Commande $this */
3618
                                /** @var OrderLine $line */
3619
                                $this->updateline(
3620
                                    $line->id,
3621
                                    ($line->description ? $line->description : $line->desc),
3622
                                    $line->subprice,
3623
                                    $line->qty,
3624
                                    $line->remise_percent,
3625
                                    $line->tva_tx,
3626
                                    $line->localtax1_tx,
3627
                                    $line->localtax2_tx,
3628
                                    'HT',
3629
                                    $line->info_bits,
3630
                                    $line->date_start,
3631
                                    $line->date_end,
3632
                                    $line->product_type,
3633
                                    $line->fk_parent_line,
3634
                                    $line->skip_update_total,
3635
                                    $line->fk_fournprice,
3636
                                    $line->pa_ht,
3637
                                    $line->label,
3638
                                    $line->special_code,
3639
                                    $line->array_options,
3640
                                    $line->fk_unit,
3641
                                    $line->multicurrency_subprice
3642
                                );
3643
                                break;
3644
                            case 'facture':
3645
                                /** @var Facture $this */
3646
                                /** @var FactureLigne $line */
3647
                                $this->updateline(
3648
                                    $line->id,
3649
                                    ($line->description ? $line->description : $line->desc),
3650
                                    $line->subprice,
3651
                                    $line->qty,
3652
                                    $line->remise_percent,
3653
                                    $line->date_start,
3654
                                    $line->date_end,
3655
                                    $line->tva_tx,
3656
                                    $line->localtax1_tx,
3657
                                    $line->localtax2_tx,
3658
                                    'HT',
3659
                                    $line->info_bits,
3660
                                    $line->product_type,
3661
                                    $line->fk_parent_line,
3662
                                    $line->skip_update_total,
3663
                                    $line->fk_fournprice,
3664
                                    $line->pa_ht,
3665
                                    $line->label,
3666
                                    $line->special_code,
3667
                                    $line->array_options,
3668
                                    $line->situation_percent,
3669
                                    $line->fk_unit,
3670
                                    $line->multicurrency_subprice
3671
                                );
3672
                                break;
3673
                            case 'supplier_proposal':
3674
                                /** @var SupplierProposal $this */
3675
                                /** @var SupplierProposalLine $line */
3676
                                $this->updateline(
3677
                                    $line->id,
3678
                                    $line->subprice,
3679
                                    $line->qty,
3680
                                    $line->remise_percent,
3681
                                    $line->tva_tx,
3682
                                    $line->localtax1_tx,
3683
                                    $line->localtax2_tx,
3684
                                    ($line->description ? $line->description : $line->desc),
3685
                                    'HT',
3686
                                    $line->info_bits,
3687
                                    $line->special_code,
3688
                                    $line->fk_parent_line,
3689
                                    $line->skip_update_total,
3690
                                    $line->fk_fournprice,
3691
                                    $line->pa_ht,
3692
                                    $line->label,
3693
                                    $line->product_type,
3694
                                    $line->array_options,
3695
                                    $line->ref_fourn,
3696
                                    $line->multicurrency_subprice
3697
                                );
3698
                                break;
3699
                            case 'order_supplier':
3700
                                /** @var CommandeFournisseur $this */
3701
                                /** @var CommandeFournisseurLigne $line */
3702
                                $this->updateline(
3703
                                    $line->id,
3704
                                    ($line->description ? $line->description : $line->desc),
3705
                                    $line->subprice,
3706
                                    $line->qty,
3707
                                    $line->remise_percent,
3708
                                    $line->tva_tx,
3709
                                    $line->localtax1_tx,
3710
                                    $line->localtax2_tx,
3711
                                    'HT',
3712
                                    $line->info_bits,
3713
                                    $line->product_type,
3714
                                    false,
3715
                                    $line->date_start,
3716
                                    $line->date_end,
3717
                                    $line->array_options,
3718
                                    $line->fk_unit,
3719
                                    $line->multicurrency_subprice,
3720
                                    $line->ref_supplier
3721
                                );
3722
                                break;
3723
                            case 'invoice_supplier':
3724
                                /** @var FactureFournisseur $this */
3725
                                /** @var SupplierInvoiceLine $line */
3726
                                $this->updateline(
3727
                                    $line->id,
3728
                                    ($line->description ? $line->description : $line->desc),
3729
                                    $line->subprice,
3730
                                    $line->tva_tx,
3731
                                    $line->localtax1_tx,
3732
                                    $line->localtax2_tx,
3733
                                    $line->qty,
3734
                                    0,
3735
                                    'HT',
3736
                                    $line->info_bits,
3737
                                    $line->product_type,
3738
                                    $line->remise_percent,
3739
                                    false,
3740
                                    $line->date_start,
3741
                                    $line->date_end,
3742
                                    $line->array_options,
3743
                                    $line->fk_unit,
3744
                                    $line->multicurrency_subprice,
3745
                                    $line->ref_supplier
3746
                                );
3747
                                break;
3748
                            default:
3749
                                dol_syslog(get_class($this) . '::setMulticurrencyRate no updateline defined', LOG_DEBUG);
3750
                                break;
3751
                        }
3752
                    }
3753
                }
3754
3755
                return 1;
3756
            } else {
3757
                dol_syslog(get_class($this) . '::setMulticurrencyRate Error ' . $sql . ' - ' . $this->db->error());
3758
                $this->error = $this->db->error();
3759
                return -1;
3760
            }
3761
        } else {
3762
            dol_syslog(get_class($this) . '::setMulticurrencyRate, status of the object is incompatible');
3763
            $this->error = 'Status of the object is incompatible ' . $this->status;
3764
            return -2;
3765
        }
3766
    }
3767
3768
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3769
3770
    /**
3771
     *  Change the payments terms
3772
     *
3773
     * @param int $id Id of new payment terms
3774
     * @param float $deposit_percent % of deposit if needed by payment terms
3775
     * @return     int                         >0 if OK, <0 if KO
3776
     */
3777
    public function setPaymentTerms($id, $deposit_percent = null)
3778
    {
3779
        dol_syslog(get_class($this) . '::setPaymentTerms(' . $id . ', ' . var_export($deposit_percent, true) . ')');
3780
        if ($this->status >= 0 || $this->element == 'societe') {
3781
            // TODO uniformize field name
3782
            $fieldname = 'fk_cond_reglement';
3783
            if ($this->element == 'societe') {
3784
                $fieldname = 'cond_reglement';
3785
            }
3786
            if (get_class($this) == 'Fournisseur') {
3787
                $fieldname = 'cond_reglement_supplier';
3788
            }
3789
3790
            if (empty($deposit_percent) || $deposit_percent < 0) {
3791
                $deposit_percent = (float)getDictionaryValue('c_payment_term', 'deposit_percent', $id);
3792
            }
3793
3794
            if ($deposit_percent > 100) {
3795
                $deposit_percent = 100;
3796
            }
3797
3798
            $sql = 'UPDATE ' . $this->db->prefix() . $this->table_element;
3799
            $sql .= " SET " . $fieldname . " = " . (($id > 0 || $id == '0') ? ((int)$id) : 'NULL');
3800
            if (in_array($this->table_element, array('propal', 'commande', 'societe'))) {
3801
                $sql .= " , deposit_percent = " . (empty($deposit_percent) ? 'NULL' : "'" . $this->db->escape($deposit_percent) . "'");
3802
            }
3803
            $sql .= ' WHERE rowid=' . ((int)$this->id);
3804
3805
            if ($this->db->query($sql)) {
3806
                $this->cond_reglement_id = $id;
3807
                // for supplier
3808
                if (get_class($this) == 'Fournisseur') {
3809
                    $this->cond_reglement_supplier_id = $id;
3810
                }
3811
                $this->cond_reglement = $id; // for compatibility
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$cond_reglement has been deprecated: Use $cond_reglement_id instead - Kept for compatibility ( Ignorable by Annotation )

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

3811
                /** @scrutinizer ignore-deprecated */ $this->cond_reglement = $id; // for compatibility

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
3812
                $this->deposit_percent = $deposit_percent;
3813
                return 1;
3814
            } else {
3815
                dol_syslog(get_class($this) . '::setPaymentTerms Error ' . $sql . ' - ' . $this->db->error());
3816
                $this->error = $this->db->error();
3817
                return -1;
3818
            }
3819
        } else {
3820
            dol_syslog(get_class($this) . '::setPaymentTerms, status of the object is incompatible');
3821
            $this->error = 'Status of the object is incompatible ' . $this->status;
3822
            return -2;
3823
        }
3824
    }
3825
3826
    /**
3827
     *  Change the transport mode methods
3828
     *
3829
     * @param int $id Id of transport mode
3830
     * @return     int             >0 if OK, <0 if KO
3831
     */
3832
    public function setTransportMode($id)
3833
    {
3834
        dol_syslog(get_class($this) . '::setTransportMode(' . $id . ')');
3835
        if ($this->status >= 0 || $this->element == 'societe') {
3836
            $fieldname = 'fk_transport_mode';
3837
            if ($this->element == 'societe') {
3838
                $fieldname = 'transport_mode';
3839
            }
3840
            if (get_class($this) == 'Fournisseur') {
3841
                $fieldname = 'transport_mode_supplier';
3842
            }
3843
3844
            $sql = 'UPDATE ' . $this->db->prefix() . $this->table_element;
3845
            $sql .= " SET " . $fieldname . " = " . (($id > 0 || $id == '0') ? ((int)$id) : 'NULL');
3846
            $sql .= ' WHERE rowid=' . ((int)$this->id);
3847
3848
            if ($this->db->query($sql)) {
3849
                $this->transport_mode_id = $id;
3850
                // for supplier
3851
                if (get_class($this) == 'Fournisseur') {
3852
                    $this->transport_mode_supplier_id = $id;
3853
                }
3854
                return 1;
3855
            } else {
3856
                dol_syslog(get_class($this) . '::setTransportMode Error ' . $sql . ' - ' . $this->db->error());
3857
                $this->error = $this->db->error();
3858
                return -1;
3859
            }
3860
        } else {
3861
            dol_syslog(get_class($this) . '::setTransportMode, status of the object is incompatible');
3862
            $this->error = 'Status of the object is incompatible ' . $this->status;
3863
            return -2;
3864
        }
3865
    }
3866
3867
    /**
3868
     *  Change the retained warranty payments terms
3869
     *
3870
     * @param int $id Id of new payment terms
3871
     * @return     int             >0 if OK, <0 if KO
3872
     */
3873
    public function setRetainedWarrantyPaymentTerms($id)
3874
    {
3875
        dol_syslog(get_class($this) . '::setRetainedWarrantyPaymentTerms(' . $id . ')');
3876
        if ($this->status >= 0 || $this->element == 'societe') {
3877
            $fieldname = 'retained_warranty_fk_cond_reglement';
3878
3879
            $sql = 'UPDATE ' . $this->db->prefix() . $this->table_element;
3880
            $sql .= " SET " . $fieldname . " = " . ((int)$id);
3881
            $sql .= ' WHERE rowid=' . ((int)$this->id);
3882
3883
            if ($this->db->query($sql)) {
3884
                $this->retained_warranty_fk_cond_reglement = $id;
3885
                return 1;
3886
            } else {
3887
                dol_syslog(get_class($this) . '::setRetainedWarrantyPaymentTerms Error ' . $sql . ' - ' . $this->db->error());
3888
                $this->error = $this->db->error();
3889
                return -1;
3890
            }
3891
        } else {
3892
            dol_syslog(get_class($this) . '::setRetainedWarrantyPaymentTerms, status of the object is incompatible');
3893
            $this->error = 'Status of the object is incompatible ' . $this->status;
3894
            return -2;
3895
        }
3896
    }
3897
3898
    /**
3899
     *  Define delivery address
3900
     * @param int $id Address id
3901
     * @return     int             Return integer <0 si ko, >0 si ok
3902
     * @deprecated
3903
     *
3904
     */
3905
    public function setDeliveryAddress($id)
3906
    {
3907
        $fieldname = 'fk_delivery_address';
3908
        if ($this->element == 'delivery' || $this->element == 'shipping') {
3909
            $fieldname = 'fk_address';
3910
        }
3911
3912
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element . " SET " . $fieldname . " = " . ((int)$id);
3913
        $sql .= " WHERE rowid = " . ((int)$this->id) . " AND fk_statut = 0";
3914
3915
        if ($this->db->query($sql)) {
3916
            $this->fk_delivery_address = $id;
3917
            return 1;
3918
        } else {
3919
            $this->error = $this->db->error();
3920
            dol_syslog(get_class($this) . '::setDeliveryAddress Error ' . $this->error);
3921
            return -1;
3922
        }
3923
    }
3924
3925
    /**
3926
     *  Change the shipping method
3927
     *
3928
     * @param int $shipping_method_id Id of shipping method
3929
     * @param int $notrigger 0=launch triggers after, 1=disable triggers
3930
     * @param User $userused Object user
3931
     * @return     int                             1 if OK, 0 if KO
3932
     */
3933
    public function setShippingMethod($shipping_method_id, $notrigger = 0, $userused = null)
3934
    {
3935
        global $user;
3936
3937
        if (empty($userused)) {
3938
            $userused = $user;
3939
        }
3940
3941
        $error = 0;
3942
3943
        if (!$this->table_element) {
3944
            dol_syslog(get_class($this) . "::setShippingMethod was called on object with property table_element not defined", LOG_ERR);
3945
            return -1;
3946
        }
3947
3948
        $this->db->begin();
3949
3950
        if ($shipping_method_id < 0) {
3951
            $shipping_method_id = 'NULL';
3952
        }
3953
        dol_syslog(get_class($this) . '::setShippingMethod(' . $shipping_method_id . ')');
3954
3955
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
3956
        $sql .= " SET fk_shipping_method = " . ((int)$shipping_method_id);
3957
        $sql .= " WHERE rowid=" . ((int)$this->id);
3958
        $resql = $this->db->query($sql);
3959
        if (!$resql) {
3960
            dol_syslog(get_class($this) . '::setShippingMethod Error ', LOG_DEBUG);
3961
            $this->error = $this->db->lasterror();
3962
            $error++;
3963
        } else {
3964
            if (!$notrigger) {
3965
                // Call trigger
3966
                $this->context = array('shippingmethodupdate' => 1);
3967
                $result = $this->call_trigger(strtoupper(get_class($this)) . '_MODIFY', $userused);
3968
                if ($result < 0) {
3969
                    $error++;
3970
                }
3971
                // End call trigger
3972
            }
3973
        }
3974
        if ($error) {
3975
            $this->db->rollback();
3976
            return -1;
3977
        } else {
3978
            $this->shipping_method_id = ($shipping_method_id == 'NULL') ? null : $shipping_method_id;
0 ignored issues
show
Documentation Bug introduced by
It seems like $shipping_method_id == '...l : $shipping_method_id can also be of type string. However, the property $shipping_method_id is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
3979
            $this->db->commit();
3980
            return 1;
3981
        }
3982
    }
3983
3984
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3985
3986
    /**
3987
     *  Change the warehouse
3988
     *
3989
     * @param int $warehouse_id Id of warehouse
3990
     * @return     int              1 if OK, 0 if KO
3991
     */
3992
    public function setWarehouse($warehouse_id)
3993
    {
3994
        if (!$this->table_element) {
3995
            dol_syslog(get_class($this) . "::setWarehouse was called on object with property table_element not defined", LOG_ERR);
3996
            return -1;
3997
        }
3998
        if ($warehouse_id < 0) {
3999
            $warehouse_id = 'NULL';
4000
        }
4001
        dol_syslog(get_class($this) . '::setWarehouse(' . $warehouse_id . ')');
4002
4003
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
4004
        $sql .= " SET fk_warehouse = " . ((int)$warehouse_id);
4005
        $sql .= " WHERE rowid=" . ((int)$this->id);
4006
4007
        if ($this->db->query($sql)) {
4008
            $this->warehouse_id = ($warehouse_id == 'NULL') ? null : $warehouse_id;
0 ignored issues
show
Documentation Bug introduced by
It seems like $warehouse_id == 'NULL' ? null : $warehouse_id can also be of type string. However, the property $warehouse_id is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
4009
            return 1;
4010
        } else {
4011
            dol_syslog(get_class($this) . '::setWarehouse Error ', LOG_DEBUG);
4012
            $this->error = $this->db->error();
4013
            return 0;
4014
        }
4015
    }
4016
4017
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4018
4019
    /**
4020
     *      Set last model used by doc generator
4021
     *
4022
     * @param User $user User object that make change
4023
     * @param string $modelpdf Modele name
4024
     * @return     int                 Return integer <0 if KO, >0 if OK
4025
     */
4026
    public function setDocModel($user, $modelpdf)
4027
    {
4028
        if (!$this->table_element) {
4029
            dol_syslog(get_class($this) . "::setDocModel was called on object with property table_element not defined", LOG_ERR);
4030
            return -1;
4031
        }
4032
4033
        $newmodelpdf = dol_trunc($modelpdf, 255);
4034
4035
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
4036
        $sql .= " SET model_pdf = '" . $this->db->escape($newmodelpdf) . "'";
4037
        $sql .= " WHERE rowid = " . ((int)$this->id);
4038
4039
        dol_syslog(get_class($this) . "::setDocModel", LOG_DEBUG);
4040
        $resql = $this->db->query($sql);
4041
        if ($resql) {
4042
            $this->model_pdf = $modelpdf;
4043
            return 1;
4044
        } else {
4045
            dol_print_error($this->db);
4046
            return 0;
4047
        }
4048
    }
4049
4050
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4051
4052
    /**
4053
     *  Change the bank account
4054
     *
4055
     * @param int $fk_account Id of bank account
4056
     * @param int $notrigger 0=launch triggers after, 1=disable triggers
4057
     * @param User $userused Object user
4058
     * @return     int                     1 if OK, 0 if KO
4059
     */
4060
    public function setBankAccount($fk_account, $notrigger = 0, $userused = null)
4061
    {
4062
        global $user;
4063
4064
        if (empty($userused)) {
4065
            $userused = $user;
4066
        }
4067
4068
        $error = 0;
4069
4070
        if (!$this->table_element) {
4071
            dol_syslog(get_class($this) . "::setBankAccount was called on object with property table_element not defined", LOG_ERR);
4072
            return -1;
4073
        }
4074
        $this->db->begin();
4075
4076
        if ($fk_account < 0) {
4077
            $fk_account = 'NULL';
4078
        }
4079
        dol_syslog(get_class($this) . '::setBankAccount(' . $fk_account . ')');
4080
4081
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
4082
        $sql .= " SET fk_account = " . ((int)$fk_account);
4083
        $sql .= " WHERE rowid=" . ((int)$this->id);
4084
4085
        $resql = $this->db->query($sql);
4086
        if (!$resql) {
4087
            dol_syslog(get_class($this) . '::setBankAccount Error ' . $sql . ' - ' . $this->db->error());
4088
            $this->error = $this->db->lasterror();
4089
            $error++;
4090
        } else {
4091
            if (!$notrigger) {
4092
                // Call trigger
4093
                $this->context['bankaccountupdate'] = 1;
4094
                $triggerName = strtoupper(get_class($this)) . '_MODIFY';
4095
                // Special cases
4096
                if ($triggerName == 'FACTUREREC_MODIFY') {
4097
                    $triggerName = 'BILLREC_MODIFY';
4098
                }
4099
                $result = $this->call_trigger($triggerName, $userused);
4100
                if ($result < 0) {
4101
                    $error++;
4102
                }
4103
                // End call trigger
4104
            }
4105
        }
4106
        if ($error) {
4107
            $this->db->rollback();
4108
            return -1;
4109
        } else {
4110
            $this->fk_account = ($fk_account == 'NULL') ? null : $fk_account;
0 ignored issues
show
Documentation Bug introduced by
It seems like $fk_account == 'NULL' ? null : $fk_account can also be of type string. However, the property $fk_account is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
4111
            $this->db->commit();
4112
            return 1;
4113
        }
4114
    }
4115
4116
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4117
4118
    /**
4119
     *  Update a line to have a lower rank
4120
     *
4121
     * @param int $rowid Id of line
4122
     * @param boolean $fk_parent_line Table with fk_parent_line field or not
4123
     * @return void
4124
     */
4125
    public function line_up($rowid, $fk_parent_line = true)
4126
    {
4127
        // phpcs:enable
4128
        $this->line_order(false, 'ASC', $fk_parent_line);
4129
4130
        // Get rang of line
4131
        $rang = $this->getRangOfLine($rowid);
4132
4133
        // Update position of line
4134
        $this->updateLineUp($rowid, $rang);
4135
    }
4136
4137
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4138
4139
    /**
4140
     *  Save a new position (field rang) for details lines.
4141
     *  You can choose to set position for lines with already a position or lines without any position defined.
4142
     *
4143
     * @param boolean $renum True to renum all already ordered lines, false to renum only not already ordered lines.
4144
     * @param string $rowidorder ASC or DESC
4145
     * @param boolean $fk_parent_line Table with fk_parent_line field or not
4146
     * @return     int                            Return integer <0 if KO, >0 if OK
4147
     */
4148
    public function line_order($renum = false, $rowidorder = 'ASC', $fk_parent_line = true)
4149
    {
4150
        // phpcs:enable
4151
        if (!$this->table_element_line) {
4152
            dol_syslog(get_class($this) . "::line_order was called on object with property table_element_line not defined", LOG_ERR);
4153
            return -1;
4154
        }
4155
        if (!$this->fk_element) {
4156
            dol_syslog(get_class($this) . "::line_order was called on object with property fk_element not defined", LOG_ERR);
4157
            return -1;
4158
        }
4159
4160
        $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
4161
        if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
4162
            $fieldposition = 'position';
4163
        }
4164
4165
        // Count number of lines to reorder (according to choice $renum)
4166
        $nl = 0;
4167
        $sql = "SELECT count(rowid) FROM " . $this->db->prefix() . $this->table_element_line;
4168
        $sql .= " WHERE " . $this->fk_element . " = " . ((int)$this->id);
4169
        if (!$renum) {
4170
            $sql .= " AND " . $fieldposition . " = 0";
4171
        }
4172
        if ($renum) {
4173
            $sql .= " AND " . $fieldposition . " <> 0";
4174
        }
4175
4176
        dol_syslog(get_class($this) . "::line_order", LOG_DEBUG);
4177
        $resql = $this->db->query($sql);
4178
        if ($resql) {
4179
            $row = $this->db->fetch_row($resql);
4180
            $nl = $row[0];
4181
        } else {
4182
            dol_print_error($this->db);
4183
        }
4184
        if ($nl > 0) {
4185
            // The goal of this part is to reorder all lines, with all children lines sharing the same counter that parents.
4186
            $rows = array();
4187
4188
            // We first search all lines that are parent lines (for multilevel details lines)
4189
            $sql = "SELECT rowid FROM " . $this->db->prefix() . $this->table_element_line;
4190
            $sql .= " WHERE " . $this->fk_element . " = " . ((int)$this->id);
4191
            if ($fk_parent_line) {
4192
                $sql .= ' AND fk_parent_line IS NULL';
4193
            }
4194
            $sql .= " ORDER BY " . $fieldposition . " ASC, rowid " . $rowidorder;
4195
4196
            dol_syslog(get_class($this) . "::line_order search all parent lines", LOG_DEBUG);
4197
            $resql = $this->db->query($sql);
4198
            if ($resql) {
4199
                $i = 0;
4200
                $num = $this->db->num_rows($resql);
4201
                while ($i < $num) {
4202
                    $row = $this->db->fetch_row($resql);
4203
                    $rows[] = $row[0]; // Add parent line into array rows
4204
                    $children = $this->getChildrenOfLine($row[0]);
4205
                    if (!empty($children)) {
4206
                        foreach ($children as $child) {
4207
                            array_push($rows, $child);
4208
                        }
4209
                    }
4210
                    $i++;
4211
                }
4212
4213
                // Now we set a new number for each lines (parent and children with children included into parent tree)
4214
                if (!empty($rows)) {
4215
                    foreach ($rows as $key => $row) {
4216
                        $this->updateRangOfLine($row, ($key + 1));
4217
                    }
4218
                }
4219
            } else {
4220
                dol_print_error($this->db);
4221
            }
4222
        }
4223
        return 1;
4224
    }
4225
4226
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4227
4228
    /**
4229
     *  Get children of line
4230
     *
4231
     * @param int $id Id of parent line
4232
     * @param int<0,1> $includealltree 0 = 1st level child, 1 = All level child
4233
     * @return int[]                       Array with list of children lines id
4234
     */
4235
    public function getChildrenOfLine($id, $includealltree = 0)
4236
    {
4237
        $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
4238
        if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
4239
            $fieldposition = 'position';
4240
        }
4241
4242
        $rows = array();
4243
4244
        $sql = "SELECT rowid FROM " . $this->db->prefix() . $this->table_element_line;
4245
        $sql .= " WHERE " . $this->fk_element . " = " . ((int)$this->id);
4246
        $sql .= ' AND fk_parent_line = ' . ((int)$id);
4247
        $sql .= " ORDER BY " . $fieldposition . " ASC";
4248
4249
        dol_syslog(get_class($this) . "::getChildrenOfLine search children lines for line " . $id, LOG_DEBUG);
4250
        $resql = $this->db->query($sql);
4251
        if ($resql) {
4252
            if ($this->db->num_rows($resql) > 0) {
4253
                while ($row = $this->db->fetch_row($resql)) {
4254
                    $rows[] = $row[0];
4255
                    if ($includealltree) {
4256
                        $rows = array_merge($rows, $this->getChildrenOfLine($row[0], $includealltree));
4257
                    }
4258
                }
4259
            }
4260
        }
4261
        return $rows;
4262
    }
4263
4264
    /**
4265
     *  Update position of line (rang)
4266
     *
4267
     * @param int $rowid Id of line
4268
     * @param int $rang Position
4269
     * @return int<-1,1>           Return integer <0 if KO, >0 if OK
4270
     */
4271
    public function updateRangOfLine($rowid, $rang)
4272
    {
4273
        global $hookmanager;
4274
        $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
4275
        if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
4276
            $fieldposition = 'position';
4277
        }
4278
4279
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element_line . " SET " . $fieldposition . " = " . ((int)$rang);
4280
        $sql .= ' WHERE rowid = ' . ((int)$rowid);
4281
4282
        dol_syslog(get_class($this) . "::updateRangOfLine", LOG_DEBUG);
4283
        if (!$this->db->query($sql)) {
4284
            dol_print_error($this->db);
4285
            return -1;
4286
        } else {
4287
            $parameters = array('rowid' => $rowid, 'rang' => $rang, 'fieldposition' => $fieldposition);
4288
            $action = '';
4289
            $reshook = $hookmanager->executeHooks('afterRankOfLineUpdate', $parameters, $this, $action);
4290
            return 1;
4291
        }
4292
    }
4293
4294
    /**
4295
     *  Get position of line (rang)
4296
     *
4297
     * @param int $rowid Id of line
4298
     * @return     int                 Value of rang in table of lines
4299
     */
4300
    public function getRangOfLine($rowid)
4301
    {
4302
        $fieldposition = 'rang';
4303
        if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
4304
            $fieldposition = 'position';
4305
        }
4306
4307
        $sql = "SELECT " . $fieldposition . " FROM " . $this->db->prefix() . $this->table_element_line;
4308
        $sql .= " WHERE rowid = " . ((int)$rowid);
4309
4310
        dol_syslog(get_class($this) . "::getRangOfLine", LOG_DEBUG);
4311
        $resql = $this->db->query($sql);
4312
        if ($resql) {
4313
            $row = $this->db->fetch_row($resql);
4314
            return $row[0];
4315
        }
4316
4317
        return 0;
4318
    }
4319
4320
    /**
4321
     *  Update position of line up (rang)
4322
     *
4323
     * @param int $rowid Id of line
4324
     * @param int $rang Position
4325
     * @return void
4326
     */
4327
    public function updateLineUp($rowid, $rang)
4328
    {
4329
        if ($rang > 1) {
4330
            $fieldposition = 'rang';
4331
            if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
4332
                $fieldposition = 'position';
4333
            }
4334
4335
            $sql = "UPDATE " . $this->db->prefix() . $this->table_element_line . " SET " . $fieldposition . " = " . ((int)$rang);
4336
            $sql .= " WHERE " . $this->fk_element . " = " . ((int)$this->id);
4337
            $sql .= " AND " . $fieldposition . " = " . ((int)($rang - 1));
4338
            if ($this->db->query($sql)) {
4339
                $sql = "UPDATE " . $this->db->prefix() . $this->table_element_line . " SET " . $fieldposition . " = " . ((int)($rang - 1));
4340
                $sql .= ' WHERE rowid = ' . ((int)$rowid);
4341
                if (!$this->db->query($sql)) {
4342
                    dol_print_error($this->db);
4343
                }
4344
            } else {
4345
                dol_print_error($this->db);
4346
            }
4347
        }
4348
    }
4349
4350
    /**
4351
     *  Update a line to have a higher rank
4352
     *
4353
     * @param int $rowid Id of line
4354
     * @param boolean $fk_parent_line Table with fk_parent_line field or not
4355
     * @return void
4356
     */
4357
    public function line_down($rowid, $fk_parent_line = true)
4358
    {
4359
        // phpcs:enable
4360
        $this->line_order(false, 'ASC', $fk_parent_line);
4361
4362
        // Get rang of line
4363
        $rang = $this->getRangOfLine($rowid);
4364
4365
        // Get max value for rang
4366
        $max = $this->line_max();
4367
4368
        // Update position of line
4369
        $this->updateLineDown($rowid, $rang, $max);
4370
    }
4371
4372
    /**
4373
     *  Get max value used for position of line (rang)
4374
     *
4375
     * @param int $fk_parent_line Parent line id
4376
     * @return     int                         Max value of rang in table of lines
4377
     */
4378
    public function line_max($fk_parent_line = 0)
4379
    {
4380
        // phpcs:enable
4381
        $positionfield = 'rang';
4382
        if (in_array($this->table_element, array('bom_bom', 'product_attribute'))) {
4383
            $positionfield = 'position';
4384
        }
4385
4386
        // Search the last rang with fk_parent_line
4387
        if ($fk_parent_line) {
4388
            $sql = "SELECT max(" . $positionfield . ") FROM " . $this->db->prefix() . $this->table_element_line;
4389
            $sql .= " WHERE " . $this->fk_element . " = " . ((int)$this->id);
4390
            $sql .= " AND fk_parent_line = " . ((int)$fk_parent_line);
4391
4392
            dol_syslog(get_class($this) . "::line_max", LOG_DEBUG);
4393
            $resql = $this->db->query($sql);
4394
            if ($resql) {
4395
                $row = $this->db->fetch_row($resql);
4396
                if (!empty($row[0])) {
4397
                    return $row[0];
4398
                } else {
4399
                    return $this->getRangOfLine($fk_parent_line);
4400
                }
4401
            }
4402
        } else {
4403
            // If not, search the last rang of element
4404
            $sql = "SELECT max(" . $positionfield . ") FROM " . $this->db->prefix() . $this->table_element_line;
4405
            $sql .= " WHERE " . $this->fk_element . " = " . ((int)$this->id);
4406
4407
            dol_syslog(get_class($this) . "::line_max", LOG_DEBUG);
4408
            $resql = $this->db->query($sql);
4409
            if ($resql) {
4410
                $row = $this->db->fetch_row($resql);
4411
                return $row[0];
4412
            }
4413
        }
4414
4415
        return 0;
4416
    }
4417
4418
    /**
4419
     *  Update position of line down (rang)
4420
     *
4421
     * @param int $rowid Id of line
4422
     * @param int $rang Position
4423
     * @param int $max Max
4424
     * @return void
4425
     */
4426
    public function updateLineDown($rowid, $rang, $max)
4427
    {
4428
        if ($rang < $max) {
4429
            $fieldposition = 'rang';
4430
            if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
4431
                $fieldposition = 'position';
4432
            }
4433
4434
            $sql = "UPDATE " . $this->db->prefix() . $this->table_element_line . " SET " . $fieldposition . " = " . ((int)$rang);
4435
            $sql .= " WHERE " . $this->fk_element . " = " . ((int)$this->id);
4436
            $sql .= " AND " . $fieldposition . " = " . ((int)($rang + 1));
4437
            if ($this->db->query($sql)) {
4438
                $sql = "UPDATE " . $this->db->prefix() . $this->table_element_line . " SET " . $fieldposition . " = " . ((int)($rang + 1));
4439
                $sql .= ' WHERE rowid = ' . ((int)$rowid);
4440
                if (!$this->db->query($sql)) {
4441
                    dol_print_error($this->db);
4442
                }
4443
            } else {
4444
                dol_print_error($this->db);
4445
            }
4446
        }
4447
    }
4448
4449
    /**
4450
     *  Update position of line with ajax (rang)
4451
     *
4452
     * @param int[] $rows Array of rows
4453
     * @return void
4454
     */
4455
    public function line_ajaxorder($rows)
4456
    {
4457
        // phpcs:enable
4458
        $num = count($rows);
4459
        for ($i = 0; $i < $num; $i++) {
4460
            $this->updateRangOfLine($rows[$i], ($i + 1));
4461
        }
4462
    }
4463
4464
    /**
4465
     *  Get rowid of the line relative to its position
4466
     *
4467
     * @param int $rang Rang value
4468
     * @return     int                 Rowid of the line
4469
     */
4470
    public function getIdOfLine($rang)
4471
    {
4472
        $fieldposition = 'rang';
4473
        if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
4474
            $fieldposition = 'position';
4475
        }
4476
4477
        $sql = "SELECT rowid FROM " . $this->db->prefix() . $this->table_element_line;
4478
        $sql .= " WHERE " . $this->fk_element . " = " . ((int)$this->id);
4479
        $sql .= " AND " . $fieldposition . " = " . ((int)$rang);
4480
        $resql = $this->db->query($sql);
4481
        if ($resql) {
4482
            $row = $this->db->fetch_row($resql);
4483
            return $row[0];
4484
        }
4485
4486
        return 0;
4487
    }
4488
4489
    /**
4490
     *  Update external ref of element
4491
     *
4492
     * @param string $ref_ext Update field ref_ext
4493
     * @return     int                     Return integer <0 if KO, >0 if OK
4494
     */
4495
    public function update_ref_ext($ref_ext)
4496
    {
4497
        // phpcs:enable
4498
        if (!$this->table_element) {
4499
            dol_syslog(get_class($this) . "::update_ref_ext was called on object with property table_element not defined", LOG_ERR);
4500
            return -1;
4501
        }
4502
4503
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
4504
        $sql .= " SET ref_ext = '" . $this->db->escape($ref_ext) . "'";
4505
        $sql .= " WHERE " . (isset($this->table_rowid) ? $this->table_rowid : 'rowid') . " = " . ((int)$this->id);
0 ignored issues
show
Bug Best Practice introduced by
The property table_rowid does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
4506
4507
        dol_syslog(get_class($this) . "::update_ref_ext", LOG_DEBUG);
4508
        if ($this->db->query($sql)) {
4509
            $this->ref_ext = $ref_ext;
4510
            return 1;
4511
        } else {
4512
            $this->error = $this->db->error();
4513
            return -1;
4514
        }
4515
    }
4516
4517
    /**
4518
     *  Update public note (kept for backward compatibility)
4519
     *
4520
     * @param string $note New value for note
4521
     * @return     int                      Return integer <0 if KO, >0 if OK
4522
     * @deprecated
4523
     * @see update_note()
4524
     */
4525
    public function update_note_public($note)
4526
    {
4527
        // phpcs:enable
4528
        return $this->update_note($note, '_public');
4529
    }
4530
4531
    /**
4532
     *  Update note of element
4533
     *
4534
     * @param string $note New value for note
4535
     * @param string $suffix '', '_public' or '_private'
4536
     * @param int $notrigger 1=Does not execute triggers, 0=execute triggers
4537
     * @return     int                     Return integer <0 if KO, >0 if OK
4538
     */
4539
    public function update_note($note, $suffix = '', $notrigger = 0)
4540
    {
4541
        // phpcs:enable
4542
        global $user;
4543
4544
        if (!$this->table_element) {
4545
            $this->error = 'update_note was called on object with property table_element not defined';
4546
            dol_syslog(get_class($this) . "::update_note was called on object with property table_element not defined", LOG_ERR);
4547
            return -1;
4548
        }
4549
        if (!in_array($suffix, array('', '_public', '_private'))) {
4550
            $this->error = 'update_note Parameter suffix must be empty, \'_private\' or \'_public\'';
4551
            dol_syslog(get_class($this) . "::update_note Parameter suffix must be empty, '_private' or '_public'", LOG_ERR);
4552
            return -2;
4553
        }
4554
4555
        $newsuffix = $suffix;
4556
4557
        // Special case
4558
        if ($this->table_element == 'product' && $newsuffix == '_private') {
4559
            $newsuffix = '';
4560
        }
4561
        if (in_array($this->table_element, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
4562
            $fieldusermod = "fk_user_mod";
4563
        } elseif ($this->table_element == 'ecm_files') {
4564
            $fieldusermod = "fk_user_m";
4565
        } else {
4566
            $fieldusermod = "fk_user_modif";
4567
        }
4568
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
4569
        $sql .= " SET note" . $newsuffix . " = " . (!empty($note) ? ("'" . $this->db->escape($note) . "'") : "NULL");
4570
        $sql .= ", " . $fieldusermod . " = " . ((int)$user->id);
4571
        $sql .= " WHERE rowid = " . ((int)$this->id);
4572
4573
        dol_syslog(get_class($this) . "::update_note", LOG_DEBUG);
4574
        if ($this->db->query($sql)) {
4575
            if ($suffix == '_public') {
4576
                $this->note_public = $note;
4577
            } elseif ($suffix == '_private') {
4578
                $this->note_private = $note;
4579
            } else {
4580
                $this->note = $note; // deprecated
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$note has been deprecated: Use $note_private instead. ( Ignorable by Annotation )

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

4580
                /** @scrutinizer ignore-deprecated */ $this->note = $note; // deprecated

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
4581
                $this->note_private = $note;
4582
            }
4583
            if (empty($notrigger)) {
4584
                switch ($this->element) {
4585
                    case 'societe':
4586
                        $trigger_name = 'COMPANY_MODIFY';
4587
                        break;
4588
                    case 'commande':
4589
                        $trigger_name = 'ORDER_MODIFY';
4590
                        break;
4591
                    case 'facture':
4592
                        $trigger_name = 'BILL_MODIFY';
4593
                        break;
4594
                    case 'invoice_supplier':
4595
                        $trigger_name = 'BILL_SUPPLIER_MODIFY';
4596
                        break;
4597
                    case 'facturerec':
4598
                        $trigger_name = 'BILLREC_MODIFIY';
4599
                        break;
4600
                    case 'expensereport':
4601
                        $trigger_name = 'EXPENSE_REPORT_MODIFY';
4602
                        break;
4603
                    default:
4604
                        $trigger_name = strtoupper($this->element) . '_MODIFY';
4605
                }
4606
                $ret = $this->call_trigger($trigger_name, $user);
4607
                if ($ret < 0) {
4608
                    return -1;
4609
                }
4610
            }
4611
            return 1;
4612
        } else {
4613
            $this->error = $this->db->lasterror();
4614
            return -1;
4615
        }
4616
    }
4617
4618
    /**
4619
     *  Update total_ht, total_ttc, total_vat, total_localtax1, total_localtax2 for an object (sum of lines).
4620
     *  Must be called at end of methods addline or updateline.
4621
     *
4622
     * @param int $exclspec >0 = Exclude special product (product_type=9)
4623
     * @param 'none'|'auto'|'0'|'1'   $roundingadjust     'none'=Do nothing, 'auto'=Use default method (MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND if defined, or '0'), '0'=Force mode Total of rounding, '1'=Force mode Rounding of total
0 ignored issues
show
Documentation Bug introduced by
The doc comment 'none'|'auto'|'0'|'1' at position 0 could not be parsed: Unknown type name ''none'' at position 0 in 'none'|'auto'|'0'|'1'.
Loading history...
4624
     * @param int<0,1> $nodatabaseupdate 1=Do not update database total fields of the main object. Update only properties in memory. Can be used to save SQL when this method is called several times, so we can do it only once at end.
4625
     * @param  ?Societe $seller If roundingadjust is '0' or '1' or maybe 'auto', it means we recalculate total for lines before calculating total for object and for this, we need seller object (used to analyze lines to check corrupted data).
4626
     * @return int<-1,1>                   Return integer <0 if KO, >0 if OK
4627
     */
4628
    public function update_price($exclspec = 0, $roundingadjust = 'auto', $nodatabaseupdate = 0, $seller = null)
4629
    {
4630
        // phpcs:enable
4631
        global $conf, $hookmanager, $action;
4632
4633
        $parameters = array('exclspec' => $exclspec, 'roundingadjust' => $roundingadjust, 'nodatabaseupdate' => $nodatabaseupdate, 'seller' => $seller);
4634
        $reshook = $hookmanager->executeHooks('updateTotalPrice', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4635
        if ($reshook > 0) {
4636
            return 1; // replacement code
4637
        } elseif ($reshook < 0) {
4638
            return -1; // failure
4639
        } // reshook = 0 => execute normal code
4640
4641
        // Some external module want no update price after a trigger because they have another method to calculate the total (ex: with an extrafield)
4642
        $isElementForSupplier = false;
4643
        $roundTotalConstName = 'MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND'; // const for customer by default
4644
        $MODULE = "";
4645
        if ($this->element == 'propal') {
4646
            $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_PROPOSAL";
4647
        } elseif ($this->element == 'commande' || $this->element == 'order') {
4648
            $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_ORDER";
4649
        } elseif ($this->element == 'facture' || $this->element == 'invoice') {
4650
            $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_INVOICE";
4651
        } elseif ($this->element == 'facture_fourn' || $this->element == 'supplier_invoice' || $this->element == 'invoice_supplier' || $this->element == 'invoice_supplier_rec') {
4652
            $isElementForSupplier = true;
4653
            $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_INVOICE";
4654
        } elseif ($this->element == 'order_supplier' || $this->element == 'supplier_order') {
4655
            $isElementForSupplier = true;
4656
            $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_ORDER";
4657
        } elseif ($this->element == 'supplier_proposal') {
4658
            $isElementForSupplier = true;
4659
            $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_PROPOSAL";
4660
        }
4661
        if ($isElementForSupplier) {
4662
            $roundTotalConstName = 'MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND_SUPPLIER'; // const for supplier
4663
        }
4664
4665
        if (!empty($MODULE)) {
4666
            if (getDolGlobalString($MODULE)) {
4667
                $modsactivated = explode(',', getDolGlobalString($MODULE));
4668
                foreach ($modsactivated as $mod) {
4669
                    if (isModEnabled($mod)) {
4670
                        return 1; // update was disabled by specific setup
4671
                    }
4672
                }
4673
            }
4674
        }
4675
4676
        include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
4677
4678
        $forcedroundingmode = $roundingadjust;
4679
        if ($forcedroundingmode == 'auto' && isset($conf->global->{$roundTotalConstName})) {
4680
            $forcedroundingmode = getDolGlobalString($roundTotalConstName);
4681
        } elseif ($forcedroundingmode == 'auto') {
4682
            $forcedroundingmode = '0';
4683
        }
4684
4685
        $error = 0;
4686
4687
        $multicurrency_tx = !empty($this->multicurrency_tx) ? $this->multicurrency_tx : 1;
4688
4689
        // Define constants to find lines to sum (field name int the table_element_line not into table_element)
4690
        $fieldtva = 'total_tva';
4691
        $fieldlocaltax1 = 'total_localtax1';
4692
        $fieldlocaltax2 = 'total_localtax2';
4693
        $fieldup = 'subprice';
4694
        $base_price_type = 'HT';
4695
        if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
4696
            $fieldtva = 'tva';
4697
            $fieldup = 'pu_ht';
4698
        }
4699
        if ($this->element == 'invoice_supplier_rec') {
4700
            $fieldup = 'pu_ht';
4701
        }
4702
        if ($this->element == 'expensereport') {
4703
            $fieldup = 'value_unit';
4704
            $base_price_type = 'TTC';
4705
        }
4706
4707
        $sql = "SELECT rowid, qty, " . $fieldup . " as up, remise_percent, total_ht, " . $fieldtva . " as total_tva, total_ttc, " . $fieldlocaltax1 . " as total_localtax1, " . $fieldlocaltax2 . " as total_localtax2,";
4708
        $sql .= ' tva_tx as vatrate, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, info_bits, product_type';
4709
        if ($this->table_element_line == 'facturedet') {
4710
            $sql .= ', situation_percent';
4711
        }
4712
        $sql .= ', multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
4713
        $sql .= " FROM " . $this->db->prefix() . $this->table_element_line;
4714
        $sql .= " WHERE " . $this->fk_element . " = " . ((int)$this->id);
4715
        if ($exclspec) {
4716
            $product_field = 'product_type';
4717
            if ($this->table_element_line == 'contratdet') {
4718
                $product_field = ''; // contratdet table has no product_type field
4719
            }
4720
            if ($product_field) {
4721
                $sql .= " AND " . $product_field . " <> 9";
4722
            }
4723
        }
4724
        $sql .= ' ORDER by rowid'; // We want to be certain to always use same order of line to not change lines differently when option MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND is used
4725
4726
        dol_syslog(get_class($this) . "::update_price", LOG_DEBUG);
4727
4728
        $resql = $this->db->query($sql);
4729
        if ($resql) {
4730
            $this->total_ht = 0;
4731
            $this->total_tva = 0;
4732
            $this->total_localtax1 = 0;
4733
            $this->total_localtax2 = 0;
4734
            $this->total_ttc = 0;
4735
            $total_ht_by_vats = array();
4736
            $total_tva_by_vats = array();
4737
            $total_ttc_by_vats = array();
4738
            $this->multicurrency_total_ht = 0;
4739
            $this->multicurrency_total_tva = 0;
4740
            $this->multicurrency_total_ttc = 0;
4741
4742
            $this->db->begin();
4743
4744
            $num = $this->db->num_rows($resql);
4745
            $i = 0;
4746
            while ($i < $num) {
4747
                $obj = $this->db->fetch_object($resql);
4748
4749
                // Note: There is no check on detail line and no check on total, if $forcedroundingmode = 'none'
4750
                $parameters = array('fk_element' => $obj->rowid);
4751
                $reshook = $hookmanager->executeHooks('changeRoundingMode', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4752
4753
                if (empty($reshook) && $forcedroundingmode == '0') {    // Check if data on line are consistent. This may solve lines that were not consistent because set with $forcedroundingmode='auto'
4754
                    // This part of code is to fix data. We should not call it too often.
4755
                    $localtax_array = array($obj->localtax1_type, $obj->localtax1_tx, $obj->localtax2_type, $obj->localtax2_tx);
4756
                    $tmpcal = calcul_price_total($obj->qty, $obj->up, $obj->remise_percent, $obj->vatrate, $obj->localtax1_tx, $obj->localtax2_tx, 0, $base_price_type, $obj->info_bits, $obj->product_type, $seller, $localtax_array, (isset($obj->situation_percent) ? $obj->situation_percent : 100), $multicurrency_tx);
4757
4758
                    $diff_when_using_price_ht = price2num($tmpcal[1] - $obj->total_tva, 'MT', 1); // If price was set with tax price and unit price HT has a low number of digits, then we may have a diff on recalculation from unit price HT.
4759
                    $diff_on_current_total = price2num($obj->total_ttc - $obj->total_ht - $obj->total_tva - $obj->total_localtax1 - $obj->total_localtax2, 'MT', 1);
4760
                    //var_dump($obj->total_ht.' '.$obj->total_tva.' '.$obj->total_localtax1.' '.$obj->total_localtax2.' => '.$obj->total_ttc);
4761
                    //var_dump($diff_when_using_price_ht.' '.$diff_on_current_total);
4762
4763
                    if ($diff_on_current_total) {
4764
                        // This should not happen, we should always have in table: total_ttc = total_ht + total_vat + total_localtax1 + total_localtax2
4765
                        $sqlfix = "UPDATE " . $this->db->prefix() . $this->table_element_line . " SET " . $fieldtva . " = " . price2num((float)$tmpcal[1]) . ", total_ttc = " . price2num((float)$tmpcal[2]) . " WHERE rowid = " . ((int)$obj->rowid);
4766
                        dol_syslog('We found inconsistent data into detailed line (diff_on_current_total = ' . $diff_on_current_total . ') for line rowid = ' . $obj->rowid . " (ht=" . $obj->total_ht . " vat=" . $obj->total_tva . " tax1=" . $obj->total_localtax1 . " tax2=" . $obj->total_localtax2 . " ttc=" . $obj->total_ttc . "). We fix the total_vat and total_ttc of line by running sqlfix = " . $sqlfix, LOG_WARNING);
4767
                        $resqlfix = $this->db->query($sqlfix);
4768
                        if (!$resqlfix) {
4769
                            dol_print_error($this->db, 'Failed to update line');
4770
                        }
4771
                        $obj->total_tva = $tmpcal[1];
4772
                        $obj->total_ttc = $tmpcal[2];
4773
                    } elseif ($diff_when_using_price_ht) {
4774
                        // After calculation from HT, total is consistent but we have found a difference between VAT part in calculation and into database and
4775
                        // we ask to force the use of rounding on line (like done on calculation) so we force update of line
4776
                        $sqlfix = "UPDATE " . $this->db->prefix() . $this->table_element_line . " SET " . $fieldtva . " = " . price2num((float)$tmpcal[1]) . ", total_ttc = " . price2num((float)$tmpcal[2]) . " WHERE rowid = " . ((int)$obj->rowid);
4777
                        dol_syslog('We found a line with different rounding data into detailed line (diff_when_using_price_ht = ' . $diff_when_using_price_ht . ' and diff_on_current_total = ' . $diff_on_current_total . ') for line rowid = ' . $obj->rowid . " (total vat of line calculated=" . $tmpcal[1] . ", database=" . $obj->total_tva . "). We fix the total_vat and total_ttc of line by running sqlfix = " . $sqlfix);
4778
                        $resqlfix = $this->db->query($sqlfix);
4779
                        if (!$resqlfix) {
4780
                            dol_print_error($this->db, 'Failed to update line');
4781
                        }
4782
                        $obj->total_tva = $tmpcal[1];
4783
                        $obj->total_ttc = $tmpcal[2];
4784
                    }
4785
                }
4786
4787
                $this->total_ht += $obj->total_ht; // The field visible at end of line detail
4788
                $this->total_tva += $obj->total_tva;
4789
                $this->total_localtax1 += $obj->total_localtax1;
4790
                $this->total_localtax2 += $obj->total_localtax2;
4791
                $this->total_ttc += $obj->total_ttc;
4792
                $this->multicurrency_total_ht += $obj->multicurrency_total_ht; // The field visible at end of line detail
4793
                $this->multicurrency_total_tva += $obj->multicurrency_total_tva;
4794
                $this->multicurrency_total_ttc += $obj->multicurrency_total_ttc;
4795
4796
                if (!isset($total_ht_by_vats[$obj->vatrate])) {
4797
                    $total_ht_by_vats[$obj->vatrate] = 0;
4798
                }
4799
                if (!isset($total_tva_by_vats[$obj->vatrate])) {
4800
                    $total_tva_by_vats[$obj->vatrate] = 0;
4801
                }
4802
                if (!isset($total_ttc_by_vats[$obj->vatrate])) {
4803
                    $total_ttc_by_vats[$obj->vatrate] = 0;
4804
                }
4805
                $total_ht_by_vats[$obj->vatrate] += $obj->total_ht;
4806
                $total_tva_by_vats[$obj->vatrate] += $obj->total_tva;
4807
                $total_ttc_by_vats[$obj->vatrate] += $obj->total_ttc;
4808
4809
                if ($forcedroundingmode == '1') {   // Check if we need adjustment onto line for vat. TODO This works on the company currency but not on foreign currency
4810
                    if ($base_price_type == 'TTC') {
4811
                        $tmpvat = price2num($total_ttc_by_vats[$obj->vatrate] * $obj->vatrate / (100 + $obj->vatrate), 'MT', 1);
4812
                    } else {
4813
                        $tmpvat = price2num($total_ht_by_vats[$obj->vatrate] * $obj->vatrate / 100, 'MT', 1);
4814
                    }
4815
                    $diff = price2num($total_tva_by_vats[$obj->vatrate] - (float)$tmpvat, 'MT', 1);
4816
                    //print 'Line '.$i.' rowid='.$obj->rowid.' vat_rate='.$obj->vatrate.' total_ht='.$obj->total_ht.' total_tva='.$obj->total_tva.' total_ttc='.$obj->total_ttc.' total_ht_by_vats='.$total_ht_by_vats[$obj->vatrate].' total_tva_by_vats='.$total_tva_by_vats[$obj->vatrate].' (new calculation = '.$tmpvat.') total_ttc_by_vats='.$total_ttc_by_vats[$obj->vatrate].($diff?" => DIFF":"")."<br>\n";
4817
                    if ($diff) {
4818
                        if (abs((float)$diff) > (10 * pow(10, -1 * getDolGlobalInt('MAIN_MAX_DECIMALS_TOT', 0)))) {
4819
                            // If error is more than 10 times the accuracy of rounding. This should not happen.
4820
                            $errmsg = 'A rounding difference was detected into TOTAL but is too high to be corrected. Some data in your lines may be corrupted. Try to edit each line manually to fix this before restarting.';
4821
                            dol_syslog($errmsg, LOG_WARNING);
4822
                            $this->error = $errmsg;
4823
                            $error++;
4824
                            break;
4825
                        }
4826
                        if ($base_price_type == 'TTC') {
4827
                            $sqlfix = "UPDATE " . $this->db->prefix() . $this->table_element_line . " SET " . $fieldtva . " = " . price2num($obj->total_tva - (float)$diff) . ", total_ht = " . price2num($obj->total_ht + (float)$diff) . " WHERE rowid = " . ((int)$obj->rowid);
4828
                            dol_syslog('We found a difference of ' . $diff . ' for line rowid = ' . $obj->rowid . ". We fix the total_vat and total_ht of line by running sqlfix = " . $sqlfix);
4829
                        } else {
4830
                            $sqlfix = "UPDATE " . $this->db->prefix() . $this->table_element_line . " SET " . $fieldtva . " = " . price2num($obj->total_tva - (float)$diff) . ", total_ttc = " . price2num($obj->total_ttc - (float)$diff) . " WHERE rowid = " . ((int)$obj->rowid);
4831
                            dol_syslog('We found a difference of ' . $diff . ' for line rowid = ' . $obj->rowid . ". We fix the total_vat and total_ttc of line by running sqlfix = " . $sqlfix);
4832
                        }
4833
4834
                        $resqlfix = $this->db->query($sqlfix);
4835
4836
                        if (!$resqlfix) {
4837
                            dol_print_error($this->db, 'Failed to update line');
4838
                        }
4839
4840
                        $this->total_tva = (float)price2num($this->total_tva - (float)$diff, '', 1);
4841
                        $total_tva_by_vats[$obj->vatrate] = (float)price2num($total_tva_by_vats[$obj->vatrate] - (float)$diff, '', 1);
4842
                        if ($base_price_type == 'TTC') {
4843
                            $this->total_ht = (float)price2num($this->total_ht + (float)$diff, '', 1);
4844
                            $total_ht_by_vats[$obj->vatrate] = (float)price2num($total_ht_by_vats[$obj->vatrate] + (float)$diff, '', 1);
4845
                        } else {
4846
                            $this->total_ttc = (float)price2num($this->total_ttc - (float)$diff, '', 1);
4847
                            $total_ttc_by_vats[$obj->vatrate] = (float)price2num($total_ttc_by_vats[$obj->vatrate] - (float)$diff, '', 1);
4848
                        }
4849
                    }
4850
                }
4851
4852
                $i++;
4853
            }
4854
4855
            // Add revenue stamp to total
4856
            $this->total_ttc += isset($this->revenuestamp) ? $this->revenuestamp : 0;
0 ignored issues
show
Bug Best Practice introduced by
The property revenuestamp does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
4857
            $this->multicurrency_total_ttc += isset($this->revenuestamp) ? ($this->revenuestamp * $multicurrency_tx) : 0;
4858
4859
            // Situations totals
4860
            if (!empty($this->situation_cycle_ref) && !empty($this->situation_counter) && $this->situation_counter > 1 && method_exists($this, 'get_prev_sits')) {
0 ignored issues
show
Bug Best Practice introduced by
The property situation_counter does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property situation_cycle_ref does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
4861
                include_once DOL_DOCUMENT_ROOT . '/compta/facture/class/facture.class.php';
4862
                if ($this->type != Facture::TYPE_CREDIT_NOTE) { // @phpstan-ignore-line
0 ignored issues
show
Bug introduced by
The type Dolibarr\Core\Base\Facture was not found. Did you mean Facture? If so, make sure to prefix the type with \.
Loading history...
4863
                    if (getDolGlobalInt('INVOICE_USE_SITUATION') != 2) {
4864
                        $prev_sits = $this->get_prev_sits();
0 ignored issues
show
Bug introduced by
The method get_prev_sits() does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

4864
                        /** @scrutinizer ignore-call */ 
4865
                        $prev_sits = $this->get_prev_sits();
Loading history...
4865
4866
                        foreach ($prev_sits as $sit) {                // $sit is an object Facture loaded with a fetch.
4867
                            $this->total_ht -= $sit->total_ht;
4868
                            $this->total_tva -= $sit->total_tva;
4869
                            $this->total_localtax1 -= $sit->total_localtax1;
4870
                            $this->total_localtax2 -= $sit->total_localtax2;
4871
                            $this->total_ttc -= $sit->total_ttc;
4872
                            $this->multicurrency_total_ht -= $sit->multicurrency_total_ht;
4873
                            $this->multicurrency_total_tva -= $sit->multicurrency_total_tva;
4874
                            $this->multicurrency_total_ttc -= $sit->multicurrency_total_ttc;
4875
                        }
4876
                    }
4877
                }
4878
            }
4879
4880
            // Clean total
4881
            $this->total_ht = (float)price2num($this->total_ht);
4882
            $this->total_tva = (float)price2num($this->total_tva);
4883
            $this->total_localtax1 = (float)price2num($this->total_localtax1);
4884
            $this->total_localtax2 = (float)price2num($this->total_localtax2);
4885
            $this->total_ttc = (float)price2num($this->total_ttc);
4886
4887
            $this->db->free($resql);
4888
4889
            // Now update global fields total_ht, total_ttc, total_tva, total_localtax1, total_localtax2, multicurrency_total_* of main object
4890
            $fieldht = 'total_ht';
4891
            $fieldtva = 'tva';
4892
            $fieldlocaltax1 = 'localtax1';
4893
            $fieldlocaltax2 = 'localtax2';
4894
            $fieldttc = 'total_ttc';
4895
            // Specific code for backward compatibility with old field names
4896
            if (in_array($this->element, array('propal', 'commande', 'facture', 'facturerec', 'supplier_proposal', 'order_supplier', 'facture_fourn', 'invoice_supplier', 'invoice_supplier_rec', 'expensereport'))) {
4897
                $fieldtva = 'total_tva';
4898
            }
4899
4900
            if (!$error && empty($nodatabaseupdate)) {
4901
                $sql = "UPDATE " . $this->db->prefix() . $this->table_element . ' SET';
4902
                $sql .= " " . $fieldht . " = " . ((float)price2num($this->total_ht, 'MT', 1)) . ",";
4903
                $sql .= " " . $fieldtva . " = " . ((float)price2num($this->total_tva, 'MT', 1)) . ",";
4904
                $sql .= " " . $fieldlocaltax1 . " = " . ((float)price2num($this->total_localtax1, 'MT', 1)) . ",";
4905
                $sql .= " " . $fieldlocaltax2 . " = " . ((float)price2num($this->total_localtax2, 'MT', 1)) . ",";
4906
                $sql .= " " . $fieldttc . " = " . ((float)price2num($this->total_ttc, 'MT', 1));
4907
                $sql .= ", multicurrency_total_ht = " . ((float)price2num($this->multicurrency_total_ht, 'MT', 1));
4908
                $sql .= ", multicurrency_total_tva = " . ((float)price2num($this->multicurrency_total_tva, 'MT', 1));
4909
                $sql .= ", multicurrency_total_ttc = " . ((float)price2num($this->multicurrency_total_ttc, 'MT', 1));
4910
                $sql .= " WHERE rowid = " . ((int)$this->id);
4911
4912
                dol_syslog(get_class($this) . "::update_price", LOG_DEBUG);
4913
                $resql = $this->db->query($sql);
4914
4915
                if (!$resql) {
4916
                    $error++;
4917
                    $this->error = $this->db->lasterror();
4918
                    $this->errors[] = $this->db->lasterror();
4919
                }
4920
            }
4921
4922
            if (!$error) {
4923
                $this->db->commit();
4924
                return 1;
4925
            } else {
4926
                $this->db->rollback();
4927
                return -1;
4928
            }
4929
        } else {
4930
            dol_print_error($this->db, 'Bad request in update_price');
4931
            return -1;
4932
        }
4933
    }
4934
4935
    /**
4936
     *  Add an object link into llx_element_element.
4937
     *
4938
     * @param string $origin Linked element type
4939
     * @param int $origin_id Linked element id
4940
     * @param User $f_user User that create
4941
     * @param int $notrigger 1=Does not execute triggers, 0=execute triggers
4942
     * @return     int                 Return integer <=0 if KO, >0 if OK
4943
     * @see        fetchObjectLinked(), updateObjectLinked(), deleteObjectLinked()
4944
     */
4945
    public function add_object_linked($origin = null, $origin_id = null, $f_user = null, $notrigger = 0)
4946
    {
4947
        // phpcs:enable
4948
        global $user, $hookmanager, $action;
4949
        $origin = (!empty($origin) ? $origin : $this->origin);
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

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

4949
        $origin = (!empty($origin) ? $origin : /** @scrutinizer ignore-deprecated */ $this->origin);

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
4950
        $origin_id = (!empty($origin_id) ? $origin_id : $this->origin_id);
4951
        $f_user = isset($f_user) ? $f_user : $user;
4952
4953
        // Special case
4954
        if ($origin == 'order') {
4955
            $origin = 'commande';
4956
        }
4957
        if ($origin == 'invoice') {
4958
            $origin = 'facture';
4959
        }
4960
        if ($origin == 'invoice_template') {
4961
            $origin = 'facturerec';
4962
        }
4963
        if ($origin == 'supplierorder') {
4964
            $origin = 'order_supplier';
4965
        }
4966
4967
        // Add module part to target type
4968
        $targettype = $this->getElementType();
4969
4970
        $parameters = array('targettype' => $targettype);
4971
        // Hook for explicitly set the targettype if it must be different than $this->element
4972
        $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4973
        if ($reshook > 0) {
4974
            if (!empty($hookmanager->resArray['targettype'])) {
4975
                $targettype = $hookmanager->resArray['targettype'];
4976
            }
4977
        }
4978
4979
        $this->db->begin();
4980
        $error = 0;
4981
4982
        $sql = "INSERT INTO " . $this->db->prefix() . "element_element (";
4983
        $sql .= "fk_source";
4984
        $sql .= ", sourcetype";
4985
        $sql .= ", fk_target";
4986
        $sql .= ", targettype";
4987
        $sql .= ") VALUES (";
4988
        $sql .= ((int)$origin_id);
4989
        $sql .= ", '" . $this->db->escape($origin) . "'";
4990
        $sql .= ", " . ((int)$this->id);
4991
        $sql .= ", '" . $this->db->escape($targettype) . "'";
4992
        $sql .= ")";
4993
4994
        dol_syslog(get_class($this) . "::add_object_linked", LOG_DEBUG);
4995
        if ($this->db->query($sql)) {
4996
            if (!$notrigger) {
4997
                // Call trigger
4998
                $this->context['link_origin'] = $origin;
4999
                $this->context['link_origin_id'] = $origin_id;
5000
                $result = $this->call_trigger('OBJECT_LINK_INSERT', $f_user);
5001
                if ($result < 0) {
5002
                    $error++;
5003
                }
5004
                // End call triggers
5005
            }
5006
        } else {
5007
            $this->error = $this->db->lasterror();
5008
            $error++;
5009
        }
5010
5011
        if (!$error) {
5012
            $this->db->commit();
5013
            return 1;
5014
        } else {
5015
            $this->db->rollback();
5016
            return 0;
5017
        }
5018
    }
5019
5020
    /**
5021
     * Return an element type string formatted like element_element target_type and source_type
5022
     *
5023
     * @return string
5024
     */
5025
    public function getElementType()
5026
    {
5027
        // Elements of the core modules having a `$module` property but for which we may not want to prefix the element name with the module name for finding the linked object in llx_element_element.
5028
        // It's because existing llx_element_element entries inserted prior to this modification (version <=14.2) may already use the element name alone in fk_source or fk_target (without the module name prefix).
5029
        $coreModule = array('knowledgemanagement', 'partnership', 'workstation', 'ticket', 'recruitment', 'eventorganization', 'asset');
5030
        // Add module part to target type if object has $module property and isn't in core modules.
5031
        return ((!empty($this->module) && !in_array($this->module, $coreModule)) ? $this->module . '_' : '') . $this->element;
5032
    }
5033
5034
    /**
5035
     *  Fetch array of objects linked to current object (object of enabled modules only). Links are loaded into
5036
     *      this->linkedObjectsIds array +
5037
     *      this->linkedObjects array if $loadalsoobjects = 1 or $loadalsoobjects = type
5038
     *  Possible usage for parameters:
5039
     *  - all parameters empty -> we look all link to current object (current object can be source or target)
5040
     *  - source id+type -> will get list of targets linked to source
5041
     *  - target id+type -> will get list of sources linked to target
5042
     *  - source id+type + target type -> will get list of targets of the type linked to source
5043
     *  - target id+type + source type -> will get list of sources of the type linked to target
5044
     *
5045
     * @param  ?int $sourceid Object source id (if not defined, $this->id)
5046
     * @param string $sourcetype Object source type (if not defined, $this->element)
5047
     * @param  ?int $targetid Object target id (if not defined, $this->id)
5048
     * @param string $targettype Object target type (if not defined, $this->element)
5049
     * @param string $clause 'OR' or 'AND' clause used when both source id and target id are provided
5050
     * @param int<0,1> $alsosametype 0=Return only links to object that differs from source type. 1=Include also link to objects of same type.
5051
     * @param string $orderby SQL 'ORDER BY' clause
5052
     * @param int<0,1>|string $loadalsoobjects Load also the array $this->linkedObjects. Use 0 to not load (increase performances), Use 1 to load all, Use value of type ('facture', 'facturerec', ...) to load only a type of object.
5053
     * @return int<-1,1>                       Return integer <0 if KO, >0 if OK
5054
     * @see    add_object_linked(), updateObjectLinked(), deleteObjectLinked()
5055
     */
5056
    public function fetchObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $clause = 'OR', $alsosametype = 1, $orderby = 'sourcetype', $loadalsoobjects = 1)
5057
    {
5058
        global $conf, $hookmanager, $action;
5059
5060
        // Important for pdf generation time reduction
5061
        // This boolean is true if $this->linkedObjects has already been loaded with all objects linked without filter
5062
        // If you need to force the reload, you can call clearObjectLinkedCache() before calling fetchObjectLinked()
5063
        if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
5064
            return 1;
5065
        }
5066
5067
        $this->linkedObjectsIds = array();
5068
        $this->linkedObjects = array();
5069
5070
        $justsource = false;
5071
        $justtarget = false;
5072
        $withtargettype = false;
5073
        $withsourcetype = false;
5074
5075
        $parameters = array('sourcetype' => $sourcetype, 'sourceid' => $sourceid, 'targettype' => $targettype, 'targetid' => $targetid);
5076
        // Hook for explicitly set the targettype if it must be differtent than $this->element
5077
        $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5078
        if ($reshook > 0) {
5079
            if (!empty($hookmanager->resArray['sourcetype'])) {
5080
                $sourcetype = $hookmanager->resArray['sourcetype'];
5081
            }
5082
            if (!empty($hookmanager->resArray['sourceid'])) {
5083
                $sourceid = $hookmanager->resArray['sourceid'];
5084
            }
5085
            if (!empty($hookmanager->resArray['targettype'])) {
5086
                $targettype = $hookmanager->resArray['targettype'];
5087
            }
5088
            if (!empty($hookmanager->resArray['targetid'])) {
5089
                $targetid = $hookmanager->resArray['targetid'];
5090
            }
5091
        }
5092
5093
        if (!empty($sourceid) && !empty($sourcetype) && empty($targetid)) {
5094
            $justsource = true; // the source (id and type) is a search criteria
5095
            if (!empty($targettype)) {
5096
                $withtargettype = true;
5097
            }
5098
        }
5099
        if (!empty($targetid) && !empty($targettype) && empty($sourceid)) {
5100
            $justtarget = true; // the target (id and type) is a search criteria
5101
            if (!empty($sourcetype)) {
5102
                $withsourcetype = true;
5103
            }
5104
        }
5105
5106
        $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
5107
        $targetid = (!empty($targetid) ? $targetid : $this->id);
5108
        $sourcetype = (!empty($sourcetype) ? $sourcetype : $this->element);
5109
        $targettype = (!empty($targettype) ? $targettype : $this->element);
5110
5111
        /*if (empty($sourceid) && empty($targetid))
5112
         {
5113
         dol_syslog('Bad usage of function. No source nor target id defined (nor as parameter nor as object id)', LOG_ERR);
5114
         return -1;
5115
         }*/
5116
5117
        // Links between objects are stored in table element_element
5118
        $sql = "SELECT rowid, fk_source, sourcetype, fk_target, targettype";
5119
        $sql .= " FROM " . $this->db->prefix() . "element_element";
5120
        $sql .= " WHERE ";
5121
        if ($justsource || $justtarget) {
5122
            if ($justsource) {
5123
                $sql .= "fk_source = " . ((int)$sourceid) . " AND sourcetype = '" . $this->db->escape($sourcetype) . "'";
5124
                if ($withtargettype) {
5125
                    $sql .= " AND targettype = '" . $this->db->escape($targettype) . "'";
5126
                }
5127
            } elseif ($justtarget) {
5128
                $sql .= "fk_target = " . ((int)$targetid) . " AND targettype = '" . $this->db->escape($targettype) . "'";
5129
                if ($withsourcetype) {
5130
                    $sql .= " AND sourcetype = '" . $this->db->escape($sourcetype) . "'";
5131
                }
5132
            }
5133
        } else {
5134
            $sql .= "(fk_source = " . ((int)$sourceid) . " AND sourcetype = '" . $this->db->escape($sourcetype) . "')";
5135
            $sql .= " " . $clause . " (fk_target = " . ((int)$targetid) . " AND targettype = '" . $this->db->escape($targettype) . "')";
5136
            if ($loadalsoobjects && $this->id > 0 && $sourceid == $this->id && $sourcetype == $this->element && $targetid == $this->id && $targettype == $this->element && $clause == 'OR') {
5137
                $this->linkedObjectsFullLoaded[$this->id] = true;
5138
            }
5139
        }
5140
        $sql .= " ORDER BY " . $orderby;
5141
5142
        dol_syslog(get_class($this) . "::fetchObjectLink", LOG_DEBUG);
5143
        $resql = $this->db->query($sql);
5144
        if ($resql) {
5145
            $num = $this->db->num_rows($resql);
5146
            $i = 0;
5147
            while ($i < $num) {
5148
                $obj = $this->db->fetch_object($resql);
5149
                if ($justsource || $justtarget) {
5150
                    if ($justsource) {
5151
                        $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
5152
                    } elseif ($justtarget) {
5153
                        $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
5154
                    }
5155
                } else {
5156
                    if ($obj->fk_source == $sourceid && $obj->sourcetype == $sourcetype) {
5157
                        $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
5158
                    }
5159
                    if ($obj->fk_target == $targetid && $obj->targettype == $targettype) {
5160
                        $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
5161
                    }
5162
                }
5163
                $i++;
5164
            }
5165
5166
            if (!empty($this->linkedObjectsIds)) {
5167
                $tmparray = $this->linkedObjectsIds;
5168
                foreach ($tmparray as $objecttype => $objectids) {       // $objecttype is a module name ('facture', 'mymodule', ...) or a module name with a suffix ('project_task', 'mymodule_myobj', ...)
5169
                    $element_properties = getElementProperties($objecttype);
5170
                    $element = $element_properties['element'];
5171
                    $classPath = $element_properties['classpath'];
5172
                    $classFile = $element_properties['classfile'];
5173
                    $className = $element_properties['classname'];
5174
                    $module = $element_properties['module'];
5175
5176
                    // Here $module, $classFile and $className are set, we can use them.
5177
                    if (isModEnabled($module) && (($element != $this->element) || $alsosametype)) {
5178
                        if ($loadalsoobjects && (is_numeric($loadalsoobjects) || ($loadalsoobjects === $objecttype))) {
5179
                            dol_include_once('/' . $classPath . '/' . $classFile . '.class.php');
5180
                            if (class_exists($className)) {
5181
                                foreach ($objectids as $i => $objectid) {   // $i is rowid into llx_element_element
5182
                                    $object = new $className($this->db);
5183
                                    $ret = $object->fetch($objectid);
5184
                                    if ($ret >= 0) {
5185
                                        $this->linkedObjects[$objecttype][$i] = $object;
5186
                                    }
5187
                                }
5188
                            }
5189
                        }
5190
                    } else {
5191
                        unset($this->linkedObjectsIds[$objecttype]);
5192
                    }
5193
                }
5194
            }
5195
            return 1;
5196
        } else {
5197
            dol_print_error($this->db);
5198
            return -1;
5199
        }
5200
    }
5201
5202
    /**
5203
     *  Clear the cache saying that all linked object were already loaded. So next fetchObjectLinked will reload all links.
5204
     *
5205
     * @return int                     Return integer <0 if KO, >0 if OK
5206
     * @see    fetchObjectLinked()
5207
     */
5208
    public function clearObjectLinkedCache()
5209
    {
5210
        if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
5211
            unset($this->linkedObjectsFullLoaded[$this->id]);
5212
        }
5213
5214
        return 1;
5215
    }
5216
5217
5218
    // --------------------
5219
    // TODO: All functions here must be redesigned and moved as they are not business functions but output functions
5220
    // --------------------
5221
5222
    /* This is to show add lines */
5223
5224
    /**
5225
     *  Update object linked of a current object
5226
     *
5227
     * @param int $sourceid Object source id
5228
     * @param string $sourcetype Object source type
5229
     * @param int $targetid Object target id
5230
     * @param string $targettype Object target type
5231
     * @param User $f_user User that create
5232
     * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
5233
     * @return                         int >0 if OK, <0 if KO
5234
     * @see    add_object_linked(), fetObjectLinked(), deleteObjectLinked()
5235
     */
5236
    public function updateObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $f_user = null, $notrigger = 0)
5237
    {
5238
        global $user;
5239
        $updatesource = false;
5240
        $updatetarget = false;
5241
        $f_user = isset($f_user) ? $f_user : $user;
5242
5243
        if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
5244
            $updatesource = true;
5245
        } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
5246
            $updatetarget = true;
5247
        }
5248
5249
        $this->db->begin();
5250
        $error = 0;
5251
5252
        $sql = "UPDATE " . $this->db->prefix() . "element_element SET ";
5253
        if ($updatesource) {
5254
            $sql .= "fk_source = " . ((int)$sourceid);
5255
            $sql .= ", sourcetype = '" . $this->db->escape($sourcetype) . "'";
5256
            $sql .= " WHERE fk_target = " . ((int)$this->id);
5257
            $sql .= " AND targettype = '" . $this->db->escape($this->element) . "'";
5258
        } elseif ($updatetarget) {
5259
            $sql .= "fk_target = " . ((int)$targetid);
5260
            $sql .= ", targettype = '" . $this->db->escape($targettype) . "'";
5261
            $sql .= " WHERE fk_source = " . ((int)$this->id);
5262
            $sql .= " AND sourcetype = '" . $this->db->escape($this->element) . "'";
5263
        }
5264
5265
        dol_syslog(get_class($this) . "::updateObjectLinked", LOG_DEBUG);
5266
        if ($this->db->query($sql)) {
5267
            if (!$notrigger) {
5268
                // Call trigger
5269
                $this->context['link_source_id'] = $sourceid;
5270
                $this->context['link_source_type'] = $sourcetype;
5271
                $this->context['link_target_id'] = $targetid;
5272
                $this->context['link_target_type'] = $targettype;
5273
                $result = $this->call_trigger('OBJECT_LINK_MODIFY', $f_user);
5274
                if ($result < 0) {
5275
                    $error++;
5276
                }
5277
                // End call triggers
5278
            }
5279
        } else {
5280
            $this->error = $this->db->lasterror();
5281
            $error++;
5282
        }
5283
5284
        if (!$error) {
5285
            $this->db->commit();
5286
            return 1;
5287
        } else {
5288
            $this->db->rollback();
5289
            return -1;
5290
        }
5291
    }
5292
5293
5294
5295
    /* This is to show array of line of details */
5296
5297
    /**
5298
     *      Set status of an object.
5299
     *
5300
     * @param int $status Status to set
5301
     * @param int $elementId Id of element to force (use this->id by default if null)
5302
     * @param string $elementType Type of element to force (use this->table_element by default)
5303
     * @param string $trigkey Trigger key to use for trigger. Use '' means automatic but it is not recommended and is deprecated.
5304
     * @param string $fieldstatus Name of status field in this->table_element
5305
     * @return int                     Return integer <0 if KO, >0 if OK
5306
     */
5307
    public function setStatut($status, $elementId = null, $elementType = '', $trigkey = '', $fieldstatus = 'fk_statut')
5308
    {
5309
        global $user;
5310
5311
        $savElementId = $elementId; // To be used later to know if we were using the method using the id of this or not.
5312
5313
        $elementId = (!empty($elementId) ? $elementId : $this->id);
5314
        $elementTable = (!empty($elementType) ? $elementType : $this->table_element);
5315
5316
        $this->db->begin();
5317
5318
        if ($elementTable == 'facture_rec') {
5319
            $fieldstatus = "suspended";
5320
        }
5321
        if ($elementTable == 'mailing') {
5322
            $fieldstatus = "statut";
5323
        }
5324
        if ($elementTable == 'cronjob') {
5325
            $fieldstatus = "status";
5326
        }
5327
        if ($elementTable == 'user') {
5328
            $fieldstatus = "statut";
5329
        }
5330
        if ($elementTable == 'expensereport') {
5331
            $fieldstatus = "fk_statut";
5332
        }
5333
        if ($elementTable == 'receptiondet_batch') {
5334
            $fieldstatus = "status";
5335
        }
5336
        if ($elementTable == 'prelevement_bons') {
5337
            $fieldstatus = "statut";
5338
        }
5339
        if (isset($this->fields) && is_array($this->fields) && array_key_exists('status', $this->fields)) {
5340
            $fieldstatus = 'status';
5341
        }
5342
5343
        $sql = "UPDATE " . $this->db->prefix() . $elementTable;
5344
        $sql .= " SET " . $fieldstatus . " = " . ((int)$status);
5345
        // If status = 1 = validated, update also fk_user_valid
5346
        // TODO Replace the test on $elementTable by doing a test on existence of the field in $this->fields
5347
        if ($status == 1 && in_array($elementTable, array('expensereport', 'inventory'))) {
5348
            $sql .= ", fk_user_valid = " . ((int)$user->id);
5349
        }
5350
        if ($status == 1 && in_array($elementTable, array('expensereport'))) {
5351
            $sql .= ", date_valid = '" . $this->db->idate(dol_now()) . "'";
5352
        }
5353
        if ($status == 1 && in_array($elementTable, array('inventory'))) {
5354
            $sql .= ", date_validation = '" . $this->db->idate(dol_now()) . "'";
5355
        }
5356
        $sql .= " WHERE rowid = " . ((int)$elementId);
5357
        $sql .= " AND " . $fieldstatus . " <> " . ((int)$status);    // We avoid update if status already correct
5358
5359
        dol_syslog(get_class($this) . "::setStatut", LOG_DEBUG);
5360
        $resql = $this->db->query($sql);
5361
        if ($resql) {
5362
            $error = 0;
5363
5364
            $nb_rows_affected = $this->db->affected_rows($resql);   // should be 1 or 0 if status was already correct
5365
5366
            if ($nb_rows_affected > 0) {
5367
                if (empty($trigkey)) {
5368
                    // Try to guess trigkey (for backward compatibility, now we should have trigkey defined into the call of setStatus)
5369
                    if ($this->element == 'supplier_proposal' && $status == 2) {
5370
                        $trigkey = 'SUPPLIER_PROPOSAL_SIGN'; // 2 = SupplierProposal::STATUS_SIGNED. Can't use constant into this generic class
5371
                    }
5372
                    if ($this->element == 'supplier_proposal' && $status == 3) {
5373
                        $trigkey = 'SUPPLIER_PROPOSAL_REFUSE'; // 3 = SupplierProposal::STATUS_REFUSED. Can't use constant into this generic class
5374
                    }
5375
                    if ($this->element == 'supplier_proposal' && $status == 4) {
5376
                        $trigkey = 'SUPPLIER_PROPOSAL_CLOSE'; // 4 = SupplierProposal::STATUS_CLOSED. Can't use constant into this generic class
5377
                    }
5378
                    if ($this->element == 'fichinter' && $status == 3) {
5379
                        $trigkey = 'FICHINTER_CLASSIFY_DONE';
5380
                    }
5381
                    if ($this->element == 'fichinter' && $status == 2) {
5382
                        $trigkey = 'FICHINTER_CLASSIFY_BILLED';
5383
                    }
5384
                    if ($this->element == 'fichinter' && $status == 1) {
5385
                        $trigkey = 'FICHINTER_CLASSIFY_UNBILLED';
5386
                    }
5387
                }
5388
5389
                if ($trigkey) {
5390
                    // Call trigger
5391
                    $result = $this->call_trigger($trigkey, $user);
5392
                    if ($result < 0) {
5393
                        $error++;
5394
                    }
5395
                    // End call triggers
5396
                }
5397
            } else {
5398
                // The status was probably already good. We do nothing more, no triggers.
5399
            }
5400
5401
            if (!$error) {
5402
                $this->db->commit();
5403
5404
                if (empty($savElementId)) {
5405
                    // If the element we update is $this (so $elementId was provided as null)
5406
                    if ($fieldstatus == 'tosell') {
5407
                        $this->status = $status;
5408
                    } elseif ($fieldstatus == 'tobuy') {
5409
                        $this->status_buy = $status;    // @phpstan-ignore-line
5410
                    } else {
5411
                        $this->status = $status;
5412
                    }
5413
                }
5414
5415
                return 1;
5416
            } else {
5417
                $this->db->rollback();
5418
                dol_syslog(get_class($this) . "::setStatut " . $this->error, LOG_ERR);
5419
                return -1;
5420
            }
5421
        } else {
5422
            $this->error = $this->db->lasterror();
5423
            $this->db->rollback();
5424
            return -1;
5425
        }
5426
    }
5427
5428
    /**
5429
     *  Load type of canvas of an object if it exists
5430
     *
5431
     * @param int $id Record id
5432
     * @param string $ref Record ref
5433
     * @return     int             Return integer <0 if KO, 0 if nothing done, >0 if OK
5434
     */
5435
    public function getCanvas($id = 0, $ref = '')
5436
    {
5437
        global $conf;
5438
5439
        if (empty($id) && empty($ref)) {
5440
            return 0;
5441
        }
5442
        if (getDolGlobalString('MAIN_DISABLE_CANVAS')) {
5443
            return 0; // To increase speed. Not enabled by default.
5444
        }
5445
5446
        // Clean parameters
5447
        $ref = trim($ref);
5448
5449
        $sql = "SELECT rowid, canvas";
5450
        $sql .= " FROM " . $this->db->prefix() . $this->table_element;
5451
        $sql .= " WHERE entity IN (" . getEntity($this->element) . ")";
5452
        if (!empty($id)) {
5453
            $sql .= " AND rowid = " . ((int)$id);
5454
        }
5455
        if (!empty($ref)) {
5456
            $sql .= " AND ref = '" . $this->db->escape($ref) . "'";
5457
        }
5458
5459
        $resql = $this->db->query($sql);
5460
        if ($resql) {
5461
            $obj = $this->db->fetch_object($resql);
5462
            if ($obj) {
5463
                $this->canvas = $obj->canvas;
5464
                return 1;
5465
            } else {
5466
                return 0;
5467
            }
5468
        } else {
5469
            dol_print_error($this->db);
5470
            return -1;
5471
        }
5472
    }
5473
5474
5475
    /* This is to show array of line of details of source object */
5476
5477
    /**
5478
     *  Get special code of a line
5479
     *
5480
     * @param int $lineid Id of line
5481
     * @return int                 Special code
5482
     */
5483
    public function getSpecialCode($lineid)
5484
    {
5485
        $sql = "SELECT special_code FROM " . $this->db->prefix() . $this->table_element_line;
5486
        $sql .= " WHERE rowid = " . ((int)$lineid);
5487
        $resql = $this->db->query($sql);
5488
        if ($resql) {
5489
            $row = $this->db->fetch_row($resql);
5490
            return (!empty($row[0]) ? $row[0] : 0);
5491
        }
5492
5493
        return 0;
5494
    }
5495
5496
    /**
5497
     *  Function to say how many lines object contains
5498
     *
5499
     * @param int $predefined -1=All, 0=Count free product/service only, 1=Count predefined product/service only, 2=Count predefined product, 3=Count predefined service
5500
     * @return int                     Return integer <0 if KO, 0 if no predefined products, nb of lines with predefined products if found
5501
     */
5502
    public function hasProductsOrServices($predefined = -1)
5503
    {
5504
        $nb = 0;
5505
5506
        foreach ($this->lines as $key => $val) {
5507
            $qualified = 0;
5508
            if ($predefined == -1) {
5509
                $qualified = 1;
5510
            }
5511
            if ($predefined == 1 && $val->fk_product > 0) {
5512
                $qualified = 1;
5513
            }
5514
            if ($predefined == 0 && $val->fk_product <= 0) {
5515
                $qualified = 1;
5516
            }
5517
            if ($predefined == 2 && $val->fk_product > 0 && $val->product_type == 0) {
5518
                $qualified = 1;
5519
            }
5520
            if ($predefined == 3 && $val->fk_product > 0 && $val->product_type == 1) {
5521
                $qualified = 1;
5522
            }
5523
            if ($qualified) {
5524
                $nb++;
5525
            }
5526
        }
5527
        dol_syslog(get_class($this) . '::hasProductsOrServices we found ' . $nb . ' qualified lines of products/servcies');
5528
        return $nb;
5529
    }
5530
5531
5532
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5533
5534
    /**
5535
     * Function that returns the total amount HT of discounts applied for all lines.
5536
     *
5537
     * @return  float|null          Total amount of discount, or null if $table_element_line is empty
5538
     */
5539
    public function getTotalDiscount()
5540
    {
5541
        if (!empty($this->table_element_line)) {
5542
            $total_discount = 0.00;
5543
5544
            $sql = "SELECT subprice as pu_ht, qty, remise_percent, total_ht";
5545
            $sql .= " FROM " . $this->db->prefix() . $this->table_element_line;
5546
            $sql .= " WHERE " . $this->fk_element . " = " . ((int)$this->id);
5547
5548
            dol_syslog(get_class($this) . '::getTotalDiscount', LOG_DEBUG);
5549
            $resql = $this->db->query($sql);
5550
            if ($resql) {
5551
                $num = $this->db->num_rows($resql);
5552
                $i = 0;
5553
                while ($i < $num) {
5554
                    $obj = $this->db->fetch_object($resql);
5555
5556
                    $pu_ht = $obj->pu_ht;
5557
                    $qty = $obj->qty;
5558
                    $total_ht = $obj->total_ht;
5559
5560
                    $total_discount_line = (float)price2num(($pu_ht * $qty) - $total_ht, 'MT');
5561
                    $total_discount += $total_discount_line;
5562
5563
                    $i++;
5564
                }
5565
            }
5566
5567
            //print $total_discount; exit;
5568
            return (float)price2num($total_discount);
5569
        }
5570
5571
        return null;
5572
    }
5573
5574
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5575
5576
    /**
5577
     * Return into unit=0, the calculated total of weight and volume of all lines * qty
5578
     * Calculate by adding weight and volume of each product line, so properties ->volume/volume_units/weight/weight_units must be loaded on line.
5579
     *
5580
     * @return  array{weight:int|float,volume:int|float,ordered:int|float,toship:int|float}|array{}     array('weight'=>...,'volume'=>...)
5581
     */
5582
    public function getTotalWeightVolume()
5583
    {
5584
        $totalWeight = 0;
5585
        $totalVolume = 0;
5586
        // defined for shipment only
5587
        $totalOrdered = '';
5588
        // defined for shipment only
5589
        $totalToShip = '';
5590
5591
        if (empty($this->lines)) {
5592
            return array();
5593
        }
5594
5595
        foreach ($this->lines as $line) {
5596
            if (isset($line->qty_asked)) {
5597
                if (empty($totalOrdered)) {
5598
                    $totalOrdered = 0; // Avoid warning because $totalOrdered is ''
5599
                }
5600
                $totalOrdered += $line->qty_asked; // defined for shipment only
5601
            }
5602
            if (isset($line->qty_shipped)) {
5603
                if (empty($totalToShip)) {
5604
                    $totalToShip = 0; // Avoid warning because $totalToShip is ''
5605
                }
5606
                $totalToShip += $line->qty_shipped; // defined for shipment only
5607
            } elseif ($line->element == 'commandefournisseurdispatch' && isset($line->qty)) {
5608
                if (empty($totalToShip)) {
5609
                    $totalToShip = 0;
5610
                }
5611
                $totalToShip += $line->qty; // defined for reception only
5612
            }
5613
5614
            // Define qty, weight, volume, weight_units, volume_units
5615
            if ($this->element == 'shipping') {
5616
                // for shipments
5617
                $qty = $line->qty_shipped ? $line->qty_shipped : 0;
5618
            } else {
5619
                $qty = $line->qty ? $line->qty : 0;
5620
            }
5621
5622
            $weight = !empty($line->weight) ? $line->weight : 0;
5623
            ($weight == 0 && !empty($line->product->weight)) ? $weight = $line->product->weight : 0;
5624
            $volume = !empty($line->volume) ? $line->volume : 0;
5625
            ($volume == 0 && !empty($line->product->volume)) ? $volume = $line->product->volume : 0;
5626
5627
            $weight_units = !empty($line->weight_units) ? $line->weight_units : 0;
5628
            ($weight_units == 0 && !empty($line->product->weight_units)) ? $weight_units = $line->product->weight_units : 0;
5629
            $volume_units = !empty($line->volume_units) ? $line->volume_units : 0;
5630
            ($volume_units == 0 && !empty($line->product->volume_units)) ? $volume_units = $line->product->volume_units : 0;
5631
5632
            $weightUnit = 0;
5633
            $volumeUnit = 0;
5634
            if (!empty($weight_units)) {
5635
                $weightUnit = $weight_units;
5636
            }
5637
            if (!empty($volume_units)) {
5638
                $volumeUnit = $volume_units;
5639
            }
5640
5641
            if (empty($totalWeight)) {
5642
                $totalWeight = 0; // Avoid warning because $totalWeight is ''
5643
            }
5644
            if (empty($totalVolume)) {
5645
                $totalVolume = 0; // Avoid warning because $totalVolume is ''
5646
            }
5647
5648
            //var_dump($line->volume_units);
5649
            if ($weight_units < 50) {   // < 50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
5650
                $trueWeightUnit = pow(10, $weightUnit);
5651
                $totalWeight += $weight * $qty * $trueWeightUnit;
5652
            } else {
5653
                if ($weight_units == 99) {
5654
                    // conversion 1 Pound = 0.45359237 KG
5655
                    $trueWeightUnit = 0.45359237;
5656
                    $totalWeight += $weight * $qty * $trueWeightUnit;
5657
                } elseif ($weight_units == 98) {
5658
                    // conversion 1 Ounce = 0.0283495 KG
5659
                    $trueWeightUnit = 0.0283495;
5660
                    $totalWeight += $weight * $qty * $trueWeightUnit;
5661
                } else {
5662
                    $totalWeight += $weight * $qty; // This may be wrong if we mix different units
5663
                }
5664
            }
5665
            if ($volume_units < 50) {   // >50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
5666
                //print $line->volume."x".$line->volume_units."x".($line->volume_units < 50)."x".$volumeUnit;
5667
                $trueVolumeUnit = pow(10, $volumeUnit);
5668
                //print $line->volume;
5669
                $totalVolume += $volume * $qty * $trueVolumeUnit;
5670
            } else {
5671
                $totalVolume += $volume * $qty; // This may be wrong if we mix different units
5672
            }
5673
        }
5674
5675
        return array('weight' => $totalWeight, 'volume' => $totalVolume, 'ordered' => $totalOrdered, 'toship' => $totalToShip);
5676
    }
5677
5678
    /**
5679
     *  Set extra parameters
5680
     *
5681
     * @return int      Return integer <0 if KO, >0 if OK
5682
     */
5683
    public function setExtraParameters()
5684
    {
5685
        $this->db->begin();
5686
5687
        $extraparams = (!empty($this->extraparams) ? json_encode($this->extraparams) : null);
5688
5689
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
5690
        $sql .= " SET extraparams = " . (!empty($extraparams) ? "'" . $this->db->escape($extraparams) . "'" : "null");
5691
        $sql .= " WHERE rowid = " . ((int)$this->id);
5692
5693
        dol_syslog(get_class($this) . "::setExtraParameters", LOG_DEBUG);
5694
        $resql = $this->db->query($sql);
5695
        if (!$resql) {
5696
            $this->error = $this->db->lasterror();
5697
            $this->db->rollback();
5698
            return -1;
5699
        } else {
5700
            $this->db->commit();
5701
            return 1;
5702
        }
5703
    }
5704
5705
    /**
5706
     *  Show add free and predefined products/services form
5707
     *
5708
     * @param int $dateSelector 1=Show also date range input fields
5709
     * @param Societe $seller Object thirdparty who sell
5710
     * @param Societe $buyer Object thirdparty who buy
5711
     * @param string $defaulttpldir Directory where to find the template
5712
     * @return void
5713
     */
5714
    public function formAddObjectLine($dateSelector, $seller, $buyer, $defaulttpldir = '/core/tpl')
5715
    {
5716
        global $conf, $user, $langs, $object, $hookmanager, $extrafields, $form;
5717
5718
        // Line extrafield
5719
        if (!is_object($extrafields)) {
5720
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/class/extrafields.class.php';
5721
            $extrafields = new ExtraFields($this->db);
5722
        }
5723
        $extrafields->fetch_name_optionals_label($this->table_element_line);
5724
5725
        // Output template part (modules that overwrite templates must declare this into descriptor)
5726
        // Use global variables + $dateSelector + $seller and $buyer
5727
        // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook 'formAddObjectLine'.
5728
        $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5729
        foreach ($dirtpls as $module => $reldir) {
5730
            if (!empty($module)) {
5731
                $tpl = dol_buildpath($reldir . '/objectline_create.tpl.php');
5732
            } else {
5733
                $tpl = DOL_DOCUMENT_ROOT . $reldir . '/objectline_create.tpl.php';
5734
            }
5735
5736
            if (empty($conf->file->strict_mode)) {
5737
                $res = @include $tpl;
5738
            } else {
5739
                $res = include $tpl; // for debug
5740
            }
5741
            if ($res) {
5742
                break;
5743
            }
5744
        }
5745
    }
5746
5747
    /**
5748
     *  Return HTML table for object lines
5749
     *  TODO Move this into an output class file (htmlline.class.php)
5750
     *  If lines are into a template, title must also be into a template
5751
     *  But for the moment we don't know if it's possible as we keep a method available on overloaded objects.
5752
     *
5753
     * @param string $action Action code
5754
     * @param Societe $seller Object of seller third party
5755
     * @param Societe $buyer Object of buyer third party
5756
     * @param int $selected ID line selected
5757
     * @param int $dateSelector 1=Show also date range input fields
5758
     * @param string $defaulttpldir Directory where to find the template
5759
     * @return void
5760
     */
5761
    public function printObjectLines($action, $seller, $buyer, $selected = 0, $dateSelector = 0, $defaulttpldir = '/core/tpl')
5762
    {
5763
        global $conf, $hookmanager, $langs, $user, $form, $extrafields, $object;
5764
        // TODO We should not use global var for this
5765
        global $inputalsopricewithtax, $usemargins, $disableedit, $disablemove, $disableremove, $outputalsopricetotalwithtax;
5766
5767
        // Define $usemargins (used by objectline_xxx.tpl.php files)
5768
        $usemargins = 0;
5769
        if (isModEnabled('margin') && !empty($this->element) && in_array($this->element, array('facture', 'facturerec', 'propal', 'commande'))) {
5770
            $usemargins = 1;
5771
        }
5772
5773
        $num = count($this->lines);
5774
5775
        // Line extrafield
5776
        if (!is_object($extrafields)) {
5777
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/class/extrafields.class.php';
5778
            $extrafields = new ExtraFields($this->db);
5779
        }
5780
        $extrafields->fetch_name_optionals_label($this->table_element_line);
5781
5782
        $parameters = array('num' => $num, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $this->table_element_line);
5783
        $reshook = $hookmanager->executeHooks('printObjectLineTitle', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5784
        if (empty($reshook)) {
5785
            // Output template part (modules that overwrite templates must declare this into descriptor)
5786
            // Use global variables + $dateSelector + $seller and $buyer
5787
            // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook.
5788
            $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5789
            foreach ($dirtpls as $module => $reldir) {
5790
                $res = 0;
5791
                if (!empty($module)) {
5792
                    $tpl = dol_buildpath($reldir . '/objectline_title.tpl.php');
5793
                } else {
5794
                    $tpl = DOL_DOCUMENT_ROOT . $reldir . '/objectline_title.tpl.php';
5795
                }
5796
                if (file_exists($tpl)) {
5797
                    if (empty($conf->file->strict_mode)) {
5798
                        $res = @include $tpl;
5799
                    } else {
5800
                        $res = include $tpl; // for debug
5801
                    }
5802
                }
5803
                if ($res) {
5804
                    break;
5805
                }
5806
            }
5807
        }
5808
5809
        $i = 0;
5810
5811
        print "<!-- begin printObjectLines() --><tbody>\n";
5812
        foreach ($this->lines as $line) {
5813
            //Line extrafield
5814
            $line->fetch_optionals();
5815
5816
            //if (is_object($hookmanager) && (($line->product_type == 9 && !empty($line->special_code)) || !empty($line->fk_parent_line)))
5817
            if (is_object($hookmanager)) {   // Old code is commented on preceding line.
5818
                if (empty($line->fk_parent_line)) {
5819
                    $parameters = array('line' => $line, 'num' => $num, 'i' => $i, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $line->table_element, 'defaulttpldir' => $defaulttpldir);
5820
                    $reshook = $hookmanager->executeHooks('printObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5821
                } else {
5822
                    $parameters = array('line' => $line, 'num' => $num, 'i' => $i, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $line->table_element, 'fk_parent_line' => $line->fk_parent_line, 'defaulttpldir' => $defaulttpldir);
5823
                    $reshook = $hookmanager->executeHooks('printObjectSubLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5824
                }
5825
            }
5826
            if (empty($reshook)) {
5827
                $this->printObjectLine($action, $line, '', $num, $i, $dateSelector, $seller, $buyer, $selected, $extrafields, $defaulttpldir);
5828
            }
5829
5830
            $i++;
5831
        }
5832
        print "</tbody><!-- end printObjectLines() -->\n";
5833
    }
5834
5835
    /**
5836
     *  Return HTML content of a detail line
5837
     *  TODO Move this into an output class file (htmlline.class.php)
5838
     *
5839
     * @param string $action GET/POST action
5840
     * @param CommonObjectLine $line Selected object line to output
5841
     * @param string $var Not used
5842
     * @param int $num Number of line (0)
5843
     * @param int $i I
5844
     * @param int $dateSelector 1=Show also date range input fields
5845
     * @param Societe $seller Object of seller third party
5846
     * @param Societe $buyer Object of buyer third party
5847
     * @param int $selected ID line selected
5848
     * @param Extrafields $extrafields Object of extrafields
5849
     * @param string $defaulttpldir Directory where to find the template (deprecated)
5850
     * @return void
5851
     */
5852
    public function printObjectLine($action, $line, $var, $num, $i, $dateSelector, $seller, $buyer, $selected = 0, $extrafields = null, $defaulttpldir = '/core/tpl')
5853
    {
5854
        global $conf, $langs, $user, $object, $hookmanager;
5855
        global $form;
5856
        global $object_rights, $disableedit, $disablemove, $disableremove; // TODO We should not use global var for this !
5857
5858
        $object_rights = $this->getRights();
5859
5860
        // var used into tpl
5861
        $text = '';
5862
        $description = '';
5863
5864
        // Line in view mode
5865
        if ($action != 'editline' || $selected != $line->id) {
5866
            // Product
5867
            if (!empty($line->fk_product) && $line->fk_product > 0) {
5868
                $product_static = new Product($this->db);
5869
                $product_static->fetch($line->fk_product);
5870
5871
                $product_static->ref = $line->ref; //can change ref in hook
5872
                $product_static->label = !empty($line->label) ? $line->label : ""; //can change label in hook
5873
5874
                $text = $product_static->getNomUrl(1);
5875
5876
                // Define output language and label
5877
                if (getDolGlobalInt('MAIN_MULTILANGS')) {
5878
                    if (property_exists($this, 'socid') && !is_object($this->thirdparty)) {
5879
                        dol_print_error(null, 'Error: Method printObjectLine was called on an object and object->fetch_thirdparty was not done before');
5880
                        return;
5881
                    }
5882
5883
                    $prod = new Product($this->db);
5884
                    $prod->fetch($line->fk_product);
5885
5886
                    $outputlangs = $langs;
5887
                    $newlang = '';
5888
                    if (empty($newlang) && GETPOST('lang_id', 'aZ09')) {
5889
                        $newlang = GETPOST('lang_id', 'aZ09');
5890
                    }
5891
                    if (getDolGlobalString('PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE') && empty($newlang) && is_object($this->thirdparty)) {
5892
                        $newlang = $this->thirdparty->default_lang; // To use language of customer
5893
                    }
5894
                    if (!empty($newlang)) {
5895
                        $outputlangs = new Translate("", $conf);
5896
                        $outputlangs->setDefaultLang($newlang);
5897
                    }
5898
5899
                    $label = (!empty($prod->multilangs[$outputlangs->defaultlang]["label"])) ? $prod->multilangs[$outputlangs->defaultlang]["label"] : $line->product_label;
5900
                } else {
5901
                    $label = $line->product_label;
5902
                }
5903
5904
                $text .= ' - ' . (!empty($line->label) ? $line->label : $label);
5905
                $description .= (getDolGlobalInt('PRODUIT_DESC_IN_FORM_ACCORDING_TO_DEVICE') ? '' : (!empty($line->description) ? dol_htmlentitiesbr($line->description) : '')); // Description is what to show on popup. We shown nothing if already into desc.
5906
            }
5907
5908
            $line->pu_ttc = price2num((!empty($line->subprice) ? $line->subprice : 0) * (1 + ((!empty($line->tva_tx) ? $line->tva_tx : 0) / 100)), 'MU');
5909
5910
            // Output template part (modules that overwrite templates must declare this into descriptor)
5911
            // Use global variables + $dateSelector + $seller and $buyer
5912
            // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5913
            $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5914
            foreach ($dirtpls as $module => $reldir) {
5915
                $res = 0;
5916
                if (!empty($module)) {
5917
                    $tpl = dol_buildpath($reldir . '/objectline_view.tpl.php');
5918
                } else {
5919
                    $tpl = DOL_DOCUMENT_ROOT . $reldir . '/objectline_view.tpl.php';
5920
                }
5921
                //var_dump($tpl);
5922
                if (file_exists($tpl)) {
5923
                    if (empty($conf->file->strict_mode)) {
5924
                        $res = @include $tpl;
5925
                    } else {
5926
                        $res = include $tpl; // for debug
5927
                    }
5928
                }
5929
                if ($res) {
5930
                    break;
5931
                }
5932
            }
5933
        }
5934
5935
        // Line in update mode
5936
        if ($this->status == 0 && $action == 'editline' && $selected == $line->id) {
5937
            $label = (!empty($line->label) ? $line->label : (($line->fk_product > 0) ? $line->product_label : ''));
5938
5939
            $line->pu_ttc = price2num($line->subprice * (1 + ($line->tva_tx / 100)), 'MU');
5940
5941
            // Output template part (modules that overwrite templates must declare this into descriptor)
5942
            // Use global variables + $dateSelector + $seller and $buyer
5943
            // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5944
            $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5945
            foreach ($dirtpls as $module => $reldir) {
5946
                if (!empty($module)) {
5947
                    $tpl = dol_buildpath($reldir . '/objectline_edit.tpl.php');
5948
                } else {
5949
                    $tpl = DOL_DOCUMENT_ROOT . $reldir . '/objectline_edit.tpl.php';
5950
                }
5951
5952
                if (empty($conf->file->strict_mode)) {
5953
                    $res = @include $tpl;
5954
                } else {
5955
                    $res = include $tpl; // for debug
5956
                }
5957
                if ($res) {
5958
                    break;
5959
                }
5960
            }
5961
        }
5962
    }
5963
5964
    /**
5965
     * Returns the rights used for this class
5966
     *
5967
     * @return null|int|stdClass        Object of permission for the module
5968
     */
5969
    public function getRights()
5970
    {
5971
        global $user;
5972
5973
        $module = empty($this->module) ? '' : $this->module;
5974
        $element = $this->element;
5975
5976
        if ($element == 'facturerec') {
5977
            $element = 'facture';
5978
        } elseif ($element == 'invoice_supplier_rec') {
5979
            return !$user->hasRight('fournisseur', 'facture') ? null : $user->hasRight('fournisseur', 'facture');
5980
        } elseif ($module && $user->hasRight($module, $element)) {
5981
            // for modules built with ModuleBuilder
5982
            return $user->hasRight($module, $element);
5983
        }
5984
5985
        return $user->rights->$element;
5986
    }
5987
5988
5989
    /* Functions common to commonobject and commonobjectline */
5990
5991
    /* For default values */
5992
5993
    /**
5994
     *  Return HTML table table of source object lines
5995
     *  TODO Move this and previous function into output html class file (htmlline.class.php).
5996
     *  If lines are into a template, title must also be into a template
5997
     *  But for the moment we don't know if it's possible, so we keep the method available on overloaded objects.
5998
     *
5999
     * @param ''|'services' $restrictlist ''=All lines, 'services'=Restrict to services only
0 ignored issues
show
Documentation Bug introduced by
The doc comment ''|'services' at position 0 could not be parsed: Unknown type name '''' at position 0 in ''|'services'.
Loading history...
6000
     * @param int[] $selectedLines Array of lines id for selected lines
6001
     * @return void
6002
     */
6003
    public function printOriginLinesList($restrictlist = '', $selectedLines = array())
6004
    {
6005
        global $langs, $hookmanager, $form, $action;
6006
6007
        print '<tr class="liste_titre">';
6008
        print '<td class="linecolref">' . $langs->trans('Ref') . '</td>';
6009
        print '<td class="linecoldescription">' . $langs->trans('Description') . '</td>';
6010
        print '<td class="linecolvat right">' . $langs->trans('VATRate') . '</td>';
6011
        print '<td class="linecoluht right">' . $langs->trans('PriceUHT') . '</td>';
6012
        if (isModEnabled("multicurrency")) {
6013
            print '<td class="linecoluht_currency right">' . $langs->trans('PriceUHTCurrency') . '</td>';
6014
        }
6015
        print '<td class="linecolqty right">' . $langs->trans('Qty') . '</td>';
6016
        if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
6017
            print '<td class="linecoluseunit left">' . $langs->trans('Unit') . '</td>';
6018
        }
6019
        print '<td class="linecoldiscount right">' . $langs->trans('ReductionShort') . '</td>';
6020
        print '<td class="linecolht right">' . $langs->trans('TotalHT') . '</td>';
6021
        print '<td class="center">' . $form->showCheckAddButtons('checkforselect', 1) . '</td>';
6022
        print '</tr>';
6023
        $i = 0;
6024
6025
        if (!empty($this->lines)) {
6026
            foreach ($this->lines as $line) {
6027
                $reshook = 0;
6028
                //if (is_object($hookmanager) && (($line->product_type == 9 && !empty($line->special_code)) || !empty($line->fk_parent_line))) {
6029
                if (is_object($hookmanager)) {   // Old code is commented on preceding line.
6030
                    $parameters = array('line' => $line, 'i' => $i, 'restrictlist' => $restrictlist, 'selectedLines' => $selectedLines);
6031
                    if (!empty($line->fk_parent_line)) {
6032
                        $parameters['fk_parent_line'] = $line->fk_parent_line;
6033
                    }
6034
                    $reshook = $hookmanager->executeHooks('printOriginObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
6035
                }
6036
                if (empty($reshook)) {
6037
                    $this->printOriginLine($line, '', $restrictlist, '/core/tpl', $selectedLines);
6038
                }
6039
6040
                $i++;
6041
            }
6042
        }
6043
    }
6044
6045
6046
    /* For triggers */
6047
6048
6049
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6050
6051
    /**
6052
     *  Return HTML with a line of table array of source object lines
6053
     *  TODO Move this and previous function into output html class file (htmlline.class.php).
6054
     *  If lines are into a template, titles must also be into a template
6055
     *  But for the moment we don't know if it's possible as we keep a method available on overloaded objects.
6056
     *
6057
     * @param CommonObjectLine $line Line
6058
     * @param string $var Not used
6059
     * @param string $restrictlist ''=All lines, 'services'=Restrict to services only (strike line if not)
6060
     * @param string $defaulttpldir Directory where to find the template
6061
     * @param int[] $selectedLines Array of lines id for selected lines
6062
     * @return void
6063
     */
6064
    public function printOriginLine($line, $var, $restrictlist = '', $defaulttpldir = '/core/tpl', $selectedLines = array())
6065
    {
6066
        global $langs, $conf;
6067
6068
        //var_dump($line);
6069
        if (!empty($line->date_start)) {
0 ignored issues
show
Bug Best Practice introduced by
The property date_start does not exist on Dolibarr\Core\Base\CommonObjectLine. Since you implemented __get, consider adding a @property annotation.
Loading history...
6070
            $date_start = $line->date_start;
6071
        } else {
6072
            $date_start = $line->date_debut_prevue;
6073
            if ($line->date_debut_reel) {
6074
                $date_start = $line->date_debut_reel;
6075
            }
6076
        }
6077
        if (!empty($line->date_end)) {
0 ignored issues
show
Bug Best Practice introduced by
The property date_end does not exist on Dolibarr\Core\Base\CommonObjectLine. Since you implemented __get, consider adding a @property annotation.
Loading history...
6078
            $date_end = $line->date_end;
6079
        } else {
6080
            $date_end = $line->date_fin_prevue;
6081
            if ($line->date_fin_reel) {
6082
                $date_end = $line->date_fin_reel;
6083
            }
6084
        }
6085
6086
        $this->tpl['id'] = $line->id;
6087
6088
        $this->tpl['label'] = '';
6089
        if (!empty($line->fk_parent_line)) {
0 ignored issues
show
Bug Best Practice introduced by
The property fk_parent_line does not exist on Dolibarr\Core\Base\CommonObjectLine. Since you implemented __get, consider adding a @property annotation.
Loading history...
6090
            $this->tpl['label'] .= img_picto('', 'rightarrow');
6091
        }
6092
6093
        if (($line->info_bits & 2) == 2) {  // TODO Not sure this is used for source object
6094
            $discount = new DiscountAbsolute($this->db);
0 ignored issues
show
Bug introduced by
The type Dolibarr\Core\Base\DiscountAbsolute was not found. Did you mean DiscountAbsolute? If so, make sure to prefix the type with \.
Loading history...
6095
            if (property_exists($this, 'socid')) {
6096
                $discount->fk_soc = $this->socid;
6097
                $discount->socid = $this->socid;
6098
            }
6099
            $this->tpl['label'] .= $discount->getNomUrl(0, 'discount');
6100
        } elseif (!empty($line->fk_product)) {
6101
            $productstatic = new Product($this->db);
6102
            $productstatic->id = $line->fk_product;
6103
            $productstatic->ref = $line->ref;
6104
            $productstatic->type = $line->fk_product_type;
6105
            if (empty($productstatic->ref)) {
6106
                $line->fetch_product();
6107
                $productstatic = $line->product;
6108
            }
6109
6110
            $this->tpl['label'] .= $productstatic->getNomUrl(1);
6111
            $this->tpl['label'] .= ' - ' . (!empty($line->label) ? $line->label : $line->product_label);
6112
            // Dates
6113
            if ($line->product_type == 1 && ($date_start || $date_end)) {
6114
                $this->tpl['label'] .= get_date_range($date_start, $date_end);
6115
            }
6116
        } else {
6117
            $this->tpl['label'] .= ($line->product_type == -1 ? '&nbsp;' : ($line->product_type == 1 ? img_object($langs->trans(''), 'service') : img_object($langs->trans(''), 'product')));
6118
            if (!empty($line->desc)) {
6119
                $this->tpl['label'] .= $line->desc;
6120
            } else {
6121
                $this->tpl['label'] .= ($line->label ? '&nbsp;' . $line->label : '');
6122
            }
6123
6124
            // Dates
6125
            if ($line->product_type == 1 && ($date_start || $date_end)) {
6126
                $this->tpl['label'] .= get_date_range($date_start, $date_end);
6127
            }
6128
        }
6129
6130
        if (!empty($line->desc)) {
6131
            if ($line->desc == '(CREDIT_NOTE)') {  // TODO Not sure this is used for source object
6132
                $discount = new DiscountAbsolute($this->db);
6133
                $discount->fetch($line->fk_remise_except);
0 ignored issues
show
Bug Best Practice introduced by
The property fk_remise_except does not exist on Dolibarr\Core\Base\CommonObjectLine. Since you implemented __get, consider adding a @property annotation.
Loading history...
6134
                $this->tpl['description'] = $langs->transnoentities("DiscountFromCreditNote", $discount->getNomUrl(0));
6135
            } elseif ($line->desc == '(DEPOSIT)') {  // TODO Not sure this is used for source object
6136
                $discount = new DiscountAbsolute($this->db);
6137
                $discount->fetch($line->fk_remise_except);
6138
                $this->tpl['description'] = $langs->transnoentities("DiscountFromDeposit", $discount->getNomUrl(0));
6139
            } elseif ($line->desc == '(EXCESS RECEIVED)') {
6140
                $discount = new DiscountAbsolute($this->db);
6141
                $discount->fetch($line->fk_remise_except);
6142
                $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessReceived", $discount->getNomUrl(0));
6143
            } elseif ($line->desc == '(EXCESS PAID)') {
6144
                $discount = new DiscountAbsolute($this->db);
6145
                $discount->fetch($line->fk_remise_except);
6146
                $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessPaid", $discount->getNomUrl(0));
6147
            } else {
6148
                $this->tpl['description'] = dol_trunc($line->desc, 60);
6149
            }
6150
        } else {
6151
            $this->tpl['description'] = '&nbsp;';
6152
        }
6153
6154
        // VAT Rate
6155
        $this->tpl['vat_rate'] = vatrate($line->tva_tx, true);
6156
        $this->tpl['vat_rate'] .= (($line->info_bits & 1) == 1) ? '*' : '';
6157
        if (!empty($line->vat_src_code) && !preg_match('/\(/', $this->tpl['vat_rate'])) {
0 ignored issues
show
Bug Best Practice introduced by
The property vat_src_code does not exist on Dolibarr\Core\Base\CommonObjectLine. Since you implemented __get, consider adding a @property annotation.
Loading history...
6158
            $this->tpl['vat_rate'] .= ' (' . $line->vat_src_code . ')';
6159
        }
6160
6161
        $this->tpl['price'] = price($line->subprice);
6162
        $this->tpl['total_ht'] = price($line->total_ht);
6163
        $this->tpl['multicurrency_price'] = price($line->multicurrency_subprice);
6164
        $this->tpl['qty'] = (($line->info_bits & 2) != 2) ? $line->qty : '&nbsp;';
6165
        if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
6166
            $this->tpl['unit'] = $langs->transnoentities($line->getLabelOfUnit('long'));
6167
        }
6168
        $this->tpl['remise_percent'] = (($line->info_bits & 2) != 2) ? vatrate($line->remise_percent, true) : '&nbsp;';
6169
6170
        // Is the line strike or not
6171
        $this->tpl['strike'] = 0;
6172
        if ($restrictlist == 'services' && $line->product_type != Product::TYPE_SERVICE) {
6173
            $this->tpl['strike'] = 1;
6174
        }
6175
6176
        // Output template part (modules that overwrite templates must declare this into descriptor)
6177
        // Use global variables + $dateSelector + $seller and $buyer
6178
        $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
6179
        foreach ($dirtpls as $module => $reldir) {
6180
            if (!empty($module)) {
6181
                $tpl = dol_buildpath($reldir . '/originproductline.tpl.php');
6182
            } else {
6183
                $tpl = DOL_DOCUMENT_ROOT . $reldir . '/originproductline.tpl.php';
6184
            }
6185
6186
            if (empty($conf->file->strict_mode)) {
6187
                $res = @include $tpl;
6188
            } else {
6189
                $res = include $tpl; // for debug
6190
            }
6191
            if ($res) {
6192
                break;
6193
            }
6194
        }
6195
    }
6196
6197
6198
    /* Functions for data in other language */
6199
6200
    /**
6201
     *      Load the product with id $this->fk_product into this->product
6202
     *
6203
     * @return     int<-1,1>   Return integer <0 if KO, >=0 if OK
6204
     */
6205
    public function fetch_product()
6206
    {
6207
        // phpcs:enable
6208
        include_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
6209
6210
        // @phan-suppress-next-line PhanUndeclaredProperty
6211
        if (empty($this->fk_product)) {
0 ignored issues
show
Bug Best Practice introduced by
The property fk_product does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
6212
            return 0;
6213
        }
6214
6215
        $product = new Product($this->db);
6216
        // @phan-suppress-next-line PhanUndeclaredProperty
6217
        $result = $product->fetch($this->fk_product);
6218
6219
        $this->product = $product;
6220
        return $result;
6221
    }
6222
6223
    /**
6224
     *  Add resources to the current object : add entry into llx_element_resources
6225
     *  Need $this->element & $this->id
6226
     *
6227
     * @param int $resource_id Resource id
6228
     * @param string $resource_type 'resource'
6229
     * @param int $busy Busy or not
6230
     * @param int $mandatory Mandatory or not
6231
     * @return     int                         Return integer <=0 if KO, >0 if OK
6232
     */
6233
    public function add_element_resource($resource_id, $resource_type, $busy = 0, $mandatory = 0)
6234
    {
6235
        // phpcs:enable
6236
        $this->db->begin();
6237
6238
        $sql = "INSERT INTO " . $this->db->prefix() . "element_resources (";
6239
        $sql .= "resource_id";
6240
        $sql .= ", resource_type";
6241
        $sql .= ", element_id";
6242
        $sql .= ", element_type";
6243
        $sql .= ", busy";
6244
        $sql .= ", mandatory";
6245
        $sql .= ") VALUES (";
6246
        $sql .= ((int)$resource_id);
6247
        $sql .= ", '" . $this->db->escape($resource_type) . "'";
6248
        $sql .= ", '" . $this->db->escape($this->id) . "'";
6249
        $sql .= ", '" . $this->db->escape($this->element) . "'";
6250
        $sql .= ", '" . $this->db->escape($busy) . "'";
6251
        $sql .= ", '" . $this->db->escape($mandatory) . "'";
6252
        $sql .= ")";
6253
6254
        dol_syslog(get_class($this) . "::add_element_resource", LOG_DEBUG);
6255
        if ($this->db->query($sql)) {
6256
            $this->db->commit();
6257
            return 1;
6258
        } else {
6259
            $this->error = $this->db->lasterror();
6260
            $this->db->rollback();
6261
            return 0;
6262
        }
6263
    }
6264
6265
6266
    /* Functions for extrafields */
6267
6268
    /**
6269
     *    Delete a link to resource line
6270
     *
6271
     * @param int $rowid Id of resource line to delete
6272
     * @param string $element element name (for trigger) TODO: use $this->element into commonobject class
6273
     * @param int $notrigger Disable all triggers
6274
     * @return   int                     >0 if OK, <0 if KO
6275
     */
6276
    public function delete_resource($rowid, $element, $notrigger = 0)
6277
    {
6278
        // phpcs:enable
6279
        global $user;
6280
6281
        $this->db->begin();
6282
6283
        $sql = "DELETE FROM " . $this->db->prefix() . "element_resources";
6284
        $sql .= " WHERE rowid = " . ((int)$rowid);
6285
6286
        dol_syslog(get_class($this) . "::delete_resource", LOG_DEBUG);
6287
6288
        $resql = $this->db->query($sql);
6289
        if (!$resql) {
6290
            $this->error = $this->db->lasterror();
6291
            $this->db->rollback();
6292
            return -1;
6293
        } else {
6294
            if (!$notrigger) {
6295
                $result = $this->call_trigger(strtoupper($element) . '_DELETE_RESOURCE', $user);
6296
                if ($result < 0) {
6297
                    $this->db->rollback();
6298
                    return -1;
6299
                }
6300
            }
6301
            $this->db->commit();
6302
            return 1;
6303
        }
6304
    }
6305
6306
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6307
6308
    /**
6309
     * Overwrite magic function to solve problem of cloning object that are kept as references
6310
     *
6311
     * @return void
6312
     */
6313
    public function __clone()
6314
    {
6315
        // Force a copy of this->lines, otherwise it will point to same object.
6316
        if (isset($this->lines) && is_array($this->lines)) {
6317
            $nboflines = count($this->lines);
6318
            for ($i = 0; $i < $nboflines; $i++) {
6319
                if (is_object($this->lines[$i])) {
6320
                    $this->lines[$i] = clone $this->lines[$i];
6321
                }
6322
            }
6323
        }
6324
    }
6325
6326
    /**
6327
     *  Build thumb
6328
     * @param string $file Path file in UTF8 to original file to create thumbs from.
6329
     * @return     void
6330
     * @todo Move this into files.lib.php
6331
     *
6332
     */
6333
    public function addThumbs($file)
6334
    {
6335
        $file_osencoded = dol_osencode($file);
6336
6337
        if (file_exists($file_osencoded)) {
6338
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/images.lib.php';
6339
6340
            $tmparraysize = getDefaultImageSizes();
6341
            $maxwidthsmall = $tmparraysize['maxwidthsmall'];
6342
            $maxheightsmall = $tmparraysize['maxheightsmall'];
6343
            $maxwidthmini = $tmparraysize['maxwidthmini'];
6344
            $maxheightmini = $tmparraysize['maxheightmini'];
6345
            //$quality = $tmparraysize['quality'];
6346
            $quality = 50;  // For thumbs, we force quality to 50
6347
6348
            // Create small thumbs for company (Ratio is near 16/9)
6349
            // Used on logon for example
6350
            vignette($file_osencoded, $maxwidthsmall, $maxheightsmall, '_small', $quality);
6351
6352
            // Create mini thumbs for company (Ratio is near 16/9)
6353
            // Used on menu or for setup page for example
6354
            vignette($file_osencoded, $maxwidthmini, $maxheightmini, '_mini', $quality);
6355
        }
6356
    }
6357
6358
    /**
6359
     *  Delete thumbs
6360
     * @param string $file Path file in UTF8 to original file to delete thumbs.
6361
     * @return     void
6362
     * @todo Move this into files.lib.php
6363
     *
6364
     */
6365
    public function delThumbs($file)
6366
    {
6367
        $imgThumbName = getImageFileNameForSize($file, '_small'); // Full path of thumb file
6368
        dol_delete_file($imgThumbName);
6369
        $imgThumbName = getImageFileNameForSize($file, '_mini'); // Full path of thumb file
6370
        dol_delete_file($imgThumbName);
6371
    }
6372
6373
    /**
6374
     * Return the default value to use for a field when showing the create form of object.
6375
     * Return values in this order:
6376
     * 1) If parameter is available into POST, we return it first.
6377
     * 2) If not but an alternate value was provided as parameter of function, we return it.
6378
     * 3) If not but a constant $conf->global->OBJECTELEMENT_FIELDNAME is set, we return it (It is better to use the dedicated table).
6379
     * 4) Return value found into database (TODO No yet implemented)
6380
     *
6381
     * @param string $fieldname Name of field
6382
     * @param string $alternatevalue Alternate value to use
6383
     * @param string $type Type of data
6384
     * @return  string|string[]                         Default value (can be an array if the GETPOST return an array)
6385
     **/
6386
    public function getDefaultCreateValueFor($fieldname, $alternatevalue = null, $type = 'alphanohtml')
6387
    {
6388
        // If param here has been posted, we use this value first.
6389
        if (GETPOSTISSET($fieldname)) {
6390
            return GETPOST($fieldname, $type, 3);
6391
        }
6392
6393
        if (isset($alternatevalue)) {
6394
            return $alternatevalue;
6395
        }
6396
6397
        $newelement = $this->element;
6398
        if ($newelement == 'facture') {
6399
            $newelement = 'invoice';
6400
        }
6401
        if ($newelement == 'commande') {
6402
            $newelement = 'order';
6403
        }
6404
        if (empty($newelement)) {
6405
            dol_syslog("Ask a default value using common method getDefaultCreateValueForField on an object with no property ->element defined. Return empty string.", LOG_WARNING);
6406
            return '';
6407
        }
6408
6409
        $keyforfieldname = strtoupper($newelement . '_DEFAULT_' . $fieldname);
6410
        //var_dump($keyforfieldname);
6411
        if (getDolGlobalString($keyforfieldname)) {
6412
            return getDolGlobalString($keyforfieldname);
6413
        }
6414
6415
        // TODO Ad here a scan into table llx_overwrite_default with a filter on $this->element and $fieldname
6416
        // store content into $conf->cache['overwrite_default']
6417
6418
        return '';
6419
    }
6420
6421
    /**
6422
     *  Function to get alternative languages of a data into $this->array_languages
6423
     *  This method is NOT called by method fetch of objects but must be called separately.
6424
     *
6425
     * @return int<-1,1>                   Return integer <0 if error, 0 if no values of alternative languages to find nor found, 1 if a value was found and loaded
6426
     * @see fetch_optionnals()
6427
     */
6428
    public function fetchValuesForExtraLanguages()
6429
    {
6430
        // To avoid SQL errors. Probably not the better solution though
6431
        if (!$this->element) {
6432
            return 0;
6433
        }
6434
        if (!($this->id > 0)) {
6435
            return 0;
6436
        }
6437
        if (is_array($this->array_languages)) {
6438
            return 1;
6439
        }
6440
6441
        $this->array_languages = array();
6442
6443
        $element = $this->element;
6444
        if ($element == 'categorie') {
6445
            $element = 'categories'; // For compatibility
6446
        }
6447
6448
        // Request to get translation values for object
6449
        $sql = "SELECT rowid, property, lang , value";
6450
        $sql .= " FROM " . $this->db->prefix() . "object_lang";
6451
        $sql .= " WHERE type_object = '" . $this->db->escape($element) . "'";
6452
        $sql .= " AND fk_object = " . ((int)$this->id);
6453
6454
        //dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG);       // Too verbose
6455
        $resql = $this->db->query($sql);
6456
        if ($resql) {
6457
            $numrows = $this->db->num_rows($resql);
6458
            if ($numrows) {
6459
                $i = 0;
6460
                while ($i < $numrows) {
6461
                    $obj = $this->db->fetch_object($resql);
6462
                    $key = $obj->property;
6463
                    $value = $obj->value;
6464
                    $codelang = $obj->lang;
6465
                    $type = $this->fields[$key]['type'];
6466
6467
                    // we can add this attribute to object
6468
                    if (preg_match('/date/', $type)) {
6469
                        $this->array_languages[$key][$codelang] = $this->db->jdate($value);
6470
                    } else {
6471
                        $this->array_languages[$key][$codelang] = $value;
6472
                    }
6473
6474
                    $i++;
6475
                }
6476
            }
6477
6478
            $this->db->free($resql);
6479
6480
            if ($numrows) {
6481
                return $numrows;
6482
            } else {
6483
                return 0;
6484
            }
6485
        } else {
6486
            dol_print_error($this->db);
6487
            return -1;
6488
        }
6489
    }
6490
6491
    /**
6492
     * Fill array_options property of object by extrafields value (using for data sent by forms)
6493
     *
6494
     * @param string $onlykey Only the following key is filled. When we make update of only one language field ($action = 'update_languages'), calling page must set this to avoid to have other languages being reset.
6495
     * @return  int<-1,1>               1 if array_options set, 0 if no value, -1 if error (field required missing for example)
6496
     */
6497
    public function setValuesForExtraLanguages($onlykey = '')
6498
    {
6499
        // Get extra fields
6500
        foreach ($_POST as $postfieldkey => $postfieldvalue) {
6501
            $tmparray = explode('-', $postfieldkey);
6502
            if ($tmparray[0] != 'field') {
6503
                continue;
6504
            }
6505
6506
            $element = $tmparray[1];
6507
            $key = $tmparray[2];
6508
            $codelang = $tmparray[3];
6509
            //var_dump("postfieldkey=".$postfieldkey." element=".$element." key=".$key." codelang=".$codelang);
6510
6511
            if (!empty($onlykey) && $key != $onlykey) {
6512
                continue;
6513
            }
6514
            if ($element != $this->element) {
6515
                continue;
6516
            }
6517
6518
            $key_type = $this->fields[$key]['type'];
6519
6520
            $enabled = 1;
6521
            if (isset($this->fields[$key]['enabled'])) {
6522
                $enabled = (int)dol_eval($this->fields[$key]['enabled'], 1, 1, '1');
6523
            }
6524
            /*$perms = 1;
6525
            if (isset($this->fields[$key]['perms']))
6526
            {
6527
                $perms = (int) dol_eval($this->fields[$key]['perms'], 1, 1, '1');
6528
            }*/
6529
            if (empty($enabled)) {
6530
                continue;
6531
            }
6532
            //if (empty($perms)) continue;
6533
6534
            if (in_array($key_type, array('date'))) {
6535
                // Clean parameters
6536
                // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6537
                $value_key = dol_mktime(0, 0, 0, GETPOSTINT($postfieldkey . "month"), GETPOSTINT($postfieldkey . "day"), GETPOSTINT($postfieldkey . "year"));
6538
            } elseif (in_array($key_type, array('datetime'))) {
6539
                // Clean parameters
6540
                // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6541
                $value_key = dol_mktime(GETPOSTINT($postfieldkey . "hour"), GETPOSTINT($postfieldkey . "min"), 0, GETPOSTINT($postfieldkey . "month"), GETPOSTINT($postfieldkey . "day"), GETPOSTINT($postfieldkey . "year"));
6542
            } elseif (in_array($key_type, array('checkbox', 'chkbxlst'))) {
6543
                $value_arr = GETPOST($postfieldkey, 'array'); // check if an array
6544
                if (!empty($value_arr)) {
6545
                    $value_key = implode(',', $value_arr);
6546
                } else {
6547
                    $value_key = '';
6548
                }
6549
            } elseif (in_array($key_type, array('price', 'double'))) {
6550
                $value_arr = GETPOST($postfieldkey, 'alpha');
6551
                $value_key = price2num($value_arr);
6552
            } else {
6553
                $value_key = GETPOST($postfieldkey);
6554
                if (in_array($key_type, array('link')) && $value_key == '-1') {
6555
                    $value_key = '';
6556
                }
6557
            }
6558
6559
            $this->array_languages[$key][$codelang] = $value_key;
6560
6561
            /*if ($nofillrequired) {
6562
                $langs->load('errors');
6563
                setEventMessages($langs->trans('ErrorFieldsRequired').' : '.implode(', ', $error_field_required), null, 'errors');
6564
                return -1;
6565
            }*/
6566
        }
6567
6568
        return 1;
6569
    }
6570
6571
    /**
6572
     * Function to make a fetch but set environment to avoid to load computed values before.
6573
     *
6574
     * @param int $id ID of object
6575
     * @return  int<-1,1>           >0 if OK, 0 if not found, <0 if KO
6576
     */
6577
    public function fetchNoCompute($id)
6578
    {
6579
        global $conf;
6580
6581
        $savDisableCompute = $conf->disable_compute;
6582
        $conf->disable_compute = 1;
6583
6584
        $ret = $this->fetch($id);
6585
        /* @phpstan-ignore-line */
6586
6587
        $conf->disable_compute = $savDisableCompute;
6588
6589
        return $ret;
6590
    }
6591
6592
    /**
6593
     *  Add/Update all extra fields values for the current object.
6594
     *  Data to describe values to insert/update are stored into $this->array_options=array('options_codeforfield1'=>'valueforfield1', 'options_codeforfield2'=>'valueforfield2', ...)
6595
     *  This function delete record with all extrafields and insert them again from the array $this->array_options.
6596
     *
6597
     * @param string $trigger If defined, call also the trigger (for example COMPANY_MODIFY)
6598
     * @param User $userused Object user
6599
     * @return int<-1,1>                   -1=error, O=did nothing, 1=OK
6600
     * @see insertExtraFields(), updateExtraField(), setValueFrom()
6601
     */
6602
    public function insertExtraLanguages($trigger = '', $userused = null)
6603
    {
6604
        global $conf, $langs, $user;
6605
6606
        if (empty($userused)) {
6607
            $userused = $user;
6608
        }
6609
6610
        $error = 0;
6611
6612
        if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
6613
            return 0; // For avoid conflicts if trigger used
6614
        }
6615
6616
        if (is_array($this->array_languages)) {
6617
            $new_array_languages = $this->array_languages;
6618
6619
            foreach ($new_array_languages as $key => $value) {
6620
                $attributeKey = $key;
6621
                $attributeType = $this->fields[$attributeKey]['type'];
6622
                $attributeLabel = $this->fields[$attributeKey]['label'];
6623
6624
                //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
6625
                //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
6626
6627
                switch ($attributeType) {
6628
                    case 'int':
6629
                        if (is_array($value) || (!is_numeric($value) && $value != '')) {
6630
                            $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
6631
                            return -1;
6632
                        } elseif ($value == '') {
6633
                            $new_array_languages[$key] = null;
6634
                        }
6635
                        break;
6636
                    case 'double':
6637
                        $value = price2num($value);
6638
                        if (!is_numeric($value) && $value != '') {
6639
                            dol_syslog($langs->trans("ExtraLanguageHasWrongValue") . " on " . $attributeLabel . "(" . $value . "is not '" . $attributeType . "')", LOG_DEBUG);
6640
                            $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
6641
                            return -1;
6642
                        } elseif ($value == '') {
6643
                            $new_array_languages[$key] = null;
6644
                        } else {
6645
                            $new_array_languages[$key] = $value;
6646
                        }
6647
                        break;
6648
                    /*case 'select':    // Not required, we chose value='0' for undefined values
6649
                     if ($value=='-1')
6650
                     {
6651
                     $this->array_options[$key] = null;
6652
                     }
6653
                     break;*/
6654
                }
6655
            }
6656
6657
            $this->db->begin();
6658
6659
            $table_element = $this->table_element;
6660
            if ($table_element == 'categorie') {    // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
6661
                $table_element = 'categories'; // For compatibility
6662
            }
6663
6664
            dol_syslog(get_class($this) . "::insertExtraLanguages delete then insert", LOG_DEBUG);
6665
6666
            foreach ($new_array_languages as $key => $langcodearray) {  // $key = 'name', 'town', ...
6667
                foreach ($langcodearray as $langcode => $value) {
6668
                    $sql_del = "DELETE FROM " . $this->db->prefix() . "object_lang";
6669
                    $sql_del .= " WHERE fk_object = " . ((int)$this->id) . " AND property = '" . $this->db->escape($key) . "' AND type_object = '" . $this->db->escape($table_element) . "'";
6670
                    $sql_del .= " AND lang = '" . $this->db->escape($langcode) . "'";
6671
                    $this->db->query($sql_del);
6672
6673
                    if ($value !== '') {
6674
                        $sql = "INSERT INTO " . $this->db->prefix() . "object_lang (fk_object, property, type_object, lang, value";
6675
                        $sql .= ") VALUES (" . $this->id . ", '" . $this->db->escape($key) . "', '" . $this->db->escape($table_element) . "', '" . $this->db->escape($langcode) . "', '" . $this->db->escape($value) . "'";
6676
                        $sql .= ")";
6677
6678
                        $resql = $this->db->query($sql);
6679
                        if (!$resql) {
6680
                            $this->error = $this->db->lasterror();
6681
                            $error++;
6682
                            break;
6683
                        }
6684
                    }
6685
                }
6686
            }
6687
6688
            if (!$error && $trigger) {
6689
                // Call trigger
6690
                $this->context = array('extralanguagesaddupdate' => 1);
6691
                $result = $this->call_trigger($trigger, $userused);
6692
                if ($result < 0) {
6693
                    $error++;
6694
                }
6695
                // End call trigger
6696
            }
6697
6698
            if ($error) {
6699
                $this->db->rollback();
6700
                return -1;
6701
            } else {
6702
                $this->db->commit();
6703
                return 1;
6704
            }
6705
        } else {
6706
            return 0;
6707
        }
6708
    }
6709
6710
    /**
6711
     *  Update 1 extra field value for the current object. Keep other fields unchanged.
6712
     *  Data to describe values to update are stored into $this->array_options=array('options_codeforfield1'=>'valueforfield1', 'options_codeforfield2'=>'valueforfield2', ...)
6713
     *
6714
     * @param string $key Key of the extrafield to update (without starting 'options_')
6715
     * @param string $trigger If defined, call also the trigger (for example COMPANY_MODIFY)
6716
     * @param User $userused Object user
6717
     * @return int<-1,1>                   -1=error, O=did nothing, 1=OK
6718
     * @see updateExtraLanguages(), insertExtraFields(), deleteExtraFields(), setValueFrom()
6719
     */
6720
    public function updateExtraField($key, $trigger = null, $userused = null)
6721
    {
6722
        global $conf, $langs, $user, $hookmanager;
6723
6724
        if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
6725
            return 0;
6726
        }
6727
6728
        if (empty($userused)) {
6729
            $userused = $user;
6730
        }
6731
6732
        $error = 0;
6733
6734
        if (!empty($this->array_options) && isset($this->array_options["options_" . $key])) {
6735
            // Check parameters
6736
            $langs->load('admin');
6737
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/class/extrafields.class.php';
6738
            $extrafields = new ExtraFields($this->db);
6739
            $extrafields->fetch_name_optionals_label($this->table_element);
6740
6741
            $value = $this->array_options["options_" . $key];
6742
6743
            $attributeKey = $key;
6744
            $attributeType = $extrafields->attributes[$this->table_element]['type'][$key];
6745
            $attributeLabel = $extrafields->attributes[$this->table_element]['label'][$key];
6746
            $attributeParam = $extrafields->attributes[$this->table_element]['param'][$key];
6747
            $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$key];
6748
            $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
6749
            $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$key];
6750
6751
            // Similar code than into insertExtraFields
6752
            if ($attributeRequired) {
6753
                $mandatorypb = false;
6754
                if ($attributeType == 'link' && $this->array_options["options_" . $key] == '-1') {
6755
                    $mandatorypb = true;
6756
                }
6757
                if ($this->array_options["options_" . $key] === '') {
6758
                    $mandatorypb = true;
6759
                }
6760
                if ($mandatorypb) {
6761
                    $langs->load("errors");
6762
                    dol_syslog("Mandatory field 'options_" . $key . "' is empty during update and set to required into definition of extrafields");
6763
                    $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
6764
                    return -1;
6765
                }
6766
            }
6767
6768
            // $new_array_options will be used for direct update, so must contains formatted data for the UPDATE.
6769
            $new_array_options = $this->array_options;
6770
6771
            //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
6772
            //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
6773
            if (!empty($attrfieldcomputed)) {
6774
                if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
6775
                    $value = dol_eval($attrfieldcomputed, 1, 0, '2');
6776
                    dol_syslog($langs->trans("Extrafieldcomputed") . " on " . $attributeLabel . "(" . $value . ")", LOG_DEBUG);
6777
6778
                    $new_array_options["options_" . $key] = $value;
6779
6780
                    $this->array_options["options_" . $key] = $new_array_options["options_" . $key];
6781
                } else {
6782
                    $new_array_options["options_" . $key] = null;
6783
6784
                    $this->array_options["options_" . $key] = $new_array_options["options_" . $key];
6785
                }
6786
            }
6787
6788
            switch ($attributeType) {
6789
                case 'int':
6790
                    if (!is_numeric($value) && $value != '') {
6791
                        $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
6792
                        return -1;
6793
                    } elseif ($value === '') {
6794
                        $new_array_options["options_" . $key] = null;
6795
6796
                        $this->array_options["options_" . $key] = $new_array_options["options_" . $key];
6797
                    }
6798
                    break;
6799
                case 'price':
6800
                case 'double':
6801
                    $value = price2num($value);
6802
                    if (!is_numeric($value) && $value != '') {
6803
                        dol_syslog($langs->trans("ExtraFieldHasWrongValue") . " on " . $attributeLabel . "(" . $value . "is not '" . $attributeType . "')", LOG_DEBUG);
6804
                        $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
6805
                        return -1;
6806
                    } elseif ($value === '') {
6807
                        $value = null;
6808
                    }
6809
                    //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
6810
                    $new_array_options["options_" . $key] = $value;
6811
6812
                    $this->array_options["options_" . $key] = $new_array_options["options_" . $key];
6813
                    break;
6814
                /*case 'select':    // Not required, we chose value='0' for undefined values
6815
                     if ($value=='-1')
6816
                     {
6817
                        $new_array_options["options_".$key] = $value;
6818
6819
                        $this->array_options["options_".$key] = $new_array_options["options_".$key];
6820
                     }
6821
                     break;*/
6822
                case 'password':
6823
                    $algo = '';
6824
                    if ($this->array_options["options_" . $key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
6825
                        // If there is an encryption choice, we use it to encrypt data before insert
6826
                        $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
6827
                        $algo = reset($tmparrays);
6828
                        if ($algo != '') {
6829
                            //global $action;       // $action may be 'create', 'update', 'update_extras'...
6830
                            //var_dump($action);
6831
                            //var_dump($this->oldcopy);exit;
6832
                            //var_dump($key.' '.$this->array_options["options_".$key].' '.$algo);
6833
                            if (is_object($this->oldcopy)) {        // If this->oldcopy is not defined, we can't know if we change attribute or not, so we must keep value
6834
                                //var_dump($this->oldcopy->array_options["options_".$key]); var_dump($this->array_options["options_".$key]);
6835
                                if (isset($this->oldcopy->array_options["options_" . $key]) && $this->array_options["options_" . $key] == $this->oldcopy->array_options["options_" . $key]) { // If old value encrypted in database is same than submitted new value, it means we don't change it, so we don't update.
6836
                                    if ($algo == 'dolcrypt') {  // dolibarr reversible encryption
6837
                                        if (!preg_match('/^dolcrypt:/', $this->array_options["options_" . $key])) {
6838
                                            $new_array_options["options_" . $key] = dolEncrypt($this->array_options["options_" . $key]);    // warning, must be called when on the master
6839
                                        } else {
6840
                                            $new_array_options["options_" . $key] = $this->array_options["options_" . $key]; // Value is kept
6841
                                        }
6842
                                    } else {
6843
                                        $new_array_options["options_" . $key] = $this->array_options["options_" . $key]; // Value is kept
6844
                                    }
6845
                                } else {
6846
                                    if ($algo == 'dolcrypt') {  // dolibarr reversible encryption
6847
                                        if (!preg_match('/^dolcrypt:/', $this->array_options["options_" . $key])) {
6848
                                            $new_array_options["options_" . $key] = dolEncrypt($this->array_options["options_" . $key]);
6849
                                        } else {
6850
                                            $new_array_options["options_" . $key] = $this->array_options["options_" . $key]; // Value is kept
6851
                                        }
6852
                                    } else {
6853
                                        $new_array_options["options_" . $key] = dol_hash($this->array_options["options_" . $key], $algo);
6854
                                    }
6855
                                }
6856
                            } else {
6857
                                if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options["options_" . $key])) {    // dolibarr reversible encryption
6858
                                    $new_array_options["options_" . $key] = dolEncrypt($this->array_options["options_" . $key]);    // warning, must be called when on the master
6859
                                } else {
6860
                                    $new_array_options["options_" . $key] = $this->array_options["options_" . $key]; // Value is kept
6861
                                }
6862
                            }
6863
                        } else {
6864
                            // No encryption
6865
                            $new_array_options["options_" . $key] = $this->array_options["options_" . $key]; // Value is kept
6866
                        }
6867
                    } else { // Common usage
6868
                        $new_array_options["options_" . $key] = $this->array_options["options_" . $key]; // Value is kept
6869
                    }
6870
6871
                    $this->array_options["options_" . $key] = $new_array_options["options_" . $key];
6872
                    break;
6873
                case 'date':
6874
                case 'datetime':
6875
                    if (empty($this->array_options["options_" . $key])) {
6876
                        $new_array_options["options_" . $key] = null;
6877
6878
                        $this->array_options["options_" . $key] = $new_array_options["options_" . $key];
6879
                    } else {
6880
                        $new_array_options["options_" . $key] = $this->db->idate($this->array_options["options_" . $key]);
6881
                    }
6882
                    break;
6883
                case 'datetimegmt':
6884
                    if (empty($this->array_options["options_" . $key])) {
6885
                        $new_array_options["options_" . $key] = null;
6886
6887
                        $this->array_options["options_" . $key] = $new_array_options["options_" . $key];
6888
                    } else {
6889
                        $new_array_options["options_" . $key] = $this->db->idate($this->array_options["options_" . $key], 'gmt');
6890
                    }
6891
                    break;
6892
                case 'boolean':
6893
                    if (empty($this->array_options["options_" . $key])) {
6894
                        $new_array_options["options_" . $key] = null;
6895
6896
                        $this->array_options["options_" . $key] = $new_array_options["options_" . $key];
6897
                    }
6898
                    break;
6899
                case 'link':
6900
                    if ($this->array_options["options_" . $key] === '') {
6901
                        $new_array_options["options_" . $key] = null;
6902
6903
                        $this->array_options["options_" . $key] = $new_array_options["options_" . $key];
6904
                    }
6905
                    break;
6906
                /*
6907
                case 'link':
6908
                    $param_list = array_keys($attributeParam['options']);
6909
                    // 0 : ObjectName
6910
                    // 1 : classPath
6911
                    $InfoFieldList = explode(":", $param_list[0]);
6912
                    dol_include_once($InfoFieldList[1]);
6913
                    if ($InfoFieldList[0] && class_exists($InfoFieldList[0]))
6914
                    {
6915
                        if ($value == '-1') // -1 is key for no defined in combo list of objects
6916
                        {
6917
                            $new_array_options[$key] = '';
6918
                        } elseif ($value) {
6919
                            $object = new $InfoFieldList[0]($this->db);
6920
                            if (is_numeric($value)) $res = $object->fetch($value);  // Common case
6921
                            else $res = $object->fetch('', $value);                 // For compatibility
6922
6923
                            if ($res > 0) $new_array_options[$key] = $object->id;
6924
                            else {
6925
                                $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
6926
                                $this->db->rollback();
6927
                                return -1;
6928
                            }
6929
                        }
6930
                    } else {
6931
                        dol_syslog('Error bad setup of extrafield', LOG_WARNING);
6932
                    }
6933
                    break;
6934
                */
6935
                case 'checkbox':
6936
                case 'chkbxlst':
6937
                    $new_array_options = array();
6938
                    if (is_array($this->array_options["options_" . $key])) {
6939
                        $new_array_options["options_" . $key] = implode(',', $this->array_options["options_" . $key]);
6940
                    } else {
6941
                        $new_array_options["options_" . $key] = $this->array_options["options_" . $key];
6942
                    }
6943
6944
                    $this->array_options["options_" . $key] = $new_array_options["options_" . $key];
6945
                    break;
6946
            }
6947
6948
            $this->db->begin();
6949
6950
            $linealreadyfound = 0;
6951
6952
            // Check if there is already a line for this object (in most cases, it is, but sometimes it is not, for example when extra field has been created after), so we must keep this overload)
6953
            $table_element = $this->table_element;
6954
            if ($table_element == 'categorie') {    // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
6955
                $table_element = 'categories'; // For compatibility
6956
            }
6957
6958
            $sql = "SELECT COUNT(rowid) as nb FROM " . $this->db->prefix() . $table_element . "_extrafields WHERE fk_object = " . ((int)$this->id);
6959
            $resql = $this->db->query($sql);
6960
            if ($resql) {
6961
                $tmpobj = $this->db->fetch_object($resql);
6962
                if ($tmpobj) {
6963
                    $linealreadyfound = $tmpobj->nb;
6964
                }
6965
            }
6966
6967
            //var_dump('linealreadyfound='.$linealreadyfound.' sql='.$sql); exit;
6968
            if ($linealreadyfound) {
6969
                if ($this->array_options["options_" . $key] === null) {
6970
                    $sql = "UPDATE " . $this->db->prefix() . $this->table_element . "_extrafields SET " . $key . " = null";
6971
                } else {
6972
                    $sql = "UPDATE " . $this->db->prefix() . $this->table_element . "_extrafields SET " . $key . " = '" . $this->db->escape($new_array_options["options_" . $key]) . "'";
6973
                }
6974
                $sql .= " WHERE fk_object = " . ((int)$this->id);
6975
6976
                $resql = $this->db->query($sql);
6977
                if (!$resql) {
6978
                    $error++;
6979
                    $this->error = $this->db->lasterror();
6980
                }
6981
            } else {
6982
                $result = $this->insertExtraFields('', $user);
6983
                if ($result < 0) {
6984
                    $error++;
6985
                }
6986
            }
6987
6988
            if (!$error) {
6989
                $parameters = array('key' => $key);
6990
                global $action;
6991
                $reshook = $hookmanager->executeHooks('updateExtraFieldBeforeCommit', $parameters, $this, $action);
6992
                if ($reshook < 0) {
6993
                    setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
6994
                }
6995
            }
6996
6997
            if (!$error && $trigger) {
6998
                // Call trigger
6999
                $this->context = array('extrafieldupdate' => 1);
7000
                $result = $this->call_trigger($trigger, $userused);
7001
                if ($result < 0) {
7002
                    $error++;
7003
                }
7004
                // End call trigger
7005
            }
7006
7007
            if ($error) {
7008
                dol_syslog(__METHOD__ . $this->error, LOG_ERR);
7009
                $this->db->rollback();
7010
                return -1;
7011
            } else {
7012
                $this->db->commit();
7013
                return 1;
7014
            }
7015
        } else {
7016
            return 0;
7017
        }
7018
    }
7019
7020
    /**
7021
     *  Add/Update all extra fields values for the current object.
7022
     *  Data to describe values to insert/update are stored into $this->array_options=array('options_codeforfield1'=>'valueforfield1', 'options_codeforfield2'=>'valueforfield2', ...)
7023
     *  This function delete record with all extrafields and insert them again from the array $this->array_options.
7024
     *
7025
     * @param string $trigger If defined, call also the trigger (for example COMPANY_MODIFY)
7026
     * @param User $userused Object user
7027
     * @return int<-1,1>                   -1=error, O=did nothing, 1=OK
7028
     * @see insertExtraLanguages(), updateExtraField(), deleteExtraField(), setValueFrom()
7029
     */
7030
    public function insertExtraFields($trigger = '', $userused = null)
7031
    {
7032
        global $conf, $langs, $user;
7033
7034
        if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
7035
            return 0;
7036
        }
7037
7038
        if (empty($userused)) {
7039
            $userused = $user;
7040
        }
7041
7042
        $error = 0;
7043
7044
        if (!empty($this->array_options)) {
7045
            // Check parameters
7046
            $langs->load('admin');
7047
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/class/extrafields.class.php';
7048
            $extrafields = new ExtraFields($this->db);
7049
            $target_extrafields = $extrafields->fetch_name_optionals_label($this->table_element);
7050
7051
            // Eliminate copied source object extra fields that do not exist in target object
7052
            $new_array_options = array();
7053
            foreach ($this->array_options as $key => $value) {
7054
                if (in_array(substr($key, 8), array_keys($target_extrafields))) {   // We remove the 'options_' from $key for test
7055
                    $new_array_options[$key] = $value;
7056
                } elseif (in_array($key, array_keys($target_extrafields))) {        // We test on $key that does not contain the 'options_' prefix
7057
                    $new_array_options['options_' . $key] = $value;
7058
                }
7059
            }
7060
7061
            foreach ($new_array_options as $key => $value) {
7062
                $attributeKey = substr($key, 8); // Remove 'options_' prefix
7063
                $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
7064
                $attributeLabel = $extrafields->attributes[$this->table_element]['label'][$attributeKey];
7065
                $attributeParam = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
7066
                $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
7067
                $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
7068
                $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$attributeKey];
7069
7070
                // If we clone, we have to clean unique extrafields to prevent duplicates.
7071
                // This behaviour can be prevented by external code by changing $this->context['createfromclone'] value in createFrom hook
7072
                if (!empty($this->context['createfromclone']) && $this->context['createfromclone'] == 'createfromclone' && !empty($attributeUnique)) {
7073
                    $new_array_options[$key] = null;
7074
                }
7075
7076
                // Similar code than into insertExtraFields
7077
                if ($attributeRequired) {
7078
                    $v = $this->array_options[$key];
7079
                    if (ExtraFields::isEmptyValue($v, $attributeType)) {
7080
                        $langs->load("errors");
7081
                        dol_syslog("Mandatory field '" . $key . "' is empty during create and set to required into definition of extrafields");
7082
                        $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
7083
                        return -1;
7084
                    }
7085
                }
7086
7087
                //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7088
                //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7089
7090
                if (!empty($attrfieldcomputed)) {
7091
                    if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
7092
                        $value = dol_eval($attrfieldcomputed, 1, 0, '2');
7093
                        dol_syslog($langs->trans("Extrafieldcomputed") . " on " . $attributeLabel . "(" . $value . ")", LOG_DEBUG);
7094
                        $new_array_options[$key] = $value;
7095
                    } else {
7096
                        $new_array_options[$key] = null;
7097
                    }
7098
                }
7099
7100
                switch ($attributeType) {
7101
                    case 'int':
7102
                        if (!is_numeric($value) && $value != '') {
7103
                            $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7104
                            return -1;
7105
                        } elseif ($value == '') {
7106
                            $new_array_options[$key] = null;
7107
                        }
7108
                        break;
7109
                    case 'price':
7110
                    case 'double':
7111
                        $value = price2num($value);
7112
                        if (!is_numeric($value) && $value != '') {
7113
                            dol_syslog($langs->trans("ExtraFieldHasWrongValue") . " for " . $attributeLabel . "(" . $value . "is not '" . $attributeType . "')", LOG_DEBUG);
7114
                            $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7115
                            return -1;
7116
                        } elseif ($value == '') {
7117
                            $value = null;
7118
                        }
7119
                        //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
7120
                        $new_array_options[$key] = $value;
7121
                        break;
7122
                    /*case 'select':    // Not required, we chose value='0' for undefined values
7123
                         if ($value=='-1')
7124
                         {
7125
                             $this->array_options[$key] = null;
7126
                         }
7127
                         break;*/
7128
                    case 'password':
7129
                        $algo = '';
7130
                        if ($this->array_options[$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
7131
                            // If there is an encryption choice, we use it to encrypt data before insert
7132
                            $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
7133
                            $algo = reset($tmparrays);
7134
                            if ($algo != '') {
7135
                                //global $action;       // $action may be 'create', 'update', 'update_extras'...
7136
                                //var_dump($action);
7137
                                //var_dump($this->oldcopy);exit;
7138
                                if (is_object($this->oldcopy)) {    // If this->oldcopy is not defined, we can't know if we change attribute or not, so we must keep value
7139
                                    //var_dump('algo='.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
7140
                                    if (isset($this->oldcopy->array_options[$key]) && $this->array_options[$key] == $this->oldcopy->array_options[$key]) {
7141
                                        // If old value encrypted in database is same than submitted new value, it means we don't change it, so we don't update.
7142
                                        if ($algo == 'dolcrypt') {  // dolibarr reversible encryption
7143
                                            if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
7144
                                                $new_array_options[$key] = dolEncrypt($this->array_options[$key]);  // warning, must be called when on the master
7145
                                            } else {
7146
                                                $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7147
                                            }
7148
                                        } else {
7149
                                            $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7150
                                        }
7151
                                    } else {
7152
                                        // If value has changed
7153
                                        if ($algo == 'dolcrypt') {  // dolibarr reversible encryption
7154
                                            if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
7155
                                                $new_array_options[$key] = dolEncrypt($this->array_options[$key]);  // warning, must be called when on the master
7156
                                            } else {
7157
                                                $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7158
                                            }
7159
                                        } else {
7160
                                            $new_array_options[$key] = dol_hash($this->array_options[$key], $algo);
7161
                                        }
7162
                                    }
7163
                                } else {
7164
                                    //var_dump('jjj'.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
7165
                                    // If this->oldcopy is not defined, we can't know if we change attribute or not, so we must keep value
7166
                                    if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options[$key])) {   // dolibarr reversible encryption
7167
                                        $new_array_options[$key] = dolEncrypt($this->array_options[$key]);  // warning, must be called when on the master
7168
                                    } else {
7169
                                        $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7170
                                    }
7171
                                }
7172
                            } else {
7173
                                // No encryption
7174
                                $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7175
                            }
7176
                        } else { // Common usage
7177
                            $new_array_options[$key] = $this->array_options[$key]; // Value is kept
7178
                        }
7179
                        break;
7180
                    case 'date':
7181
                    case 'datetime':
7182
                        // If data is a string instead of a timestamp, we convert it
7183
                        if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
7184
                            $this->array_options[$key] = strtotime($this->array_options[$key]);
7185
                        }
7186
                        $new_array_options[$key] = $this->db->idate($this->array_options[$key]);
7187
                        break;
7188
                    case 'datetimegmt':
7189
                        // If data is a string instead of a timestamp, we convert it
7190
                        if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
7191
                            $this->array_options[$key] = strtotime($this->array_options[$key]);
7192
                        }
7193
                        $new_array_options[$key] = $this->db->idate($this->array_options[$key], 'gmt');
7194
                        break;
7195
                    case 'link':
7196
                        $param_list = array_keys($attributeParam['options']);
7197
                        // 0 : ObjectName
7198
                        // 1 : classPath
7199
                        $InfoFieldList = explode(":", $param_list[0]);
7200
                        dol_include_once($InfoFieldList[1]);
7201
                        if ($InfoFieldList[0] && class_exists($InfoFieldList[0])) {
7202
                            if ($value == '-1') {   // -1 is key for no defined in combo list of objects
7203
                                $new_array_options[$key] = '';
7204
                            } elseif ($value) {
7205
                                $object = new $InfoFieldList[0]($this->db);
7206
                                if (is_numeric($value)) {
7207
                                    $res = $object->fetch($value); // Common case
7208
                                } else {
7209
                                    $res = $object->fetch('', $value); // For compatibility
7210
                                }
7211
7212
                                if ($res > 0) {
7213
                                    $new_array_options[$key] = $object->id;
7214
                                } else {
7215
                                    $this->error = "Id/Ref '" . $value . "' for object '" . $object->element . "' not found";
7216
                                    return -1;
7217
                                }
7218
                            }
7219
                        } else {
7220
                            dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7221
                        }
7222
                        break;
7223
                    case 'checkbox':
7224
                    case 'chkbxlst':
7225
                        if (is_array($this->array_options[$key])) {
7226
                            $new_array_options[$key] = implode(',', $this->array_options[$key]);
7227
                        } else {
7228
                            $new_array_options[$key] = $this->array_options[$key];
7229
                        }
7230
                        break;
7231
                }
7232
            }
7233
7234
            $this->db->begin();
7235
7236
            $table_element = $this->table_element;
7237
            if ($table_element == 'categorie') {
7238
                $table_element = 'categories'; // For compatibility
7239
            }
7240
7241
            dol_syslog(get_class($this) . "::insertExtraFields delete then insert", LOG_DEBUG);
7242
7243
            $sql_del = "DELETE FROM " . $this->db->prefix() . $table_element . "_extrafields WHERE fk_object = " . ((int)$this->id);
7244
            $this->db->query($sql_del);
7245
7246
            $sql = "INSERT INTO " . $this->db->prefix() . $table_element . "_extrafields (fk_object";
7247
            foreach ($new_array_options as $key => $value) {
7248
                $attributeKey = substr($key, 8); // Remove 'options_' prefix
7249
                // Add field of attribute
7250
                if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] != 'separate') { // Only for other type than separator
7251
                    $sql .= "," . $attributeKey;
7252
                }
7253
            }
7254
            // We must insert a default value for fields for other entities that are mandatory to avoid not null error
7255
            if (!empty($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities']) && is_array($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'])) {
7256
                foreach ($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'] as $tmpkey => $tmpval) {
7257
                    if (!isset($extrafields->attributes[$this->table_element]['type'][$tmpkey])) {    // If field not already added previously
7258
                        $sql .= "," . $tmpkey;
7259
                    }
7260
                }
7261
            }
7262
            $sql .= ") VALUES (" . $this->id;
7263
7264
            foreach ($new_array_options as $key => $value) {
7265
                $attributeKey = substr($key, 8); // Remove 'options_' prefix
7266
                // Add field of attribute
7267
                if (!in_array($extrafields->attributes[$this->table_element]['type'][$attributeKey], ['separate', 'point', 'multipts', 'linestrg', 'polygon'])) { // Only for other type than separator)
7268
                    if ($new_array_options[$key] != '' || $new_array_options[$key] == '0') {
7269
                        $sql .= ",'" . $this->db->escape($new_array_options[$key]) . "'";
7270
                    } else {
7271
                        $sql .= ",null";
7272
                    }
7273
                }
7274
                if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] == 'point') { // for point type
7275
                    if (!empty($new_array_options[$key])) {
7276
                        $sql .= ",ST_PointFromText('" . $this->db->escape($new_array_options[$key]) . "')";
7277
                    } else {
7278
                        $sql .= ",null";
7279
                    }
7280
                }
7281
                if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] == 'multipts') { // for point type
7282
                    if (!empty($new_array_options[$key])) {
7283
                        $sql .= ",ST_MultiPointFromText('" . $this->db->escape($new_array_options[$key]) . "')";
7284
                    } else {
7285
                        $sql .= ",null";
7286
                    }
7287
                }
7288
                if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] == 'linestrg') { // for linestring type
7289
                    if (!empty($new_array_options[$key])) {
7290
                        $sql .= ",ST_LineFromText('" . $this->db->escape($new_array_options[$key]) . "')";
7291
                    } else {
7292
                        $sql .= ",null";
7293
                    }
7294
                }
7295
                if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] == 'polygon') { // for polygon type
7296
                    if (!empty($new_array_options[$key])) {
7297
                        $sql .= ",ST_PolyFromText('" . $this->db->escape($new_array_options[$key]) . "')";
7298
                    } else {
7299
                        $sql .= ",null";
7300
                    }
7301
                }
7302
            }
7303
            // We must insert a default value for fields for other entities that are mandatory to avoid not null error
7304
            if (!empty($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities']) && is_array($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'])) {
7305
                foreach ($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'] as $tmpkey => $tmpval) {
7306
                    if (!isset($extrafields->attributes[$this->table_element]['type'][$tmpkey])) {   // If field not already added previously
7307
                        if (in_array($tmpval, array('int', 'double', 'price'))) {
7308
                            $sql .= ", 0";
7309
                        } else {
7310
                            $sql .= ", ''";
7311
                        }
7312
                    }
7313
                }
7314
            }
7315
7316
            $sql .= ")";
7317
7318
            $resql = $this->db->query($sql);
7319
            if (!$resql) {
7320
                $this->error = $this->db->lasterror();
7321
                $error++;
7322
            }
7323
7324
            if (!$error && $trigger) {
7325
                // Call trigger
7326
                $this->context = array('extrafieldaddupdate' => 1);
7327
                $result = $this->call_trigger($trigger, $userused);
7328
                if ($result < 0) {
7329
                    $error++;
7330
                }
7331
                // End call trigger
7332
            }
7333
7334
            if ($error) {
7335
                $this->db->rollback();
7336
                return -1;
7337
            } else {
7338
                $this->db->commit();
7339
                return 1;
7340
            }
7341
        } else {
7342
            return 0;
7343
        }
7344
    }
7345
7346
    /**
7347
     * Convenience method for retrieving the value of an extrafield without actually fetching it from the database.
7348
     *
7349
     * @param string $key Name of the extrafield
7350
     * @return mixed|null
7351
     */
7352
    public function getExtraField($key)
7353
    {
7354
        return $this->array_options['options_' . $key] ?? null;
7355
    }
7356
7357
    /**
7358
     * Convenience method for setting the value of an extrafield without actually updating it in the database.
7359
     *
7360
     * @param string $key Name of the extrafield
7361
     * @param mixed $value Value to be assigned to the extrafield
7362
     * @return void
7363
     */
7364
    public function setExtraField($key, $value)
7365
    {
7366
        $this->array_options['options_' . $key] = $value;
7367
    }
7368
7369
    /**
7370
     *  Update an extra language value for the current object.
7371
     *  Data to describe values to update are stored into $this->array_options=array('options_codeforfield1'=>'valueforfield1', 'options_codeforfield2'=>'valueforfield2', ...)
7372
     *
7373
     * @param string $key Key of the extrafield (without starting 'options_')
7374
     * @param string $trigger If defined, call also the trigger (for example COMPANY_MODIFY)
7375
     * @param User $userused Object user
7376
     * @return int                         -1=error, O=did nothing, 1=OK
7377
     * @see updateExtraField(), insertExtraLanguages()
7378
     */
7379
    public function updateExtraLanguages($key, $trigger = null, $userused = null)
7380
    {
7381
        global $conf, $langs, $user;
7382
7383
        if (empty($userused)) {
7384
            $userused = $user;
7385
        }
7386
7387
        $error = 0;
7388
7389
        if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7390
            return 0; // For avoid conflicts if trigger used
7391
        }
7392
7393
        return 0;
7394
    }
7395
7396
    /**
7397
     * Return validation test result for a field
7398
     *
7399
     * @param array<string,array{type:string,label:string,enabled:int<0,2>|string,position:int,notnull?:int,visible:int,noteditable?:int,default?:string,index?:int,foreignkey?:string,searchall?:int,isameasure?:int,css?:string,csslist?:string,help?:string,showoncombobox?:int,disabled?:int,arrayofkeyval?:array<int,string>,comment?:string}> $fields Array of properties of field to show
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string,array{type:...ring>,comment?:string}> at position 16 could not be parsed: Expected '}' at position 16, but found 'int'.
Loading history...
7400
     * @param string $fieldKey Key of attribute
7401
     * @param string $fieldValue value of attribute
7402
     * @return bool return false if fail true on success, see $this->error for error message
7403
     */
7404
    public function validateField($fields, $fieldKey, $fieldValue)
7405
    {
7406
        global $langs;
7407
7408
        if (!class_exists('Validate')) {
7409
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/class/validate.class.php';
7410
        }
7411
7412
        $this->clearFieldError($fieldKey);
7413
7414
        if (!isset($fields[$fieldKey]) || $fields[$fieldKey] === null) {
7415
            $this->setFieldError($fieldKey, $langs->trans('FieldNotFoundInObject'));
7416
            return false;
7417
        }
7418
7419
        $val = $fields[$fieldKey];
7420
7421
        $param = array();
7422
        $param['options'] = array();
7423
        $type = $val['type'];
7424
7425
        $required = false;
7426
        if (isset($val['notnull']) && $val['notnull'] === 1) {
7427
            // 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
7428
            $required = true;
7429
        }
7430
7431
        $maxSize = 0;
7432
        $minSize = 0;
7433
7434
        //
7435
        // PREPARE Elements
7436
        //
7437
        $reg = array();
7438
7439
        // Convert var to be able to share same code than showOutputField of extrafields
7440
        if (preg_match('/varchar\((\d+)\)/', $type, $reg)) {
7441
            $type = 'varchar'; // convert varchar(xx) int varchar
7442
            $maxSize = $reg[1];
7443
        } elseif (preg_match('/varchar/', $type)) {
7444
            $type = 'varchar'; // convert varchar(xx) int varchar
7445
        }
7446
7447
        if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
7448
            $type = 'select';
7449
        }
7450
7451
        if (!empty($val['type']) && preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
7452
            $type = 'link';
7453
        }
7454
7455
        if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
7456
            $param['options'] = $val['arrayofkeyval'];
7457
        }
7458
7459
        if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
7460
            $type = 'link';
7461
            $param['options'] = array($reg[1] . ':' . $reg[2] => $reg[1] . ':' . $reg[2]);
7462
        } elseif (preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7463
            $param['options'] = array($reg[1] . ':' . $reg[2] . ':' . $reg[3] . ':' . $reg[4] => 'N');
7464
            $type = 'sellist';
7465
        } elseif (preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
7466
            $param['options'] = array($reg[1] . ':' . $reg[2] . ':' . $reg[3] => 'N');
7467
            $type = 'sellist';
7468
        } elseif (preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
7469
            $param['options'] = array($reg[1] . ':' . $reg[2] => 'N');
7470
            $type = 'sellist';
7471
        }
7472
7473
        //
7474
        // TEST Value
7475
        //
7476
7477
        // Use Validate class to allow external Modules to use data validation part instead of concentrate all test here (factoring) or just for reuse
7478
        $validate = new Validate($this->db, $langs);
0 ignored issues
show
Bug introduced by
The type Dolibarr\Core\Base\Validate was not found. Did you mean Validate? If so, make sure to prefix the type with \.
Loading history...
7479
7480
7481
        // little trick : to perform tests with good performances sort tests by quick to low
7482
7483
        //
7484
        // COMMON TESTS
7485
        //
7486
7487
        // Required test and empty value
7488
        if ($required && !$validate->isNotEmptyString($fieldValue)) {
7489
            $this->setFieldError($fieldKey, $validate->error);
7490
            return false;
7491
        } elseif (!$required && !$validate->isNotEmptyString($fieldValue)) {
7492
            // if no value sent and the field is not mandatory, no need to perform tests
7493
            return true;
7494
        }
7495
7496
        // MAX Size test
7497
        if (!empty($maxSize) && !$validate->isMaxLength($fieldValue, $maxSize)) {
7498
            $this->setFieldError($fieldKey, $validate->error);
7499
            return false;
7500
        }
7501
7502
        // MIN Size test
7503
        if (!empty($minSize) && !$validate->isMinLength($fieldValue, $minSize)) {
7504
            $this->setFieldError($fieldKey, $validate->error);
7505
            return false;
7506
        }
7507
7508
        //
7509
        // TESTS for TYPE
7510
        //
7511
7512
        if (in_array($type, array('date', 'datetime', 'timestamp'))) {
7513
            if (!$validate->isTimestamp($fieldValue)) {
7514
                $this->setFieldError($fieldKey, $validate->error);
7515
                return false;
7516
            } else {
7517
                return true;
7518
            }
7519
        } elseif ($type == 'duration') {
7520
            if (!$validate->isDuration($fieldValue)) {
7521
                $this->setFieldError($fieldKey, $validate->error);
7522
                return false;
7523
            } else {
7524
                return true;
7525
            }
7526
        } elseif (in_array($type, array('double', 'real', 'price'))) {
7527
            // is numeric
7528
            if (!$validate->isNumeric($fieldValue)) {
7529
                $this->setFieldError($fieldKey, $validate->error);
7530
                return false;
7531
            } else {
7532
                return true;
7533
            }
7534
        } elseif ($type == 'boolean') {
7535
            if (!$validate->isBool($fieldValue)) {
7536
                $this->setFieldError($fieldKey, $validate->error);
7537
                return false;
7538
            } else {
7539
                return true;
7540
            }
7541
        } elseif ($type == 'mail') {
7542
            if (!$validate->isEmail($fieldValue)) {
7543
                $this->setFieldError($fieldKey, $validate->error);
7544
                return false;
7545
            }
7546
        } elseif ($type == 'url') {
7547
            if (!$validate->isUrl($fieldValue)) {
7548
                $this->setFieldError($fieldKey, $validate->error);
7549
                return false;
7550
            } else {
7551
                return true;
7552
            }
7553
        } elseif ($type == 'phone') {
7554
            if (!$validate->isPhone($fieldValue)) {
7555
                $this->setFieldError($fieldKey, $validate->error);
7556
                return false;
7557
            } else {
7558
                return true;
7559
            }
7560
        } elseif ($type == 'select' || $type == 'radio') {
7561
            if (!isset($param['options'][$fieldValue])) {
7562
                $this->error = $langs->trans('RequireValidValue');
7563
                return false;
7564
            } else {
7565
                return true;
7566
            }
7567
        } elseif ($type == 'sellist' || $type == 'chkbxlst') {
7568
            $param_list = array_keys($param['options']);
7569
            $InfoFieldList = explode(":", $param_list[0]);
7570
            $value_arr = explode(',', $fieldValue);
7571
            $value_arr = array_map(array($this->db, 'escape'), $value_arr);
7572
7573
            $selectkey = "rowid";
7574
            if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
7575
                $selectkey = $InfoFieldList[2];
7576
            }
7577
7578
            if (!$validate->isInDb($value_arr, $InfoFieldList[0], $selectkey)) {
7579
                $this->setFieldError($fieldKey, $validate->error);
7580
                return false;
7581
            } else {
7582
                return true;
7583
            }
7584
        } elseif ($type == 'link') {
7585
            $param_list = array_keys($param['options']); // $param_list='ObjectName:classPath'
7586
            $InfoFieldList = explode(":", $param_list[0]);
7587
            $classname = $InfoFieldList[0];
7588
            $classpath = $InfoFieldList[1];
7589
            if (!$validate->isFetchable($fieldValue, $classname, $classpath)) {
7590
                $lastIsFetchableError = $validate->error;
7591
7592
                // from V19 of Dolibarr, In some cases link use element instead of class, example project_task
7593
                if ($validate->isFetchableElement($fieldValue, $classname)) {
7594
                    return true;
7595
                }
7596
7597
                $this->setFieldError($fieldKey, $lastIsFetchableError);
7598
                return false;
7599
            } else {
7600
                return true;
7601
            }
7602
        }
7603
7604
        // if no test failed all is ok
7605
        return true;
7606
    }
7607
7608
    /**
7609
     * clear validation message result for a field
7610
     *
7611
     * @param string $fieldKey Key of attribute to clear
7612
     * @return void
7613
     */
7614
    public function clearFieldError($fieldKey)
7615
    {
7616
        $this->error = '';
7617
        unset($this->validateFieldsErrors[$fieldKey]);
7618
    }
7619
7620
    /**
7621
     * set validation error message a field
7622
     *
7623
     * @param string $fieldKey Key of attribute
7624
     * @param string $msg the field error message
7625
     * @return void
7626
     */
7627
    public function setFieldError($fieldKey, $msg = '')
7628
    {
7629
        global $langs;
7630
        if (empty($msg)) {
7631
            $msg = $langs->trans("UnknownError");
7632
        }
7633
7634
        $this->error = $this->validateFieldsErrors[$fieldKey] = $msg;
7635
    }
7636
7637
    /**
7638
     * Function test if type is duration
7639
     *
7640
     * @param array{type:string,label:string,enabled:int<0,2>|string,position:int,notnull?:int,visible:int,noteditable?:int,default?:string,index?:int,foreignkey?:string,searchall?:int,isameasure?:int,css?:string,csslist?:string,help?:string,showoncombobox?:int,disabled?:int,arrayofkeyval?:array<int,string>,comment?:string} $info content information of field
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{type:string,label:...tring>,comment?:string} at position 12 could not be parsed: Expected '}' at position 12, but found 'int'.
Loading history...
7641
     * @return  bool            true if field of type duration
7642
     */
7643
    public function isDuration($info)
7644
    {
7645
        if (is_array($info)) {
7646
            if (isset($info['type']) && ($info['type'] == 'duration')) {
7647
                return true;
7648
            } else {
7649
                return false;
7650
            }
7651
        } else {
7652
            return false;
7653
        }
7654
    }
7655
7656
    /**
7657
     * Function to show lines of extrafields with output data.
7658
     * This function is responsible to output the <tr> and <td> according to correct number of columns received into $params['colspan'] or <div> according to $display_type
7659
     *
7660
     * @param Extrafields $extrafields Extrafield Object
7661
     * @param string $mode Show output ('view') or input ('create' or 'edit') for extrafield
7662
     * @param array<string,mixed> $params Optional parameters. Example: array('style'=>'class="oddeven"', 'colspan'=>$colspan)
7663
     * @param string $keysuffix Suffix string to add after name and id of field (can be used to avoid duplicate names)
7664
     * @param string $keyprefix Prefix string to add before name and id of field (can be used to avoid duplicate names)
7665
     * @param string $onetrtd All fields in same tr td. Used by objectline_create.tpl.php for example.
7666
     * @param string $display_type "card" for form display, "line" for document line display (extrafields on propal line, order line, etc...)
7667
     * @return  string                      String with html content to show
7668
     */
7669
    public function showOptionals($extrafields, $mode = 'view', $params = null, $keysuffix = '', $keyprefix = '', $onetrtd = '', $display_type = 'card')
7670
    {
7671
        global $db, $conf, $langs, $action, $form, $hookmanager;
7672
7673
        if (!is_object($form)) {
7674
            $form = new Form($db);
7675
        }
7676
        if (!is_object($extrafields)) {
7677
            dol_syslog('Bad parameter extrafields for showOptionals', LOG_ERR);
7678
            return 'Bad parameter extrafields for showOptionals';
7679
        }
7680
        if (!is_array($extrafields->attributes[$this->table_element])) {
7681
            dol_syslog("extrafields->attributes was not loaded with extrafields->fetch_name_optionals_label(table_element);", LOG_WARNING);
7682
        }
7683
7684
        $out = '';
7685
7686
        $parameters = array('mode' => $mode, 'params' => $params, 'keysuffix' => $keysuffix, 'keyprefix' => $keyprefix, 'display_type' => $display_type);
7687
        $reshook = $hookmanager->executeHooks('showOptionals', $parameters, $this, $action); // Note that $action and $object may have been modified by hook
7688
7689
        if (empty($reshook)) {
7690
            if (is_array($extrafields->attributes[$this->table_element]) && array_key_exists('label', $extrafields->attributes[$this->table_element]) && is_array($extrafields->attributes[$this->table_element]['label']) && count($extrafields->attributes[$this->table_element]['label']) > 0) {
7691
                $out .= "\n";
7692
                $out .= '<!-- commonobject:showOptionals --> ';
7693
                $out .= "\n";
7694
7695
                $nbofextrafieldsshown = 0;
7696
                $e = 0; // var to manage the modulo (odd/even)
7697
7698
                $lastseparatorkeyfound = '';
7699
                $extrafields_collapse_num = '';
7700
                $extrafields_collapse_num_old = '';
7701
                $i = 0;
7702
7703
                foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $label) {
7704
                    $i++;
7705
7706
                    // Show only the key field in params  @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7707
                    if (is_array($params) && array_key_exists('onlykey', $params) && $key != $params['onlykey']) {
7708
                        continue;
7709
                    }
7710
7711
                    // Test on 'enabled' ('enabled' is different than 'list' = 'visibility')
7712
                    $enabled = 1;
7713
                    if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
7714
                        $enabled = (int)dol_eval($extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
7715
                    }
7716
                    if (empty($enabled)) {
7717
                        continue;
7718
                    }
7719
7720
                    $visibility = 1;
7721
                    if (isset($extrafields->attributes[$this->table_element]['list'][$key])) {
7722
                        $visibility = (int)dol_eval($extrafields->attributes[$this->table_element]['list'][$key], 1, 1, '2');
7723
                    }
7724
7725
                    $perms = 1;
7726
                    if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
7727
                        $perms = (int)dol_eval($extrafields->attributes[$this->table_element]['perms'][$key], 1, 1, '2');
7728
                    }
7729
7730
                    if (($mode == 'create') && !in_array(abs($visibility), array(1, 3))) {
7731
                        continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list
7732
                    } elseif (($mode == 'edit') && !in_array(abs($visibility), array(1, 3, 4))) {
7733
                        continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list and <> 4 = not visible at the creation
7734
                    } elseif ($mode == 'view' && empty($visibility)) {
7735
                        continue;
7736
                    }
7737
                    if (empty($perms)) {
7738
                        continue;
7739
                    }
7740
7741
                    // Load language if required
7742
                    if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
7743
                        $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
7744
                    }
7745
7746
                    $colspan = 0;
7747
                    $value = null;
7748
                    if (is_array($params) && count($params) > 0 && $display_type == 'card') {
7749
                        if (array_key_exists('cols', $params)) {
7750
                            $colspan = $params['cols'];
7751
                        } elseif (array_key_exists('colspan', $params)) {   // For backward compatibility. Use cols instead now.
7752
                            $reg = array();
7753
                            if (preg_match('/colspan="(\d+)"/', $params['colspan'], $reg)) {
7754
                                $colspan = $reg[1];
7755
                            } else {
7756
                                $colspan = $params['colspan'];
7757
                            }
7758
                        }
7759
                    }
7760
                    $colspan = intval($colspan);
7761
7762
                    switch ($mode) {
7763
                        case "view":
7764
                            $value = ((!empty($this->array_options) && array_key_exists("options_" . $key . $keysuffix, $this->array_options)) ? $this->array_options["options_" . $key . $keysuffix] : null); // Value may be cleaned or formatted later
7765
                            break;
7766
                        case "create":
7767
                        case "edit":
7768
                            // We get the value of property found with GETPOST so it takes into account:
7769
                            // default values overwrite, restore back to list link, ... (but not 'default value in database' of field)
7770
                            $check = 'alphanohtml';
7771
                            if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text'))) {
7772
                                $check = 'restricthtml';
7773
                            }
7774
                            $getposttemp = GETPOST($keyprefix . 'options_' . $key . $keysuffix, $check, 3); // GETPOST can get value from GET, POST or setup of default values overwrite.
7775
                            // GETPOST("options_" . $key) can be 'abc' or array(0=>'abc')
7776
                            if (is_array($getposttemp) || $getposttemp != '' || GETPOSTISSET($keyprefix . 'options_' . $key . $keysuffix)) {
7777
                                if (is_array($getposttemp)) {
7778
                                    // $getposttemp is an array but following code expects a comma separated string
7779
                                    $value = implode(",", $getposttemp);
7780
                                } else {
7781
                                    $value = $getposttemp;
7782
                                }
7783
                            } elseif (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('int'))) {
7784
                                $value = (!empty($this->array_options["options_" . $key]) || $this->array_options["options_" . $key] === '0') ? $this->array_options["options_" . $key] : '';
7785
                            } else {
7786
                                $value = (!empty($this->array_options["options_" . $key]) ? $this->array_options["options_" . $key] : ''); // No GET, no POST, no default value, so we take value of object.
7787
                            }
7788
                            //var_dump($keyprefix.' - '.$key.' - '.$keysuffix.' - '.$keyprefix.'options_'.$key.$keysuffix.' - '.$this->array_options["options_".$key.$keysuffix].' - '.$getposttemp.' - '.$value);
7789
                            break;
7790
                    }
7791
7792
                    $nbofextrafieldsshown++;
7793
7794
                    // Output value of the current field
7795
                    if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
7796
                        $extrafields_collapse_num = $key;
7797
                        /*
7798
                        $extrafield_param = $extrafields->attributes[$this->table_element]['param'][$key];
7799
                        if (!empty($extrafield_param) && is_array($extrafield_param)) {
7800
                            $extrafield_param_list = array_keys($extrafield_param['options']);
7801
7802
                            if (count($extrafield_param_list) > 0) {
7803
                                $extrafield_collapse_display_value = intval($extrafield_param_list[0]);
7804
7805
                                if ($extrafield_collapse_display_value == 1 || $extrafield_collapse_display_value == 2) {
7806
                                    //$extrafields_collapse_num = $extrafields->attributes[$this->table_element]['pos'][$key];
7807
                                    $extrafields_collapse_num = $key;
7808
                                }
7809
                            }
7810
                        }
7811
                        */
7812
7813
                        // if colspan=0 or 1, the second column is not extended, so the separator must be on 2 columns
7814
                        $out .= $extrafields->showSeparator($key, $this, ($colspan ? $colspan + 1 : 2), $display_type, $mode);
7815
7816
                        $lastseparatorkeyfound = $key;
7817
                    } else {
7818
                        $collapse_group = $extrafields_collapse_num . (!empty($this->id) ? '_' . $this->id : '');
7819
7820
                        $class = (!empty($extrafields->attributes[$this->table_element]['hidden'][$key]) ? 'hideobject ' : '');
7821
                        $csstyle = '';
7822
                        if (is_array($params) && count($params) > 0) {
7823
                            if (array_key_exists('class', $params)) {
7824
                                $class .= $params['class'] . ' ';
7825
                            }
7826
                            if (array_key_exists('style', $params)) {
7827
                                $csstyle = $params['style'];
7828
                            }
7829
                        }
7830
7831
                        // add html5 elements
7832
                        $domData = ' data-element="extrafield"';
7833
                        $domData .= ' data-targetelement="' . $this->element . '"';
7834
                        $domData .= ' data-targetid="' . $this->id . '"';
7835
7836
                        $html_id = (empty($this->id) ? '' : 'extrarow-' . $this->element . '_' . $key . '_' . $this->id);
7837
                        if ($display_type == 'card') {
7838
                            if (getDolGlobalString('MAIN_EXTRAFIELDS_USE_TWO_COLUMS') && ($e % 2) == 0) {
7839
                                $colspan = 0;
7840
                            }
7841
7842
                            if ($action == 'selectlines') {
7843
                                $colspan++;
7844
                            }
7845
                        }
7846
7847
                        // Convert date into timestamp format (value in memory must be a timestamp)
7848
                        if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date'))) {
7849
                            $datenotinstring = null;
7850
                            if (array_key_exists('options_' . $key, $this->array_options)) {
7851
                                $datenotinstring = $this->array_options['options_' . $key];
7852
                                if (!is_numeric($this->array_options['options_' . $key])) {   // For backward compatibility
7853
                                    $datenotinstring = $this->db->jdate($datenotinstring);
7854
                                }
7855
                            }
7856
                            $datekey = $keyprefix . 'options_' . $key . $keysuffix;
7857
                            $value = (GETPOSTISSET($datekey)) ? dol_mktime(12, 0, 0, GETPOSTINT($datekey . 'month', 3), GETPOSTINT($datekey . 'day', 3), GETPOSTINT($datekey . 'year', 3)) : $datenotinstring;
7858
                        }
7859
                        if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('datetime'))) {
7860
                            $datenotinstring = null;
7861
                            if (array_key_exists('options_' . $key, $this->array_options)) {
7862
                                $datenotinstring = $this->array_options['options_' . $key];
7863
                                if (!is_numeric($this->array_options['options_' . $key])) {   // For backward compatibility
7864
                                    $datenotinstring = $this->db->jdate($datenotinstring);
7865
                                }
7866
                            }
7867
                            $timekey = $keyprefix . 'options_' . $key . $keysuffix;
7868
                            $value = (GETPOSTISSET($timekey)) ? dol_mktime(GETPOSTINT($timekey . 'hour', 3), GETPOSTINT($timekey . 'min', 3), GETPOSTINT($timekey . 'sec', 3), GETPOSTINT($timekey . 'month', 3), GETPOSTINT($timekey . 'day', 3), GETPOSTINT($timekey . 'year', 3), 'tzuserrel') : $datenotinstring;
7869
                        }
7870
                        // Convert float submitted string into real php numeric (value in memory must be a php numeric)
7871
                        if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('price', 'double'))) {
7872
                            if (GETPOSTISSET($keyprefix . 'options_' . $key . $keysuffix) || $value) {
7873
                                $value = price2num($value);
7874
                            } elseif (isset($this->array_options['options_' . $key])) {
7875
                                $value = $this->array_options['options_' . $key];
7876
                            }
7877
                        }
7878
7879
                        // HTML, text, select, integer and varchar: take into account default value in database if in create mode
7880
                        if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text', 'varchar', 'select', 'radio', 'int', 'boolean'))) {
7881
                            if ($action == 'create' || $mode == 'create') {
7882
                                $value = (GETPOSTISSET($keyprefix . 'options_' . $key . $keysuffix) || $value) ? $value : $extrafields->attributes[$this->table_element]['default'][$key];
7883
                            }
7884
                        }
7885
7886
                        $labeltoshow = $langs->trans($label);
7887
                        $helptoshow = $langs->trans($extrafields->attributes[$this->table_element]['help'][$key]);
7888
                        if ($display_type == 'card') {
7889
                            $out .= '<tr ' . ($html_id ? 'id="' . $html_id . '" ' : '') . $csstyle . ' class="field_options_' . $key . ' ' . $class . $this->element . '_extras_' . $key . ' trextrafields_collapse' . $collapse_group . '" ' . $domData . ' >';
7890
                            if (getDolGlobalString('MAIN_VIEW_LINE_NUMBER') && ($action == 'view' || $action == 'valid' || $action == 'editline' || $action == 'confirm_valid' || $action == 'confirm_cancel')) {
7891
                                $out .= '<td></td>';
7892
                            }
7893
                            $out .= '<td class="' . (empty($params['tdclass']) ? 'titlefieldcreate' : $params['tdclass']) . ' wordbreak';
7894
                            if ($extrafields->attributes[$this->table_element]['type'][$key] == 'text') {
7895
                                $out .= ' tdtop';
7896
                            }
7897
                        } elseif ($display_type == 'line') {
7898
                            $out .= '<div ' . ($html_id ? 'id="' . $html_id . '" ' : '') . $csstyle . ' class="fieldline_options_' . $key . ' ' . $class . $this->element . '_extras_' . $key . ' trextrafields_collapse' . $collapse_group . '" ' . $domData . ' >';
7899
                            $out .= '<div style="display: inline-block; padding-right:4px" class="wordbreak';
7900
                        }
7901
                        //$out .= "titlefield";
7902
                        //if (GETPOST('action', 'restricthtml') == 'create') $out.='create';
7903
                        // BUG #11554 : For public page, use red dot for required fields, instead of bold label
7904
                        $tpl_context = isset($params["tpl_context"]) ? $params["tpl_context"] : "none";
7905
                        if ($tpl_context != "public") { // Public page : red dot instead of fieldrequired characters
7906
                            if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
7907
                                $out .= ' fieldrequired';
7908
                            }
7909
                        }
7910
                        $out .= '">';
7911
                        if ($tpl_context == "public") { // Public page : red dot instead of fieldrequired characters
7912
                            if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
7913
                                $out .= $form->textwithpicto($labeltoshow, $helptoshow);
7914
                            } else {
7915
                                $out .= $labeltoshow;
7916
                            }
7917
                            if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
7918
                                $out .= '&nbsp;<span style="color: red">*</span>';
7919
                            }
7920
                        } else {
7921
                            if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
7922
                                $out .= $form->textwithpicto($labeltoshow, $helptoshow);
7923
                            } else {
7924
                                $out .= $labeltoshow;
7925
                            }
7926
                        }
7927
7928
                        $out .= ($display_type == 'card' ? '</td>' : '</div>');
7929
7930
                        // Second column
7931
                        $html_id = !empty($this->id) ? $this->element . '_extras_' . $key . '_' . $this->id : '';
7932
                        if ($display_type == 'card') {
7933
                            // a first td column was already output (and may be another on before if MAIN_VIEW_LINE_NUMBER set), so this td is the next one
7934
                            $out .= '<td ' . ($html_id ? 'id="' . $html_id . '" ' : '') . ' class="valuefieldcreate ' . $this->element . '_extras_' . $key;
7935
                            $out .= '" ' . ($colspan ? ' colspan="' . $colspan . '"' : '');
7936
                            $out .= '>';
7937
                        } elseif ($display_type == 'line') {
7938
                            $out .= '<div ' . ($html_id ? 'id="' . $html_id . '" ' : '') . ' style="display: inline-block" class="valuefieldcreate ' . $this->element . '_extras_' . $key . ' extra_inline_' . $extrafields->attributes[$this->table_element]['type'][$key] . '">';
7939
                        }
7940
7941
                        switch ($mode) {
7942
                            case "view":
7943
                                $out .= $extrafields->showOutputField($key, $value, '', $this->table_element);
7944
                                break;
7945
                            case "create":
7946
                                $listoftypestoshowpicto = explode(',', getDolGlobalString('MAIN_TYPES_TO_SHOW_PICOT', 'email,phone,ip,password'));
7947
                                if (in_array($extrafields->attributes[$this->table_element]['type'][$key], $listoftypestoshowpicto)) {
7948
                                    $out .= getPictoForType($extrafields->attributes[$this->table_element]['type'][$key], ($extrafields->attributes[$this->table_element]['type'][$key] == 'text' ? 'tdtop' : ''));
7949
                                }
7950
                                //$out .= '<!-- type = '.$extrafields->attributes[$this->table_element]['type'][$key].' -->';
7951
                                $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', 0, $this->id, $this->table_element);
7952
                                break;
7953
                            case "edit":
7954
                                $listoftypestoshowpicto = explode(',', getDolGlobalString('MAIN_TYPES_TO_SHOW_PICOT', 'email,phone,ip,password'));
7955
                                if (in_array($extrafields->attributes[$this->table_element]['type'][$key], $listoftypestoshowpicto)) {
7956
                                    $out .= getPictoForType($extrafields->attributes[$this->table_element]['type'][$key], ($extrafields->attributes[$this->table_element]['type'][$key] == 'text' ? 'tdtop' : ''));
7957
                                }
7958
                                $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', 0, $this->id, $this->table_element);
7959
                                break;
7960
                        }
7961
7962
                        $out .= ($display_type == 'card' ? '</td>' : '</div>');
7963
                        $out .= ($display_type == 'card' ? '</tr>' : '</div>');
7964
                        $e++;
7965
                    }
7966
                }
7967
                $out .= "\n";
7968
                // Add code to manage list depending on others
7969
                if (!empty($conf->use_javascript_ajax)) {
7970
                    $out .= $this->getJSListDependancies();
7971
                }
7972
7973
                $out .= '<!-- commonobject:showOptionals end --> ' . "\n";
7974
7975
                if (empty($nbofextrafieldsshown)) {
7976
                    $out = '';
7977
                }
7978
            }
7979
        }
7980
7981
        $out .= $hookmanager->resPrint;
7982
7983
        return $out;
7984
    }
7985
7986
    /**
7987
     * Return HTML string to put an input field into a page
7988
     * Code very similar with showInputField of extra fields
7989
     *
7990
     * @param ?array{type:string,label:string,enabled:int<0,2>|string,position:int,notnull?:int,visible:int,noteditable?:int,default?:string,index?:int,foreignkey?:string,searchall?:int,isameasure?:int,css?:string,csslist?:string,help?:string,showoncombobox?:int,disabled?:int,arrayofkeyval?:array<int,string>,comment?:string} $val Array of properties for field to show (used only if ->fields not defined)
0 ignored issues
show
Documentation Bug introduced by
The doc comment ?array{type:string,label...tring>,comment?:string} at position 12 could not be parsed: Expected '}' at position 12, but found 'int'.
Loading history...
7991
     *                                                                                                                                                                                                                                                                                                                                          Array of properties of field to show
7992
     * @param string $key Key of attribute
7993
     * @param string|string[] $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 array type must be array)
7994
     * @param string $moreparam To add more parameters on html input tag
7995
     * @param string $keysuffix Suffix string to add into name and id of field (can be used to avoid duplicate names)
7996
     * @param string $keyprefix Prefix string to add into name and id of field (can be used to avoid duplicate names)
7997
     * @param string|int $morecss Value for css to define style/length of field. May also be a numeric.
7998
     * @param int<0,1> $nonewbutton Force to not show the new button on field that are links to object
7999
     * @return string
8000
     */
8001
    public function showInputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = 0, $nonewbutton = 0)
8002
    {
8003
        global $conf, $langs, $form;
8004
8005
        if (!is_object($form)) {
8006
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/class/html.form.class.php';
8007
            $form = new Form($this->db);
8008
        }
8009
8010
        if (!empty($this->fields)) {
8011
            $val = $this->fields[$key];
8012
        }
8013
8014
        // Validation tests and output
8015
        $fieldValidationErrorMsg = '';
8016
        $validationClass = '';
8017
        $fieldValidationErrorMsg = $this->getFieldError($key);
8018
        if (!empty($fieldValidationErrorMsg)) {
8019
            $validationClass = ' --error'; // the -- is use as class state in css :  .--error can't be be defined alone it must be define with another class like .my-class.--error or input.--error
8020
        } else {
8021
            $validationClass = ' --success'; // the -- is use as class state in css :  .--success can't be be defined alone it must be define with another class like .my-class.--success or input.--success
8022
        }
8023
8024
        $valuemultiselectinput = array();
8025
        $out = '';
8026
        $type = '';
8027
        $isDependList = 0;
8028
        $param = array();
8029
        $param['options'] = array();
8030
        $reg = array();
8031
        // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
8032
        $size = !empty($this->fields[$key]['size']) ? $this->fields[$key]['size'] : 0;
8033
        // Because we work on extrafields
8034
        if (preg_match('/^(integer|link):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
8035
            $param['options'] = array($reg[2] . ':' . $reg[3] . ':' . $reg[4] . ':' . $reg[5] => 'N');
8036
            $type = 'link';
8037
        } elseif (preg_match('/^(integer|link):(.*):(.*):(.*)/i', $val['type'], $reg)) {
8038
            $param['options'] = array($reg[2] . ':' . $reg[3] . ':' . $reg[4] => 'N');
8039
            $type = 'link';
8040
        } elseif (preg_match('/^(integer|link):(.*):(.*)/i', $val['type'], $reg)) {
8041
            $param['options'] = array($reg[2] . ':' . $reg[3] => 'N');
8042
            $type = 'link';
8043
        } elseif (preg_match('/^(sellist):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
8044
            $param['options'] = array($reg[2] . ':' . $reg[3] . ':' . $reg[4] . ':' . $reg[5] => 'N');
8045
            $type = 'sellist';
8046
        } elseif (preg_match('/^(sellist):(.*):(.*):(.*)/i', $val['type'], $reg)) {
8047
            $param['options'] = array($reg[2] . ':' . $reg[3] . ':' . $reg[4] => 'N');
8048
            $type = 'sellist';
8049
        } elseif (preg_match('/^(sellist):(.*):(.*)/i', $val['type'], $reg)) {
8050
            $param['options'] = array($reg[2] . ':' . $reg[3] => 'N');
8051
            $type = 'sellist';
8052
        } elseif (preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
8053
            $param['options'] = array($reg[1] => 'N');
8054
            $type = 'chkbxlst';
8055
        } elseif (preg_match('/varchar\((\d+)\)/', $val['type'], $reg)) {
8056
            $param['options'] = array();
8057
            $type = 'varchar';
8058
            $size = $reg[1];
8059
        } elseif (preg_match('/varchar/', $val['type'])) {
8060
            $param['options'] = array();
8061
            $type = 'varchar';
8062
        } else {
8063
            $param['options'] = array();
8064
            $type = $this->fields[$key]['type'];
8065
        }
8066
        //var_dump($type); var_dump($param['options']);
8067
8068
        // Special case that force options and type ($type can be integer, varchar, ...)
8069
        if (!empty($this->fields[$key]['arrayofkeyval']) && is_array($this->fields[$key]['arrayofkeyval'])) {
8070
            $param['options'] = $this->fields[$key]['arrayofkeyval'];
8071
            // Special case that prevent to force $type to have multiple input
8072
            if (empty($this->fields[$key]['multiinput'])) {
8073
                $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
8074
            }
8075
        }
8076
8077
        $label = $this->fields[$key]['label'];
8078
        //$elementtype=$this->fields[$key]['elementtype'];  // Seems not used
8079
        // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
8080
        $default = (!empty($this->fields[$key]['default']) ? $this->fields[$key]['default'] : '');
8081
        // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
8082
        $computed = (!empty($this->fields[$key]['computed']) ? $this->fields[$key]['computed'] : '');
8083
        // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
8084
        $unique = (!empty($this->fields[$key]['unique']) ? $this->fields[$key]['unique'] : 0);
8085
        // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
8086
        $required = (!empty($this->fields[$key]['required']) ? $this->fields[$key]['required'] : 0);
8087
        // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
8088
        $autofocusoncreate = (!empty($this->fields[$key]['autofocusoncreate']) ? $this->fields[$key]['autofocusoncreate'] : 0);
8089
8090
        // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
8091
        $langfile = (!empty($this->fields[$key]['langfile']) ? $this->fields[$key]['langfile'] : '');
8092
        // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
8093
        $list = (!empty($this->fields[$key]['list']) ? $this->fields[$key]['list'] : 0);
8094
        $hidden = (in_array(abs($this->fields[$key]['visible']), array(0, 2)) ? 1 : 0);
8095
8096
        $objectid = $this->id;
8097
8098
        if ($computed) {
8099
            if (!preg_match('/^search_/', $keyprefix)) {
8100
                return '<span class="opacitymedium">' . $langs->trans("AutomaticallyCalculated") . '</span>';
8101
            } else {
8102
                return '';
8103
            }
8104
        }
8105
8106
        // Set value of $morecss. For this, we use in priority showsize from parameters, then $val['css'] then autodefine
8107
        if (empty($morecss) && !empty($val['css'])) {
8108
            $morecss = $val['css'];
8109
        } elseif (empty($morecss)) {
8110
            if ($type == 'date') {
8111
                $morecss = 'minwidth100imp';
8112
            } elseif ($type == 'datetime' || $type == 'link') { // link means an foreign key to another primary id
8113
                $morecss = 'minwidth200imp';
8114
            } elseif (in_array($type, array('int', 'integer', 'price')) || preg_match('/^double(\([0-9],[0-9]\)){0,1}/', (string)$type)) {
8115
                $morecss = 'maxwidth75';
8116
            } elseif ($type == 'url') {
8117
                $morecss = 'minwidth400';
8118
            } elseif ($type == 'boolean') {
8119
                $morecss = '';
8120
            } else {
8121
                if (is_numeric($size) && round((float)$size) < 12) {
8122
                    $morecss = 'minwidth100';
8123
                } elseif (is_numeric($size) && round((float)$size) <= 48) {
8124
                    $morecss = 'minwidth200';
8125
                } else {
8126
                    $morecss = 'minwidth400';
8127
                }
8128
            }
8129
        }
8130
8131
        // Add validation state class
8132
        if (!empty($validationClass)) {
8133
            $morecss .= $validationClass;
8134
        }
8135
8136
        if (in_array($type, array('date'))) {
8137
            $tmp = explode(',', $size);
8138
            $newsize = $tmp[0];
8139
            $showtime = 0;
8140
8141
            // Do not show current date when field not required (see selectDate() method)
8142
            if (!$required && $value == '') {
8143
                $value = '-1';
8144
            }
8145
8146
            // TODO Must also support $moreparam
8147
            $out = $form->selectDate($value, $keyprefix . $key . $keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1);
8148
        } elseif (in_array($type, array('datetime'))) {
8149
            $tmp = explode(',', $size);
8150
            $newsize = $tmp[0];
8151
            $showtime = 1;
8152
8153
            // Do not show current date when field not required (see selectDate() method)
8154
            if (!$required && $value == '') {
8155
                $value = '-1';
8156
            }
8157
8158
            // TODO Must also support $moreparam
8159
            $out = $form->selectDate($value, $keyprefix . $key . $keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1, '', '', '', 1, '', '', 'tzuserrel');
8160
        } elseif (in_array($type, array('duration'))) {
8161
            $out = $form->select_duration($keyprefix . $key . $keysuffix, $value, 0, 'text', 0, 1);
8162
        } elseif (in_array($type, array('int', 'integer'))) {
8163
            $tmp = explode(',', $size);
8164
            $newsize = $tmp[0];
8165
            $out = '<input type="text" class="flat ' . $morecss . '" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '"' . ($newsize > 0 ? ' maxlength="' . $newsize . '"' : '') . ' value="' . dol_escape_htmltag($value) . '"' . ($moreparam ? $moreparam : '') . ($autofocusoncreate ? ' autofocus' : '') . '>';
8166
        } elseif (in_array($type, array('real'))) {
8167
            $out = '<input type="text" class="flat ' . $morecss . '" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" value="' . dol_escape_htmltag($value) . '"' . ($moreparam ? $moreparam : '') . ($autofocusoncreate ? ' autofocus' : '') . '>';
8168
        } elseif (preg_match('/varchar/', (string)$type)) {
8169
            $out = '<input type="text" class="flat ' . $morecss . '" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '"' . ($size > 0 ? ' maxlength="' . $size . '"' : '') . ' value="' . dol_escape_htmltag($value) . '"' . ($moreparam ? $moreparam : '') . ($autofocusoncreate ? ' autofocus' : '') . '>';
8170
        } elseif (in_array($type, array('email', 'mail', 'phone', 'url', 'ip'))) {
8171
            $out = '<input type="text" class="flat ' . $morecss . '" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" value="' . dol_escape_htmltag($value) . '" ' . ($moreparam ? $moreparam : '') . ($autofocusoncreate ? ' autofocus' : '') . '>';
8172
        } elseif (preg_match('/^text/', (string)$type)) {
8173
            if (!preg_match('/search_/', $keyprefix)) {     // If keyprefix is search_ or search_options_, we must just use a simple text field
8174
                if (!empty($param['options'])) {
8175
                    // If the textarea field has a list of arrayofkeyval into its definition, we suggest a combo with possible values to fill the textarea.
8176
                    //var_dump($param['options']);
8177
                    $out .= $form->selectarray($keyprefix . $key . $keysuffix . "_multiinput", $param['options'], '', 1, 0, 0, "flat maxwidthonphone" . $morecss);
8178
                    $out .= "<script>";
8179
                    $out .= '
8180
					function handlemultiinputdisabling(htmlname){
8181
						console.log("We handle the disabling of used options for "+htmlname+"_multiinput");
8182
						multiinput = $("#"+htmlname+"_multiinput");
8183
						multiinput.find("option").each(function(){
8184
							tmpval = $("#"+htmlname).val();
8185
							tmpvalarray = tmpval.split(",");
8186
							valtotest = $(this).val();
8187
							if(tmpvalarray.includes(valtotest)){
8188
								$(this).prop("disabled",true);
8189
							} else {
8190
								if($(this).prop("disabled") == true){
8191
									console.log(valtotest)
8192
									$(this).prop("disabled", false);
8193
								}
8194
							}
8195
						});
8196
					}
8197
8198
					$(document).ready(function () {
8199
						$("#' . $keyprefix . $key . $keysuffix . '_multiinput").on("change",function() {
8200
							console.log("We add the selected value to the text area ' . $keyprefix . $key . $keysuffix . '");
8201
							tmpval = $("#' . $keyprefix . $key . $keysuffix . '").val();
8202
							tmpvalarray = tmpval.split(",");
8203
							valtotest = $(this).val();
8204
							if(valtotest != -1 && !tmpvalarray.includes(valtotest)){
8205
								if(tmpval == ""){
8206
									tmpval = valtotest;
8207
								} else {
8208
									tmpval = tmpval + "," + valtotest;
8209
								}
8210
								$("#' . $keyprefix . $key . $keysuffix . '").val(tmpval);
8211
								handlemultiinputdisabling("' . $keyprefix . $key . $keysuffix . '");
8212
							}
8213
						});
8214
						$("#' . $keyprefix . $key . $keysuffix . '").on("change",function(){
8215
							handlemultiinputdisabling($(this).attr("id"));
8216
						});
8217
						handlemultiinputdisabling("' . $keyprefix . $key . $keysuffix . '");
8218
					})';
8219
                    $out .= "</script>";
8220
                }
8221
                require_once constant('DOL_DOCUMENT_ROOT') . '/core/class/doleditor.class.php';
8222
                $doleditor = new DolEditor($keyprefix . $key . $keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, false, ROWS_5, '90%');
8223
                $out .= (string)$doleditor->Create(1, '', true, '', '', '', $morecss);
8224
            } else {
8225
                $out = '<input type="text" class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" value="' . dol_escape_htmltag($value) . '" ' . ($moreparam ? $moreparam : '') . '>';
8226
            }
8227
        } elseif (preg_match('/^html/', (string)$type)) {
8228
            if (!preg_match('/search_/', $keyprefix)) {     // If keyprefix is search_ or search_options_, we must just use a simple text field
8229
                require_once constant('DOL_DOCUMENT_ROOT') . '/core/class/doleditor.class.php';
8230
                $doleditor = new DolEditor($keyprefix . $key . $keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, isModEnabled('fckeditor') && getDolGlobalInt('FCKEDITOR_ENABLE_SOCIETE'), ROWS_5, '90%');
8231
                $out = (string)$doleditor->Create(1, '', true, '', '', $moreparam, $morecss);
8232
            } else {
8233
                $out = '<input type="text" class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" value="' . dol_escape_htmltag($value) . '" ' . ($moreparam ? $moreparam : '') . '>';
8234
            }
8235
        } elseif ($type == 'boolean') {
8236
            $checked = '';
8237
            if (!empty($value)) {
8238
                $checked = ' checked value="1" ';
8239
            } else {
8240
                $checked = ' value="1" ';
8241
            }
8242
            $out = '<input type="checkbox" class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" ' . $checked . ' ' . ($moreparam ? $moreparam : '') . '>';
8243
        } elseif ($type == 'price') {
8244
            if (!empty($value)) {       // $value in memory is a php numeric, we format it into user number format.
8245
                $value = price($value);
8246
            }
8247
            $out = '<input type="text" class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" value="' . $value . '" ' . ($moreparam ? $moreparam : '') . '> ' . $langs->getCurrencySymbol($conf->currency);
8248
        } elseif (preg_match('/^double(\([0-9],[0-9]\)){0,1}/', (string)$type)) {
8249
            if (!empty($value)) {       // $value in memory is a php numeric, we format it into user number format.
8250
                $value = price($value);
8251
            }
8252
            $out = '<input type="text" class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" value="' . $value . '" ' . ($moreparam ? $moreparam : '') . '> ';
8253
        } elseif ($type == 'select') {  // combo list
8254
            $out = '';
8255
            if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
8256
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
8257
                $out .= ajax_combobox($keyprefix . $key . $keysuffix, array(), 0);
8258
            }
8259
8260
            $tmpselect = '';
8261
            $nbchoice = 0;
8262
            foreach ($param['options'] as $keyb => $valb) {
8263
                if ((string)$keyb == '') {
8264
                    continue;
8265
                }
8266
                if (strpos($valb, "|") !== false) {
8267
                    list($valb, $parent) = explode('|', $valb);
8268
                }
8269
                $nbchoice++;
8270
                $tmpselect .= '<option value="' . $keyb . '"';
8271
                $tmpselect .= (((string)$value == (string)$keyb) ? ' selected' : '');
8272
                if (!empty($parent)) {
8273
                    $isDependList = 1;
8274
                }
8275
                $tmpselect .= (!empty($parent) ? ' parent="' . $parent . '"' : '');
8276
                $tmpselect .= '>' . $langs->trans($valb) . '</option>';
8277
            }
8278
8279
            $out .= '<select class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" ' . ($moreparam ? $moreparam : '') . '>';
8280
            if ((!isset($this->fields[$key]['default'])) || ($this->fields[$key]['notnull'] != 1) || $nbchoice >= 2) {
8281
                $out .= '<option value="0">&nbsp;</option>';
8282
            }
8283
            $out .= $tmpselect;
8284
            $out .= '</select>';
8285
        } elseif ($type == 'sellist') {
8286
            $out = '';
8287
            if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
8288
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
8289
                $out .= ajax_combobox($keyprefix . $key . $keysuffix, array(), 0);
8290
            }
8291
8292
            $out .= '<select class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" ' . ($moreparam ? $moreparam : '') . '>';
8293
            if (is_array($param['options'])) {
8294
                $param_list = array_keys($param['options']);
8295
                $InfoFieldList = explode(":", $param_list[0], 5);
8296
                if (!empty($InfoFieldList[4])) {
8297
                    $pos = 0;
8298
                    $parenthesisopen = 0;
8299
                    while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
8300
                        if (substr($InfoFieldList[4], $pos, 1) == '(') {
8301
                            $parenthesisopen++;
8302
                        }
8303
                        if (substr($InfoFieldList[4], $pos, 1) == ')') {
8304
                            $parenthesisopen--;
8305
                        }
8306
                        $pos++;
8307
                    }
8308
                    $tmpbefore = substr($InfoFieldList[4], 0, $pos);
8309
                    $tmpafter = substr($InfoFieldList[4], $pos + 1);
8310
                    //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter);
8311
                    $InfoFieldList[4] = $tmpbefore;
8312
                    if ($tmpafter !== '') {
8313
                        $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
8314
                    }
8315
                    //var_dump($InfoFieldList);
8316
                }
8317
                $parentName = '';
8318
                $parentField = '';
8319
8320
                // 0 : tableName
8321
                // 1 : label field name
8322
                // 2 : key fields name (if differ of rowid)
8323
                // 3 : key field parent (for dependent lists)
8324
                // 4 : where clause filter on column or table extrafield, syntax field='value' or extra.field=value
8325
                // 5 : id category type
8326
                // 6 : ids categories list separated by comma for category root
8327
                // 7 : sort field
8328
                $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2] . ' as rowid');
8329
8330
                if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
8331
                    if (strpos($InfoFieldList[4], 'extra.') !== false) {
8332
                        $keyList = 'main.' . $InfoFieldList[2] . ' as rowid';
8333
                    } else {
8334
                        $keyList = $InfoFieldList[2] . ' as rowid';
8335
                    }
8336
                }
8337
                if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
8338
                    list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
8339
                    $keyList .= ', ' . $parentField;
8340
                }
8341
8342
                $filter_categorie = false;
8343
                if (count($InfoFieldList) > 5) {
8344
                    if ($InfoFieldList[0] == 'categorie') {
8345
                        $filter_categorie = true;
8346
                    }
8347
                }
8348
8349
                if (!$filter_categorie) {
8350
                    $fields_label = explode('|', $InfoFieldList[1]);
8351
                    if (is_array($fields_label)) {
8352
                        $keyList .= ', ';
8353
                        $keyList .= implode(', ', $fields_label);
8354
                    }
8355
8356
                    $sqlwhere = '';
8357
                    $sql = "SELECT " . $keyList;
8358
                    $sql .= " FROM " . $this->db->prefix() . $InfoFieldList[0];
8359
                    if (!empty($InfoFieldList[4])) {
8360
                        // can use SELECT request
8361
                        if (strpos($InfoFieldList[4], '$SEL$') !== false) {
8362
                            $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
8363
                        }
8364
8365
                        // current object id can be use into filter
8366
                        if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
8367
                            $InfoFieldList[4] = str_replace('$ID$', (string)$objectid, $InfoFieldList[4]);
8368
                        } else {
8369
                            $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
8370
                        }
8371
8372
                        // We have to join on extrafield table
8373
                        $errstr = '';
8374
                        if (strpos($InfoFieldList[4], 'extra') !== false) {
8375
                            $sql .= " as main, " . $this->db->prefix() . $InfoFieldList[0] . "_extrafields as extra";
8376
                            $sqlwhere .= " WHERE extra.fk_object=main." . $InfoFieldList[2];
8377
                            $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8378
                        } else {
8379
                            $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
8380
                        }
8381
                    } else {
8382
                        $sqlwhere .= ' WHERE 1=1';
8383
                    }
8384
                    // Some tables may have field, some other not. For the moment we disable it.
8385
                    if (in_array($InfoFieldList[0], array('tablewithentity'))) {
8386
                        $sqlwhere .= " AND entity = " . ((int)$conf->entity);
8387
                    }
8388
                    $sql .= $sqlwhere;
8389
                    //print $sql;
8390
8391
                    // Note: $InfoFieldList can be 'sellist:TableName:LabelFieldName[:KeyFieldName[:KeyFieldParent[:Filter[:CategoryIdType[:CategoryIdList[:Sortfield]]]]]]'
8392
                    if (isset($InfoFieldList[7]) && preg_match('/^[a-z0-9_\-,]+$/i', $InfoFieldList[7])) {
8393
                        $sql .= " ORDER BY " . $this->db->escape($InfoFieldList[7]);
8394
                    } else {
8395
                        $sql .= " ORDER BY " . $this->db->sanitize(implode(', ', $fields_label));
8396
                    }
8397
8398
                    dol_syslog(get_class($this) . '::showInputField type=sellist', LOG_DEBUG);
8399
                    $resql = $this->db->query($sql);
8400
                    if ($resql) {
8401
                        $out .= '<option value="0">&nbsp;</option>';
8402
                        $num = $this->db->num_rows($resql);
8403
                        $i = 0;
8404
                        while ($i < $num) {
8405
                            $labeltoshow = '';
8406
                            $obj = $this->db->fetch_object($resql);
8407
8408
                            // Several field into label (eq table:code|libelle:rowid)
8409
                            $notrans = false;
8410
                            $fields_label = explode('|', $InfoFieldList[1]);
8411
                            if (count($fields_label) > 1) {
8412
                                $notrans = true;
8413
                                foreach ($fields_label as $field_toshow) {
8414
                                    $labeltoshow .= $obj->$field_toshow . ' ';
8415
                                }
8416
                            } else {
8417
                                $labeltoshow = $obj->{$InfoFieldList[1]};
8418
                            }
8419
                            $labeltoshow = dol_trunc($labeltoshow, 45);
8420
8421
                            if ($value == $obj->rowid) {
8422
                                foreach ($fields_label as $field_toshow) {
8423
                                    $translabel = $langs->trans($obj->$field_toshow);
8424
                                    if ($translabel != $obj->$field_toshow) {
8425
                                        $labeltoshow = dol_trunc($translabel) . ' ';
8426
                                    } else {
8427
                                        $labeltoshow = dol_trunc($obj->$field_toshow) . ' ';
8428
                                    }
8429
                                }
8430
                                $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
8431
                            } else {
8432
                                if (!$notrans) {
8433
                                    $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8434
                                    if ($translabel != $obj->{$InfoFieldList[1]}) {
8435
                                        $labeltoshow = dol_trunc($translabel, 18);
8436
                                    } else {
8437
                                        $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]});
8438
                                    }
8439
                                }
8440
                                if (empty($labeltoshow)) {
8441
                                    $labeltoshow = '(not defined)';
8442
                                }
8443
                                if ($value == $obj->rowid) {
8444
                                    $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
8445
                                }
8446
8447
                                if (!empty($InfoFieldList[3]) && $parentField) {
8448
                                    $parent = $parentName . ':' . $obj->{$parentField};
8449
                                    $isDependList = 1;
8450
                                }
8451
8452
                                $out .= '<option value="' . $obj->rowid . '"';
8453
                                $out .= ($value == $obj->rowid ? ' selected' : '');
8454
                                $out .= (!empty($parent) ? ' parent="' . $parent . '"' : '');
8455
                                $out .= '>' . $labeltoshow . '</option>';
8456
                            }
8457
8458
                            $i++;
8459
                        }
8460
                        $this->db->free($resql);
8461
                    } else {
8462
                        print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
8463
                    }
8464
                } else {
8465
                    require_once constant('DOL_DOCUMENT_ROOT') . '/categories/class/categorie.class.php';
8466
                    $data = $form->select_all_categories(Categorie::$MAP_ID_TO_CODE[$InfoFieldList[5]], '', 'parent', 64, $InfoFieldList[6], 1, 1);
8467
                    $out .= '<option value="0">&nbsp;</option>';
8468
                    foreach ($data as $data_key => $data_value) {
8469
                        $out .= '<option value="' . $data_key . '"';
8470
                        $out .= ($value == $data_key ? ' selected' : '');
8471
                        $out .= '>' . $data_value . '</option>';
8472
                    }
8473
                }
8474
            }
8475
            $out .= '</select>';
8476
        } elseif ($type == 'checkbox') {
8477
            $value_arr = explode(',', $value);
8478
            $out = $form->multiselectarray($keyprefix . $key . $keysuffix, (empty($param['options']) ? null : $param['options']), $value_arr, 0, 0, $morecss, 0, '100%');
8479
        } elseif ($type == 'radio') {
8480
            $out = '';
8481
            foreach ($param['options'] as $keyopt => $valopt) {
8482
                $out .= '<input class="flat ' . $morecss . '" type="radio" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" ' . ($moreparam ? $moreparam : '');
8483
                $out .= ' value="' . $keyopt . '"';
8484
                $out .= ' id="' . $keyprefix . $key . $keysuffix . '_' . $keyopt . '"';
8485
                $out .= ($value == $keyopt ? 'checked' : '');
8486
                $out .= '/><label for="' . $keyprefix . $key . $keysuffix . '_' . $keyopt . '">' . $valopt . '</label><br>';
8487
            }
8488
        } elseif ($type == 'chkbxlst') {
8489
            if (is_array($value)) {
8490
                $value_arr = $value;
8491
            } else {
8492
                $value_arr = explode(',', $value);
8493
            }
8494
8495
            if (is_array($param['options'])) {
8496
                $param_list = array_keys($param['options']);
8497
                $InfoFieldList = explode(":", $param_list[0]);
8498
                $parentName = '';
8499
                $parentField = '';
8500
                // 0 : tableName
8501
                // 1 : label field name
8502
                // 2 : key fields name (if differ of rowid)
8503
                // 3 : key field parent (for dependent lists)
8504
                // 4 : where clause filter on column or table extrafield, syntax field='value' or extra.field=value
8505
                // 5 : id category type
8506
                // 6 : ids categories list separated by comma for category root
8507
                '@phan-var-force array{0:string,1:string,2:string,3:string,3:string,5:string,6:string} $InfoFieldList';
8508
8509
                $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2] . ' as rowid');
8510
8511
                if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
8512
                    list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
8513
                    $keyList .= ', ' . $parentField;
8514
                }
8515
                if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
8516
                    if (strpos($InfoFieldList[4], 'extra.') !== false) {
8517
                        $keyList = 'main.' . $InfoFieldList[2] . ' as rowid';
8518
                    } else {
8519
                        $keyList = $InfoFieldList[2] . ' as rowid';
8520
                    }
8521
                }
8522
8523
                $filter_categorie = false;
8524
                if (count($InfoFieldList) > 5) {
8525
                    if ($InfoFieldList[0] == 'categorie') {
8526
                        $filter_categorie = true;
8527
                    }
8528
                }
8529
8530
                if (!$filter_categorie) {
8531
                    $fields_label = explode('|', $InfoFieldList[1]);
8532
                    if (is_array($fields_label)) {
8533
                        $keyList .= ', ';
8534
                        $keyList .= implode(', ', $fields_label);
8535
                    }
8536
8537
                    $sqlwhere = '';
8538
                    $sql = "SELECT " . $keyList;
8539
                    $sql .= ' FROM ' . $this->db->prefix() . $InfoFieldList[0];
8540
                    if (!empty($InfoFieldList[4])) {
8541
                        // can use SELECT request
8542
                        if (strpos($InfoFieldList[4], '$SEL$') !== false) {
8543
                            $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
8544
                        }
8545
8546
                        // current object id can be use into filter
8547
                        if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
8548
                            $InfoFieldList[4] = str_replace('$ID$', (string)$objectid, $InfoFieldList[4]);
8549
                        } else {
8550
                            $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
8551
                        }
8552
8553
                        // We have to join on extrafield table
8554
                        if (strpos($InfoFieldList[4], 'extra') !== false) {
8555
                            $sql .= ' as main, ' . $this->db->prefix() . $InfoFieldList[0] . '_extrafields as extra';
8556
                            $sqlwhere .= " WHERE extra.fk_object=main." . $InfoFieldList[2] . " AND " . $InfoFieldList[4];
8557
                        } else {
8558
                            $sqlwhere .= " WHERE " . $InfoFieldList[4];
8559
                        }
8560
                    } else {
8561
                        $sqlwhere .= ' WHERE 1=1';
8562
                    }
8563
                    // Some tables may have field, some other not. For the moment we disable it.
8564
                    if (in_array($InfoFieldList[0], array('tablewithentity'))) {
8565
                        $sqlwhere .= " AND entity = " . ((int)$conf->entity);
8566
                    }
8567
                    // $sql.=preg_replace('/^ AND /','',$sqlwhere);
8568
                    // print $sql;
8569
8570
                    $sql .= $sqlwhere;
8571
                    dol_syslog(get_class($this) . '::showInputField type=chkbxlst', LOG_DEBUG);
8572
                    $resql = $this->db->query($sql);
8573
                    if ($resql) {
8574
                        $num = $this->db->num_rows($resql);
8575
                        $i = 0;
8576
8577
                        $data = array();
8578
8579
                        while ($i < $num) {
8580
                            $labeltoshow = '';
8581
                            $obj = $this->db->fetch_object($resql);
8582
8583
                            $notrans = false;
8584
                            // Several field into label (eq table:code|libelle:rowid)
8585
                            $fields_label = explode('|', $InfoFieldList[1]);
8586
                            if (count($fields_label) > 1) {
8587
                                $notrans = true;
8588
                                foreach ($fields_label as $field_toshow) {
8589
                                    $labeltoshow .= $obj->$field_toshow . ' ';
8590
                                }
8591
                            } else {
8592
                                $labeltoshow = $obj->{$InfoFieldList[1]};
8593
                            }
8594
                            $labeltoshow = dol_trunc($labeltoshow, 45);
8595
8596
                            if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8597
                                foreach ($fields_label as $field_toshow) {
8598
                                    $translabel = $langs->trans($obj->$field_toshow);
8599
                                    if ($translabel != $obj->$field_toshow) {
8600
                                        $labeltoshow = dol_trunc($translabel, 18) . ' ';
8601
                                    } else {
8602
                                        $labeltoshow = dol_trunc($obj->$field_toshow, 18) . ' ';
8603
                                    }
8604
                                }
8605
8606
                                $data[$obj->rowid] = $labeltoshow;
8607
                            } else {
8608
                                if (!$notrans) {
8609
                                    $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8610
                                    if ($translabel != $obj->{$InfoFieldList[1]}) {
8611
                                        $labeltoshow = dol_trunc($translabel, 18);
8612
                                    } else {
8613
                                        $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]}, 18);
8614
                                    }
8615
                                }
8616
                                if (empty($labeltoshow)) {
8617
                                    $labeltoshow = '(not defined)';
8618
                                }
8619
8620
                                if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8621
                                    $data[$obj->rowid] = $labeltoshow;
8622
                                }
8623
8624
                                if (!empty($InfoFieldList[3]) && $parentField) {
8625
                                    $parent = $parentName . ':' . $obj->{$parentField};
8626
                                    $isDependList = 1;
8627
                                }
8628
8629
                                $data[$obj->rowid] = $labeltoshow;
8630
                            }
8631
8632
                            $i++;
8633
                        }
8634
                        $this->db->free($resql);
8635
8636
                        $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, 0, 0, $morecss, 0, '100%');
8637
                    } else {
8638
                        print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
8639
                    }
8640
                } else {
8641
                    require_once constant('DOL_DOCUMENT_ROOT') . '/categories/class/categorie.class.php';
8642
                    $data = $form->select_all_categories(Categorie::$MAP_ID_TO_CODE[$InfoFieldList[5]], '', 'parent', 64, $InfoFieldList[6], 1, 1);
8643
                    $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, 0, 0, $morecss, 0, '100%');
8644
                }
8645
            }
8646
        } elseif ($type == 'link') {
8647
            // $param_list='ObjectName:classPath[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
8648
            // Filter can contains some ':' inside.
8649
            $param_list = array_keys($param['options']);
8650
            $param_list_array = explode(':', $param_list[0], 4);
8651
8652
            $showempty = (($required && $default != '') ? 0 : 1);
8653
8654
            if (!preg_match('/search_/', $keyprefix)) {
8655
                if (!empty($param_list_array[2])) {     // If the entry into $fields is set to add a create button
8656
                    if (!empty($this->fields[$key]['picto'])) {
8657
                        $morecss .= ' widthcentpercentminusxx';
8658
                    } else {
8659
                        $morecss .= ' widthcentpercentminusx';
8660
                    }
8661
                } else {
8662
                    if (!empty($this->fields[$key]['picto'])) {
8663
                        $morecss .= ' widthcentpercentminusx';
8664
                    }
8665
                }
8666
            }
8667
            $objectfield = $this->element . ($this->module ? '@' . $this->module : '') . ':' . $key . $keysuffix;
8668
            $out = $form->selectForForms($param_list_array[0], $keyprefix . $key . $keysuffix, $value, $showempty, '', '', $morecss, $moreparam, 0, (empty($val['disabled']) ? 0 : 1), '', $objectfield);
8669
8670
            if (!empty($param_list_array[2])) {     // If the entry into $fields is set, we must add a create button
8671
                if (
8672
                    (!GETPOSTISSET('backtopage') || strpos(GETPOST('backtopage'), $_SERVER['PHP_SELF']) === 0)  // // To avoid to open several times the 'Plus' button (we accept only one level)
8673
                    && empty($val['disabled']) && empty($nonewbutton)
8674
                ) {    // and to avoid to show the button if the field is protected by a "disabled".
8675
                    list($class, $classfile) = explode(':', $param_list[0]);
8676
                    if (file_exists(dol_buildpath(dirname(dirname($classfile)) . '/card.php'))) {
8677
                        $url_path = dol_buildpath(dirname(dirname($classfile)) . '/card.php', 1);
8678
                    } else {
8679
                        $url_path = dol_buildpath(dirname(dirname($classfile)) . '/' . strtolower($class) . '_card.php', 1);
8680
                    }
8681
                    $paramforthenewlink = '';
8682
                    $paramforthenewlink .= (GETPOSTISSET('action') ? '&action=' . GETPOST('action', 'aZ09') : '');
8683
                    $paramforthenewlink .= (GETPOSTISSET('id') ? '&id=' . GETPOSTINT('id') : '');
8684
                    $paramforthenewlink .= (GETPOSTISSET('origin') ? '&origin=' . GETPOST('origin', 'aZ09') : '');
8685
                    $paramforthenewlink .= (GETPOSTISSET('originid') ? '&originid=' . GETPOSTINT('originid') : '');
8686
                    $paramforthenewlink .= '&fk_' . strtolower($class) . '=--IDFORBACKTOPAGE--';
8687
                    // TODO Add JavaScript code to add input fields already filled into $paramforthenewlink so we won't loose them when going back to main page
8688
                    $out .= '<a class="butActionNew" title="' . $langs->trans("New") . '" href="' . $url_path . '?action=create&backtopage=' . urlencode($_SERVER['PHP_SELF'] . ($paramforthenewlink ? '?' . $paramforthenewlink : '')) . '"><span class="fa fa-plus-circle valignmiddle"></span></a>';
8689
                }
8690
            }
8691
        } elseif ($type == 'password') {
8692
            // If prefix is 'search_', field is used as a filter, we use a common text field.
8693
            if ($keyprefix . $key . $keysuffix == 'pass_crypted') {
8694
                $out = '<input type="' . ($keyprefix == 'search_' ? 'text' : 'password') . '" class="flat ' . $morecss . '" name="pass" id="pass" value="" ' . ($moreparam ? $moreparam : '') . '>';
8695
                $out .= '<input type="hidden" name="pass_crypted" id="pass_crypted" value="' . $value . '" ' . ($moreparam ? $moreparam : '') . '>';
8696
            } else {
8697
                $out = '<input type="' . ($keyprefix == 'search_' ? 'text' : 'password') . '" class="flat ' . $morecss . '" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" value="' . $value . '" ' . ($moreparam ? $moreparam : '') . '>';
8698
            }
8699
        } elseif ($type == 'array') {
8700
            $newval = $val;
8701
            $newval['type'] = 'varchar(256)';
8702
8703
            $out = '';
8704
            if (!empty($value)) {
8705
                foreach ($value as $option) {
8706
                    $out .= '<span><a class="' . dol_escape_htmltag($keyprefix . $key . $keysuffix) . '_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
8707
                    $out .= $this->showInputField($newval, $keyprefix . $key . $keysuffix . '[]', $option, $moreparam, '', '', $morecss) . '<br></span>';
8708
                }
8709
            }
8710
            $out .= '<a id="' . dol_escape_htmltag($keyprefix . $key . $keysuffix) . '_add" href="javascript:;"><span class="fa fa-plus-circle valignmiddle"></span></a>';
8711
8712
            $newInput = '<span><a class="' . dol_escape_htmltag($keyprefix . $key . $keysuffix) . '_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
8713
            $newInput .= $this->showInputField($newval, $keyprefix . $key . $keysuffix . '[]', '', $moreparam, '', '', $morecss) . '<br></span>';
8714
8715
            if (!empty($conf->use_javascript_ajax)) {
8716
                $out .= '
8717
					<script nonce="' . getNonce() . '">
8718
					$(document).ready(function() {
8719
						$("a#' . dol_escape_js($keyprefix . $key . $keysuffix) . '_add").click(function() {
8720
							$("' . dol_escape_js($newInput) . '").insertBefore(this);
8721
						});
8722
8723
						$(document).on("click", "a.' . dol_escape_js($keyprefix . $key . $keysuffix) . '_del", function() {
8724
							$(this).parent().remove();
8725
						});
8726
					});
8727
					</script>';
8728
            }
8729
        }
8730
        if (!empty($hidden)) {
8731
            $out = '<input type="hidden" value="' . $value . '" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '"/>';
8732
        }
8733
8734
        if ($isDependList == 1) {
8735
            $out .= $this->getJSListDependancies('_common');
8736
        }
8737
        /* Add comments
8738
         if ($type == 'date') $out.=' (YYYY-MM-DD)';
8739
         elseif ($type == 'datetime') $out.=' (YYYY-MM-DD HH:MM:SS)';
8740
         */
8741
8742
        // Display error message for field
8743
        if (!empty($fieldValidationErrorMsg) && function_exists('getFieldErrorIcon')) {
8744
            $out .= ' ' . getFieldErrorIcon($fieldValidationErrorMsg);
8745
        }
8746
8747
        return $out;
8748
    }
8749
8750
    /**
8751
     * get field error message
8752
     *
8753
     * @param string $fieldKey Key of attribute
8754
     * @return string                       Error message of validation ('' if no error)
8755
     */
8756
    public function getFieldError($fieldKey)
8757
    {
8758
        if (!empty($this->validateFieldsErrors[$fieldKey])) {
8759
            return $this->validateFieldsErrors[$fieldKey];
8760
        }
8761
        return '';
8762
    }
8763
8764
    /**
8765
     * @param string $type Type for prefix
8766
     * @return  string          JavaScript code to manage dependency
8767
     */
8768
    public function getJSListDependancies($type = '_extra')
8769
    {
8770
        $out = '
8771
					<script nonce="' . getNonce() . '">
8772
					jQuery(document).ready(function() {
8773
						function showOptions' . $type . '(child_list, parent_list, orig_select)
8774
						{
8775
							var val = $("select[name=\""+parent_list+"\"]").val();
8776
							var parentVal = parent_list + ":" + val;
8777
							if(typeof val == "string"){
8778
								if(val != "") {
8779
									var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
8780
									$("select[name=\""+child_list+"\"] option[parent]").remove();
8781
									$("select[name=\""+child_list+"\"]").append(options);
8782
								} else {
8783
									var options = orig_select.find("option[parent]").clone();
8784
									$("select[name=\""+child_list+"\"] option[parent]").remove();
8785
									$("select[name=\""+child_list+"\"]").append(options);
8786
								}
8787
							} else if(val > 0) {
8788
								var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
8789
								$("select[name=\""+child_list+"\"] option[parent]").remove();
8790
								$("select[name=\""+child_list+"\"]").append(options);
8791
							} else {
8792
								var options = orig_select.find("option[parent]").clone();
8793
								$("select[name=\""+child_list+"\"] option[parent]").remove();
8794
								$("select[name=\""+child_list+"\"]").append(options);
8795
							}
8796
						}
8797
						function setListDependencies' . $type . '() {
8798
							jQuery("select option[parent]").parent().each(function() {
8799
								var orig_select = {};
8800
								var child_list = $(this).attr("name");
8801
								orig_select[child_list] = $(this).clone();
8802
								var parent = $(this).find("option[parent]:first").attr("parent");
8803
								var infos = parent.split(":");
8804
								var parent_list = infos[0];
8805
8806
								//Hide daughters lists
8807
								if ($("#"+child_list).val() == 0 && $("#"+parent_list).val() == 0){
8808
									$("#"+child_list).hide();
8809
								//Show mother lists
8810
								} else if ($("#"+parent_list).val() != 0){
8811
									$("#"+parent_list).show();
8812
								}
8813
								//Show the child list if the parent list value is selected
8814
								$("select[name=\""+parent_list+"\"]").click(function() {
8815
									if ($(this).val() != 0){
8816
										$("#"+child_list).show()
8817
									}
8818
								});
8819
8820
								//When we change parent list
8821
								$("select[name=\""+parent_list+"\"]").change(function() {
8822
									showOptions' . $type . '(child_list, parent_list, orig_select[child_list]);
8823
									//Select the value 0 on child list after a change on the parent list
8824
									$("#"+child_list).val(0).trigger("change");
8825
									//Hide child lists if the parent value is set to 0
8826
									if ($(this).val() == 0){
8827
								   		$("#"+child_list).hide();
8828
									}
8829
								});
8830
							});
8831
						}
8832
8833
						setListDependencies' . $type . '();
8834
					});
8835
					</script>' . "\n";
8836
        return $out;
8837
    }
8838
8839
8840
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
8841
8842
    /**
8843
     * Get buy price to use for margin calculation. This function is called when buy price is unknown.
8844
     *   Set buy price = sell price if ForceBuyingPriceIfNull configured,
8845
     *   elseif calculation MARGIN_TYPE = 'costprice' and costprice is defined, use costprice as buyprice
8846
     *   elseif calculation MARGIN_TYPE = 'pmp' and pmp is calculated, use pmp as buyprice
8847
     *   else set min buy price as buy price
8848
     *
8849
     * @param float $unitPrice Product unit price
8850
     * @param float $discountPercent Line discount percent
8851
     * @param int $fk_product Product id
8852
     * @return float|int<-1,-1>             Return buy price if OK, integer <0 if KO
8853
     */
8854
    public function defineBuyPrice($unitPrice = 0.0, $discountPercent = 0.0, $fk_product = 0)
8855
    {
8856
        global $conf;
8857
8858
        $buyPrice = 0;
8859
8860
        if (($unitPrice > 0) && (isset($conf->global->ForceBuyingPriceIfNull) && getDolGlobalInt('ForceBuyingPriceIfNull') > 0)) {
8861
            // When ForceBuyingPriceIfNull is set
8862
            $buyPrice = $unitPrice * (1 - $discountPercent / 100);
8863
        } else {
8864
            // Get cost price for margin calculation
8865
            if (!empty($fk_product) && $fk_product > 0) {
8866
                $result = 0;
8867
                if (getDolGlobalString('MARGIN_TYPE') == 'costprice') {
8868
                    require_once constant('DOL_DOCUMENT_ROOT') . '/product/class/product.class.php';
8869
                    $product = new Product($this->db);
8870
                    $result = $product->fetch($fk_product);
8871
                    if ($result <= 0) {
8872
                        $this->errors[] = 'ErrorProductIdDoesNotExists';
8873
                        return -1;
8874
                    }
8875
                    if ($product->cost_price > 0) {
8876
                        $buyPrice = $product->cost_price;
8877
                    } elseif ($product->pmp > 0) {
8878
                        $buyPrice = $product->pmp;
8879
                    }
8880
                } elseif (getDolGlobalString('MARGIN_TYPE') == 'pmp') {
8881
                    require_once constant('DOL_DOCUMENT_ROOT') . '/product/class/product.class.php';
8882
                    $product = new Product($this->db);
8883
                    $result = $product->fetch($fk_product);
8884
                    if ($result <= 0) {
8885
                        $this->errors[] = 'ErrorProductIdDoesNotExists';
8886
                        return -1;
8887
                    }
8888
                    if ($product->pmp > 0) {
8889
                        $buyPrice = $product->pmp;
8890
                    }
8891
                }
8892
8893
                if (empty($buyPrice) && isset($conf->global->MARGIN_TYPE) && in_array($conf->global->MARGIN_TYPE, array('1', 'pmp', 'costprice'))) {
8894
                    require_once constant('DOL_DOCUMENT_ROOT') . '/fourn/class/fournisseur.product.class.php';
8895
                    $productFournisseur = new ProductFournisseur($this->db);
0 ignored issues
show
Bug introduced by
The type Dolibarr\Core\Base\ProductFournisseur was not found. Did you mean ProductFournisseur? If so, make sure to prefix the type with \.
Loading history...
8896
                    if (($result = $productFournisseur->find_min_price_product_fournisseur($fk_product)) > 0) {
8897
                        $buyPrice = $productFournisseur->fourn_unitprice;
8898
                    } elseif ($result < 0) {
8899
                        $this->errors[] = $productFournisseur->error;
8900
                        return -2;
8901
                    }
8902
                }
8903
            }
8904
        }
8905
        return $buyPrice;
8906
    }
8907
8908
    /**
8909
     * Function used to get the logos or photos of an object
8910
     *
8911
     * @param string $modulepart Module part
8912
     * @param string $imagesize Image size ('', 'mini' or 'small')
8913
     * @return  array{dir:string,file:string,originalfile:string,altfile:string,email:string,capture:string}    Array of data to show photo
8914
     */
8915
    public function getDataToShowPhoto($modulepart, $imagesize)
8916
    {
8917
        // See getDataToShowPhoto() implemented by Product for example.
8918
        return array('dir' => '', 'file' => '', 'originalfile' => '', 'altfile' => '', 'email' => '', 'capture' => '');
8919
    }
8920
8921
    /**
8922
     *  Show photos of an object (nbmax maximum), into several columns
8923
     *
8924
     * @param string $modulepart 'product', 'ticket', ...
8925
     * @param string $sdir Directory to scan (full absolute path)
8926
     * @param int<0,1>|''|'small' $size 0 or ''=original size, 1 or 'small'=use thumbnail if possible
8927
     * @param int $nbmax Nombre maximum de photos (0=pas de max)
8928
     * @param int $nbbyrow Number of image per line or -1 to use div separator or 0 to use no separator. Used only if size=1 or 'small'.
8929
     * @param int $showfilename 1=Show filename
8930
     * @param int $showaction 1=Show icon with action links (resize, delete)
8931
     * @param int $maxHeight Max height of original image when size='small' (so we can use original even if small requested). If 0, always use 'small' thumb image.
8932
     * @param int $maxWidth Max width of original image when size='small'
8933
     * @param int $nolink Do not add a href link to view enlarged imaged into a new tab
8934
     * @param int|string $overwritetitle Do not add title tag on image
8935
     * @param int $usesharelink Use the public shared link of image (if not available, the 'nophoto' image will be shown instead)
8936
     * @param string $cache A string if we want to use a cached version of image
8937
     * @param string $addphotorefcss Add CSS to img of photos
8938
     * @return     string                                  Html code to show photo. Number of photos shown is saved in this->nbphoto
8939
     */
8940
    public function show_photos($modulepart, $sdir, $size = 0, $nbmax = 0, $nbbyrow = 5, $showfilename = 0, $showaction = 0, $maxHeight = 120, $maxWidth = 160, $nolink = 0, $overwritetitle = 0, $usesharelink = 0, $cache = '', $addphotorefcss = 'photoref')
8941
    {
8942
        // phpcs:enable
8943
        global $user, $langs;
8944
8945
        include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
8946
        include_once DOL_DOCUMENT_ROOT . '/core/lib/images.lib.php';
8947
8948
        $sortfield = 'position_name';
8949
        $sortorder = 'asc';
8950
8951
        $dir = $sdir . '/';
8952
        $pdir = '/';
8953
8954
        $dir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
8955
        $pdir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
8956
8957
        // For backward compatibility
8958
        if ($modulepart == 'product') {
8959
            if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
8960
                $dir = $sdir . '/' . get_exdir($this->id, 2, 0, 0, $this, $modulepart) . $this->id . "/photos/";
8961
                $pdir = '/' . get_exdir($this->id, 2, 0, 0, $this, $modulepart) . $this->id . "/photos/";
8962
            }
8963
        }
8964
        if ($modulepart == 'category') {
8965
            $dir = $sdir . '/' . get_exdir($this->id, 2, 0, 0, $this, $modulepart) . $this->id . "/photos/";
8966
            $pdir = '/' . get_exdir($this->id, 2, 0, 0, $this, $modulepart) . $this->id . "/photos/";
8967
        }
8968
8969
        // Defined relative dir to DOL_DATA_ROOT
8970
        $relativedir = '';
8971
        if ($dir) {
8972
            $relativedir = preg_replace('/^' . preg_quote(DOL_DATA_ROOT, '/') . '/', '', $dir);
8973
            $relativedir = preg_replace('/^[\\/]/', '', $relativedir);
8974
            $relativedir = preg_replace('/[\\/]$/', '', $relativedir);
8975
        }
8976
8977
        $dirthumb = $dir . 'thumbs/';
8978
        $pdirthumb = $pdir . 'thumbs/';
8979
8980
        $return = '<!-- Photo -->' . "\n";
8981
        $nbphoto = 0;
8982
8983
        $filearray = dol_dir_list($dir, "files", 0, '', '(\.meta|_preview.*\.png)$', $sortfield, (strtolower($sortorder) == 'desc' ? SORT_DESC : SORT_ASC), 1);
8984
8985
        /*if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO'))    // For backward compatibility, we scan also old dirs
8986
         {
8987
         $filearrayold=dol_dir_list($dirold,"files",0,'','(\.meta|_preview.*\.png)$',$sortfield,(strtolower($sortorder)=='desc'?SORT_DESC:SORT_ASC),1);
8988
         $filearray=array_merge($filearray, $filearrayold);
8989
         }*/
8990
8991
        completeFileArrayWithDatabaseInfo($filearray, $relativedir);
8992
8993
        if (count($filearray)) {
8994
            if ($sortfield && $sortorder) {
8995
                $filearray = dol_sort_array($filearray, $sortfield, $sortorder);
8996
            }
8997
8998
            foreach ($filearray as $key => $val) {
8999
                $photo = '';
9000
                $file = $val['name'];
9001
9002
                //if (dol_is_file($dir.$file) && image_format_supported($file) >= 0)
9003
                if (image_format_supported($file) >= 0) {
9004
                    $nbphoto++;
9005
                    $photo = $file;
9006
                    $viewfilename = $file;
9007
9008
                    if ($size == 1 || $size == 'small') {   // Format vignette
9009
                        // Find name of thumb file
9010
                        $photo_vignette = basename(getImageFileNameForSize($dir . $file, '_small'));
9011
                        if (!dol_is_file($dirthumb . $photo_vignette)) {
9012
                            // The thumb does not exists, so we will use the original file
9013
                            $dirthumb = $dir;
9014
                            $pdirthumb = $pdir;
9015
                            $photo_vignette = basename($file);
9016
                        }
9017
9018
                        // Get filesize of original file
9019
                        $imgarray = dol_getImageSize($dir . $photo);
9020
9021
                        if ($nbbyrow > 0) {
9022
                            if ($nbphoto == 1) {
9023
                                $return .= '<table class="valigntop center centpercent" style="border: 0; padding: 2px; border-spacing: 2px; border-collapse: separate;">';
9024
                            }
9025
9026
                            if ($nbphoto % $nbbyrow == 1) {
9027
                                $return .= '<tr class="center valignmiddle" style="border: 1px">';
9028
                            }
9029
                            $return .= '<td style="width: ' . ceil(100 / $nbbyrow) . '%" class="photo">' . "\n";
9030
                        } elseif ($nbbyrow < 0) {
9031
                            $return .= '<div class="inline-block">' . "\n";
9032
                        }
9033
9034
                        $relativefile = preg_replace('/^\//', '', $pdir . $photo);
9035
                        if (empty($nolink)) {
9036
                            $urladvanced = getAdvancedPreviewUrl($modulepart, $relativefile, 0, 'entity=' . $this->entity);
9037
                            if ($urladvanced) {
9038
                                $return .= '<a href="' . $urladvanced . '">';
9039
                            } else {
9040
                                $return .= '<a href="' . constant('BASE_URL') . '/viewimage.php?modulepart=' . $modulepart . '&entity=' . $this->entity . '&file=' . urlencode($pdir . $photo) . '" class="aphoto" target="_blank" rel="noopener noreferrer">';
9041
                            }
9042
                        }
9043
9044
                        // Show image (width height=$maxHeight)
9045
                        // Si fichier vignette disponible et image source trop grande, on utilise la vignette, sinon on utilise photo origine
9046
                        $alt = $langs->transnoentitiesnoconv('File') . ': ' . $relativefile;
9047
                        $alt .= ' - ' . $langs->transnoentitiesnoconv('Size') . ': ' . $imgarray['width'] . 'x' . $imgarray['height'];
9048
                        if ($overwritetitle) {
9049
                            if (is_numeric($overwritetitle)) {
9050
                                $alt = '';
9051
                            } else {
9052
                                $alt = $overwritetitle;
9053
                            }
9054
                        }
9055
                        if (empty($cache) && !empty($val['label'])) {
9056
                            // label is md5 of file
9057
                            // use it in url to say we want to cache this version of the file
9058
                            $cache = $val['label'];
9059
                        }
9060
                        if ($usesharelink) {
9061
                            if (array_key_exists('share', $val) && $val['share']) {
9062
                                if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
9063
                                    $return .= '<!-- Show original file (thumb not yet available with shared links) -->';
9064
                                    $return .= '<img class="photo photowithmargin' . ($addphotorefcss ? ' ' . $addphotorefcss : '') . '"' . ($maxHeight ? ' height="' . $maxHeight . '"' : '') . ' src="' . constant('BASE_URL') . '/viewimage.php?hashp=' . urlencode($val['share']) . ($cache ? '&cache=' . urlencode($cache) : '') . '" title="' . dol_escape_htmltag($alt) . '">';
9065
                                } else {
9066
                                    $return .= '<!-- Show original file -->';
9067
                                    $return .= '<img class="photo photowithmargin' . ($addphotorefcss ? ' ' . $addphotorefcss : '') . '" height="' . $maxHeight . '" src="' . constant('BASE_URL') . '/viewimage.php?hashp=' . urlencode($val['share']) . ($cache ? '&cache=' . urlencode($cache) : '') . '" title="' . dol_escape_htmltag($alt) . '">';
9068
                                }
9069
                            } else {
9070
                                $return .= '<!-- Show nophoto file (because file is not shared) -->';
9071
                                $return .= '<img class="photo photowithmargin' . ($addphotorefcss ? ' ' . $addphotorefcss : '') . '" height="' . $maxHeight . '" src="' . constant('BASE_URL') . '/public/theme/common/nophoto.png" title="' . dol_escape_htmltag($alt) . '">';
9072
                            }
9073
                        } else {
9074
                            if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
9075
                                $return .= '<!-- Show thumb -->';
9076
                                $return .= '<img class="photo photowithmargin' . ($addphotorefcss ? ' ' . $addphotorefcss : '') . ' maxwidth150onsmartphone maxwidth200"' . ($maxHeight ? ' height="' . $maxHeight . '"' : '') . ' src="' . constant('BASE_URL') . '/viewimage.php?modulepart=' . $modulepart . '&entity=' . $this->entity . ($cache ? '&cache=' . urlencode($cache) : '') . '&file=' . urlencode($pdirthumb . $photo_vignette) . '" title="' . dol_escape_htmltag($alt) . '">';
9077
                            } else {
9078
                                $return .= '<!-- Show original file -->';
9079
                                $return .= '<img class="photo photowithmargin' . ($addphotorefcss ? ' ' . $addphotorefcss : '') . '" height="' . $maxHeight . '" src="' . constant('BASE_URL') . '/viewimage.php?modulepart=' . $modulepart . '&entity=' . $this->entity . ($cache ? '&cache=' . urlencode($cache) : '') . '&file=' . urlencode($pdir . $photo) . '" title="' . dol_escape_htmltag($alt) . '">';
9080
                            }
9081
                        }
9082
9083
                        if (empty($nolink)) {
9084
                            $return .= '</a>';
9085
                        }
9086
9087
                        if ($showfilename) {
9088
                            $return .= '<br>' . $viewfilename;
9089
                        }
9090
                        if ($showaction) {
9091
                            $return .= '<br>';
9092
                            // If $photo_vignette set, we add a link to generate thumbs if file is an image and width or height higher than limits
9093
                            if ($photo_vignette && (image_format_supported($photo) > 0) && ((isset($imgarray['width']) && $imgarray['width'] > $maxWidth) || (isset($imgarray['width']) && $imgarray['width'] > $maxHeight))) {
9094
                                $return .= '<a href="' . $_SERVER["PHP_SELF"] . '?id=' . $this->id . '&action=addthumb&token=' . newToken() . '&file=' . urlencode($pdir . $viewfilename) . '">' . img_picto($langs->trans('GenerateThumb'), 'refresh') . '&nbsp;&nbsp;</a>';
9095
                            }
9096
                            // Special case for product
9097
                            if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
9098
                                // Link to resize
9099
                                $return .= '<a href="' . constant('BASE_URL') . '/core/photos_resize.php?modulepart=' . urlencode('produit|service') . '&id=' . $this->id . '&file=' . urlencode($pdir . $viewfilename) . '" title="' . dol_escape_htmltag($langs->trans("Resize")) . '">' . img_picto($langs->trans("Resize"), 'resize', '') . '</a> &nbsp; ';
9100
9101
                                // Link to delete
9102
                                $return .= '<a href="' . $_SERVER["PHP_SELF"] . '?id=' . $this->id . '&action=delete&token=' . newToken() . '&file=' . urlencode($pdir . $viewfilename) . '">';
9103
                                $return .= img_delete() . '</a>';
9104
                            }
9105
                        }
9106
                        $return .= "\n";
9107
9108
                        if ($nbbyrow > 0) {
9109
                            $return .= '</td>';
9110
                            if (($nbphoto % $nbbyrow) == 0) {
9111
                                $return .= '</tr>';
9112
                            }
9113
                        } elseif ($nbbyrow < 0) {
9114
                            $return .= '</div>' . "\n";
9115
                        }
9116
                    }
9117
9118
                    if (empty($size)) {     // Format origine
9119
                        $return .= '<img class="photo photowithmargin" src="' . constant('BASE_URL') . '/viewimage.php?modulepart=' . $modulepart . '&entity=' . $this->entity . '&file=' . urlencode($pdir . $photo) . '">';
9120
9121
                        if ($showfilename) {
9122
                            $return .= '<br>' . $viewfilename;
9123
                        }
9124
                        if ($showaction) {
9125
                            // Special case for product
9126
                            if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
9127
                                // Link to resize
9128
                                $return .= '<a href="' . constant('BASE_URL') . '/core/photos_resize.php?modulepart=' . urlencode('produit|service') . '&id=' . $this->id . '&file=' . urlencode($pdir . $viewfilename) . '" title="' . dol_escape_htmltag($langs->trans("Resize")) . '">' . img_picto($langs->trans("Resize"), 'resize', '') . '</a> &nbsp; ';
9129
9130
                                // Link to delete
9131
                                $return .= '<a href="' . $_SERVER["PHP_SELF"] . '?id=' . $this->id . '&action=delete&token=' . newToken() . '&file=' . urlencode($pdir . $viewfilename) . '">';
9132
                                $return .= img_delete() . '</a>';
9133
                            }
9134
                        }
9135
                    }
9136
9137
                    // On continue ou on arrete de boucler ?
9138
                    if ($nbmax && $nbphoto >= $nbmax) {
9139
                        break;
9140
                    }
9141
                }
9142
            }
9143
9144
            if ($size == 1 || $size == 'small') {
9145
                if ($nbbyrow > 0) {
9146
                    // Ferme tableau
9147
                    while ($nbphoto % $nbbyrow) {
9148
                        $return .= '<td style="width: ' . ceil(100 / $nbbyrow) . '%">&nbsp;</td>';
9149
                        $nbphoto++;
9150
                    }
9151
9152
                    if ($nbphoto) {
9153
                        $return .= '</table>';
9154
                    }
9155
                }
9156
            }
9157
        }
9158
9159
        $this->nbphoto = $nbphoto;
9160
9161
        return $return;
9162
    }
9163
9164
    /**
9165
     * Function test if type is text
9166
     *
9167
     * @param array{type:string,label:string,enabled:int<0,2>|string,position:int,notnull?:int,visible:int,noteditable?:int,default?:string,index?:int,foreignkey?:string,searchall?:int,isameasure?:int,css?:string,csslist?:string,help?:string,showoncombobox?:int,disabled?:int,arrayofkeyval?:array<int,string>,comment?:string} $info Properties of field
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{type:string,label:...tring>,comment?:string} at position 12 could not be parsed: Expected '}' at position 12, but found 'int'.
Loading history...
9168
     * @return  bool            true if type text
9169
     */
9170
    public function isText($info)
9171
    {
9172
        if (is_array($info)) {
9173
            if (isset($info['type']) && $info['type'] == 'text') {
9174
                return true;
9175
            } else {
9176
                return false;
9177
            }
9178
        }
9179
        return false;
9180
    }
9181
9182
    /**
9183
     * Sets all object fields to null. Useful for example in lists, when printing multiple lines and a different object os fetched for each line.
9184
     * @return void
9185
     */
9186
    public function emtpyObjectVars()
9187
    {
9188
        foreach ($this->fields as $field => $arr) {
9189
            $this->$field = null;
9190
        }
9191
    }
9192
9193
    /**
9194
     * Create object in the database
9195
     *
9196
     * @param User $user User that creates
9197
     * @param int<0,1> $notrigger 0=launch triggers after, 1=disable triggers
9198
     * @return int<-1,max>          Return integer <0 if KO, Id of created object if OK
9199
     */
9200
    public function createCommon(User $user, $notrigger = 0)
9201
    {
9202
        global $langs;
9203
9204
        dol_syslog(get_class($this) . "::createCommon create", LOG_DEBUG);
9205
9206
        $error = 0;
9207
9208
        $now = dol_now();
9209
9210
        $fieldvalues = $this->setSaveQuery();
9211
9212
        // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
9213
9214
        if (array_key_exists('date_creation', $fieldvalues) && empty($fieldvalues['date_creation'])) {
9215
            $fieldvalues['date_creation'] = $this->db->idate($now);
9216
        }
9217
        if (array_key_exists('fk_user_creat', $fieldvalues) && !($fieldvalues['fk_user_creat'] > 0)) {
9218
            $fieldvalues['fk_user_creat'] = $user->id;
9219
            $this->fk_user_creat = $user->id;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$fk_user_creat has been deprecated: Use $user_creation_id ( Ignorable by Annotation )

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

9219
            /** @scrutinizer ignore-deprecated */ $this->fk_user_creat = $user->id;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
9220
        }
9221
        if (array_key_exists('user_creation_id', $fieldvalues) && !($fieldvalues['user_creation_id'] > 0)) {
9222
            $fieldvalues['user_creation_id'] = $user->id;
9223
            $this->user_creation_id = $user->id;
9224
        }
9225
        if (array_key_exists('pass_crypted', $fieldvalues) && property_exists($this, 'pass')) {
9226
            // @phan-suppress-next-line PhanUndeclaredProperty
9227
            $fieldvalues['pass_crypted'] = dol_hash($this->pass);
0 ignored issues
show
Bug Best Practice introduced by
The property pass does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
9228
        }
9229
        if (array_key_exists('ref', $fieldvalues)) {
9230
            $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
9231
        }
9232
9233
        unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into insert.
9234
9235
        $keys = array();
9236
        $values = array(); // Array to store string forged for SQL syntax
9237
        foreach ($fieldvalues as $k => $v) {
9238
            $keys[$k] = $k;
9239
            $value = $this->fields[$k];
9240
            // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
9241
            $values[$k] = $this->quote($v, $value); // May return string 'NULL' if $value is null
9242
        }
9243
9244
        // Clean and check mandatory
9245
        foreach ($keys as $key) {
9246
            if (!isset($this->fields[$key])) {
9247
                continue;
9248
            }
9249
            $key_fields = $this->fields[$key];
9250
9251
            // If field is an implicit foreign key field (so type = 'integer:...')
9252
            if (preg_match('/^integer:/i', $key_fields['type']) && $values[$key] == '-1') {
9253
                $values[$key] = '';
9254
            }
9255
            if (!empty($key_fields['foreignkey']) && $values[$key] == '-1') {
9256
                $values[$key] = '';
9257
            }
9258
9259
            if (isset($key_fields['notnull']) && $key_fields['notnull'] == 1 && (!isset($values[$key]) || $values[$key] === 'NULL') && (!isset($key_fields['default']) || is_null($key_fields['default']))) {
9260
                $error++;
9261
                $langs->load("errors");
9262
                dol_syslog("Mandatory field '" . $key . "' is empty and required into ->fields definition of class");
9263
                $this->errors[] = $langs->trans("ErrorFieldRequired", isset($key_fields['label']) ? $key_fields['label'] : $key);
9264
            }
9265
9266
            // If value is null and there is a default value for field @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
9267
            if (isset($key_fields['notnull']) && $key_fields['notnull'] == 1 && (!isset($values[$key]) || $values[$key] === 'NULL') && !is_null($key_fields['default'])) {
9268
                $values[$key] = $this->quote($key_fields['default'], $key_fields);
9269
            }
9270
9271
            // If field is an implicit foreign key field (so type = 'integer:...')
9272
            if (isset($key_fields['type']) && preg_match('/^integer:/i', $key_fields['type']) && empty($values[$key])) {
9273
                if (isset($key_fields['default'])) {
9274
                    $values[$key] = ((int)$key_fields['default']);
9275
                } else {
9276
                    $values[$key] = 'null';
9277
                }
9278
            }
9279
            if (!empty($key_fields['foreignkey']) && empty($values[$key])) {
9280
                $values[$key] = 'null';
9281
            }
9282
        }
9283
9284
        if ($error) {
9285
            return -1;
9286
        }
9287
9288
        $this->db->begin();
9289
9290
        if (!$error) {
9291
            $sql = "INSERT INTO " . $this->db->prefix() . $this->table_element;
9292
            $sql .= " (" . implode(", ", $keys) . ')';
9293
            $sql .= " VALUES (" . implode(", ", $values) . ")";     // $values can contains 'abc' or 123
9294
9295
            $res = $this->db->query($sql);
9296
            if (!$res) {
9297
                $error++;
9298
                if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
9299
                    $this->errors[] = "ErrorRefAlreadyExists";
9300
                } else {
9301
                    $this->errors[] = $this->db->lasterror();
9302
                }
9303
            }
9304
        }
9305
9306
        if (!$error) {
9307
            $this->id = $this->db->last_insert_id($this->db->prefix() . $this->table_element);
9308
        }
9309
9310
        // If we have a field ref with a default value of (PROV)
9311
        if (!$error) {
9312
            // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
9313
            if (array_key_exists('ref', $this->fields) && array_key_exists('notnull', $this->fields['ref']) && $this->fields['ref']['notnull'] > 0 && array_key_exists('default', $this->fields['ref']) && $this->fields['ref']['default'] == '(PROV)') {
9314
                $sql = "UPDATE " . $this->db->prefix() . $this->table_element . " SET ref = '(PROV" . ((int)$this->id) . ")' WHERE (ref = '(PROV)' OR ref = '') AND rowid = " . ((int)$this->id);
9315
                $resqlupdate = $this->db->query($sql);
9316
9317
                if ($resqlupdate === false) {
9318
                    $error++;
9319
                    $this->errors[] = $this->db->lasterror();
9320
                } else {
9321
                    $this->ref = '(PROV' . $this->id . ')';
9322
                }
9323
            }
9324
        }
9325
9326
        // Create extrafields
9327
        if (!$error) {
9328
            $result = $this->insertExtraFields();
9329
            if ($result < 0) {
9330
                $error++;
9331
            }
9332
        }
9333
9334
        // Create lines
9335
        if (!empty($this->table_element_line) && !empty($this->fk_element)) {
9336
            foreach ($this->lines as $line) {
9337
                $keyforparent = $this->fk_element;
9338
                $line->$keyforparent = $this->id;
9339
9340
                // Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
9341
                //if (! is_object($line)) $line=json_decode(json_encode($line), false);  // convert recursively array into object.
9342
                if (!is_object($line)) {
9343
                    $line = (object)$line;
9344
                }
9345
9346
                $result = 0;
9347
                if (method_exists($line, 'insert')) {
9348
                    $result = $line->insert($user, 1);
9349
                } elseif (method_exists($line, 'create')) {
9350
                    $result = $line->create($user, 1);
9351
                }
9352
                if ($result < 0) {
9353
                    $this->error = $line->error;
9354
                    $this->db->rollback();
9355
                    return -1;
9356
                }
9357
            }
9358
        }
9359
9360
        // Triggers
9361
        if (!$error && !$notrigger) {
9362
            // Call triggers
9363
            $result = $this->call_trigger(strtoupper(get_class($this)) . '_CREATE', $user);
9364
            if ($result < 0) {
9365
                $error++;
9366
            }
9367
            // End call triggers
9368
        }
9369
9370
        // Commit or rollback
9371
        if ($error) {
9372
            $this->db->rollback();
9373
            return -1;
9374
        } else {
9375
            $this->db->commit();
9376
            return $this->id;
9377
        }
9378
    }
9379
9380
    /**
9381
     * Function to return the array of data key-value from the ->fields and all the ->properties of an object.
9382
     *
9383
     * Note: $this->${field} are set by the page that make the createCommon() or the updateCommon().
9384
     * $this->${field} should be a clean and string value (so date are formatted for SQL insert).
9385
     *
9386
     * @return array<string,null|int|float|string>  Array with all values of each property to update
9387
     */
9388
    protected function setSaveQuery()
9389
    {
9390
        global $conf;
9391
9392
        $queryarray = array();
9393
        foreach ($this->fields as $field => $info) {    // Loop on definition of fields
9394
            // Depending on field type ('datetime', ...)
9395
            if ($this->isDate($info)) {
9396
                if (empty($this->{$field})) {
9397
                    $queryarray[$field] = null;
9398
                } else {
9399
                    $queryarray[$field] = $this->db->idate($this->{$field});
9400
                }
9401
            } elseif ($this->isDuration($info)) {
9402
                // $this->{$field} may be null, '', 0, '0', 123, '123'
9403
                if ((isset($this->{$field}) && $this->{$field} != '') || !empty($info['notnull'])) {
9404
                    if (!isset($this->{$field})) {
9405
                        if (!empty($info['default'])) {
9406
                            $queryarray[$field] = $info['default'];
9407
                        } else {
9408
                            $queryarray[$field] = 0;
9409
                        }
9410
                    } else {
9411
                        $queryarray[$field] = (int)$this->{$field};        // If '0', it may be set to null later if $info['notnull'] == -1
9412
                    }
9413
                } else {
9414
                    $queryarray[$field] = null;
9415
                }
9416
            } elseif ($this->isInt($info) || $this->isFloat($info)) {
9417
                if ($field == 'entity' && is_null($this->{$field})) {
9418
                    $queryarray[$field] = ((int)$conf->entity);
9419
                } else {
9420
                    // $this->{$field} may be null, '', 0, '0', 123, '123'
9421
                    if ((isset($this->{$field}) && ((string)$this->{$field}) != '') || !empty($info['notnull'])) {
9422
                        if (!isset($this->{$field})) {
9423
                            $queryarray[$field] = 0;
9424
                        } elseif ($this->isInt($info)) {
9425
                            $queryarray[$field] = (int)$this->{$field};    // If '0', it may be set to null later if $info['notnull'] == -1
9426
                        } elseif ($this->isFloat($info)) {
9427
                            $queryarray[$field] = (float)$this->{$field};  // If '0', it may be set to null later if $info['notnull'] == -1
9428
                        }
9429
                    } else {
9430
                        $queryarray[$field] = null;
9431
                    }
9432
                }
9433
            } else {
9434
                // Note: If $this->{$field} is not defined, it means there is a bug into definition of ->fields or a missing declaration of property
9435
                // We should keep the warning generated by this because it is a bug somewhere else in code, not here.
9436
                $queryarray[$field] = $this->{$field};
9437
            }
9438
9439
            if (array_key_exists('type', $info) && $info['type'] == 'timestamp' && empty($queryarray[$field])) {
9440
                unset($queryarray[$field]);
9441
            }
9442
            if (!empty($info['notnull']) && $info['notnull'] == -1 && empty($queryarray[$field])) {
9443
                $queryarray[$field] = null; // May force 0 to null
9444
            }
9445
        }
9446
9447
        return $queryarray;
9448
    }
9449
9450
    /**
9451
     * Add quote to field value if necessary
9452
     *
9453
     * @param string|int $value Value to protect
9454
     * @param array{type:string,label:string,enabled:int<0,2>|string,position:int,notnull?:int,visible:int,noteditable?:int,default?:string,index?:int,foreignkey?:string,searchall?:int,isameasure?:int,css?:string,csslist?:string,help?:string,showoncombobox?:int,disabled?:int,arrayofkeyval?:array<int,string>,comment?:string} $fieldsentry Properties of field
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{type:string,label:...tring>,comment?:string} at position 12 could not be parsed: Expected '}' at position 12, but found 'int'.
Loading history...
9455
     * @return  string|int
9456
     */
9457
    protected function quote($value, $fieldsentry)
9458
    {
9459
        if (is_null($value)) {
9460
            return 'NULL';
9461
        } elseif (preg_match('/^(int|double|real|price)/i', $fieldsentry['type'])) {
9462
            return price2num((string)$value);
9463
        } elseif (preg_match('/int$/i', $fieldsentry['type'])) {
9464
            return (int)$value;
9465
        } elseif ($fieldsentry['type'] == 'boolean') {
9466
            if ($value) {
9467
                return 'true';
9468
            } else {
9469
                return 'false';
9470
            }
9471
        } else {
9472
            return "'" . $this->db->escape($value) . "'";
9473
        }
9474
    }
9475
9476
    /**
9477
     * Load object in memory from the database
9478
     *
9479
     * @param string $morewhere More SQL filters (' AND ...')
9480
     * @param int<0,1> $noextrafields 0=Default to load extrafields, 1=No extrafields
9481
     * @return  int<-1,1>                   Return integer <0 if KO, 0 if not found, >0 if OK
9482
     */
9483
    public function fetchLinesCommon($morewhere = '', $noextrafields = 0)
9484
    {
9485
        $objectlineclassname = get_class($this) . 'Line';
9486
        if (!class_exists($objectlineclassname)) {
9487
            $this->error = 'Error, class ' . $objectlineclassname . ' not found during call of fetchLinesCommon';
9488
            return -1;
9489
        }
9490
9491
        $objectline = new $objectlineclassname($this->db);
9492
        '@phan-var-force CommonObjectLine $objectline';
9493
9494
        $sql = "SELECT " . $objectline->getFieldList('l');
9495
        $sql .= " FROM " . $this->db->prefix() . $objectline->table_element . " as l";
9496
        $sql .= " WHERE l.fk_" . $this->db->escape($this->element) . " = " . ((int)$this->id);
9497
        if ($morewhere) {
9498
            $sql .= $morewhere;
9499
        }
9500
        if (isset($objectline->fields['position'])) {
9501
            $sql .= $this->db->order('position', 'ASC');
9502
        }
9503
9504
        $resql = $this->db->query($sql);
9505
        if ($resql) {
9506
            $num_rows = $this->db->num_rows($resql);
9507
            $i = 0;
9508
            while ($i < $num_rows) {
9509
                $obj = $this->db->fetch_object($resql);
9510
                if ($obj) {
9511
                    $newline = new $objectlineclassname($this->db);
9512
                    '@phan-var-force CommonObjectLine $newline';
9513
                    $newline->setVarsFromFetchObj($obj);
9514
9515
                    // Note: extrafields load of line not yet supported
9516
                    /*
9517
                    if (empty($noextrafields)) {
9518
                        // Load extrafields of line
9519
                    }*/
9520
9521
                    $this->lines[] = $newline;
9522
                }
9523
                $i++;
9524
            }
9525
9526
            return 1;
9527
        } else {
9528
            $this->error = $this->db->lasterror();
9529
            $this->errors[] = $this->error;
9530
            return -1;
9531
        }
9532
    }
9533
9534
    /**
9535
     * Update object into database
9536
     *
9537
     * @param User $user User that modifies
9538
     * @param int<0,1> $notrigger 0=launch triggers after, 1=disable triggers
9539
     * @return int<-1,1>            Return integer <0 if KO, >0 if OK
9540
     */
9541
    public function updateCommon(User $user, $notrigger = 0)
9542
    {
9543
        dol_syslog(get_class($this) . "::updateCommon update", LOG_DEBUG);
9544
9545
        $error = 0;
9546
9547
        $now = dol_now();
9548
9549
        // $this->oldcopy should have been set by the caller of update
9550
        //if (empty($this->oldcopy)) {
9551
        //  dol_syslog("this->oldcopy should have been set by the caller of update (here properties were already modified)", LOG_WARNING);
9552
        //  $this->oldcopy = dol_clone($this, 2);
9553
        //}
9554
9555
        $fieldvalues = $this->setSaveQuery();
9556
9557
        // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
9558
9559
        if (array_key_exists('date_modification', $fieldvalues) && empty($fieldvalues['date_modification'])) {
9560
            $fieldvalues['date_modification'] = $this->db->idate($now);
9561
        }
9562
        if (array_key_exists('fk_user_modif', $fieldvalues) && !($fieldvalues['fk_user_modif'] > 0)) {
9563
            $fieldvalues['fk_user_modif'] = $user->id;
9564
        }
9565
        if (array_key_exists('user_modification_id', $fieldvalues) && !($fieldvalues['user_modification_id'] > 0)) {
9566
            $fieldvalues['user_modification_id'] = $user->id;
9567
        }
9568
        if (array_key_exists('ref', $fieldvalues)) {
9569
            $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
9570
        }
9571
9572
        unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into update.
9573
9574
        // Add quotes and escape on fields with type string
9575
        $keys = array();
9576
        $values = array();
9577
        $tmp = array();
9578
        foreach ($fieldvalues as $k => $v) {
9579
            $keys[$k] = $k;
9580
            $value = $this->fields[$k];
9581
            // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
9582
            $values[$k] = $this->quote($v, $value);
9583
            // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
9584
            $tmp[] = $k . '=' . $this->quote($v, $this->fields[$k]);
9585
        }
9586
9587
        // Clean and check mandatory fields
9588
        foreach ($keys as $key) {
9589
            if (preg_match('/^integer:/i', $this->fields[$key]['type']) && $values[$key] == '-1') {
9590
                $values[$key] = ''; // This is an implicit foreign key field
9591
            }
9592
            if (!empty($this->fields[$key]['foreignkey']) && $values[$key] == '-1') {
9593
                $values[$key] = ''; // This is an explicit foreign key field
9594
            }
9595
9596
            //var_dump($key.'-'.$values[$key].'-'.($this->fields[$key]['notnull'] == 1));
9597
            /*
9598
            if ($this->fields[$key]['notnull'] == 1 && empty($values[$key]))
9599
            {
9600
                $error++;
9601
                $this->errors[]=$langs->trans("ErrorFieldRequired", $this->fields[$key]['label']);
9602
            }*/
9603
        }
9604
9605
        $sql = 'UPDATE ' . $this->db->prefix() . $this->table_element . ' SET ' . implode(', ', $tmp) . ' WHERE rowid=' . ((int)$this->id);
9606
9607
        $this->db->begin();
9608
9609
        if (!$error) {
9610
            $res = $this->db->query($sql);
9611
            if (!$res) {
9612
                $error++;
9613
                $this->errors[] = $this->db->lasterror();
9614
            }
9615
        }
9616
9617
        // Update extrafield
9618
        if (!$error) {
9619
            $result = $this->insertExtraFields();   // This delete and reinsert extrafields
9620
            if ($result < 0) {
9621
                $error++;
9622
            }
9623
        }
9624
9625
        // Triggers
9626
        if (!$error && !$notrigger) {
9627
            // Call triggers
9628
            $result = $this->call_trigger(strtoupper(get_class($this)) . '_MODIFY', $user);
9629
            if ($result < 0) {
9630
                $error++;
9631
            } //Do also here what you must do to rollback action if trigger fail
9632
            // End call triggers
9633
        }
9634
9635
        // Commit or rollback
9636
        if ($error) {
9637
            $this->db->rollback();
9638
            return -1;
9639
        } else {
9640
            $this->db->commit();
9641
            return $this->id;
9642
        }
9643
    }
9644
9645
    /**
9646
     * Delete object in database
9647
     *
9648
     * @param User $user User that deletes
9649
     * @param int<0,1> $notrigger 0=launch triggers after, 1=disable triggers
9650
     * @param int<0,1> $forcechilddeletion 0=no, 1=Force deletion of children
9651
     * @return  int<-1,1>                       Return integer <0 if KO, 0=Nothing done because object has child, >0 if OK
9652
     */
9653
    public function deleteCommon(User $user, $notrigger = 0, $forcechilddeletion = 0)
9654
    {
9655
        dol_syslog(get_class($this) . "::deleteCommon delete", LOG_DEBUG);
9656
9657
        $error = 0;
9658
9659
        $this->db->begin();
9660
9661
        if ($forcechilddeletion) {  // Force also delete of childtables that should lock deletion in standard case when option force is off
9662
            foreach ($this->childtables as $table) {
9663
                $sql = "DELETE FROM " . $this->db->prefix() . $table . " WHERE " . $this->fk_element . " = " . ((int)$this->id);
9664
                $resql = $this->db->query($sql);
9665
                if (!$resql) {
9666
                    $this->error = $this->db->lasterror();
9667
                    $this->errors[] = $this->error;
9668
                    $this->db->rollback();
9669
                    return -1;
9670
                }
9671
            }
9672
        } elseif (!empty($this->childtables)) { // If object has children linked with a foreign key field, we check all child tables.
9673
            $objectisused = $this->isObjectUsed($this->id);
9674
            if (!empty($objectisused)) {
9675
                dol_syslog(get_class($this) . "::deleteCommon Can't delete record as it has some child", LOG_WARNING);
9676
                $this->error = 'ErrorRecordHasChildren';
9677
                $this->errors[] = $this->error;
9678
                $this->db->rollback();
9679
                return 0;
9680
            }
9681
        }
9682
9683
        // Delete cascade first
9684
        if (is_array($this->childtablesoncascade) && !empty($this->childtablesoncascade)) {
9685
            foreach ($this->childtablesoncascade as $tabletodelete) {
9686
                $deleteFromObject = explode(':', $tabletodelete, 4);
9687
                if (count($deleteFromObject) >= 2) {
9688
                    $className = str_replace('@', '', $deleteFromObject[0]);
9689
                    $filePath = $deleteFromObject[1];
9690
                    $columnName = $deleteFromObject[2];
9691
                    $filter = '';
9692
                    if (!empty($deleteFromObject[3])) {
9693
                        $filter = $deleteFromObject[3];
9694
                    }
9695
                    if (dol_include_once($filePath)) {
9696
                        $childObject = new $className($this->db);
9697
                        if (method_exists($childObject, 'deleteByParentField')) {
9698
                            '@phan-var-force CommonObject $childObject';
9699
                            $result = $childObject->deleteByParentField($this->id, $columnName, $filter);
9700
                            if ($result < 0) {
9701
                                $error++;
9702
                                $this->errors[] = $childObject->error;
9703
                                break;
9704
                            }
9705
                        } else {
9706
                            $error++;
9707
                            $this->errors[] = "You defined a cascade delete on an object $className/$this->id but there is no method deleteByParentField for it";
9708
                            break;
9709
                        }
9710
                    } else {
9711
                        $error++;
9712
                        $this->errors[] = 'Cannot include child class file ' . $filePath;
9713
                        break;
9714
                    }
9715
                } else {
9716
                    // Delete record in child table
9717
                    $sql = "DELETE FROM " . $this->db->prefix() . $tabletodelete . " WHERE " . $this->fk_element . " = " . ((int)$this->id);
9718
9719
                    $resql = $this->db->query($sql);
9720
                    if (!$resql) {
9721
                        $error++;
9722
                        $this->error = $this->db->lasterror();
9723
                        $this->errors[] = $this->error;
9724
                        break;
9725
                    }
9726
                }
9727
            }
9728
        }
9729
9730
        if (!$error) {
9731
            if (!$notrigger) {
9732
                // Call triggers
9733
                $result = $this->call_trigger(strtoupper(get_class($this)) . '_DELETE', $user);
9734
                if ($result < 0) {
9735
                    $error++;
9736
                } // Do also here what you must do to rollback action if trigger fail
9737
                // End call triggers
9738
            }
9739
        }
9740
9741
        // Delete llx_ecm_files
9742
        if (!$error) {
9743
            $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
9744
            if (!$res) {
9745
                $error++;
9746
            }
9747
        }
9748
9749
        // Delete linked object
9750
        $res = $this->deleteObjectLinked();
9751
        if ($res < 0) {
9752
            $error++;
9753
        }
9754
9755
        if (!$error && !empty($this->isextrafieldmanaged)) {
9756
            $result = $this->deleteExtraFields();
9757
            if ($result < 0) {
9758
                $error++;
9759
            }
9760
        }
9761
9762
        if (!$error) {
9763
            $sql = 'DELETE FROM ' . $this->db->prefix() . $this->table_element . ' WHERE rowid=' . ((int)$this->id);
9764
9765
            $resql = $this->db->query($sql);
9766
            if (!$resql) {
9767
                $error++;
9768
                $this->errors[] = $this->db->lasterror();
9769
            }
9770
        }
9771
9772
        // Commit or rollback
9773
        if ($error) {
9774
            $this->db->rollback();
9775
            return -1;
9776
        } else {
9777
            $this->db->commit();
9778
            return 1;
9779
        }
9780
    }
9781
9782
    /**
9783
     *  Function to check if an object is used by others (by children).
9784
     *  Check is done into this->childtables. There is no check into llx_element_element.
9785
     *
9786
     * @param int $id Force id of object
9787
     * @param int $entity Force entity to check
9788
     * @return int                 Return integer <0 if KO, 0 if not used, >0 if already used
9789
     */
9790
    public function isObjectUsed($id = 0, $entity = 0)
9791
    {
9792
        global $langs;
9793
9794
        if (empty($id)) {
9795
            $id = $this->id;
9796
        }
9797
9798
        // Check parameters
9799
        if (!isset($this->childtables) || !is_array($this->childtables) || count($this->childtables) == 0) {
9800
            dol_print_error(null, 'Called isObjectUsed on a class with property this->childtables not defined');
9801
            return -1;
9802
        }
9803
9804
        $arraytoscan = $this->childtables;      // array('tablename'=>array('fk_element'=>'parentfield'), ...) or array('tablename'=>array('parent'=>table_parent, 'parentkey'=>'nameoffieldforparentfkkey'), ...)
9805
        // For backward compatibility, we check if array is old format array('tablename1', 'tablename2', ...)
9806
        $tmparray = array_keys($this->childtables);
9807
        if (is_numeric($tmparray[0])) {
9808
            $arraytoscan = array_flip($this->childtables);
9809
        }
9810
9811
        // Test if child exists
9812
        $haschild = 0;
9813
        foreach ($arraytoscan as $table => $element) {
9814
            //print $id.'-'.$table.'-'.$elementname.'<br>';
9815
            // Check if element can be deleted
9816
            $sql = "SELECT COUNT(*) as nb";
9817
            $sql .= " FROM " . $this->db->prefix() . $table . " as c";
9818
            if (!empty($element['parent']) && !empty($element['parentkey'])) {
9819
                $sql .= ", " . $this->db->prefix() . $element['parent'] . " as p";
9820
            }
9821
            if (!empty($element['fk_element'])) {
9822
                $sql .= " WHERE c." . $element['fk_element'] . " = " . ((int)$id);
9823
            } else {
9824
                $sql .= " WHERE c." . $this->fk_element . " = " . ((int)$id);
9825
            }
9826
            if (!empty($element['parent']) && !empty($element['parentkey'])) {
9827
                $sql .= " AND c." . $element['parentkey'] . " = p.rowid";
9828
            }
9829
            if (!empty($element['parent']) && !empty($element['parenttypefield']) && !empty($element['parenttypevalue'])) {
9830
                $sql .= " AND c." . $element['parenttypefield'] . " = '" . $this->db->escape($element['parenttypevalue']) . "'";
9831
            }
9832
            if (!empty($entity)) {
9833
                if (!empty($element['parent']) && !empty($element['parentkey'])) {
9834
                    $sql .= " AND p.entity = " . ((int)$entity);
9835
                } else {
9836
                    $sql .= " AND c.entity = " . ((int)$entity);
9837
                }
9838
            }
9839
9840
            $resql = $this->db->query($sql);
9841
            if ($resql) {
9842
                $obj = $this->db->fetch_object($resql);
9843
                if ($obj->nb > 0) {
9844
                    $langs->load("errors");
9845
                    //print 'Found into table '.$table.', type '.$langs->transnoentitiesnoconv($elementname).', haschild='.$haschild;
9846
                    $haschild += $obj->nb;
9847
                    if (is_numeric($element)) { // very old usage array('table1', 'table2', ...)
9848
                        $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $table);
9849
                    } elseif (is_string($element)) { // old usage array('table1' => 'TranslateKey1', 'table2' => 'TranslateKey2', ...)
9850
                        $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element));
9851
                    } else { // new usage: $element['name']=Translation key
9852
                        $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element['name']));
9853
                    }
9854
                    break; // We found at least one, we stop here
9855
                }
9856
            } else {
9857
                $this->errors[] = $this->db->lasterror();
9858
                return -1;
9859
            }
9860
        }
9861
        if ($haschild > 0) {
9862
            $this->errors[] = "ErrorRecordHasChildren";
9863
            return $haschild;
9864
        } else {
9865
            return 0;
9866
        }
9867
    }
9868
9869
    /**
9870
     * Delete all child object from a parent ID
9871
     *
9872
     * @param int $parentId Parent Id
9873
     * @param string $parentField Name of Foreign key parent column
9874
     * @param string $filter Filter as an Universal Search string.
9875
     *                                      Example: '((client:=:1) OR ((client:>=:2) AND (client:<=:3))) AND (client:!=:8) AND (nom:like:'a%')'
9876
     * @param string $filtermode No more used
9877
     * @return  int                         Return integer <0 if KO, >0 if OK
9878
     * @throws  Exception
9879
     */
9880
    public function deleteByParentField($parentId = 0, $parentField = '', $filter = '', $filtermode = "AND")
9881
    {
9882
        global $user;
9883
9884
        $error = 0;
9885
        $deleted = 0;
9886
9887
        if (!empty($parentId) && !empty($parentField)) {
9888
            $this->db->begin();
9889
9890
            $sql = "SELECT rowid FROM " . $this->db->prefix() . $this->table_element;
9891
            $sql .= " WHERE " . $this->db->sanitize($parentField) . " = " . (int)$parentId;
9892
9893
            // Manage filter
9894
            $errormessage = '';
9895
            $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
9896
            if ($errormessage) {
9897
                $this->errors[] = $errormessage;
9898
                dol_syslog(__METHOD__ . ' ' . implode(',', $this->errors), LOG_ERR);
9899
                return -1;
9900
            }
9901
9902
            $resql = $this->db->query($sql);
9903
            if (!$resql) {
9904
                $this->errors[] = $this->db->lasterror();
9905
                $error++;
9906
            } else {
9907
                while ($obj = $this->db->fetch_object($resql)) {
9908
                    $result = $this->fetch($obj->rowid);    // @phpstan-ignore-line
9909
                    if ($result < 0) {
9910
                        $error++;
9911
                        $this->errors[] = $this->error;
9912
                    } else {
9913
                        $result = $this->delete($user); // @phpstan-ignore-line
0 ignored issues
show
Bug introduced by
The method delete() does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

9913
                        /** @scrutinizer ignore-call */ 
9914
                        $result = $this->delete($user); // @phpstan-ignore-line
Loading history...
9914
                        if ($result < 0) {
9915
                            $error++;
9916
                            $this->errors[] = $this->error;
9917
                        } else {
9918
                            $deleted++;
9919
                        }
9920
                    }
9921
                }
9922
            }
9923
9924
            if (empty($error)) {
9925
                $this->db->commit();
9926
                return $deleted;
9927
            } else {
9928
                $this->error = implode(', ', $this->errors);
9929
                $this->db->rollback();
9930
                return $error * -1;
9931
            }
9932
        }
9933
9934
        return $deleted;
9935
    }
9936
9937
    /**
9938
     * Delete related files of object in database
9939
     *
9940
     * @param integer $mode 0=Use path to find record, 1=Use src_object_xxx fields (Mode 1 is recommended for new objects)
9941
     * @return  bool                    True if OK, False if KO
9942
     */
9943
    public function deleteEcmFiles($mode = 0)
9944
    {
9945
        global $conf;
9946
9947
        $this->db->begin();
9948
9949
        // Delete in database with mode 0
9950
        if ($mode == 0) {
9951
            switch ($this->element) {
9952
                case 'propal':
9953
                    $element = 'propale';
9954
                    break;
9955
                case 'product':
9956
                    $element = 'produit';
9957
                    break;
9958
                case 'order_supplier':
9959
                    $element = 'fournisseur/commande';
9960
                    break;
9961
                case 'invoice_supplier':
9962
                    // Special cases that need to use get_exdir to get real dir of object
9963
                    // In future, all object should use this to define path of documents.
9964
                    $element = 'fournisseur/facture/' . get_exdir($this->id, 2, 0, 1, $this, 'invoice_supplier');
9965
                    break;
9966
                case 'shipping':
9967
                    $element = 'expedition/sending';
9968
                    break;
9969
                case 'task':
9970
                case 'project_task':
9971
                    require_once constant('DOL_DOCUMENT_ROOT') . '/projet/class/task.class.php';
9972
9973
                    $project_result = $this->fetch_projet();
9974
                    if ($project_result >= 0) {
9975
                        $element = 'projet/' . dol_sanitizeFileName($this->project->ref) . '/';
9976
                    }
9977
                // no break
9978
                default:
9979
                    $element = $this->element;
9980
            }
9981
            '@phan-var-force string $element';
9982
9983
            // Delete ecm_files_extrafields with mode 0 (using name)
9984
            $sql = "DELETE FROM " . $this->db->prefix() . "ecm_files_extrafields WHERE fk_object IN (";
9985
            $sql .= " SELECT rowid FROM " . $this->db->prefix() . "ecm_files WHERE filename LIKE '" . $this->db->escape($this->ref) . "%'";
9986
            $sql .= " AND filepath = '" . $this->db->escape($element) . "/" . $this->db->escape($this->ref) . "' AND entity = " . ((int)$conf->entity); // No need of getEntity here
9987
            $sql .= ")";
9988
9989
            if (!$this->db->query($sql)) {
9990
                $this->error = $this->db->lasterror();
9991
                $this->db->rollback();
9992
                return false;
9993
            }
9994
9995
            // Delete ecm_files with mode 0 (using name)
9996
            $sql = "DELETE FROM " . $this->db->prefix() . "ecm_files";
9997
            $sql .= " WHERE filename LIKE '" . $this->db->escape($this->ref) . "%'";
9998
            $sql .= " AND filepath = '" . $this->db->escape($element) . "/" . $this->db->escape($this->ref) . "' AND entity = " . ((int)$conf->entity); // No need of getEntity here
9999
10000
            if (!$this->db->query($sql)) {
10001
                $this->error = $this->db->lasterror();
10002
                $this->db->rollback();
10003
                return false;
10004
            }
10005
        }
10006
10007
        // Delete in database with mode 1
10008
        if ($mode == 1) {
10009
            $sql = 'DELETE FROM ' . $this->db->prefix() . "ecm_files_extrafields";
10010
            $sql .= " WHERE fk_object IN (SELECT rowid FROM " . $this->db->prefix() . "ecm_files WHERE src_object_type = '" . $this->db->escape($this->table_element . (empty($this->module) ? "" : "@" . $this->module)) . "' AND src_object_id = " . ((int)$this->id) . ")";
10011
            $resql = $this->db->query($sql);
10012
            if (!$resql) {
10013
                $this->error = $this->db->lasterror();
10014
                $this->db->rollback();
10015
                return false;
10016
            }
10017
10018
            $sql = 'DELETE FROM ' . $this->db->prefix() . "ecm_files";
10019
            $sql .= " WHERE src_object_type = '" . $this->db->escape($this->table_element . (empty($this->module) ? "" : "@" . $this->module)) . "' AND src_object_id = " . ((int)$this->id);
10020
            $resql = $this->db->query($sql);
10021
            if (!$resql) {
10022
                $this->error = $this->db->lasterror();
10023
                $this->db->rollback();
10024
                return false;
10025
            }
10026
        }
10027
10028
        $this->db->commit();
10029
        return true;
10030
    }
10031
10032
    /**
10033
     *  Delete all links between an object $this
10034
     *
10035
     * @param int $sourceid Object source id
10036
     * @param string $sourcetype Object source type
10037
     * @param int $targetid Object target id
10038
     * @param string $targettype Object target type
10039
     * @param int $rowid Row id of line to delete. If defined, other parameters are not used.
10040
     * @param User $f_user User that create
10041
     * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
10042
     * @return                         int >0 if OK, <0 if KO
10043
     * @see    add_object_linked(), updateObjectLinked(), fetchObjectLinked()
10044
     */
10045
    public function deleteObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $rowid = 0, $f_user = null, $notrigger = 0)
10046
    {
10047
        global $user;
10048
        $deletesource = false;
10049
        $deletetarget = false;
10050
        $f_user = isset($f_user) ? $f_user : $user;
10051
10052
        if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
10053
            $deletesource = true;
10054
        } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
10055
            $deletetarget = true;
10056
        }
10057
10058
        $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
10059
        $sourcetype = (!empty($sourcetype) ? $sourcetype : $this->element);
10060
        $targetid = (!empty($targetid) ? $targetid : $this->id);
10061
        $targettype = (!empty($targettype) ? $targettype : $this->element);
10062
        $this->db->begin();
10063
        $error = 0;
10064
10065
        if (!$notrigger) {
10066
            // Call trigger
10067
            $this->context['link_id'] = $rowid;
10068
            $this->context['link_source_id'] = $sourceid;
10069
            $this->context['link_source_type'] = $sourcetype;
10070
            $this->context['link_target_id'] = $targetid;
10071
            $this->context['link_target_type'] = $targettype;
10072
            $result = $this->call_trigger('OBJECT_LINK_DELETE', $f_user);
10073
            if ($result < 0) {
10074
                $error++;
10075
            }
10076
            // End call triggers
10077
        }
10078
10079
        if (!$error) {
10080
            $sql = "DELETE FROM " . $this->db->prefix() . "element_element";
10081
            $sql .= " WHERE";
10082
            if ($rowid > 0) {
10083
                $sql .= " rowid = " . ((int)$rowid);
10084
            } else {
10085
                if ($deletesource) {
10086
                    $sql .= " fk_source = " . ((int)$sourceid) . " AND sourcetype = '" . $this->db->escape($sourcetype) . "'";
10087
                    $sql .= " AND fk_target = " . ((int)$this->id) . " AND targettype = '" . $this->db->escape($this->element) . "'";
10088
                } elseif ($deletetarget) {
10089
                    $sql .= " fk_target = " . ((int)$targetid) . " AND targettype = '" . $this->db->escape($targettype) . "'";
10090
                    $sql .= " AND fk_source = " . ((int)$this->id) . " AND sourcetype = '" . $this->db->escape($this->element) . "'";
10091
                } else {
10092
                    $sql .= " (fk_source = " . ((int)$this->id) . " AND sourcetype = '" . $this->db->escape($this->element) . "')";
10093
                    $sql .= " OR";
10094
                    $sql .= " (fk_target = " . ((int)$this->id) . " AND targettype = '" . $this->db->escape($this->element) . "')";
10095
                }
10096
            }
10097
10098
            dol_syslog(get_class($this) . "::deleteObjectLinked", LOG_DEBUG);
10099
            if (!$this->db->query($sql)) {
10100
                $this->error = $this->db->lasterror();
10101
                $this->errors[] = $this->error;
10102
                $error++;
10103
            }
10104
        }
10105
10106
        if (!$error) {
10107
            $this->db->commit();
10108
            return 1;
10109
        } else {
10110
            $this->db->rollback();
10111
            return 0;
10112
        }
10113
    }
10114
10115
    /**
10116
     *  Delete all extra fields values for the current object.
10117
     *
10118
     * @return int<-1,1>   Return integer <0 if KO, >0 if OK
10119
     * @see deleteExtraLanguages(), insertExtraField(), updateExtraField(), setValueFrom()
10120
     */
10121
    public function deleteExtraFields()
10122
    {
10123
        global $conf;
10124
10125
        if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
10126
            return 0;
10127
        }
10128
10129
        $this->db->begin();
10130
10131
        $table_element = $this->table_element;
10132
        if ($table_element == 'categorie') {
10133
            $table_element = 'categories'; // For compatibility
10134
        }
10135
10136
        dol_syslog(get_class($this) . "::deleteExtraFields delete", LOG_DEBUG);
10137
10138
        $sql_del = "DELETE FROM " . $this->db->prefix() . $table_element . "_extrafields WHERE fk_object = " . ((int)$this->id);
10139
10140
        $resql = $this->db->query($sql_del);
10141
        if (!$resql) {
10142
            $this->error = $this->db->lasterror();
10143
            $this->db->rollback();
10144
            return -1;
10145
        } else {
10146
            $this->db->commit();
10147
            return 1;
10148
        }
10149
    }
10150
10151
    /**
10152
     *  Delete a line of object in database
10153
     *
10154
     * @param User $user User that delete
10155
     * @param int $idline Id of line to delete
10156
     * @param int $notrigger 0=launch triggers after, 1=disable triggers
10157
     * @return int                 >0 if OK, <0 if KO
10158
     */
10159
    public function deleteLineCommon(User $user, $idline, $notrigger = 0)
10160
    {
10161
        $error = 0;
10162
10163
        $tmpforobjectclass = get_class($this);
10164
        $tmpforobjectlineclass = ucfirst($tmpforobjectclass) . 'Line';
10165
10166
        $this->db->begin();
10167
10168
        // Call trigger
10169
        $result = $this->call_trigger('LINE' . strtoupper($tmpforobjectclass) . '_DELETE', $user);
10170
        if ($result < 0) {
10171
            $error++;
10172
        }
10173
        // End call triggers
10174
10175
        if (empty($error)) {
10176
            $sql = "DELETE FROM " . $this->db->prefix() . $this->table_element_line;
10177
            $sql .= " WHERE rowid = " . ((int)$idline);
10178
10179
            $resql = $this->db->query($sql);
10180
            if (!$resql) {
10181
                $this->error = "Error " . $this->db->lasterror();
10182
                $error++;
10183
            }
10184
        }
10185
10186
        if (empty($error)) {
10187
            // Remove extrafields
10188
            $tmpobjectline = new $tmpforobjectlineclass($this->db);
10189
            '@phan-var-force CommonObjectLine $tmpobjectline';
10190
            if (!isset($tmpobjectline->isextrafieldmanaged) || !empty($tmpobjectline->isextrafieldmanaged)) {
10191
                $tmpobjectline->id = $idline;
10192
                $result = $tmpobjectline->deleteExtraFields();
10193
                if ($result < 0) {
10194
                    $error++;
10195
                    $this->error = "Error " . get_class($this) . "::deleteLineCommon deleteExtraFields error -4 " . $tmpobjectline->error;
10196
                }
10197
            }
10198
        }
10199
10200
        if (empty($error)) {
10201
            $this->db->commit();
10202
            return 1;
10203
        } else {
10204
            dol_syslog(get_class($this) . "::deleteLineCommon ERROR:" . $this->error, LOG_ERR);
10205
            $this->db->rollback();
10206
            return -1;
10207
        }
10208
    }
10209
10210
    /**
10211
     *  Set to a status
10212
     *
10213
     * @param User $user Object user that modify
10214
     * @param int $status New status to set (often a constant like self::STATUS_XXX)
10215
     * @param int $notrigger 1=Does not execute triggers, 0=Execute triggers
10216
     * @param string $triggercode Trigger code to use
10217
     * @return int                     Return integer <0 if KO, >0 if OK
10218
     */
10219
    public function setStatusCommon($user, $status, $notrigger = 0, $triggercode = '')
10220
    {
10221
        $error = 0;
10222
10223
        $this->db->begin();
10224
10225
        $statusfield = 'status';
10226
        if (in_array($this->element, array('don', 'donation', 'shipping'))) {
10227
            $statusfield = 'fk_statut';
10228
        }
10229
10230
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
10231
        $sql .= " SET " . $statusfield . " = " . ((int)$status);
10232
        $sql .= " WHERE rowid = " . ((int)$this->id);
10233
10234
        if ($this->db->query($sql)) {
10235
            if (!$error) {
10236
                $this->oldcopy = clone $this;
10237
            }
10238
10239
            if (!$error && !$notrigger) {
10240
                // Call trigger
10241
                $result = $this->call_trigger($triggercode, $user);
10242
                if ($result < 0) {
10243
                    $error++;
10244
                }
10245
            }
10246
10247
            if (!$error) {
10248
                $this->status = $status;
10249
                if (property_exists($this, 'statut')) { // For backward compatibility
10250
                    $this->statut = $status;
10251
                }
10252
                $this->db->commit();
10253
                return 1;
10254
            } else {
10255
                $this->db->rollback();
10256
                return -1;
10257
            }
10258
        } else {
10259
            $this->error = $this->db->error();
10260
            $this->db->rollback();
10261
            return -1;
10262
        }
10263
    }
10264
10265
    /**
10266
     *  Set to a signed status
10267
     *
10268
     * @param User $user Object user that modify
10269
     * @param int $status New status to set (often a constant like self::STATUS_XXX)
10270
     * @param int $notrigger 1=Does not execute triggers, 0=Execute triggers
10271
     * @param string $triggercode Trigger code to use
10272
     * @return int                     Return integer <0 if KO, >0 if OK
10273
     */
10274
    public function setSignedStatusCommon($user, $status, $notrigger = 0, $triggercode = '')
10275
    {
10276
        $error = 0;
10277
10278
        $this->db->begin();
10279
10280
        $statusfield = 'signed_status';
10281
10282
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
10283
        $sql .= " SET " . $statusfield . " = " . ((int)$status);
10284
        $sql .= " WHERE rowid = " . ((int)$this->id);
10285
10286
        if ($this->db->query($sql)) {
10287
            if (!$error) {
10288
                $this->oldcopy = clone $this;
10289
            }
10290
10291
            if (!$error && !$notrigger) {
10292
                // Call trigger
10293
                $result = $this->call_trigger($triggercode, $user);
10294
                if ($result < 0) {
10295
                    $error++;
10296
                }
10297
            }
10298
10299
            if (!$error) {
10300
                $this->status = $status;
10301
                $this->db->commit();
10302
                return 1;
10303
            } else {
10304
                $this->db->rollback();
10305
                return -1;
10306
            }
10307
        } else {
10308
            $this->error = $this->db->error();
10309
            $this->db->rollback();
10310
            return -1;
10311
        }
10312
    }
10313
10314
    /**
10315
     * Initialise object with example values
10316
     * Id must be 0 if object instance is a specimen
10317
     *
10318
     * @return int
10319
     */
10320
    public function initAsSpecimenCommon()
10321
    {
10322
        global $user;
10323
10324
        $this->id = 0;
10325
        $this->specimen = 1;
10326
        $fields = array(
10327
            'label' => 'This is label',
10328
            'ref' => 'ABCD1234',
10329
            'description' => 'This is a description',
10330
            'qty' => 123.12,
10331
            'note_public' => 'Public note',
10332
            'note_private' => 'Private note',
10333
            'date_creation' => (dol_now() - 3600 * 48),
10334
            'date_modification' => (dol_now() - 3600 * 24),
10335
            'fk_user_creat' => $user->id,
10336
            'fk_user_modif' => $user->id,
10337
            'date' => dol_now(),
10338
        );
10339
        foreach ($fields as $key => $value) {
10340
            if (array_key_exists($key, $this->fields)) {
10341
                $this->{$key} = $value;     // @phpstan-ignore-line
10342
            }
10343
        }
10344
10345
        // Force values to default values when known
10346
        if (property_exists($this, 'fields')) {
10347
            foreach ($this->fields as $key => $value) {
10348
                // If fields are already set, do nothing
10349
                if (array_key_exists($key, $fields)) {
10350
                    continue;
10351
                }
10352
10353
                if (!empty($value['default'])) {
10354
                    $this->$key = $value['default'];
10355
                }
10356
            }
10357
        }
10358
10359
        return 1;
10360
    }
10361
10362
    /**
10363
     * Load comments linked with current task
10364
     *
10365
     * @return int<0,max>|-1        Returns the number of comments if OK, -1 if error
10366
     */
10367
    public function fetchComments()
10368
    {
10369
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/class/comment.class.php';
10370
10371
        $comment = new Comment($this->db);
0 ignored issues
show
Bug introduced by
The type Dolibarr\Core\Base\Comment was not found. Did you mean Comment? If so, make sure to prefix the type with \.
Loading history...
10372
        $result = $comment->fetchAllFor($this->element, $this->id);
10373
        if ($result < 0) {
10374
            $this->errors = array_merge($this->errors, $comment->errors);
10375
            return -1;
10376
        } else {
10377
            $this->comments = $comment->comments;
10378
        }
10379
        return count($this->comments);
10380
    }
10381
10382
    /**
10383
     * Return nb comments already posted
10384
     *
10385
     * @return int
10386
     */
10387
    public function getNbComments()
10388
    {
10389
        return count($this->comments);
10390
    }
10391
10392
    /**
10393
     * Trim object parameters
10394
     *
10395
     * @param string[] $parameters array of parameters to trim
10396
     * @return void
10397
     */
10398
    public function trimParameters($parameters)
10399
    {
10400
        if (!is_array($parameters)) {
10401
            return;
10402
        }
10403
        foreach ($parameters as $parameter) {
10404
            if (isset($this->$parameter)) {
10405
                $this->$parameter = trim($this->$parameter);
10406
            }
10407
        }
10408
    }
10409
10410
    /**
10411
     * Sets object to given categories.
10412
     *
10413
     * Deletes object from existing categories not supplied.
10414
     * Adds it to non existing supplied categories.
10415
     * Existing categories are left untouch.
10416
     *
10417
     * @param string $type_categ Category type ('customer', 'supplier', 'website_page', ...)
10418
     * @return  int                         Array of category objects or < 0 if KO
10419
     */
10420
    public function getCategoriesCommon($type_categ)
10421
    {
10422
        require_once constant('DOL_DOCUMENT_ROOT') . '/categories/class/categorie.class.php';
10423
10424
        // Get current categories
10425
        $c = new Categorie($this->db);
10426
        $existing = $c->containing($this->id, $type_categ, 'id');
10427
10428
        return $existing;
10429
    }
10430
10431
    /**
10432
     * Sets object to given categories.
10433
     *
10434
     * Adds it to non existing supplied categories.
10435
     * Deletes object from existing categories not supplied (if remove_existing==true).
10436
     * Existing categories are left untouch.
10437
     *
10438
     * @param int[]|int $categories Category ID or array of Categories IDs
10439
     * @param string $type_categ Category type ('customer', 'supplier', 'website_page', ...) defined into const class Categorie type
10440
     * @param boolean $remove_existing True: Remove existings categories from Object if not supplies by $categories, False: let them
10441
     * @return  int                             Return integer <0 if KO, >0 if OK
10442
     */
10443
    public function setCategoriesCommon($categories, $type_categ = '', $remove_existing = true)
10444
    {
10445
        // Handle single category
10446
        if (!is_array($categories)) {
10447
            $categories = array($categories);
10448
        }
10449
10450
        dol_syslog(get_class($this) . "::setCategoriesCommon Object Id:" . $this->id . ' type_categ:' . $type_categ . ' nb tag add:' . count($categories), LOG_DEBUG);
10451
10452
        require_once constant('DOL_DOCUMENT_ROOT') . '/categories/class/categorie.class.php';
10453
10454
        if (empty($type_categ)) {
10455
            dol_syslog(__METHOD__ . ': Type ' . $type_categ . 'is an unknown category type. Done nothing.', LOG_ERR);
10456
            return -1;
10457
        }
10458
10459
        // Get current categories
10460
        $c = new Categorie($this->db);
10461
        $existing = $c->containing($this->id, $type_categ, 'id');
10462
        if ($remove_existing) {
10463
            // Diff
10464
            if (is_array($existing)) {
10465
                $to_del = array_diff($existing, $categories);
10466
                $to_add = array_diff($categories, $existing);
10467
            } else {
10468
                $to_del = array(); // Nothing to delete
10469
                $to_add = $categories;
10470
            }
10471
        } else {
10472
            $to_del = array(); // Nothing to delete
10473
            $to_add = array_diff($categories, $existing);
10474
        }
10475
10476
        $error = 0;
10477
        $ok = 0;
10478
10479
        // Process
10480
        foreach ($to_del as $del) {
10481
            if ($c->fetch($del) > 0) {
10482
                $result = $c->del_type($this, $type_categ);
10483
                if ($result < 0) {
10484
                    $error++;
10485
                    $this->error = $c->error;
10486
                    $this->errors = $c->errors;
10487
                    break;
10488
                } else {
10489
                    $ok += $result;
10490
                }
10491
            }
10492
        }
10493
        foreach ($to_add as $add) {
10494
            if ($c->fetch($add) > 0) {
10495
                $result = $c->add_type($this, $type_categ);
10496
                if ($result < 0) {
10497
                    $error++;
10498
                    $this->error = $c->error;
10499
                    $this->errors = $c->errors;
10500
                    break;
10501
                } else {
10502
                    $ok += $result;
10503
                }
10504
            }
10505
        }
10506
10507
        return $error ? (-1 * $error) : $ok;
10508
    }
10509
10510
10511
    /* Part for comments */
10512
10513
    /**
10514
     * Copy related categories to another object
10515
     *
10516
     * @param int $fromId Id object source
10517
     * @param int $toId Id object cible
10518
     * @param string $type Type of category ('product', ...)
10519
     * @return int      Return integer < 0 if error, > 0 if ok
10520
     */
10521
    public function cloneCategories($fromId, $toId, $type = '')
10522
    {
10523
        $this->db->begin();
10524
10525
        if (empty($type)) {
10526
            $type = $this->table_element;
10527
        }
10528
10529
        require_once constant('DOL_DOCUMENT_ROOT') . '/categories/class/categorie.class.php';
10530
        $categorystatic = new Categorie($this->db);
10531
10532
        $sql = "INSERT INTO " . $this->db->prefix() . "categorie_" . (empty($categorystatic->MAP_CAT_TABLE[$type]) ? $type : $categorystatic->MAP_CAT_TABLE[$type]) . " (fk_categorie, fk_product)";
10533
        $sql .= " SELECT fk_categorie, $toId FROM " . $this->db->prefix() . "categorie_" . (empty($categorystatic->MAP_CAT_TABLE[$type]) ? $type : $categorystatic->MAP_CAT_TABLE[$type]);
10534
        $sql .= " WHERE fk_product = " . ((int)$fromId);
10535
10536
        if (!$this->db->query($sql)) {
10537
            $this->error = $this->db->lasterror();
10538
            $this->db->rollback();
10539
            return -1;
10540
        }
10541
10542
        $this->db->commit();
10543
        return 1;
10544
    }
10545
10546
    /**
10547
     * Provide list of deprecated properties and replacements
10548
     *
10549
     * @return array<string,string>
10550
     */
10551
    protected function deprecatedProperties()
10552
    {
10553
        return array(
10554
            'alreadypaid' => 'totalpaid',
10555
            'cond_reglement' => 'depr_cond_reglement',
10556
            //'note' => 'note_private',     // Some classes needs ->note and others need ->note_public/private so we can't manage deprecation for this field with dolDeprecationHandler
10557
            'commandeFournisseur' => 'origin_object',
10558
            'expedition' => 'origin_object',
10559
            'fk_project' => 'fk_project',
10560
            'livraison' => 'origin_object',
10561
            'projet' => 'project',
10562
            'statut' => 'status',
10563
        );
10564
    }
10565
10566
    /**
10567
     * Common function for all objects extending CommonObject for generating documents
10568
     *
10569
     * @param string $modelspath Relative folder where generators are placed
10570
     * @param string $modele Generator to use. Caller must set it to from obj->model_pdf or from GETPOST for example.
10571
     * @param Translate $outputlangs Output language to use
10572
     * @param int<0,1> $hidedetails 1 to hide details. 0 by default
10573
     * @param int<0,1> $hidedesc 1 to hide product description. 0 by default
10574
     * @param int<0,1> $hideref 1 to hide product reference. 0 by default
10575
     * @param   ?array<string,mixed> $moreparams Array to provide more information
10576
     * @return  int<-1,1>               >0 if OK, <0 if KO
10577
     * @see addFileIntoDatabaseIndex()
10578
     */
10579
    protected function commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams = null)
10580
    {
10581
        global $conf, $langs, $user, $hookmanager, $action;
10582
10583
        $srctemplatepath = '';
10584
10585
        $parameters = array('modelspath' => $modelspath, 'modele' => $modele, 'outputlangs' => $outputlangs, 'hidedetails' => $hidedetails, 'hidedesc' => $hidedesc, 'hideref' => $hideref, 'moreparams' => $moreparams);
10586
        $reshook = $hookmanager->executeHooks('commonGenerateDocument', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
10587
10588
        if (!empty($reshook)) {
10589
            return $reshook;
10590
        }
10591
10592
        dol_syslog("commonGenerateDocument modele=" . $modele . " outputlangs->defaultlang=" . (is_object($outputlangs) ? $outputlangs->defaultlang : 'null'));
10593
10594
        if (empty($modele)) {
10595
            $this->error = 'BadValueForParameterModele';
10596
            return -1;
10597
        }
10598
10599
        // Increase limit for PDF build
10600
        $err = error_reporting();
10601
        error_reporting(0);
10602
        @set_time_limit(120);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for set_time_limit(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

10602
        /** @scrutinizer ignore-unhandled */ @set_time_limit(120);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
10603
        error_reporting($err);
10604
10605
        // If selected model is a filename template (then $modele="modelname" or "modelname:filename")
10606
        $tmp = explode(':', $modele, 2);
10607
        $saved_model = $modele;
10608
        if (!empty($tmp[1])) {
10609
            $modele = $tmp[0];
10610
            $srctemplatepath = $tmp[1];
10611
        }
10612
10613
        // Search template files
10614
        $file = '';
10615
        $classname = '';
10616
        $filefound = '';
10617
        $dirmodels = array('/');
10618
        if (is_array($conf->modules_parts['models'])) {
10619
            $dirmodels = array_merge($dirmodels, $conf->modules_parts['models']);
10620
        }
10621
        foreach ($dirmodels as $reldir) {
10622
            foreach (array('doc', 'pdf') as $prefix) {
10623
                if (in_array(get_class($this), array('Adherent'))) {
10624
                    // Member module use prefix_modele.class.php
10625
                    $file = $prefix . "_" . $modele . ".class.php";
10626
                } else {
10627
                    // Other module use prefix_modele.modules.php
10628
                    $file = $prefix . "_" . $modele . ".modules.php";
10629
                }
10630
10631
                $file = dol_sanitizeFileName($file);
10632
10633
                // We check if the file exists
10634
                $file = dol_buildpath($reldir . $modelspath . $file, 0);
10635
                if (file_exists($file)) {
10636
                    $filefound = $file;
10637
                    $classname = $prefix . '_' . $modele;
10638
                    break;
10639
                }
10640
            }
10641
            if ($filefound) {
10642
                break;
10643
            }
10644
        }
10645
10646
        if ($filefound === '' || $classname === '') {
10647
            $this->error = $langs->trans("Error") . ' Failed to load doc generator with modelpaths=' . $modelspath . ' - modele=' . $modele;
10648
            $this->errors[] = $this->error;
10649
            dol_syslog($this->error, LOG_ERR);
10650
            return -1;
10651
        }
10652
10653
        // Sanitize $filefound
10654
        $filefound = dol_sanitizePathName($filefound);
10655
10656
        // If generator was found
10657
        global $db; // Required to solve a conception error making an include of some code that uses $db instead of $this->db just after.
10658
10659
        require_once $filefound;
10660
10661
        $obj = new $classname($this->db);
10662
10663
        // If generator is ODT, we must have srctemplatepath defined, if not we set it.
10664
        if ($obj->type == 'odt' && empty($srctemplatepath)) {
10665
            $varfortemplatedir = $obj->scandir;
10666
            if ($varfortemplatedir && getDolGlobalString($varfortemplatedir)) {
10667
                $dirtoscan = getDolGlobalString($varfortemplatedir);
10668
10669
                $listoffiles = array();
10670
10671
                // Now we add first model found in directories scanned
10672
                $listofdir = explode(',', $dirtoscan);
10673
                foreach ($listofdir as $key => $tmpdir) {
10674
                    $tmpdir = trim($tmpdir);
10675
                    $tmpdir = preg_replace('/DOL_DATA_ROOT/', DOL_DATA_ROOT, $tmpdir);
10676
                    if (!$tmpdir) {
10677
                        unset($listofdir[$key]);
10678
                        continue;
10679
                    }
10680
                    if (is_dir($tmpdir)) {
10681
                        $tmpfiles = dol_dir_list($tmpdir, 'files', 0, '\.od(s|t)$', '', 'name', SORT_ASC, 0);
10682
                        if (count($tmpfiles)) {
10683
                            $listoffiles = array_merge($listoffiles, $tmpfiles);
10684
                        }
10685
                    }
10686
                }
10687
10688
                if (count($listoffiles)) {
10689
                    foreach ($listoffiles as $record) {
10690
                        $srctemplatepath = $record['fullname'];
10691
                        break;
10692
                    }
10693
                }
10694
            }
10695
10696
            if (empty($srctemplatepath)) {
10697
                $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotDefined';
10698
                return -1;
10699
            }
10700
        }
10701
10702
        if ($obj->type == 'odt' && !empty($srctemplatepath)) {
10703
            if (!dol_is_file($srctemplatepath)) {
10704
                dol_syslog("Failed to locate template file " . $srctemplatepath, LOG_WARNING);
10705
                $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotFound';
10706
                return -1;
10707
            }
10708
        }
10709
10710
        // We save charset_output to restore it because write_file can change it if needed for
10711
        // output format that does not support UTF8.
10712
        $sav_charset_output = empty($outputlangs->charset_output) ? '' : $outputlangs->charset_output;
10713
10714
        // update model_pdf in object
10715
        $this->model_pdf = $saved_model;
10716
10717
        if (in_array(get_class($this), array('Adherent'))) {
10718
            '@phan-var-force Adherent $this';
10719
            $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, 'member', 1, 'tmp_cards', $moreparams);
10720
        } else {
10721
            $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, $hidedetails, $hidedesc, $hideref, $moreparams);
10722
        }
10723
        // After call of write_file $obj->result['fullpath'] is set with generated file. It will be used to update the ECM database index.
10724
10725
        if ($resultwritefile > 0) {
10726
            $outputlangs->charset_output = $sav_charset_output;
10727
10728
            // We delete old preview
10729
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
10730
            dol_delete_preview($this);
10731
10732
            // Index file in database
10733
            if (!empty($obj->result['fullpath'])) {
10734
                $destfull = $obj->result['fullpath'];
10735
10736
                // Update the last_main_doc field into main object (if document generator has property ->update_main_doc_field set)
10737
                $update_main_doc_field = 0;
10738
                if (!empty($obj->update_main_doc_field)) {
10739
                    $update_main_doc_field = 1;
10740
                }
10741
10742
                // Check that the file exists, before indexing it.
10743
                // Hint: It does not exist, if we create a PDF and auto delete the ODT File
10744
                if (dol_is_file($destfull)) {
10745
                    $this->indexFile($destfull, $update_main_doc_field);
10746
                }
10747
            } else {
10748
                dol_syslog('Method ->write_file was called on object ' . get_class($obj) . ' and return a success but the return array ->result["fullpath"] was not set.', LOG_WARNING);
10749
            }
10750
10751
            // Success in building document. We build meta file.
10752
            dol_meta_create($this);
10753
10754
            return 1;
10755
        } else {
10756
            $outputlangs->charset_output = $sav_charset_output;
10757
            $this->error = $obj->error;
10758
            $this->errors = $obj->errors;
10759
            dol_syslog("Error generating document for " . __CLASS__ . ". Error: " . $obj->error, LOG_ERR);
10760
            return -1;
10761
        }
10762
    }
10763
10764
    /* Part for categories/tags */
10765
10766
    /**
10767
     * Index a file into the ECM database
10768
     *
10769
     * @param string $destfull Full path of file to index
10770
     * @param int $update_main_doc_field Update field main_doc field into the table of the object.
10771
     *                                          This param is set when called for a document generation if document generator hase
10772
     *                                          ->update_main_doc_field set and returns ->result['fullpath'].
10773
     * @return  int                             Return integer <0 if KO, >0 if OK
10774
     */
10775
    public function indexFile($destfull, $update_main_doc_field)
10776
    {
10777
        global $conf, $user;
10778
10779
        $upload_dir = dirname($destfull);
10780
        $destfile = basename($destfull);
10781
        $rel_dir = preg_replace('/^' . preg_quote(DOL_DATA_ROOT, '/') . '/', '', $upload_dir);
10782
10783
        if (!preg_match('/[\\/]temp[\\/]|[\\/]thumbs|\.meta$/', $rel_dir)) {     // If not a tmp dir
10784
            $filename = basename($destfile);
10785
            $rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
10786
            $rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
10787
10788
            include_once DOL_DOCUMENT_ROOT . '/ecm/class/ecmfiles.class.php';
10789
            $ecmfile = new EcmFiles($this->db);
10790
            $result = $ecmfile->fetch(0, '', ($rel_dir ? $rel_dir . '/' : '') . $filename);
10791
10792
            // Set the public "share" key
10793
            $setsharekey = false;
10794
            if ($this->element == 'propal' || $this->element == 'proposal') {
10795
                if (getDolGlobalInt("PROPOSAL_ALLOW_ONLINESIGN")) {
10796
                    $setsharekey = true;    // feature to make online signature is not set or set to on (default)
10797
                }
10798
                if (getDolGlobalInt("PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
10799
                    $setsharekey = true;
10800
                }
10801
            }
10802
            if ($this->element == 'commande' && getDolGlobalInt("ORDER_ALLOW_EXTERNAL_DOWNLOAD")) {
10803
                $setsharekey = true;
10804
            }
10805
            if ($this->element == 'facture' && getDolGlobalInt("INVOICE_ALLOW_EXTERNAL_DOWNLOAD")) {
10806
                $setsharekey = true;
10807
            }
10808
            if ($this->element == 'bank_account' && getDolGlobalInt("BANK_ACCOUNT_ALLOW_EXTERNAL_DOWNLOAD")) {
10809
                $setsharekey = true;
10810
            }
10811
            if ($this->element == 'product' && getDolGlobalInt("PRODUCT_ALLOW_EXTERNAL_DOWNLOAD")) {
10812
                $setsharekey = true;
10813
            }
10814
            if ($this->element == 'contrat' && getDolGlobalInt("CONTRACT_ALLOW_EXTERNAL_DOWNLOAD")) {
10815
                $setsharekey = true;
10816
            }
10817
            if ($this->element == 'fichinter' && getDolGlobalInt("FICHINTER_ALLOW_EXTERNAL_DOWNLOAD")) {
10818
                $setsharekey = true;
10819
            }
10820
            if ($this->element == 'supplier_proposal' && getDolGlobalInt("SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
10821
                $setsharekey = true;
10822
            }
10823
            if ($this->element == 'societe_rib' && getDolGlobalInt("SOCIETE_RIB_ALLOW_ONLINESIGN")) {
10824
                $setsharekey = true;
10825
            }
10826
10827
            if ($setsharekey) {
10828
                if (empty($ecmfile->share)) {   // Because object not found or share not set yet
10829
                    require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/security2.lib.php';
10830
                    $ecmfile->share = getRandomPassword(true);
10831
                }
10832
            }
10833
10834
            if ($result > 0) {
10835
                $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
10836
                $ecmfile->fullpath_orig = '';
10837
                $ecmfile->gen_or_uploaded = 'generated';
10838
                $ecmfile->description = ''; // indexed content
10839
                $ecmfile->keywords = ''; // keyword content
10840
                $result = $ecmfile->update($user);
10841
                if ($result < 0) {
10842
                    setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
10843
                    return -1;
10844
                }
10845
            } else {
10846
                $ecmfile->entity = $conf->entity;
10847
                $ecmfile->filepath = $rel_dir;
10848
                $ecmfile->filename = $filename;
10849
                $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
10850
                $ecmfile->fullpath_orig = '';
10851
                $ecmfile->gen_or_uploaded = 'generated';
10852
                $ecmfile->description = ''; // indexed content
10853
                $ecmfile->keywords = ''; // keyword content
10854
                $ecmfile->src_object_type = $this->table_element;   // $this->table_name is 'myobject' or 'mymodule_myobject'.
10855
                $ecmfile->src_object_id = $this->id;
10856
10857
                $result = $ecmfile->create($user);
10858
                if ($result < 0) {
10859
                    setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
10860
                    return -1;
10861
                }
10862
            }
10863
10864
            /*$this->result['fullname']=$destfull;
10865
             $this->result['filepath']=$ecmfile->filepath;
10866
             $this->result['filename']=$ecmfile->filename;*/
10867
            //var_dump($obj->update_main_doc_field);exit;
10868
10869
            if ($update_main_doc_field && !empty($this->table_element)) {
10870
                $sql = "UPDATE " . $this->db->prefix() . $this->table_element . " SET last_main_doc = '" . $this->db->escape($ecmfile->filepath . "/" . $ecmfile->filename) . "'";
10871
                $sql .= " WHERE rowid = " . ((int)$this->id);
10872
10873
                $resql = $this->db->query($sql);
10874
                if (!$resql) {
10875
                    dol_print_error($this->db);
10876
                    return -1;
10877
                } else {
10878
                    $this->last_main_doc = $ecmfile->filepath . '/' . $ecmfile->filename;
10879
                }
10880
            }
10881
        }
10882
10883
        return 1;
10884
    }
10885
10886
    /**
10887
     * Function test if type is array
10888
     *
10889
     * @param array{type:string,label:string,enabled:int<0,2>|string,position:int,notnull?:int,visible:int,noteditable?:int,default?:string,index?:int,foreignkey?:string,searchall?:int,isameasure?:int,css?:string,csslist?:string,help?:string,showoncombobox?:int,disabled?:int,arrayofkeyval?:array<int,string>,comment?:string} $info content information of field
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{type:string,label:...tring>,comment?:string} at position 12 could not be parsed: Expected '}' at position 12, but found 'int'.
Loading history...
10890
     * @return  bool            true if array
10891
     */
10892
    protected function isArray($info)
10893
    {
10894
        if (is_array($info)) {
10895
            if (isset($info['type']) && $info['type'] == 'array') {
10896
                return true;
10897
            } else {
10898
                return false;
10899
            }
10900
        }
10901
        return false;
10902
    }
10903
10904
    /**
10905
     * Function test if field can be null
10906
     *
10907
     * @param array{type:string,label:string,enabled:int<0,2>|string,position:int,notnull?:int,visible:int,noteditable?:int,default?:string,index?:int,foreignkey?:string,searchall?:int,isameasure?:int,css?:string,csslist?:string,help?:string,showoncombobox?:int,disabled?:int,arrayofkeyval?:array<int,string>,comment?:string} $info content information of field
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{type:string,label:...tring>,comment?:string} at position 12 could not be parsed: Expected '}' at position 12, but found 'int'.
Loading history...
10908
     * @return  bool            true if it can be null
10909
     */
10910
    protected function canBeNull($info)
10911
    {
10912
        if (is_array($info)) {
10913
            if (array_key_exists('notnull', $info) && $info['notnull'] != '1') {
10914
                return true;
10915
            } else {
10916
                return false;
10917
            }
10918
        }
10919
        return true;
10920
    }
10921
10922
    /**
10923
     * Function test if is indexed
10924
     *
10925
     * @param array{type:string,label:string,enabled:int<0,2>|string,position:int,notnull?:int,visible:int,noteditable?:int,default?:string,index?:int,foreignkey?:string,searchall?:int,isameasure?:int,css?:string,csslist?:string,help?:string,showoncombobox?:int,disabled?:int,arrayofkeyval?:array<int,string>,comment?:string} $info content information of field
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{type:string,label:...tring>,comment?:string} at position 12 could not be parsed: Expected '}' at position 12, but found 'int'.
Loading history...
10926
     * @return                  bool
10927
     */
10928
    protected function isIndex($info)
10929
    {
10930
        if (is_array($info)) {
10931
            if (array_key_exists('index', $info) && $info['index'] == true) {
10932
                return true;
10933
            } else {
10934
                return false;
10935
            }
10936
        }
10937
        return false;
10938
    }
10939
}
10940