Test Failed
Branch main (fda838)
by Rafael
50:22
created

GenericDocument::deleteObjectLinked()   F

Complexity

Conditions 22
Paths 5184

Size

Total Lines 67
Code Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 22
eloc 50
nc 5184
nop 7
dl 0
loc 67
rs 0
c 1
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
/**
39
 *  \file       htdocs/core/class/commonobject.class.php
40
 *  \ingroup    core
41
 *  \brief      File of parent class of all other business classes (invoices, contracts, proposals, orders, ...)
42
 */
43
44
namespace DoliCore\Base;
45
46
use Alxarafe\Base\Exception;
47
use Alxarafe\Base\stdClass;
48
use Categorie;
49
use Commande;
50
use CommandeFournisseur;
51
use CommandeFournisseurLigne;
52
use Comment;
53
use CommonObject;
54
use CommonObjectLine;
55
use Contact;
56
use DiscountAbsolute;
57
use DolEditor;
58
use DoliDB;
59
use EcmFiles;
60
use Extrafields;
61
use Facture;
62
use FactureFournisseur;
63
use FactureLigne;
64
use Form;
65
use Interfaces;
66
use MultiCurrency;
67
use OrderLine;
68
use Product;
69
use ProductFournisseur;
70
use Project;
71
use Propal;
72
use PropaleLigne;
73
use Societe;
74
use SupplierInvoiceLine;
75
use SupplierProposal;
76
use SupplierProposalLine;
77
use Translate;
78
use User;
79
use Validate;
80
81
/**
82
 * Class GenericDocument replaces CommonObject
83
 * Parent class of all other business classes (invoices, contracts, proposals, orders, ...)
84
 *
85
 * @deprecated This class is only needed for compatibility with Dolibarr.
86
 */
87
abstract class GenericDocument extends Model
0 ignored issues
show
Deprecated Code introduced by
The class DoliCore\Base\Model has been deprecated: This class is only needed for compatibility with Dolibarr. ( Ignorable by Annotation )

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

87
abstract class GenericDocument extends /** @scrutinizer ignore-deprecated */ Model
Loading history...
88
{
89
    const TRIGGER_PREFIX = ''; // to be overridden in child class implementations, i.e. 'BILL', 'TASK', 'PROPAL', etc.
90
91
    /**
92
     * @var string      ID of module.
93
     */
94
    public $module;
95
96
    /**
97
     * @var DoliDB      Database handler (result of a new DoliDB)
98
     */
99
    public $db;
100
101
    /**
102
     * @var int         The object identifier
103
     */
104
    public $id;
105
106
    /**
107
     * @var int         Another ID that is the $id but with an offset so that ID of the website start at 1
108
     */
109
    public $newid;
110
111
    /**
112
     * @var int         The environment ID when using a multicompany module
113
     */
114
    public $entity;
115
116
    /**
117
     * @var string      Error string
118
     * @see             $errors
119
     */
120
    public $error;
121
122
    /**
123
     * @var string      Error string that is hidden but can be used to store additional technical code
124
     */
125
    public $errorhidden;
126
127
    /**
128
     * @var string[]    Array of error strings
129
     */
130
    public $errors = array();
131
132
    /**
133
     * @var array       To store error results of ->validateField()
134
     */
135
    private $validateFieldsErrors = array();
136
137
    /**
138
     * @var string      ID to identify managed object
139
     */
140
    public $element;
141
142
    /**
143
     * @var string|int  Field with ID of parent key if this field has a parent (a string). For example 'fk_product'.
144
     *                  ID of parent key itself (an int). For example in few classes like 'Comment', 'ActionComm' or 'AdvanceTargetingMailing'.
145
     */
146
    public $fk_element;
147
148
    /**
149
     * @var string      Name to use for 'features' parameter to check module permissions user->rights->feature with restrictedArea().
150
     *                  Undefined means same value than $element.
151
     *                  Can be use to force a check on another element (for example for class of a line, we mention here its parent element).
152
     */
153
    public $element_for_permission;
154
155
    /**
156
     * @var string      Name of table without prefix where object is stored
157
     */
158
    public $table_element;
159
160
    /**
161
     * @var string      Name of subtable line
162
     */
163
    public $table_element_line = '';
164
165
    /**
166
     * Does this object supports the multicompany module ?
167
     *
168
     * @var int|string      0 if no test on entity, 1 if test with field entity, 2 if test with link by fk_soc, 'field@table' if test with link by field@table
169
     */
170
    public $ismultientitymanaged;
171
172
    /**
173
     * @var string      Key value used to track if data is coming from import wizard
174
     */
175
    public $import_key;
176
177
    /**
178
     * @var mixed       Contains data to manage extrafields
179
     */
180
    public $array_options = array();
181
182
183
    /**
184
     * @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...
185
     */
186
    public $fields = array();
187
188
    /**
189
     * @var mixed       Array to store alternative languages values of object
190
     */
191
    public $array_languages = null; // Value is array() when load already tried
192
193
    /**
194
     * @var array       To store result of ->liste_contact()
195
     */
196
    public $contacts_ids;
197
198
    /**
199
     * @var mixed       Array of linked objects, set and used when calling ->create() to be able to create links during the creation of object
200
     */
201
    public $linked_objects;
202
203
    /**
204
     * @var int[][]     Array of linked objects ids. Loaded by ->fetchObjectLinked
205
     */
206
    public $linkedObjectsIds;
207
208
    /**
209
     * @var mixed       Array of linked objects. Loaded by ->fetchObjectLinked
210
     */
211
    public $linkedObjects;
212
213
    /**
214
     * @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.
215
     */
216
    private $linkedObjectsFullLoaded = array();
217
218
    /**
219
     * @var CommonObject    To store a cloned copy of the object before editing it (to keep track of its former properties)
220
     */
221
    public $oldcopy;
222
223
    /**
224
     * @var string      To store the old value of a modified reference
225
     */
226
    public $oldref;
227
228
    /**
229
     * @var string      Column name of the ref field.
230
     */
231
    protected $table_ref_field = '';
232
233
    /**
234
     * @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
235
     */
236
    public $restrictiononfksoc = 0;
237
238
239
    // The following vars are used by some objects only.
240
    // We keep these properties in CommonObject in order to provide common methods using them.
241
242
    /**
243
     * @var array<string,mixed>     Can be used to pass information when only the object is provided to the method
244
     */
245
    public $context = array();
246
247
    // Properties set and used by Agenda trigger
248
    public $actionmsg;
249
    public $actionmsg2;
250
251
    /**
252
     * @var string          Contains canvas name if record is an alternative canvas record
253
     */
254
    public $canvas;
255
256
    /**
257
     * @var Project|null    The related project object
258
     * @see fetch_projet()
259
     */
260
    public $project;
261
262
    /**
263
     * @var int             The related project ID
264
     * @see setProject(), project
265
     */
266
    public $fk_project;
267
268
    /**
269
     * @var Project         The related project object
270
     * @deprecated  Use $project instead
271
     * @see $project
272
     */
273
    public $projet;
274
275
    /**
276
     * @deprecated  Use $fk_project instead
277
     * @see $fk_project
278
     */
279
    public $fk_projet;
280
281
    /**
282
     * @var Contact|null    A related contact object
283
     * @see fetch_contact()
284
     */
285
    public $contact;
286
287
    /**
288
     * @var int             The related contact ID
289
     * @see fetch_contact()
290
     */
291
    public $contact_id;
292
293
    /**
294
     * @var Societe|null    A related thirdparty object
295
     * @see fetch_thirdparty()
296
     */
297
    public $thirdparty;
298
299
    /**
300
     * @var User            A related user
301
     * @see fetch_user()
302
     */
303
    public $user;
304
305
    /**
306
     * @var string      The type of originating object. Combined with $origin_id, it allows to reload $origin_object
307
     * @see fetch_origin()
308
     */
309
    public $origin_type;
310
311
    /**
312
     * @var int         The id of originating object. Combined with $origin_type, it allows to reload $origin_object
313
     * @see fetch_origin()
314
     */
315
    public $origin_id;
316
317
    /**
318
     * @var ?CommonObject   Origin object. This is set by fetch_origin() from this->origin and this->origin_id.
319
     */
320
    public $origin_object;
321
322
    /**
323
     * @var CommonObject|string|null    Sometimes the type of the originating object ('commande', 'facture', ...), sometimes the object (as with MouvementStock)
324
     * @deprecated                      Use now $origin_type and $origin_id;
325
     * @see fetch_origin()
326
     */
327
    public $origin;
328
329
    // TODO Remove this. Has been replaced with ->origin_object.
330
    // This is set by fetch_origin() from this->origin and this->origin_id
331
    /** @deprecated */
332
    public $expedition;
333
    /** @deprecated */
334
    public $livraison;
335
    /** @deprecated */
336
    public $commandeFournisseur;
337
338
339
    /**
340
     * @var string      The object's reference
341
     */
342
    public $ref;
343
344
    /**
345
     * @var string      An external reference to the object
346
     */
347
    public $ref_ext;
348
349
    /**
350
     * @var string      The object's previous reference
351
     */
352
    public $ref_previous;
353
354
    /**
355
     * @var string      The object's next reference
356
     */
357
    public $ref_next;
358
359
    /**
360
     * @var string      Ref to store on object to save the new ref to use for example when making a validate() of an object
361
     */
362
    public $newref;
363
364
    /**
365
     * @var int         The object's status. Use status instead.
366
     * @deprecated  Use $status instead
367
     * @see setStatut()
368
     */
369
    public $statut;
370
371
    /**
372
     * @var int|array<int, string>      The object's status (an int).
373
     *                                  Or an array listing all the potential status of the object:
374
     *                                    array: int of the status => translated label of the status
375
     *                                    See for example the Account class.
376
     * @see setStatut()
377
     */
378
    public $status;
379
380
381
    /**
382
     * @var string      Country name
383
     * @see getFullAddress()
384
     */
385
    public $country;
386
387
    /**
388
     * @var int         Country ID
389
     * @see getFullAddress(), country
390
     */
391
    public $country_id;
392
393
    /**
394
     * @var string      ISO country code on 2 chars
395
     * @see getFullAddress(), isInEEC(), country
396
     */
397
    public $country_code;
398
399
    /**
400
     * @var string      State name
401
     * @see getFullAddress()
402
     */
403
    public $state;
404
405
    /**
406
     * @var int         State ID
407
     * @see getFullAddress(), state
408
     */
409
    public $state_id;
410
411
    /**
412
     * var  int         State ID
413
     * @deprecated  Use $state_id. We can remove this property when the field 'fk_departement' have been renamed into 'state_id' in all tables
414
     */
415
    public $fk_departement;
416
417
    /**
418
     * @var string      State code
419
     * @see getFullAddress(), $state
420
     */
421
    public $state_code;
422
423
    /**
424
     * @var int         Region ID
425
     * @see getFullAddress(), $region_code, $region
426
     */
427
    public $region_id;
428
429
    /**
430
     * @var string      Region code
431
     * @see getFullAddress(), $region_id, $region
432
     */
433
    public $region_code;
434
435
    /**
436
     * @var string      Region name
437
     * @see getFullAddress(), $region_id, $region_code
438
     */
439
    public $region;
440
441
442
    /**
443
     * @var int         Barcode type
444
     * @see fetch_barcode()
445
     */
446
    public $barcode_type;
447
448
    /**
449
     * @var string      Code of the barcode type
450
     * @see fetch_barcode(), barcode_type
451
     */
452
    public $barcode_type_code;
453
454
    /**
455
     * @var string      Label of the barcode type
456
     * @see fetch_barcode(), barcode_type
457
     */
458
    public $barcode_type_label;
459
460
    /**
461
     * @var string
462
     * @see fetch_barcode(), barcode_type
463
     */
464
    public $barcode_type_coder;
465
466
    /**
467
     * @var int         Payment method ID (cheque, cash, ...)
468
     * @see setPaymentMethods()
469
     */
470
    public $mode_reglement_id;
471
472
    /**
473
     * @var int         Payment terms ID
474
     * @see setPaymentTerms()
475
     */
476
    public $cond_reglement_id;
477
478
    /**
479
     * @var int         Demand reason ID
480
     */
481
    public $demand_reason_id;
482
483
    /**
484
     * @var int         Transport mode ID (For module intracomm report)
485
     * @see setTransportMode()
486
     */
487
    public $transport_mode_id;
488
489
    /**
490
     * @var int         Payment terms ID
491
     * @deprecated Use $cond_reglement_id instead - Kept for compatibility
492
     * @see cond_reglement_id;
493
     */
494
    public $cond_reglement;
495
496
    /**
497
     * @var int         Delivery address ID
498
     * @see setDeliveryAddress()
499
     * @deprecated
500
     */
501
    public $fk_delivery_address;
502
503
    /**
504
     * @var int         Shipping method ID
505
     * @see setShippingMethod()
506
     */
507
    public $shipping_method_id;
508
509
    /**
510
     * @var string      Shipping method label
511
     * @see setShippingMethod()
512
     */
513
    public $shipping_method;
514
515
    // Multicurrency
516
    /**
517
     * @var int ID
518
     */
519
    public $fk_multicurrency;
520
521
    /**
522
     * @var string|string[]             Multicurrency code
523
     *                                  Or, just for the Paiement object, an array: invoice ID => currency code for that invoice.
524
     */
525
    public $multicurrency_code;
526
527
    /**
528
     * @var float|float[]               Multicurrency rate ("tx" = "taux" in French)
529
     *                                  Or, just for the Paiement object, an array: invoice ID => currency rate for that invoice.
530
     */
531
    public $multicurrency_tx;
532
533
    /**
534
     * @var float       Multicurrency total amount excluding taxes (HT = "Hors Taxe" in French)
535
     */
536
    public $multicurrency_total_ht;
537
538
    /**
539
     * @var float       Multicurrency total VAT amount (TVA = "Taxe sur la Valeur Ajoutée" in French)
540
     */
541
    public $multicurrency_total_tva;
542
543
    /**
544
     * @var float       Multicurrency total amount including taxes (TTC = "Toutes Taxes Comprises" in French)
545
     */
546
    public $multicurrency_total_ttc;
547
548
    /**
549
     * @var float Multicurrency total localta1
550
     */
551
    public $multicurrency_total_localtax1;  // not in database
552
553
    /**
554
     * @var float Multicurrency total localtax2
555
     */
556
    public $multicurrency_total_localtax2;  // not in database
557
558
    /**
559
     * @var string
560
     * @see SetDocModel()
561
     */
562
    public $model_pdf;
563
564
    /**
565
     * @var string
566
     * @deprecated
567
     * @see $model_pdf
568
     */
569
    public $modelpdf;
570
571
    /**
572
     * @var string
573
     * Contains relative path of last generated main file
574
     */
575
    public $last_main_doc;
576
577
    /**
578
     * @var int         Bank account ID sometimes, ID of record into llx_bank sometimes
579
     * @deprecated
580
     * @see $fk_account
581
     */
582
    public $fk_bank;
583
584
    /**
585
     * @var int         Bank account ID
586
     * @see SetBankAccount()
587
     */
588
    public $fk_account;
589
590
    /**
591
     * @var string      Public note
592
     * @see update_note()
593
     */
594
    public $note_public;
595
596
    /**
597
     * @var string      Private note
598
     * @see update_note()
599
     */
600
    public $note_private;
601
602
    /**
603
     * @deprecated
604
     * @see $note_private
605
     */
606
    public $note;
607
608
    /**
609
     * @var float       Total amount excluding taxes (HT = "Hors Taxe" in French)
610
     * @see update_price()
611
     */
612
    public $total_ht;
613
614
    /**
615
     * @var float       Total VAT amount (TVA = "Taxe sur la Valeur Ajoutée" in French)
616
     * @see update_price()
617
     */
618
    public $total_tva;
619
620
    /**
621
     * @var float       Total local tax 1 amount
622
     * @see update_price()
623
     */
624
    public $total_localtax1;
625
626
    /**
627
     * @var float       Total local tax 2 amount
628
     * @see update_price()
629
     */
630
    public $total_localtax2;
631
632
    /**
633
     * @var float       Total amount including taxes (TTC = "Toutes Taxes Comprises" in French)
634
     * @see update_price()
635
     */
636
    public $total_ttc;
637
638
639
    /**
640
     * @var CommonObjectLine[]
641
     */
642
    public $lines;
643
644
    /**
645
     * @var string  Action code to use to record auto event in agenda. For example 'AC_OTH_AUTO'
646
     */
647
    public $actiontypecode;
648
649
    /**
650
     * @var mixed       Comments
651
     * @see fetchComments()
652
     */
653
    public $comments = array();
654
655
    /**
656
     * @var string      The name
657
     */
658
    public $name;
659
660
    /**
661
     * @var string      The lastname
662
     */
663
    public $lastname;
664
665
    /**
666
     * @var string      The firstname
667
     */
668
    public $firstname;
669
670
    /**
671
     * @var string      The civility code, not an integer
672
     */
673
    public $civility_id;
674
675
    // Dates
676
    /**
677
     * @var integer|string|null     Object creation date
678
     */
679
    public $date_creation;
680
681
    /**
682
     * @var integer|string|null     Object last validation date
683
     */
684
    public $date_validation;
685
686
    /**
687
     * @var integer|string|null     Object last modification date
688
     */
689
    public $date_modification;
690
691
    /**
692
     * Update timestamp record (tms)
693
     * @var integer
694
     * @deprecated                  Use $date_modification
695
     */
696
    public $tms;
697
698
    /**
699
     * @var integer|string|null     Object closing date
700
     */
701
    public $date_cloture;
702
703
    /**
704
     * @var User        User author/creation
705
     * @deprecated      Store only id in user_creation_id
706
     */
707
    public $user_author;
708
709
    /**
710
     * @var User        User author/creation
711
     * @deprecated
712
     */
713
    public $user_creation;
714
715
    /**
716
     * @var int         User id author/creation
717
     */
718
    public $user_creation_id;
719
720
    /**
721
     * @var User        User of validation
722
     * @deprecated
723
     */
724
    public $user_valid;
725
726
    /**
727
     * @var User        User of validation
728
     * @deprecated
729
     */
730
    public $user_validation;
731
732
    /**
733
     * @var int|null        User id of validation
734
     */
735
    public $user_validation_id;
736
737
    /**
738
     * @var int         User id closing object
739
     */
740
    public $user_closing_id;
741
742
    /**
743
     * @var User    User last modifier
744
     * @deprecated
745
     */
746
    public $user_modification;
747
748
    /**
749
     * @var int         User ID who last modified the object
750
     */
751
    public $user_modification_id;
752
753
    /**
754
     * @var int ID
755
     * @deprecated  Use $user_creation_id
756
     */
757
    public $fk_user_creat;
758
759
    /**
760
     * @var int ID
761
     * @deprecated  Use $user_modification_id
762
     */
763
    public $fk_user_modif;
764
765
766
    public $next_prev_filter;
767
768
    /**
769
     * @var int 1 if object is specimen
770
     */
771
    public $specimen = 0;
772
773
    /**
774
     * @var int[]       Id of contacts to send objects (mails, etc.)
775
     */
776
    public $sendtoid;
777
778
    /**
779
     * @var float       Amount already paid from getSommePaiement() (used to show correct status)
780
     * @deprecated      Use $totalpaid instead
781
     */
782
    public $alreadypaid;
783
    /**
784
     * @var float       Amount already paid from getSommePaiement() (used to show correct status)
785
     */
786
    public $totalpaid;
787
788
    /**
789
     * @var array       Array with labels of status
790
     */
791
    public $labelStatus = array();
792
793
    /**
794
     * @var array       Array with short labels of status
795
     */
796
    public $labelStatusShort = array();
797
798
    /**
799
     * @var array       Array to store lists of tpl
800
     */
801
    public $tpl;
802
803
804
    /**
805
     * @var int         show photo on popup
806
     */
807
    public $showphoto_on_popup;
808
809
    /**
810
     * @var array       nb used in load_stateboard
811
     */
812
    public $nb = array();
813
814
    /**
815
     * @var int         used for the return of show_photos()
816
     */
817
    public $nbphoto;
818
819
    /**
820
     * @var string      output
821
     */
822
    public $output;
823
824
    /**
825
     * @var array|string    extra parameters. Try to store here the array of parameters. Old code is sometimes storing a string.
826
     */
827
    public $extraparams = array();
828
829
    /**
830
     * @var array<string,string[]|array{parent:string,parentkey:string}>    List of child tables. To test if we can delete object.
831
     */
832
    protected $childtables = array();
833
834
    /**
835
     * @var string[]    List of child tables. To know object to delete on cascade.
836
     *               If name is like '@ClassName:FilePathClass:ParentFkFieldName', it will
837
     *               call method deleteByParentField(parentId, ParentFkFieldName) to fetch and delete child object.
838
     */
839
    protected $childtablesoncascade = array();
840
841
    /**
842
     * @var Product     Populated by fetch_product()
843
     */
844
    public $product;
845
846
    /**
847
     * @var int         Populated by setPaymentTerms()
848
     */
849
    public $cond_reglement_supplier_id;
850
851
    /**
852
     * @var float       Deposit percent for payment terms.
853
     *                  Populated by setPaymentTerms().
854
     * @see setPaymentTerms()
855
     */
856
    public $deposit_percent;
857
858
859
    /**
860
     * @var int         Populated by setRetainedWarrantyPaymentTerms()
861
     */
862
    public $retained_warranty_fk_cond_reglement;
863
864
    /**
865
     * @var int         Populated by setWarehouse()
866
     */
867
    public $warehouse_id;
868
869
    // No constructor as it is an abstract class
870
871
872
    /**
873
     * Check if an object id or ref exists
874
     * 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
875
     *
876
     *  @param  string  $element    String of element ('product', 'facture', ...)
877
     *  @param  int     $id         Id of object
878
     *  @param  string  $ref        Ref of object to check
879
     *  @param  string  $ref_ext    Ref ext of object to check
880
     *  @return int                 Return integer <0 if KO, 0 if OK but not found, >0 if OK and exists
881
     */
882
    public static function isExistingObject($element, $id, $ref = '', $ref_ext = '')
883
    {
884
        global $db, $conf;
885
886
        $sql = "SELECT rowid, ref, ref_ext";
887
        $sql .= " FROM " . $db->prefix() . $element;
888
        $sql .= " WHERE entity IN (" . getEntity($element) . ")";
889
890
        if ($id > 0) {
891
            $sql .= " AND rowid = " . ((int) $id);
892
        } elseif ($ref) {
893
            $sql .= " AND ref = '" . $db->escape($ref) . "'";
894
        } elseif ($ref_ext) {
895
            $sql .= " AND ref_ext = '" . $db->escape($ref_ext) . "'";
896
        } else {
897
            $error = 'ErrorWrongParameters';
898
            dol_print_error(get_class() . "::isExistingObject " . $error, LOG_ERR);
899
            return -1;
900
        }
901
        if ($ref || $ref_ext) {     // Because the same ref can exists in 2 different entities, we force the current one in priority
902
            $sql .= " AND entity = " . ((int) $conf->entity);
903
        }
904
905
        dol_syslog(get_class() . "::isExistingObject", LOG_DEBUG);
906
        $resql = $db->query($sql);
907
        if ($resql) {
908
            $num = $db->num_rows($resql);
909
            if ($num > 0) {
910
                return 1;
911
            } else {
912
                return 0;
913
            }
914
        }
915
        return -1;
916
    }
917
918
    /**
919
     * setErrorsFromObject
920
     *
921
     * @param CommonObject $object commonobject
922
     * @return void
923
     */
924
    public function setErrorsFromObject($object)
925
    {
926
        if (!empty($object->error)) {
927
            $this->error = $object->error;
928
        }
929
        if (!empty($object->errors)) {
930
            $this->errors = array_merge($this->errors, $object->errors);
931
        }
932
    }
933
934
    /**
935
     * Return array of data to show into a tooltip. This method must be implemented in each object class.
936
     *
937
     * @since v18
938
     * @param array $params params to construct tooltip data
939
     * @return array
940
     */
941
    public function getTooltipContentArray($params)
942
    {
943
        return [];
944
    }
945
946
    /**
947
     * getTooltipContent
948
     *
949
     * @param array $params params
950
     * @since v18
951
     * @return string
952
     */
953
    public function getTooltipContent($params)
954
    {
955
        global $action, $extrafields, $langs, $hookmanager;
956
957
        // If there is too much extrafields, we do not include them into tooltip
958
        $MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP = getDolGlobalInt('MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP', 3);
959
960
        $data = $this->getTooltipContentArray($params);
961
        $count = 0;
962
963
        // Add extrafields
964
        if (!empty($extrafields->attributes[$this->table_element]['label'])) {
965
            $data['opendivextra'] = '<div class="centpercent wordbreak divtooltipextra">';
966
            foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
967
                if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
968
                    continue;
969
                }
970
                if ($count >= abs($MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP)) {
971
                    $data['more_extrafields'] = '<br>...';
972
                    break;
973
                }
974
                $enabled = 1;
975
                if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
976
                    $enabled = (int) dol_eval($extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
977
                }
978
                if ($enabled && isset($extrafields->attributes[$this->table_element]['list'][$key])) {
979
                    $enabled = (int) dol_eval($extrafields->attributes[$this->table_element]['list'][$key], 1, 1, '2');
980
                }
981
                $perms = 1;
982
                if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
983
                    $perms = (int) dol_eval($extrafields->attributes[$this->table_element]['perms'][$key], 1, 1, '2');
984
                }
985
                if (empty($enabled)) {
986
                    continue; // 0 = Never visible field
987
                }
988
                if (abs($enabled) != 1 && abs($enabled) != 3 && abs($enabled) != 5 && abs($enabled) != 4) {
989
                    continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list <> 4 = not visible at the creation
990
                }
991
                if (empty($perms)) {
992
                    continue; // 0 = Not visible
993
                }
994
                if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
995
                    $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
996
                }
997
                $labelextra = $langs->trans((string) $extrafields->attributes[$this->table_element]['label'][$key]);
998
                if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
999
                    $data[$key] = '<br><b><u>' . $labelextra . '</u></b>';
1000
                } else {
1001
                    $value = (empty($this->array_options['options_' . $key]) ? '' : $this->array_options['options_' . $key]);
1002
                    $data[$key] = '<br><b>' . $labelextra . ':</b> ' . $extrafields->showOutputField($key, $value, '', $this->table_element);
1003
                    $count++;
1004
                }
1005
            }
1006
            $data['closedivextra'] = '</div>';
1007
        }
1008
1009
        $hookmanager->initHooks(array($this->element . 'dao'));
1010
        $parameters = array(
1011
            'tooltipcontentarray' => &$data,
1012
            'params' => $params,
1013
        );
1014
        // Note that $action and $object may have been modified by some hooks
1015
        $hookmanager->executeHooks('getTooltipContent', $parameters, $this, $action);
1016
1017
        //var_dump($data);
1018
        $label = implode($data);
1019
1020
        return $label;
1021
    }
1022
1023
1024
    /**
1025
     * Method to output saved errors
1026
     *
1027
     * @return  string      String with errors
1028
     */
1029
    public function errorsToString()
1030
    {
1031
        return $this->error . (is_array($this->errors) ? (($this->error != '' ? ', ' : '') . implode(', ', $this->errors)) : '');
1032
    }
1033
1034
1035
    /**
1036
     * Return customer ref for screen output.
1037
     *
1038
     * @param  string      $objref        Customer ref
1039
     * @return string                     Customer ref formatted
1040
     */
1041
    public function getFormatedCustomerRef($objref)
1042
    {
1043
        global $hookmanager;
1044
1045
        $parameters = array('objref' => $objref);
1046
        $action = '';
1047
        $reshook = $hookmanager->executeHooks('getFormatedCustomerRef', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1048
        if ($reshook > 0) {
1049
            return $hookmanager->resArray['objref'];
1050
        }
1051
        return $objref . (isset($hookmanager->resArray['objref']) ? $hookmanager->resArray['objref'] : '');
1052
    }
1053
1054
    /**
1055
     * Return supplier ref for screen output.
1056
     *
1057
     * @param  string      $objref        Supplier ref
1058
     * @return string                     Supplier ref formatted
1059
     */
1060
    public function getFormatedSupplierRef($objref)
1061
    {
1062
        global $hookmanager;
1063
1064
        $parameters = array('objref' => $objref);
1065
        $action = '';
1066
        $reshook = $hookmanager->executeHooks('getFormatedSupplierRef', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1067
        if ($reshook > 0) {
1068
            return $hookmanager->resArray['objref'];
1069
        }
1070
        return $objref . (isset($hookmanager->resArray['objref']) ? $hookmanager->resArray['objref'] : '');
1071
    }
1072
1073
    /**
1074
     *  Return full address of contact
1075
     *
1076
     *  @param      int         $withcountry        1=Add country into address string
1077
     *  @param      string      $sep                Separator to use to build string
1078
     *  @param      int         $withregion         1=Add region into address string
1079
     *  @param      string      $extralangcode      User extralanguages as value
1080
     *  @return     string                          Full address string
1081
     */
1082
    public function getFullAddress($withcountry = 0, $sep = "\n", $withregion = 0, $extralangcode = '')
1083
    {
1084
        if ($withcountry && $this->country_id && (empty($this->country_code) || empty($this->country))) {
1085
            require_once DOL_DOCUMENT_ROOT . '/core/lib/company.lib.php';
1086
            $tmparray = getCountry($this->country_id, 'all');
1087
            $this->country_code = $tmparray['code'];
1088
            $this->country = $tmparray['label'];
1089
        }
1090
1091
        if ($withregion && $this->state_id && (empty($this->state_code) || empty($this->state) || empty($this->region) || empty($this->region_code))) {
1092
            require_once DOL_DOCUMENT_ROOT . '/core/lib/company.lib.php';
1093
            $tmparray = getState($this->state_id, 'all', 0, 1);
1094
            $this->state_code   = $tmparray['code'];
1095
            $this->state        = $tmparray['label'];
1096
            $this->region_code  = $tmparray['region_code'];
1097
            $this->region       = $tmparray['region'];
1098
        }
1099
1100
        return dol_format_address($this, $withcountry, $sep, '', 0, $extralangcode);
1101
    }
1102
1103
1104
    /**
1105
     * Return the link of last main doc file for direct public download.
1106
     *
1107
     * @param   string  $modulepart         Module related to document
1108
     * @param   int     $initsharekey       Init the share key if it was not yet defined
1109
     * @param   int     $relativelink       0=Return full external link, 1=Return link relative to root of file
1110
     * @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...
1111
     */
1112
    public function getLastMainDocLink($modulepart, $initsharekey = 0, $relativelink = 0)
1113
    {
1114
        global $user, $dolibarr_main_url_root;
1115
1116
        if (empty($this->last_main_doc)) {
1117
            return ''; // No way to known which document name to use
1118
        }
1119
1120
        include_once DOL_DOCUMENT_ROOT . '/ecm/class/ecmfiles.class.php';
1121
        $ecmfile = new EcmFiles($this->db);
1122
        $result = $ecmfile->fetch(0, '', $this->last_main_doc);
1123
        if ($result < 0) {
1124
            $this->error = $ecmfile->error;
1125
            $this->errors = $ecmfile->errors;
1126
            return -1;
1127
        }
1128
1129
        if (empty($ecmfile->id)) {
1130
            // Add entry into index
1131
            if ($initsharekey) {
1132
                require_once DOL_DOCUMENT_ROOT . '/core/lib/security2.lib.php';
1133
1134
                // 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
1135
                /*
1136
                $ecmfile->filepath = $rel_dir;
1137
                $ecmfile->filename = $filename;
1138
                $ecmfile->label = md5_file(dol_osencode($destfull));    // hash of file content
1139
                $ecmfile->fullpath_orig = '';
1140
                $ecmfile->gen_or_uploaded = 'generated';
1141
                $ecmfile->description = '';    // indexed content
1142
                $ecmfile->keywords = '';        // keyword content
1143
                $ecmfile->share = getRandomPassword(true);
1144
                $result = $ecmfile->create($user);
1145
                if ($result < 0)
1146
                {
1147
                    $this->error = $ecmfile->error;
1148
                    $this->errors = $ecmfile->errors;
1149
                }
1150
                */
1151
            } else {
1152
                return '';
1153
            }
1154
        } elseif (empty($ecmfile->share)) {
1155
            // Add entry into index
1156
            if ($initsharekey) {
1157
                require_once DOL_DOCUMENT_ROOT . '/core/lib/security2.lib.php';
1158
                $ecmfile->share = getRandomPassword(true);
1159
                $ecmfile->update($user);
1160
            } else {
1161
                return '';
1162
            }
1163
        }
1164
        // Define $urlwithroot
1165
        $urlwithouturlroot = preg_replace('/' . preg_quote(DOL_URL_ROOT, '/') . '$/i', '', trim($dolibarr_main_url_root));
1166
        // This is to use external domain name found into config file
1167
        //if (DOL_URL_ROOT && ! preg_match('/\/$/', $urlwithouturlroot) && ! preg_match('/^\//', DOL_URL_ROOT)) $urlwithroot=$urlwithouturlroot.'/'.DOL_URL_ROOT;
1168
        //else
1169
        $urlwithroot = $urlwithouturlroot . DOL_URL_ROOT;
1170
        //$urlwithroot=DOL_MAIN_URL_ROOT;                   // This is to use same domain name than current
1171
1172
        $forcedownload = 0;
1173
1174
        $paramlink = '';
1175
        //if (!empty($modulepart)) $paramlink.=($paramlink?'&':'').'modulepart='.$modulepart;       // For sharing with hash (so public files), modulepart is not required.
1176
        //if (!empty($ecmfile->entity)) $paramlink.='&entity='.$ecmfile->entity;                    // For sharing with hash (so public files), entity is not required.
1177
        //$paramlink.=($paramlink?'&':'').'file='.urlencode($filepath);                             // No need of name of file for public link, we will use the hash
1178
        if (!empty($ecmfile->share)) {
1179
            $paramlink .= ($paramlink ? '&' : '') . 'hashp=' . $ecmfile->share; // Hash for public share
1180
        }
1181
        if ($forcedownload) {
1182
            $paramlink .= ($paramlink ? '&' : '') . 'attachment=1';
1183
        }
1184
1185
        if ($relativelink) {
1186
            $linktoreturn = 'document.php' . ($paramlink ? '?' . $paramlink : '');
1187
        } else {
1188
            $linktoreturn = $urlwithroot . '/document.php' . ($paramlink ? '?' . $paramlink : '');
1189
        }
1190
1191
        // Here $ecmfile->share is defined
1192
        return $linktoreturn;
1193
    }
1194
1195
1196
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1197
    /**
1198
     *  Add a link between element $this->element and a contact
1199
     *
1200
     *  @param  int         $fk_socpeople       Id of thirdparty contact (if source = 'external') or id of user (if source = 'internal') to link
1201
     *  @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
1202
     *  @param  string      $source             external=Contact extern (llx_socpeople), internal=Contact intern (llx_user)
1203
     *  @param  int         $notrigger          Disable all triggers
1204
     *  @return int                             Return integer <0 if KO, 0 if already added or code not valid, >0 if OK
1205
     */
1206
    public function add_contact($fk_socpeople, $type_contact, $source = 'external', $notrigger = 0)
1207
    {
1208
        // phpcs:enable
1209
        global $user, $langs;
1210
1211
1212
        dol_syslog(get_class($this) . "::add_contact $fk_socpeople, $type_contact, $source, $notrigger");
1213
1214
        // Check parameters
1215
        if ($fk_socpeople <= 0) {
1216
            $langs->load("errors");
1217
            $this->error = $langs->trans("ErrorWrongValueForParameterX", "1");
1218
            dol_syslog(get_class($this) . "::add_contact " . $this->error, LOG_ERR);
1219
            return -1;
1220
        }
1221
        if (!$type_contact) {
1222
            $langs->load("errors");
1223
            $this->error = $langs->trans("ErrorWrongValueForParameterX", "2");
1224
            dol_syslog(get_class($this) . "::add_contact " . $this->error, LOG_ERR);
1225
            return -2;
1226
        }
1227
1228
        $id_type_contact = 0;
1229
        if (is_numeric($type_contact)) {
1230
            $id_type_contact = $type_contact;
1231
        } else {
1232
            // We look for id type_contact
1233
            $sql = "SELECT tc.rowid";
1234
            $sql .= " FROM " . $this->db->prefix() . "c_type_contact as tc";
1235
            $sql .= " WHERE tc.element='" . $this->db->escape($this->element) . "'";
1236
            $sql .= " AND tc.source='" . $this->db->escape($source) . "'";
1237
            $sql .= " AND tc.code='" . $this->db->escape($type_contact) . "' AND tc.active=1";
1238
            //print $sql;
1239
            $resql = $this->db->query($sql);
1240
            if ($resql) {
1241
                $obj = $this->db->fetch_object($resql);
1242
                if ($obj) {
1243
                    $id_type_contact = $obj->rowid;
1244
                }
1245
            }
1246
        }
1247
1248
        if ($id_type_contact == 0) {
1249
            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");
1250
            return 0;
1251
        }
1252
1253
        $datecreate = dol_now();
1254
1255
        // Socpeople must have already been added by some trigger, then we have to check it to avoid DB_ERROR_RECORD_ALREADY_EXISTS error
1256
        $TListeContacts = $this->liste_contact(-1, $source);
1257
        $already_added = false;
1258
        if (is_array($TListeContacts) && !empty($TListeContacts)) {
1259
            foreach ($TListeContacts as $array_contact) {
1260
                if ($array_contact['status'] == 4 && $array_contact['id'] == $fk_socpeople && $array_contact['fk_c_type_contact'] == $id_type_contact) {
1261
                    $already_added = true;
1262
                    break;
1263
                }
1264
            }
1265
        }
1266
1267
        if (!$already_added) {
1268
            $this->db->begin();
1269
1270
            // Insert into database
1271
            $sql = "INSERT INTO " . $this->db->prefix() . "element_contact";
1272
            $sql .= " (element_id, fk_socpeople, datecreate, statut, fk_c_type_contact) ";
1273
            $sql .= " VALUES (" . $this->id . ", " . ((int) $fk_socpeople) . " , ";
1274
            $sql .= "'" . $this->db->idate($datecreate) . "'";
1275
            $sql .= ", 4, " . ((int) $id_type_contact);
1276
            $sql .= ")";
1277
1278
            $resql = $this->db->query($sql);
1279
            if ($resql) {
1280
                if (!$notrigger) {
1281
                    $result = $this->call_trigger(strtoupper($this->element) . '_ADD_CONTACT', $user);
1282
                    if ($result < 0) {
1283
                        $this->db->rollback();
1284
                        return -1;
1285
                    }
1286
                }
1287
1288
                $this->db->commit();
1289
                return 1;
1290
            } else {
1291
                if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1292
                    $this->error = $this->db->errno();
1293
                    $this->db->rollback();
1294
                    return -2;
1295
                } else {
1296
                    $this->error = $this->db->lasterror();
1297
                    $this->db->rollback();
1298
                    return -1;
1299
                }
1300
            }
1301
        } else {
1302
            return 0;
1303
        }
1304
    }
1305
1306
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1307
    /**
1308
     *    Copy contact from one element to current
1309
     *
1310
     *    @param    CommonObject    $objFrom    Source element
1311
     *    @param    string          $source     Nature of contact ('internal' or 'external')
1312
     *    @return   int                         >0 if OK, <0 if KO
1313
     */
1314
    public function copy_linked_contact($objFrom, $source = 'internal')
1315
    {
1316
        // phpcs:enable
1317
        $contacts = $objFrom->liste_contact(-1, $source);
1318
        foreach ($contacts as $contact) {
1319
            if ($this->add_contact($contact['id'], $contact['fk_c_type_contact'], $contact['source']) < 0) {
1320
                return -1;
1321
            }
1322
        }
1323
        return 1;
1324
    }
1325
1326
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1327
    /**
1328
     *      Update a link to contact line
1329
     *
1330
     *      @param  int     $rowid              Id of line contact-element
1331
     *      @param  int     $statut             New status of link
1332
     *      @param  int     $type_contact_id    Id of contact type (not modified if 0)
1333
     *      @param  int     $fk_socpeople       Id of soc_people to update (not modified if 0)
1334
     *      @return int                         Return integer <0 if KO, >= 0 if OK
1335
     */
1336
    public function update_contact($rowid, $statut, $type_contact_id = 0, $fk_socpeople = 0)
1337
    {
1338
        // phpcs:enable
1339
        // Insert into database
1340
        $sql = "UPDATE " . $this->db->prefix() . "element_contact set";
1341
        $sql .= " statut = " . $statut;
1342
        if ($type_contact_id) {
1343
            $sql .= ", fk_c_type_contact = " . ((int) $type_contact_id);
1344
        }
1345
        if ($fk_socpeople) {
1346
            $sql .= ", fk_socpeople = " . ((int) $fk_socpeople);
1347
        }
1348
        $sql .= " where rowid = " . ((int) $rowid);
1349
        $resql = $this->db->query($sql);
1350
        if ($resql) {
1351
            return 0;
1352
        } else {
1353
            $this->error = $this->db->lasterror();
1354
            return -1;
1355
        }
1356
    }
1357
1358
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1359
    /**
1360
     *    Delete a link to contact line
1361
     *
1362
     *    @param    int     $rowid          Id of contact link line to delete
1363
     *    @param    int     $notrigger      Disable all triggers
1364
     *    @return   int                     >0 if OK, <0 if KO
1365
     */
1366
    public function delete_contact($rowid, $notrigger = 0)
1367
    {
1368
        // phpcs:enable
1369
        global $user;
1370
1371
        $error = 0;
1372
1373
        $this->db->begin();
1374
1375
        if (!$error && empty($notrigger)) {
1376
            // Call trigger
1377
            $this->context['contact_id'] = ((int) $rowid);
1378
            $result = $this->call_trigger(strtoupper($this->element) . '_DELETE_CONTACT', $user);
1379
            if ($result < 0) {
1380
                $error++;
1381
            }
1382
            // End call triggers
1383
        }
1384
1385
        if (!$error) {
1386
            dol_syslog(get_class($this) . "::delete_contact", LOG_DEBUG);
1387
1388
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . "element_contact";
1389
            $sql .= " WHERE rowid = " . ((int) $rowid);
1390
1391
            $result = $this->db->query($sql);
1392
            if (!$result) {
1393
                $error++;
1394
                $this->errors[] = $this->db->lasterror();
1395
            }
1396
        }
1397
1398
        if (!$error) {
1399
            $this->db->commit();
1400
            return 1;
1401
        } else {
1402
            $this->error = $this->db->lasterror();
1403
            $this->db->rollback();
1404
            return -1;
1405
        }
1406
    }
1407
1408
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1409
    /**
1410
     *    Delete all links between an object $this and all its contacts in llx_element_contact
1411
     *
1412
     *    @param    string  $source     '' or 'internal' or 'external'
1413
     *    @param    string  $code       Type of contact (code or id)
1414
     *    @return   int                 Return integer <0 if KO, 0=Nothing done, >0 if OK
1415
     */
1416
    public function delete_linked_contact($source = '', $code = '')
1417
    {
1418
        // phpcs:enable
1419
        $listId = '';
1420
        $temp = array();
1421
        $typeContact = $this->liste_type_contact($source, '', 0, 0, $code);
1422
1423
        if (!empty($typeContact)) {
1424
            foreach ($typeContact as $key => $value) {
1425
                array_push($temp, $key);
1426
            }
1427
            $listId = implode(",", $temp);
1428
        }
1429
1430
        // If $listId is empty, we have not criteria on fk_c_type_contact so we will delete record on element_id for
1431
        // any type or record instead of only the ones of the current object. So we do nothing in such a case.
1432
        if (empty($listId)) {
1433
            return 0;
1434
        }
1435
1436
        $sql = "DELETE FROM " . $this->db->prefix() . "element_contact";
1437
        $sql .= " WHERE element_id = " . ((int) $this->id);
1438
        $sql .= " AND fk_c_type_contact IN (" . $this->db->sanitize($listId) . ")";
1439
1440
        dol_syslog(get_class($this) . "::delete_linked_contact", LOG_DEBUG);
1441
        if ($this->db->query($sql)) {
1442
            return 1;
1443
        } else {
1444
            $this->error = $this->db->lasterror();
1445
            return -1;
1446
        }
1447
    }
1448
1449
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1450
    /**
1451
     *    Get array of all contacts for an object
1452
     *
1453
     *    @param    int         $statusoflink   Status of links to get (-1=all). Not used.
1454
     *    @param    string      $source         Source of contact: 'external' or 'thirdparty' (llx_socpeople) or 'internal' (llx_user)
1455
     *    @param    int         $list           0:Returned array contains all properties, 1:Return array contains just id
1456
     *    @param    string      $code           Filter on this code of contact type ('SHIPPING', 'BILLING', ...)
1457
     *    @param    int         $status         Status of user or company
1458
     *    @param    array       $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.
1459
     *    @return   array|int                   Array of contacts, -1 if error
1460
     */
1461
    public function liste_contact($statusoflink = -1, $source = 'external', $list = 0, $code = '', $status = -1, $arrayoftcids = array())
1462
    {
1463
        // phpcs:enable
1464
        global $langs;
1465
1466
        $tab = array();
1467
1468
        $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
1469
        if ($source == 'internal') {
1470
            $sql .= ", '-1' as socid, t.statut as statuscontact, t.login, t.photo";
1471
        }
1472
        if ($source == 'external' || $source == 'thirdparty') {
1473
            $sql .= ", t.fk_soc as socid, t.statut as statuscontact";
1474
        }
1475
        $sql .= ", t.civility as civility, t.lastname as lastname, t.firstname, t.email";
1476
        $sql .= ", tc.source, tc.element, tc.code, tc.libelle as type_label";
1477
        $sql .= " FROM " . $this->db->prefix() . "c_type_contact tc,";
1478
        $sql .= " " . $this->db->prefix() . "element_contact ec";
1479
        if ($source == 'internal') {    // internal contact (user)
1480
            $sql .= " LEFT JOIN " . $this->db->prefix() . "user t on ec.fk_socpeople = t.rowid";
1481
        }
1482
        if ($source == 'external' || $source == 'thirdparty') { // external contact (socpeople)
1483
            $sql .= " LEFT JOIN " . $this->db->prefix() . "socpeople t on ec.fk_socpeople = t.rowid";
1484
        }
1485
        $sql .= " WHERE ec.element_id = " . ((int) $this->id);
1486
        $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1487
        $sql .= " AND tc.element = '" . $this->db->escape($this->element) . "'";
1488
        if ($code) {
1489
            $sql .= " AND tc.code = '" . $this->db->escape($code) . "'";
1490
        }
1491
        if ($source == 'internal') {
1492
            $sql .= " AND tc.source = 'internal'";
1493
            if ($status >= 0) {
1494
                $sql .= " AND t.statut = " . ((int) $status);
1495
            }
1496
        }
1497
        if ($source == 'external' || $source == 'thirdparty') {
1498
            $sql .= " AND tc.source = 'external'";
1499
            if ($status >= 0) {
1500
                $sql .= " AND t.statut = " . ((int) $status); // t is llx_socpeople
1501
            }
1502
        }
1503
        $sql .= " AND tc.active = 1";
1504
        if ($statusoflink >= 0) {
1505
            $sql .= " AND ec.statut = " . ((int) $statusoflink);
1506
        }
1507
        $sql .= " ORDER BY t.lastname ASC";
1508
1509
        dol_syslog(get_class($this) . "::liste_contact", LOG_DEBUG);
1510
        $resql = $this->db->query($sql);
1511
        if ($resql) {
1512
            $num = $this->db->num_rows($resql);
1513
            $i = 0;
1514
            while ($i < $num) {
1515
                $obj = $this->db->fetch_object($resql);
1516
1517
                if (!$list) {
1518
                    $transkey = "TypeContact_" . $obj->element . "_" . $obj->source . "_" . $obj->code;
1519
                    $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1520
                    $tab[$i] = array(
1521
                        'parentId' => $this->id,
1522
                        'source' => $obj->source,
1523
                        'socid' => $obj->socid,
1524
                        'id' => $obj->id,
1525
                        'nom' => $obj->lastname, // For backward compatibility
1526
                        'civility' => $obj->civility,
1527
                        'lastname' => $obj->lastname,
1528
                        'firstname' => $obj->firstname,
1529
                        'email' => $obj->email,
1530
                        'login' => (empty($obj->login) ? '' : $obj->login),
1531
                        'photo' => (empty($obj->photo) ? '' : $obj->photo),
1532
                        'statuscontact' => $obj->statuscontact,
1533
                        'rowid' => $obj->rowid,
1534
                        'code' => $obj->code,
1535
                        'libelle' => $libelle_type,
1536
                        'status' => $obj->statuslink,
1537
                        'fk_c_type_contact' => $obj->fk_c_type_contact
1538
                    );
1539
                } else {
1540
                    $tab[$i] = $obj->id;
1541
                }
1542
1543
                $i++;
1544
            }
1545
1546
            return $tab;
1547
        } else {
1548
            $this->error = $this->db->lasterror();
1549
            dol_print_error($this->db);
1550
            return -1;
1551
        }
1552
    }
1553
1554
1555
    /**
1556
     *      Update status of a contact linked to object
1557
     *
1558
     *      @param  int     $rowid      Id of link between object and contact
1559
     *      @return int                 Return integer <0 if KO, >=0 if OK
1560
     */
1561
    public function swapContactStatus($rowid)
1562
    {
1563
        $sql = "SELECT ec.datecreate, ec.statut, ec.fk_socpeople, ec.fk_c_type_contact,";
1564
        $sql .= " tc.code, tc.libelle as type_label";
1565
        $sql .= " FROM (" . $this->db->prefix() . "element_contact as ec, " . $this->db->prefix() . "c_type_contact as tc)";
1566
        $sql .= " WHERE ec.rowid =" . ((int) $rowid);
1567
        $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1568
        $sql .= " AND tc.element = '" . $this->db->escape($this->element) . "'";
1569
1570
        dol_syslog(get_class($this) . "::swapContactStatus", LOG_DEBUG);
1571
        $resql = $this->db->query($sql);
1572
        if ($resql) {
1573
            $obj = $this->db->fetch_object($resql);
1574
            $newstatut = ($obj->statut == 4) ? 5 : 4;
1575
            $result = $this->update_contact($rowid, $newstatut);
1576
            $this->db->free($resql);
1577
            return $result;
1578
        } else {
1579
            $this->error = $this->db->error();
1580
            dol_print_error($this->db);
1581
            return -1;
1582
        }
1583
    }
1584
1585
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1586
    /**
1587
     *      Return array with list of possible values for type of contacts
1588
     *
1589
     *      @param  string  $source     'internal', 'external' or 'all'
1590
     *      @param  string  $order      Sort order by : 'position', 'code', 'rowid'...
1591
     *      @param  int     $option     0=Return array id->label, 1=Return array code->label
1592
     *      @param  int     $activeonly 0=all status of contact, 1=only the active
1593
     *      @param  string  $code       Type of contact (Example: 'CUSTOMER', 'SERVICE')
1594
     *      @return array|null          Array list of type of contacts (id->label if option=0, code->label if option=1), or null if error
1595
     */
1596
    public function liste_type_contact($source = 'internal', $order = 'position', $option = 0, $activeonly = 0, $code = '')
1597
    {
1598
        // phpcs:enable
1599
        global $langs;
1600
1601
        if (empty($order)) {
1602
            $order = 'position';
1603
        }
1604
        if ($order == 'position') {
1605
            $order .= ',code';
1606
        }
1607
1608
        $tab = array();
1609
        $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position";
1610
        $sql .= " FROM " . $this->db->prefix() . "c_type_contact as tc";
1611
        $sql .= " WHERE tc.element='" . $this->db->escape($this->element) . "'";
1612
        if ($activeonly == 1) {
1613
            $sql .= " AND tc.active=1"; // only the active types
1614
        }
1615
        if (!empty($source) && $source != 'all') {
1616
            $sql .= " AND tc.source='" . $this->db->escape($source) . "'";
1617
        }
1618
        if (!empty($code)) {
1619
            $sql .= " AND tc.code='" . $this->db->escape($code) . "'";
1620
        }
1621
        $sql .= $this->db->order($order, 'ASC');
1622
1623
        //print "sql=".$sql;
1624
        $resql = $this->db->query($sql);
1625
        if ($resql) {
1626
            $num = $this->db->num_rows($resql);
1627
            $i = 0;
1628
            while ($i < $num) {
1629
                $obj = $this->db->fetch_object($resql);
1630
1631
                $transkey = "TypeContact_" . $this->element . "_" . $source . "_" . $obj->code;
1632
                $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1633
                if (empty($option)) {
1634
                    $tab[$obj->rowid] = $libelle_type;
1635
                } else {
1636
                    $tab[$obj->code] = $libelle_type;
1637
                }
1638
                $i++;
1639
            }
1640
            return $tab;
1641
        } else {
1642
            $this->error = $this->db->lasterror();
1643
            //dol_print_error($this->db);
1644
            return null;
1645
        }
1646
    }
1647
1648
    /**
1649
     *      Return array with list of possible values for type of contacts
1650
     *
1651
     *      @param  string  $source             'internal', 'external' or 'all'
1652
     *      @param  int     $option             0=Return array id->label, 1=Return array code->label
1653
     *      @param  int     $activeonly         0=all status of contact, 1=only the active
1654
     *      @param  string  $code               Type of contact (Example: 'CUSTOMER', 'SERVICE')
1655
     *      @param  string  $element            Filter on 1 element type
1656
     *      @param  string  $excludeelement     Exclude 1 element type. Example: 'agenda'
1657
     *      @return array|null                  Array list of type of contacts (id->label if option=0, code->label if option=1), or null if error
1658
     */
1659
    public function listeTypeContacts($source = 'internal', $option = 0, $activeonly = 0, $code = '', $element = '', $excludeelement = '')
1660
    {
1661
        global $langs, $conf;
1662
1663
        $langs->loadLangs(array('bills', 'contracts', 'interventions', 'orders', 'projects', 'propal', 'ticket', 'agenda'));
1664
1665
        $tab = array();
1666
1667
        $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position, tc.element";
1668
        $sql .= " FROM " . $this->db->prefix() . "c_type_contact as tc";
1669
1670
        $sqlWhere = array();
1671
        if (!empty($element)) {
1672
            $sqlWhere[] = " tc.element='" . $this->db->escape($element) . "'";
1673
        }
1674
        if (!empty($excludeelement)) {
1675
            $sqlWhere[] = " tc.element <> '" . $this->db->escape($excludeelement) . "'";
1676
        }
1677
1678
        if ($activeonly == 1) {
1679
            $sqlWhere[] = " tc.active=1"; // only the active types
1680
        }
1681
1682
        if (!empty($source) && $source != 'all') {
1683
            $sqlWhere[] = " tc.source='" . $this->db->escape($source) . "'";
1684
        }
1685
1686
        if (!empty($code)) {
1687
            $sqlWhere[] = " tc.code='" . $this->db->escape($code) . "'";
1688
        }
1689
1690
        if (count($sqlWhere) > 0) {
1691
            $sql .= " WHERE " . implode(' AND ', $sqlWhere);
1692
        }
1693
1694
        $sql .= $this->db->order('tc.element, tc.position', 'ASC');
1695
1696
        dol_syslog(__METHOD__, LOG_DEBUG);
1697
        $resql = $this->db->query($sql);
1698
        if ($resql) {
1699
            $num = $this->db->num_rows($resql);
1700
            if ($num > 0) {
1701
                $langs->loadLangs(array("propal", "orders", "bills", "suppliers", "contracts", "supplier_proposal"));
1702
1703
                while ($obj = $this->db->fetch_object($resql)) {
1704
                    $modulename = $obj->element;
1705
                    if (strpos($obj->element, 'project') !== false) {
1706
                        $modulename = 'projet';
1707
                    } elseif ($obj->element == 'contrat') {
1708
                        $element = 'contract';
1709
                    } elseif ($obj->element == 'action') {
1710
                        $modulename = 'agenda';
1711
                    } elseif (strpos($obj->element, 'supplier') !== false && $obj->element != 'supplier_proposal') {
1712
                        $modulename = 'fournisseur';
1713
                    }
1714
                    if (!empty($conf->{$modulename}->enabled)) {
1715
                        $libelle_element = $langs->trans('ContactDefault_' . $obj->element);
1716
                        $tmpelement = $obj->element;
1717
                        $transkey = "TypeContact_" . $tmpelement . "_" . $source . "_" . $obj->code;
1718
                        $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1719
                        $tab[$obj->rowid] = $libelle_element . ' - ' . $libelle_type;
1720
                    }
1721
                }
1722
            }
1723
            return $tab;
1724
        } else {
1725
            $this->error = $this->db->lasterror();
1726
            return null;
1727
        }
1728
    }
1729
1730
    /**
1731
     *      Return id of contacts for a source and a contact code.
1732
     *      Example: contact client de facturation ('external', 'BILLING')
1733
     *      Example: contact client de livraison ('external', 'SHIPPING')
1734
     *      Example: contact interne suivi paiement ('internal', 'SALESREPFOLL')
1735
     *
1736
     *      @param  string  $source     'external' or 'internal'
1737
     *      @param  string  $code       'BILLING', 'SHIPPING', 'SALESREPFOLL', ...
1738
     *      @param  int     $status     limited to a certain status
1739
     *      @return array|null          List of id for such contacts, or null if error
1740
     */
1741
    public function getIdContact($source, $code, $status = 0)
1742
    {
1743
        global $conf;
1744
1745
        $result = array();
1746
        $i = 0;
1747
        // Particular case for shipping
1748
        if ($this->element == 'shipping' && $this->origin_id != 0) {
1749
            $id = $this->origin_id;
1750
            $element = 'commande';
1751
        } elseif ($this->element == 'reception' && $this->origin_id != 0) {
1752
            $id = $this->origin_id;
1753
            $element = 'order_supplier';
1754
        } else {
1755
            $id = $this->id;
1756
            $element = $this->element;
1757
        }
1758
1759
        $sql = "SELECT ec.fk_socpeople";
1760
        $sql .= " FROM " . $this->db->prefix() . "element_contact as ec,";
1761
        if ($source == 'internal') {
1762
            $sql .= " " . $this->db->prefix() . "user as c,";
1763
        }
1764
        if ($source == 'external') {
1765
            $sql .= " " . $this->db->prefix() . "socpeople as c,";
1766
        }
1767
        $sql .= " " . $this->db->prefix() . "c_type_contact as tc";
1768
        $sql .= " WHERE ec.element_id = " . ((int) $id);
1769
        $sql .= " AND ec.fk_socpeople = c.rowid";
1770
        if ($source == 'internal') {
1771
            $sql .= " AND c.entity IN (" . getEntity('user') . ")";
1772
        }
1773
        if ($source == 'external') {
1774
            $sql .= " AND c.entity IN (" . getEntity('societe') . ")";
1775
        }
1776
        $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1777
        $sql .= " AND tc.element = '" . $this->db->escape($element) . "'";
1778
        $sql .= " AND tc.source = '" . $this->db->escape($source) . "'";
1779
        if ($code) {
1780
            $sql .= " AND tc.code = '" . $this->db->escape($code) . "'";
1781
        }
1782
        $sql .= " AND tc.active = 1";
1783
        if ($status) {
1784
            $sql .= " AND ec.statut = " . ((int) $status);
1785
        }
1786
1787
        dol_syslog(get_class($this) . "::getIdContact", LOG_DEBUG);
1788
        $resql = $this->db->query($sql);
1789
        if ($resql) {
1790
            while ($obj = $this->db->fetch_object($resql)) {
1791
                $result[$i] = $obj->fk_socpeople;
1792
                $i++;
1793
            }
1794
        } else {
1795
            $this->error = $this->db->error();
1796
            return null;
1797
        }
1798
1799
        return $result;
1800
    }
1801
1802
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1803
    /**
1804
     *      Load object contact with id=$this->contact_id into $this->contact
1805
     *
1806
     *      @param  int     $contactid      Id du contact. Use this->contact_id if empty.
1807
     *      @return int                     Return integer <0 if KO, >0 if OK
1808
     */
1809
    public function fetch_contact($contactid = null)
1810
    {
1811
        // phpcs:enable
1812
        if (empty($contactid)) {
1813
            $contactid = $this->contact_id;
1814
        }
1815
1816
        if (empty($contactid)) {
1817
            return 0;
1818
        }
1819
1820
        require_once DOL_DOCUMENT_ROOT . '/contact/class/contact.class.php';
1821
        $contact = new Contact($this->db);
1822
        $result = $contact->fetch($contactid);
1823
        $this->contact = $contact;
1824
        return $result;
1825
    }
1826
1827
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1828
    /**
1829
     *      Load the third party of object, from id $this->socid or $this->fk_soc, into this->thirdparty
1830
     *
1831
     *      @param      int     $force_thirdparty_id    Force thirdparty id
1832
     *      @return     int                             Return integer <0 if KO, >0 if OK
1833
     */
1834
    public function fetch_thirdparty($force_thirdparty_id = 0)
1835
    {
1836
        // phpcs:enable
1837
        if (empty($this->socid) && empty($this->fk_soc) && empty($force_thirdparty_id)) {
1838
            return 0;
1839
        }
1840
1841
        require_once DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php';
1842
1843
        $idtofetch = isset($this->socid) ? $this->socid : (isset($this->fk_soc) ? $this->fk_soc : 0);
1844
        if ($force_thirdparty_id) {
1845
            $idtofetch = $force_thirdparty_id;
1846
        }
1847
1848
        if ($idtofetch) {
1849
            $thirdparty = new Societe($this->db);
1850
            $result = $thirdparty->fetch($idtofetch);
1851
            if ($result < 0) {
1852
                $this->errors = array_merge($this->errors, $thirdparty->errors);
1853
            }
1854
            $this->thirdparty = $thirdparty;
1855
1856
            // Use first price level if level not defined for third party
1857
            if (getDolGlobalString('PRODUIT_MULTIPRICES') && empty($this->thirdparty->price_level)) {
1858
                $this->thirdparty->price_level = 1;
1859
            }
1860
1861
            return $result;
1862
        } else {
1863
            return -1;
1864
        }
1865
    }
1866
1867
1868
    /**
1869
     * Looks for an object with ref matching the wildcard provided
1870
     * It does only work when $this->table_ref_field is set
1871
     *
1872
     * @param   string  $ref    Wildcard
1873
     * @return  int             >1 = OK, 0 = Not found or table_ref_field not defined, <0 = KO
1874
     */
1875
    public function fetchOneLike($ref)
1876
    {
1877
        if (!$this->table_ref_field) {
1878
            return 0;
1879
        }
1880
1881
        $sql = "SELECT rowid FROM " . $this->db->prefix() . $this->table_element;
1882
        $sql .= " WHERE " . $this->table_ref_field . " LIKE '" . $this->db->escape($ref) . "'"; // no escapeforlike here
1883
        $sql .= " LIMIT 1";
1884
1885
        $query = $this->db->query($sql);
1886
1887
        if (!$this->db->num_rows($query)) {
1888
            return 0;
1889
        }
1890
1891
        $result = $this->db->fetch_object($query);
1892
1893
        if (method_exists($this, 'fetch')) {
1894
            return $this->fetch($result->rowid);
1895
        } else {
1896
            $this->error = 'Fetch method not implemented on ' . get_class($this);
1897
            dol_syslog(get_class($this) . '::fetchOneLike Error=' . $this->error, LOG_ERR);
1898
            array_push($this->errors, $this->error);
1899
            return -1;
1900
        }
1901
    }
1902
1903
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1904
    /**
1905
     *  Load data for barcode into properties ->barcode_type*
1906
     *  Properties ->barcode_type that is id of barcode. Type is used to find other properties, but
1907
     *  if it is not defined, ->element must be defined to know default barcode type.
1908
     *
1909
     *  @return     int         Return integer <0 if KO, 0 if can't guess type of barcode (ISBN, EAN13...), >0 if OK (all barcode properties loaded)
1910
     */
1911
    public function fetch_barcode()
1912
    {
1913
        // phpcs:enable
1914
        global $conf;
1915
1916
        dol_syslog(get_class($this) . '::fetch_barcode this->element=' . $this->element . ' this->barcode_type=' . $this->barcode_type);
1917
1918
        $idtype = $this->barcode_type;
1919
        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
1920
            if ($this->element == 'product' && getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE')) {
1921
                $idtype = getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE');
1922
            } elseif ($this->element == 'societe') {
1923
                $idtype = getDolGlobalString('GENBARCODE_BARCODETYPE_THIRDPARTY');
1924
            } else {
1925
                dol_syslog('Call fetch_barcode with barcode_type not defined and cannot be guessed', LOG_WARNING);
1926
            }
1927
        }
1928
1929
        if ($idtype > 0) {
1930
            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
1931
                $sql = "SELECT rowid, code, libelle as label, coder";
1932
                $sql .= " FROM " . $this->db->prefix() . "c_barcode_type";
1933
                $sql .= " WHERE rowid = " . ((int) $idtype);
1934
                dol_syslog(get_class($this) . '::fetch_barcode', LOG_DEBUG);
1935
                $resql = $this->db->query($sql);
1936
                if ($resql) {
1937
                    $obj = $this->db->fetch_object($resql);
1938
                    $this->barcode_type       = $obj->rowid;
1939
                    $this->barcode_type_code  = $obj->code;
1940
                    $this->barcode_type_label = $obj->label;
1941
                    $this->barcode_type_coder = $obj->coder;
1942
                    return 1;
1943
                } else {
1944
                    dol_print_error($this->db);
1945
                    return -1;
1946
                }
1947
            }
1948
        }
1949
        return 0;
1950
    }
1951
1952
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1953
    /**
1954
     *      Load the project with id $this->fk_project into this->project
1955
     *
1956
     *      @return     int         Return integer <0 if KO, >=0 if OK
1957
     */
1958
    public function fetch_project()
1959
    {
1960
        // phpcs:enable
1961
        return $this->fetch_projet();
1962
    }
1963
1964
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1965
    /**
1966
     *      Load the project with id $this->fk_project into this->project
1967
     *
1968
     *      @return     int         Return integer <0 if KO, >=0 if OK
1969
     */
1970
    public function fetch_projet()
1971
    {
1972
        // phpcs:enable
1973
        include_once DOL_DOCUMENT_ROOT . '/projet/class/project.class.php';
1974
1975
        if (empty($this->fk_project) && !empty($this->fk_projet)) {
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocument::$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

1975
        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...
1976
            $this->fk_project = $this->fk_projet; // For backward compatibility
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocument::$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

1976
            $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...
1977
        }
1978
        if (empty($this->fk_project)) {
1979
            return 0;
1980
        }
1981
1982
        $project = new Project($this->db);
1983
        $result = $project->fetch($this->fk_project);
1984
1985
        $this->projet = $project; // deprecated
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocument::$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

1985
        /** @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...
1986
        $this->project = $project;
1987
        return $result;
1988
    }
1989
1990
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1991
    /**
1992
     *      Load the product with id $this->fk_product into this->product
1993
     *
1994
     *      @return     int         Return integer <0 if KO, >=0 if OK
1995
     */
1996
    public function fetch_product()
1997
    {
1998
        // phpcs:enable
1999
        include_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
2000
2001
        if (empty($this->fk_product)) {
2002
            return 0;
2003
        }
2004
2005
        $product = new Product($this->db);
2006
        $result = $product->fetch($this->fk_product);
2007
2008
        $this->product = $product;
2009
        return $result;
2010
    }
2011
2012
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2013
    /**
2014
     *      Load the user with id $userid into this->user
2015
     *
2016
     *      @param  int     $userid         Id du contact
2017
     *      @return int                     Return integer <0 if KO, >0 if OK
2018
     */
2019
    public function fetch_user($userid)
2020
    {
2021
        // phpcs:enable
2022
        $user = new User($this->db);
2023
        $result = $user->fetch($userid);
2024
        $this->user = $user;
2025
        return $result;
2026
    }
2027
2028
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2029
    /**
2030
     *  Read linked origin object.
2031
     *  Set ->origin_object
2032
     *  Set also ->expedition or ->livraison or ->commandFournisseur (deprecated)
2033
     *
2034
     *  @return     void
2035
     */
2036
    public function fetch_origin()
2037
    {
2038
        // phpcs:enable
2039
        if ($this->origin == 'shipping') {
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocument::$origin has been deprecated: Use now $origin_type and $origin_id; ( Ignorable by Annotation )

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

2039
        if (/** @scrutinizer ignore-deprecated */ $this->origin == 'shipping') {

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...
2040
            $this->origin = 'expedition';
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocument::$origin has been deprecated: Use now $origin_type and $origin_id; ( Ignorable by Annotation )

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

2040
            /** @scrutinizer ignore-deprecated */ $this->origin = 'expedition';

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...
2041
        }
2042
        if ($this->origin == 'delivery') {
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocument::$origin has been deprecated: Use now $origin_type and $origin_id; ( Ignorable by Annotation )

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

2042
        if (/** @scrutinizer ignore-deprecated */ $this->origin == 'delivery') {

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...
2043
            $this->origin = 'livraison';
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocument::$origin has been deprecated: Use now $origin_type and $origin_id; ( Ignorable by Annotation )

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

2043
            /** @scrutinizer ignore-deprecated */ $this->origin = 'livraison';

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...
2044
        }
2045
        if ($this->origin == 'order_supplier' || $this->origin == 'supplier_order') {
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocument::$origin has been deprecated: Use now $origin_type and $origin_id; ( Ignorable by Annotation )

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

2045
        if ($this->origin == 'order_supplier' || /** @scrutinizer ignore-deprecated */ $this->origin == 'supplier_order') {

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...
2046
            $this->origin = 'commandeFournisseur';
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocument::$origin has been deprecated: Use now $origin_type and $origin_id; ( Ignorable by Annotation )

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

2046
            /** @scrutinizer ignore-deprecated */ $this->origin = 'commandeFournisseur';

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...
2047
        }
2048
2049
        $origin = $this->origin;
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocument::$origin has been deprecated: Use now $origin_type and $origin_id; ( Ignorable by Annotation )

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

2049
        $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...
2050
2051
        $classname = ucfirst($origin);
2052
        $this->origin_object = new $classname($this->db);
2053
        $this->origin_object->fetch($this->origin_id);
2054
2055
        // TODO Remove this line
2056
        $this->$origin = $this->origin_object;
2057
    }
2058
2059
    /**
2060
     *  Load object from specific field
2061
     *
2062
     *  @param  string  $table      Table element or element line
2063
     *  @param  string  $field      Field selected
2064
     *  @param  string  $key        Import key
2065
     *  @param  string  $element    Element name
2066
     *  @return int|false           Return -1 or false if KO, >0 if OK
2067
     */
2068
    public function fetchObjectFrom($table, $field, $key, $element = null)
2069
    {
2070
        global $conf;
2071
2072
        $result = false;
2073
2074
        $sql = "SELECT rowid FROM " . $this->db->prefix() . $table;
2075
        $sql .= " WHERE " . $field . " = '" . $this->db->escape($key) . "'";
2076
        if (!empty($element)) {
2077
            $sql .= " AND entity IN (" . getEntity($element) . ")";
2078
        } else {
2079
            $sql .= " AND entity = " . ((int) $conf->entity);
2080
        }
2081
2082
        dol_syslog(get_class($this) . '::fetchObjectFrom', LOG_DEBUG);
2083
        $resql = $this->db->query($sql);
2084
        if ($resql) {
2085
            $obj = $this->db->fetch_object($resql);
2086
            // Test for avoid error -1
2087
            if ($obj) {
2088
                if (method_exists($this, 'fetch')) {
2089
                    $result = $this->fetch($obj->rowid);
2090
                } else {
2091
                    $this->error = 'fetch() method not implemented on ' . get_class($this);
2092
                    dol_syslog(get_class($this) . '::fetchOneLike Error=' . $this->error, LOG_ERR);
2093
                    array_push($this->errors, $this->error);
2094
                    $result = -1;
2095
                }
2096
            }
2097
        }
2098
2099
        return $result;
2100
    }
2101
2102
    /**
2103
     *  Getter generic. Load value from a specific field
2104
     *
2105
     *  @param  string  $table      Table of element or element line
2106
     *  @param  int     $id         Element id
2107
     *  @param  string  $field      Field selected
2108
     *  @return int                 Return integer <0 if KO, >0 if OK
2109
     */
2110
    public function getValueFrom($table, $id, $field)
2111
    {
2112
        $result = false;
2113
        if (!empty($id) && !empty($field) && !empty($table)) {
2114
            $sql = "SELECT " . $field . " FROM " . $this->db->prefix() . $table;
2115
            $sql .= " WHERE rowid = " . ((int) $id);
2116
2117
            dol_syslog(get_class($this) . '::getValueFrom', LOG_DEBUG);
2118
            $resql = $this->db->query($sql);
2119
            if ($resql) {
2120
                $row = $this->db->fetch_row($resql);
2121
                $result = $row[0];
2122
            }
2123
        }
2124
        return $result;
2125
    }
2126
2127
    /**
2128
     *  Setter generic. Update a specific field into database.
2129
     *  Warning: Trigger is run only if param trigkey is provided.
2130
     *
2131
     *  @param  string      $field          Field to update
2132
     *  @param  mixed       $value          New value
2133
     *  @param  string      $table          To force other table element or element line (should not be used)
2134
     *  @param  int         $id             To force other object id (should not be used)
2135
     *  @param  string      $format         Data format ('text', 'int', 'date'). 'text' is used if not defined
2136
     *  @param  string      $id_field       To force rowid field name. 'rowid' is used if not defined
2137
     *  @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'
2138
     *  @param  string      $trigkey        Trigger key to run (in most cases something like 'XXX_MODIFY')
2139
     *  @param  string      $fk_user_field  Name of field to save user id making change
2140
     *  @return int                         Return integer <0 if KO, >0 if OK
2141
     *  @see updateExtraField()
2142
     */
2143
    public function setValueFrom($field, $value, $table = '', $id = null, $format = '', $id_field = '', $fuser = null, $trigkey = '', $fk_user_field = 'fk_user_modif')
2144
    {
2145
        global $user;
2146
2147
        if (empty($table)) {
2148
            $table = $this->table_element;
2149
        }
2150
        if (empty($id)) {
2151
            $id = $this->id;
2152
        }
2153
        if (empty($format)) {
2154
            $format = 'text';
2155
        }
2156
        if (empty($id_field)) {
2157
            $id_field = 'rowid';
2158
        }
2159
2160
        // Special case
2161
        if ($table == 'product' && $field == 'note_private') {
2162
            $field = 'note';
2163
        }
2164
2165
        if (in_array($table, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
2166
            $fk_user_field = 'fk_user_mod';
2167
        }
2168
        if (in_array($table, array('prelevement_bons'))) {  // TODO Add a field fk_user_modif into llx_prelevement_bons
2169
            $fk_user_field = '';
2170
        }
2171
2172
        if ($trigkey) {
2173
            $oldvalue = null;
2174
2175
            $sql = "SELECT " . $field;
2176
            $sql .= " FROM " . MAIN_DB_PREFIX . $table;
2177
            $sql .= " WHERE " . $id_field . " = " . ((int) $id);
2178
2179
            $resql = $this->db->query($sql);
2180
            if ($resql) {
2181
                if ($obj = $this->db->fetch_object($resql)) {
2182
                    if ($format == 'date') {
2183
                        $oldvalue = $this->db->jdate($obj->$field);
2184
                    } else {
2185
                        $oldvalue = $obj->$field;
2186
                    }
2187
                }
2188
            } else {
2189
                $this->error = $this->db->lasterror();
2190
                return -1;
2191
            }
2192
        }
2193
2194
        $error = 0;
2195
2196
        dol_syslog(__METHOD__, LOG_DEBUG);
2197
2198
        $this->db->begin();
2199
2200
        $sql = "UPDATE " . $this->db->prefix() . $table . " SET ";
2201
2202
        if ($format == 'text') {
2203
            $sql .= $field . " = '" . $this->db->escape($value) . "'";
2204
        } elseif ($format == 'int') {
2205
            $sql .= $field . " = " . ((int) $value);
2206
        } elseif ($format == 'date') {
2207
            $sql .= $field . " = " . ($value ? "'" . $this->db->idate($value) . "'" : "null");
2208
        } elseif ($format == 'dategmt') {
2209
            $sql .= $field . " = " . ($value ? "'" . $this->db->idate($value, 'gmt') . "'" : "null");
2210
        }
2211
2212
        if ($fk_user_field) {
2213
            if (!empty($fuser) && is_object($fuser)) {
2214
                $sql .= ", " . $fk_user_field . " = " . ((int) $fuser->id);
2215
            } elseif (empty($fuser) || $fuser != 'none') {
2216
                $sql .= ", " . $fk_user_field . " = " . ((int) $user->id);
2217
            }
2218
        }
2219
2220
        $sql .= " WHERE " . $id_field . " = " . ((int) $id);
2221
2222
        $resql = $this->db->query($sql);
2223
        if ($resql) {
2224
            if ($trigkey) {
2225
                // call trigger with updated object values
2226
                if (method_exists($this, 'fetch')) {
2227
                    $result = $this->fetch($id);
2228
                } else {
2229
                    $result = $this->fetchCommon($id);
2230
                }
2231
                $this->oldcopy = clone $this;
0 ignored issues
show
Documentation Bug introduced by
It seems like clone $this of type DoliCore\Base\GenericDocument is incompatible with the declared type CommonObject of property $oldcopy.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
2232
                if (property_exists($this->oldcopy, $field)) {
2233
                    $this->oldcopy->$field = $oldvalue;
2234
                }
2235
2236
                if ($result >= 0) {
2237
                    $result = $this->call_trigger($trigkey, (!empty($fuser) && is_object($fuser)) ? $fuser : $user); // This may set this->errors
2238
                }
2239
                if ($result < 0) {
2240
                    $error++;
2241
                }
2242
            }
2243
2244
            if (!$error) {
2245
                if (property_exists($this, $field)) {
2246
                    $this->$field = $value;
2247
                }
2248
                $this->db->commit();
2249
                return 1;
2250
            } else {
2251
                $this->db->rollback();
2252
                return -2;
2253
            }
2254
        } else {
2255
            if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
2256
                $this->error = 'DB_ERROR_RECORD_ALREADY_EXISTS';
2257
            } else {
2258
                $this->error = $this->db->lasterror();
2259
            }
2260
            $this->db->rollback();
2261
            return -1;
2262
        }
2263
    }
2264
2265
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2266
    /**
2267
     *      Load properties id_previous and id_next by comparing $fieldid with $this->ref
2268
     *
2269
     *      @param  string  $filter     Optional SQL filter. Example: "(t.field1 = 'aa' OR t.field2 = 'bb')". Do not allow user input data here.
2270
     *                                  Use SQL and not Universal Search Filter. @TODO Replace this with an USF string after changing all ->next_prev_filter
2271
     *      @param  string  $fieldid    Name of field to use for the select MAX and MIN
2272
     *      @param  int     $nodbprefix Do not include DB prefix to forge table name
2273
     *      @return int                 Return integer <0 if KO, >0 if OK
2274
     */
2275
    public function load_previous_next_ref($filter, $fieldid, $nodbprefix = 0)
2276
    {
2277
        // phpcs:enable
2278
        global $conf, $user;
2279
2280
        if (!$this->table_element) {
2281
            dol_print_error(null, get_class($this) . "::load_previous_next_ref was called on object with property table_element not defined");
2282
            return -1;
2283
        }
2284
        if ($fieldid == 'none') {
2285
            return 1;
2286
        }
2287
2288
        // For backward compatibility
2289
        if (in_array($this->table_element, array('facture_rec', 'facture_fourn_rec')) && $fieldid == 'title') {
2290
            $fieldid = 'titre';
2291
        }
2292
2293
        // Security on socid
2294
        $socid = 0;
2295
        if ($user->socid > 0) {
2296
            $socid = $user->socid;
2297
        }
2298
2299
        // this->ismultientitymanaged contains
2300
        // 0=No test on entity, 1=Test with field entity, 'field@table'=Test with link by field@table
2301
        $aliastablesociete = 's';
2302
        if ($this->element == 'societe') {
2303
            $aliastablesociete = 'te'; // te as table_element
2304
        }
2305
        $restrictiononfksoc = empty($this->restrictiononfksoc) ? 0 : $this->restrictiononfksoc;
2306
        $sql = "SELECT MAX(te." . $fieldid . ")";
2307
        $sql .= " FROM " . (empty($nodbprefix) ? $this->db->prefix() : '') . $this->table_element . " as te";
2308
        if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2309
            $tmparray = explode('@', $this->ismultientitymanaged);
2310
            $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
2311
        } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2312
            $sql .= ", " . $this->db->prefix() . "societe as s"; // If we need to link to societe to limit select to socid
2313
        } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2314
            $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
2315
        }
2316
        if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2317
            $sql .= " LEFT JOIN " . $this->db->prefix() . "societe_commerciaux as sc ON " . $aliastablesociete . ".rowid = sc.fk_soc";
2318
        }
2319
        if ($fieldid == 'rowid') {
2320
            $sql .= " WHERE te." . $fieldid . " < " . ((int) $this->id);
2321
        } else {
2322
            $sql .= " WHERE te." . $fieldid . " < '" . $this->db->escape($this->ref) . "'"; // ->ref must always be defined (set to id if field does not exists)
2323
        }
2324
        if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2325
            $sql .= " AND sc.fk_user = " . ((int) $user->id);
2326
        }
2327
        if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2328
            $sql .= " AND (sc.fk_user = " . ((int) $user->id) . ' OR te.fk_soc IS NULL)';
2329
        }
2330
        if (!empty($filter)) {
2331
            if (!preg_match('/^\s*AND/i', $filter)) {
2332
                $sql .= " AND ";
2333
            }
2334
            $sql .= $filter;
2335
        }
2336
        if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2337
            $tmparray = explode('@', $this->ismultientitymanaged);
2338
            $sql .= " AND te." . $tmparray[0] . " = " . ($tmparray[1] == "societe" ? "s" : "parenttable") . ".rowid"; // If we need to link to this table to limit select to entity
2339
        } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2340
            $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2341
        }
2342
        if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2343
            if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2344
                if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2345
                    $sql .= " AND te.entity IS NOT NULL"; // Show all users
2346
                } else {
2347
                    $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM " . $this->db->prefix() . "usergroup_user as ug WHERE ug.entity IN (" . getEntity('usergroup') . "))";
2348
                }
2349
            } else {
2350
                $sql .= ' AND te.entity IN (' . getEntity($this->element) . ')';
2351
            }
2352
        }
2353
        if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2354
            $tmparray = explode('@', $this->ismultientitymanaged);
2355
            $sql .= ' AND parenttable.entity IN (' . getEntity($tmparray[1]) . ')';
2356
        }
2357
        if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2358
            $sql .= ' AND te.fk_soc = ' . ((int) $socid);
2359
        }
2360
        if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2361
            $sql .= ' AND (te.fk_soc = ' . ((int) $socid) . ' OR te.fk_soc IS NULL)';
2362
        }
2363
        if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2364
            $sql .= ' AND te.rowid = ' . ((int) $socid);
2365
        }
2366
        //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2367
2368
        $result = $this->db->query($sql);
2369
        if (!$result) {
2370
            $this->error = $this->db->lasterror();
2371
            return -1;
2372
        }
2373
        $row = $this->db->fetch_row($result);
2374
        $this->ref_previous = $row[0];
2375
2376
        $sql = "SELECT MIN(te." . $fieldid . ")";
2377
        $sql .= " FROM " . (empty($nodbprefix) ? $this->db->prefix() : '') . $this->table_element . " as te";
2378
        if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2379
            $tmparray = explode('@', $this->ismultientitymanaged);
2380
            $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
2381
        } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2382
            $sql .= ", " . $this->db->prefix() . "societe as s"; // If we need to link to societe to limit select to socid
2383
        } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2384
            $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
2385
        }
2386
        if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2387
            $sql .= " LEFT JOIN " . $this->db->prefix() . "societe_commerciaux as sc ON " . $aliastablesociete . ".rowid = sc.fk_soc";
2388
        }
2389
        if ($fieldid == 'rowid') {
2390
            $sql .= " WHERE te." . $fieldid . " > " . ((int) $this->id);
2391
        } else {
2392
            $sql .= " WHERE te." . $fieldid . " > '" . $this->db->escape($this->ref) . "'"; // ->ref must always be defined (set to id if field does not exists)
2393
        }
2394
        if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2395
            $sql .= " AND (sc.fk_user = " . ((int) $user->id);
2396
            if (getDolGlobalInt('MAIN_SEE_SUBORDINATES')) {
2397
                $userschilds = $user->getAllChildIds();
2398
                $sql .= " OR sc.fk_user IN (" . $this->db->sanitize(implode(',', $userschilds)) . ")";
2399
            }
2400
            $sql .= ')';
2401
        }
2402
        if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2403
            $sql .= " AND (sc.fk_user = " . ((int) $user->id) . ' OR te.fk_soc IS NULL)';
2404
        }
2405
        if (!empty($filter)) {
2406
            if (!preg_match('/^\s*AND/i', $filter)) {
2407
                $sql .= " AND "; // For backward compatibility
2408
            }
2409
            $sql .= $filter;
2410
        }
2411
        if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2412
            $tmparray = explode('@', $this->ismultientitymanaged);
2413
            $sql .= " AND te." . $tmparray[0] . " = " . ($tmparray[1] == "societe" ? "s" : "parenttable") . ".rowid"; // If we need to link to this table to limit select to entity
2414
        } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2415
            $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2416
        }
2417
        if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2418
            if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2419
                if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2420
                    $sql .= " AND te.entity IS NOT NULL"; // Show all users
2421
                } else {
2422
                    $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM " . $this->db->prefix() . "usergroup_user as ug WHERE ug.entity IN (" . getEntity('usergroup') . "))";
2423
                }
2424
            } else {
2425
                $sql .= ' AND te.entity IN (' . getEntity($this->element) . ')';
2426
            }
2427
        }
2428
        if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2429
            $tmparray = explode('@', $this->ismultientitymanaged);
2430
            $sql .= ' AND parenttable.entity IN (' . getEntity($tmparray[1]) . ')';
2431
        }
2432
        if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2433
            $sql .= ' AND te.fk_soc = ' . ((int) $socid);
2434
        }
2435
        if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2436
            $sql .= ' AND (te.fk_soc = ' . ((int) $socid) . ' OR te.fk_soc IS NULL)';
2437
        }
2438
        if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2439
            $sql .= ' AND te.rowid = ' . ((int) $socid);
2440
        }
2441
        //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2442
        // 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
2443
2444
        $result = $this->db->query($sql);
2445
        if (!$result) {
2446
            $this->error = $this->db->lasterror();
2447
            return -2;
2448
        }
2449
        $row = $this->db->fetch_row($result);
2450
        $this->ref_next = $row[0];
2451
2452
        return 1;
2453
    }
2454
2455
2456
    /**
2457
     *      Return list of id of contacts of object
2458
     *
2459
     *      @param  string  $source     Source of contact: external (llx_socpeople) or internal (llx_user) or thirdparty (llx_societe)
2460
     *      @return array               Array of id of contacts (if source=external or internal)
2461
     *                                  Array of id of third parties with at least one contact on object (if source=thirdparty)
2462
     */
2463
    public function getListContactId($source = 'external')
2464
    {
2465
        $contactAlreadySelected = array();
2466
        $tab = $this->liste_contact(-1, $source);
2467
        $num = count($tab);
2468
        $i = 0;
2469
        while ($i < $num) {
2470
            if ($source == 'thirdparty') {
2471
                $contactAlreadySelected[$i] = $tab[$i]['socid'];
2472
            } else {
2473
                $contactAlreadySelected[$i] = $tab[$i]['id'];
2474
            }
2475
            $i++;
2476
        }
2477
        return $contactAlreadySelected;
2478
    }
2479
2480
2481
    /**
2482
     *  Link element with a project
2483
     *
2484
     *  @param      int     $projectid      Project id to link element to
2485
     *  @param      int     $notrigger      Disable the trigger
2486
     *  @return     int                     Return integer <0 if KO, >0 if OK
2487
     */
2488
    public function setProject($projectid, $notrigger = 0)
2489
    {
2490
        global $user;
2491
        $error = 0;
2492
2493
        if (!$this->table_element) {
2494
            dol_syslog(get_class($this) . "::setProject was called on object with property table_element not defined", LOG_ERR);
2495
            return -1;
2496
        }
2497
2498
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
2499
        if (!empty($this->fields['fk_project'])) {      // Common case
2500
            if ($projectid) {
2501
                $sql .= " SET fk_project = " . ((int) $projectid);
2502
            } else {
2503
                $sql .= " SET fk_project = NULL";
2504
            }
2505
            $sql .= ' WHERE rowid = ' . ((int) $this->id);
2506
        } elseif ($this->table_element == 'actioncomm') {   // Special case for actioncomm
2507
            if ($projectid) {
2508
                $sql .= " SET fk_project = " . ((int) $projectid);
2509
            } else {
2510
                $sql .= " SET fk_project = NULL";
2511
            }
2512
            $sql .= ' WHERE id = ' . ((int) $this->id);
2513
        } else { // Special case for old architecture objects
2514
            if ($projectid) {
2515
                $sql .= ' SET fk_projet = ' . ((int) $projectid);
2516
            } else {
2517
                $sql .= ' SET fk_projet = NULL';
2518
            }
2519
            $sql .= " WHERE rowid = " . ((int) $this->id);
2520
        }
2521
2522
        $this->db->begin();
2523
2524
        dol_syslog(get_class($this) . "::setProject", LOG_DEBUG);
2525
        if ($this->db->query($sql)) {
2526
            $this->fk_project = ((int) $projectid);
2527
        } else {
2528
            dol_print_error($this->db);
2529
            $error++;
2530
        }
2531
2532
        // Triggers
2533
        if (!$error && !$notrigger) {
2534
            // Call triggers
2535
            $result = $this->call_trigger(strtoupper($this->element) . '_MODIFY', $user);
2536
            if ($result < 0) {
2537
                $error++;
2538
            } //Do also here what you must do to rollback action if trigger fail
2539
            // End call triggers
2540
        }
2541
2542
        // Commit or rollback
2543
        if ($error) {
2544
            $this->db->rollback();
2545
            return -1;
2546
        } else {
2547
            $this->db->commit();
2548
            return 1;
2549
        }
2550
    }
2551
2552
    /**
2553
     *  Change the payments methods
2554
     *
2555
     *  @param      int     $id     Id of new payment method
2556
     *  @return     int             >0 if OK, <0 if KO
2557
     */
2558
    public function setPaymentMethods($id)
2559
    {
2560
        global $user;
2561
2562
        $error = 0;
2563
        $notrigger = 0;
2564
2565
        dol_syslog(get_class($this) . '::setPaymentMethods(' . $id . ')');
2566
2567
        if ($this->statut >= 0 || $this->element == 'societe') {
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocument::$statut has been deprecated: Use $status instead ( Ignorable by Annotation )

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

2567
        if (/** @scrutinizer ignore-deprecated */ $this->statut >= 0 || $this->element == 'societe') {

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...
2568
            // TODO uniformize field name
2569
            $fieldname = 'fk_mode_reglement';
2570
            if ($this->element == 'societe') {
2571
                $fieldname = 'mode_reglement';
2572
            }
2573
            if (get_class($this) == 'Fournisseur') {
2574
                $fieldname = 'mode_reglement_supplier';
2575
            }
2576
            if (get_class($this) == 'Tva') {
2577
                $fieldname = 'fk_typepayment';
2578
            }
2579
            if (get_class($this) == 'Salary') {
2580
                $fieldname = 'fk_typepayment';
2581
            }
2582
2583
            $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
2584
            $sql .= " SET " . $fieldname . " = " . (($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
2585
            $sql .= ' WHERE rowid=' . ((int) $this->id);
2586
2587
            if ($this->db->query($sql)) {
2588
                $this->mode_reglement_id = $id;
2589
                // for supplier
2590
                if (get_class($this) == 'Fournisseur') {
2591
                    $this->mode_reglement_supplier_id = $id;
2592
                }
2593
                // Triggers
2594
                if (!$error && !$notrigger) {
2595
                    // Call triggers
2596
                    if (get_class($this) == 'Commande') {
2597
                        $result = $this->call_trigger('ORDER_MODIFY', $user);
2598
                    } else {
2599
                        $result = $this->call_trigger(strtoupper(get_class($this)) . '_MODIFY', $user);
2600
                    }
2601
                    if ($result < 0) {
2602
                        $error++;
2603
                    }
2604
                    // End call triggers
2605
                }
2606
                return 1;
2607
            } else {
2608
                dol_syslog(get_class($this) . '::setPaymentMethods Error ' . $this->db->error());
2609
                $this->error = $this->db->error();
2610
                return -1;
2611
            }
2612
        } else {
2613
            dol_syslog(get_class($this) . '::setPaymentMethods, status of the object is incompatible');
2614
            $this->error = 'Status of the object is incompatible ' . $this->statut;
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocument::$statut has been deprecated: Use $status instead ( Ignorable by Annotation )

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

2614
            $this->error = 'Status of the object is incompatible ' . /** @scrutinizer ignore-deprecated */ $this->statut;

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...
2615
            return -2;
2616
        }
2617
    }
2618
2619
    /**
2620
     *  Change the multicurrency code
2621
     *
2622
     *  @param      string  $code   multicurrency code
2623
     *  @return     int             >0 if OK, <0 if KO
2624
     */
2625
    public function setMulticurrencyCode($code)
2626
    {
2627
        dol_syslog(get_class($this) . '::setMulticurrencyCode(' . $code . ')');
2628
        if ($this->statut >= 0 || $this->element == 'societe') {
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocument::$statut has been deprecated: Use $status instead ( Ignorable by Annotation )

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

2628
        if (/** @scrutinizer ignore-deprecated */ $this->statut >= 0 || $this->element == 'societe') {

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...
2629
            $fieldname = 'multicurrency_code';
2630
2631
            $sql = 'UPDATE ' . $this->db->prefix() . $this->table_element;
2632
            $sql .= " SET " . $fieldname . " = '" . $this->db->escape($code) . "'";
2633
            $sql .= ' WHERE rowid=' . ((int) $this->id);
2634
2635
            if ($this->db->query($sql)) {
2636
                $this->multicurrency_code = $code;
2637
2638
                list($fk_multicurrency, $rate) = MultiCurrency::getIdAndTxFromCode($this->db, $code);
2639
                if ($rate) {
2640
                    $this->setMulticurrencyRate($rate, 2);
2641
                }
2642
2643
                return 1;
2644
            } else {
2645
                dol_syslog(get_class($this) . '::setMulticurrencyCode Error ' . $sql . ' - ' . $this->db->error());
2646
                $this->error = $this->db->error();
2647
                return -1;
2648
            }
2649
        } else {
2650
            dol_syslog(get_class($this) . '::setMulticurrencyCode, status of the object is incompatible');
2651
            $this->error = 'Status of the object is incompatible ' . $this->statut;
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocument::$statut has been deprecated: Use $status instead ( Ignorable by Annotation )

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

2651
            $this->error = 'Status of the object is incompatible ' . /** @scrutinizer ignore-deprecated */ $this->statut;

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...
2652
            return -2;
2653
        }
2654
    }
2655
2656
    /**
2657
     *  Change the multicurrency rate
2658
     *
2659
     *  @param      double  $rate   multicurrency rate
2660
     *  @param      int     $mode   mode 1 : amounts in company currency will be recalculated, mode 2 : amounts in foreign currency will be recalculated
2661
     *  @return     int             >0 if OK, <0 if KO
2662
     */
2663
    public function setMulticurrencyRate($rate, $mode = 1)
2664
    {
2665
        dol_syslog(get_class($this) . '::setMulticurrencyRate(' . $rate . ', ' . $mode . ')');
2666
        if ($this->statut >= 0 || $this->element == 'societe') {
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocument::$statut has been deprecated: Use $status instead ( Ignorable by Annotation )

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

2666
        if (/** @scrutinizer ignore-deprecated */ $this->statut >= 0 || $this->element == 'societe') {

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...
2667
            $fieldname = 'multicurrency_tx';
2668
2669
            $sql = 'UPDATE ' . $this->db->prefix() . $this->table_element;
2670
            $sql .= " SET " . $fieldname . " = " . ((float) $rate);
2671
            $sql .= ' WHERE rowid=' . ((int) $this->id);
2672
2673
            if ($this->db->query($sql)) {
2674
                $this->multicurrency_tx = $rate;
2675
2676
                // Update line price
2677
                if (!empty($this->lines)) {
2678
                    foreach ($this->lines as &$line) {
2679
                        // Amounts in company currency will be recalculated
2680
                        if ($mode == 1) {
2681
                            $line->subprice = 0;
2682
                        }
2683
2684
                        // Amounts in foreign currency will be recalculated
2685
                        if ($mode == 2) {
2686
                            $line->multicurrency_subprice = 0;
2687
                        }
2688
2689
                        switch ($this->element) {
2690
                            case 'propal':
2691
                                /** @var Propal $this */
2692
                                /** @var PropaleLigne $line */
2693
                                $this->updateline(
0 ignored issues
show
Bug introduced by
The method updateline() does not exist on DoliCore\Base\GenericDocument. Did you maybe mean updateLineUp()? ( Ignorable by Annotation )

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

2693
                                $this->/** @scrutinizer ignore-call */ 
2694
                                       updateline(

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

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

Loading history...
2694
                                    $line->id,
2695
                                    $line->subprice,
2696
                                    $line->qty,
2697
                                    $line->remise_percent,
2698
                                    $line->tva_tx,
2699
                                    $line->localtax1_tx,
2700
                                    $line->localtax2_tx,
2701
                                    ($line->description ? $line->description : $line->desc),
2702
                                    'HT',
2703
                                    $line->info_bits,
2704
                                    $line->special_code,
2705
                                    $line->fk_parent_line,
2706
                                    $line->skip_update_total,
2707
                                    $line->fk_fournprice,
2708
                                    $line->pa_ht,
2709
                                    $line->label,
2710
                                    $line->product_type,
2711
                                    $line->date_start,
2712
                                    $line->date_end,
2713
                                    $line->array_options,
2714
                                    $line->fk_unit,
2715
                                    $line->multicurrency_subprice
2716
                                );
2717
                                break;
2718
                            case 'commande':
2719
                                /** @var Commande $this */
2720
                                /** @var OrderLine $line */
2721
                                $this->updateline(
2722
                                    $line->id,
2723
                                    ($line->description ? $line->description : $line->desc),
2724
                                    $line->subprice,
2725
                                    $line->qty,
2726
                                    $line->remise_percent,
2727
                                    $line->tva_tx,
2728
                                    $line->localtax1_tx,
2729
                                    $line->localtax2_tx,
2730
                                    'HT',
2731
                                    $line->info_bits,
2732
                                    $line->date_start,
2733
                                    $line->date_end,
2734
                                    $line->product_type,
2735
                                    $line->fk_parent_line,
2736
                                    $line->skip_update_total,
2737
                                    $line->fk_fournprice,
2738
                                    $line->pa_ht,
2739
                                    $line->label,
2740
                                    $line->special_code,
2741
                                    $line->array_options,
2742
                                    $line->fk_unit,
2743
                                    $line->multicurrency_subprice
2744
                                );
2745
                                break;
2746
                            case 'facture':
2747
                                /** @var Facture $this */
2748
                                /** @var FactureLigne $line */
2749
                                $this->updateline(
2750
                                    $line->id,
2751
                                    ($line->description ? $line->description : $line->desc),
2752
                                    $line->subprice,
2753
                                    $line->qty,
2754
                                    $line->remise_percent,
2755
                                    $line->date_start,
2756
                                    $line->date_end,
2757
                                    $line->tva_tx,
2758
                                    $line->localtax1_tx,
2759
                                    $line->localtax2_tx,
2760
                                    'HT',
2761
                                    $line->info_bits,
2762
                                    $line->product_type,
2763
                                    $line->fk_parent_line,
2764
                                    $line->skip_update_total,
2765
                                    $line->fk_fournprice,
2766
                                    $line->pa_ht,
2767
                                    $line->label,
2768
                                    $line->special_code,
2769
                                    $line->array_options,
2770
                                    $line->situation_percent,
2771
                                    $line->fk_unit,
2772
                                    $line->multicurrency_subprice
2773
                                );
2774
                                break;
2775
                            case 'supplier_proposal':
2776
                                /** @var SupplierProposal $this */
2777
                                /** @var SupplierProposalLine $line */
2778
                                $this->updateline(
2779
                                    $line->id,
2780
                                    $line->subprice,
2781
                                    $line->qty,
2782
                                    $line->remise_percent,
2783
                                    $line->tva_tx,
2784
                                    $line->localtax1_tx,
2785
                                    $line->localtax2_tx,
2786
                                    ($line->description ? $line->description : $line->desc),
2787
                                    'HT',
2788
                                    $line->info_bits,
2789
                                    $line->special_code,
2790
                                    $line->fk_parent_line,
2791
                                    $line->skip_update_total,
2792
                                    $line->fk_fournprice,
2793
                                    $line->pa_ht,
2794
                                    $line->label,
2795
                                    $line->product_type,
2796
                                    $line->array_options,
2797
                                    $line->ref_fourn,
2798
                                    $line->multicurrency_subprice
2799
                                );
2800
                                break;
2801
                            case 'order_supplier':
2802
                                /** @var CommandeFournisseur $this */
2803
                                /** @var CommandeFournisseurLigne $line */
2804
                                $this->updateline(
2805
                                    $line->id,
2806
                                    ($line->description ? $line->description : $line->desc),
2807
                                    $line->subprice,
2808
                                    $line->qty,
2809
                                    $line->remise_percent,
2810
                                    $line->tva_tx,
2811
                                    $line->localtax1_tx,
2812
                                    $line->localtax2_tx,
2813
                                    'HT',
2814
                                    $line->info_bits,
2815
                                    $line->product_type,
2816
                                    false,
2817
                                    $line->date_start,
2818
                                    $line->date_end,
2819
                                    $line->array_options,
2820
                                    $line->fk_unit,
2821
                                    $line->multicurrency_subprice,
2822
                                    $line->ref_supplier
2823
                                );
2824
                                break;
2825
                            case 'invoice_supplier':
2826
                                /** @var FactureFournisseur $this */
2827
                                /** @var SupplierInvoiceLine $line */
2828
                                $this->updateline(
2829
                                    $line->id,
2830
                                    ($line->description ? $line->description : $line->desc),
0 ignored issues
show
Deprecated Code introduced by
The property SupplierInvoiceLine::$description has been deprecated: Use $desc ( Ignorable by Annotation )

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

2830
                                    ($line->description ? /** @scrutinizer ignore-deprecated */ $line->description : $line->desc),

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...
2831
                                    $line->subprice,
2832
                                    $line->tva_tx,
2833
                                    $line->localtax1_tx,
2834
                                    $line->localtax2_tx,
2835
                                    $line->qty,
2836
                                    0,
2837
                                    'HT',
2838
                                    $line->info_bits,
2839
                                    $line->product_type,
2840
                                    $line->remise_percent,
2841
                                    false,
2842
                                    $line->date_start,
2843
                                    $line->date_end,
2844
                                    $line->array_options,
2845
                                    $line->fk_unit,
2846
                                    $line->multicurrency_subprice,
2847
                                    $line->ref_supplier
2848
                                );
2849
                                break;
2850
                            default:
2851
                                dol_syslog(get_class($this) . '::setMulticurrencyRate no updateline defined', LOG_DEBUG);
2852
                                break;
2853
                        }
2854
                    }
2855
                }
2856
2857
                return 1;
2858
            } else {
2859
                dol_syslog(get_class($this) . '::setMulticurrencyRate Error ' . $sql . ' - ' . $this->db->error());
2860
                $this->error = $this->db->error();
2861
                return -1;
2862
            }
2863
        } else {
2864
            dol_syslog(get_class($this) . '::setMulticurrencyRate, status of the object is incompatible');
2865
            $this->error = 'Status of the object is incompatible ' . $this->statut;
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocument::$statut has been deprecated: Use $status instead ( Ignorable by Annotation )

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

2865
            $this->error = 'Status of the object is incompatible ' . /** @scrutinizer ignore-deprecated */ $this->statut;

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...
2866
            return -2;
2867
        }
2868
    }
2869
2870
    /**
2871
     *  Change the payments terms
2872
     *
2873
     *  @param      int     $id                 Id of new payment terms
2874
     *  @param      float   $deposit_percent    % of deposit if needed by payment terms
2875
     *  @return     int                         >0 if OK, <0 if KO
2876
     */
2877
    public function setPaymentTerms($id, $deposit_percent = null)
2878
    {
2879
        dol_syslog(get_class($this) . '::setPaymentTerms(' . $id . ', ' . var_export($deposit_percent, true) . ')');
2880
        if ($this->statut >= 0 || $this->element == 'societe') {
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocument::$statut has been deprecated: Use $status instead ( Ignorable by Annotation )

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

2880
        if (/** @scrutinizer ignore-deprecated */ $this->statut >= 0 || $this->element == 'societe') {

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...
2881
            // TODO uniformize field name
2882
            $fieldname = 'fk_cond_reglement';
2883
            if ($this->element == 'societe') {
2884
                $fieldname = 'cond_reglement';
2885
            }
2886
            if (get_class($this) == 'Fournisseur') {
2887
                $fieldname = 'cond_reglement_supplier';
2888
            }
2889
2890
            if (empty($deposit_percent) || $deposit_percent < 0) {
2891
                $deposit_percent = (float) getDictionaryValue('c_payment_term', 'deposit_percent', $id);
2892
            }
2893
2894
            if ($deposit_percent > 100) {
2895
                $deposit_percent = 100;
2896
            }
2897
2898
            $sql = 'UPDATE ' . $this->db->prefix() . $this->table_element;
2899
            $sql .= " SET " . $fieldname . " = " . (($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
2900
            if (in_array($this->table_element, array('propal', 'commande', 'societe'))) {
2901
                $sql .= " , deposit_percent = " . (empty($deposit_percent) ? 'NULL' : "'" . $this->db->escape($deposit_percent) . "'");
2902
            }
2903
            $sql .= ' WHERE rowid=' . ((int) $this->id);
2904
2905
            if ($this->db->query($sql)) {
2906
                $this->cond_reglement_id = $id;
2907
                // for supplier
2908
                if (get_class($this) == 'Fournisseur') {
2909
                    $this->cond_reglement_supplier_id = $id;
2910
                }
2911
                $this->cond_reglement = $id; // for compatibility
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocument::$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

2911
                /** @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...
2912
                $this->deposit_percent = $deposit_percent;
2913
                return 1;
2914
            } else {
2915
                dol_syslog(get_class($this) . '::setPaymentTerms Error ' . $sql . ' - ' . $this->db->error());
2916
                $this->error = $this->db->error();
2917
                return -1;
2918
            }
2919
        } else {
2920
            dol_syslog(get_class($this) . '::setPaymentTerms, status of the object is incompatible');
2921
            $this->error = 'Status of the object is incompatible ' . $this->statut;
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocument::$statut has been deprecated: Use $status instead ( Ignorable by Annotation )

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

2921
            $this->error = 'Status of the object is incompatible ' . /** @scrutinizer ignore-deprecated */ $this->statut;

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...
2922
            return -2;
2923
        }
2924
    }
2925
2926
    /**
2927
     *  Change the transport mode methods
2928
     *
2929
     *  @param      int     $id     Id of transport mode
2930
     *  @return     int             >0 if OK, <0 if KO
2931
     */
2932
    public function setTransportMode($id)
2933
    {
2934
        dol_syslog(get_class($this) . '::setTransportMode(' . $id . ')');
2935
        if ($this->statut >= 0 || $this->element == 'societe') {
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocument::$statut has been deprecated: Use $status instead ( Ignorable by Annotation )

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

2935
        if (/** @scrutinizer ignore-deprecated */ $this->statut >= 0 || $this->element == 'societe') {

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...
2936
            $fieldname = 'fk_transport_mode';
2937
            if ($this->element == 'societe') {
2938
                $fieldname = 'transport_mode';
2939
            }
2940
            if (get_class($this) == 'Fournisseur') {
2941
                $fieldname = 'transport_mode_supplier';
2942
            }
2943
2944
            $sql = 'UPDATE ' . $this->db->prefix() . $this->table_element;
2945
            $sql .= " SET " . $fieldname . " = " . (($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
2946
            $sql .= ' WHERE rowid=' . ((int) $this->id);
2947
2948
            if ($this->db->query($sql)) {
2949
                $this->transport_mode_id = $id;
2950
                // for supplier
2951
                if (get_class($this) == 'Fournisseur') {
2952
                    $this->transport_mode_supplier_id = $id;
2953
                }
2954
                return 1;
2955
            } else {
2956
                dol_syslog(get_class($this) . '::setTransportMode Error ' . $sql . ' - ' . $this->db->error());
2957
                $this->error = $this->db->error();
2958
                return -1;
2959
            }
2960
        } else {
2961
            dol_syslog(get_class($this) . '::setTransportMode, status of the object is incompatible');
2962
            $this->error = 'Status of the object is incompatible ' . $this->statut;
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocument::$statut has been deprecated: Use $status instead ( Ignorable by Annotation )

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

2962
            $this->error = 'Status of the object is incompatible ' . /** @scrutinizer ignore-deprecated */ $this->statut;

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...
2963
            return -2;
2964
        }
2965
    }
2966
2967
    /**
2968
     *  Change the retained warranty payments terms
2969
     *
2970
     *  @param      int     $id     Id of new payment terms
2971
     *  @return     int             >0 if OK, <0 if KO
2972
     */
2973
    public function setRetainedWarrantyPaymentTerms($id)
2974
    {
2975
        dol_syslog(get_class($this) . '::setRetainedWarrantyPaymentTerms(' . $id . ')');
2976
        if ($this->statut >= 0 || $this->element == 'societe') {
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocument::$statut has been deprecated: Use $status instead ( Ignorable by Annotation )

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

2976
        if (/** @scrutinizer ignore-deprecated */ $this->statut >= 0 || $this->element == 'societe') {

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...
2977
            $fieldname = 'retained_warranty_fk_cond_reglement';
2978
2979
            $sql = 'UPDATE ' . $this->db->prefix() . $this->table_element;
2980
            $sql .= " SET " . $fieldname . " = " . ((int) $id);
2981
            $sql .= ' WHERE rowid=' . ((int) $this->id);
2982
2983
            if ($this->db->query($sql)) {
2984
                $this->retained_warranty_fk_cond_reglement = $id;
2985
                return 1;
2986
            } else {
2987
                dol_syslog(get_class($this) . '::setRetainedWarrantyPaymentTerms Error ' . $sql . ' - ' . $this->db->error());
2988
                $this->error = $this->db->error();
2989
                return -1;
2990
            }
2991
        } else {
2992
            dol_syslog(get_class($this) . '::setRetainedWarrantyPaymentTerms, status of the object is incompatible');
2993
            $this->error = 'Status of the object is incompatible ' . $this->statut;
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocument::$statut has been deprecated: Use $status instead ( Ignorable by Annotation )

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

2993
            $this->error = 'Status of the object is incompatible ' . /** @scrutinizer ignore-deprecated */ $this->statut;

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...
2994
            return -2;
2995
        }
2996
    }
2997
2998
    /**
2999
     *  Define delivery address
3000
     *  @deprecated
3001
     *
3002
     *  @param      int     $id     Address id
3003
     *  @return     int             Return integer <0 si ko, >0 si ok
3004
     */
3005
    public function setDeliveryAddress($id)
3006
    {
3007
        $fieldname = 'fk_delivery_address';
3008
        if ($this->element == 'delivery' || $this->element == 'shipping') {
3009
            $fieldname = 'fk_address';
3010
        }
3011
3012
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element . " SET " . $fieldname . " = " . ((int) $id);
3013
        $sql .= " WHERE rowid = " . ((int) $this->id) . " AND fk_statut = 0";
3014
3015
        if ($this->db->query($sql)) {
3016
            $this->fk_delivery_address = $id;
3017
            return 1;
3018
        } else {
3019
            $this->error = $this->db->error();
3020
            dol_syslog(get_class($this) . '::setDeliveryAddress Error ' . $this->error);
3021
            return -1;
3022
        }
3023
    }
3024
3025
3026
    /**
3027
     *  Change the shipping method
3028
     *
3029
     *  @param      int     $shipping_method_id     Id of shipping method
3030
     *  @param      int     $notrigger              0=launch triggers after, 1=disable triggers
3031
     *  @param      User    $userused               Object user
3032
     *  @return     int                             1 if OK, 0 if KO
3033
     */
3034
    public function setShippingMethod($shipping_method_id, $notrigger = 0, $userused = null)
3035
    {
3036
        global $user;
3037
3038
        if (empty($userused)) {
3039
            $userused = $user;
3040
        }
3041
3042
        $error = 0;
3043
3044
        if (!$this->table_element) {
3045
            dol_syslog(get_class($this) . "::setShippingMethod was called on object with property table_element not defined", LOG_ERR);
3046
            return -1;
3047
        }
3048
3049
        $this->db->begin();
3050
3051
        if ($shipping_method_id < 0) {
3052
            $shipping_method_id = 'NULL';
3053
        }
3054
        dol_syslog(get_class($this) . '::setShippingMethod(' . $shipping_method_id . ')');
3055
3056
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
3057
        $sql .= " SET fk_shipping_method = " . ((int) $shipping_method_id);
3058
        $sql .= " WHERE rowid=" . ((int) $this->id);
3059
        $resql = $this->db->query($sql);
3060
        if (!$resql) {
3061
            dol_syslog(get_class($this) . '::setShippingMethod Error ', LOG_DEBUG);
3062
            $this->error = $this->db->lasterror();
3063
            $error++;
3064
        } else {
3065
            if (!$notrigger) {
3066
                // Call trigger
3067
                $this->context = array('shippingmethodupdate' => 1);
3068
                $result = $this->call_trigger(strtoupper(get_class($this)) . '_MODIFY', $userused);
3069
                if ($result < 0) {
3070
                    $error++;
3071
                }
3072
                // End call trigger
3073
            }
3074
        }
3075
        if ($error) {
3076
            $this->db->rollback();
3077
            return -1;
3078
        } else {
3079
            $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...
3080
            $this->db->commit();
3081
            return 1;
3082
        }
3083
    }
3084
3085
3086
    /**
3087
     *  Change the warehouse
3088
     *
3089
     *  @param      int     $warehouse_id     Id of warehouse
3090
     *  @return     int              1 if OK, 0 if KO
3091
     */
3092
    public function setWarehouse($warehouse_id)
3093
    {
3094
        if (!$this->table_element) {
3095
            dol_syslog(get_class($this) . "::setWarehouse was called on object with property table_element not defined", LOG_ERR);
3096
            return -1;
3097
        }
3098
        if ($warehouse_id < 0) {
3099
            $warehouse_id = 'NULL';
3100
        }
3101
        dol_syslog(get_class($this) . '::setWarehouse(' . $warehouse_id . ')');
3102
3103
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
3104
        $sql .= " SET fk_warehouse = " . ((int) $warehouse_id);
3105
        $sql .= " WHERE rowid=" . ((int) $this->id);
3106
3107
        if ($this->db->query($sql)) {
3108
            $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...
3109
            return 1;
3110
        } else {
3111
            dol_syslog(get_class($this) . '::setWarehouse Error ', LOG_DEBUG);
3112
            $this->error = $this->db->error();
3113
            return 0;
3114
        }
3115
    }
3116
3117
3118
    /**
3119
     *      Set last model used by doc generator
3120
     *
3121
     *      @param      User    $user       User object that make change
3122
     *      @param      string  $modelpdf   Modele name
3123
     *      @return     int                 Return integer <0 if KO, >0 if OK
3124
     */
3125
    public function setDocModel($user, $modelpdf)
3126
    {
3127
        if (!$this->table_element) {
3128
            dol_syslog(get_class($this) . "::setDocModel was called on object with property table_element not defined", LOG_ERR);
3129
            return -1;
3130
        }
3131
3132
        $newmodelpdf = dol_trunc($modelpdf, 255);
3133
3134
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
3135
        $sql .= " SET model_pdf = '" . $this->db->escape($newmodelpdf) . "'";
3136
        $sql .= " WHERE rowid = " . ((int) $this->id);
3137
3138
        dol_syslog(get_class($this) . "::setDocModel", LOG_DEBUG);
3139
        $resql = $this->db->query($sql);
3140
        if ($resql) {
3141
            $this->model_pdf = $modelpdf;
3142
            return 1;
3143
        } else {
3144
            dol_print_error($this->db);
3145
            return 0;
3146
        }
3147
    }
3148
3149
3150
    /**
3151
     *  Change the bank account
3152
     *
3153
     *  @param      int     $fk_account     Id of bank account
3154
     *  @param      int     $notrigger      0=launch triggers after, 1=disable triggers
3155
     *  @param      User    $userused       Object user
3156
     *  @return     int                     1 if OK, 0 if KO
3157
     */
3158
    public function setBankAccount($fk_account, $notrigger = 0, $userused = null)
3159
    {
3160
        global $user;
3161
3162
        if (empty($userused)) {
3163
            $userused = $user;
3164
        }
3165
3166
        $error = 0;
3167
3168
        if (!$this->table_element) {
3169
            dol_syslog(get_class($this) . "::setBankAccount was called on object with property table_element not defined", LOG_ERR);
3170
            return -1;
3171
        }
3172
        $this->db->begin();
3173
3174
        if ($fk_account < 0) {
3175
            $fk_account = 'NULL';
3176
        }
3177
        dol_syslog(get_class($this) . '::setBankAccount(' . $fk_account . ')');
3178
3179
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
3180
        $sql .= " SET fk_account = " . ((int) $fk_account);
3181
        $sql .= " WHERE rowid=" . ((int) $this->id);
3182
3183
        $resql = $this->db->query($sql);
3184
        if (!$resql) {
3185
            dol_syslog(get_class($this) . '::setBankAccount Error ' . $sql . ' - ' . $this->db->error());
3186
            $this->error = $this->db->lasterror();
3187
            $error++;
3188
        } else {
3189
            if (!$notrigger) {
3190
                // Call trigger
3191
                $this->context['bankaccountupdate'] = 1;
3192
                $triggerName = strtoupper(get_class($this)) . '_MODIFY';
3193
                // Special cases
3194
                if ($triggerName == 'FACTUREREC_MODIFY') {
3195
                    $triggerName = 'BILLREC_MODIFY';
3196
                }
3197
                $result = $this->call_trigger($triggerName, $userused);
3198
                if ($result < 0) {
3199
                    $error++;
3200
                }
3201
                // End call trigger
3202
            }
3203
        }
3204
        if ($error) {
3205
            $this->db->rollback();
3206
            return -1;
3207
        } else {
3208
            $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...
3209
            $this->db->commit();
3210
            return 1;
3211
        }
3212
    }
3213
3214
3215
    // TODO: Move line related operations to CommonObjectLine?
3216
3217
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3218
    /**
3219
     *  Save a new position (field rang) for details lines.
3220
     *  You can choose to set position for lines with already a position or lines without any position defined.
3221
     *
3222
     *  @param      boolean     $renum             True to renum all already ordered lines, false to renum only not already ordered lines.
3223
     *  @param      string      $rowidorder        ASC or DESC
3224
     *  @param      boolean     $fk_parent_line    Table with fk_parent_line field or not
3225
     *  @return     int                            Return integer <0 if KO, >0 if OK
3226
     */
3227
    public function line_order($renum = false, $rowidorder = 'ASC', $fk_parent_line = true)
3228
    {
3229
        // phpcs:enable
3230
        if (!$this->table_element_line) {
3231
            dol_syslog(get_class($this) . "::line_order was called on object with property table_element_line not defined", LOG_ERR);
3232
            return -1;
3233
        }
3234
        if (!$this->fk_element) {
3235
            dol_syslog(get_class($this) . "::line_order was called on object with property fk_element not defined", LOG_ERR);
3236
            return -1;
3237
        }
3238
3239
        $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3240
        if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3241
            $fieldposition = 'position';
3242
        }
3243
3244
        // Count number of lines to reorder (according to choice $renum)
3245
        $nl = 0;
3246
        $sql = "SELECT count(rowid) FROM " . $this->db->prefix() . $this->table_element_line;
3247
        $sql .= " WHERE " . $this->fk_element . " = " . ((int) $this->id);
3248
        if (!$renum) {
3249
            $sql .= " AND " . $fieldposition . " = 0";
3250
        }
3251
        if ($renum) {
3252
            $sql .= " AND " . $fieldposition . " <> 0";
3253
        }
3254
3255
        dol_syslog(get_class($this) . "::line_order", LOG_DEBUG);
3256
        $resql = $this->db->query($sql);
3257
        if ($resql) {
3258
            $row = $this->db->fetch_row($resql);
3259
            $nl = $row[0];
3260
        } else {
3261
            dol_print_error($this->db);
3262
        }
3263
        if ($nl > 0) {
3264
            // The goal of this part is to reorder all lines, with all children lines sharing the same counter that parents.
3265
            $rows = array();
3266
3267
            // We first search all lines that are parent lines (for multilevel details lines)
3268
            $sql = "SELECT rowid FROM " . $this->db->prefix() . $this->table_element_line;
3269
            $sql .= " WHERE " . $this->fk_element . " = " . ((int) $this->id);
3270
            if ($fk_parent_line) {
3271
                $sql .= ' AND fk_parent_line IS NULL';
3272
            }
3273
            $sql .= " ORDER BY " . $fieldposition . " ASC, rowid " . $rowidorder;
3274
3275
            dol_syslog(get_class($this) . "::line_order search all parent lines", LOG_DEBUG);
3276
            $resql = $this->db->query($sql);
3277
            if ($resql) {
3278
                $i = 0;
3279
                $num = $this->db->num_rows($resql);
3280
                while ($i < $num) {
3281
                    $row = $this->db->fetch_row($resql);
3282
                    $rows[] = $row[0]; // Add parent line into array rows
3283
                    $children = $this->getChildrenOfLine($row[0]);
3284
                    if (!empty($children)) {
3285
                        foreach ($children as $child) {
3286
                            array_push($rows, $child);
3287
                        }
3288
                    }
3289
                    $i++;
3290
                }
3291
3292
                // Now we set a new number for each lines (parent and children with children included into parent tree)
3293
                if (!empty($rows)) {
3294
                    foreach ($rows as $key => $row) {
3295
                        $this->updateRangOfLine($row, ($key + 1));
3296
                    }
3297
                }
3298
            } else {
3299
                dol_print_error($this->db);
3300
            }
3301
        }
3302
        return 1;
3303
    }
3304
3305
    /**
3306
     *  Get children of line
3307
     *
3308
     *  @param  int     $id                 Id of parent line
3309
     *  @param  int     $includealltree     0 = 1st level child, 1 = All level child
3310
     *  @return array                       Array with list of children lines id
3311
     */
3312
    public function getChildrenOfLine($id, $includealltree = 0)
3313
    {
3314
        $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3315
        if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3316
            $fieldposition = 'position';
3317
        }
3318
3319
        $rows = array();
3320
3321
        $sql = "SELECT rowid FROM " . $this->db->prefix() . $this->table_element_line;
3322
        $sql .= " WHERE " . $this->fk_element . " = " . ((int) $this->id);
3323
        $sql .= ' AND fk_parent_line = ' . ((int) $id);
3324
        $sql .= " ORDER BY " . $fieldposition . " ASC";
3325
3326
        dol_syslog(get_class($this) . "::getChildrenOfLine search children lines for line " . $id, LOG_DEBUG);
3327
        $resql = $this->db->query($sql);
3328
        if ($resql) {
3329
            if ($this->db->num_rows($resql) > 0) {
3330
                while ($row = $this->db->fetch_row($resql)) {
3331
                    $rows[] = $row[0];
3332
                    if ($includealltree) {
3333
                        $rows = array_merge($rows, $this->getChildrenOfLine($row[0], $includealltree));
3334
                    }
3335
                }
3336
            }
3337
        }
3338
        return $rows;
3339
    }
3340
3341
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3342
    /**
3343
     *  Update a line to have a lower rank
3344
     *
3345
     *  @param  int         $rowid              Id of line
3346
     *  @param  boolean     $fk_parent_line     Table with fk_parent_line field or not
3347
     *  @return void
3348
     */
3349
    public function line_up($rowid, $fk_parent_line = true)
3350
    {
3351
        // phpcs:enable
3352
        $this->line_order(false, 'ASC', $fk_parent_line);
3353
3354
        // Get rang of line
3355
        $rang = $this->getRangOfLine($rowid);
3356
3357
        // Update position of line
3358
        $this->updateLineUp($rowid, $rang);
3359
    }
3360
3361
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3362
    /**
3363
     *  Update a line to have a higher rank
3364
     *
3365
     *  @param  int         $rowid              Id of line
3366
     *  @param  boolean     $fk_parent_line     Table with fk_parent_line field or not
3367
     *  @return void
3368
     */
3369
    public function line_down($rowid, $fk_parent_line = true)
3370
    {
3371
        // phpcs:enable
3372
        $this->line_order(false, 'ASC', $fk_parent_line);
3373
3374
        // Get rang of line
3375
        $rang = $this->getRangOfLine($rowid);
3376
3377
        // Get max value for rang
3378
        $max = $this->line_max();
3379
3380
        // Update position of line
3381
        $this->updateLineDown($rowid, $rang, $max);
3382
    }
3383
3384
    /**
3385
     *  Update position of line (rang)
3386
     *
3387
     *  @param  int     $rowid      Id of line
3388
     *  @param  int     $rang       Position
3389
     *  @return int                 Return integer <0 if KO, >0 if OK
3390
     */
3391
    public function updateRangOfLine($rowid, $rang)
3392
    {
3393
        global $hookmanager;
3394
        $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3395
        if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3396
            $fieldposition = 'position';
3397
        }
3398
3399
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element_line . " SET " . $fieldposition . " = " . ((int) $rang);
3400
        $sql .= ' WHERE rowid = ' . ((int) $rowid);
3401
3402
        dol_syslog(get_class($this) . "::updateRangOfLine", LOG_DEBUG);
3403
        if (!$this->db->query($sql)) {
3404
            dol_print_error($this->db);
3405
            return -1;
3406
        } else {
3407
            $parameters = array('rowid' => $rowid, 'rang' => $rang, 'fieldposition' => $fieldposition);
3408
            $action = '';
3409
            $reshook = $hookmanager->executeHooks('afterRankOfLineUpdate', $parameters, $this, $action);
3410
            return 1;
3411
        }
3412
    }
3413
3414
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3415
    /**
3416
     *  Update position of line with ajax (rang)
3417
     *
3418
     *  @param  array   $rows   Array of rows
3419
     *  @return void
3420
     */
3421
    public function line_ajaxorder($rows)
3422
    {
3423
        // phpcs:enable
3424
        $num = count($rows);
3425
        for ($i = 0; $i < $num; $i++) {
3426
            $this->updateRangOfLine($rows[$i], ($i + 1));
3427
        }
3428
    }
3429
3430
    /**
3431
     *  Update position of line up (rang)
3432
     *
3433
     *  @param  int     $rowid      Id of line
3434
     *  @param  int     $rang       Position
3435
     *  @return void
3436
     */
3437
    public function updateLineUp($rowid, $rang)
3438
    {
3439
        if ($rang > 1) {
3440
            $fieldposition = 'rang';
3441
            if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3442
                $fieldposition = 'position';
3443
            }
3444
3445
            $sql = "UPDATE " . $this->db->prefix() . $this->table_element_line . " SET " . $fieldposition . " = " . ((int) $rang);
3446
            $sql .= " WHERE " . $this->fk_element . " = " . ((int) $this->id);
3447
            $sql .= " AND " . $fieldposition . " = " . ((int) ($rang - 1));
3448
            if ($this->db->query($sql)) {
3449
                $sql = "UPDATE " . $this->db->prefix() . $this->table_element_line . " SET " . $fieldposition . " = " . ((int) ($rang - 1));
3450
                $sql .= ' WHERE rowid = ' . ((int) $rowid);
3451
                if (!$this->db->query($sql)) {
3452
                    dol_print_error($this->db);
3453
                }
3454
            } else {
3455
                dol_print_error($this->db);
3456
            }
3457
        }
3458
    }
3459
3460
    /**
3461
     *  Update position of line down (rang)
3462
     *
3463
     *  @param  int     $rowid      Id of line
3464
     *  @param  int     $rang       Position
3465
     *  @param  int     $max        Max
3466
     *  @return void
3467
     */
3468
    public function updateLineDown($rowid, $rang, $max)
3469
    {
3470
        if ($rang < $max) {
3471
            $fieldposition = 'rang';
3472
            if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3473
                $fieldposition = 'position';
3474
            }
3475
3476
            $sql = "UPDATE " . $this->db->prefix() . $this->table_element_line . " SET " . $fieldposition . " = " . ((int) $rang);
3477
            $sql .= " WHERE " . $this->fk_element . " = " . ((int) $this->id);
3478
            $sql .= " AND " . $fieldposition . " = " . ((int) ($rang + 1));
3479
            if ($this->db->query($sql)) {
3480
                $sql = "UPDATE " . $this->db->prefix() . $this->table_element_line . " SET " . $fieldposition . " = " . ((int) ($rang + 1));
3481
                $sql .= ' WHERE rowid = ' . ((int) $rowid);
3482
                if (!$this->db->query($sql)) {
3483
                    dol_print_error($this->db);
3484
                }
3485
            } else {
3486
                dol_print_error($this->db);
3487
            }
3488
        }
3489
    }
3490
3491
    /**
3492
     *  Get position of line (rang)
3493
     *
3494
     *  @param      int     $rowid      Id of line
3495
     *  @return     int                 Value of rang in table of lines
3496
     */
3497
    public function getRangOfLine($rowid)
3498
    {
3499
        $fieldposition = 'rang';
3500
        if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3501
            $fieldposition = 'position';
3502
        }
3503
3504
        $sql = "SELECT " . $fieldposition . " FROM " . $this->db->prefix() . $this->table_element_line;
3505
        $sql .= " WHERE rowid = " . ((int) $rowid);
3506
3507
        dol_syslog(get_class($this) . "::getRangOfLine", LOG_DEBUG);
3508
        $resql = $this->db->query($sql);
3509
        if ($resql) {
3510
            $row = $this->db->fetch_row($resql);
3511
            return $row[0];
3512
        }
3513
3514
        return 0;
3515
    }
3516
3517
    /**
3518
     *  Get rowid of the line relative to its position
3519
     *
3520
     *  @param      int     $rang       Rang value
3521
     *  @return     int                 Rowid of the line
3522
     */
3523
    public function getIdOfLine($rang)
3524
    {
3525
        $fieldposition = 'rang';
3526
        if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3527
            $fieldposition = 'position';
3528
        }
3529
3530
        $sql = "SELECT rowid FROM " . $this->db->prefix() . $this->table_element_line;
3531
        $sql .= " WHERE " . $this->fk_element . " = " . ((int) $this->id);
3532
        $sql .= " AND " . $fieldposition . " = " . ((int) $rang);
3533
        $resql = $this->db->query($sql);
3534
        if ($resql) {
3535
            $row = $this->db->fetch_row($resql);
3536
            return $row[0];
3537
        }
3538
3539
        return 0;
3540
    }
3541
3542
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3543
    /**
3544
     *  Get max value used for position of line (rang)
3545
     *
3546
     *  @param      int     $fk_parent_line     Parent line id
3547
     *  @return     int                         Max value of rang in table of lines
3548
     */
3549
    public function line_max($fk_parent_line = 0)
3550
    {
3551
        // phpcs:enable
3552
        $positionfield = 'rang';
3553
        if (in_array($this->table_element, array('bom_bom', 'product_attribute'))) {
3554
            $positionfield = 'position';
3555
        }
3556
3557
        // Search the last rang with fk_parent_line
3558
        if ($fk_parent_line) {
3559
            $sql = "SELECT max(" . $positionfield . ") FROM " . $this->db->prefix() . $this->table_element_line;
3560
            $sql .= " WHERE " . $this->fk_element . " = " . ((int) $this->id);
3561
            $sql .= " AND fk_parent_line = " . ((int) $fk_parent_line);
3562
3563
            dol_syslog(get_class($this) . "::line_max", LOG_DEBUG);
3564
            $resql = $this->db->query($sql);
3565
            if ($resql) {
3566
                $row = $this->db->fetch_row($resql);
3567
                if (!empty($row[0])) {
3568
                    return $row[0];
3569
                } else {
3570
                    return $this->getRangOfLine($fk_parent_line);
3571
                }
3572
            }
3573
        } else {
3574
            // If not, search the last rang of element
3575
            $sql = "SELECT max(" . $positionfield . ") FROM " . $this->db->prefix() . $this->table_element_line;
3576
            $sql .= " WHERE " . $this->fk_element . " = " . ((int) $this->id);
3577
3578
            dol_syslog(get_class($this) . "::line_max", LOG_DEBUG);
3579
            $resql = $this->db->query($sql);
3580
            if ($resql) {
3581
                $row = $this->db->fetch_row($resql);
3582
                return $row[0];
3583
            }
3584
        }
3585
3586
        return 0;
3587
    }
3588
3589
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3590
    /**
3591
     *  Update external ref of element
3592
     *
3593
     *  @param      string      $ref_ext    Update field ref_ext
3594
     *  @return     int                     Return integer <0 if KO, >0 if OK
3595
     */
3596
    public function update_ref_ext($ref_ext)
3597
    {
3598
        // phpcs:enable
3599
        if (!$this->table_element) {
3600
            dol_syslog(get_class($this) . "::update_ref_ext was called on object with property table_element not defined", LOG_ERR);
3601
            return -1;
3602
        }
3603
3604
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
3605
        $sql .= " SET ref_ext = '" . $this->db->escape($ref_ext) . "'";
3606
        $sql .= " WHERE " . (isset($this->table_rowid) ? $this->table_rowid : 'rowid') . " = " . ((int) $this->id);
3607
3608
        dol_syslog(get_class($this) . "::update_ref_ext", LOG_DEBUG);
3609
        if ($this->db->query($sql)) {
3610
            $this->ref_ext = $ref_ext;
3611
            return 1;
3612
        } else {
3613
            $this->error = $this->db->error();
3614
            return -1;
3615
        }
3616
    }
3617
3618
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3619
    /**
3620
     *  Update note of element
3621
     *
3622
     *  @param      string      $note       New value for note
3623
     *  @param      string      $suffix     '', '_public' or '_private'
3624
     *  @param      int         $notrigger  1=Does not execute triggers, 0=execute triggers
3625
     *  @return     int                     Return integer <0 if KO, >0 if OK
3626
     */
3627
    public function update_note($note, $suffix = '', $notrigger = 0)
3628
    {
3629
        // phpcs:enable
3630
        global $user;
3631
3632
        if (!$this->table_element) {
3633
            $this->error = 'update_note was called on object with property table_element not defined';
3634
            dol_syslog(get_class($this) . "::update_note was called on object with property table_element not defined", LOG_ERR);
3635
            return -1;
3636
        }
3637
        if (!in_array($suffix, array('', '_public', '_private'))) {
3638
            $this->error = 'update_note Parameter suffix must be empty, \'_private\' or \'_public\'';
3639
            dol_syslog(get_class($this) . "::update_note Parameter suffix must be empty, '_private' or '_public'", LOG_ERR);
3640
            return -2;
3641
        }
3642
3643
        $newsuffix = $suffix;
3644
3645
        // Special case
3646
        if ($this->table_element == 'product' && $newsuffix == '_private') {
3647
            $newsuffix = '';
3648
        }
3649
        if (in_array($this->table_element, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
3650
            $fieldusermod =  "fk_user_mod";
3651
        } elseif ($this->table_element == 'ecm_files') {
3652
            $fieldusermod = "fk_user_m";
3653
        } else {
3654
            $fieldusermod = "fk_user_modif";
3655
        }
3656
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
3657
        $sql .= " SET note" . $newsuffix . " = " . (!empty($note) ? ("'" . $this->db->escape($note) . "'") : "NULL");
3658
        $sql .= ", " . $fieldusermod . " = " . ((int) $user->id);
3659
        $sql .= " WHERE rowid = " . ((int) $this->id);
3660
3661
        dol_syslog(get_class($this) . "::update_note", LOG_DEBUG);
3662
        if ($this->db->query($sql)) {
3663
            if ($suffix == '_public') {
3664
                $this->note_public = $note;
3665
            } elseif ($suffix == '_private') {
3666
                $this->note_private = $note;
3667
            } else {
3668
                $this->note = $note; // deprecated
3669
                $this->note_private = $note;
3670
            }
3671
            if (empty($notrigger)) {
3672
                switch ($this->element) {
3673
                    case 'societe':
3674
                        $trigger_name = 'COMPANY_MODIFY';
3675
                        break;
3676
                    case 'commande':
3677
                        $trigger_name = 'ORDER_MODIFY';
3678
                        break;
3679
                    case 'facture':
3680
                        $trigger_name = 'BILL_MODIFY';
3681
                        break;
3682
                    case 'invoice_supplier':
3683
                        $trigger_name = 'BILL_SUPPLIER_MODIFY';
3684
                        break;
3685
                    case 'facturerec':
3686
                        $trigger_name = 'BILLREC_MODIFIY';
3687
                        break;
3688
                    case 'expensereport':
3689
                        $trigger_name = 'EXPENSE_REPORT_MODIFY';
3690
                        break;
3691
                    default:
3692
                        $trigger_name = strtoupper($this->element) . '_MODIFY';
3693
                }
3694
                $ret = $this->call_trigger($trigger_name, $user);
3695
                if ($ret < 0) {
3696
                    return -1;
3697
                }
3698
            }
3699
            return 1;
3700
        } else {
3701
            $this->error = $this->db->lasterror();
3702
            return -1;
3703
        }
3704
    }
3705
3706
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3707
    /**
3708
     *  Update public note (kept for backward compatibility)
3709
     *
3710
     * @param      string       $note       New value for note
3711
     * @return     int                      Return integer <0 if KO, >0 if OK
3712
     * @deprecated
3713
     * @see update_note()
3714
     */
3715
    public function update_note_public($note)
3716
    {
3717
        // phpcs:enable
3718
        return $this->update_note($note, '_public');
3719
    }
3720
3721
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3722
    /**
3723
     *  Update total_ht, total_ttc, total_vat, total_localtax1, total_localtax2 for an object (sum of lines).
3724
     *  Must be called at end of methods addline or updateline.
3725
     *
3726
     *  @param  int     $exclspec           >0 = Exclude special product (product_type=9)
3727
     *  @param  string  $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
3728
     *  @param  int     $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.
3729
     *  @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).
3730
     *  @return int                         Return integer <0 if KO, >0 if OK
3731
     */
3732
    public function update_price($exclspec = 0, $roundingadjust = 'none', $nodatabaseupdate = 0, $seller = null)
3733
    {
3734
        // phpcs:enable
3735
        global $conf, $hookmanager, $action;
3736
3737
        $parameters = array('exclspec' => $exclspec, 'roundingadjust' => $roundingadjust, 'nodatabaseupdate' => $nodatabaseupdate, 'seller' => $seller);
3738
        $reshook = $hookmanager->executeHooks('updateTotalPrice', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3739
        if ($reshook > 0) {
3740
            return 1; // replacement code
3741
        } elseif ($reshook < 0) {
3742
            return -1; // failure
3743
        } // reshook = 0 => execute normal code
3744
3745
        // Some external module want no update price after a trigger because they have another method to calculate the total (ex: with an extrafield)
3746
        $MODULE = "";
3747
        if ($this->element == 'propal') {
3748
            $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_PROPOSAL";
3749
        } elseif ($this->element == 'commande' || $this->element == 'order') {
3750
            $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_ORDER";
3751
        } elseif ($this->element == 'facture' || $this->element == 'invoice') {
3752
            $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_INVOICE";
3753
        } elseif ($this->element == 'facture_fourn' || $this->element == 'supplier_invoice' || $this->element == 'invoice_supplier' || $this->element == 'invoice_supplier_rec') {
3754
            $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_INVOICE";
3755
        } elseif ($this->element == 'order_supplier' || $this->element == 'supplier_order') {
3756
            $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_ORDER";
3757
        } elseif ($this->element == 'supplier_proposal') {
3758
            $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_PROPOSAL";
3759
        }
3760
3761
        if (!empty($MODULE)) {
3762
            if (getDolGlobalString($MODULE)) {
3763
                $modsactivated = explode(',', getDolGlobalString($MODULE));
3764
                foreach ($modsactivated as $mod) {
3765
                    if (isModEnabled($mod)) {
3766
                        return 1; // update was disabled by specific setup
3767
                    }
3768
                }
3769
            }
3770
        }
3771
3772
        include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
3773
3774
        $forcedroundingmode = $roundingadjust;
3775
        if ($forcedroundingmode == 'auto' && isset($conf->global->MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND)) {
3776
            $forcedroundingmode = getDolGlobalString('MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND');
3777
        } elseif ($forcedroundingmode == 'auto') {
3778
            $forcedroundingmode = '0';
3779
        }
3780
3781
        $error = 0;
3782
3783
        $multicurrency_tx = !empty($this->multicurrency_tx) ? $this->multicurrency_tx : 1;
3784
3785
        // Define constants to find lines to sum (field name int the table_element_line not into table_element)
3786
        $fieldtva = 'total_tva';
3787
        $fieldlocaltax1 = 'total_localtax1';
3788
        $fieldlocaltax2 = 'total_localtax2';
3789
        $fieldup = 'subprice';
3790
        if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
3791
            $fieldtva = 'tva';
3792
            $fieldup = 'pu_ht';
3793
        }
3794
        if ($this->element == 'invoice_supplier_rec') {
3795
            $fieldup = 'pu_ht';
3796
        }
3797
        if ($this->element == 'expensereport') {
3798
            $fieldup = 'value_unit';
3799
        }
3800
3801
        $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,";
3802
        $sql .= ' tva_tx as vatrate, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, info_bits, product_type';
3803
        if ($this->table_element_line == 'facturedet') {
3804
            $sql .= ', situation_percent';
3805
        }
3806
        $sql .= ', multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
3807
        $sql .= " FROM " . $this->db->prefix() . $this->table_element_line;
3808
        $sql .= " WHERE " . $this->fk_element . " = " . ((int) $this->id);
3809
        if ($exclspec) {
3810
            $product_field = 'product_type';
3811
            if ($this->table_element_line == 'contratdet') {
3812
                $product_field = ''; // contratdet table has no product_type field
3813
            }
3814
            if ($product_field) {
3815
                $sql .= " AND " . $product_field . " <> 9";
3816
            }
3817
        }
3818
        $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
3819
3820
        dol_syslog(get_class($this) . "::update_price", LOG_DEBUG);
3821
3822
        $resql = $this->db->query($sql);
3823
        if ($resql) {
3824
            $this->total_ht  = 0;
3825
            $this->total_tva = 0;
3826
            $this->total_localtax1 = 0;
3827
            $this->total_localtax2 = 0;
3828
            $this->total_ttc = 0;
3829
            $total_ht_by_vats  = array();
3830
            $total_tva_by_vats = array();
3831
            $total_ttc_by_vats = array();
3832
            $this->multicurrency_total_ht = 0;
3833
            $this->multicurrency_total_tva  = 0;
3834
            $this->multicurrency_total_ttc  = 0;
3835
3836
            $this->db->begin();
3837
3838
            $num = $this->db->num_rows($resql);
3839
            $i = 0;
3840
            while ($i < $num) {
3841
                $obj = $this->db->fetch_object($resql);
3842
3843
                // Note: There is no check on detail line and no check on total, if $forcedroundingmode = 'none'
3844
                $parameters = array('fk_element' => $obj->rowid);
3845
                $reshook = $hookmanager->executeHooks('changeRoundingMode', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3846
3847
                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'
3848
                    // This part of code is to fix data. We should not call it too often.
3849
                    $localtax_array = array($obj->localtax1_type, $obj->localtax1_tx, $obj->localtax2_type, $obj->localtax2_tx);
3850
                    $tmpcal = calcul_price_total($obj->qty, $obj->up, $obj->remise_percent, $obj->vatrate, $obj->localtax1_tx, $obj->localtax2_tx, 0, 'HT', $obj->info_bits, $obj->product_type, $seller, $localtax_array, (isset($obj->situation_percent) ? $obj->situation_percent : 100), $multicurrency_tx);
3851
3852
                    $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.
3853
                    $diff_on_current_total = price2num($obj->total_ttc - $obj->total_ht - $obj->total_tva - $obj->total_localtax1 - $obj->total_localtax2, 'MT', 1);
3854
                    //var_dump($obj->total_ht.' '.$obj->total_tva.' '.$obj->total_localtax1.' '.$obj->total_localtax2.' => '.$obj->total_ttc);
3855
                    //var_dump($diff_when_using_price_ht.' '.$diff_on_current_total);
3856
3857
                    if ($diff_on_current_total) {
3858
                        // This should not happen, we should always have in table: total_ttc = total_ht + total_vat + total_localtax1 + total_localtax2
3859
                        $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);
3860
                        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);
3861
                        $resqlfix = $this->db->query($sqlfix);
3862
                        if (!$resqlfix) {
3863
                            dol_print_error($this->db, 'Failed to update line');
3864
                        }
3865
                        $obj->total_tva = $tmpcal[1];
3866
                        $obj->total_ttc = $tmpcal[2];
3867
                    } elseif ($diff_when_using_price_ht && $roundingadjust == '0') {
3868
                        // After calculation from HT, total is consistent but we have found a difference between VAT part in calculation and into database and
3869
                        // we ask to force the use of rounding on line (like done on calculation) so we force update of line
3870
                        $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);
3871
                        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);
3872
                        $resqlfix = $this->db->query($sqlfix);
3873
                        if (!$resqlfix) {
3874
                            dol_print_error($this->db, 'Failed to update line');
3875
                        }
3876
                        $obj->total_tva = $tmpcal[1];
3877
                        $obj->total_ttc = $tmpcal[2];
3878
                    }
3879
                }
3880
3881
                $this->total_ht        += $obj->total_ht; // The field visible at end of line detail
3882
                $this->total_tva       += $obj->total_tva;
3883
                $this->total_localtax1 += $obj->total_localtax1;
3884
                $this->total_localtax2 += $obj->total_localtax2;
3885
                $this->total_ttc       += $obj->total_ttc;
3886
                $this->multicurrency_total_ht        += $obj->multicurrency_total_ht; // The field visible at end of line detail
3887
                $this->multicurrency_total_tva       += $obj->multicurrency_total_tva;
3888
                $this->multicurrency_total_ttc       += $obj->multicurrency_total_ttc;
3889
3890
                if (!isset($total_ht_by_vats[$obj->vatrate])) {
3891
                    $total_ht_by_vats[$obj->vatrate] = 0;
3892
                }
3893
                if (!isset($total_tva_by_vats[$obj->vatrate])) {
3894
                    $total_tva_by_vats[$obj->vatrate] = 0;
3895
                }
3896
                if (!isset($total_ttc_by_vats[$obj->vatrate])) {
3897
                    $total_ttc_by_vats[$obj->vatrate] = 0;
3898
                }
3899
                $total_ht_by_vats[$obj->vatrate]  += $obj->total_ht;
3900
                $total_tva_by_vats[$obj->vatrate] += $obj->total_tva;
3901
                $total_ttc_by_vats[$obj->vatrate] += $obj->total_ttc;
3902
3903
                if ($forcedroundingmode == '1') {   // Check if we need adjustment onto line for vat. TODO This works on the company currency but not on foreign currency
3904
                    $tmpvat = price2num($total_ht_by_vats[$obj->vatrate] * $obj->vatrate / 100, 'MT', 1);
3905
                    $diff = price2num($total_tva_by_vats[$obj->vatrate] - $tmpvat, 'MT', 1);
3906
                    //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";
3907
                    if ($diff) {
3908
                        if (abs($diff) > (10 * pow(10, -1 * getDolGlobalInt('MAIN_MAX_DECIMALS_TOT', 0)))) {
3909
                            // If error is more than 10 times the accuracy of rounding. This should not happen.
3910
                            $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.';
3911
                            dol_syslog($errmsg, LOG_WARNING);
3912
                            $this->error = $errmsg;
3913
                            $error++;
3914
                            break;
3915
                        }
3916
                        $sqlfix = "UPDATE " . $this->db->prefix() . $this->table_element_line . " SET " . $fieldtva . " = " . price2num($obj->total_tva - $diff) . ", total_ttc = " . price2num($obj->total_ttc - $diff) . " WHERE rowid = " . ((int) $obj->rowid);
3917
                        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);
3918
3919
                        $resqlfix = $this->db->query($sqlfix);
3920
3921
                        if (!$resqlfix) {
3922
                            dol_print_error($this->db, 'Failed to update line');
3923
                        }
3924
3925
                        $this->total_tva = (float) price2num($this->total_tva - $diff, '', 1);
3926
                        $this->total_ttc = (float) price2num($this->total_ttc - $diff, '', 1);
3927
                        $total_tva_by_vats[$obj->vatrate] = (float) price2num($total_tva_by_vats[$obj->vatrate] - $diff, '', 1);
3928
                        $total_ttc_by_vats[$obj->vatrate] = (float) price2num($total_ttc_by_vats[$obj->vatrate] - $diff, '', 1);
3929
                    }
3930
                }
3931
3932
                $i++;
3933
            }
3934
3935
            // Add revenue stamp to total
3936
            $this->total_ttc += isset($this->revenuestamp) ? $this->revenuestamp : 0;
3937
            $this->multicurrency_total_ttc += isset($this->revenuestamp) ? ($this->revenuestamp * $multicurrency_tx) : 0;
3938
3939
            // Situations totals
3940
            if (!empty($this->situation_cycle_ref) && !empty($this->situation_counter) && $this->situation_counter > 1 && method_exists($this, 'get_prev_sits')) {
3941
                include_once DOL_DOCUMENT_ROOT . '/compta/facture/class/facture.class.php';
3942
                if ($this->type != Facture::TYPE_CREDIT_NOTE) { // @phpstan-ignore-line
3943
                    $prev_sits = $this->get_prev_sits();
3944
3945
                    foreach ($prev_sits as $sit) {              // $sit is an object Facture loaded with a fetch.
3946
                        $this->total_ht -= $sit->total_ht;
3947
                        $this->total_tva -= $sit->total_tva;
3948
                        $this->total_localtax1 -= $sit->total_localtax1;
3949
                        $this->total_localtax2 -= $sit->total_localtax2;
3950
                        $this->total_ttc -= $sit->total_ttc;
3951
                        $this->multicurrency_total_ht -= $sit->multicurrency_total_ht;
3952
                        $this->multicurrency_total_tva -= $sit->multicurrency_total_tva;
3953
                        $this->multicurrency_total_ttc -= $sit->multicurrency_total_ttc;
3954
                    }
3955
                }
3956
            }
3957
3958
            // Clean total
3959
            $this->total_ht = (float) price2num($this->total_ht);
3960
            $this->total_tva = (float) price2num($this->total_tva);
3961
            $this->total_localtax1 = (float) price2num($this->total_localtax1);
3962
            $this->total_localtax2 = (float) price2num($this->total_localtax2);
3963
            $this->total_ttc = (float) price2num($this->total_ttc);
3964
3965
            $this->db->free($resql);
3966
3967
            // Now update global fields total_ht, total_ttc, total_tva, total_localtax1, total_localtax2, multicurrency_total_* of main object
3968
            $fieldht = 'total_ht';
3969
            $fieldtva = 'tva';
3970
            $fieldlocaltax1 = 'localtax1';
3971
            $fieldlocaltax2 = 'localtax2';
3972
            $fieldttc = 'total_ttc';
3973
            // Specific code for backward compatibility with old field names
3974
            if (in_array($this->element, array('propal', 'commande', 'facture', 'facturerec', 'supplier_proposal', 'order_supplier', 'facture_fourn', 'invoice_supplier', 'invoice_supplier_rec', 'expensereport'))) {
3975
                $fieldtva = 'total_tva';
3976
            }
3977
3978
            if (!$error && empty($nodatabaseupdate)) {
3979
                $sql = "UPDATE " . $this->db->prefix() . $this->table_element . ' SET';
3980
                $sql .= " " . $fieldht . " = " . ((float) price2num($this->total_ht, 'MT', 1)) . ",";
3981
                $sql .= " " . $fieldtva . " = " . ((float) price2num($this->total_tva, 'MT', 1)) . ",";
3982
                $sql .= " " . $fieldlocaltax1 . " = " . ((float) price2num($this->total_localtax1, 'MT', 1)) . ",";
3983
                $sql .= " " . $fieldlocaltax2 . " = " . ((float) price2num($this->total_localtax2, 'MT', 1)) . ",";
3984
                $sql .= " " . $fieldttc . " = " . ((float) price2num($this->total_ttc, 'MT', 1));
3985
                $sql .= ", multicurrency_total_ht = " . ((float) price2num($this->multicurrency_total_ht, 'MT', 1));
3986
                $sql .= ", multicurrency_total_tva = " . ((float) price2num($this->multicurrency_total_tva, 'MT', 1));
3987
                $sql .= ", multicurrency_total_ttc = " . ((float) price2num($this->multicurrency_total_ttc, 'MT', 1));
3988
                $sql .= " WHERE rowid = " . ((int) $this->id);
3989
3990
                dol_syslog(get_class($this) . "::update_price", LOG_DEBUG);
3991
                $resql = $this->db->query($sql);
3992
3993
                if (!$resql) {
3994
                    $error++;
3995
                    $this->error = $this->db->lasterror();
3996
                    $this->errors[] = $this->db->lasterror();
3997
                }
3998
            }
3999
4000
            if (!$error) {
4001
                $this->db->commit();
4002
                return 1;
4003
            } else {
4004
                $this->db->rollback();
4005
                return -1;
4006
            }
4007
        } else {
4008
            dol_print_error($this->db, 'Bad request in update_price');
4009
            return -1;
4010
        }
4011
    }
4012
4013
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4014
    /**
4015
     *  Add an object link into llx_element_element.
4016
     *
4017
     *  @param      string  $origin     Linked element type
4018
     *  @param      int     $origin_id  Linked element id
4019
     *  @param      User    $f_user     User that create
4020
     *  @param      int     $notrigger  1=Does not execute triggers, 0=execute triggers
4021
     *  @return     int                 Return integer <=0 if KO, >0 if OK
4022
     *  @see        fetchObjectLinked(), updateObjectLinked(), deleteObjectLinked()
4023
     */
4024
    public function add_object_linked($origin = null, $origin_id = null, $f_user = null, $notrigger = 0)
4025
    {
4026
        // phpcs:enable
4027
        global $user, $hookmanager, $action;
4028
        $origin = (!empty($origin) ? $origin : $this->origin);
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocument::$origin has been deprecated: Use now $origin_type and $origin_id; ( Ignorable by Annotation )

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

4028
        $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...
4029
        $origin_id = (!empty($origin_id) ? $origin_id : $this->origin_id);
4030
        $f_user = isset($f_user) ? $f_user : $user;
4031
4032
        // Special case
4033
        if ($origin == 'order') {
4034
            $origin = 'commande';
4035
        }
4036
        if ($origin == 'invoice') {
4037
            $origin = 'facture';
4038
        }
4039
        if ($origin == 'invoice_template') {
4040
            $origin = 'facturerec';
4041
        }
4042
        if ($origin == 'supplierorder') {
4043
            $origin = 'order_supplier';
4044
        }
4045
4046
        // Add module part to target type
4047
        $targettype = $this->getElementType();
4048
4049
        $parameters = array('targettype' => $targettype);
4050
        // Hook for explicitly set the targettype if it must be different than $this->element
4051
        $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4052
        if ($reshook > 0) {
4053
            if (!empty($hookmanager->resArray['targettype'])) {
4054
                $targettype = $hookmanager->resArray['targettype'];
4055
            }
4056
        }
4057
4058
        $this->db->begin();
4059
        $error = 0;
4060
4061
        $sql = "INSERT INTO " . $this->db->prefix() . "element_element (";
4062
        $sql .= "fk_source";
4063
        $sql .= ", sourcetype";
4064
        $sql .= ", fk_target";
4065
        $sql .= ", targettype";
4066
        $sql .= ") VALUES (";
4067
        $sql .= ((int) $origin_id);
4068
        $sql .= ", '" . $this->db->escape($origin) . "'";
4069
        $sql .= ", " . ((int) $this->id);
4070
        $sql .= ", '" . $this->db->escape($targettype) . "'";
4071
        $sql .= ")";
4072
4073
        dol_syslog(get_class($this) . "::add_object_linked", LOG_DEBUG);
4074
        if ($this->db->query($sql)) {
4075
            if (!$notrigger) {
4076
                // Call trigger
4077
                $this->context['link_origin'] = $origin;
4078
                $this->context['link_origin_id'] = $origin_id;
4079
                $result = $this->call_trigger('OBJECT_LINK_INSERT', $f_user);
4080
                if ($result < 0) {
4081
                    $error++;
4082
                }
4083
                // End call triggers
4084
            }
4085
        } else {
4086
            $this->error = $this->db->lasterror();
4087
            $error++;
4088
        }
4089
4090
        if (!$error) {
4091
            $this->db->commit();
4092
            return 1;
4093
        } else {
4094
            $this->db->rollback();
4095
            return 0;
4096
        }
4097
    }
4098
4099
    /**
4100
     * Return an element type string formatted like element_element target_type and source_type
4101
     *
4102
     * @return string
4103
     */
4104
    public function getElementType()
4105
    {
4106
        // 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.
4107
        // 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).
4108
        $coreModule = array('knowledgemanagement', 'partnership', 'workstation', 'ticket', 'recruitment', 'eventorganization', 'asset');
4109
        // Add module part to target type if object has $module property and isn't in core modules.
4110
        return ((!empty($this->module) && !in_array($this->module, $coreModule)) ? $this->module . '_' : '') . $this->element;
4111
    }
4112
4113
4114
    /**
4115
     *  Fetch array of objects linked to current object (object of enabled modules only). Links are loaded into
4116
     *      this->linkedObjectsIds array +
4117
     *      this->linkedObjects array if $loadalsoobjects = 1 or $loadalsoobjects = type
4118
     *  Possible usage for parameters:
4119
     *  - all parameters empty -> we look all link to current object (current object can be source or target)
4120
     *  - source id+type -> will get list of targets linked to source
4121
     *  - target id+type -> will get list of sources linked to target
4122
     *  - source id+type + target type -> will get list of targets of the type linked to source
4123
     *  - target id+type + source type -> will get list of sources of the type linked to target
4124
     *
4125
     *  @param  int         $sourceid           Object source id (if not defined, $this->id)
4126
     *  @param  string      $sourcetype         Object source type (if not defined, $this->element)
4127
     *  @param  int         $targetid           Object target id (if not defined, $this->id)
4128
     *  @param  string      $targettype         Object target type (if not defined, $this->element)
4129
     *  @param  string      $clause             'OR' or 'AND' clause used when both source id and target id are provided
4130
     *  @param  int         $alsosametype       0=Return only links to object that differs from source type. 1=Include also link to objects of same type.
4131
     *  @param  string      $orderby            SQL 'ORDER BY' clause
4132
     *  @param  int|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.
4133
     *  @return int                             Return integer <0 if KO, >0 if OK
4134
     *  @see    add_object_linked(), updateObjectLinked(), deleteObjectLinked()
4135
     */
4136
    public function fetchObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $clause = 'OR', $alsosametype = 1, $orderby = 'sourcetype', $loadalsoobjects = 1)
4137
    {
4138
        global $conf, $hookmanager, $action;
4139
4140
        // Important for pdf generation time reduction
4141
        // This boolean is true if $this->linkedObjects has already been loaded with all objects linked without filter
4142
        // If you need to force the reload, you can call clearObjectLinkedCache() before calling fetchObjectLinked()
4143
        if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
4144
            return 1;
4145
        }
4146
4147
        $this->linkedObjectsIds = array();
4148
        $this->linkedObjects = array();
4149
4150
        $justsource = false;
4151
        $justtarget = false;
4152
        $withtargettype = false;
4153
        $withsourcetype = false;
4154
4155
        $parameters = array('sourcetype' => $sourcetype, 'sourceid' => $sourceid, 'targettype' => $targettype, 'targetid' => $targetid);
4156
        // Hook for explicitly set the targettype if it must be differtent than $this->element
4157
        $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4158
        if ($reshook > 0) {
4159
            if (!empty($hookmanager->resArray['sourcetype'])) {
4160
                $sourcetype = $hookmanager->resArray['sourcetype'];
4161
            }
4162
            if (!empty($hookmanager->resArray['sourceid'])) {
4163
                $sourceid = $hookmanager->resArray['sourceid'];
4164
            }
4165
            if (!empty($hookmanager->resArray['targettype'])) {
4166
                $targettype = $hookmanager->resArray['targettype'];
4167
            }
4168
            if (!empty($hookmanager->resArray['targetid'])) {
4169
                $targetid = $hookmanager->resArray['targetid'];
4170
            }
4171
        }
4172
4173
        if (!empty($sourceid) && !empty($sourcetype) && empty($targetid)) {
4174
            $justsource = true; // the source (id and type) is a search criteria
4175
            if (!empty($targettype)) {
4176
                $withtargettype = true;
4177
            }
4178
        }
4179
        if (!empty($targetid) && !empty($targettype) && empty($sourceid)) {
4180
            $justtarget = true; // the target (id and type) is a search criteria
4181
            if (!empty($sourcetype)) {
4182
                $withsourcetype = true;
4183
            }
4184
        }
4185
4186
        $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
4187
        $targetid = (!empty($targetid) ? $targetid : $this->id);
4188
        $sourcetype = (!empty($sourcetype) ? $sourcetype : $this->element);
4189
        $targettype = (!empty($targettype) ? $targettype : $this->element);
4190
4191
        /*if (empty($sourceid) && empty($targetid))
4192
         {
4193
         dol_syslog('Bad usage of function. No source nor target id defined (nor as parameter nor as object id)', LOG_ERR);
4194
         return -1;
4195
         }*/
4196
4197
        // Links between objects are stored in table element_element
4198
        $sql = "SELECT rowid, fk_source, sourcetype, fk_target, targettype";
4199
        $sql .= " FROM " . $this->db->prefix() . "element_element";
4200
        $sql .= " WHERE ";
4201
        if ($justsource || $justtarget) {
4202
            if ($justsource) {
4203
                $sql .= "fk_source = " . ((int) $sourceid) . " AND sourcetype = '" . $this->db->escape($sourcetype) . "'";
4204
                if ($withtargettype) {
4205
                    $sql .= " AND targettype = '" . $this->db->escape($targettype) . "'";
4206
                }
4207
            } elseif ($justtarget) {
4208
                $sql .= "fk_target = " . ((int) $targetid) . " AND targettype = '" . $this->db->escape($targettype) . "'";
4209
                if ($withsourcetype) {
4210
                    $sql .= " AND sourcetype = '" . $this->db->escape($sourcetype) . "'";
4211
                }
4212
            }
4213
        } else {
4214
            $sql .= "(fk_source = " . ((int) $sourceid) . " AND sourcetype = '" . $this->db->escape($sourcetype) . "')";
4215
            $sql .= " " . $clause . " (fk_target = " . ((int) $targetid) . " AND targettype = '" . $this->db->escape($targettype) . "')";
4216
            if ($loadalsoobjects && $this->id > 0 && $sourceid == $this->id && $sourcetype == $this->element && $targetid == $this->id && $targettype == $this->element && $clause == 'OR') {
4217
                $this->linkedObjectsFullLoaded[$this->id] = true;
4218
            }
4219
        }
4220
        $sql .= " ORDER BY " . $orderby;
4221
4222
        dol_syslog(get_class($this) . "::fetchObjectLink", LOG_DEBUG);
4223
        $resql = $this->db->query($sql);
4224
        if ($resql) {
4225
            $num = $this->db->num_rows($resql);
4226
            $i = 0;
4227
            while ($i < $num) {
4228
                $obj = $this->db->fetch_object($resql);
4229
                if ($justsource || $justtarget) {
4230
                    if ($justsource) {
4231
                        $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
4232
                    } elseif ($justtarget) {
4233
                        $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
4234
                    }
4235
                } else {
4236
                    if ($obj->fk_source == $sourceid && $obj->sourcetype == $sourcetype) {
4237
                        $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
4238
                    }
4239
                    if ($obj->fk_target == $targetid && $obj->targettype == $targettype) {
4240
                        $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
4241
                    }
4242
                }
4243
                $i++;
4244
            }
4245
4246
            if (!empty($this->linkedObjectsIds)) {
4247
                $tmparray = $this->linkedObjectsIds;
4248
                foreach ($tmparray as $objecttype => $objectids) {       // $objecttype is a module name ('facture', 'mymodule', ...) or a module name with a suffix ('project_task', 'mymodule_myobj', ...)
4249
                    $element_properties = getElementProperties($objecttype);
4250
                    $element = $element_properties['element'];
4251
                    $classPath = $element_properties['classpath'];
4252
                    $classFile = $element_properties['classfile'];
4253
                    $className = $element_properties['classname'];
4254
                    $module = $element_properties['module'];
4255
4256
                    // Here $module, $classFile and $className are set, we can use them.
4257
                    if (isModEnabled($module) && (($element != $this->element) || $alsosametype)) {
4258
                        if ($loadalsoobjects && (is_numeric($loadalsoobjects) || ($loadalsoobjects === $objecttype))) {
4259
                            dol_include_once('/' . $classPath . '/' . $classFile . '.class.php');
4260
                            if (class_exists($className)) {
4261
                                foreach ($objectids as $i => $objectid) {   // $i is rowid into llx_element_element
4262
                                    $object = new $className($this->db);
4263
                                    $ret = $object->fetch($objectid);
4264
                                    if ($ret >= 0) {
4265
                                        $this->linkedObjects[$objecttype][$i] = $object;
4266
                                    }
4267
                                }
4268
                            }
4269
                        }
4270
                    } else {
4271
                        unset($this->linkedObjectsIds[$objecttype]);
4272
                    }
4273
                }
4274
            }
4275
            return 1;
4276
        } else {
4277
            dol_print_error($this->db);
4278
            return -1;
4279
        }
4280
    }
4281
4282
    /**
4283
     *  Clear the cache saying that all linked object were already loaded. So next fetchObjectLinked will reload all links.
4284
     *
4285
     *  @return int                     Return integer <0 if KO, >0 if OK
4286
     *  @see    fetchObjectLinked()
4287
     */
4288
    public function clearObjectLinkedCache()
4289
    {
4290
        if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
4291
            unset($this->linkedObjectsFullLoaded[$this->id]);
4292
        }
4293
4294
        return 1;
4295
    }
4296
4297
    /**
4298
     *  Update object linked of a current object
4299
     *
4300
     *  @param  int     $sourceid       Object source id
4301
     *  @param  string  $sourcetype     Object source type
4302
     *  @param  int     $targetid       Object target id
4303
     *  @param  string  $targettype     Object target type
4304
     *  @param  User    $f_user         User that create
4305
     *  @param  int     $notrigger      1=Does not execute triggers, 0= execute triggers
4306
     *  @return                         int >0 if OK, <0 if KO
4307
     *  @see    add_object_linked(), fetObjectLinked(), deleteObjectLinked()
4308
     */
4309
    public function updateObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $f_user = null, $notrigger = 0)
4310
    {
4311
        global $user;
4312
        $updatesource = false;
4313
        $updatetarget = false;
4314
        $f_user = isset($f_user) ? $f_user : $user;
4315
4316
        if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
4317
            $updatesource = true;
4318
        } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
4319
            $updatetarget = true;
4320
        }
4321
4322
        $this->db->begin();
4323
        $error = 0;
4324
4325
        $sql = "UPDATE " . $this->db->prefix() . "element_element SET ";
4326
        if ($updatesource) {
4327
            $sql .= "fk_source = " . ((int) $sourceid);
4328
            $sql .= ", sourcetype = '" . $this->db->escape($sourcetype) . "'";
4329
            $sql .= " WHERE fk_target = " . ((int) $this->id);
4330
            $sql .= " AND targettype = '" . $this->db->escape($this->element) . "'";
4331
        } elseif ($updatetarget) {
4332
            $sql .= "fk_target = " . ((int) $targetid);
4333
            $sql .= ", targettype = '" . $this->db->escape($targettype) . "'";
4334
            $sql .= " WHERE fk_source = " . ((int) $this->id);
4335
            $sql .= " AND sourcetype = '" . $this->db->escape($this->element) . "'";
4336
        }
4337
4338
        dol_syslog(get_class($this) . "::updateObjectLinked", LOG_DEBUG);
4339
        if ($this->db->query($sql)) {
4340
            if (!$notrigger) {
4341
                // Call trigger
4342
                $this->context['link_source_id'] = $sourceid;
4343
                $this->context['link_source_type'] = $sourcetype;
4344
                $this->context['link_target_id'] = $targetid;
4345
                $this->context['link_target_type'] = $targettype;
4346
                $result = $this->call_trigger('OBJECT_LINK_MODIFY', $f_user);
4347
                if ($result < 0) {
4348
                    $error++;
4349
                }
4350
                // End call triggers
4351
            }
4352
        } else {
4353
            $this->error = $this->db->lasterror();
4354
            $error++;
4355
        }
4356
4357
        if (!$error) {
4358
            $this->db->commit();
4359
            return 1;
4360
        } else {
4361
            $this->db->rollback();
4362
            return -1;
4363
        }
4364
    }
4365
4366
    /**
4367
     *  Delete all links between an object $this
4368
     *
4369
     *  @param  int     $sourceid       Object source id
4370
     *  @param  string  $sourcetype     Object source type
4371
     *  @param  int     $targetid       Object target id
4372
     *  @param  string  $targettype     Object target type
4373
     *  @param  int     $rowid          Row id of line to delete. If defined, other parameters are not used.
4374
     *  @param  User    $f_user         User that create
4375
     *  @param  int     $notrigger      1=Does not execute triggers, 0= execute triggers
4376
     *  @return                         int >0 if OK, <0 if KO
4377
     *  @see    add_object_linked(), updateObjectLinked(), fetchObjectLinked()
4378
     */
4379
    public function deleteObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $rowid = 0, $f_user = null, $notrigger = 0)
4380
    {
4381
        global $user;
4382
        $deletesource = false;
4383
        $deletetarget = false;
4384
        $f_user = isset($f_user) ? $f_user : $user;
4385
4386
        if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
4387
            $deletesource = true;
4388
        } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
4389
            $deletetarget = true;
4390
        }
4391
4392
        $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
4393
        $sourcetype = (!empty($sourcetype) ? $sourcetype : $this->element);
4394
        $targetid = (!empty($targetid) ? $targetid : $this->id);
4395
        $targettype = (!empty($targettype) ? $targettype : $this->element);
4396
        $this->db->begin();
4397
        $error = 0;
4398
4399
        if (!$notrigger) {
4400
            // Call trigger
4401
            $this->context['link_id'] = $rowid;
4402
            $this->context['link_source_id'] = $sourceid;
4403
            $this->context['link_source_type'] = $sourcetype;
4404
            $this->context['link_target_id'] = $targetid;
4405
            $this->context['link_target_type'] = $targettype;
4406
            $result = $this->call_trigger('OBJECT_LINK_DELETE', $f_user);
4407
            if ($result < 0) {
4408
                $error++;
4409
            }
4410
            // End call triggers
4411
        }
4412
4413
        if (!$error) {
4414
            $sql = "DELETE FROM " . $this->db->prefix() . "element_element";
4415
            $sql .= " WHERE";
4416
            if ($rowid > 0) {
4417
                $sql .= " rowid = " . ((int) $rowid);
4418
            } else {
4419
                if ($deletesource) {
4420
                    $sql .= " fk_source = " . ((int) $sourceid) . " AND sourcetype = '" . $this->db->escape($sourcetype) . "'";
4421
                    $sql .= " AND fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($this->element) . "'";
4422
                } elseif ($deletetarget) {
4423
                    $sql .= " fk_target = " . ((int) $targetid) . " AND targettype = '" . $this->db->escape($targettype) . "'";
4424
                    $sql .= " AND fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($this->element) . "'";
4425
                } else {
4426
                    $sql .= " (fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($this->element) . "')";
4427
                    $sql .= " OR";
4428
                    $sql .= " (fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($this->element) . "')";
4429
                }
4430
            }
4431
4432
            dol_syslog(get_class($this) . "::deleteObjectLinked", LOG_DEBUG);
4433
            if (!$this->db->query($sql)) {
4434
                $this->error = $this->db->lasterror();
4435
                $this->errors[] = $this->error;
4436
                $error++;
4437
            }
4438
        }
4439
4440
        if (!$error) {
4441
            $this->db->commit();
4442
            return 1;
4443
        } else {
4444
            $this->db->rollback();
4445
            return 0;
4446
        }
4447
    }
4448
4449
    /**
4450
     * Function used to get an array with all items linked to an object id in association table
4451
     *
4452
     * @param   int     $fk_object_where        id of object we need to get linked items
4453
     * @param   string  $field_select           name of field we need to get a list
4454
     * @param   string  $field_where            name of field of object we need to get linked items
4455
     * @param   string  $table_element          name of association table
4456
     * @return  array|int                       Array of record, -1 if empty
4457
     */
4458
    public static function getAllItemsLinkedByObjectID($fk_object_where, $field_select, $field_where, $table_element)
4459
    {
4460
        if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4461
            return -1;
4462
        }
4463
        if (!preg_match('/^[_a-zA-Z0-9]+$/', $field_select)) {
4464
            dol_syslog('Invalid value $field_select for parameter ' . $field_select . ' in call to getAllItemsLinkedByObjectID(). Must be a single field name.', LOG_ERR);
4465
        }
4466
4467
        global $db;
4468
4469
        $sql = "SELECT " . $field_select . " FROM " . $db->prefix() . $table_element . " WHERE " . $field_where . " = " . ((int) $fk_object_where);
4470
        $resql = $db->query($sql);
4471
4472
        $TRes = array();
4473
        if (!empty($resql)) {
4474
            while ($res = $db->fetch_object($resql)) {
4475
                $TRes[] = $res->{$field_select};
4476
            }
4477
        }
4478
4479
        return $TRes;
4480
    }
4481
4482
    /**
4483
     * Count items linked to an object id in association table
4484
     *
4485
     * @param   int     $fk_object_where        id of object we need to get linked items
4486
     * @param   string  $field_where            name of field of object we need to get linked items
4487
     * @param   string  $table_element          name of association table
4488
     * @return  array|int                       Array of record, -1 if empty
4489
     */
4490
    public static function getCountOfItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
4491
    {
4492
        if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4493
            return -1;
4494
        }
4495
4496
        global $db;
4497
4498
        $sql = "SELECT COUNT(*) as nb FROM " . $db->prefix() . $table_element . " WHERE " . $field_where . " = " . ((int) $fk_object_where);
4499
        $resql = $db->query($sql);
4500
        $n = 0;
4501
        if ($resql) {
4502
            $res = $db->fetch_object($resql);
4503
            if ($res) {
4504
                $n = $res->nb;
4505
            }
4506
        }
4507
4508
        return $n;
4509
    }
4510
4511
    /**
4512
     * Function used to remove all items linked to an object id in association table
4513
     *
4514
     * @param   int     $fk_object_where        id of object we need to remove linked items
4515
     * @param   string  $field_where            name of field of object we need to delete linked items
4516
     * @param   string  $table_element          name of association table
4517
     * @return  int                             Return integer <0 if KO, 0 if nothing done, >0 if OK and something done
4518
     */
4519
    public static function deleteAllItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
4520
    {
4521
        if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4522
            return -1;
4523
        }
4524
4525
        global $db;
4526
4527
        $sql = "DELETE FROM " . $db->prefix() . $table_element . " WHERE " . $field_where . " = " . ((int) $fk_object_where);
4528
        $resql = $db->query($sql);
4529
4530
        if (empty($resql)) {
4531
            return 0;
4532
        }
4533
4534
        return 1;
4535
    }
4536
4537
    /**
4538
     *      Set status of an object.
4539
     *
4540
     *      @param  int     $status         Status to set
4541
     *      @param  int     $elementId      Id of element to force (use this->id by default if null)
4542
     *      @param  string  $elementType    Type of element to force (use this->table_element by default)
4543
     *      @param  string  $trigkey        Trigger key to use for trigger. Use '' means automatic but it is not recommended and is deprecated.
4544
     *      @param  string  $fieldstatus    Name of status field in this->table_element
4545
     *      @return int                     Return integer <0 if KO, >0 if OK
4546
     */
4547
    public function setStatut($status, $elementId = null, $elementType = '', $trigkey = '', $fieldstatus = 'fk_statut')
4548
    {
4549
        global $user;
4550
4551
        $savElementId = $elementId; // To be used later to know if we were using the method using the id of this or not.
4552
4553
        $elementId = (!empty($elementId) ? $elementId : $this->id);
4554
        $elementTable = (!empty($elementType) ? $elementType : $this->table_element);
4555
4556
        $this->db->begin();
4557
4558
        if ($elementTable == 'facture_rec') {
4559
            $fieldstatus = "suspended";
4560
        }
4561
        if ($elementTable == 'mailing') {
4562
            $fieldstatus = "statut";
4563
        }
4564
        if ($elementTable == 'cronjob') {
4565
            $fieldstatus = "status";
4566
        }
4567
        if ($elementTable == 'user') {
4568
            $fieldstatus = "statut";
4569
        }
4570
        if ($elementTable == 'expensereport') {
4571
            $fieldstatus = "fk_statut";
4572
        }
4573
        if ($elementTable == 'commande_fournisseur_dispatch') {
4574
            $fieldstatus = "status";
4575
        }
4576
        if ($elementTable == 'prelevement_bons') {
4577
            $fieldstatus = "statut";
4578
        }
4579
        if (isset($this->fields) && is_array($this->fields) && array_key_exists('status', $this->fields)) {
4580
            $fieldstatus = 'status';
4581
        }
4582
4583
        $sql = "UPDATE " . $this->db->prefix() . $elementTable;
4584
        $sql .= " SET " . $fieldstatus . " = " . ((int) $status);
4585
        // If status = 1 = validated, update also fk_user_valid
4586
        // TODO Replace the test on $elementTable by doing a test on existence of the field in $this->fields
4587
        if ($status == 1 && in_array($elementTable, array('expensereport', 'inventory'))) {
4588
            $sql .= ", fk_user_valid = " . ((int) $user->id);
4589
        }
4590
        if ($status == 1 && in_array($elementTable, array('expensereport'))) {
4591
            $sql .= ", date_valid = '" . $this->db->idate(dol_now()) . "'";
4592
        }
4593
        if ($status == 1 && in_array($elementTable, array('inventory'))) {
4594
            $sql .= ", date_validation = '" . $this->db->idate(dol_now()) . "'";
4595
        }
4596
        $sql .= " WHERE rowid = " . ((int) $elementId);
4597
        $sql .= " AND " . $fieldstatus . " <> " . ((int) $status);    // We avoid update if status already correct
4598
4599
        dol_syslog(get_class($this) . "::setStatut", LOG_DEBUG);
4600
        $resql = $this->db->query($sql);
4601
        if ($resql) {
4602
            $error = 0;
4603
4604
            $nb_rows_affected = $this->db->affected_rows($resql);   // should be 1 or 0 if status was already correct
4605
4606
            if ($nb_rows_affected > 0) {
4607
                if (empty($trigkey)) {
4608
                    // Try to guess trigkey (for backward compatibility, now we should have trigkey defined into the call of setStatus)
4609
                    if ($this->element == 'supplier_proposal' && $status == 2) {
4610
                        $trigkey = 'SUPPLIER_PROPOSAL_SIGN'; // 2 = SupplierProposal::STATUS_SIGNED. Can't use constant into this generic class
4611
                    }
4612
                    if ($this->element == 'supplier_proposal' && $status == 3) {
4613
                        $trigkey = 'SUPPLIER_PROPOSAL_REFUSE'; // 3 = SupplierProposal::STATUS_REFUSED. Can't use constant into this generic class
4614
                    }
4615
                    if ($this->element == 'supplier_proposal' && $status == 4) {
4616
                        $trigkey = 'SUPPLIER_PROPOSAL_CLOSE'; // 4 = SupplierProposal::STATUS_CLOSED. Can't use constant into this generic class
4617
                    }
4618
                    if ($this->element == 'fichinter' && $status == 3) {
4619
                        $trigkey = 'FICHINTER_CLASSIFY_DONE';
4620
                    }
4621
                    if ($this->element == 'fichinter' && $status == 2) {
4622
                        $trigkey = 'FICHINTER_CLASSIFY_BILLED';
4623
                    }
4624
                    if ($this->element == 'fichinter' && $status == 1) {
4625
                        $trigkey = 'FICHINTER_CLASSIFY_UNBILLED';
4626
                    }
4627
                }
4628
4629
                if ($trigkey) {
4630
                    // Call trigger
4631
                    $result = $this->call_trigger($trigkey, $user);
4632
                    if ($result < 0) {
4633
                        $error++;
4634
                    }
4635
                    // End call triggers
4636
                }
4637
            } else {
4638
                // The status was probably already good. We do nothing more, no triggers.
4639
            }
4640
4641
            if (!$error) {
4642
                $this->db->commit();
4643
4644
                if (empty($savElementId)) {
4645
                    // If the element we update is $this (so $elementId was provided as null)
4646
                    if ($fieldstatus == 'tosell') {
4647
                        $this->status = $status;
4648
                    } elseif ($fieldstatus == 'tobuy') {
4649
                        $this->status_buy = $status;    // @phpstan-ignore-line
4650
                    } else {
4651
                        $this->statut = $status;
4652
                        $this->status = $status;
4653
                    }
4654
                }
4655
4656
                return 1;
4657
            } else {
4658
                $this->db->rollback();
4659
                dol_syslog(get_class($this) . "::setStatut " . $this->error, LOG_ERR);
4660
                return -1;
4661
            }
4662
        } else {
4663
            $this->error = $this->db->lasterror();
4664
            $this->db->rollback();
4665
            return -1;
4666
        }
4667
    }
4668
4669
4670
    /**
4671
     *  Load type of canvas of an object if it exists
4672
     *
4673
     *  @param      int     $id     Record id
4674
     *  @param      string  $ref    Record ref
4675
     *  @return     int             Return integer <0 if KO, 0 if nothing done, >0 if OK
4676
     */
4677
    public function getCanvas($id = 0, $ref = '')
4678
    {
4679
        global $conf;
4680
4681
        if (empty($id) && empty($ref)) {
4682
            return 0;
4683
        }
4684
        if (getDolGlobalString('MAIN_DISABLE_CANVAS')) {
4685
            return 0; // To increase speed. Not enabled by default.
4686
        }
4687
4688
        // Clean parameters
4689
        $ref = trim($ref);
4690
4691
        $sql = "SELECT rowid, canvas";
4692
        $sql .= " FROM " . $this->db->prefix() . $this->table_element;
4693
        $sql .= " WHERE entity IN (" . getEntity($this->element) . ")";
4694
        if (!empty($id)) {
4695
            $sql .= " AND rowid = " . ((int) $id);
4696
        }
4697
        if (!empty($ref)) {
4698
            $sql .= " AND ref = '" . $this->db->escape($ref) . "'";
4699
        }
4700
4701
        $resql = $this->db->query($sql);
4702
        if ($resql) {
4703
            $obj = $this->db->fetch_object($resql);
4704
            if ($obj) {
4705
                $this->canvas = $obj->canvas;
4706
                return 1;
4707
            } else {
4708
                return 0;
4709
            }
4710
        } else {
4711
            dol_print_error($this->db);
4712
            return -1;
4713
        }
4714
    }
4715
4716
4717
    /**
4718
     *  Get special code of a line
4719
     *
4720
     *  @param  int     $lineid     Id of line
4721
     *  @return int                 Special code
4722
     */
4723
    public function getSpecialCode($lineid)
4724
    {
4725
        $sql = "SELECT special_code FROM " . $this->db->prefix() . $this->table_element_line;
4726
        $sql .= " WHERE rowid = " . ((int) $lineid);
4727
        $resql = $this->db->query($sql);
4728
        if ($resql) {
4729
            $row = $this->db->fetch_row($resql);
4730
            return $row[0];
4731
        }
4732
4733
        return 0;
4734
    }
4735
4736
    /**
4737
     *  Function to check if an object is used by others (by children).
4738
     *  Check is done into this->childtables. There is no check into llx_element_element.
4739
     *
4740
     *  @param  int     $id         Force id of object
4741
     *  @param  int     $entity     Force entity to check
4742
     *  @return int                 Return integer <0 if KO, 0 if not used, >0 if already used
4743
     */
4744
    public function isObjectUsed($id = 0, $entity = 0)
4745
    {
4746
        global $langs;
4747
4748
        if (empty($id)) {
4749
            $id = $this->id;
4750
        }
4751
4752
        // Check parameters
4753
        if (!isset($this->childtables) || !is_array($this->childtables) || count($this->childtables) == 0) {
4754
            dol_print_error(null, 'Called isObjectUsed on a class with property this->childtables not defined');
4755
            return -1;
4756
        }
4757
4758
        $arraytoscan = $this->childtables;      // array('tablename'=>array('fk_element'=>'parentfield'), ...) or array('tablename'=>array('parent'=>table_parent, 'parentkey'=>'nameoffieldforparentfkkey'), ...)
4759
        // For backward compatibility, we check if array is old format array('tablename1', 'tablename2', ...)
4760
        $tmparray = array_keys($this->childtables);
4761
        if (is_numeric($tmparray[0])) {
4762
            $arraytoscan = array_flip($this->childtables);
4763
        }
4764
4765
        // Test if child exists
4766
        $haschild = 0;
4767
        foreach ($arraytoscan as $table => $element) {
4768
            //print $id.'-'.$table.'-'.$elementname.'<br>';
4769
            // Check if element can be deleted
4770
            $sql = "SELECT COUNT(*) as nb";
4771
            $sql .= " FROM " . $this->db->prefix() . $table . " as c";
4772
            if (!empty($element['parent']) && !empty($element['parentkey'])) {
4773
                $sql .= ", " . $this->db->prefix() . $element['parent'] . " as p";
4774
            }
4775
            if (!empty($element['fk_element'])) {
4776
                $sql .= " WHERE c." . $element['fk_element'] . " = " . ((int) $id);
4777
            } else {
4778
                $sql .= " WHERE c." . $this->fk_element . " = " . ((int) $id);
4779
            }
4780
            if (!empty($element['parent']) && !empty($element['parentkey'])) {
4781
                $sql .= " AND c." . $element['parentkey'] . " = p.rowid";
4782
            }
4783
            if (!empty($element['parent']) && !empty($element['parenttypefield']) && !empty($element['parenttypevalue'])) {
4784
                $sql .= " AND c." . $element['parenttypefield'] . " = '" . $this->db->escape($element['parenttypevalue']) . "'";
4785
            }
4786
            if (!empty($entity)) {
4787
                if (!empty($element['parent']) && !empty($element['parentkey'])) {
4788
                    $sql .= " AND p.entity = " . ((int) $entity);
4789
                } else {
4790
                    $sql .= " AND c.entity = " . ((int) $entity);
4791
                }
4792
            }
4793
4794
            $resql = $this->db->query($sql);
4795
            if ($resql) {
4796
                $obj = $this->db->fetch_object($resql);
4797
                if ($obj->nb > 0) {
4798
                    $langs->load("errors");
4799
                    //print 'Found into table '.$table.', type '.$langs->transnoentitiesnoconv($elementname).', haschild='.$haschild;
4800
                    $haschild += $obj->nb;
4801
                    if (is_numeric($element)) { // very old usage array('table1', 'table2', ...)
4802
                        $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $table);
4803
                    } elseif (is_string($element)) { // old usage array('table1' => 'TranslateKey1', 'table2' => 'TranslateKey2', ...)
4804
                        $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element));
4805
                    } else { // new usage: $element['name']=Translation key
4806
                        $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element['name']));
4807
                    }
4808
                    break; // We found at least one, we stop here
4809
                }
4810
            } else {
4811
                $this->errors[] = $this->db->lasterror();
4812
                return -1;
4813
            }
4814
        }
4815
        if ($haschild > 0) {
4816
            $this->errors[] = "ErrorRecordHasChildren";
4817
            return $haschild;
4818
        } else {
4819
            return 0;
4820
        }
4821
    }
4822
4823
    /**
4824
     *  Function to say how many lines object contains
4825
     *
4826
     *  @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
4827
     *  @return int                     Return integer <0 if KO, 0 if no predefined products, nb of lines with predefined products if found
4828
     */
4829
    public function hasProductsOrServices($predefined = -1)
4830
    {
4831
        $nb = 0;
4832
4833
        foreach ($this->lines as $key => $val) {
4834
            $qualified = 0;
4835
            if ($predefined == -1) {
4836
                $qualified = 1;
4837
            }
4838
            if ($predefined == 1 && $val->fk_product > 0) {
4839
                $qualified = 1;
4840
            }
4841
            if ($predefined == 0 && $val->fk_product <= 0) {
4842
                $qualified = 1;
4843
            }
4844
            if ($predefined == 2 && $val->fk_product > 0 && $val->product_type == 0) {
4845
                $qualified = 1;
4846
            }
4847
            if ($predefined == 3 && $val->fk_product > 0 && $val->product_type == 1) {
4848
                $qualified = 1;
4849
            }
4850
            if ($qualified) {
4851
                $nb++;
4852
            }
4853
        }
4854
        dol_syslog(get_class($this) . '::hasProductsOrServices we found ' . $nb . ' qualified lines of products/servcies');
4855
        return $nb;
4856
    }
4857
4858
    /**
4859
     * Function that returns the total amount HT of discounts applied for all lines.
4860
     *
4861
     * @return  float|null          Total amount of discount, or null if $table_element_line is empty
4862
     */
4863
    public function getTotalDiscount()
4864
    {
4865
        if (!empty($this->table_element_line)) {
4866
            $total_discount = 0.00;
4867
4868
            $sql = "SELECT subprice as pu_ht, qty, remise_percent, total_ht";
4869
            $sql .= " FROM " . $this->db->prefix() . $this->table_element_line;
4870
            $sql .= " WHERE " . $this->fk_element . " = " . ((int) $this->id);
4871
4872
            dol_syslog(get_class($this) . '::getTotalDiscount', LOG_DEBUG);
4873
            $resql = $this->db->query($sql);
4874
            if ($resql) {
4875
                $num = $this->db->num_rows($resql);
4876
                $i = 0;
4877
                while ($i < $num) {
4878
                    $obj = $this->db->fetch_object($resql);
4879
4880
                    $pu_ht = $obj->pu_ht;
4881
                    $qty = $obj->qty;
4882
                    $total_ht = $obj->total_ht;
4883
4884
                    $total_discount_line = (float) price2num(($pu_ht * $qty) - $total_ht, 'MT');
4885
                    $total_discount += $total_discount_line;
4886
4887
                    $i++;
4888
                }
4889
            }
4890
4891
            //print $total_discount; exit;
4892
            return (float) price2num($total_discount);
4893
        }
4894
4895
        return null;
4896
    }
4897
4898
4899
    /**
4900
     * Return into unit=0, the calculated total of weight and volume of all lines * qty
4901
     * Calculate by adding weight and volume of each product line, so properties ->volume/volume_units/weight/weight_units must be loaded on line.
4902
     *
4903
     * @return  array                           array('weight'=>...,'volume'=>...)
4904
     */
4905
    public function getTotalWeightVolume()
4906
    {
4907
        $totalWeight = 0;
4908
        $totalVolume = 0;
4909
        // defined for shipment only
4910
        $totalOrdered = '';
4911
        // defined for shipment only
4912
        $totalToShip = '';
4913
4914
        foreach ($this->lines as $line) {
4915
            if (isset($line->qty_asked)) {
4916
                if (empty($totalOrdered)) {
4917
                    $totalOrdered = 0; // Avoid warning because $totalOrdered is ''
4918
                }
4919
                $totalOrdered += $line->qty_asked; // defined for shipment only
4920
            }
4921
            if (isset($line->qty_shipped)) {
4922
                if (empty($totalToShip)) {
4923
                    $totalToShip = 0; // Avoid warning because $totalToShip is ''
4924
                }
4925
                $totalToShip += $line->qty_shipped; // defined for shipment only
4926
            } elseif ($line->element == 'commandefournisseurdispatch' && isset($line->qty)) {
4927
                if (empty($totalToShip)) {
4928
                    $totalToShip = 0;
4929
                }
4930
                $totalToShip += $line->qty; // defined for reception only
4931
            }
4932
4933
            // Define qty, weight, volume, weight_units, volume_units
4934
            if ($this->element == 'shipping') {
4935
                // for shipments
4936
                $qty = $line->qty_shipped ? $line->qty_shipped : 0;
4937
            } else {
4938
                $qty = $line->qty ? $line->qty : 0;
4939
            }
4940
4941
            $weight = !empty($line->weight) ? $line->weight : 0;
4942
            ($weight == 0 && !empty($line->product->weight)) ? $weight = $line->product->weight : 0;
4943
            $volume = !empty($line->volume) ? $line->volume : 0;
4944
            ($volume == 0 && !empty($line->product->volume)) ? $volume = $line->product->volume : 0;
4945
4946
            $weight_units = !empty($line->weight_units) ? $line->weight_units : 0;
4947
            ($weight_units == 0 && !empty($line->product->weight_units)) ? $weight_units = $line->product->weight_units : 0;
4948
            $volume_units = !empty($line->volume_units) ? $line->volume_units : 0;
4949
            ($volume_units == 0 && !empty($line->product->volume_units)) ? $volume_units = $line->product->volume_units : 0;
4950
4951
            $weightUnit = 0;
4952
            $volumeUnit = 0;
4953
            if (!empty($weight_units)) {
4954
                $weightUnit = $weight_units;
4955
            }
4956
            if (!empty($volume_units)) {
4957
                $volumeUnit = $volume_units;
4958
            }
4959
4960
            if (empty($totalWeight)) {
4961
                $totalWeight = 0; // Avoid warning because $totalWeight is ''
4962
            }
4963
            if (empty($totalVolume)) {
4964
                $totalVolume = 0; // Avoid warning because $totalVolume is ''
4965
            }
4966
4967
            //var_dump($line->volume_units);
4968
            if ($weight_units < 50) {   // < 50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
4969
                $trueWeightUnit = pow(10, $weightUnit);
4970
                $totalWeight += $weight * $qty * $trueWeightUnit;
4971
            } else {
4972
                if ($weight_units == 99) {
4973
                    // conversion 1 Pound = 0.45359237 KG
4974
                    $trueWeightUnit = 0.45359237;
4975
                    $totalWeight += $weight * $qty * $trueWeightUnit;
4976
                } elseif ($weight_units == 98) {
4977
                    // conversion 1 Ounce = 0.0283495 KG
4978
                    $trueWeightUnit = 0.0283495;
4979
                    $totalWeight += $weight * $qty * $trueWeightUnit;
4980
                } else {
4981
                    $totalWeight += $weight * $qty; // This may be wrong if we mix different units
4982
                }
4983
            }
4984
            if ($volume_units < 50) {   // >50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
4985
                //print $line->volume."x".$line->volume_units."x".($line->volume_units < 50)."x".$volumeUnit;
4986
                $trueVolumeUnit = pow(10, $volumeUnit);
4987
                //print $line->volume;
4988
                $totalVolume += $volume * $qty * $trueVolumeUnit;
4989
            } else {
4990
                $totalVolume += $volume * $qty; // This may be wrong if we mix different units
4991
            }
4992
        }
4993
4994
        return array('weight' => $totalWeight, 'volume' => $totalVolume, 'ordered' => $totalOrdered, 'toship' => $totalToShip);
4995
    }
4996
4997
4998
    /**
4999
     *  Set extra parameters
5000
     *
5001
     *  @return int      Return integer <0 if KO, >0 if OK
5002
     */
5003
    public function setExtraParameters()
5004
    {
5005
        $this->db->begin();
5006
5007
        $extraparams = (!empty($this->extraparams) ? json_encode($this->extraparams) : null);
5008
5009
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
5010
        $sql .= " SET extraparams = " . (!empty($extraparams) ? "'" . $this->db->escape($extraparams) . "'" : "null");
5011
        $sql .= " WHERE rowid = " . ((int) $this->id);
5012
5013
        dol_syslog(get_class($this) . "::setExtraParameters", LOG_DEBUG);
5014
        $resql = $this->db->query($sql);
5015
        if (!$resql) {
5016
            $this->error = $this->db->lasterror();
5017
            $this->db->rollback();
5018
            return -1;
5019
        } else {
5020
            $this->db->commit();
5021
            return 1;
5022
        }
5023
    }
5024
5025
5026
    // --------------------
5027
    // TODO: All functions here must be redesigned and moved as they are not business functions but output functions
5028
    // --------------------
5029
5030
    /* This is to show add lines */
5031
5032
    /**
5033
     *  Show add free and predefined products/services form
5034
     *
5035
     *  @param  int             $dateSelector       1=Show also date range input fields
5036
     *  @param  Societe         $seller             Object thirdparty who sell
5037
     *  @param  Societe         $buyer              Object thirdparty who buy
5038
     *  @param  string          $defaulttpldir      Directory where to find the template
5039
     *  @return void
5040
     */
5041
    public function formAddObjectLine($dateSelector, $seller, $buyer, $defaulttpldir = '/core/tpl')
5042
    {
5043
        global $conf, $user, $langs, $object, $hookmanager, $extrafields, $form;
5044
5045
        // Line extrafield
5046
        if (!is_object($extrafields)) {
5047
            require_once DOL_DOCUMENT_ROOT . '/core/class/extrafields.class.php';
5048
            $extrafields = new ExtraFields($this->db);
5049
        }
5050
        $extrafields->fetch_name_optionals_label($this->table_element_line);
5051
5052
        // Output template part (modules that overwrite templates must declare this into descriptor)
5053
        // Use global variables + $dateSelector + $seller and $buyer
5054
        // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook 'formAddObjectLine'.
5055
        $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5056
        foreach ($dirtpls as $module => $reldir) {
5057
            if (!empty($module)) {
5058
                $tpl = dol_buildpath($reldir . '/objectline_create.tpl.php');
5059
            } else {
5060
                $tpl = DOL_DOCUMENT_ROOT . $reldir . '/objectline_create.tpl.php';
5061
            }
5062
5063
            if (empty($conf->file->strict_mode)) {
5064
                $res = @include $tpl;
5065
            } else {
5066
                $res = include $tpl; // for debug
5067
            }
5068
            if ($res) {
5069
                break;
5070
            }
5071
        }
5072
    }
5073
5074
5075
5076
    /* This is to show array of line of details */
5077
5078
5079
    /**
5080
     *  Return HTML table for object lines
5081
     *  TODO Move this into an output class file (htmlline.class.php)
5082
     *  If lines are into a template, title must also be into a template
5083
     *  But for the moment we don't know if it's possible as we keep a method available on overloaded objects.
5084
     *
5085
     *  @param  string      $action             Action code
5086
     *  @param  Societe     $seller             Object of seller third party
5087
     *  @param  Societe     $buyer              Object of buyer third party
5088
     *  @param  int         $selected           ID line selected
5089
     *  @param  int         $dateSelector       1=Show also date range input fields
5090
     *  @param  string      $defaulttpldir      Directory where to find the template
5091
     *  @return void
5092
     */
5093
    public function printObjectLines($action, $seller, $buyer, $selected = 0, $dateSelector = 0, $defaulttpldir = '/core/tpl')
5094
    {
5095
        global $conf, $hookmanager, $langs, $user, $form, $extrafields, $object;
5096
        // TODO We should not use global var for this
5097
        global $inputalsopricewithtax, $usemargins, $disableedit, $disablemove, $disableremove, $outputalsopricetotalwithtax;
5098
5099
        // Define usemargins
5100
        $usemargins = 0;
5101
        if (isModEnabled('margin') && !empty($this->element) && in_array($this->element, array('facture', 'facturerec', 'propal', 'commande'))) {
5102
            $usemargins = 1;
5103
        }
5104
5105
        $num = count($this->lines);
5106
5107
        // Line extrafield
5108
        if (!is_object($extrafields)) {
5109
            require_once DOL_DOCUMENT_ROOT . '/core/class/extrafields.class.php';
5110
            $extrafields = new ExtraFields($this->db);
5111
        }
5112
        $extrafields->fetch_name_optionals_label($this->table_element_line);
5113
5114
        $parameters = array('num' => $num, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $this->table_element_line);
5115
        $reshook = $hookmanager->executeHooks('printObjectLineTitle', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5116
        if (empty($reshook)) {
5117
            // Output template part (modules that overwrite templates must declare this into descriptor)
5118
            // Use global variables + $dateSelector + $seller and $buyer
5119
            // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook.
5120
            $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5121
            foreach ($dirtpls as $module => $reldir) {
5122
                $res = 0;
5123
                if (!empty($module)) {
5124
                    $tpl = dol_buildpath($reldir . '/objectline_title.tpl.php');
5125
                } else {
5126
                    $tpl = DOL_DOCUMENT_ROOT . $reldir . '/objectline_title.tpl.php';
5127
                }
5128
                if (file_exists($tpl)) {
5129
                    if (empty($conf->file->strict_mode)) {
5130
                        $res = @include $tpl;
5131
                    } else {
5132
                        $res = include $tpl; // for debug
5133
                    }
5134
                }
5135
                if ($res) {
5136
                    break;
5137
                }
5138
            }
5139
        }
5140
5141
        $i = 0;
5142
5143
        print "<!-- begin printObjectLines() --><tbody>\n";
5144
        foreach ($this->lines as $line) {
5145
            //Line extrafield
5146
            $line->fetch_optionals();
5147
5148
            //if (is_object($hookmanager) && (($line->product_type == 9 && !empty($line->special_code)) || !empty($line->fk_parent_line)))
5149
            if (is_object($hookmanager)) {   // Old code is commented on preceding line.
5150
                if (empty($line->fk_parent_line)) {
5151
                    $parameters = array('line' => $line, 'num' => $num, 'i' => $i, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $line->table_element, 'defaulttpldir' => $defaulttpldir);
5152
                    $reshook = $hookmanager->executeHooks('printObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5153
                } else {
5154
                    $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);
5155
                    $reshook = $hookmanager->executeHooks('printObjectSubLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5156
                }
5157
            }
5158
            if (empty($reshook)) {
5159
                $this->printObjectLine($action, $line, '', $num, $i, $dateSelector, $seller, $buyer, $selected, $extrafields, $defaulttpldir);
5160
            }
5161
5162
            $i++;
5163
        }
5164
        print "</tbody><!-- end printObjectLines() -->\n";
5165
    }
5166
5167
    /**
5168
     *  Return HTML content of a detail line
5169
     *  TODO Move this into an output class file (htmlline.class.php)
5170
     *
5171
     *  @param  string              $action             GET/POST action
5172
     *  @param  CommonObjectLine    $line               Selected object line to output
5173
     *  @param  string              $var                Not used
5174
     *  @param  int                 $num                Number of line (0)
5175
     *  @param  int                 $i                  I
5176
     *  @param  int                 $dateSelector       1=Show also date range input fields
5177
     *  @param  Societe             $seller             Object of seller third party
5178
     *  @param  Societe             $buyer              Object of buyer third party
5179
     *  @param  int                 $selected           ID line selected
5180
     *  @param  Extrafields         $extrafields        Object of extrafields
5181
     *  @param  string              $defaulttpldir      Directory where to find the template (deprecated)
5182
     *  @return void
5183
     */
5184
    public function printObjectLine($action, $line, $var, $num, $i, $dateSelector, $seller, $buyer, $selected = 0, $extrafields = null, $defaulttpldir = '/core/tpl')
5185
    {
5186
        global $conf, $langs, $user, $object, $hookmanager;
5187
        global $form;
5188
        global $object_rights, $disableedit, $disablemove, $disableremove; // TODO We should not use global var for this !
5189
5190
        $object_rights = $this->getRights();
5191
5192
        // var used into tpl
5193
        $text = '';
5194
        $description = '';
5195
5196
        // Line in view mode
5197
        if ($action != 'editline' || $selected != $line->id) {
5198
            // Product
5199
            if (!empty($line->fk_product) && $line->fk_product > 0) {
5200
                $product_static = new Product($this->db);
5201
                $product_static->fetch($line->fk_product);
5202
5203
                $product_static->ref = $line->ref; //can change ref in hook
5204
                $product_static->label = !empty($line->label) ? $line->label : ""; //can change label in hook
5205
5206
                $text = $product_static->getNomUrl(1);
5207
5208
                // Define output language and label
5209
                if (getDolGlobalInt('MAIN_MULTILANGS')) {
5210
                    if (property_exists($this, 'socid') && !is_object($this->thirdparty)) {
5211
                        dol_print_error(null, 'Error: Method printObjectLine was called on an object and object->fetch_thirdparty was not done before');
5212
                        return;
5213
                    }
5214
5215
                    $prod = new Product($this->db);
5216
                    $prod->fetch($line->fk_product);
5217
5218
                    $outputlangs = $langs;
5219
                    $newlang = '';
5220
                    if (empty($newlang) && GETPOST('lang_id', 'aZ09')) {
5221
                        $newlang = GETPOST('lang_id', 'aZ09');
5222
                    }
5223
                    if (getDolGlobalString('PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE') && empty($newlang) && is_object($this->thirdparty)) {
5224
                        $newlang = $this->thirdparty->default_lang; // To use language of customer
5225
                    }
5226
                    if (!empty($newlang)) {
5227
                        $outputlangs = new Translate("", $conf);
5228
                        $outputlangs->setDefaultLang($newlang);
5229
                    }
5230
5231
                    $label = (!empty($prod->multilangs[$outputlangs->defaultlang]["label"])) ? $prod->multilangs[$outputlangs->defaultlang]["label"] : $line->product_label;
5232
                } else {
5233
                    $label = $line->product_label;
5234
                }
5235
5236
                $text .= ' - ' . (!empty($line->label) ? $line->label : $label);
5237
                $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.
5238
            }
5239
5240
            $line->pu_ttc = price2num((!empty($line->subprice) ? $line->subprice : 0) * (1 + ((!empty($line->tva_tx) ? $line->tva_tx : 0) / 100)), 'MU');
5241
5242
            // Output template part (modules that overwrite templates must declare this into descriptor)
5243
            // Use global variables + $dateSelector + $seller and $buyer
5244
            // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5245
            $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5246
            foreach ($dirtpls as $module => $reldir) {
5247
                $res = 0;
5248
                if (!empty($module)) {
5249
                    $tpl = dol_buildpath($reldir . '/objectline_view.tpl.php');
5250
                } else {
5251
                    $tpl = DOL_DOCUMENT_ROOT . $reldir . '/objectline_view.tpl.php';
5252
                }
5253
                //var_dump($tpl);
5254
                if (file_exists($tpl)) {
5255
                    if (empty($conf->file->strict_mode)) {
5256
                        $res = @include $tpl;
5257
                    } else {
5258
                        $res = include $tpl; // for debug
5259
                    }
5260
                }
5261
                if ($res) {
5262
                    break;
5263
                }
5264
            }
5265
        }
5266
5267
        // Line in update mode
5268
        if ($this->statut == 0 && $action == 'editline' && $selected == $line->id) {
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocument::$statut has been deprecated: Use $status instead ( Ignorable by Annotation )

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

5268
        if (/** @scrutinizer ignore-deprecated */ $this->statut == 0 && $action == 'editline' && $selected == $line->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...
5269
            $label = (!empty($line->label) ? $line->label : (($line->fk_product > 0) ? $line->product_label : ''));
5270
5271
            $line->pu_ttc = price2num($line->subprice * (1 + ($line->tva_tx / 100)), 'MU');
5272
5273
            // Output template part (modules that overwrite templates must declare this into descriptor)
5274
            // Use global variables + $dateSelector + $seller and $buyer
5275
            // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5276
            $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5277
            foreach ($dirtpls as $module => $reldir) {
5278
                if (!empty($module)) {
5279
                    $tpl = dol_buildpath($reldir . '/objectline_edit.tpl.php');
5280
                } else {
5281
                    $tpl = DOL_DOCUMENT_ROOT . $reldir . '/objectline_edit.tpl.php';
5282
                }
5283
5284
                if (empty($conf->file->strict_mode)) {
5285
                    $res = @include $tpl;
5286
                } else {
5287
                    $res = include $tpl; // for debug
5288
                }
5289
                if ($res) {
5290
                    break;
5291
                }
5292
            }
5293
        }
5294
    }
5295
5296
5297
    /* This is to show array of line of details of source object */
5298
5299
5300
    /**
5301
     *  Return HTML table table of source object lines
5302
     *  TODO Move this and previous function into output html class file (htmlline.class.php).
5303
     *  If lines are into a template, title must also be into a template
5304
     *  But for the moment we don't know if it's possible, so we keep the method available on overloaded objects.
5305
     *
5306
     *  @param  string      $restrictlist       ''=All lines, 'services'=Restrict to services only
5307
     *  @param  array       $selectedLines      Array of lines id for selected lines
5308
     *  @return void
5309
     */
5310
    public function printOriginLinesList($restrictlist = '', $selectedLines = array())
5311
    {
5312
        global $langs, $hookmanager, $conf, $form, $action;
5313
5314
        print '<tr class="liste_titre">';
5315
        print '<td class="linecolref">' . $langs->trans('Ref') . '</td>';
5316
        print '<td class="linecoldescription">' . $langs->trans('Description') . '</td>';
5317
        print '<td class="linecolvat right">' . $langs->trans('VATRate') . '</td>';
5318
        print '<td class="linecoluht right">' . $langs->trans('PriceUHT') . '</td>';
5319
        if (isModEnabled("multicurrency")) {
5320
            print '<td class="linecoluht_currency right">' . $langs->trans('PriceUHTCurrency') . '</td>';
5321
        }
5322
        print '<td class="linecolqty right">' . $langs->trans('Qty') . '</td>';
5323
        if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
5324
            print '<td class="linecoluseunit left">' . $langs->trans('Unit') . '</td>';
5325
        }
5326
        print '<td class="linecoldiscount right">' . $langs->trans('ReductionShort') . '</td>';
5327
        print '<td class="linecolht right">' . $langs->trans('TotalHT') . '</td>';
5328
        print '<td class="center">' . $form->showCheckAddButtons('checkforselect', 1) . '</td>';
5329
        print '</tr>';
5330
        $i = 0;
5331
5332
        if (!empty($this->lines)) {
5333
            foreach ($this->lines as $line) {
5334
                $reshook = 0;
5335
                //if (is_object($hookmanager) && (($line->product_type == 9 && !empty($line->special_code)) || !empty($line->fk_parent_line))) {
5336
                if (is_object($hookmanager)) {   // Old code is commented on preceding line.
5337
                    $parameters = array('line' => $line, 'i' => $i, 'restrictlist' => $restrictlist, 'selectedLines' => $selectedLines);
5338
                    if (!empty($line->fk_parent_line)) {
5339
                        $parameters['fk_parent_line'] = $line->fk_parent_line;
5340
                    }
5341
                    $reshook = $hookmanager->executeHooks('printOriginObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5342
                }
5343
                if (empty($reshook)) {
5344
                    $this->printOriginLine($line, '', $restrictlist, '/core/tpl', $selectedLines);
5345
                }
5346
5347
                $i++;
5348
            }
5349
        }
5350
    }
5351
5352
    /**
5353
     *  Return HTML with a line of table array of source object lines
5354
     *  TODO Move this and previous function into output html class file (htmlline.class.php).
5355
     *  If lines are into a template, title must also be into a template
5356
     *  But for the moment we don't know if it's possible as we keep a method available on overloaded objects.
5357
     *
5358
     *  @param  CommonObjectLine    $line               Line
5359
     *  @param  string              $var                Not used
5360
     *  @param  string              $restrictlist       ''=All lines, 'services'=Restrict to services only (strike line if not)
5361
     *  @param  string              $defaulttpldir      Directory where to find the template
5362
     *  @param  array               $selectedLines      Array of lines id for selected lines
5363
     *  @return void
5364
     */
5365
    public function printOriginLine($line, $var, $restrictlist = '', $defaulttpldir = '/core/tpl', $selectedLines = array())
5366
    {
5367
        global $langs, $conf;
5368
5369
        //var_dump($line);
5370
        if (!empty($line->date_start)) {
5371
            $date_start = $line->date_start;
5372
        } else {
5373
            $date_start = $line->date_debut_prevue;
5374
            if ($line->date_debut_reel) {
5375
                $date_start = $line->date_debut_reel;
5376
            }
5377
        }
5378
        if (!empty($line->date_end)) {
5379
            $date_end = $line->date_end;
5380
        } else {
5381
            $date_end = $line->date_fin_prevue;
5382
            if ($line->date_fin_reel) {
5383
                $date_end = $line->date_fin_reel;
5384
            }
5385
        }
5386
5387
        $this->tpl['id'] = $line->id;
5388
5389
        $this->tpl['label'] = '';
5390
        if (!empty($line->fk_parent_line)) {
5391
            $this->tpl['label'] .= img_picto('', 'rightarrow');
5392
        }
5393
5394
        if (($line->info_bits & 2) == 2) {  // TODO Not sure this is used for source object
5395
            $discount = new DiscountAbsolute($this->db);
5396
            if (property_exists($this, 'socid')) {
5397
                $discount->fk_soc = $this->socid;
5398
            }
5399
            $this->tpl['label'] .= $discount->getNomUrl(0, 'discount');
5400
        } elseif (!empty($line->fk_product)) {
5401
            $productstatic = new Product($this->db);
5402
            $productstatic->id = $line->fk_product;
5403
            $productstatic->ref = $line->ref;
5404
            $productstatic->type = $line->fk_product_type;
5405
            if (empty($productstatic->ref)) {
5406
                $line->fetch_product();
5407
                $productstatic = $line->product;
5408
            }
5409
5410
            $this->tpl['label'] .= $productstatic->getNomUrl(1);
5411
            $this->tpl['label'] .= ' - ' . (!empty($line->label) ? $line->label : $line->product_label);
5412
            // Dates
5413
            if ($line->product_type == 1 && ($date_start || $date_end)) {
5414
                $this->tpl['label'] .= get_date_range($date_start, $date_end);
5415
            }
5416
        } else {
5417
            $this->tpl['label'] .= ($line->product_type == -1 ? '&nbsp;' : ($line->product_type == 1 ? img_object($langs->trans(''), 'service') : img_object($langs->trans(''), 'product')));
5418
            if (!empty($line->desc)) {
5419
                $this->tpl['label'] .= $line->desc;
5420
            } else {
5421
                $this->tpl['label'] .= ($line->label ? '&nbsp;' . $line->label : '');
5422
            }
5423
5424
            // Dates
5425
            if ($line->product_type == 1 && ($date_start || $date_end)) {
5426
                $this->tpl['label'] .= get_date_range($date_start, $date_end);
5427
            }
5428
        }
5429
5430
        if (!empty($line->desc)) {
5431
            if ($line->desc == '(CREDIT_NOTE)') {  // TODO Not sure this is used for source object
5432
                $discount = new DiscountAbsolute($this->db);
5433
                $discount->fetch($line->fk_remise_except);
5434
                $this->tpl['description'] = $langs->transnoentities("DiscountFromCreditNote", $discount->getNomUrl(0));
5435
            } elseif ($line->desc == '(DEPOSIT)') {  // TODO Not sure this is used for source object
5436
                $discount = new DiscountAbsolute($this->db);
5437
                $discount->fetch($line->fk_remise_except);
5438
                $this->tpl['description'] = $langs->transnoentities("DiscountFromDeposit", $discount->getNomUrl(0));
5439
            } elseif ($line->desc == '(EXCESS RECEIVED)') {
5440
                $discount = new DiscountAbsolute($this->db);
5441
                $discount->fetch($line->fk_remise_except);
5442
                $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessReceived", $discount->getNomUrl(0));
5443
            } elseif ($line->desc == '(EXCESS PAID)') {
5444
                $discount = new DiscountAbsolute($this->db);
5445
                $discount->fetch($line->fk_remise_except);
5446
                $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessPaid", $discount->getNomUrl(0));
5447
            } else {
5448
                $this->tpl['description'] = dol_trunc($line->desc, 60);
5449
            }
5450
        } else {
5451
            $this->tpl['description'] = '&nbsp;';
5452
        }
5453
5454
        // VAT Rate
5455
        $this->tpl['vat_rate'] = vatrate($line->tva_tx, true);
5456
        $this->tpl['vat_rate'] .= (($line->info_bits & 1) == 1) ? '*' : '';
5457
        if (!empty($line->vat_src_code) && !preg_match('/\(/', $this->tpl['vat_rate'])) {
5458
            $this->tpl['vat_rate'] .= ' (' . $line->vat_src_code . ')';
5459
        }
5460
5461
        $this->tpl['price'] = price($line->subprice);
5462
        $this->tpl['total_ht'] = price($line->total_ht);
5463
        $this->tpl['multicurrency_price'] = price($line->multicurrency_subprice);
5464
        $this->tpl['qty'] = (($line->info_bits & 2) != 2) ? $line->qty : '&nbsp;';
5465
        if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
5466
            $this->tpl['unit'] = $langs->transnoentities($line->getLabelOfUnit('long'));
5467
        }
5468
        $this->tpl['remise_percent'] = (($line->info_bits & 2) != 2) ? vatrate($line->remise_percent, true) : '&nbsp;';
5469
5470
        // Is the line strike or not
5471
        $this->tpl['strike'] = 0;
5472
        if ($restrictlist == 'services' && $line->product_type != Product::TYPE_SERVICE) {
5473
            $this->tpl['strike'] = 1;
5474
        }
5475
5476
        // Output template part (modules that overwrite templates must declare this into descriptor)
5477
        // Use global variables + $dateSelector + $seller and $buyer
5478
        $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5479
        foreach ($dirtpls as $module => $reldir) {
5480
            if (!empty($module)) {
5481
                $tpl = dol_buildpath($reldir . '/originproductline.tpl.php');
5482
            } else {
5483
                $tpl = DOL_DOCUMENT_ROOT . $reldir . '/originproductline.tpl.php';
5484
            }
5485
5486
            if (empty($conf->file->strict_mode)) {
5487
                $res = @include $tpl;
5488
            } else {
5489
                $res = include $tpl; // for debug
5490
            }
5491
            if ($res) {
5492
                break;
5493
            }
5494
        }
5495
    }
5496
5497
5498
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5499
    /**
5500
     *  Add resources to the current object : add entry into llx_element_resources
5501
     *  Need $this->element & $this->id
5502
     *
5503
     *  @param      int     $resource_id        Resource id
5504
     *  @param      string  $resource_type      'resource'
5505
     *  @param      int     $busy               Busy or not
5506
     *  @param      int     $mandatory          Mandatory or not
5507
     *  @return     int                         Return integer <=0 if KO, >0 if OK
5508
     */
5509
    public function add_element_resource($resource_id, $resource_type, $busy = 0, $mandatory = 0)
5510
    {
5511
        // phpcs:enable
5512
        $this->db->begin();
5513
5514
        $sql = "INSERT INTO " . $this->db->prefix() . "element_resources (";
5515
        $sql .= "resource_id";
5516
        $sql .= ", resource_type";
5517
        $sql .= ", element_id";
5518
        $sql .= ", element_type";
5519
        $sql .= ", busy";
5520
        $sql .= ", mandatory";
5521
        $sql .= ") VALUES (";
5522
        $sql .= ((int) $resource_id);
5523
        $sql .= ", '" . $this->db->escape($resource_type) . "'";
5524
        $sql .= ", '" . $this->db->escape($this->id) . "'";
5525
        $sql .= ", '" . $this->db->escape($this->element) . "'";
5526
        $sql .= ", '" . $this->db->escape($busy) . "'";
5527
        $sql .= ", '" . $this->db->escape($mandatory) . "'";
5528
        $sql .= ")";
5529
5530
        dol_syslog(get_class($this) . "::add_element_resource", LOG_DEBUG);
5531
        if ($this->db->query($sql)) {
5532
            $this->db->commit();
5533
            return 1;
5534
        } else {
5535
            $this->error = $this->db->lasterror();
5536
            $this->db->rollback();
5537
            return  0;
5538
        }
5539
    }
5540
5541
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5542
    /**
5543
     *    Delete a link to resource line
5544
     *
5545
     *    @param    int     $rowid          Id of resource line to delete
5546
     *    @param    int     $element        element name (for trigger) TODO: use $this->element into commonobject class
5547
     *    @param    int     $notrigger      Disable all triggers
5548
     *    @return   int                     >0 if OK, <0 if KO
5549
     */
5550
    public function delete_resource($rowid, $element, $notrigger = 0)
5551
    {
5552
        // phpcs:enable
5553
        global $user;
5554
5555
        $this->db->begin();
5556
5557
        $sql = "DELETE FROM " . $this->db->prefix() . "element_resources";
5558
        $sql .= " WHERE rowid = " . ((int) $rowid);
5559
5560
        dol_syslog(get_class($this) . "::delete_resource", LOG_DEBUG);
5561
5562
        $resql = $this->db->query($sql);
5563
        if (!$resql) {
5564
            $this->error = $this->db->lasterror();
5565
            $this->db->rollback();
5566
            return -1;
5567
        } else {
5568
            if (!$notrigger) {
5569
                $result = $this->call_trigger(strtoupper($element) . '_DELETE_RESOURCE', $user);
5570
                if ($result < 0) {
5571
                    $this->db->rollback();
5572
                    return -1;
5573
                }
5574
            }
5575
            $this->db->commit();
5576
            return 1;
5577
        }
5578
    }
5579
5580
5581
    /**
5582
     * Overwrite magic function to solve problem of cloning object that are kept as references
5583
     *
5584
     * @return void
5585
     */
5586
    public function __clone()
5587
    {
5588
        // Force a copy of this->lines, otherwise it will point to same object.
5589
        if (isset($this->lines) && is_array($this->lines)) {
5590
            $nboflines = count($this->lines);
5591
            for ($i = 0; $i < $nboflines; $i++) {
5592
                if (is_object($this->lines[$i])) {
5593
                    $this->lines[$i] = clone $this->lines[$i];
5594
                }
5595
            }
5596
        }
5597
    }
5598
5599
    /**
5600
     * Common function for all objects extending CommonObject for generating documents
5601
     *
5602
     * @param   string      $modelspath     Relative folder where generators are placed
5603
     * @param   string      $modele         Generator to use. Caller must set it to obj->model_pdf or $_POST for example.
5604
     * @param   Translate   $outputlangs    Output language to use
5605
     * @param   int         $hidedetails    1 to hide details. 0 by default
5606
     * @param   int         $hidedesc       1 to hide product description. 0 by default
5607
     * @param   int         $hideref        1 to hide product reference. 0 by default
5608
     * @param   null|array  $moreparams     Array to provide more information
5609
     * @return  int                         >0 if OK, <0 if KO
5610
     * @see addFileIntoDatabaseIndex()
5611
     */
5612
    protected function commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams = null)
5613
    {
5614
        global $conf, $langs, $user, $hookmanager, $action;
5615
5616
        $srctemplatepath = '';
5617
5618
        $parameters = array('modelspath' => $modelspath, 'modele' => $modele, 'outputlangs' => $outputlangs, 'hidedetails' => $hidedetails, 'hidedesc' => $hidedesc, 'hideref' => $hideref, 'moreparams' => $moreparams);
5619
        $reshook = $hookmanager->executeHooks('commonGenerateDocument', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5620
5621
        if (!empty($reshook)) {
5622
            return $reshook;
5623
        }
5624
5625
        dol_syslog("commonGenerateDocument modele=" . $modele . " outputlangs->defaultlang=" . (is_object($outputlangs) ? $outputlangs->defaultlang : 'null'));
5626
5627
        if (empty($modele)) {
5628
            $this->error = 'BadValueForParameterModele';
5629
            return -1;
5630
        }
5631
5632
        // Increase limit for PDF build
5633
        $err = error_reporting();
5634
        error_reporting(0);
5635
        @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

5635
        /** @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...
5636
        error_reporting($err);
5637
5638
        // If selected model is a filename template (then $modele="modelname" or "modelname:filename")
5639
        $tmp = explode(':', $modele, 2);
5640
        $saved_model = $modele;
5641
        if (!empty($tmp[1])) {
5642
            $modele = $tmp[0];
5643
            $srctemplatepath = $tmp[1];
5644
        }
5645
5646
        // Search template files
5647
        $file = '';
5648
        $classname = '';
5649
        $filefound = '';
5650
        $dirmodels = array('/');
5651
        if (is_array($conf->modules_parts['models'])) {
5652
            $dirmodels = array_merge($dirmodels, $conf->modules_parts['models']);
5653
        }
5654
        foreach ($dirmodels as $reldir) {
5655
            foreach (array('doc', 'pdf') as $prefix) {
5656
                if (in_array(get_class($this), array('Adherent'))) {
5657
                    // Member module use prefix_modele.class.php
5658
                    $file = $prefix . "_" . $modele . ".class.php";
5659
                } else {
5660
                    // Other module use prefix_modele.modules.php
5661
                    $file = $prefix . "_" . $modele . ".modules.php";
5662
                }
5663
5664
                $file = dol_sanitizeFileName($file);
5665
5666
                // We check if the file exists
5667
                $file = dol_buildpath($reldir . $modelspath . $file, 0);
5668
                if (file_exists($file)) {
5669
                    $filefound = $file;
5670
                    $classname = $prefix . '_' . $modele;
5671
                    break;
5672
                }
5673
            }
5674
            if ($filefound) {
5675
                break;
5676
            }
5677
        }
5678
5679
        if ($filefound === '' || $classname === '') {
5680
            $this->error = $langs->trans("Error") . ' Failed to load doc generator with modelpaths=' . $modelspath . ' - modele=' . $modele;
5681
            $this->errors[] = $this->error;
5682
            dol_syslog($this->error, LOG_ERR);
5683
            return -1;
5684
        }
5685
5686
        // Sanitize $filefound
5687
        $filefound = dol_sanitizePathName($filefound);
5688
5689
        // If generator was found
5690
        global $db; // Required to solve a conception error making an include of some code that uses $db instead of $this->db just after.
5691
5692
        require_once $filefound;
5693
5694
        $obj = new $classname($this->db);
5695
5696
        // If generator is ODT, we must have srctemplatepath defined, if not we set it.
5697
        if ($obj->type == 'odt' && empty($srctemplatepath)) {
5698
            $varfortemplatedir = $obj->scandir;
5699
            if ($varfortemplatedir && getDolGlobalString($varfortemplatedir)) {
5700
                $dirtoscan = getDolGlobalString($varfortemplatedir);
5701
5702
                $listoffiles = array();
5703
5704
                // Now we add first model found in directories scanned
5705
                $listofdir = explode(',', $dirtoscan);
5706
                foreach ($listofdir as $key => $tmpdir) {
5707
                    $tmpdir = trim($tmpdir);
5708
                    $tmpdir = preg_replace('/DOL_DATA_ROOT/', DOL_DATA_ROOT, $tmpdir);
5709
                    if (!$tmpdir) {
5710
                        unset($listofdir[$key]);
5711
                        continue;
5712
                    }
5713
                    if (is_dir($tmpdir)) {
5714
                        $tmpfiles = dol_dir_list($tmpdir, 'files', 0, '\.od(s|t)$', '', 'name', SORT_ASC, 0);
5715
                        if (count($tmpfiles)) {
5716
                            $listoffiles = array_merge($listoffiles, $tmpfiles);
5717
                        }
5718
                    }
5719
                }
5720
5721
                if (count($listoffiles)) {
5722
                    foreach ($listoffiles as $record) {
5723
                        $srctemplatepath = $record['fullname'];
5724
                        break;
5725
                    }
5726
                }
5727
            }
5728
5729
            if (empty($srctemplatepath)) {
5730
                $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotDefined';
5731
                return -1;
5732
            }
5733
        }
5734
5735
        if ($obj->type == 'odt' && !empty($srctemplatepath)) {
5736
            if (!dol_is_file($srctemplatepath)) {
5737
                dol_syslog("Failed to locate template file " . $srctemplatepath, LOG_WARNING);
5738
                $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotFound';
5739
                return -1;
5740
            }
5741
        }
5742
5743
        // We save charset_output to restore it because write_file can change it if needed for
5744
        // output format that does not support UTF8.
5745
        $sav_charset_output = empty($outputlangs->charset_output) ? '' : $outputlangs->charset_output;
5746
5747
        // update model_pdf in object
5748
        $this->model_pdf = $saved_model;
5749
5750
        if (in_array(get_class($this), array('Adherent'))) {
5751
            $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, 'member', 1, 'tmp_cards', $moreparams);
5752
        } else {
5753
            $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, $hidedetails, $hidedesc, $hideref, $moreparams);
5754
        }
5755
        // After call of write_file $obj->result['fullpath'] is set with generated file. It will be used to update the ECM database index.
5756
5757
        if ($resultwritefile > 0) {
5758
            $outputlangs->charset_output = $sav_charset_output;
5759
5760
            // We delete old preview
5761
            require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
5762
            dol_delete_preview($this);
5763
5764
            // Index file in database
5765
            if (!empty($obj->result['fullpath'])) {
5766
                $destfull = $obj->result['fullpath'];
5767
5768
                // Update the last_main_doc field into main object (if document generator has property ->update_main_doc_field set)
5769
                $update_main_doc_field = 0;
5770
                if (!empty($obj->update_main_doc_field)) {
5771
                    $update_main_doc_field = 1;
5772
                }
5773
5774
                // Check that the file exists, before indexing it.
5775
                // Hint: It does not exist, if we create a PDF and auto delete the ODT File
5776
                if (dol_is_file($destfull)) {
5777
                    $this->indexFile($destfull, $update_main_doc_field);
5778
                }
5779
            } else {
5780
                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);
5781
            }
5782
5783
            // Success in building document. We build meta file.
5784
            dol_meta_create($this);
5785
5786
            return 1;
5787
        } else {
5788
            $outputlangs->charset_output = $sav_charset_output;
5789
            $this->error = $obj->error;
5790
            $this->errors = $obj->errors;
5791
            dol_syslog("Error generating document for " . __CLASS__ . ". Error: " . $obj->error, LOG_ERR);
5792
            return -1;
5793
        }
5794
    }
5795
5796
    /**
5797
     * Index a file into the ECM database
5798
     *
5799
     * @param   string  $destfull               Full path of file to index
5800
     * @param   int     $update_main_doc_field  Update field main_doc field into the table of the object.
5801
     *                                          This param is set when called for a document generation if document generator hase
5802
     *                                          ->update_main_doc_field set and returns ->result['fullpath'].
5803
     * @return  int                             Return integer <0 if KO, >0 if OK
5804
     */
5805
    public function indexFile($destfull, $update_main_doc_field)
5806
    {
5807
        global $conf, $user;
5808
5809
        $upload_dir = dirname($destfull);
5810
        $destfile = basename($destfull);
5811
        $rel_dir = preg_replace('/^' . preg_quote(DOL_DATA_ROOT, '/') . '/', '', $upload_dir);
5812
5813
        if (!preg_match('/[\\/]temp[\\/]|[\\/]thumbs|\.meta$/', $rel_dir)) {     // If not a tmp dir
5814
            $filename = basename($destfile);
5815
            $rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
5816
            $rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
5817
5818
            include_once DOL_DOCUMENT_ROOT . '/ecm/class/ecmfiles.class.php';
5819
            $ecmfile = new EcmFiles($this->db);
5820
            $result = $ecmfile->fetch(0, '', ($rel_dir ? $rel_dir . '/' : '') . $filename);
5821
5822
            // Set the public "share" key
5823
            $setsharekey = false;
5824
            if ($this->element == 'propal' || $this->element == 'proposal') {
5825
                if (getDolGlobalInt("PROPOSAL_ALLOW_ONLINESIGN")) {
5826
                    $setsharekey = true;    // feature to make online signature is not set or set to on (default)
5827
                }
5828
                if (getDolGlobalInt("PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
5829
                    $setsharekey = true;
5830
                }
5831
            }
5832
            if ($this->element == 'commande' && getDolGlobalInt("ORDER_ALLOW_EXTERNAL_DOWNLOAD")) {
5833
                $setsharekey = true;
5834
            }
5835
            if ($this->element == 'facture' && getDolGlobalInt("INVOICE_ALLOW_EXTERNAL_DOWNLOAD")) {
5836
                $setsharekey = true;
5837
            }
5838
            if ($this->element == 'bank_account' && getDolGlobalInt("BANK_ACCOUNT_ALLOW_EXTERNAL_DOWNLOAD")) {
5839
                $setsharekey = true;
5840
            }
5841
            if ($this->element == 'product' && getDolGlobalInt("PRODUCT_ALLOW_EXTERNAL_DOWNLOAD")) {
5842
                $setsharekey = true;
5843
            }
5844
            if ($this->element == 'contrat' && getDolGlobalInt("CONTRACT_ALLOW_EXTERNAL_DOWNLOAD")) {
5845
                $setsharekey = true;
5846
            }
5847
            if ($this->element == 'fichinter' && getDolGlobalInt("FICHINTER_ALLOW_EXTERNAL_DOWNLOAD")) {
5848
                $setsharekey = true;
5849
            }
5850
            if ($this->element == 'supplier_proposal' && getDolGlobalInt("SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
5851
                $setsharekey = true;
5852
            }
5853
            if ($this->element == 'societe_rib' && getDolGlobalInt("SOCIETE_RIB_ALLOW_ONLINESIGN")) {
5854
                $setsharekey = true;
5855
            }
5856
5857
            if ($setsharekey) {
5858
                if (empty($ecmfile->share)) {   // Because object not found or share not set yet
5859
                    require_once DOL_DOCUMENT_ROOT . '/core/lib/security2.lib.php';
5860
                    $ecmfile->share = getRandomPassword(true);
5861
                }
5862
            }
5863
5864
            if ($result > 0) {
5865
                $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
5866
                $ecmfile->fullpath_orig = '';
5867
                $ecmfile->gen_or_uploaded = 'generated';
5868
                $ecmfile->description = ''; // indexed content
5869
                $ecmfile->keywords = ''; // keyword content
5870
                $result = $ecmfile->update($user);
5871
                if ($result < 0) {
5872
                    setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
5873
                    return -1;
5874
                }
5875
            } else {
5876
                $ecmfile->entity = $conf->entity;
5877
                $ecmfile->filepath = $rel_dir;
5878
                $ecmfile->filename = $filename;
5879
                $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
5880
                $ecmfile->fullpath_orig = '';
5881
                $ecmfile->gen_or_uploaded = 'generated';
5882
                $ecmfile->description = ''; // indexed content
5883
                $ecmfile->keywords = ''; // keyword content
5884
                $ecmfile->src_object_type = $this->table_element;   // $this->table_name is 'myobject' or 'mymodule_myobject'.
5885
                $ecmfile->src_object_id   = $this->id;
5886
5887
                $result = $ecmfile->create($user);
5888
                if ($result < 0) {
5889
                    setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
5890
                    return -1;
5891
                }
5892
            }
5893
5894
            /*$this->result['fullname']=$destfull;
5895
             $this->result['filepath']=$ecmfile->filepath;
5896
             $this->result['filename']=$ecmfile->filename;*/
5897
            //var_dump($obj->update_main_doc_field);exit;
5898
5899
            if ($update_main_doc_field && !empty($this->table_element)) {
5900
                $sql = "UPDATE " . $this->db->prefix() . $this->table_element . " SET last_main_doc = '" . $this->db->escape($ecmfile->filepath . "/" . $ecmfile->filename) . "'";
5901
                $sql .= " WHERE rowid = " . ((int) $this->id);
5902
5903
                $resql = $this->db->query($sql);
5904
                if (!$resql) {
5905
                    dol_print_error($this->db);
5906
                    return -1;
5907
                } else {
5908
                    $this->last_main_doc = $ecmfile->filepath . '/' . $ecmfile->filename;
5909
                }
5910
            }
5911
        }
5912
5913
        return 1;
5914
    }
5915
5916
    /**
5917
     *  Build thumb
5918
     *  @todo Move this into files.lib.php
5919
     *
5920
     *  @param      string  $file           Path file in UTF8 to original file to create thumbs from.
5921
     *  @return     void
5922
     */
5923
    public function addThumbs($file)
5924
    {
5925
        $file_osencoded = dol_osencode($file);
5926
5927
        if (file_exists($file_osencoded)) {
5928
            require_once DOL_DOCUMENT_ROOT . '/core/lib/images.lib.php';
5929
5930
            $tmparraysize = getDefaultImageSizes();
5931
            $maxwidthsmall = $tmparraysize['maxwidthsmall'];
5932
            $maxheightsmall = $tmparraysize['maxheightsmall'];
5933
            $maxwidthmini = $tmparraysize['maxwidthmini'];
5934
            $maxheightmini = $tmparraysize['maxheightmini'];
5935
            //$quality = $tmparraysize['quality'];
5936
            $quality = 50;  // For thumbs, we force quality to 50
5937
5938
            // Create small thumbs for company (Ratio is near 16/9)
5939
            // Used on logon for example
5940
            vignette($file_osencoded, $maxwidthsmall, $maxheightsmall, '_small', $quality);
5941
5942
            // Create mini thumbs for company (Ratio is near 16/9)
5943
            // Used on menu or for setup page for example
5944
            vignette($file_osencoded, $maxwidthmini, $maxheightmini, '_mini', $quality);
5945
        }
5946
    }
5947
5948
    /**
5949
     *  Delete thumbs
5950
     *  @todo Move this into files.lib.php
5951
     *
5952
     *  @param      string  $file           Path file in UTF8 to original file to delete thumbs.
5953
     *  @return     void
5954
     */
5955
    public function delThumbs($file)
5956
    {
5957
        $imgThumbName = getImageFileNameForSize($file, '_small'); // Full path of thumb file
5958
        dol_delete_file($imgThumbName);
5959
        $imgThumbName = getImageFileNameForSize($file, '_mini'); // Full path of thumb file
5960
        dol_delete_file($imgThumbName);
5961
    }
5962
5963
5964
    /* Functions common to commonobject and commonobjectline */
5965
5966
    /* For default values */
5967
5968
    /**
5969
     * Return the default value to use for a field when showing the create form of object.
5970
     * Return values in this order:
5971
     * 1) If parameter is available into POST, we return it first.
5972
     * 2) If not but an alternate value was provided as parameter of function, we return it.
5973
     * 3) If not but a constant $conf->global->OBJECTELEMENT_FIELDNAME is set, we return it (It is better to use the dedicated table).
5974
     * 4) Return value found into database (TODO No yet implemented)
5975
     *
5976
     * @param   string              $fieldname          Name of field
5977
     * @param   string              $alternatevalue     Alternate value to use
5978
     * @param   string              $type               Type of data
5979
     * @return  string|string[]                         Default value (can be an array if the GETPOST return an array)
5980
     **/
5981
    public function getDefaultCreateValueFor($fieldname, $alternatevalue = null, $type = 'alphanohtml')
5982
    {
5983
        global $_POST;
5984
5985
        // If param here has been posted, we use this value first.
5986
        if (GETPOSTISSET($fieldname)) {
5987
            return GETPOST($fieldname, $type, 3);
5988
        }
5989
5990
        if (isset($alternatevalue)) {
5991
            return $alternatevalue;
5992
        }
5993
5994
        $newelement = $this->element;
5995
        if ($newelement == 'facture') {
5996
            $newelement = 'invoice';
5997
        }
5998
        if ($newelement == 'commande') {
5999
            $newelement = 'order';
6000
        }
6001
        if (empty($newelement)) {
6002
            dol_syslog("Ask a default value using common method getDefaultCreateValueForField on an object with no property ->element defined. Return empty string.", LOG_WARNING);
6003
            return '';
6004
        }
6005
6006
        $keyforfieldname = strtoupper($newelement . '_DEFAULT_' . $fieldname);
6007
        //var_dump($keyforfieldname);
6008
        if (getDolGlobalString($keyforfieldname)) {
6009
            return getDolGlobalString($keyforfieldname);
6010
        }
6011
6012
        // TODO Ad here a scan into table llx_overwrite_default with a filter on $this->element and $fieldname
6013
        // store content into $conf->cache['overwrite_default']
6014
6015
        return '';
6016
    }
6017
6018
6019
    /* For triggers */
6020
6021
6022
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6023
    /**
6024
     * Call trigger based on this instance.
6025
     * Some context information may also be provided into array property this->context.
6026
     * NB:  Error from trigger are stacked in interface->errors
6027
     * NB2: If return code of triggers are < 0, action calling trigger should cancel all transaction.
6028
     *
6029
     * @param   string    $triggerName   trigger's name to execute
6030
     * @param   User      $user           Object user
6031
     * @return  int                       Result of run_triggers
6032
     */
6033
    public function call_trigger($triggerName, $user)
6034
    {
6035
        // phpcs:enable
6036
        global $langs, $conf;
6037
6038
        if (!empty(self::TRIGGER_PREFIX) && strpos($triggerName, self::TRIGGER_PREFIX . '_') !== 0) {
6039
            dol_print_error(null, 'The trigger "' . $triggerName . '" does not start with "' . self::TRIGGER_PREFIX . '_" as required.');
6040
            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...
6041
        }
6042
        if (!is_object($langs)) {   // If lang was not defined, we set it. It is required by run_triggers().
6043
            include_once DOL_DOCUMENT_ROOT . '/core/class/translate.class.php';
6044
            $langs = new Translate('', $conf);
6045
        }
6046
6047
        include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
6048
        $interface = new Interfaces($this->db);
6049
        $result = $interface->run_triggers($triggerName, $this, $user, $langs, $conf);
6050
6051
        if ($result < 0) {
6052
            if (!empty($this->errors)) {
6053
                $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.
6054
            } else {
6055
                $this->errors = $interface->errors;
6056
            }
6057
        }
6058
        return $result;
6059
    }
6060
6061
6062
    /* Functions for data in other language */
6063
6064
6065
    /**
6066
     *  Function to get alternative languages of a data into $this->array_languages
6067
     *  This method is NOT called by method fetch of objects but must be called separately.
6068
     *
6069
     *  @return int                     Return integer <0 if error, 0 if no values of alternative languages to find nor found, 1 if a value was found and loaded
6070
     *  @see fetch_optionnals()
6071
     */
6072
    public function fetchValuesForExtraLanguages()
6073
    {
6074
        // To avoid SQL errors. Probably not the better solution though
6075
        if (!$this->element) {
6076
            return 0;
6077
        }
6078
        if (!($this->id > 0)) {
6079
            return 0;
6080
        }
6081
        if (is_array($this->array_languages)) {
6082
            return 1;
6083
        }
6084
6085
        $this->array_languages = array();
6086
6087
        $element = $this->element;
6088
        if ($element == 'categorie') {
6089
            $element = 'categories'; // For compatibility
6090
        }
6091
6092
        // Request to get translation values for object
6093
        $sql = "SELECT rowid, property, lang , value";
6094
        $sql .= " FROM " . $this->db->prefix() . "object_lang";
6095
        $sql .= " WHERE type_object = '" . $this->db->escape($element) . "'";
6096
        $sql .= " AND fk_object = " . ((int) $this->id);
6097
6098
        //dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG);       // Too verbose
6099
        $resql = $this->db->query($sql);
6100
        if ($resql) {
6101
            $numrows = $this->db->num_rows($resql);
6102
            if ($numrows) {
6103
                $i = 0;
6104
                while ($i < $numrows) {
6105
                    $obj = $this->db->fetch_object($resql);
6106
                    $key = $obj->property;
6107
                    $value = $obj->value;
6108
                    $codelang = $obj->lang;
6109
                    $type = $this->fields[$key]['type'];
6110
6111
                    // we can add this attribute to object
6112
                    if (preg_match('/date/', $type)) {
6113
                        $this->array_languages[$key][$codelang] = $this->db->jdate($value);
6114
                    } else {
6115
                        $this->array_languages[$key][$codelang] = $value;
6116
                    }
6117
6118
                    $i++;
6119
                }
6120
            }
6121
6122
            $this->db->free($resql);
6123
6124
            if ($numrows) {
6125
                return $numrows;
6126
            } else {
6127
                return 0;
6128
            }
6129
        } else {
6130
            dol_print_error($this->db);
6131
            return -1;
6132
        }
6133
    }
6134
6135
    /**
6136
     * Fill array_options property of object by extrafields value (using for data sent by forms)
6137
     *
6138
     * @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.
6139
     * @return  int                     1 if array_options set, 0 if no value, -1 if error (field required missing for example)
6140
     */
6141
    public function setValuesForExtraLanguages($onlykey = '')
6142
    {
6143
        global $_POST, $langs;
6144
6145
        // Get extra fields
6146
        foreach ($_POST as $postfieldkey => $postfieldvalue) {
6147
            $tmparray = explode('-', $postfieldkey);
6148
            if ($tmparray[0] != 'field') {
6149
                continue;
6150
            }
6151
6152
            $element = $tmparray[1];
6153
            $key = $tmparray[2];
6154
            $codelang = $tmparray[3];
6155
            //var_dump("postfieldkey=".$postfieldkey." element=".$element." key=".$key." codelang=".$codelang);
6156
6157
            if (!empty($onlykey) && $key != $onlykey) {
6158
                continue;
6159
            }
6160
            if ($element != $this->element) {
6161
                continue;
6162
            }
6163
6164
            $key_type = $this->fields[$key]['type'];
6165
6166
            $enabled = 1;
6167
            if (isset($this->fields[$key]['enabled'])) {
6168
                $enabled = (int) dol_eval($this->fields[$key]['enabled'], 1, 1, '1');
6169
            }
6170
            /*$perms = 1;
6171
            if (isset($this->fields[$key]['perms']))
6172
            {
6173
                $perms = (int) dol_eval($this->fields[$key]['perms'], 1, 1, '1');
6174
            }*/
6175
            if (empty($enabled)) {
6176
                continue;
6177
            }
6178
            //if (empty($perms)) continue;
6179
6180
            if (in_array($key_type, array('date'))) {
6181
                // Clean parameters
6182
                // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6183
                $value_key = dol_mktime(0, 0, 0, GETPOSTINT($postfieldkey . "month"), GETPOSTINT($postfieldkey . "day"), GETPOSTINT($postfieldkey . "year"));
6184
            } elseif (in_array($key_type, array('datetime'))) {
6185
                // Clean parameters
6186
                // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6187
                $value_key = dol_mktime(GETPOSTINT($postfieldkey . "hour"), GETPOSTINT($postfieldkey . "min"), 0, GETPOSTINT($postfieldkey . "month"), GETPOSTINT($postfieldkey . "day"), GETPOSTINT($postfieldkey . "year"));
6188
            } elseif (in_array($key_type, array('checkbox', 'chkbxlst'))) {
6189
                $value_arr = GETPOST($postfieldkey, 'array'); // check if an array
6190
                if (!empty($value_arr)) {
6191
                    $value_key = implode(',', $value_arr);
6192
                } else {
6193
                    $value_key = '';
6194
                }
6195
            } elseif (in_array($key_type, array('price', 'double'))) {
6196
                $value_arr = GETPOST($postfieldkey, 'alpha');
6197
                $value_key = price2num($value_arr);
6198
            } else {
6199
                $value_key = GETPOST($postfieldkey);
6200
                if (in_array($key_type, array('link')) && $value_key == '-1') {
6201
                    $value_key = '';
6202
                }
6203
            }
6204
6205
            $this->array_languages[$key][$codelang] = $value_key;
6206
6207
            /*if ($nofillrequired) {
6208
                $langs->load('errors');
6209
                setEventMessages($langs->trans('ErrorFieldsRequired').' : '.implode(', ', $error_field_required), null, 'errors');
6210
                return -1;
6211
            }*/
6212
        }
6213
6214
        return 1;
6215
    }
6216
6217
6218
    /* Functions for extrafields */
6219
6220
    /**
6221
     * Function to make a fetch but set environment to avoid to load computed values before.
6222
     *
6223
     * @param   int     $id         ID of object
6224
     * @return  int                 >0 if OK, 0 if not found, <0 if KO
6225
     */
6226
    public function fetchNoCompute($id)
6227
    {
6228
        global $conf;
6229
6230
        $savDisableCompute = $conf->disable_compute;
6231
        $conf->disable_compute = 1;
6232
6233
        $ret = $this->fetch($id);   /* @phpstan-ignore-line */
0 ignored issues
show
Bug introduced by
The method fetch() does not exist on DoliCore\Base\GenericDocument. Did you maybe mean fetch_user()? ( Ignorable by Annotation )

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

6233
        /** @scrutinizer ignore-call */ 
6234
        $ret = $this->fetch($id);   /* @phpstan-ignore-line */

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

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

Loading history...
6234
6235
        $conf->disable_compute = $savDisableCompute;
6236
6237
        return $ret;
6238
    }
6239
6240
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6241
    /**
6242
     *  Function to get extra fields of an object into $this->array_options
6243
     *  This method is in most cases called by method fetch of objects but you can call it separately.
6244
     *
6245
     *  @param  int     $rowid          Id of line. Use the id of object if not defined. Deprecated. Function must be called without parameters.
6246
     *  @param  array   $optionsArray   Array resulting of call of extrafields->fetch_name_optionals_label(). Deprecated. Function must be called without parameters.
6247
     *  @return int                     Return integer <0 if error, 0 if no values of extrafield to find nor found, 1 if an attribute is found and value loaded
6248
     *  @see fetchValuesForExtraLanguages()
6249
     */
6250
    public function fetch_optionals($rowid = null, $optionsArray = null)
6251
    {
6252
        // phpcs:enable
6253
        global $conf, $extrafields;
6254
6255
        if (empty($rowid)) {
6256
            $rowid = $this->id;
6257
        }
6258
        if (empty($rowid) && isset($this->rowid)) {
6259
            $rowid = $this->rowid; // deprecated
6260
        }
6261
6262
        // To avoid SQL errors. Probably not the better solution though
6263
        if (!$this->table_element) {
6264
            return 0;
6265
        }
6266
6267
        $this->array_options = array();
6268
6269
        if (!is_array($optionsArray)) {
6270
            // If $extrafields is not a known object, we initialize it. Best practice is to have $extrafields defined into card.php or list.php page.
6271
            if (!isset($extrafields) || !is_object($extrafields)) {
6272
                require_once DOL_DOCUMENT_ROOT . '/core/class/extrafields.class.php';
6273
                $extrafields = new ExtraFields($this->db);
6274
            }
6275
6276
            // Load array of extrafields for elementype = $this->table_element
6277
            if (empty($extrafields->attributes[$this->table_element]['loaded'])) {
6278
                $extrafields->fetch_name_optionals_label($this->table_element);
6279
            }
6280
            $optionsArray = (!empty($extrafields->attributes[$this->table_element]['label']) ? $extrafields->attributes[$this->table_element]['label'] : null);
6281
        } else {
6282
            global $extrafields;
6283
            dol_syslog("Warning: fetch_optionals was called with param optionsArray defined when you should pass null now", LOG_WARNING);
6284
        }
6285
6286
        $table_element = $this->table_element;
6287
        if ($table_element == 'categorie') {
6288
            $table_element = 'categories'; // For compatibility
6289
        }
6290
6291
        // Request to get complementary values
6292
        if (is_array($optionsArray) && count($optionsArray) > 0) {
6293
            $sql = "SELECT rowid";
6294
            foreach ($optionsArray as $name => $label) {
6295
                if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || $extrafields->attributes[$this->table_element]['type'][$name] != 'separate') {
6296
                    $sql .= ", " . $name;
6297
                }
6298
            }
6299
            $sql .= " FROM " . $this->db->prefix() . $table_element . "_extrafields";
6300
            $sql .= " WHERE fk_object = " . ((int) $rowid);
6301
6302
            //dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG);       // Too verbose
6303
            $resql = $this->db->query($sql);
6304
            if ($resql) {
6305
                $numrows = $this->db->num_rows($resql);
6306
                if ($numrows) {
6307
                    $tab = $this->db->fetch_array($resql);
6308
6309
                    foreach ($tab as $key => $value) {
6310
                        // 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)
6311
                        if ($key != 'rowid' && $key != 'tms' && $key != 'fk_member' && !is_int($key)) {
6312
                            // we can add this attribute to object
6313
                            if (!empty($extrafields->attributes[$this->table_element]) && in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date', 'datetime'))) {
6314
                                //var_dump($extrafields->attributes[$this->table_element]['type'][$key]);
6315
                                $this->array_options["options_" . $key] = $this->db->jdate($value);
6316
                            } else {
6317
                                $this->array_options["options_" . $key] = $value;
6318
                            }
6319
6320
                            //var_dump('key '.$key.' '.$value.' type='.$extrafields->attributes[$this->table_element]['type'][$key].' '.$this->array_options["options_".$key]);
6321
                        }
6322
                        if (!empty($extrafields->attributes[$this->table_element]['type'][$key]) && $extrafields->attributes[$this->table_element]['type'][$key] == 'password') {
6323
                            if (!empty($value) && preg_match('/^dolcrypt:/', $value)) {
6324
                                $this->array_options["options_" . $key] = dolDecrypt($value);
6325
                            }
6326
                        }
6327
                    }
6328
                }
6329
6330
                // If field is a computed field, value must become result of compute (regardless of whether a row exists
6331
                // in the element's extrafields table)
6332
                if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6333
                    foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6334
                        if (!empty($extrafields->attributes[$this->table_element]) && !empty($extrafields->attributes[$this->table_element]['computed'][$key])) {
6335
                            //var_dump($conf->disable_compute);
6336
                            if (empty($conf->disable_compute)) {
6337
                                global $objectoffield;        // We set a global variable to $objectoffield so
6338
                                $objectoffield = $this;        // we can use it inside computed formula
6339
                                $this->array_options['options_' . $key] = dol_eval($extrafields->attributes[$this->table_element]['computed'][$key], 1, 0, '2');
6340
                            }
6341
                        }
6342
                    }
6343
                }
6344
6345
                $this->db->free($resql);
6346
6347
                if ($numrows) {
6348
                    return $numrows;
6349
                } else {
6350
                    return 0;
6351
                }
6352
            } else {
6353
                $this->errors[] = $this->db->lasterror;
6354
                return -1;
6355
            }
6356
        }
6357
        return 0;
6358
    }
6359
6360
    /**
6361
     *  Delete all extra fields values for the current object.
6362
     *
6363
     *  @return int     Return integer <0 if KO, >0 if OK
6364
     *  @see deleteExtraLanguages(), insertExtraField(), updateExtraField(), setValueFrom()
6365
     */
6366
    public function deleteExtraFields()
6367
    {
6368
        global $conf;
6369
6370
        if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
6371
            return 0;
6372
        }
6373
6374
        $this->db->begin();
6375
6376
        $table_element = $this->table_element;
6377
        if ($table_element == 'categorie') {
6378
            $table_element = 'categories'; // For compatibility
6379
        }
6380
6381
        dol_syslog(get_class($this) . "::deleteExtraFields delete", LOG_DEBUG);
6382
6383
        $sql_del = "DELETE FROM " . $this->db->prefix() . $table_element . "_extrafields WHERE fk_object = " . ((int) $this->id);
6384
6385
        $resql = $this->db->query($sql_del);
6386
        if (!$resql) {
6387
            $this->error = $this->db->lasterror();
6388
            $this->db->rollback();
6389
            return -1;
6390
        } else {
6391
            $this->db->commit();
6392
            return 1;
6393
        }
6394
    }
6395
6396
    /**
6397
     *  Add/Update all extra fields values for the current object.
6398
     *  Data to describe values to insert/update are stored into $this->array_options=array('options_codeforfield1'=>'valueforfield1', 'options_codeforfield2'=>'valueforfield2', ...)
6399
     *  This function delete record with all extrafields and insert them again from the array $this->array_options.
6400
     *
6401
     *  @param  string      $trigger        If defined, call also the trigger (for example COMPANY_MODIFY)
6402
     *  @param  User        $userused       Object user
6403
     *  @return int                         -1=error, O=did nothing, 1=OK
6404
     *  @see insertExtraLanguages(), updateExtraField(), deleteExtraField(), setValueFrom()
6405
     */
6406
    public function insertExtraFields($trigger = '', $userused = null)
6407
    {
6408
        global $conf, $langs, $user;
6409
6410
        if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
6411
            return 0;
6412
        }
6413
6414
        if (empty($userused)) {
6415
            $userused = $user;
6416
        }
6417
6418
        $error = 0;
6419
6420
        if (!empty($this->array_options)) {
6421
            // Check parameters
6422
            $langs->load('admin');
6423
            require_once DOL_DOCUMENT_ROOT . '/core/class/extrafields.class.php';
6424
            $extrafields = new ExtraFields($this->db);
6425
            $target_extrafields = $extrafields->fetch_name_optionals_label($this->table_element);
6426
6427
            // Eliminate copied source object extra fields that do not exist in target object
6428
            $new_array_options = array();
6429
            foreach ($this->array_options as $key => $value) {
6430
                if (in_array(substr($key, 8), array_keys($target_extrafields))) {   // We remove the 'options_' from $key for test
6431
                    $new_array_options[$key] = $value;
6432
                } elseif (in_array($key, array_keys($target_extrafields))) {        // We test on $key that does not contain the 'options_' prefix
6433
                    $new_array_options['options_' . $key] = $value;
6434
                }
6435
            }
6436
6437
            foreach ($new_array_options as $key => $value) {
6438
                $attributeKey      = substr($key, 8); // Remove 'options_' prefix
6439
                $attributeType     = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
6440
                $attributeLabel    = $extrafields->attributes[$this->table_element]['label'][$attributeKey];
6441
                $attributeParam    = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
6442
                $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
6443
                $attributeUnique   = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
6444
                $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$attributeKey];
6445
6446
                // If we clone, we have to clean unique extrafields to prevent duplicates.
6447
                // This behaviour can be prevented by external code by changing $this->context['createfromclone'] value in createFrom hook
6448
                if (!empty($this->context['createfromclone']) && $this->context['createfromclone'] == 'createfromclone' && !empty($attributeUnique)) {
6449
                    $new_array_options[$key] = null;
6450
                }
6451
6452
                // Similar code than into insertExtraFields
6453
                if ($attributeRequired) {
6454
                    $v = $this->array_options[$key];
6455
                    if (ExtraFields::isEmptyValue($v, $attributeType)) {
6456
                        $langs->load("errors");
6457
                        dol_syslog("Mandatory field '" . $key . "' is empty during create and set to required into definition of extrafields");
6458
                        $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
6459
                        return -1;
6460
                    }
6461
                }
6462
6463
                //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
6464
                //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
6465
6466
                if (!empty($attrfieldcomputed)) {
6467
                    if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
6468
                        $value = dol_eval($attrfieldcomputed, 1, 0, '2');
6469
                        dol_syslog($langs->trans("Extrafieldcomputed") . " on " . $attributeLabel . "(" . $value . ")", LOG_DEBUG);
6470
                        $new_array_options[$key] = $value;
6471
                    } else {
6472
                        $new_array_options[$key] = null;
6473
                    }
6474
                }
6475
6476
                switch ($attributeType) {
6477
                    case 'int':
6478
                        if (!is_numeric($value) && $value != '') {
6479
                            $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
6480
                            return -1;
6481
                        } elseif ($value == '') {
6482
                            $new_array_options[$key] = null;
6483
                        }
6484
                        break;
6485
                    case 'price':
6486
                    case 'double':
6487
                        $value = price2num($value);
6488
                        if (!is_numeric($value) && $value != '') {
6489
                            dol_syslog($langs->trans("ExtraFieldHasWrongValue") . " for " . $attributeLabel . "(" . $value . "is not '" . $attributeType . "')", LOG_DEBUG);
6490
                            $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
6491
                            return -1;
6492
                        } elseif ($value == '') {
6493
                            $value = null;
6494
                        }
6495
                        //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
6496
                        $new_array_options[$key] = $value;
6497
                        break;
6498
                    /*case 'select':    // Not required, we chose value='0' for undefined values
6499
                         if ($value=='-1')
6500
                         {
6501
                             $this->array_options[$key] = null;
6502
                         }
6503
                         break;*/
6504
                    case 'password':
6505
                        $algo = '';
6506
                        if ($this->array_options[$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
6507
                            // If there is an encryption choice, we use it to encrypt data before insert
6508
                            $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
6509
                            $algo = reset($tmparrays);
6510
                            if ($algo != '') {
6511
                                //global $action;       // $action may be 'create', 'update', 'update_extras'...
6512
                                //var_dump($action);
6513
                                //var_dump($this->oldcopy);exit;
6514
                                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
6515
                                    //var_dump('iii'.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
6516
                                    if (isset($this->oldcopy->array_options[$key]) && $this->array_options[$key] == $this->oldcopy->array_options[$key]) {
6517
                                        // If old value encrypted in database is same than submitted new value, it means we don't change it, so we don't update.
6518
                                        if ($algo == 'dolcrypt') {  // dolibarr reversible encryption
6519
                                            if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
6520
                                                $new_array_options[$key] = dolEncrypt($this->array_options[$key]);  // warning, must be called when on the master
6521
                                            } else {
6522
                                                $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6523
                                            }
6524
                                        } else {
6525
                                            $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6526
                                        }
6527
                                    } else {
6528
                                        // If value has changed
6529
                                        if ($algo == 'dolcrypt') {  // dolibarr reversible encryption
6530
                                            if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
6531
                                                $new_array_options[$key] = dolEncrypt($this->array_options[$key]);  // warning, must be called when on the master
6532
                                            } else {
6533
                                                $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6534
                                            }
6535
                                        } else {
6536
                                            $new_array_options[$key] = dol_hash($this->array_options[$key], $algo);
6537
                                        }
6538
                                    }
6539
                                } else {
6540
                                    //var_dump('jjj'.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
6541
                                    // If this->oldcopy is not defined, we can't know if we change attribute or not, so we must keep value
6542
                                    if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options[$key])) {   // dolibarr reversible encryption
6543
                                        $new_array_options[$key] = dolEncrypt($this->array_options[$key]);  // warning, must be called when on the master
6544
                                    } else {
6545
                                        $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6546
                                    }
6547
                                }
6548
                            } else {
6549
                                // No encryption
6550
                                $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6551
                            }
6552
                        } else { // Common usage
6553
                            $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6554
                        }
6555
                        break;
6556
                    case 'date':
6557
                    case 'datetime':
6558
                        // If data is a string instead of a timestamp, we convert it
6559
                        if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
6560
                            $this->array_options[$key] = strtotime($this->array_options[$key]);
6561
                        }
6562
                        $new_array_options[$key] = $this->db->idate($this->array_options[$key]);
6563
                        break;
6564
                    case 'datetimegmt':
6565
                        // If data is a string instead of a timestamp, we convert it
6566
                        if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
6567
                            $this->array_options[$key] = strtotime($this->array_options[$key]);
6568
                        }
6569
                        $new_array_options[$key] = $this->db->idate($this->array_options[$key], 'gmt');
6570
                        break;
6571
                    case 'link':
6572
                        $param_list = array_keys($attributeParam['options']);
6573
                        // 0 : ObjectName
6574
                        // 1 : classPath
6575
                        $InfoFieldList = explode(":", $param_list[0]);
6576
                        dol_include_once($InfoFieldList[1]);
6577
                        if ($InfoFieldList[0] && class_exists($InfoFieldList[0])) {
6578
                            if ($value == '-1') {   // -1 is key for no defined in combo list of objects
6579
                                $new_array_options[$key] = '';
6580
                            } elseif ($value) {
6581
                                $object = new $InfoFieldList[0]($this->db);
6582
                                if (is_numeric($value)) {
6583
                                    $res = $object->fetch($value); // Common case
6584
                                } else {
6585
                                    $res = $object->fetch('', $value); // For compatibility
6586
                                }
6587
6588
                                if ($res > 0) {
6589
                                    $new_array_options[$key] = $object->id;
6590
                                } else {
6591
                                    $this->error = "Id/Ref '" . $value . "' for object '" . $object->element . "' not found";
6592
                                    return -1;
6593
                                }
6594
                            }
6595
                        } else {
6596
                            dol_syslog('Error bad setup of extrafield', LOG_WARNING);
6597
                        }
6598
                        break;
6599
                    case 'checkbox':
6600
                    case 'chkbxlst':
6601
                        if (is_array($this->array_options[$key])) {
6602
                            $new_array_options[$key] = implode(',', $this->array_options[$key]);
6603
                        } else {
6604
                            $new_array_options[$key] = $this->array_options[$key];
6605
                        }
6606
                        break;
6607
                }
6608
            }
6609
6610
            $this->db->begin();
6611
6612
            $table_element = $this->table_element;
6613
            if ($table_element == 'categorie') {
6614
                $table_element = 'categories'; // For compatibility
6615
            }
6616
6617
            dol_syslog(get_class($this) . "::insertExtraFields delete then insert", LOG_DEBUG);
6618
6619
            $sql_del = "DELETE FROM " . $this->db->prefix() . $table_element . "_extrafields WHERE fk_object = " . ((int) $this->id);
6620
            $this->db->query($sql_del);
6621
6622
            $sql = "INSERT INTO " . $this->db->prefix() . $table_element . "_extrafields (fk_object";
6623
            foreach ($new_array_options as $key => $value) {
6624
                $attributeKey = substr($key, 8); // Remove 'options_' prefix
6625
                // Add field of attribute
6626
                if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] != 'separate') { // Only for other type than separator
6627
                    $sql .= "," . $attributeKey;
6628
                }
6629
            }
6630
            // We must insert a default value for fields for other entities that are mandatory to avoid not null error
6631
            if (!empty($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities']) && is_array($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'])) {
6632
                foreach ($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'] as $tmpkey => $tmpval) {
6633
                    if (!isset($extrafields->attributes[$this->table_element]['type'][$tmpkey])) {    // If field not already added previously
6634
                        $sql .= "," . $tmpkey;
6635
                    }
6636
                }
6637
            }
6638
            $sql .= ") VALUES (" . $this->id;
6639
6640
            foreach ($new_array_options as $key => $value) {
6641
                $attributeKey = substr($key, 8); // Remove 'options_' prefix
6642
                // Add field of attribute
6643
                if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] != 'separate') { // Only for other type than separator)
6644
                    if ($new_array_options[$key] != '' || $new_array_options[$key] == '0') {
6645
                        $sql .= ",'" . $this->db->escape($new_array_options[$key]) . "'";
6646
                    } else {
6647
                        $sql .= ",null";
6648
                    }
6649
                }
6650
            }
6651
            // We must insert a default value for fields for other entities that are mandatory to avoid not null error
6652
            if (!empty($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities']) && is_array($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'])) {
6653
                foreach ($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'] as $tmpkey => $tmpval) {
6654
                    if (!isset($extrafields->attributes[$this->table_element]['type'][$tmpkey])) {   // If field not already added previously
6655
                        if (in_array($tmpval, array('int', 'double', 'price'))) {
6656
                            $sql .= ", 0";
6657
                        } else {
6658
                            $sql .= ", ''";
6659
                        }
6660
                    }
6661
                }
6662
            }
6663
6664
            $sql .= ")";
6665
6666
            $resql = $this->db->query($sql);
6667
            if (!$resql) {
6668
                $this->error = $this->db->lasterror();
6669
                $error++;
6670
            }
6671
6672
            if (!$error && $trigger) {
6673
                // Call trigger
6674
                $this->context = array('extrafieldaddupdate' => 1);
6675
                $result = $this->call_trigger($trigger, $userused);
6676
                if ($result < 0) {
6677
                    $error++;
6678
                }
6679
                // End call trigger
6680
            }
6681
6682
            if ($error) {
6683
                $this->db->rollback();
6684
                return -1;
6685
            } else {
6686
                $this->db->commit();
6687
                return 1;
6688
            }
6689
        } else {
6690
            return 0;
6691
        }
6692
    }
6693
6694
    /**
6695
     *  Add/Update all extra fields values for the current object.
6696
     *  Data to describe values to insert/update are stored into $this->array_options=array('options_codeforfield1'=>'valueforfield1', 'options_codeforfield2'=>'valueforfield2', ...)
6697
     *  This function delete record with all extrafields and insert them again from the array $this->array_options.
6698
     *
6699
     *  @param  string      $trigger        If defined, call also the trigger (for example COMPANY_MODIFY)
6700
     *  @param  User        $userused       Object user
6701
     *  @return int                         -1=error, O=did nothing, 1=OK
6702
     *  @see insertExtraFields(), updateExtraField(), setValueFrom()
6703
     */
6704
    public function insertExtraLanguages($trigger = '', $userused = null)
6705
    {
6706
        global $conf, $langs, $user;
6707
6708
        if (empty($userused)) {
6709
            $userused = $user;
6710
        }
6711
6712
        $error = 0;
6713
6714
        if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
6715
            return 0; // For avoid conflicts if trigger used
6716
        }
6717
6718
        if (is_array($this->array_languages)) {
6719
            $new_array_languages = $this->array_languages;
6720
6721
            foreach ($new_array_languages as $key => $value) {
6722
                $attributeKey      = $key;
6723
                $attributeType     = $this->fields[$attributeKey]['type'];
6724
                $attributeLabel    = $this->fields[$attributeKey]['label'];
6725
6726
                //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
6727
                //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
6728
6729
                switch ($attributeType) {
6730
                    case 'int':
6731
                        if (!is_numeric($value) && $value != '') {
6732
                            $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
6733
                            return -1;
6734
                        } elseif ($value == '') {
6735
                            $new_array_languages[$key] = null;
6736
                        }
6737
                        break;
6738
                    case 'double':
6739
                        $value = price2num($value);
6740
                        if (!is_numeric($value) && $value != '') {
6741
                            dol_syslog($langs->trans("ExtraLanguageHasWrongValue") . " on " . $attributeLabel . "(" . $value . "is not '" . $attributeType . "')", LOG_DEBUG);
6742
                            $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
6743
                            return -1;
6744
                        } elseif ($value == '') {
6745
                            $new_array_languages[$key] = null;
6746
                        } else {
6747
                            $new_array_languages[$key] = $value;
6748
                        }
6749
                        break;
6750
                    /*case 'select':    // Not required, we chose value='0' for undefined values
6751
                     if ($value=='-1')
6752
                     {
6753
                     $this->array_options[$key] = null;
6754
                     }
6755
                     break;*/
6756
                }
6757
            }
6758
6759
            $this->db->begin();
6760
6761
            $table_element = $this->table_element;
6762
            if ($table_element == 'categorie') {
6763
                $table_element = 'categories'; // For compatibility
6764
            }
6765
6766
            dol_syslog(get_class($this) . "::insertExtraLanguages delete then insert", LOG_DEBUG);
6767
6768
            foreach ($new_array_languages as $key => $langcodearray) {  // $key = 'name', 'town', ...
6769
                foreach ($langcodearray as $langcode => $value) {
6770
                    $sql_del = "DELETE FROM " . $this->db->prefix() . "object_lang";
6771
                    $sql_del .= " WHERE fk_object = " . ((int) $this->id) . " AND property = '" . $this->db->escape($key) . "' AND type_object = '" . $this->db->escape($table_element) . "'";
6772
                    $sql_del .= " AND lang = '" . $this->db->escape($langcode) . "'";
6773
                    $this->db->query($sql_del);
6774
6775
                    if ($value !== '') {
6776
                        $sql = "INSERT INTO " . $this->db->prefix() . "object_lang (fk_object, property, type_object, lang, value";
6777
                        $sql .= ") VALUES (" . $this->id . ", '" . $this->db->escape($key) . "', '" . $this->db->escape($table_element) . "', '" . $this->db->escape($langcode) . "', '" . $this->db->escape($value) . "'";
6778
                        $sql .= ")";
6779
6780
                        $resql = $this->db->query($sql);
6781
                        if (!$resql) {
6782
                            $this->error = $this->db->lasterror();
6783
                            $error++;
6784
                            break;
6785
                        }
6786
                    }
6787
                }
6788
            }
6789
6790
            if (!$error && $trigger) {
6791
                // Call trigger
6792
                $this->context = array('extralanguagesaddupdate' => 1);
6793
                $result = $this->call_trigger($trigger, $userused);
6794
                if ($result < 0) {
6795
                    $error++;
6796
                }
6797
                // End call trigger
6798
            }
6799
6800
            if ($error) {
6801
                $this->db->rollback();
6802
                return -1;
6803
            } else {
6804
                $this->db->commit();
6805
                return 1;
6806
            }
6807
        } else {
6808
            return 0;
6809
        }
6810
    }
6811
6812
    /**
6813
     *  Update 1 extra field value for the current object. Keep other fields unchanged.
6814
     *  Data to describe values to update are stored into $this->array_options=array('options_codeforfield1'=>'valueforfield1', 'options_codeforfield2'=>'valueforfield2', ...)
6815
     *
6816
     *  @param  string      $key            Key of the extrafield to update (without starting 'options_')
6817
     *  @param  string      $trigger        If defined, call also the trigger (for example COMPANY_MODIFY)
6818
     *  @param  User        $userused       Object user
6819
     *  @return int                         -1=error, O=did nothing, 1=OK
6820
     *  @see updateExtraLanguages(), insertExtraFields(), deleteExtraFields(), setValueFrom()
6821
     */
6822
    public function updateExtraField($key, $trigger = null, $userused = null)
6823
    {
6824
        global $conf, $langs, $user;
6825
6826
        if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
6827
            return 0;
6828
        }
6829
6830
        if (empty($userused)) {
6831
            $userused = $user;
6832
        }
6833
6834
        $error = 0;
6835
6836
        if (!empty($this->array_options) && isset($this->array_options["options_" . $key])) {
6837
            // Check parameters
6838
            $langs->load('admin');
6839
            require_once DOL_DOCUMENT_ROOT . '/core/class/extrafields.class.php';
6840
            $extrafields = new ExtraFields($this->db);
6841
            $extrafields->fetch_name_optionals_label($this->table_element);
6842
6843
            $value = $this->array_options["options_" . $key];
6844
6845
            $attributeKey      = $key;
6846
            $attributeType     = $extrafields->attributes[$this->table_element]['type'][$key];
6847
            $attributeLabel    = $extrafields->attributes[$this->table_element]['label'][$key];
6848
            $attributeParam    = $extrafields->attributes[$this->table_element]['param'][$key];
6849
            $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$key];
6850
            $attributeUnique   = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
6851
            $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$key];
6852
6853
            // Similar code than into insertExtraFields
6854
            if ($attributeRequired) {
6855
                $mandatorypb = false;
6856
                if ($attributeType == 'link' && $this->array_options["options_" . $key] == '-1') {
6857
                    $mandatorypb = true;
6858
                }
6859
                if ($this->array_options["options_" . $key] === '') {
6860
                    $mandatorypb = true;
6861
                }
6862
                if ($mandatorypb) {
6863
                    $langs->load("errors");
6864
                    dol_syslog("Mandatory field 'options_" . $key . "' is empty during update and set to required into definition of extrafields");
6865
                    $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
6866
                    return -1;
6867
                }
6868
            }
6869
6870
            // $new_array_options will be used for direct update, so must contains formatted data for the UPDATE.
6871
            $new_array_options = $this->array_options;
6872
6873
            //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
6874
            //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
6875
            if (!empty($attrfieldcomputed)) {
6876
                if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
6877
                    $value = dol_eval($attrfieldcomputed, 1, 0, '2');
6878
                    dol_syslog($langs->trans("Extrafieldcomputed") . " on " . $attributeLabel . "(" . $value . ")", LOG_DEBUG);
6879
6880
                    $new_array_options["options_" . $key] = $value;
6881
6882
                    $this->array_options["options_" . $key] = $new_array_options["options_" . $key];
6883
                } else {
6884
                    $new_array_options["options_" . $key] = null;
6885
6886
                    $this->array_options["options_" . $key] = $new_array_options["options_" . $key];
6887
                }
6888
            }
6889
6890
            switch ($attributeType) {
6891
                case 'int':
6892
                    if (!is_numeric($value) && $value != '') {
6893
                        $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
6894
                        return -1;
6895
                    } elseif ($value === '') {
6896
                        $new_array_options["options_" . $key] = null;
6897
6898
                        $this->array_options["options_" . $key] = $new_array_options["options_" . $key];
6899
                    }
6900
                    break;
6901
                case 'price':
6902
                case 'double':
6903
                    $value = price2num($value);
6904
                    if (!is_numeric($value) && $value != '') {
6905
                        dol_syslog($langs->trans("ExtraFieldHasWrongValue") . " on " . $attributeLabel . "(" . $value . "is not '" . $attributeType . "')", LOG_DEBUG);
6906
                        $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
6907
                        return -1;
6908
                    } elseif ($value === '') {
6909
                        $value = null;
6910
                    }
6911
                    //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
6912
                    $new_array_options["options_" . $key] = $value;
6913
6914
                    $this->array_options["options_" . $key] = $new_array_options["options_" . $key];
6915
                    break;
6916
                /*case 'select':    // Not required, we chose value='0' for undefined values
6917
                     if ($value=='-1')
6918
                     {
6919
                        $new_array_options["options_".$key] = $value;
6920
6921
                        $this->array_options["options_".$key] = $new_array_options["options_".$key];
6922
                     }
6923
                     break;*/
6924
                case 'password':
6925
                    $algo = '';
6926
                    if ($this->array_options["options_" . $key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
6927
                        // If there is an encryption choice, we use it to encrypt data before insert
6928
                        $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
6929
                        $algo = reset($tmparrays);
6930
                        if ($algo != '') {
6931
                            //global $action;       // $action may be 'create', 'update', 'update_extras'...
6932
                            //var_dump($action);
6933
                            //var_dump($this->oldcopy);exit;
6934
                            //var_dump($key.' '.$this->array_options["options_".$key].' '.$algo);
6935
                            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
6936
                                //var_dump($this->oldcopy->array_options["options_".$key]); var_dump($this->array_options["options_".$key]);
6937
                                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.
6938
                                    if ($algo == 'dolcrypt') {  // dolibarr reversible encryption
6939
                                        if (!preg_match('/^dolcrypt:/', $this->array_options["options_" . $key])) {
6940
                                            $new_array_options["options_" . $key] = dolEncrypt($this->array_options["options_" . $key]);    // warning, must be called when on the master
6941
                                        } else {
6942
                                            $new_array_options["options_" . $key] = $this->array_options["options_" . $key]; // Value is kept
6943
                                        }
6944
                                    } else {
6945
                                        $new_array_options["options_" . $key] = $this->array_options["options_" . $key]; // Value is kept
6946
                                    }
6947
                                } else {
6948
                                    if ($algo == 'dolcrypt') {  // dolibarr reversible encryption
6949
                                        if (!preg_match('/^dolcrypt:/', $this->array_options["options_" . $key])) {
6950
                                            $new_array_options["options_" . $key] = dolEncrypt($this->array_options["options_" . $key]);
6951
                                        } else {
6952
                                            $new_array_options["options_" . $key] = $this->array_options["options_" . $key]; // Value is kept
6953
                                        }
6954
                                    } else {
6955
                                        $new_array_options["options_" . $key] = dol_hash($this->array_options["options_" . $key], $algo);
6956
                                    }
6957
                                }
6958
                            } else {
6959
                                if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options["options_" . $key])) {    // dolibarr reversible encryption
6960
                                    $new_array_options["options_" . $key] = dolEncrypt($this->array_options["options_" . $key]);    // warning, must be called when on the master
6961
                                } else {
6962
                                    $new_array_options["options_" . $key] = $this->array_options["options_" . $key]; // Value is kept
6963
                                }
6964
                            }
6965
                        } else {
6966
                            // No encryption
6967
                            $new_array_options["options_" . $key] = $this->array_options["options_" . $key]; // Value is kept
6968
                        }
6969
                    } else { // Common usage
6970
                        $new_array_options["options_" . $key] = $this->array_options["options_" . $key]; // Value is kept
6971
                    }
6972
6973
                    $this->array_options["options_" . $key] = $new_array_options["options_" . $key];
6974
                    break;
6975
                case 'date':
6976
                case 'datetime':
6977
                    if (empty($this->array_options["options_" . $key])) {
6978
                        $new_array_options["options_" . $key] = null;
6979
6980
                        $this->array_options["options_" . $key] = $new_array_options["options_" . $key];
6981
                    } else {
6982
                        $new_array_options["options_" . $key] = $this->db->idate($this->array_options["options_" . $key]);
6983
                    }
6984
                    break;
6985
                case 'datetimegmt':
6986
                    if (empty($this->array_options["options_" . $key])) {
6987
                        $new_array_options["options_" . $key] = null;
6988
6989
                        $this->array_options["options_" . $key] = $new_array_options["options_" . $key];
6990
                    } else {
6991
                        $new_array_options["options_" . $key] = $this->db->idate($this->array_options["options_" . $key], 'gmt');
6992
                    }
6993
                    break;
6994
                case 'boolean':
6995
                    if (empty($this->array_options["options_" . $key])) {
6996
                        $new_array_options["options_" . $key] = null;
6997
6998
                        $this->array_options["options_" . $key] = $new_array_options["options_" . $key];
6999
                    }
7000
                    break;
7001
                case 'link':
7002
                    if ($this->array_options["options_" . $key] === '') {
7003
                        $new_array_options["options_" . $key] = null;
7004
7005
                        $this->array_options["options_" . $key] = $new_array_options["options_" . $key];
7006
                    }
7007
                    break;
7008
                /*
7009
                case 'link':
7010
                    $param_list = array_keys($attributeParam['options']);
7011
                    // 0 : ObjectName
7012
                    // 1 : classPath
7013
                    $InfoFieldList = explode(":", $param_list[0]);
7014
                    dol_include_once($InfoFieldList[1]);
7015
                    if ($InfoFieldList[0] && class_exists($InfoFieldList[0]))
7016
                    {
7017
                        if ($value == '-1') // -1 is key for no defined in combo list of objects
7018
                        {
7019
                            $new_array_options[$key] = '';
7020
                        } elseif ($value) {
7021
                            $object = new $InfoFieldList[0]($this->db);
7022
                            if (is_numeric($value)) $res = $object->fetch($value);  // Common case
7023
                            else $res = $object->fetch('', $value);                 // For compatibility
7024
7025
                            if ($res > 0) $new_array_options[$key] = $object->id;
7026
                            else {
7027
                                $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
7028
                                $this->db->rollback();
7029
                                return -1;
7030
                            }
7031
                        }
7032
                    } else {
7033
                        dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7034
                    }
7035
                    break;
7036
                */
7037
                case 'checkbox':
7038
                case 'chkbxlst':
7039
                    $new_array_options = array();
7040
                    if (is_array($this->array_options["options_" . $key])) {
7041
                        $new_array_options["options_" . $key] = implode(',', $this->array_options["options_" . $key]);
7042
                    } else {
7043
                        $new_array_options["options_" . $key] = $this->array_options["options_" . $key];
7044
                    }
7045
7046
                    $this->array_options["options_" . $key] = $new_array_options["options_" . $key];
7047
                    break;
7048
            }
7049
7050
            $this->db->begin();
7051
7052
            $linealreadyfound = 0;
7053
7054
            // 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)
7055
            $sql = "SELECT COUNT(rowid) as nb FROM " . $this->db->prefix() . $this->table_element . "_extrafields WHERE fk_object = " . ((int) $this->id);
7056
            $resql = $this->db->query($sql);
7057
            if ($resql) {
7058
                $tmpobj = $this->db->fetch_object($resql);
7059
                if ($tmpobj) {
7060
                    $linealreadyfound = $tmpobj->nb;
7061
                }
7062
            }
7063
7064
            //var_dump('linealreadyfound='.$linealreadyfound.' sql='.$sql); exit;
7065
            if ($linealreadyfound) {
7066
                if ($this->array_options["options_" . $key] === null) {
7067
                    $sql = "UPDATE " . $this->db->prefix() . $this->table_element . "_extrafields SET " . $key . " = null";
7068
                } else {
7069
                    $sql = "UPDATE " . $this->db->prefix() . $this->table_element . "_extrafields SET " . $key . " = '" . $this->db->escape($new_array_options["options_" . $key]) . "'";
7070
                }
7071
                $sql .= " WHERE fk_object = " . ((int) $this->id);
7072
7073
                $resql = $this->db->query($sql);
7074
                if (!$resql) {
7075
                    $error++;
7076
                    $this->error = $this->db->lasterror();
7077
                }
7078
            } else {
7079
                $result = $this->insertExtraFields('', $user);
7080
                if ($result < 0) {
7081
                    $error++;
7082
                }
7083
            }
7084
7085
            if (!$error && $trigger) {
7086
                // Call trigger
7087
                $this->context = array('extrafieldupdate' => 1);
7088
                $result = $this->call_trigger($trigger, $userused);
7089
                if ($result < 0) {
7090
                    $error++;
7091
                }
7092
                // End call trigger
7093
            }
7094
7095
            if ($error) {
7096
                dol_syslog(__METHOD__ . $this->error, LOG_ERR);
7097
                $this->db->rollback();
7098
                return -1;
7099
            } else {
7100
                $this->db->commit();
7101
                return 1;
7102
            }
7103
        } else {
7104
            return 0;
7105
        }
7106
    }
7107
7108
    /**
7109
     *  Update an extra language value for the current object.
7110
     *  Data to describe values to update are stored into $this->array_options=array('options_codeforfield1'=>'valueforfield1', 'options_codeforfield2'=>'valueforfield2', ...)
7111
     *
7112
     *  @param  string      $key            Key of the extrafield (without starting 'options_')
7113
     *  @param  string      $trigger        If defined, call also the trigger (for example COMPANY_MODIFY)
7114
     *  @param  User        $userused       Object user
7115
     *  @return int                         -1=error, O=did nothing, 1=OK
7116
     *  @see updateExtraField(), insertExtraLanguages()
7117
     */
7118
    public function updateExtraLanguages($key, $trigger = null, $userused = null)
7119
    {
7120
        global $conf, $langs, $user;
7121
7122
        if (empty($userused)) {
7123
            $userused = $user;
7124
        }
7125
7126
        $error = 0;
7127
7128
        if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7129
            return 0; // For avoid conflicts if trigger used
7130
        }
7131
7132
        return 0;
7133
    }
7134
7135
7136
    /**
7137
     * Return HTML string to put an input field into a page
7138
     * Code very similar with showInputField of extra fields
7139
     *
7140
     * @param  array|null   $val           Array of properties for field to show (used only if ->fields not defined)
7141
     * @param  string       $key           Key of attribute
7142
     * @param  string|array $value         Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array)
7143
     * @param  string       $moreparam     To add more parameters on html input tag
7144
     * @param  string       $keysuffix     Prefix string to add into name and id of field (can be used to avoid duplicate names)
7145
     * @param  string       $keyprefix     Suffix string to add into name and id of field (can be used to avoid duplicate names)
7146
     * @param  string|int   $morecss       Value for css to define style/length of field. May also be a numeric.
7147
     * @param  int          $nonewbutton   Force to not show the new button on field that are links to object
7148
     * @return string
7149
     */
7150
    public function showInputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = 0, $nonewbutton = 0)
7151
    {
7152
        global $conf, $langs, $form;
7153
7154
        if (!is_object($form)) {
7155
            require_once DOL_DOCUMENT_ROOT . '/core/class/html.form.class.php';
7156
            $form = new Form($this->db);
7157
        }
7158
7159
        if (!empty($this->fields)) {
7160
            $val = $this->fields[$key];
7161
        }
7162
7163
        // Validation tests and output
7164
        $fieldValidationErrorMsg = '';
7165
        $validationClass = '';
7166
        $fieldValidationErrorMsg = $this->getFieldError($key);
7167
        if (!empty($fieldValidationErrorMsg)) {
7168
            $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
7169
        } else {
7170
            $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
7171
        }
7172
7173
        $out = '';
7174
        $type = '';
7175
        $isDependList = 0;
7176
        $param = array();
7177
        $param['options'] = array();
7178
        $reg = array();
7179
        $size = !empty($this->fields[$key]['size']) ? $this->fields[$key]['size'] : 0;
7180
        // Because we work on extrafields
7181
        if (preg_match('/^(integer|link):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7182
            $param['options'] = array($reg[2] . ':' . $reg[3] . ':' . $reg[4] . ':' . $reg[5] => 'N');
7183
            $type = 'link';
7184
        } elseif (preg_match('/^(integer|link):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7185
            $param['options'] = array($reg[2] . ':' . $reg[3] . ':' . $reg[4] => 'N');
7186
            $type = 'link';
7187
        } elseif (preg_match('/^(integer|link):(.*):(.*)/i', $val['type'], $reg)) {
7188
            $param['options'] = array($reg[2] . ':' . $reg[3] => 'N');
7189
            $type = 'link';
7190
        } elseif (preg_match('/^(sellist):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7191
            $param['options'] = array($reg[2] . ':' . $reg[3] . ':' . $reg[4] . ':' . $reg[5] => 'N');
7192
            $type = 'sellist';
7193
        } elseif (preg_match('/^(sellist):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7194
            $param['options'] = array($reg[2] . ':' . $reg[3] . ':' . $reg[4] => 'N');
7195
            $type = 'sellist';
7196
        } elseif (preg_match('/^(sellist):(.*):(.*)/i', $val['type'], $reg)) {
7197
            $param['options'] = array($reg[2] . ':' . $reg[3] => 'N');
7198
            $type = 'sellist';
7199
        } elseif (preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
7200
            $param['options'] = array($reg[1] => 'N');
7201
            $type = 'chkbxlst';
7202
        } elseif (preg_match('/varchar\((\d+)\)/', $val['type'], $reg)) {
7203
            $param['options'] = array();
7204
            $type = 'varchar';
7205
            $size = $reg[1];
7206
        } elseif (preg_match('/varchar/', $val['type'])) {
7207
            $param['options'] = array();
7208
            $type = 'varchar';
7209
        } else {
7210
            $param['options'] = array();
7211
            $type = $this->fields[$key]['type'];
7212
        }
7213
        //var_dump($type); var_dump($param['options']);
7214
7215
        // Special case that force options and type ($type can be integer, varchar, ...)
7216
        if (!empty($this->fields[$key]['arrayofkeyval']) && is_array($this->fields[$key]['arrayofkeyval'])) {
7217
            $param['options'] = $this->fields[$key]['arrayofkeyval'];
7218
            $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
7219
        }
7220
7221
        $label = $this->fields[$key]['label'];
7222
        //$elementtype=$this->fields[$key]['elementtype'];  // Seems not used
7223
        $default = (!empty($this->fields[$key]['default']) ? $this->fields[$key]['default'] : '');
7224
        $computed = (!empty($this->fields[$key]['computed']) ? $this->fields[$key]['computed'] : '');
7225
        $unique = (!empty($this->fields[$key]['unique']) ? $this->fields[$key]['unique'] : 0);
7226
        $required = (!empty($this->fields[$key]['required']) ? $this->fields[$key]['required'] : 0);
7227
        $autofocusoncreate = (!empty($this->fields[$key]['autofocusoncreate']) ? $this->fields[$key]['autofocusoncreate'] : 0);
7228
7229
        $langfile = (!empty($this->fields[$key]['langfile']) ? $this->fields[$key]['langfile'] : '');
7230
        $list = (!empty($this->fields[$key]['list']) ? $this->fields[$key]['list'] : 0);
7231
        $hidden = (in_array(abs($this->fields[$key]['visible']), array(0, 2)) ? 1 : 0);
7232
7233
        $objectid = $this->id;
7234
7235
        if ($computed) {
7236
            if (!preg_match('/^search_/', $keyprefix)) {
7237
                return '<span class="opacitymedium">' . $langs->trans("AutomaticallyCalculated") . '</span>';
7238
            } else {
7239
                return '';
7240
            }
7241
        }
7242
7243
        // Set value of $morecss. For this, we use in priority showsize from parameters, then $val['css'] then autodefine
7244
        if (empty($morecss) && !empty($val['css'])) {
7245
            $morecss = $val['css'];
7246
        } elseif (empty($morecss)) {
7247
            if ($type == 'date') {
7248
                $morecss = 'minwidth100imp';
7249
            } elseif ($type == 'datetime' || $type == 'link') { // link means an foreign key to another primary id
7250
                $morecss = 'minwidth200imp';
7251
            } elseif (in_array($type, array('int', 'integer', 'price')) || preg_match('/^double(\([0-9],[0-9]\)){0,1}/', $type)) {
7252
                $morecss = 'maxwidth75';
7253
            } elseif ($type == 'url') {
7254
                $morecss = 'minwidth400';
7255
            } elseif ($type == 'boolean') {
7256
                $morecss = '';
7257
            } else {
7258
                if (round($size) < 12) {
7259
                    $morecss = 'minwidth100';
7260
                } elseif (round($size) <= 48) {
7261
                    $morecss = 'minwidth200';
7262
                } else {
7263
                    $morecss = 'minwidth400';
7264
                }
7265
            }
7266
        }
7267
7268
        // Add validation state class
7269
        if (!empty($validationClass)) {
7270
            $morecss .= $validationClass;
7271
        }
7272
7273
        if (in_array($type, array('date'))) {
7274
            $tmp = explode(',', $size);
7275
            $newsize = $tmp[0];
7276
            $showtime = 0;
7277
7278
            // Do not show current date when field not required (see selectDate() method)
7279
            if (!$required && $value == '') {
7280
                $value = '-1';
7281
            }
7282
7283
            // TODO Must also support $moreparam
7284
            $out = $form->selectDate($value, $keyprefix . $key . $keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1);
7285
        } elseif (in_array($type, array('datetime'))) {
7286
            $tmp = explode(',', $size);
7287
            $newsize = $tmp[0];
7288
            $showtime = 1;
7289
7290
            // Do not show current date when field not required (see selectDate() method)
7291
            if (!$required && $value == '') {
7292
                $value = '-1';
7293
            }
7294
7295
            // TODO Must also support $moreparam
7296
            $out = $form->selectDate($value, $keyprefix . $key . $keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1, '', '', '', 1, '', '', 'tzuserrel');
7297
        } elseif (in_array($type, array('duration'))) {
7298
            $out = $form->select_duration($keyprefix . $key . $keysuffix, $value, 0, 'text', 0, 1);
7299
        } elseif (in_array($type, array('int', 'integer'))) {
7300
            $tmp = explode(',', $size);
7301
            $newsize = $tmp[0];
7302
            $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' : '') . '>';
7303
        } elseif (in_array($type, array('real'))) {
7304
            $out = '<input type="text" class="flat ' . $morecss . '" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" value="' . dol_escape_htmltag($value) . '"' . ($moreparam ? $moreparam : '') . ($autofocusoncreate ? ' autofocus' : '') . '>';
7305
        } elseif (preg_match('/varchar/', $type)) {
7306
            $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' : '') . '>';
7307
        } elseif (in_array($type, array('email', 'mail', 'phone', 'url', 'ip'))) {
7308
            $out = '<input type="text" class="flat ' . $morecss . '" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" value="' . dol_escape_htmltag($value) . '" ' . ($moreparam ? $moreparam : '') . ($autofocusoncreate ? ' autofocus' : '') . '>';
7309
        } elseif (preg_match('/^text/', $type)) {
7310
            if (!preg_match('/search_/', $keyprefix)) {     // If keyprefix is search_ or search_options_, we must just use a simple text field
7311
                require_once DOL_DOCUMENT_ROOT . '/core/class/doleditor.class.php';
7312
                $doleditor = new DolEditor($keyprefix . $key . $keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, false, ROWS_5, '90%');
7313
                $out = $doleditor->Create(1, '', true, '', '', '', $morecss);
7314
            } else {
7315
                $out = '<input type="text" class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" value="' . dol_escape_htmltag($value) . '" ' . ($moreparam ? $moreparam : '') . '>';
7316
            }
7317
        } elseif (preg_match('/^html/', $type)) {
7318
            if (!preg_match('/search_/', $keyprefix)) {     // If keyprefix is search_ or search_options_, we must just use a simple text field
7319
                require_once DOL_DOCUMENT_ROOT . '/core/class/doleditor.class.php';
7320
                $doleditor = new DolEditor($keyprefix . $key . $keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, isModEnabled('fckeditor') && $conf->global->FCKEDITOR_ENABLE_SOCIETE, ROWS_5, '90%');
7321
                $out = $doleditor->Create(1, '', true, '', '', $moreparam, $morecss);
7322
            } else {
7323
                $out = '<input type="text" class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" value="' . dol_escape_htmltag($value) . '" ' . ($moreparam ? $moreparam : '') . '>';
7324
            }
7325
        } elseif ($type == 'boolean') {
7326
            $checked = '';
7327
            if (!empty($value)) {
7328
                $checked = ' checked value="1" ';
7329
            } else {
7330
                $checked = ' value="1" ';
7331
            }
7332
            $out = '<input type="checkbox" class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" ' . $checked . ' ' . ($moreparam ? $moreparam : '') . '>';
7333
        } elseif ($type == 'price') {
7334
            if (!empty($value)) {       // $value in memory is a php numeric, we format it into user number format.
7335
                $value = price($value);
7336
            }
7337
            $out = '<input type="text" class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" value="' . $value . '" ' . ($moreparam ? $moreparam : '') . '> ' . $langs->getCurrencySymbol($conf->currency);
7338
        } elseif (preg_match('/^double(\([0-9],[0-9]\)){0,1}/', $type)) {
7339
            if (!empty($value)) {       // $value in memory is a php numeric, we format it into user number format.
7340
                $value = price($value);
7341
            }
7342
            $out = '<input type="text" class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" value="' . $value . '" ' . ($moreparam ? $moreparam : '') . '> ';
7343
        } elseif ($type == 'select') {  // combo list
7344
            $out = '';  // @phan-suppress-current-line PhanPluginRedundantAssignment
7345
            if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
7346
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
7347
                $out .= ajax_combobox($keyprefix . $key . $keysuffix, array(), 0);
7348
            }
7349
7350
            $tmpselect = '';
7351
            $nbchoice = 0;
7352
            foreach ($param['options'] as $keyb => $valb) {
7353
                if ((string) $keyb == '') {
7354
                    continue;
7355
                }
7356
                if (strpos($valb, "|") !== false) {
7357
                    list($valb, $parent) = explode('|', $valb);
7358
                }
7359
                $nbchoice++;
7360
                $tmpselect .= '<option value="' . $keyb . '"';
7361
                $tmpselect .= (((string) $value == (string) $keyb) ? ' selected' : '');
7362
                if (!empty($parent)) {
7363
                    $isDependList = 1;
7364
                }
7365
                $tmpselect .= (!empty($parent) ? ' parent="' . $parent . '"' : '');
7366
                $tmpselect .= '>' . $langs->trans($valb) . '</option>';
7367
            }
7368
7369
            $out .= '<select class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" ' . ($moreparam ? $moreparam : '') . '>';
7370
            if ((!isset($this->fields[$key]['default'])) || ($this->fields[$key]['notnull'] != 1) || $nbchoice >= 2) {
7371
                $out .= '<option value="0">&nbsp;</option>';
7372
            }
7373
            $out .= $tmpselect;
7374
            $out .= '</select>';
7375
        } elseif ($type == 'sellist') {
7376
            $out = '';  // @phan-suppress-current-line PhanPluginRedundantAssignment
7377
            if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) {
7378
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
7379
                $out .= ajax_combobox($keyprefix . $key . $keysuffix, array(), 0);
7380
            }
7381
7382
            $out .= '<select class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" ' . ($moreparam ? $moreparam : '') . '>';
7383
            if (is_array($param['options'])) {
7384
                $param_list = array_keys($param['options']);
7385
                $InfoFieldList = explode(":", $param_list[0], 5);
7386
                if (! empty($InfoFieldList[4])) {
7387
                    $pos = 0;
7388
                    $parenthesisopen = 0;
7389
                    while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
7390
                        if (substr($InfoFieldList[4], $pos, 1) == '(') {
7391
                            $parenthesisopen++;
7392
                        }
7393
                        if (substr($InfoFieldList[4], $pos, 1) == ')') {
7394
                            $parenthesisopen--;
7395
                        }
7396
                        $pos++;
7397
                    }
7398
                    $tmpbefore = substr($InfoFieldList[4], 0, $pos);
7399
                    $tmpafter = substr($InfoFieldList[4], $pos + 1);
7400
                    //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter);
7401
                    $InfoFieldList[4] = $tmpbefore;
7402
                    if ($tmpafter !== '') {
7403
                        $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
7404
                    }
7405
                    //var_dump($InfoFieldList);
7406
                }
7407
                $parentName = '';
7408
                $parentField = '';
7409
7410
                // 0 : tableName
7411
                // 1 : label field name
7412
                // 2 : key fields name (if differ of rowid)
7413
                // 3 : key field parent (for dependent lists)
7414
                // 4 : where clause filter on column or table extrafield, syntax field='value' or extra.field=value
7415
                // 5 : id category type
7416
                // 6 : ids categories list separated by comma for category root
7417
                // 7 : sort field
7418
                $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2] . ' as rowid');
7419
7420
                if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
7421
                    if (strpos($InfoFieldList[4], 'extra.') !== false) {
7422
                        $keyList = 'main.' . $InfoFieldList[2] . ' as rowid';
7423
                    } else {
7424
                        $keyList = $InfoFieldList[2] . ' as rowid';
7425
                    }
7426
                }
7427
                if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
7428
                    list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
7429
                    $keyList .= ', ' . $parentField;
7430
                }
7431
7432
                $filter_categorie = false;
7433
                if (count($InfoFieldList) > 5) {
7434
                    if ($InfoFieldList[0] == 'categorie') {
7435
                        $filter_categorie = true;
7436
                    }
7437
                }
7438
7439
                if ($filter_categorie === false) {
7440
                    $fields_label = explode('|', $InfoFieldList[1]);
7441
                    if (is_array($fields_label)) {
7442
                        $keyList .= ', ';
7443
                        $keyList .= implode(', ', $fields_label);
7444
                    }
7445
7446
                    $sqlwhere = '';
7447
                    $sql = "SELECT " . $keyList;
7448
                    $sql .= " FROM " . $this->db->prefix() . $InfoFieldList[0];
7449
                    if (!empty($InfoFieldList[4])) {
7450
                        // can use SELECT request
7451
                        if (strpos($InfoFieldList[4], '$SEL$') !== false) {
7452
                            $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
7453
                        }
7454
7455
                        // current object id can be use into filter
7456
                        if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
7457
                            $InfoFieldList[4] = str_replace('$ID$', $objectid, $InfoFieldList[4]);
7458
                        } else {
7459
                            $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
7460
                        }
7461
7462
                        // We have to join on extrafield table
7463
                        $errstr = '';
7464
                        if (strpos($InfoFieldList[4], 'extra') !== false) {
7465
                            $sql .= " as main, " . $this->db->prefix() . $InfoFieldList[0] . "_extrafields as extra";
7466
                            $sqlwhere .= " WHERE extra.fk_object=main." . $InfoFieldList[2];
7467
                            $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
7468
                        } else {
7469
                            $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1);
7470
                        }
7471
                    } else {
7472
                        $sqlwhere .= ' WHERE 1=1';
7473
                    }
7474
                    // Some tables may have field, some other not. For the moment we disable it.
7475
                    if (in_array($InfoFieldList[0], array('tablewithentity'))) {
7476
                        $sqlwhere .= " AND entity = " . ((int) $conf->entity);
7477
                    }
7478
                    $sql .= $sqlwhere;
7479
                    //print $sql;
7480
7481
                    // Note: $InfoFieldList can be 'sellist:TableName:LabelFieldName[:KeyFieldName[:KeyFieldParent[:Filter[:CategoryIdType[:CategoryIdList[:Sortfield]]]]]]'
7482
                    if (isset($InfoFieldList[7]) && preg_match('/^[a-z0-9_\-,]+$/i', $InfoFieldList[7])) {
7483
                        $sql .= " ORDER BY " . $this->db->escape($InfoFieldList[7]);
7484
                    } else {
7485
                        $sql .= " ORDER BY " . $this->db->sanitize(implode(', ', $fields_label));
7486
                    }
7487
7488
                    dol_syslog(get_class($this) . '::showInputField type=sellist', LOG_DEBUG);
7489
                    $resql = $this->db->query($sql);
7490
                    if ($resql) {
7491
                        $out .= '<option value="0">&nbsp;</option>';
7492
                        $num = $this->db->num_rows($resql);
7493
                        $i = 0;
7494
                        while ($i < $num) {
7495
                            $labeltoshow = '';
7496
                            $obj = $this->db->fetch_object($resql);
7497
7498
                            // Several field into label (eq table:code|libelle:rowid)
7499
                            $notrans = false;
7500
                            $fields_label = explode('|', $InfoFieldList[1]);
7501
                            if (count($fields_label) > 1) {
7502
                                $notrans = true;
7503
                                foreach ($fields_label as $field_toshow) {
7504
                                    $labeltoshow .= $obj->$field_toshow . ' ';
7505
                                }
7506
                            } else {
7507
                                $labeltoshow = $obj->{$InfoFieldList[1]};
7508
                            }
7509
                            $labeltoshow = dol_trunc($labeltoshow, 45);
7510
7511
                            if ($value == $obj->rowid) {
7512
                                foreach ($fields_label as $field_toshow) {
7513
                                    $translabel = $langs->trans($obj->$field_toshow);
7514
                                    if ($translabel != $obj->$field_toshow) {
7515
                                        $labeltoshow = dol_trunc($translabel) . ' ';
7516
                                    } else {
7517
                                        $labeltoshow = dol_trunc($obj->$field_toshow) . ' ';
7518
                                    }
7519
                                }
7520
                                $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
7521
                            } else {
7522
                                if (!$notrans) {
7523
                                    $translabel = $langs->trans($obj->{$InfoFieldList[1]});
7524
                                    if ($translabel != $obj->{$InfoFieldList[1]}) {
7525
                                        $labeltoshow = dol_trunc($translabel, 18);
7526
                                    } else {
7527
                                        $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]});
7528
                                    }
7529
                                }
7530
                                if (empty($labeltoshow)) {
7531
                                    $labeltoshow = '(not defined)';
7532
                                }
7533
                                if ($value == $obj->rowid) {
7534
                                    $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
7535
                                }
7536
7537
                                if (!empty($InfoFieldList[3]) && $parentField) {
7538
                                    $parent = $parentName . ':' . $obj->{$parentField};
7539
                                    $isDependList = 1;
7540
                                }
7541
7542
                                $out .= '<option value="' . $obj->rowid . '"';
7543
                                $out .= ($value == $obj->rowid ? ' selected' : '');
7544
                                $out .= (!empty($parent) ? ' parent="' . $parent . '"' : '');
7545
                                $out .= '>' . $labeltoshow . '</option>';
7546
                            }
7547
7548
                            $i++;
7549
                        }
7550
                        $this->db->free($resql);
7551
                    } else {
7552
                        print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
7553
                    }
7554
                } else {
7555
                    require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
7556
                    $data = $form->select_all_categories(Categorie::$MAP_ID_TO_CODE[$InfoFieldList[5]], '', 'parent', 64, $InfoFieldList[6], 1, 1);
7557
                    $out .= '<option value="0">&nbsp;</option>';
7558
                    foreach ($data as $data_key => $data_value) {
7559
                        $out .= '<option value="' . $data_key . '"';
7560
                        $out .= ($value == $data_key ? ' selected' : '');
7561
                        $out .= '>' . $data_value . '</option>';
7562
                    }
7563
                }
7564
            }
7565
            $out .= '</select>';
7566
        } elseif ($type == 'checkbox') {
7567
            $value_arr = explode(',', $value);
7568
            $out = $form->multiselectarray($keyprefix . $key . $keysuffix, (empty($param['options']) ? null : $param['options']), $value_arr, '', 0, $morecss, 0, '100%');
7569
        } elseif ($type == 'radio') {
7570
            $out = '';  // @phan-suppress-current-line PhanPluginRedundantAssignment
7571
            foreach ($param['options'] as $keyopt => $valopt) {
7572
                $out .= '<input class="flat ' . $morecss . '" type="radio" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" ' . ($moreparam ? $moreparam : '');
7573
                $out .= ' value="' . $keyopt . '"';
7574
                $out .= ' id="' . $keyprefix . $key . $keysuffix . '_' . $keyopt . '"';
7575
                $out .= ($value == $keyopt ? 'checked' : '');
7576
                $out .= '/><label for="' . $keyprefix . $key . $keysuffix . '_' . $keyopt . '">' . $valopt . '</label><br>';
7577
            }
7578
        } elseif ($type == 'chkbxlst') {
7579
            if (is_array($value)) {
7580
                $value_arr = $value;
7581
            } else {
7582
                $value_arr = explode(',', $value);
7583
            }
7584
7585
            if (is_array($param['options'])) {
7586
                $param_list = array_keys($param['options']);
7587
                $InfoFieldList = explode(":", $param_list[0]);
7588
                $parentName = '';
7589
                $parentField = '';
7590
                // 0 : tableName
7591
                // 1 : label field name
7592
                // 2 : key fields name (if differ of rowid)
7593
                // 3 : key field parent (for dependent lists)
7594
                // 4 : where clause filter on column or table extrafield, syntax field='value' or extra.field=value
7595
                // 5 : id category type
7596
                // 6 : ids categories list separated by comma for category root
7597
                $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2] . ' as rowid');
7598
7599
                if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
7600
                    list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
7601
                    $keyList .= ', ' . $parentField;
7602
                }
7603
                if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
7604
                    if (strpos($InfoFieldList[4], 'extra.') !== false) {
7605
                        $keyList = 'main.' . $InfoFieldList[2] . ' as rowid';
7606
                    } else {
7607
                        $keyList = $InfoFieldList[2] . ' as rowid';
7608
                    }
7609
                }
7610
7611
                $filter_categorie = false;
7612
                if (count($InfoFieldList) > 5) {
7613
                    if ($InfoFieldList[0] == 'categorie') {
7614
                        $filter_categorie = true;
7615
                    }
7616
                }
7617
7618
                if ($filter_categorie === false) {
7619
                    $fields_label = explode('|', $InfoFieldList[1]);
7620
                    if (is_array($fields_label)) {
7621
                        $keyList .= ', ';
7622
                        $keyList .= implode(', ', $fields_label);
7623
                    }
7624
7625
                    $sqlwhere = '';
7626
                    $sql = "SELECT " . $keyList;
7627
                    $sql .= ' FROM ' . $this->db->prefix() . $InfoFieldList[0];
7628
                    if (!empty($InfoFieldList[4])) {
7629
                        // can use SELECT request
7630
                        if (strpos($InfoFieldList[4], '$SEL$') !== false) {
7631
                            $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
7632
                        }
7633
7634
                        // current object id can be use into filter
7635
                        if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
7636
                            $InfoFieldList[4] = str_replace('$ID$', $objectid, $InfoFieldList[4]);
7637
                        } else {
7638
                            $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
7639
                        }
7640
7641
                        // We have to join on extrafield table
7642
                        if (strpos($InfoFieldList[4], 'extra') !== false) {
7643
                            $sql .= ' as main, ' . $this->db->prefix() . $InfoFieldList[0] . '_extrafields as extra';
7644
                            $sqlwhere .= " WHERE extra.fk_object=main." . $InfoFieldList[2] . " AND " . $InfoFieldList[4];
7645
                        } else {
7646
                            $sqlwhere .= " WHERE " . $InfoFieldList[4];
7647
                        }
7648
                    } else {
7649
                        $sqlwhere .= ' WHERE 1=1';
7650
                    }
7651
                    // Some tables may have field, some other not. For the moment we disable it.
7652
                    if (in_array($InfoFieldList[0], array('tablewithentity'))) {
7653
                        $sqlwhere .= " AND entity = " . ((int) $conf->entity);
7654
                    }
7655
                    // $sql.=preg_replace('/^ AND /','',$sqlwhere);
7656
                    // print $sql;
7657
7658
                    $sql .= $sqlwhere;
7659
                    dol_syslog(get_class($this) . '::showInputField type=chkbxlst', LOG_DEBUG);
7660
                    $resql = $this->db->query($sql);
7661
                    if ($resql) {
7662
                        $num = $this->db->num_rows($resql);
7663
                        $i = 0;
7664
7665
                        $data = array();
7666
7667
                        while ($i < $num) {
7668
                            $labeltoshow = '';
7669
                            $obj = $this->db->fetch_object($resql);
7670
7671
                            $notrans = false;
7672
                            // Several field into label (eq table:code|libelle:rowid)
7673
                            $fields_label = explode('|', $InfoFieldList[1]);
7674
                            if (count($fields_label) > 1) {
7675
                                $notrans = true;
7676
                                foreach ($fields_label as $field_toshow) {
7677
                                    $labeltoshow .= $obj->$field_toshow . ' ';
7678
                                }
7679
                            } else {
7680
                                $labeltoshow = $obj->{$InfoFieldList[1]};
7681
                            }
7682
                            $labeltoshow = dol_trunc($labeltoshow, 45);
7683
7684
                            if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
7685
                                foreach ($fields_label as $field_toshow) {
7686
                                    $translabel = $langs->trans($obj->$field_toshow);
7687
                                    if ($translabel != $obj->$field_toshow) {
7688
                                        $labeltoshow = dol_trunc($translabel, 18) . ' ';
7689
                                    } else {
7690
                                        $labeltoshow = dol_trunc($obj->$field_toshow, 18) . ' ';
7691
                                    }
7692
                                }
7693
7694
                                $data[$obj->rowid] = $labeltoshow;
7695
                            } else {
7696
                                if (!$notrans) {
7697
                                    $translabel = $langs->trans($obj->{$InfoFieldList[1]});
7698
                                    if ($translabel != $obj->{$InfoFieldList[1]}) {
7699
                                        $labeltoshow = dol_trunc($translabel, 18);
7700
                                    } else {
7701
                                        $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]}, 18);
7702
                                    }
7703
                                }
7704
                                if (empty($labeltoshow)) {
7705
                                    $labeltoshow = '(not defined)';
7706
                                }
7707
7708
                                if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
7709
                                    $data[$obj->rowid] = $labeltoshow;
7710
                                }
7711
7712
                                if (!empty($InfoFieldList[3]) && $parentField) {
7713
                                    $parent = $parentName . ':' . $obj->{$parentField};
7714
                                    $isDependList = 1;
7715
                                }
7716
7717
                                $data[$obj->rowid] = $labeltoshow;
7718
                            }
7719
7720
                            $i++;
7721
                        }
7722
                        $this->db->free($resql);
7723
7724
                        $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, '', 0, $morecss, 0, '100%');
7725
                    } else {
7726
                        print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
7727
                    }
7728
                } else {
7729
                    require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
7730
                    $data = $form->select_all_categories(Categorie::$MAP_ID_TO_CODE[$InfoFieldList[5]], '', 'parent', 64, $InfoFieldList[6], 1, 1);
7731
                    $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, '', 0, $morecss, 0, '100%');
7732
                }
7733
            }
7734
        } elseif ($type == 'link') {
7735
            // $param_list='ObjectName:classPath[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
7736
            // Filter can contains some ':' inside.
7737
            $param_list = array_keys($param['options']);
7738
            $param_list_array = explode(':', $param_list[0], 4);
7739
7740
            $showempty = (($required && $default != '') ? 0 : 1);
7741
7742
            if (!preg_match('/search_/', $keyprefix)) {
7743
                if (!empty($param_list_array[2])) {     // If the entry into $fields is set to add a create button
7744
                    if (!empty($this->fields[$key]['picto'])) {
7745
                        $morecss .= ' widthcentpercentminusxx';
7746
                    } else {
7747
                        $morecss .= ' widthcentpercentminusx';
7748
                    }
7749
                } else {
7750
                    if (!empty($this->fields[$key]['picto'])) {
7751
                        $morecss .= ' widthcentpercentminusx';
7752
                    }
7753
                }
7754
            }
7755
            $objectfield = $this->element . ($this->module ? '@' . $this->module : '') . ':' . $key . $keysuffix;
7756
            $out = $form->selectForForms($param_list_array[0], $keyprefix . $key . $keysuffix, $value, $showempty, '', '', $morecss, $moreparam, 0, (empty($val['disabled']) ? 0 : 1), '', $objectfield);
7757
7758
            if (!empty($param_list_array[2])) {     // If the entry into $fields is set, we must add a create button
7759
                if (
7760
                    (!GETPOSTISSET('backtopage') || strpos(GETPOST('backtopage'), $_SERVER['PHP_SELF']) === 0)  // // To avoid to open several times the 'Plus' button (we accept only one level)
7761
                    && empty($val['disabled']) && empty($nonewbutton)
7762
                ) {    // and to avoid to show the button if the field is protected by a "disabled".
7763
                    list($class, $classfile) = explode(':', $param_list[0]);
7764
                    if (file_exists(dol_buildpath(dirname(dirname($classfile)) . '/card.php'))) {
7765
                        $url_path = dol_buildpath(dirname(dirname($classfile)) . '/card.php', 1);
7766
                    } else {
7767
                        $url_path = dol_buildpath(dirname(dirname($classfile)) . '/' . strtolower($class) . '_card.php', 1);
7768
                    }
7769
                    $paramforthenewlink = '';
7770
                    $paramforthenewlink .= (GETPOSTISSET('action') ? '&action=' . GETPOST('action', 'aZ09') : '');
7771
                    $paramforthenewlink .= (GETPOSTISSET('id') ? '&id=' . GETPOSTINT('id') : '');
7772
                    $paramforthenewlink .= (GETPOSTISSET('origin') ? '&origin=' . GETPOST('origin', 'aZ09') : '');
7773
                    $paramforthenewlink .= (GETPOSTISSET('originid') ? '&originid=' . GETPOSTINT('originid') : '');
7774
                    $paramforthenewlink .= '&fk_' . strtolower($class) . '=--IDFORBACKTOPAGE--';
7775
                    // TODO Add JavaScript code to add input fields already filled into $paramforthenewlink so we won't loose them when going back to main page
7776
                    $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>';
7777
                }
7778
            }
7779
        } elseif ($type == 'password') {
7780
            // If prefix is 'search_', field is used as a filter, we use a common text field.
7781
            if ($keyprefix . $key . $keysuffix == 'pass_crypted') {
7782
                $out = '<input type="' . ($keyprefix == 'search_' ? 'text' : 'password') . '" class="flat ' . $morecss . '" name="pass" id="pass" value="" ' . ($moreparam ? $moreparam : '') . '>';
7783
                $out .= '<input type="hidden" name="pass_crypted" id="pass_crypted" value="' . $value . '" ' . ($moreparam ? $moreparam : '') . '>';
7784
            } else {
7785
                $out = '<input type="' . ($keyprefix == 'search_' ? 'text' : 'password') . '" class="flat ' . $morecss . '" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" value="' . $value . '" ' . ($moreparam ? $moreparam : '') . '>';
7786
            }
7787
        } elseif ($type == 'array') {
7788
            $newval = $val;
7789
            $newval['type'] = 'varchar(256)';
7790
7791
            $out = '';  // @phan-suppress-current-line PhanPluginRedundantAssignment
7792
            if (!empty($value)) {
7793
                foreach ($value as $option) {
7794
                    $out .= '<span><a class="' . dol_escape_htmltag($keyprefix . $key . $keysuffix) . '_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
7795
                    $out .= $this->showInputField($newval, $keyprefix . $key . $keysuffix . '[]', $option, $moreparam, '', '', $morecss) . '<br></span>';
7796
                }
7797
            }
7798
            $out .= '<a id="' . dol_escape_htmltag($keyprefix . $key . $keysuffix) . '_add" href="javascript:;"><span class="fa fa-plus-circle valignmiddle"></span></a>';
7799
7800
            $newInput = '<span><a class="' . dol_escape_htmltag($keyprefix . $key . $keysuffix) . '_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
7801
            $newInput .= $this->showInputField($newval, $keyprefix . $key . $keysuffix . '[]', '', $moreparam, '', '', $morecss) . '<br></span>';
7802
7803
            if (!empty($conf->use_javascript_ajax)) {
7804
                $out .= '
7805
					<script nonce="' . getNonce() . '">
7806
					$(document).ready(function() {
7807
						$("a#' . dol_escape_js($keyprefix . $key . $keysuffix) . '_add").click(function() {
7808
							$("' . dol_escape_js($newInput) . '").insertBefore(this);
7809
						});
7810
7811
						$(document).on("click", "a.' . dol_escape_js($keyprefix . $key . $keysuffix) . '_del", function() {
7812
							$(this).parent().remove();
7813
						});
7814
					});
7815
					</script>';
7816
            }
7817
        }
7818
        if (!empty($hidden)) {
7819
            $out = '<input type="hidden" value="' . $value . '" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '"/>';
7820
        }
7821
7822
        if ($isDependList == 1) {
7823
            $out .= $this->getJSListDependancies('_common');
7824
        }
7825
        /* Add comments
7826
         if ($type == 'date') $out.=' (YYYY-MM-DD)';
7827
         elseif ($type == 'datetime') $out.=' (YYYY-MM-DD HH:MM:SS)';
7828
         */
7829
7830
        // Display error message for field
7831
        if (!empty($fieldValidationErrorMsg) && function_exists('getFieldErrorIcon')) {
7832
            $out .= ' ' . getFieldErrorIcon($fieldValidationErrorMsg);
7833
        }
7834
7835
        return $out;
7836
    }
7837
7838
    /**
7839
     * Return HTML string to show a field into a page
7840
     * Code very similar with showOutputField of extra fields
7841
     *
7842
     * @param  array    $val                Array of properties of field to show
7843
     * @param  string   $key                Key of attribute
7844
     * @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)
7845
     * @param  string   $moreparam          To add more parameters on html tag
7846
     * @param  string   $keysuffix          Prefix string to add into name and id of field (can be used to avoid duplicate names)
7847
     * @param  string   $keyprefix          Suffix string to add into name and id of field (can be used to avoid duplicate names)
7848
     * @param  mixed    $morecss            Value for CSS to use (Old usage: May also be a numeric to define a size).
7849
     * @return string
7850
     */
7851
    public function showOutputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = '')
7852
    {
7853
        global $conf, $langs, $form;
7854
7855
        if (!is_object($form)) {
7856
            require_once DOL_DOCUMENT_ROOT . '/core/class/html.form.class.php';
7857
            $form = new Form($this->db);
7858
        }
7859
7860
        //$label = empty($val['label']) ? '' : $val['label'];
7861
        $type  = empty($val['type']) ? '' : $val['type'];
7862
        $size  = empty($val['css']) ? '' : $val['css'];
7863
        $reg = array();
7864
7865
        // Convert var to be able to share same code than showOutputField of extrafields
7866
        if (preg_match('/varchar\((\d+)\)/', $type, $reg)) {
7867
            $type = 'varchar'; // convert varchar(xx) int varchar
7868
            $size = $reg[1];
7869
        } elseif (preg_match('/varchar/', $type)) {
7870
            $type = 'varchar'; // convert varchar(xx) int varchar
7871
        }
7872
        if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
7873
            $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
7874
        }
7875
        if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
7876
            $type = 'link';
7877
        }
7878
7879
        $default = empty($val['default']) ? '' : $val['default'];
7880
        $computed = empty($val['computed']) ? '' : $val['computed'];
7881
        $unique = empty($val['unique']) ? '' : $val['unique'];
7882
        $required = empty($val['required']) ? '' : $val['required'];
7883
        $param = array();
7884
        $param['options'] = array();
7885
7886
        if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
7887
            $param['options'] = $val['arrayofkeyval'];
7888
        }
7889
        if (preg_match('/^integer:([^:]*):([^:]*)/i', $val['type'], $reg)) {    // ex: integer:User:user/class/user.class.php
7890
            $type = 'link';
7891
            $stringforoptions = $reg[1] . ':' . $reg[2];
7892
            // Special case: Force addition of getnomurlparam1 to -1 for users
7893
            if ($reg[1] == 'User') {
7894
                $stringforoptions .= ':#getnomurlparam1=-1';
7895
            }
7896
            $param['options'] = array($stringforoptions => $stringforoptions);
7897
        } elseif (preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7898
            $param['options'] = array($reg[1] . ':' . $reg[2] . ':' . $reg[3] . ':' . $reg[4] => 'N');
7899
            $type = 'sellist';
7900
        } elseif (preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
7901
            $param['options'] = array($reg[1] . ':' . $reg[2] . ':' . $reg[3] => 'N');
7902
            $type = 'sellist';
7903
        } elseif (preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
7904
            $param['options'] = array($reg[1] . ':' . $reg[2] => 'N');
7905
            $type = 'sellist';
7906
        } elseif (preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
7907
            $param['options'] = array($reg[1] => 'N');
7908
            $type = 'chkbxlst';
7909
        }
7910
7911
        $langfile = empty($val['langfile']) ? '' : $val['langfile'];
7912
        $list = (empty($val['list']) ? '' : $val['list']);
7913
        $help = (empty($val['help']) ? '' : $val['help']);
7914
        $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)
7915
7916
        if ($hidden) {
7917
            return '';
7918
        }
7919
7920
        // If field is a computed field, value must become result of compute
7921
        if ($computed) {
7922
            // Make the eval of compute string
7923
            //var_dump($computed);
7924
            $value = dol_eval($computed, 1, 0, '2');
7925
        }
7926
7927
        if (empty($morecss)) {
7928
            if ($type == 'date') {
7929
                $morecss = 'minwidth100imp';
7930
            } elseif ($type == 'datetime' || $type == 'timestamp') {
7931
                $morecss = 'minwidth200imp';
7932
            } elseif (in_array($type, array('int', 'double', 'price'))) {
7933
                $morecss = 'maxwidth75';
7934
            } elseif ($type == 'url') {
7935
                $morecss = 'minwidth400';
7936
            } elseif ($type == 'boolean') {
7937
                $morecss = '';
7938
            } else {
7939
                if (is_numeric($size) && round($size) < 12) {
7940
                    $morecss = 'minwidth100';
7941
                } elseif (is_numeric($size) && round($size) <= 48) {
7942
                    $morecss = 'minwidth200';
7943
                } else {
7944
                    $morecss = 'minwidth400';
7945
                }
7946
            }
7947
        }
7948
7949
        // Format output value differently according to properties of field
7950
        if (in_array($key, array('rowid', 'ref')) && method_exists($this, 'getNomUrl')) {
7951
            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.
7952
                $value = $this->getNomUrl(1, '', 0, '', 1);
7953
            }
7954
        } elseif ($key == 'status' && method_exists($this, 'getLibStatut')) {
7955
            $value = $this->getLibStatut(3);
7956
        } elseif ($type == 'date') {
7957
            if (!empty($value)) {
7958
                $value = dol_print_date($value, 'day'); // We suppose dates without time are always gmt (storage of course + output)
7959
            } else {
7960
                $value = '';
7961
            }
7962
        } elseif ($type == 'datetime' || $type == 'timestamp') {
7963
            if (!empty($value)) {
7964
                $value = dol_print_date($value, 'dayhour', 'tzuserrel');
7965
            } else {
7966
                $value = '';
7967
            }
7968
        } elseif ($type == 'duration') {
7969
            include_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php';
7970
            if (!is_null($value) && $value !== '') {
7971
                $value = convertSecondToTime($value, 'allhourmin');
7972
            }
7973
        } elseif ($type == 'double' || $type == 'real') {
7974
            if (!is_null($value) && $value !== '') {
7975
                $value = price($value);
7976
            }
7977
        } elseif ($type == 'boolean') {
7978
            $checked = '';
7979
            if (!empty($value)) {
7980
                $checked = ' checked ';
7981
            }
7982
            if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2) {
7983
                $value = '<input type="checkbox" ' . $checked . ' ' . ($moreparam ? $moreparam : '') . ' readonly disabled>';
7984
            } else {
7985
                $value = yn($value ? 1 : 0);
7986
            }
7987
        } elseif ($type == 'mail' || $type == 'email') {
7988
            $value = dol_print_email($value, 0, 0, 0, 64, 1, 1);
7989
        } elseif ($type == 'url') {
7990
            $value = dol_print_url($value, '_blank', 32, 1);
7991
        } elseif ($type == 'phone') {
7992
            $value = dol_print_phone($value, '', 0, 0, '', '&nbsp;', 'phone');
7993
        } elseif ($type == 'ip') {
7994
            $value = dol_print_ip($value, 0);
7995
        } elseif ($type == 'price') {
7996
            if (!is_null($value) && $value !== '') {
7997
                $value = price($value, 0, $langs, 0, 0, -1, $conf->currency);
7998
            }
7999
        } elseif ($type == 'select') {
8000
            $value = isset($param['options'][$value]) ? $param['options'][$value] : '';
8001
            if (strpos($value, "|") !== false) {
8002
                $value = $langs->trans(explode('|', $value)[0]);
8003
            }
8004
        } elseif ($type == 'sellist') {
8005
            $param_list = array_keys($param['options']);
8006
            $InfoFieldList = explode(":", $param_list[0]);
8007
8008
            $selectkey = "rowid";
8009
            $keyList = 'rowid';
8010
8011
            if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
8012
                $selectkey = $InfoFieldList[2];
8013
                $keyList = $InfoFieldList[2] . ' as rowid';
8014
            }
8015
8016
            $fields_label = explode('|', $InfoFieldList[1]);
8017
            if (is_array($fields_label)) {
8018
                $keyList .= ', ';
8019
                $keyList .= implode(', ', $fields_label);
8020
            }
8021
8022
            $filter_categorie = false;
8023
            if (count($InfoFieldList) > 5) {
8024
                if ($InfoFieldList[0] == 'categorie') {
8025
                    $filter_categorie = true;
8026
                }
8027
            }
8028
8029
            $sql = "SELECT " . $keyList;
8030
            $sql .= ' FROM ' . $this->db->prefix() . $InfoFieldList[0];
8031
            if (strpos($InfoFieldList[4], 'extra') !== false) {
8032
                $sql .= ' as main';
8033
            }
8034
            if ($selectkey == 'rowid' && empty($value)) {
8035
                $sql .= " WHERE " . $selectkey . " = 0";
8036
            } elseif ($selectkey == 'rowid') {
8037
                $sql .= " WHERE " . $selectkey . " = " . ((int) $value);
8038
            } else {
8039
                $sql .= " WHERE " . $selectkey . " = '" . $this->db->escape($value) . "'";
8040
            }
8041
8042
            //$sql.= ' AND entity = '.$conf->entity;
8043
8044
            dol_syslog(get_class($this) . ':showOutputField:$type=sellist', LOG_DEBUG);
8045
            $resql = $this->db->query($sql);
8046
            if ($resql) {
8047
                if ($filter_categorie === false) {
8048
                    $value = ''; // value was used, so now we reste it to use it to build final output
8049
                    $numrows = $this->db->num_rows($resql);
8050
                    if ($numrows) {
8051
                        $obj = $this->db->fetch_object($resql);
8052
8053
                        // Several field into label (eq table:code|libelle:rowid)
8054
                        $fields_label = explode('|', $InfoFieldList[1]);
8055
8056
                        if (is_array($fields_label) && count($fields_label) > 1) {
8057
                            foreach ($fields_label as $field_toshow) {
8058
                                $translabel = '';
8059
                                if (!empty($obj->$field_toshow)) {
8060
                                    $translabel = $langs->trans($obj->$field_toshow);
8061
                                }
8062
                                if ($translabel != $field_toshow) {
8063
                                    $value .= dol_trunc($translabel, 18) . ' ';
8064
                                } else {
8065
                                    $value .= $obj->$field_toshow . ' ';
8066
                                }
8067
                            }
8068
                        } else {
8069
                            $translabel = '';
8070
                            if (!empty($obj->{$InfoFieldList[1]})) {
8071
                                $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8072
                            }
8073
                            if ($translabel != $obj->{$InfoFieldList[1]}) {
8074
                                $value = dol_trunc($translabel, 18);
8075
                            } else {
8076
                                $value = $obj->{$InfoFieldList[1]};
8077
                            }
8078
                        }
8079
                    }
8080
                } else {
8081
                    require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
8082
8083
                    $toprint = array();
8084
                    $obj = $this->db->fetch_object($resql);
8085
                    $c = new Categorie($this->db);
8086
                    $c->fetch($obj->rowid);
8087
                    $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
8088
                    foreach ($ways as $way) {
8089
                        $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
8090
                    }
8091
                    $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
8092
                }
8093
            } else {
8094
                dol_syslog(get_class($this) . '::showOutputField error ' . $this->db->lasterror(), LOG_WARNING);
8095
            }
8096
        } elseif ($type == 'radio') {
8097
            $value = $param['options'][$value];
8098
        } elseif ($type == 'checkbox') {
8099
            $value_arr = explode(',', $value);
8100
            $value = '';
8101
            if (is_array($value_arr) && count($value_arr) > 0) {
8102
                $toprint = array();
8103
                foreach ($value_arr as $keyval => $valueval) {
8104
                    if (!empty($valueval)) {
8105
                        $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $param['options'][$valueval] . '</li>';
8106
                    }
8107
                }
8108
                if (!empty($toprint)) {
8109
                    $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
8110
                }
8111
            }
8112
        } elseif ($type == 'chkbxlst') {
8113
            $value_arr = (isset($value) ? explode(',', $value) : array());
8114
8115
            $param_list = array_keys($param['options']);
8116
            $InfoFieldList = explode(":", $param_list[0]);
8117
8118
            $selectkey = "rowid";
8119
            $keyList = 'rowid';
8120
8121
            if (count($InfoFieldList) >= 3) {
8122
                $selectkey = $InfoFieldList[2];
8123
                $keyList = $InfoFieldList[2] . ' as rowid';
8124
            }
8125
8126
            $fields_label = explode('|', $InfoFieldList[1]);
8127
            if (is_array($fields_label)) {
8128
                $keyList .= ', ';
8129
                $keyList .= implode(', ', $fields_label);
8130
            }
8131
8132
            $filter_categorie = false;
8133
            if (count($InfoFieldList) > 5) {
8134
                if ($InfoFieldList[0] == 'categorie') {
8135
                    $filter_categorie = true;
8136
                }
8137
            }
8138
8139
            $sql = "SELECT " . $keyList;
8140
            $sql .= ' FROM ' . $this->db->prefix() . $InfoFieldList[0];
8141
            if (strpos($InfoFieldList[4], 'extra') !== false) {
8142
                $sql .= ' as main';
8143
            }
8144
            // $sql.= " WHERE ".$selectkey."='".$this->db->escape($value)."'";
8145
            // $sql.= ' AND entity = '.$conf->entity;
8146
8147
            dol_syslog(get_class($this) . ':showOutputField:$type=chkbxlst', LOG_DEBUG);
8148
            $resql = $this->db->query($sql);
8149
            if ($resql) {
8150
                if ($filter_categorie === false) {
8151
                    $value = ''; // value was used, so now we reste it to use it to build final output
8152
                    $toprint = array();
8153
                    while ($obj = $this->db->fetch_object($resql)) {
8154
                        // Several field into label (eq table:code|libelle:rowid)
8155
                        $fields_label = explode('|', $InfoFieldList[1]);
8156
                        if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8157
                            if (is_array($fields_label) && count($fields_label) > 1) {
8158
                                foreach ($fields_label as $field_toshow) {
8159
                                    $translabel = '';
8160
                                    if (!empty($obj->$field_toshow)) {
8161
                                        $translabel = $langs->trans($obj->$field_toshow);
8162
                                    }
8163
                                    if ($translabel != $field_toshow) {
8164
                                        $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
8165
                                    } else {
8166
                                        $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->$field_toshow . '</li>';
8167
                                    }
8168
                                }
8169
                            } else {
8170
                                $translabel = '';
8171
                                if (!empty($obj->{$InfoFieldList[1]})) {
8172
                                    $translabel = $langs->trans($obj->{$InfoFieldList[1]});
8173
                                }
8174
                                if ($translabel != $obj->{$InfoFieldList[1]}) {
8175
                                    $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
8176
                                } else {
8177
                                    $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->{$InfoFieldList[1]} . '</li>';
8178
                                }
8179
                            }
8180
                        }
8181
                    }
8182
                } else {
8183
                    require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
8184
8185
                    $toprint = array();
8186
                    while ($obj = $this->db->fetch_object($resql)) {
8187
                        if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
8188
                            $c = new Categorie($this->db);
8189
                            $c->fetch($obj->rowid);
8190
                            $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
8191
                            foreach ($ways as $way) {
8192
                                $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
8193
                            }
8194
                        }
8195
                    }
8196
                }
8197
                $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
8198
            } else {
8199
                dol_syslog(get_class($this) . '::showOutputField error ' . $this->db->lasterror(), LOG_WARNING);
8200
            }
8201
        } elseif ($type == 'link') {
8202
            $out = '';
8203
8204
            // only if something to display (perf)
8205
            if ($value) {
8206
                $param_list = array_keys($param['options']);
8207
                // Example: $param_list='ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
8208
                // Example: $param_list='ObjectClass:PathToClass:#getnomurlparam1=-1#getnomurlparam2=customer'
8209
8210
                $InfoFieldList = explode(":", $param_list[0]);
8211
8212
                $classname = $InfoFieldList[0];
8213
                $classpath = $InfoFieldList[1];
8214
8215
                // Set $getnomurlparam1 et getnomurlparam2
8216
                $getnomurlparam = 3;
8217
                $getnomurlparam2 = '';
8218
                $regtmp = array();
8219
                if (preg_match('/#getnomurlparam1=([^#]*)/', $param_list[0], $regtmp)) {
8220
                    $getnomurlparam = $regtmp[1];
8221
                }
8222
                if (preg_match('/#getnomurlparam2=([^#]*)/', $param_list[0], $regtmp)) {
8223
                    $getnomurlparam2 = $regtmp[1];
8224
                }
8225
8226
                if (!empty($classpath)) {
8227
                    dol_include_once($InfoFieldList[1]);
8228
                    if ($classname && class_exists($classname)) {
8229
                        $object = new $classname($this->db);
8230
                        if ($object->element === 'product') {   // Special case for product because default valut of fetch are wrong
8231
                            $result = $object->fetch($value, '', '', '', 0, 1, 1);
8232
                        } else {
8233
                            $result = $object->fetch($value);
8234
                        }
8235
                        if ($result > 0) {
8236
                            if ($object->element === 'product') {
8237
                                $get_name_url_param_arr = array($getnomurlparam, $getnomurlparam2, 0, -1, 0, '', 0);
8238
                                if (isset($val['get_name_url_params'])) {
8239
                                    $get_name_url_params = explode(':', $val['get_name_url_params']);
8240
                                    if (!empty($get_name_url_params)) {
8241
                                        $param_num_max = count($get_name_url_param_arr) - 1;
8242
                                        foreach ($get_name_url_params as $param_num => $param_value) {
8243
                                            if ($param_num > $param_num_max) {
8244
                                                break;
8245
                                            }
8246
                                            $get_name_url_param_arr[$param_num] = $param_value;
8247
                                        }
8248
                                    }
8249
                                }
8250
8251
                                /**
8252
                                 * @var Product $object
8253
                                 */
8254
                                $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]);
8255
                            } else {
8256
                                $value = $object->getNomUrl($getnomurlparam, $getnomurlparam2);
8257
                            }
8258
                        } else {
8259
                            $value = '';
8260
                        }
8261
                    }
8262
                } else {
8263
                    dol_syslog('Error bad setup of extrafield', LOG_WARNING);
8264
                    return 'Error bad setup of extrafield';
8265
                }
8266
            } else {
8267
                $value = '';
8268
            }
8269
        } elseif ($type == 'password') {
8270
            $value = '<span class="opacitymedium">' . $langs->trans("Encrypted") . '</span>';
8271
            //$value = preg_replace('/./i', '*', $value);
8272
        } elseif ($type == 'array') {
8273
            $value = implode('<br>', $value);
8274
        } else {    // text|html|varchar
8275
            $value = dol_htmlentitiesbr($value);
8276
        }
8277
8278
        //print $type.'-'.$size.'-'.$value;
8279
        $out = $value;
8280
8281
        return $out;
8282
    }
8283
8284
    /**
8285
     * clear validation message result for a field
8286
     *
8287
     * @param string $fieldKey Key of attribute to clear
8288
     * @return void
8289
     */
8290
    public function clearFieldError($fieldKey)
8291
    {
8292
        $this->error = '';
8293
        unset($this->validateFieldsErrors[$fieldKey]);
8294
    }
8295
8296
    /**
8297
     * set validation error message a field
8298
     *
8299
     * @param string $fieldKey Key of attribute
8300
     * @param string $msg the field error message
8301
     * @return void
8302
     */
8303
    public function setFieldError($fieldKey, $msg = '')
8304
    {
8305
        global $langs;
8306
        if (empty($msg)) {
8307
            $msg = $langs->trans("UnknownError");
8308
        }
8309
8310
        $this->error = $this->validateFieldsErrors[$fieldKey] = $msg;
8311
    }
8312
8313
    /**
8314
     * get field error message
8315
     *
8316
     * @param  string  $fieldKey            Key of attribute
8317
     * @return string                       Error message of validation ('' if no error)
8318
     */
8319
    public function getFieldError($fieldKey)
8320
    {
8321
        if (!empty($this->validateFieldsErrors[$fieldKey])) {
8322
            return $this->validateFieldsErrors[$fieldKey];
8323
        }
8324
        return '';
8325
    }
8326
8327
    /**
8328
     * Return validation test result for a field
8329
     *
8330
     * @param  array   $fields              Array of properties of field to show
8331
     * @param  string  $fieldKey            Key of attribute
8332
     * @param  string  $fieldValue          value of attribute
8333
     * @return bool return false if fail true on success, see $this->error for error message
8334
     */
8335
    public function validateField($fields, $fieldKey, $fieldValue)
8336
    {
8337
        global $langs;
8338
8339
        if (!class_exists('Validate')) {
8340
            require_once DOL_DOCUMENT_ROOT . '/core/class/validate.class.php';
8341
        }
8342
8343
        $this->clearFieldError($fieldKey);
8344
8345
        if (!isset($fields[$fieldKey])) {
8346
            $this->setFieldError($fieldKey, $langs->trans('FieldNotFoundInObject'));
8347
            return false;
8348
        }
8349
8350
        $val = $fields[$fieldKey];
8351
8352
        $param = array();
8353
        $param['options'] = array();
8354
        $type  = $val['type'];
8355
8356
        $required = false;
8357
        if (isset($val['notnull']) && $val['notnull'] === 1) {
8358
            // 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
8359
            $required = true;
8360
        }
8361
8362
        $maxSize = 0;
8363
        $minSize = 0;
8364
8365
        //
8366
        // PREPARE Elements
8367
        //
8368
        $reg = array();
8369
8370
        // Convert var to be able to share same code than showOutputField of extrafields
8371
        if (preg_match('/varchar\((\d+)\)/', $type, $reg)) {
8372
            $type = 'varchar'; // convert varchar(xx) int varchar
8373
            $maxSize = $reg[1];
8374
        } elseif (preg_match('/varchar/', $type)) {
8375
            $type = 'varchar'; // convert varchar(xx) int varchar
8376
        }
8377
8378
        if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
8379
            $type = 'select';
8380
        }
8381
8382
        if (!empty($val['type']) && preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
8383
            $type = 'link';
8384
        }
8385
8386
        if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
8387
            $param['options'] = $val['arrayofkeyval'];
8388
        }
8389
8390
        if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
8391
            $type = 'link';
8392
            $param['options'] = array($reg[1] . ':' . $reg[2] => $reg[1] . ':' . $reg[2]);
8393
        } elseif (preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
8394
            $param['options'] = array($reg[1] . ':' . $reg[2] . ':' . $reg[3] . ':' . $reg[4] => 'N');
8395
            $type = 'sellist';
8396
        } elseif (preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
8397
            $param['options'] = array($reg[1] . ':' . $reg[2] . ':' . $reg[3] => 'N');
8398
            $type = 'sellist';
8399
        } elseif (preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
8400
            $param['options'] = array($reg[1] . ':' . $reg[2] => 'N');
8401
            $type = 'sellist';
8402
        }
8403
8404
        //
8405
        // TEST Value
8406
        //
8407
8408
        // Use Validate class to allow external Modules to use data validation part instead of concentrate all test here (factoring) or just for reuse
8409
        $validate = new Validate($this->db, $langs);
8410
8411
8412
        // little trick : to perform tests with good performances sort tests by quick to low
8413
8414
        //
8415
        // COMMON TESTS
8416
        //
8417
8418
        // Required test and empty value
8419
        if ($required && !$validate->isNotEmptyString($fieldValue)) {
8420
            $this->setFieldError($fieldKey, $validate->error);
8421
            return false;
8422
        } elseif (!$required && !$validate->isNotEmptyString($fieldValue)) {
8423
            // if no value sent and the field is not mandatory, no need to perform tests
8424
            return true;
8425
        }
8426
8427
        // MAX Size test
8428
        if (!empty($maxSize) && !$validate->isMaxLength($fieldValue, $maxSize)) {
8429
            $this->setFieldError($fieldKey, $validate->error);
8430
            return false;
8431
        }
8432
8433
        // MIN Size test
8434
        if (!empty($minSize) && !$validate->isMinLength($fieldValue, $minSize)) {
8435
            $this->setFieldError($fieldKey, $validate->error);
8436
            return false;
8437
        }
8438
8439
        //
8440
        // TESTS for TYPE
8441
        //
8442
8443
        if (in_array($type, array('date', 'datetime', 'timestamp'))) {
8444
            if (!$validate->isTimestamp($fieldValue)) {
8445
                $this->setFieldError($fieldKey, $validate->error);
8446
                return false;
8447
            } else {
8448
                return true;
8449
            }
8450
        } elseif ($type == 'duration') {
8451
            if (!$validate->isDuration($fieldValue)) {
8452
                $this->setFieldError($fieldKey, $validate->error);
8453
                return false;
8454
            } else {
8455
                return true;
8456
            }
8457
        } elseif (in_array($type, array('double', 'real', 'price'))) {
8458
            // is numeric
8459
            if (!$validate->isNumeric($fieldValue)) {
8460
                $this->setFieldError($fieldKey, $validate->error);
8461
                return false;
8462
            } else {
8463
                return true;
8464
            }
8465
        } elseif ($type == 'boolean') {
8466
            if (!$validate->isBool($fieldValue)) {
8467
                $this->setFieldError($fieldKey, $validate->error);
8468
                return false;
8469
            } else {
8470
                return true;
8471
            }
8472
        } elseif ($type == 'mail') {
8473
            if (!$validate->isEmail($fieldValue)) {
8474
                $this->setFieldError($fieldKey, $validate->error);
8475
                return false;
8476
            }
8477
        } elseif ($type == 'url') {
8478
            if (!$validate->isUrl($fieldValue)) {
8479
                $this->setFieldError($fieldKey, $validate->error);
8480
                return false;
8481
            } else {
8482
                return true;
8483
            }
8484
        } elseif ($type == 'phone') {
8485
            if (!$validate->isPhone($fieldValue)) {
8486
                $this->setFieldError($fieldKey, $validate->error);
8487
                return false;
8488
            } else {
8489
                return true;
8490
            }
8491
        } elseif ($type == 'select' || $type == 'radio') {
8492
            if (!isset($param['options'][$fieldValue])) {
8493
                $this->error = $langs->trans('RequireValidValue');
8494
                return false;
8495
            } else {
8496
                return true;
8497
            }
8498
        } elseif ($type == 'sellist' || $type == 'chkbxlst') {
8499
            $param_list = array_keys($param['options']);
8500
            $InfoFieldList = explode(":", $param_list[0]);
8501
            $value_arr = explode(',', $fieldValue);
8502
            $value_arr = array_map(array($this->db, 'escape'), $value_arr);
8503
8504
            $selectkey = "rowid";
8505
            if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
8506
                $selectkey = $InfoFieldList[2];
8507
            }
8508
8509
            if (!$validate->isInDb($value_arr, $InfoFieldList[0], $selectkey)) {
8510
                $this->setFieldError($fieldKey, $validate->error);
8511
                return false;
8512
            } else {
8513
                return true;
8514
            }
8515
        } elseif ($type == 'link') {
8516
            $param_list = array_keys($param['options']); // $param_list='ObjectName:classPath'
8517
            $InfoFieldList = explode(":", $param_list[0]);
8518
            $classname = $InfoFieldList[0];
8519
            $classpath = $InfoFieldList[1];
8520
            if (!$validate->isFetchable($fieldValue, $classname, $classpath)) {
8521
                $this->setFieldError($fieldKey, $validate->error);
8522
                return false;
8523
            } else {
8524
                return true;
8525
            }
8526
        }
8527
8528
        // if no test failed all is ok
8529
        return true;
8530
    }
8531
8532
    /**
8533
     * Function to show lines of extrafields with output data.
8534
     * 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
8535
     *
8536
     * @param   Extrafields $extrafields    Extrafield Object
8537
     * @param   string      $mode           Show output ('view') or input ('create' or 'edit') for extrafield
8538
     * @param   array       $params         Optional parameters. Example: array('style'=>'class="oddeven"', 'colspan'=>$colspan)
8539
     * @param   string      $keysuffix      Suffix string to add after name and id of field (can be used to avoid duplicate names)
8540
     * @param   string      $keyprefix      Prefix string to add before name and id of field (can be used to avoid duplicate names)
8541
     * @param   string      $onetrtd        All fields in same tr td. Used by objectline_create.tpl.php for example.
8542
     * @param   string      $display_type   "card" for form display, "line" for document line display (extrafields on propal line, order line, etc...)
8543
     * @return  string                      String with html content to show
8544
     */
8545
    public function showOptionals($extrafields, $mode = 'view', $params = null, $keysuffix = '', $keyprefix = '', $onetrtd = '', $display_type = 'card')
8546
    {
8547
        global $db, $conf, $langs, $action, $form, $hookmanager;
8548
8549
        if (!is_object($form)) {
8550
            $form = new Form($db);
8551
        }
8552
        if (!is_object($extrafields)) {
8553
            dol_syslog('Bad parameter extrafields for showOptionals', LOG_ERR);
8554
            return 'Bad parameter extrafields for showOptionals';
8555
        }
8556
        if (!is_array($extrafields->attributes[$this->table_element])) {
8557
            dol_syslog("extrafields->attributes was not loaded with extrafields->fetch_name_optionals_label(table_element);", LOG_WARNING);
8558
        }
8559
8560
        $out = '';
8561
8562
        $parameters = array('mode' => $mode, 'params' => $params, 'keysuffix' => $keysuffix, 'keyprefix' => $keyprefix, 'display_type' => $display_type);
8563
        $reshook = $hookmanager->executeHooks('showOptionals', $parameters, $this, $action); // Note that $action and $object may have been modified by hook
8564
8565
        if (empty($reshook)) {
8566
            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) {
8567
                $out .= "\n";
8568
                $out .= '<!-- commonobject:showOptionals --> ';
8569
                $out .= "\n";
8570
8571
                $nbofextrafieldsshown = 0;
8572
                $e = 0; // var to manage the modulo (odd/even)
8573
8574
                $lastseparatorkeyfound = '';
8575
                $extrafields_collapse_num = '';
8576
                $extrafields_collapse_num_old = '';
8577
                $i = 0;
8578
8579
                foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $label) {
8580
                    $i++;
8581
8582
                    // Show only the key field in params
8583
                    if (is_array($params) && array_key_exists('onlykey', $params) && $key != $params['onlykey']) {
8584
                        continue;
8585
                    }
8586
8587
                    // Test on 'enabled' ('enabled' is different than 'list' = 'visibility')
8588
                    $enabled = 1;
8589
                    if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
8590
                        $enabled = (int) dol_eval($extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
8591
                    }
8592
                    if (empty($enabled)) {
8593
                        continue;
8594
                    }
8595
8596
                    $visibility = 1;
8597
                    if (isset($extrafields->attributes[$this->table_element]['list'][$key])) {
8598
                        $visibility = (int) dol_eval($extrafields->attributes[$this->table_element]['list'][$key], 1, 1, '2');
8599
                    }
8600
8601
                    $perms = 1;
8602
                    if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
8603
                        $perms = (int) dol_eval($extrafields->attributes[$this->table_element]['perms'][$key], 1, 1, '2');
8604
                    }
8605
8606
                    if (($mode == 'create') && !in_array(abs($visibility), array(1, 3))) {
8607
                        continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list
8608
                    } elseif (($mode == 'edit') && !in_array(abs($visibility), array(1, 3, 4))) {
8609
                        continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list and <> 4 = not visible at the creation
8610
                    } elseif ($mode == 'view' && empty($visibility)) {
8611
                        continue;
8612
                    }
8613
                    if (empty($perms)) {
8614
                        continue;
8615
                    }
8616
8617
                    // Load language if required
8618
                    if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
8619
                        $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
8620
                    }
8621
8622
                    $colspan = 0;
8623
                    if (is_array($params) && count($params) > 0 && $display_type == 'card') {
8624
                        if (array_key_exists('cols', $params)) {
8625
                            $colspan = $params['cols'];
8626
                        } elseif (array_key_exists('colspan', $params)) {   // For backward compatibility. Use cols instead now.
8627
                            $reg = array();
8628
                            if (preg_match('/colspan="(\d+)"/', $params['colspan'], $reg)) {
8629
                                $colspan = $reg[1];
8630
                            } else {
8631
                                $colspan = $params['colspan'];
8632
                            }
8633
                        }
8634
                    }
8635
                    $colspan = intval($colspan);
8636
8637
                    switch ($mode) {
8638
                        case "view":
8639
                            $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
8640
                            break;
8641
                        case "create":
8642
                        case "edit":
8643
                            // We get the value of property found with GETPOST so it takes into account:
8644
                            // default values overwrite, restore back to list link, ... (but not 'default value in database' of field)
8645
                            $check = 'alphanohtml';
8646
                            if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text'))) {
8647
                                $check = 'restricthtml';
8648
                            }
8649
                            $getposttemp = GETPOST($keyprefix . 'options_' . $key . $keysuffix, $check, 3); // GETPOST can get value from GET, POST or setup of default values overwrite.
8650
                            // GETPOST("options_" . $key) can be 'abc' or array(0=>'abc')
8651
                            if (is_array($getposttemp) || $getposttemp != '' || GETPOSTISSET($keyprefix . 'options_' . $key . $keysuffix)) {
8652
                                if (is_array($getposttemp)) {
8653
                                    // $getposttemp is an array but following code expects a comma separated string
8654
                                    $value = implode(",", $getposttemp);
8655
                                } else {
8656
                                    $value = $getposttemp;
8657
                                }
8658
                            } else {
8659
                                $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.
8660
                            }
8661
                            //var_dump($keyprefix.' - '.$key.' - '.$keysuffix.' - '.$keyprefix.'options_'.$key.$keysuffix.' - '.$this->array_options["options_".$key.$keysuffix].' - '.$getposttemp.' - '.$value);
8662
                            break;
8663
                    }
8664
8665
                    $nbofextrafieldsshown++;
8666
8667
                    // Output value of the current field
8668
                    if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
8669
                        $extrafields_collapse_num = $key;
8670
                        /*
8671
                        $extrafield_param = $extrafields->attributes[$this->table_element]['param'][$key];
8672
                        if (!empty($extrafield_param) && is_array($extrafield_param)) {
8673
                            $extrafield_param_list = array_keys($extrafield_param['options']);
8674
8675
                            if (count($extrafield_param_list) > 0) {
8676
                                $extrafield_collapse_display_value = intval($extrafield_param_list[0]);
8677
8678
                                if ($extrafield_collapse_display_value == 1 || $extrafield_collapse_display_value == 2) {
8679
                                    //$extrafields_collapse_num = $extrafields->attributes[$this->table_element]['pos'][$key];
8680
                                    $extrafields_collapse_num = $key;
8681
                                }
8682
                            }
8683
                        }
8684
                        */
8685
8686
                        // if colspan=0 or 1, the second column is not extended, so the separator must be on 2 columns
8687
                        $out .= $extrafields->showSeparator($key, $this, ($colspan ? $colspan + 1 : 2), $display_type, $mode);
8688
8689
                        $lastseparatorkeyfound = $key;
8690
                    } else {
8691
                        $collapse_group = $extrafields_collapse_num . (!empty($this->id) ? '_' . $this->id : '');
8692
8693
                        $class = (!empty($extrafields->attributes[$this->table_element]['hidden'][$key]) ? 'hideobject ' : '');
8694
                        $csstyle = '';
8695
                        if (is_array($params) && count($params) > 0) {
8696
                            if (array_key_exists('class', $params)) {
8697
                                $class .= $params['class'] . ' ';
8698
                            }
8699
                            if (array_key_exists('style', $params)) {
8700
                                $csstyle = $params['style'];
8701
                            }
8702
                        }
8703
8704
                        // add html5 elements
8705
                        $domData  = ' data-element="extrafield"';
8706
                        $domData .= ' data-targetelement="' . $this->element . '"';
8707
                        $domData .= ' data-targetid="' . $this->id . '"';
8708
8709
                        $html_id = (empty($this->id) ? '' : 'extrarow-' . $this->element . '_' . $key . '_' . $this->id);
8710
                        if ($display_type == 'card') {
8711
                            if (getDolGlobalString('MAIN_EXTRAFIELDS_USE_TWO_COLUMS') && ($e % 2) == 0) {
8712
                                $colspan = 0;
8713
                            }
8714
8715
                            if ($action == 'selectlines') {
8716
                                $colspan++;
8717
                            }
8718
                        }
8719
8720
                        // Convert date into timestamp format (value in memory must be a timestamp)
8721
                        if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date'))) {
8722
                            $datenotinstring = null;
8723
                            if (array_key_exists('options_' . $key, $this->array_options)) {
8724
                                $datenotinstring = $this->array_options['options_' . $key];
8725
                                if (!is_numeric($this->array_options['options_' . $key])) {   // For backward compatibility
8726
                                    $datenotinstring = $this->db->jdate($datenotinstring);
8727
                                }
8728
                            }
8729
                            $datekey = $keyprefix . 'options_' . $key . $keysuffix;
8730
                            $value = (GETPOSTISSET($datekey)) ? dol_mktime(12, 0, 0, GETPOSTINT($datekey . 'month', 3), GETPOSTINT($datekey . 'day', 3), GETPOSTINT($datekey . 'year', 3)) : $datenotinstring;
8731
                        }
8732
                        if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('datetime'))) {
8733
                            $datenotinstring = null;
8734
                            if (array_key_exists('options_' . $key, $this->array_options)) {
8735
                                $datenotinstring = $this->array_options['options_' . $key];
8736
                                if (!is_numeric($this->array_options['options_' . $key])) {   // For backward compatibility
8737
                                    $datenotinstring = $this->db->jdate($datenotinstring);
8738
                                }
8739
                            }
8740
                            $timekey = $keyprefix . 'options_' . $key . $keysuffix;
8741
                            $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;
8742
                        }
8743
                        // Convert float submitted string into real php numeric (value in memory must be a php numeric)
8744
                        if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('price', 'double'))) {
8745
                            if (GETPOSTISSET($keyprefix . 'options_' . $key . $keysuffix) || $value) {
8746
                                $value = price2num($value);
8747
                            } elseif (isset($this->array_options['options_' . $key])) {
8748
                                $value = $this->array_options['options_' . $key];
8749
                            }
8750
                        }
8751
8752
                        // HTML, text, select, integer and varchar: take into account default value in database if in create mode
8753
                        if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text', 'varchar', 'select', 'radio', 'int', 'boolean'))) {
8754
                            if ($action == 'create' || $mode == 'create') {
8755
                                $value = (GETPOSTISSET($keyprefix . 'options_' . $key . $keysuffix) || $value) ? $value : $extrafields->attributes[$this->table_element]['default'][$key];
8756
                            }
8757
                        }
8758
8759
                        $labeltoshow = $langs->trans($label);
8760
                        $helptoshow = $langs->trans($extrafields->attributes[$this->table_element]['help'][$key]);
8761
8762
                        if ($display_type == 'card') {
8763
                            $out .= '<tr ' . ($html_id ? 'id="' . $html_id . '" ' : '') . $csstyle . ' class="field_options_' . $key . ' ' . $class . $this->element . '_extras_' . $key . ' trextrafields_collapse' . $collapse_group . '" ' . $domData . ' >';
8764
                            if (getDolGlobalString('MAIN_VIEW_LINE_NUMBER') && ($action == 'view' || $action == 'valid' || $action == 'editline' || $action == 'confirm_valid' || $action == 'confirm_cancel')) {
8765
                                $out .= '<td></td>';
8766
                            }
8767
                            $out .= '<td class="' . (empty($params['tdclass']) ? 'titlefieldcreate' : $params['tdclass']) . ' wordbreak';
8768
                        } elseif ($display_type == 'line') {
8769
                            $out .= '<div ' . ($html_id ? 'id="' . $html_id . '" ' : '') . $csstyle . ' class="fieldline_options_' . $key . ' ' . $class . $this->element . '_extras_' . $key . ' trextrafields_collapse' . $collapse_group . '" ' . $domData . ' >';
8770
                            $out .= '<div style="display: inline-block; padding-right:4px" class="wordbreak';
8771
                        }
8772
                        //$out .= "titlefield";
8773
                        //if (GETPOST('action', 'restricthtml') == 'create') $out.='create';
8774
                        // BUG #11554 : For public page, use red dot for required fields, instead of bold label
8775
                        $tpl_context = isset($params["tpl_context"]) ? $params["tpl_context"] : "none";
8776
                        if ($tpl_context != "public") { // Public page : red dot instead of fieldrequired characters
8777
                            if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
8778
                                $out .= ' fieldrequired';
8779
                            }
8780
                        }
8781
                        $out .= '">';
8782
                        if ($tpl_context == "public") { // Public page : red dot instead of fieldrequired characters
8783
                            if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
8784
                                $out .= $form->textwithpicto($labeltoshow, $helptoshow);
8785
                            } else {
8786
                                $out .= $labeltoshow;
8787
                            }
8788
                            if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
8789
                                $out .= '&nbsp;<span style="color: red">*</span>';
8790
                            }
8791
                        } else {
8792
                            if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
8793
                                $out .= $form->textwithpicto($labeltoshow, $helptoshow);
8794
                            } else {
8795
                                $out .= $labeltoshow;
8796
                            }
8797
                        }
8798
8799
                        $out .= ($display_type == 'card' ? '</td>' : '</div>');
8800
8801
                        $html_id = !empty($this->id) ? $this->element . '_extras_' . $key . '_' . $this->id : '';
8802
                        if ($display_type == 'card') {
8803
                            // 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
8804
                            $out .= '<td ' . ($html_id ? 'id="' . $html_id . '" ' : '') . ' class="valuefieldcreate ' . $this->element . '_extras_' . $key . '" ' . ($colspan ? ' colspan="' . $colspan . '"' : '') . '>';
8805
                        } elseif ($display_type == 'line') {
8806
                            $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] . '">';
8807
                        }
8808
8809
                        switch ($mode) {
8810
                            case "view":
8811
                                $out .= $extrafields->showOutputField($key, $value, '', $this->table_element);
8812
                                break;
8813
                            case "create":
8814
                                $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', 0, $this->id, $this->table_element);
8815
                                break;
8816
                            case "edit":
8817
                                $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', 0, $this->id, $this->table_element);
8818
                                break;
8819
                        }
8820
8821
                        $out .= ($display_type == 'card' ? '</td>' : '</div>');
8822
                        $out .= ($display_type == 'card' ? '</tr>' : '</div>');
8823
                        $e++;
8824
                    }
8825
                }
8826
                $out .= "\n";
8827
                // Add code to manage list depending on others
8828
                if (!empty($conf->use_javascript_ajax)) {
8829
                    $out .= $this->getJSListDependancies();
8830
                }
8831
8832
                $out .= '<!-- commonobject:showOptionals end --> ' . "\n";
8833
8834
                if (empty($nbofextrafieldsshown)) {
8835
                    $out = '';
8836
                }
8837
            }
8838
        }
8839
8840
        $out .= $hookmanager->resPrint;
8841
8842
        return $out;
8843
    }
8844
8845
    /**
8846
     * @param   string  $type   Type for prefix
8847
     * @return  string          JavaScript code to manage dependency
8848
     */
8849
    public function getJSListDependancies($type = '_extra')
8850
    {
8851
        $out = '
8852
					<script nonce="' . getNonce() . '">
8853
					jQuery(document).ready(function() {
8854
						function showOptions' . $type . '(child_list, parent_list, orig_select)
8855
						{
8856
							var val = $("select[name=\""+parent_list+"\"]").val();
8857
							var parentVal = parent_list + ":" + val;
8858
							if(typeof val == "string"){
8859
								if(val != "") {
8860
									var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
8861
									$("select[name=\""+child_list+"\"] option[parent]").remove();
8862
									$("select[name=\""+child_list+"\"]").append(options);
8863
								} else {
8864
									var options = orig_select.find("option[parent]").clone();
8865
									$("select[name=\""+child_list+"\"] option[parent]").remove();
8866
									$("select[name=\""+child_list+"\"]").append(options);
8867
								}
8868
							} else if(val > 0) {
8869
								var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
8870
								$("select[name=\""+child_list+"\"] option[parent]").remove();
8871
								$("select[name=\""+child_list+"\"]").append(options);
8872
							} else {
8873
								var options = orig_select.find("option[parent]").clone();
8874
								$("select[name=\""+child_list+"\"] option[parent]").remove();
8875
								$("select[name=\""+child_list+"\"]").append(options);
8876
							}
8877
						}
8878
						function setListDependencies' . $type . '() {
8879
							jQuery("select option[parent]").parent().each(function() {
8880
								var orig_select = {};
8881
								var child_list = $(this).attr("name");
8882
								orig_select[child_list] = $(this).clone();
8883
								var parent = $(this).find("option[parent]:first").attr("parent");
8884
								var infos = parent.split(":");
8885
								var parent_list = infos[0];
8886
8887
								//Hide daughters lists
8888
								if ($("#"+child_list).val() == 0 && $("#"+parent_list).val() == 0){
8889
									$("#"+child_list).hide();
8890
								//Show mother lists
8891
								} else if ($("#"+parent_list).val() != 0){
8892
									$("#"+parent_list).show();
8893
								}
8894
								//Show the child list if the parent list value is selected
8895
								$("select[name=\""+parent_list+"\"]").click(function() {
8896
									if ($(this).val() != 0){
8897
										$("#"+child_list).show()
8898
									}
8899
								});
8900
8901
								//When we change parent list
8902
								$("select[name=\""+parent_list+"\"]").change(function() {
8903
									showOptions' . $type . '(child_list, parent_list, orig_select[child_list]);
8904
									//Select the value 0 on child list after a change on the parent list
8905
									$("#"+child_list).val(0).trigger("change");
8906
									//Hide child lists if the parent value is set to 0
8907
									if ($(this).val() == 0){
8908
								   		$("#"+child_list).hide();
8909
									}
8910
								});
8911
							});
8912
						}
8913
8914
						setListDependencies' . $type . '();
8915
					});
8916
					</script>' . "\n";
8917
        return $out;
8918
    }
8919
8920
    /**
8921
     * Returns the rights used for this class
8922
     *
8923
     * @return null|int|stdClass        Object of permission for the module
8924
     */
8925
    public function getRights()
8926
    {
8927
        global $user;
8928
8929
        $module = empty($this->module) ? '' : $this->module;
8930
        $element = $this->element;
8931
8932
        if ($element == 'facturerec') {
8933
            $element = 'facture';
8934
        } elseif ($element == 'invoice_supplier_rec') {
8935
            return !$user->hasRight('fournisseur', 'facture') ? null : $user->hasRight('fournisseur', 'facture');
8936
        } elseif ($module && $user->hasRight($module, $element)) {
8937
            // for modules built with ModuleBuilder
8938
            return $user->hasRight($module, $element);
8939
        }
8940
8941
        return $user->rights->$element;
8942
    }
8943
8944
    /**
8945
     * Function used to replace a thirdparty id with another one.
8946
     * This function is meant to be called from replaceThirdparty with the appropriate tables
8947
     * Column name fk_soc MUST be used to identify thirdparties
8948
     *
8949
     * @param  DoliDB      $dbs           Database handler
8950
     * @param  int         $origin_id     Old thirdparty id (the thirdparty to delete)
8951
     * @param  int         $dest_id       New thirdparty id (the thirdparty that will received element of the other)
8952
     * @param  string[]    $tables        Tables that need to be changed
8953
     * @param  int         $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)
8954
     * @return bool                       True if success, False if error
8955
     */
8956
    public static function commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
8957
    {
8958
        foreach ($tables as $table) {
8959
            $sql = 'UPDATE ' . $dbs->prefix() . $table . ' SET fk_soc = ' . ((int) $dest_id) . ' WHERE fk_soc = ' . ((int) $origin_id);
8960
8961
            if (!$dbs->query($sql)) {
8962
                if ($ignoreerrors) {
8963
                    return true; // TODO Not enough. If there is A-B on kept thirdparty and B-C on old one, we must get A-B-C after merge. Not A-B.
8964
                }
8965
                //$this->errors = $db->lasterror();
8966
                return false;
8967
            }
8968
        }
8969
8970
        return true;
8971
    }
8972
8973
    /**
8974
     * Function used to replace a product id with another one.
8975
     * This function is meant to be called from replaceProduct with the appropriate tables
8976
     * Column name fk_product MUST be used to identify products
8977
     *
8978
     * @param  DoliDB      $dbs           Database handler
8979
     * @param  int         $origin_id     Old product id (the product to delete)
8980
     * @param  int         $dest_id       New product id (the product that will received element of the other)
8981
     * @param  string[]    $tables        Tables that need to be changed
8982
     * @param  int         $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)
8983
     * @return bool                       True if success, False if error
8984
     */
8985
    public static function commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
8986
    {
8987
        foreach ($tables as $table) {
8988
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . $table . ' SET fk_product = ' . ((int) $dest_id) . ' WHERE fk_product = ' . ((int) $origin_id);
8989
8990
            if (!$dbs->query($sql)) {
8991
                if ($ignoreerrors) {
8992
                    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.
8993
                }
8994
                //$this->errors = $db->lasterror();
8995
                return false;
8996
            }
8997
        }
8998
8999
        return true;
9000
    }
9001
9002
    /**
9003
     * Get buy price to use for margin calculation. This function is called when buy price is unknown.
9004
     *   Set buy price = sell price if ForceBuyingPriceIfNull configured,
9005
     *   elseif calculation MARGIN_TYPE = 'costprice' and costprice is defined, use costprice as buyprice
9006
     *   elseif calculation MARGIN_TYPE = 'pmp' and pmp is calculated, use pmp as buyprice
9007
     *   else set min buy price as buy price
9008
     *
9009
     * @param float     $unitPrice       Product unit price
9010
     * @param float     $discountPercent Line discount percent
9011
     * @param int       $fk_product      Product id
9012
     * @return float|int                 Return buy price if OK, integer <0 if KO
9013
     */
9014
    public function defineBuyPrice($unitPrice = 0.0, $discountPercent = 0.0, $fk_product = 0)
9015
    {
9016
        global $conf;
9017
9018
        $buyPrice = 0;
9019
9020
        if (($unitPrice > 0) && (isset($conf->global->ForceBuyingPriceIfNull) && getDolGlobalInt('ForceBuyingPriceIfNull') > 0)) {
9021
            // When ForceBuyingPriceIfNull is set
9022
            $buyPrice = $unitPrice * (1 - $discountPercent / 100);
9023
        } else {
9024
            // Get cost price for margin calculation
9025
            if (!empty($fk_product) && $fk_product > 0) {
9026
                if (getDolGlobalString('MARGIN_TYPE') == 'costprice') {
9027
                    require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
9028
                    $product = new Product($this->db);
9029
                    $result = $product->fetch($fk_product);
9030
                    if ($result <= 0) {
9031
                        $this->errors[] = 'ErrorProductIdDoesNotExists';
9032
                        return -1;
9033
                    }
9034
                    if ($product->cost_price > 0) {
9035
                        $buyPrice = $product->cost_price;
9036
                    } elseif ($product->pmp > 0) {
9037
                        $buyPrice = $product->pmp;
9038
                    }
9039
                } elseif (getDolGlobalString('MARGIN_TYPE') == 'pmp') {
9040
                    require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
9041
                    $product = new Product($this->db);
9042
                    $result = $product->fetch($fk_product);
9043
                    if ($result <= 0) {
9044
                        $this->errors[] = 'ErrorProductIdDoesNotExists';
9045
                        return -1;
9046
                    }
9047
                    if ($product->pmp > 0) {
9048
                        $buyPrice = $product->pmp;
9049
                    }
9050
                }
9051
9052
                if (empty($buyPrice) && isset($conf->global->MARGIN_TYPE) && in_array($conf->global->MARGIN_TYPE, array('1', 'pmp', 'costprice'))) {
9053
                    require_once DOL_DOCUMENT_ROOT . '/fourn/class/fournisseur.product.class.php';
9054
                    $productFournisseur = new ProductFournisseur($this->db);
9055
                    if (($result = $productFournisseur->find_min_price_product_fournisseur($fk_product)) > 0) {
9056
                        $buyPrice = $productFournisseur->fourn_unitprice;
9057
                    } elseif ($result < 0) {
9058
                        $this->errors[] = $productFournisseur->error;
9059
                        return -2;
9060
                    }
9061
                }
9062
            }
9063
        }
9064
        return $buyPrice;
9065
    }
9066
9067
    /**
9068
     * getDataToShowPhoto
9069
     *
9070
     * @param   string  $modulepart     Module part
9071
     * @param   string  $imagesize      Image size
9072
     * @return  array                   Array of data to show photo
9073
     */
9074
    public function getDataToShowPhoto($modulepart, $imagesize)
9075
    {
9076
        global $conf;
9077
9078
        $file = '';
9079
        $originalfile = '';
9080
        $newmodulepart = $modulepart;
9081
        if ($modulepart == 'unknown' && !empty($this->module)) {
9082
            $newmodulepart = $this->module;
9083
        }
9084
9085
        $id = $this->id;
9086
        $dir = $conf->$newmodulepart->dir_output ?? '';
9087
        if (!empty($this->photo)) {
9088
            if (dolIsAllowedForPreview($this->photo)) {
9089
                if ((string) $imagesize == 'mini') {
9090
                    $file = get_exdir(0, 0, 0, 0, $this, $newmodulepart) . 'photos/' . dol_sanitizeFileName(getImageFileNameForSize($this->photo, '_mini'));
9091
                } elseif ((string) $imagesize == 'small') {
9092
                    $file = get_exdir(0, 0, 0, 0, $this, $newmodulepart) . 'photos/' . dol_sanitizeFileName(getImageFileNameForSize($this->photo, '_small'));
9093
                } else {
9094
                    $file = get_exdir(0, 0, 0, 0, $this, $newmodulepart) . 'photos/' . dol_sanitizeFileName($this->photo);
9095
                }
9096
                $originalfile = get_exdir(0, 0, 0, 0, $this, $newmodulepart) . 'photos/' . dol_sanitizeFileName($this->photo);
9097
            }
9098
        }
9099
9100
        $altfile = '';
9101
        $email = empty($this->email) ? '' : $this->email;
9102
        $capture = '';
9103
9104
        return array('dir' => $dir, 'file' => $file, 'originalfile' => $originalfile, 'altfile' => $altfile, 'email' => $email, 'capture' => $capture);
9105
    }
9106
9107
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
9108
    /**
9109
     *  Show photos of an object (nbmax maximum), into several columns
9110
     *
9111
     *  @param      string      $modulepart     'product', 'ticket', ...
9112
     *  @param      string      $sdir           Directory to scan (full absolute path)
9113
     *  @param      int         $size           0=original size, 1='small' use thumbnail if possible
9114
     *  @param      int         $nbmax          Nombre maximum de photos (0=pas de max)
9115
     *  @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'.
9116
     *  @param      int         $showfilename   1=Show filename
9117
     *  @param      int         $showaction     1=Show icon with action links (resize, delete)
9118
     *  @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.
9119
     *  @param      int         $maxWidth       Max width of original image when size='small'
9120
     *  @param      int         $nolink         Do not add a href link to view enlarged imaged into a new tab
9121
     *  @param      int|string  $overwritetitle Do not add title tag on image
9122
     *  @param      int         $usesharelink   Use the public shared link of image (if not available, the 'nophoto' image will be shown instead)
9123
     *  @param      string      $cache          A string if we want to use a cached version of image
9124
     *  @param      string      $addphotorefcss Add CSS to img of photos
9125
     *  @return     string                      Html code to show photo. Number of photos shown is saved in this->nbphoto
9126
     */
9127
    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')
9128
    {
9129
        // phpcs:enable
9130
        global $conf, $user, $langs;
9131
9132
        include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
9133
        include_once DOL_DOCUMENT_ROOT . '/core/lib/images.lib.php';
9134
9135
        $sortfield = 'position_name';
9136
        $sortorder = 'asc';
9137
9138
        $dir = $sdir . '/';
9139
        $pdir = '/';
9140
9141
        $dir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
9142
        $pdir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
9143
9144
        // For backward compatibility
9145
        if ($modulepart == 'product') {
9146
            if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
9147
                $dir = $sdir . '/' . get_exdir($this->id, 2, 0, 0, $this, $modulepart) . $this->id . "/photos/";
9148
                $pdir = '/' . get_exdir($this->id, 2, 0, 0, $this, $modulepart) . $this->id . "/photos/";
9149
            }
9150
        }
9151
        if ($modulepart == 'category') {
9152
            $dir = $sdir . '/' . get_exdir($this->id, 2, 0, 0, $this, $modulepart) . $this->id . "/photos/";
9153
            $pdir = '/' . get_exdir($this->id, 2, 0, 0, $this, $modulepart) . $this->id . "/photos/";
9154
        }
9155
9156
        // Defined relative dir to DOL_DATA_ROOT
9157
        $relativedir = '';
9158
        if ($dir) {
9159
            $relativedir = preg_replace('/^' . preg_quote(DOL_DATA_ROOT, '/') . '/', '', $dir);
9160
            $relativedir = preg_replace('/^[\\/]/', '', $relativedir);
9161
            $relativedir = preg_replace('/[\\/]$/', '', $relativedir);
9162
        }
9163
9164
        $dirthumb = $dir . 'thumbs/';
9165
        $pdirthumb = $pdir . 'thumbs/';
9166
9167
        $return = '<!-- Photo -->' . "\n";
9168
        $nbphoto = 0;
9169
9170
        $filearray = dol_dir_list($dir, "files", 0, '', '(\.meta|_preview.*\.png)$', $sortfield, (strtolower($sortorder) == 'desc' ? SORT_DESC : SORT_ASC), 1);
9171
9172
        /*if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO'))    // For backward compatibility, we scan also old dirs
9173
         {
9174
         $filearrayold=dol_dir_list($dirold,"files",0,'','(\.meta|_preview.*\.png)$',$sortfield,(strtolower($sortorder)=='desc'?SORT_DESC:SORT_ASC),1);
9175
         $filearray=array_merge($filearray, $filearrayold);
9176
         }*/
9177
9178
        completeFileArrayWithDatabaseInfo($filearray, $relativedir);
9179
9180
        if (count($filearray)) {
9181
            if ($sortfield && $sortorder) {
9182
                $filearray = dol_sort_array($filearray, $sortfield, $sortorder);
9183
            }
9184
9185
            foreach ($filearray as $key => $val) {
9186
                $photo = '';
9187
                $file = $val['name'];
9188
9189
                //if (dol_is_file($dir.$file) && image_format_supported($file) >= 0)
9190
                if (image_format_supported($file) >= 0) {
9191
                    $nbphoto++;
9192
                    $photo = $file;
9193
                    $viewfilename = $file;
9194
9195
                    if ($size == 1 || $size == 'small') {   // Format vignette
9196
                        // Find name of thumb file
9197
                        $photo_vignette = basename(getImageFileNameForSize($dir . $file, '_small'));
9198
                        if (!dol_is_file($dirthumb . $photo_vignette)) {
9199
                            // The thumb does not exists, so we will use the original file
9200
                            $dirthumb = $dir;
9201
                            $pdirthumb = $pdir;
9202
                            $photo_vignette = basename($file);
9203
                        }
9204
9205
                        // Get filesize of original file
9206
                        $imgarray = dol_getImageSize($dir . $photo);
9207
9208
                        if ($nbbyrow > 0) {
9209
                            if ($nbphoto == 1) {
9210
                                $return .= '<table class="valigntop center centpercent" style="border: 0; padding: 2px; border-spacing: 2px; border-collapse: separate;">';
9211
                            }
9212
9213
                            if ($nbphoto % $nbbyrow == 1) {
9214
                                $return .= '<tr class="center valignmiddle" style="border: 1px">';
9215
                            }
9216
                            $return .= '<td style="width: ' . ceil(100 / $nbbyrow) . '%" class="photo">' . "\n";
9217
                        } elseif ($nbbyrow < 0) {
9218
                            $return .= '<div class="inline-block">' . "\n";
9219
                        }
9220
9221
                        $relativefile = preg_replace('/^\//', '', $pdir . $photo);
9222
                        if (empty($nolink)) {
9223
                            $urladvanced = getAdvancedPreviewUrl($modulepart, $relativefile, 0, 'entity=' . $this->entity);
9224
                            if ($urladvanced) {
9225
                                $return .= '<a href="' . $urladvanced . '">';
9226
                            } else {
9227
                                $return .= '<a href="' . DOL_URL_ROOT . '/viewimage.php?modulepart=' . $modulepart . '&entity=' . $this->entity . '&file=' . urlencode($pdir . $photo) . '" class="aphoto" target="_blank" rel="noopener noreferrer">';
9228
                            }
9229
                        }
9230
9231
                        // Show image (width height=$maxHeight)
9232
                        // Si fichier vignette disponible et image source trop grande, on utilise la vignette, sinon on utilise photo origine
9233
                        $alt = $langs->transnoentitiesnoconv('File') . ': ' . $relativefile;
9234
                        $alt .= ' - ' . $langs->transnoentitiesnoconv('Size') . ': ' . $imgarray['width'] . 'x' . $imgarray['height'];
9235
                        if ($overwritetitle) {
9236
                            if (is_numeric($overwritetitle)) {
9237
                                $alt = '';
9238
                            } else {
9239
                                $alt = $overwritetitle;
9240
                            }
9241
                        }
9242
                        if (empty($cache) && !empty($val['label'])) {
9243
                            // label is md5 of file
9244
                            // use it in url to say we want to cache this version of the file
9245
                            $cache = $val['label'];
9246
                        }
9247
                        if ($usesharelink) {
9248
                            if ($val['share']) {
9249
                                if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
9250
                                    $return .= '<!-- Show original file (thumb not yet available with shared links) -->';
9251
                                    $return .= '<img class="photo photowithmargin' . ($addphotorefcss ? ' ' . $addphotorefcss : '') . '"' . ($maxHeight ? ' height="' . $maxHeight . '"' : '') . ' src="' . DOL_URL_ROOT . '/viewimage.php?hashp=' . urlencode($val['share']) . ($cache ? '&cache=' . urlencode($cache) : '') . '" title="' . dol_escape_htmltag($alt) . '">';
9252
                                } else {
9253
                                    $return .= '<!-- Show original file -->';
9254
                                    $return .= '<img class="photo photowithmargin' . ($addphotorefcss ? ' ' . $addphotorefcss : '') . '" height="' . $maxHeight . '" src="' . DOL_URL_ROOT . '/viewimage.php?hashp=' . urlencode($val['share']) . ($cache ? '&cache=' . urlencode($cache) : '') . '" title="' . dol_escape_htmltag($alt) . '">';
9255
                                }
9256
                            } else {
9257
                                $return .= '<!-- Show nophoto file (because file is not shared) -->';
9258
                                $return .= '<img class="photo photowithmargin' . ($addphotorefcss ? ' ' . $addphotorefcss : '') . '" height="' . $maxHeight . '" src="' . DOL_URL_ROOT . '/public/theme/common/nophoto.png" title="' . dol_escape_htmltag($alt) . '">';
9259
                            }
9260
                        } else {
9261
                            if (empty($maxHeight) || ($photo_vignette && $imgarray['height'] > $maxHeight)) {
9262
                                $return .= '<!-- Show thumb -->';
9263
                                $return .= '<img class="photo photowithmargin' . ($addphotorefcss ? ' ' . $addphotorefcss : '') . ' maxwidth150onsmartphone maxwidth200"' . ($maxHeight ? ' height="' . $maxHeight . '"' : '') . ' src="' . DOL_URL_ROOT . '/viewimage.php?modulepart=' . $modulepart . '&entity=' . $this->entity . ($cache ? '&cache=' . urlencode($cache) : '') . '&file=' . urlencode($pdirthumb . $photo_vignette) . '" title="' . dol_escape_htmltag($alt) . '">';
9264
                            } else {
9265
                                $return .= '<!-- Show original file -->';
9266
                                $return .= '<img class="photo photowithmargin' . ($addphotorefcss ? ' ' . $addphotorefcss : '') . '" height="' . $maxHeight . '" src="' . DOL_URL_ROOT . '/viewimage.php?modulepart=' . $modulepart . '&entity=' . $this->entity . ($cache ? '&cache=' . urlencode($cache) : '') . '&file=' . urlencode($pdir . $photo) . '" title="' . dol_escape_htmltag($alt) . '">';
9267
                            }
9268
                        }
9269
9270
                        if (empty($nolink)) {
9271
                            $return .= '</a>';
9272
                        }
9273
9274
                        if ($showfilename) {
9275
                            $return .= '<br>' . $viewfilename;
9276
                        }
9277
                        if ($showaction) {
9278
                            $return .= '<br>';
9279
                            // If $photo_vignette set, we add link to generate thumbs if file is an image and ->imgWidth or->imgHeight higher than limits
9280
                            if ($photo_vignette && (image_format_supported($photo) > 0) && ((isset($this->imgWidth) && $this->imgWidth > $maxWidth) || (isset($this->imgHeight) && $this->imgHeight > $maxHeight))) {
9281
                                $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>';
9282
                            }
9283
                            // Special case for product
9284
                            if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
9285
                                // Link to resize
9286
                                $return .= '<a href="' . DOL_URL_ROOT . '/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; ';
9287
9288
                                // Link to delete
9289
                                $return .= '<a href="' . $_SERVER['PHP_SELF'] . '?id=' . $this->id . '&action=delete&token=' . newToken() . '&file=' . urlencode($pdir . $viewfilename) . '">';
9290
                                $return .= img_delete() . '</a>';
9291
                            }
9292
                        }
9293
                        $return .= "\n";
9294
9295
                        if ($nbbyrow > 0) {
9296
                            $return .= '</td>';
9297
                            if (($nbphoto % $nbbyrow) == 0) {
9298
                                $return .= '</tr>';
9299
                            }
9300
                        } elseif ($nbbyrow < 0) {
9301
                            $return .= '</div>' . "\n";
9302
                        }
9303
                    }
9304
9305
                    if (empty($size)) {     // Format origine
9306
                        $return .= '<img class="photo photowithmargin" src="' . DOL_URL_ROOT . '/viewimage.php?modulepart=' . $modulepart . '&entity=' . $this->entity . '&file=' . urlencode($pdir . $photo) . '">';
9307
9308
                        if ($showfilename) {
9309
                            $return .= '<br>' . $viewfilename;
9310
                        }
9311
                        if ($showaction) {
9312
                            // Special case for product
9313
                            if ($modulepart == 'product' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
9314
                                // Link to resize
9315
                                $return .= '<a href="' . DOL_URL_ROOT . '/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; ';
9316
9317
                                // Link to delete
9318
                                $return .= '<a href="' . $_SERVER['PHP_SELF'] . '?id=' . $this->id . '&action=delete&token=' . newToken() . '&file=' . urlencode($pdir . $viewfilename) . '">';
9319
                                $return .= img_delete() . '</a>';
9320
                            }
9321
                        }
9322
                    }
9323
9324
                    // On continue ou on arrete de boucler ?
9325
                    if ($nbmax && $nbphoto >= $nbmax) {
9326
                        break;
9327
                    }
9328
                }
9329
            }
9330
9331
            if ($size == 1 || $size == 'small') {
9332
                if ($nbbyrow > 0) {
9333
                    // Ferme tableau
9334
                    while ($nbphoto % $nbbyrow) {
9335
                        $return .= '<td style="width: ' . ceil(100 / $nbbyrow) . '%">&nbsp;</td>';
9336
                        $nbphoto++;
9337
                    }
9338
9339
                    if ($nbphoto) {
9340
                        $return .= '</table>';
9341
                    }
9342
                }
9343
            }
9344
        }
9345
9346
        $this->nbphoto = $nbphoto;
9347
9348
        return $return;
9349
    }
9350
9351
9352
    /**
9353
     * Function test if type is array
9354
     *
9355
     * @param   array   $info   content information of field
9356
     * @return  bool            true if array
9357
     */
9358
    protected function isArray($info)
9359
    {
9360
        if (is_array($info)) {
9361
            if (isset($info['type']) && $info['type'] == 'array') {
9362
                return true;
9363
            } else {
9364
                return false;
9365
            }
9366
        }
9367
        return false;
9368
    }
9369
9370
    /**
9371
     * Function test if type is date
9372
     *
9373
     * @param   array   $info   content information of field
9374
     * @return  bool            true if date
9375
     */
9376
    public function isDate($info)
9377
    {
9378
        if (isset($info['type']) && ($info['type'] == 'date' || $info['type'] == 'datetime' || $info['type'] == 'timestamp')) {
9379
            return true;
9380
        }
9381
        return false;
9382
    }
9383
9384
    /**
9385
     * Function test if type is duration
9386
     *
9387
     * @param   array   $info   content information of field
9388
     * @return  bool            true if field of type duration
9389
     */
9390
    public function isDuration($info)
9391
    {
9392
        if (is_array($info)) {
9393
            if (isset($info['type']) && ($info['type'] == 'duration')) {
9394
                return true;
9395
            } else {
9396
                return false;
9397
            }
9398
        } else {
9399
            return false;
9400
        }
9401
    }
9402
9403
    /**
9404
     * Function test if type is integer
9405
     *
9406
     * @param   array   $info   content information of field
9407
     * @return  bool            true if integer
9408
     */
9409
    public function isInt($info)
9410
    {
9411
        if (is_array($info)) {
9412
            if (isset($info['type']) && (preg_match('/(^int|int$)/i', $info['type']))) {
9413
                return true;
9414
            } else {
9415
                return false;
9416
            }
9417
        } else {
9418
            return false;
9419
        }
9420
    }
9421
9422
    /**
9423
     * Function test if type is float
9424
     *
9425
     * @param   array   $info   content information of field
9426
     * @return  bool            true if float
9427
     */
9428
    public function isFloat($info)
9429
    {
9430
        if (is_array($info)) {
9431
            if (isset($info['type']) && (preg_match('/^(double|real|price)/i', $info['type']))) {
9432
                return true;
9433
            } else {
9434
                return false;
9435
            }
9436
        }
9437
        return false;
9438
    }
9439
9440
    /**
9441
     * Function test if type is text
9442
     *
9443
     * @param   array   $info   content information of field
9444
     * @return  bool            true if type text
9445
     */
9446
    public function isText($info)
9447
    {
9448
        if (is_array($info)) {
9449
            if (isset($info['type']) && $info['type'] == 'text') {
9450
                return true;
9451
            } else {
9452
                return false;
9453
            }
9454
        }
9455
        return false;
9456
    }
9457
9458
    /**
9459
     * Function test if field can be null
9460
     *
9461
     * @param   array   $info   content information of field
9462
     * @return  bool            true if it can be null
9463
     */
9464
    protected function canBeNull($info)
9465
    {
9466
        if (is_array($info)) {
9467
            if (isset($info['notnull']) && $info['notnull'] != '1') {
9468
                return true;
9469
            } else {
9470
                return false;
9471
            }
9472
        }
9473
        return true;
9474
    }
9475
9476
    /**
9477
     * Function test if field is forced to null if zero or empty
9478
     *
9479
     * @param   array   $info   content information of field
9480
     * @return  bool            true if forced to null
9481
     */
9482
    protected function isForcedToNullIfZero($info)
9483
    {
9484
        if (is_array($info)) {
9485
            if (isset($info['notnull']) && $info['notnull'] == '-1') {
9486
                return true;
9487
            } else {
9488
                return false;
9489
            }
9490
        }
9491
        return false;
9492
    }
9493
9494
    /**
9495
     * Function test if is indexed
9496
     *
9497
     * @param   array   $info   content information of field
9498
     * @return                  bool
9499
     */
9500
    protected function isIndex($info)
9501
    {
9502
        if (is_array($info)) {
9503
            if (isset($info['index']) && $info['index'] == true) {
9504
                return true;
9505
            } else {
9506
                return false;
9507
            }
9508
        }
9509
        return false;
9510
    }
9511
9512
9513
    /**
9514
     * Function to return the array of data key-value from the ->fields and all the ->properties of an object.
9515
     *
9516
     * Note: $this->${field} are set by the page that make the createCommon() or the updateCommon().
9517
     * $this->${field} should be a clean and string value (so date are formatted for SQL insert).
9518
     *
9519
     * @return array        Array with all values of each properties to update
9520
     */
9521
    protected function setSaveQuery()
9522
    {
9523
        global $conf;
9524
9525
        $queryarray = array();
9526
        foreach ($this->fields as $field => $info) {    // Loop on definition of fields
9527
            // Depending on field type ('datetime', ...)
9528
            if ($this->isDate($info)) {
9529
                if (empty($this->{$field})) {
9530
                    $queryarray[$field] = null;
9531
                } else {
9532
                    $queryarray[$field] = $this->db->idate($this->{$field});
9533
                }
9534
            } elseif ($this->isDuration($info)) {
9535
                // $this->{$field} may be null, '', 0, '0', 123, '123'
9536
                if ((isset($this->{$field}) && $this->{$field} != '') || !empty($info['notnull'])) {
9537
                    if (!isset($this->{$field})) {
9538
                        if (!empty($info['default'])) {
9539
                            $queryarray[$field] = $info['default'];
9540
                        } else {
9541
                            $queryarray[$field] = 0;
9542
                        }
9543
                    } else {
9544
                        $queryarray[$field] = (int) $this->{$field};        // If '0', it may be set to null later if $info['notnull'] == -1
9545
                    }
9546
                } else {
9547
                    $queryarray[$field] = null;
9548
                }
9549
            } elseif ($this->isInt($info) || $this->isFloat($info)) {
9550
                if ($field == 'entity' && is_null($this->{$field})) {
9551
                    $queryarray[$field] = ((int) $conf->entity);
9552
                } else {
9553
                    // $this->{$field} may be null, '', 0, '0', 123, '123'
9554
                    if ((isset($this->{$field}) && ((string) $this->{$field}) != '') || !empty($info['notnull'])) {
9555
                        if (!isset($this->{$field})) {
9556
                            $queryarray[$field] = 0;
9557
                        } elseif ($this->isInt($info)) {
9558
                            $queryarray[$field] = (int) $this->{$field};    // If '0', it may be set to null later if $info['notnull'] == -1
9559
                        } elseif ($this->isFloat($info)) {
9560
                            $queryarray[$field] = (float) $this->{$field};  // If '0', it may be set to null later if $info['notnull'] == -1
9561
                        }
9562
                    } else {
9563
                        $queryarray[$field] = null;
9564
                    }
9565
                }
9566
            } else {
9567
                // Note: If $this->{$field} is not defined, it means there is a bug into definition of ->fields or a missing declaration of property
9568
                // We should keep the warning generated by this because it is a bug somewhere else in code, not here.
9569
                $queryarray[$field] = $this->{$field};
9570
            }
9571
9572
            if ($info['type'] == 'timestamp' && empty($queryarray[$field])) {
9573
                unset($queryarray[$field]);
9574
            }
9575
            if (!empty($info['notnull']) && $info['notnull'] == -1 && empty($queryarray[$field])) {
9576
                $queryarray[$field] = null; // May force 0 to null
9577
            }
9578
        }
9579
9580
        return $queryarray;
9581
    }
9582
9583
    /**
9584
     * Function to load data from a SQL pointer into properties of current object $this
9585
     *
9586
     * @param   stdClass    $obj    Contain data of object from database
9587
     * @return void
9588
     */
9589
    public function setVarsFromFetchObj(&$obj)
9590
    {
9591
        global $db;
9592
9593
        foreach ($this->fields as $field => $info) {
9594
            if ($this->isDate($info)) {
9595
                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') {
9596
                    $this->$field = '';
9597
                } else {
9598
                    $this->$field = $db->jdate($obj->$field);
9599
                }
9600
            } elseif ($this->isInt($info)) {
9601
                if ($field == 'rowid') {
9602
                    $this->id = (int) $obj->$field;
9603
                } else {
9604
                    if ($this->isForcedToNullIfZero($info)) {
9605
                        if (empty($obj->$field)) {
9606
                            $this->$field = null;
9607
                        } else {
9608
                            $this->$field = (float) $obj->$field;
9609
                        }
9610
                    } else {
9611
                        if (isset($obj->$field) && (!is_null($obj->$field) || (isset($info['notnull']) && $info['notnull'] == 1))) {
9612
                            $this->$field = (int) $obj->$field;
9613
                        } else {
9614
                            $this->$field = null;
9615
                        }
9616
                    }
9617
                }
9618
            } elseif ($this->isFloat($info)) {
9619
                if ($this->isForcedToNullIfZero($info)) {
9620
                    if (empty($obj->$field)) {
9621
                        $this->$field = null;
9622
                    } else {
9623
                        $this->$field = (float) $obj->$field;
9624
                    }
9625
                } else {
9626
                    if (isset($obj->$field) && (!is_null($obj->$field) || (isset($info['notnull']) && $info['notnull'] == 1))) {
9627
                        $this->$field = (float) $obj->$field;
9628
                    } else {
9629
                        $this->$field = null;
9630
                    }
9631
                }
9632
            } else {
9633
                $this->$field = isset($obj->$field) ? $obj->$field : null;
9634
            }
9635
        }
9636
9637
        // If there is no 'ref' field, we force property ->ref to ->id for a better compatibility with common functions.
9638
        if (!isset($this->fields['ref']) && isset($this->id)) {
9639
            $this->ref = (string) $this->id;
9640
        }
9641
    }
9642
9643
    /**
9644
     * Sets all object fields to null. Useful for example in lists, when printing multiple lines and a different object os fetched for each line.
9645
     * @return void
9646
     */
9647
    public function emtpyObjectVars()
9648
    {
9649
        foreach ($this->fields as $field => $arr) {
9650
            $this->$field = null;
9651
        }
9652
    }
9653
9654
    /**
9655
     * Function to concat keys of fields
9656
     *
9657
     * @param   string  $alias          String of alias of table for fields. For example 't'. It is recommended to use '' and set alias into fields definition.
9658
     * @param   array   $excludefields  Array of fields to exclude
9659
     * @return  string                  List of alias fields
9660
     */
9661
    public function getFieldList($alias = '', $excludefields = array())
9662
    {
9663
        $keys = array_keys($this->fields);
9664
        if (!empty($alias)) {
9665
            $keys_with_alias = array();
9666
            foreach ($keys as $fieldname) {
9667
                if (!empty($excludefields)) {
9668
                    if (in_array($fieldname, $excludefields)) { // The field is excluded and must not be in output
9669
                        continue;
9670
                    }
9671
                }
9672
                $keys_with_alias[] = $alias . '.' . $fieldname;
9673
            }
9674
            return implode(',', $keys_with_alias);
9675
        } else {
9676
            return implode(',', $keys);
9677
        }
9678
    }
9679
9680
    /**
9681
     * Add quote to field value if necessary
9682
     *
9683
     * @param   string|int  $value          Value to protect
9684
     * @param   array       $fieldsentry    Properties of field
9685
     * @return  string|int
9686
     */
9687
    protected function quote($value, $fieldsentry)
9688
    {
9689
        if (is_null($value)) {
9690
            return 'NULL';
9691
        } elseif (preg_match('/^(int|double|real|price)/i', $fieldsentry['type'])) {
9692
            return price2num("$value");
9693
        } elseif (preg_match('/int$/i', $fieldsentry['type'])) {
9694
            return (int) $value;
9695
        } elseif ($fieldsentry['type'] == 'boolean') {
9696
            if ($value) {
9697
                return 'true';
9698
            } else {
9699
                return 'false';
9700
            }
9701
        } else {
9702
            return "'" . $this->db->escape($value) . "'";
9703
        }
9704
    }
9705
9706
9707
    /**
9708
     * Create object into database
9709
     *
9710
     * @param  User $user      User that creates
9711
     * @param  int  $notrigger 0=launch triggers after, 1=disable triggers
9712
     * @return int             Return integer <0 if KO, Id of created object if OK
9713
     */
9714
    public function createCommon(User $user, $notrigger = 0)
9715
    {
9716
        global $langs;
9717
9718
        dol_syslog(get_class($this) . "::createCommon create", LOG_DEBUG);
9719
9720
        $error = 0;
9721
9722
        $now = dol_now();
9723
9724
        $fieldvalues = $this->setSaveQuery();
9725
9726
        // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
9727
9728
        if (array_key_exists('date_creation', $fieldvalues) && empty($fieldvalues['date_creation'])) {
9729
            $fieldvalues['date_creation'] = $this->db->idate($now);
9730
        }
9731
        if (array_key_exists('fk_user_creat', $fieldvalues) && !($fieldvalues['fk_user_creat'] > 0)) {
9732
            $fieldvalues['fk_user_creat'] = $user->id;
9733
            $this->fk_user_creat = $user->id;
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocument::$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

9733
            /** @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...
9734
        }
9735
        if (array_key_exists('user_creation_id', $fieldvalues) && !($fieldvalues['user_creation_id'] > 0)) {
9736
            $fieldvalues['user_creation_id'] = $user->id;
9737
            $this->user_creation_id = $user->id;
9738
        }
9739
        if (array_key_exists('pass_crypted', $fieldvalues) && property_exists($this, 'pass')) {
9740
            $fieldvalues['pass_crypted'] = dol_hash($this->pass);
9741
        }
9742
        if (array_key_exists('ref', $fieldvalues)) {
9743
            $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
9744
        }
9745
9746
        unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into insert.
9747
9748
        $keys = array();
9749
        $values = array(); // Array to store string forged for SQL syntax
9750
        foreach ($fieldvalues as $k => $v) {
9751
            $keys[$k] = $k;
9752
            $value = $this->fields[$k];
9753
            // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
9754
            $values[$k] = $this->quote($v, $value); // May return string 'NULL' if $value is null
9755
        }
9756
9757
        // Clean and check mandatory
9758
        foreach ($keys as $key) {
9759
            // If field is an implicit foreign key field (so type = 'integer:...')
9760
            if (preg_match('/^integer:/i', $this->fields[$key]['type']) && $values[$key] == '-1') {
9761
                $values[$key] = '';
9762
            }
9763
            if (!empty($this->fields[$key]['foreignkey']) && $values[$key] == '-1') {
9764
                $values[$key] = '';
9765
            }
9766
9767
            if (isset($this->fields[$key]['notnull']) && $this->fields[$key]['notnull'] == 1 && (!isset($values[$key]) || $values[$key] === 'NULL') && is_null($this->fields[$key]['default'])) {
9768
                $error++;
9769
                $langs->load("errors");
9770
                dol_syslog("Mandatory field '" . $key . "' is empty and required into ->fields definition of class");
9771
                $this->errors[] = $langs->trans("ErrorFieldRequired", $this->fields[$key]['label']);
9772
            }
9773
9774
            // If value is null and there is a default value for field
9775
            if (isset($this->fields[$key]['notnull']) && $this->fields[$key]['notnull'] == 1 && (!isset($values[$key]) || $values[$key] === 'NULL') && !is_null($this->fields[$key]['default'])) {
9776
                $values[$key] = $this->quote($this->fields[$key]['default'], $this->fields[$key]);
9777
            }
9778
9779
            // If field is an implicit foreign key field (so type = 'integer:...')
9780
            if (preg_match('/^integer:/i', $this->fields[$key]['type']) && empty($values[$key])) {
9781
                if (isset($this->fields[$key]['default'])) {
9782
                    $values[$key] = ((int) $this->fields[$key]['default']);
9783
                } else {
9784
                    $values[$key] = 'null';
9785
                }
9786
            }
9787
            if (!empty($this->fields[$key]['foreignkey']) && empty($values[$key])) {
9788
                $values[$key] = 'null';
9789
            }
9790
        }
9791
9792
        if ($error) {
9793
            return -1;
9794
        }
9795
9796
        $this->db->begin();
9797
9798
        if (!$error) {
9799
            $sql = "INSERT INTO " . $this->db->prefix() . $this->table_element;
9800
            $sql .= " (" . implode(", ", $keys) . ')';
9801
            $sql .= " VALUES (" . implode(", ", $values) . ")";     // $values can contains 'abc' or 123
9802
9803
            $res = $this->db->query($sql);
9804
            if (!$res) {
9805
                $error++;
9806
                if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
9807
                    $this->errors[] = "ErrorRefAlreadyExists";
9808
                } else {
9809
                    $this->errors[] = $this->db->lasterror();
9810
                }
9811
            }
9812
        }
9813
9814
        if (!$error) {
9815
            $this->id = $this->db->last_insert_id($this->db->prefix() . $this->table_element);
9816
        }
9817
9818
        // If we have a field ref with a default value of (PROV)
9819
        if (!$error) {
9820
            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)') {
9821
                $sql = "UPDATE " . $this->db->prefix() . $this->table_element . " SET ref = '(PROV" . ((int) $this->id) . ")' WHERE (ref = '(PROV)' OR ref = '') AND rowid = " . ((int) $this->id);
9822
                $resqlupdate = $this->db->query($sql);
9823
9824
                if ($resqlupdate === false) {
9825
                    $error++;
9826
                    $this->errors[] = $this->db->lasterror();
9827
                } else {
9828
                    $this->ref = '(PROV' . $this->id . ')';
9829
                }
9830
            }
9831
        }
9832
9833
        // Create extrafields
9834
        if (!$error) {
9835
            $result = $this->insertExtraFields();
9836
            if ($result < 0) {
9837
                $error++;
9838
            }
9839
        }
9840
9841
        // Create lines
9842
        if (!empty($this->table_element_line) && !empty($this->fk_element)) {
9843
            foreach ($this->lines as $line) {
9844
                $keyforparent = $this->fk_element;
9845
                $line->$keyforparent = $this->id;
9846
9847
                // Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
9848
                //if (! is_object($line)) $line=json_decode(json_encode($line), false);  // convert recursively array into object.
9849
                if (!is_object($line)) {
9850
                    $line = (object) $line;
9851
                }
9852
9853
                $result = 0;
9854
                if (method_exists($line, 'insert')) {
9855
                    $result = $line->insert($user, 1);
9856
                } elseif (method_exists($line, 'create')) {
9857
                    $result = $line->create($user, 1);
9858
                }
9859
                if ($result < 0) {
9860
                    $this->error = $line->error;
9861
                    $this->db->rollback();
9862
                    return -1;
9863
                }
9864
            }
9865
        }
9866
9867
        // Triggers
9868
        if (!$error && !$notrigger) {
9869
            // Call triggers
9870
            $result = $this->call_trigger(strtoupper(get_class($this)) . '_CREATE', $user);
9871
            if ($result < 0) {
9872
                $error++;
9873
            }
9874
            // End call triggers
9875
        }
9876
9877
        // Commit or rollback
9878
        if ($error) {
9879
            $this->db->rollback();
9880
            return -1;
9881
        } else {
9882
            $this->db->commit();
9883
            return $this->id;
9884
        }
9885
    }
9886
9887
9888
    /**
9889
     * Load object in memory from the database. This does not load line. This is done by parent fetch() that call fetchCommon
9890
     *
9891
     * @param   int     $id             Id object
9892
     * @param   string  $ref            Ref
9893
     * @param   string  $morewhere      More SQL filters (' AND ...')
9894
     * @param   int     $noextrafields  0=Default to load extrafields, 1=No extrafields
9895
     * @return  int                     Return integer <0 if KO, 0 if not found, >0 if OK
9896
     */
9897
    public function fetchCommon($id, $ref = null, $morewhere = '', $noextrafields = 0)
9898
    {
9899
        if (empty($id) && empty($ref) && empty($morewhere)) {
9900
            return -1;
9901
        }
9902
9903
        $fieldlist = $this->getFieldList('t');
9904
        if (empty($fieldlist)) {
9905
            return 0;
9906
        }
9907
9908
        $sql = "SELECT " . $fieldlist;
9909
        $sql .= " FROM " . $this->db->prefix() . $this->table_element . ' as t';
9910
9911
        if (!empty($id)) {
9912
            $sql .= ' WHERE t.rowid = ' . ((int) $id);
9913
        } elseif (!empty($ref)) {
9914
            $sql .= " WHERE t.ref = '" . $this->db->escape($ref) . "'";
9915
        } else {
9916
            $sql .= ' WHERE 1 = 1'; // usage with empty id and empty ref is very rare
9917
        }
9918
        if (empty($id) && isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
9919
            $sql .= ' AND t.entity IN (' . getEntity($this->element) . ')';
9920
        }
9921
        if ($morewhere) {
9922
            $sql .= $morewhere;
9923
        }
9924
        $sql .= ' LIMIT 1'; // This is a fetch, to be certain to get only one record
9925
9926
        $res = $this->db->query($sql);
9927
        if ($res) {
9928
            $obj = $this->db->fetch_object($res);
9929
            if ($obj) {
9930
                $this->setVarsFromFetchObj($obj);
9931
9932
                // Retrieve all extrafield
9933
                // fetch optionals attributes and labels
9934
                if (empty($noextrafields)) {
9935
                    $result = $this->fetch_optionals();
9936
                    if ($result < 0) {
9937
                        $this->error = $this->db->lasterror();
9938
                        $this->errors[] = $this->error;
9939
                        return -4;
9940
                    }
9941
                }
9942
9943
                return $this->id;
9944
            } else {
9945
                return 0;
9946
            }
9947
        } else {
9948
            $this->error = $this->db->lasterror();
9949
            $this->errors[] = $this->error;
9950
            return -1;
9951
        }
9952
    }
9953
9954
    /**
9955
     * Load object in memory from the database
9956
     *
9957
     * @param   string  $morewhere      More SQL filters (' AND ...')
9958
     * @param   int     $noextrafields  0=Default to load extrafields, 1=No extrafields
9959
     * @return  int                     Return integer <0 if KO, 0 if not found, >0 if OK
9960
     */
9961
    public function fetchLinesCommon($morewhere = '', $noextrafields = 0)
9962
    {
9963
        $objectlineclassname = get_class($this) . 'Line';
9964
        if (!class_exists($objectlineclassname)) {
9965
            $this->error = 'Error, class ' . $objectlineclassname . ' not found during call of fetchLinesCommon';
9966
            return -1;
9967
        }
9968
9969
        $objectline = new $objectlineclassname($this->db);
9970
9971
        $sql = "SELECT " . $objectline->getFieldList('l');
9972
        $sql .= " FROM " . $this->db->prefix() . $objectline->table_element . " as l";
9973
        $sql .= " WHERE l.fk_" . $this->db->escape($this->element) . " = " . ((int) $this->id);
9974
        if ($morewhere) {
9975
            $sql .= $morewhere;
9976
        }
9977
        if (isset($objectline->fields['position'])) {
9978
            $sql .= $this->db->order('position', 'ASC');
9979
        }
9980
9981
        $resql = $this->db->query($sql);
9982
        if ($resql) {
9983
            $num_rows = $this->db->num_rows($resql);
9984
            $i = 0;
9985
            while ($i < $num_rows) {
9986
                $obj = $this->db->fetch_object($resql);
9987
                if ($obj) {
9988
                    $newline = new $objectlineclassname($this->db);
9989
                    $newline->setVarsFromFetchObj($obj);
9990
9991
                    // Note: extrafields load of line not yet supported
9992
                    /*
9993
                    if (empty($noextrafields)) {
9994
                        // Load extrafields of line
9995
                    }*/
9996
9997
                    $this->lines[] = $newline;
9998
                }
9999
                $i++;
10000
            }
10001
10002
            return 1;
10003
        } else {
10004
            $this->error = $this->db->lasterror();
10005
            $this->errors[] = $this->error;
10006
            return -1;
10007
        }
10008
    }
10009
10010
    /**
10011
     * Update object into database
10012
     *
10013
     * @param  User $user       User that modifies
10014
     * @param  int  $notrigger  0=launch triggers after, 1=disable triggers
10015
     * @return int              Return integer <0 if KO, >0 if OK
10016
     */
10017
    public function updateCommon(User $user, $notrigger = 0)
10018
    {
10019
        dol_syslog(get_class($this) . "::updateCommon update", LOG_DEBUG);
10020
10021
        $error = 0;
10022
10023
        $now = dol_now();
10024
10025
        // $this->oldcopy should have been set by the caller of update
10026
        //if (empty($this->oldcopy)) {
10027
        //  $this->oldcopy = dol_clone($this);
10028
        //}
10029
10030
        $fieldvalues = $this->setSaveQuery();
10031
10032
        // Note: Here, $fieldvalues contains same keys (or less) that are inside ->fields
10033
10034
        if (array_key_exists('date_modification', $fieldvalues) && empty($fieldvalues['date_modification'])) {
10035
            $fieldvalues['date_modification'] = $this->db->idate($now);
10036
        }
10037
        if (array_key_exists('fk_user_modif', $fieldvalues) && !($fieldvalues['fk_user_modif'] > 0)) {
10038
            $fieldvalues['fk_user_modif'] = $user->id;
10039
        }
10040
        if (array_key_exists('user_modification_id', $fieldvalues) && !($fieldvalues['user_modification_id'] > 0)) {
10041
            $fieldvalues['user_modification_id'] = $user->id;
10042
        }
10043
        if (array_key_exists('ref', $fieldvalues)) {
10044
            $fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
10045
        }
10046
10047
        unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into update.
10048
10049
        // Add quotes and escape on fields with type string
10050
        $keys = array();
10051
        $values = array();
10052
        $tmp = array();
10053
        foreach ($fieldvalues as $k => $v) {
10054
            $keys[$k] = $k;
10055
            $value = $this->fields[$k];
10056
            // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
10057
            $values[$k] = $this->quote($v, $value);
10058
            // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
10059
            $tmp[] = $k . '=' . $this->quote($v, $this->fields[$k]);
10060
        }
10061
10062
        // Clean and check mandatory fields
10063
        foreach ($keys as $key) {
10064
            if (preg_match('/^integer:/i', $this->fields[$key]['type']) && $values[$key] == '-1') {
10065
                $values[$key] = ''; // This is an implicit foreign key field
10066
            }
10067
            if (!empty($this->fields[$key]['foreignkey']) && $values[$key] == '-1') {
10068
                $values[$key] = ''; // This is an explicit foreign key field
10069
            }
10070
10071
            //var_dump($key.'-'.$values[$key].'-'.($this->fields[$key]['notnull'] == 1));
10072
            /*
10073
            if ($this->fields[$key]['notnull'] == 1 && empty($values[$key]))
10074
            {
10075
                $error++;
10076
                $this->errors[]=$langs->trans("ErrorFieldRequired", $this->fields[$key]['label']);
10077
            }*/
10078
        }
10079
10080
        $sql = 'UPDATE ' . $this->db->prefix() . $this->table_element . ' SET ' . implode(', ', $tmp) . ' WHERE rowid=' . ((int) $this->id);
10081
10082
        $this->db->begin();
10083
10084
        if (!$error) {
10085
            $res = $this->db->query($sql);
10086
            if (!$res) {
10087
                $error++;
10088
                $this->errors[] = $this->db->lasterror();
10089
            }
10090
        }
10091
10092
        // Update extrafield
10093
        if (!$error) {
10094
            $result = $this->insertExtraFields();   // This delete and reinsert extrafields
10095
            if ($result < 0) {
10096
                $error++;
10097
            }
10098
        }
10099
10100
        // Triggers
10101
        if (!$error && !$notrigger) {
10102
            // Call triggers
10103
            $result = $this->call_trigger(strtoupper(get_class($this)) . '_MODIFY', $user);
10104
            if ($result < 0) {
10105
                $error++;
10106
            } //Do also here what you must do to rollback action if trigger fail
10107
            // End call triggers
10108
        }
10109
10110
        // Commit or rollback
10111
        if ($error) {
10112
            $this->db->rollback();
10113
            return -1;
10114
        } else {
10115
            $this->db->commit();
10116
            return $this->id;
10117
        }
10118
    }
10119
10120
    /**
10121
     * Delete object in database
10122
     *
10123
     * @param   User    $user                   User that deletes
10124
     * @param   int     $notrigger              0=launch triggers after, 1=disable triggers
10125
     * @param   int     $forcechilddeletion     0=no, 1=Force deletion of children
10126
     * @return  int                             Return integer <0 if KO, 0=Nothing done because object has child, >0 if OK
10127
     */
10128
    public function deleteCommon(User $user, $notrigger = 0, $forcechilddeletion = 0)
10129
    {
10130
        dol_syslog(get_class($this) . "::deleteCommon delete", LOG_DEBUG);
10131
10132
        $error = 0;
10133
10134
        $this->db->begin();
10135
10136
        if ($forcechilddeletion) {  // Force also delete of childtables that should lock deletion in standard case when option force is off
10137
            foreach ($this->childtables as $table) {
10138
                $sql = "DELETE FROM " . $this->db->prefix() . $table . " WHERE " . $this->fk_element . " = " . ((int) $this->id);
10139
                $resql = $this->db->query($sql);
10140
                if (!$resql) {
10141
                    $this->error = $this->db->lasterror();
10142
                    $this->errors[] = $this->error;
10143
                    $this->db->rollback();
10144
                    return -1;
10145
                }
10146
            }
10147
        } elseif (!empty($this->childtables)) { // If object has children linked with a foreign key field, we check all child tables.
10148
            $objectisused = $this->isObjectUsed($this->id);
10149
            if (!empty($objectisused)) {
10150
                dol_syslog(get_class($this) . "::deleteCommon Can't delete record as it has some child", LOG_WARNING);
10151
                $this->error = 'ErrorRecordHasChildren';
10152
                $this->errors[] = $this->error;
10153
                $this->db->rollback();
10154
                return 0;
10155
            }
10156
        }
10157
10158
        // Delete cascade first
10159
        if (is_array($this->childtablesoncascade) && !empty($this->childtablesoncascade)) {
10160
            foreach ($this->childtablesoncascade as $tabletodelete) {
10161
                $deleteFromObject = explode(':', $tabletodelete, 4);
10162
                if (count($deleteFromObject) >= 2) {
10163
                    $className = str_replace('@', '', $deleteFromObject[0]);
10164
                    $filePath = $deleteFromObject[1];
10165
                    $columnName = $deleteFromObject[2];
10166
                    $filter = '';
10167
                    if (!empty($deleteFromObject[3])) {
10168
                        $filter = $deleteFromObject[3];
10169
                    }
10170
                    if (dol_include_once($filePath)) {
10171
                        $childObject = new $className($this->db);
10172
                        if (method_exists($childObject, 'deleteByParentField')) {
10173
                            $result = $childObject->deleteByParentField($this->id, $columnName, $filter);
10174
                            if ($result < 0) {
10175
                                $error++;
10176
                                $this->errors[] = $childObject->error;
10177
                                break;
10178
                            }
10179
                        } else {
10180
                            $error++;
10181
                            $this->errors[] = "You defined a cascade delete on an object $childObject but there is no method deleteByParentField for it";
10182
                            break;
10183
                        }
10184
                    } else {
10185
                        $error++;
10186
                        $this->errors[] = 'Cannot include child class file ' . $filePath;
10187
                        break;
10188
                    }
10189
                } else {
10190
                    // Delete record in child table
10191
                    $sql = "DELETE FROM " . $this->db->prefix() . $tabletodelete . " WHERE " . $this->fk_element . " = " . ((int) $this->id);
10192
10193
                    $resql = $this->db->query($sql);
10194
                    if (!$resql) {
10195
                        $error++;
10196
                        $this->error = $this->db->lasterror();
10197
                        $this->errors[] = $this->error;
10198
                        break;
10199
                    }
10200
                }
10201
            }
10202
        }
10203
10204
        if (!$error) {
10205
            if (!$notrigger) {
10206
                // Call triggers
10207
                $result = $this->call_trigger(strtoupper(get_class($this)) . '_DELETE', $user);
10208
                if ($result < 0) {
10209
                    $error++;
10210
                } // Do also here what you must do to rollback action if trigger fail
10211
                // End call triggers
10212
            }
10213
        }
10214
10215
        // Delete llx_ecm_files
10216
        if (!$error) {
10217
            $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
10218
            if (!$res) {
10219
                $error++;
10220
            }
10221
        }
10222
10223
        // Delete linked object
10224
        $res = $this->deleteObjectLinked();
10225
        if ($res < 0) {
10226
            $error++;
10227
        }
10228
10229
        if (!$error && !empty($this->isextrafieldmanaged)) {
10230
            $result = $this->deleteExtraFields();
10231
            if ($result < 0) {
10232
                $error++;
10233
            }
10234
        }
10235
10236
        if (!$error) {
10237
            $sql = 'DELETE FROM ' . $this->db->prefix() . $this->table_element . ' WHERE rowid=' . ((int) $this->id);
10238
10239
            $resql = $this->db->query($sql);
10240
            if (!$resql) {
10241
                $error++;
10242
                $this->errors[] = $this->db->lasterror();
10243
            }
10244
        }
10245
10246
        // Commit or rollback
10247
        if ($error) {
10248
            $this->db->rollback();
10249
            return -1;
10250
        } else {
10251
            $this->db->commit();
10252
            return 1;
10253
        }
10254
    }
10255
10256
    /**
10257
     * Delete all child object from a parent ID
10258
     *
10259
     * @param   int         $parentId       Parent Id
10260
     * @param   string      $parentField    Name of Foreign key parent column
10261
     * @param   string      $filter         Filter as an Universal Search string.
10262
     *                                      Example: '((client:=:1) OR ((client:>=:2) AND (client:<=:3))) AND (client:!=:8) AND (nom:like:'a%')'
10263
     * @param   string      $filtermode     No more used
10264
     * @return  int                         Return integer <0 if KO, >0 if OK
10265
     * @throws  Exception
10266
     */
10267
    public function deleteByParentField($parentId = 0, $parentField = '', $filter = '', $filtermode = "AND")
10268
    {
10269
        global $user;
10270
10271
        $error = 0;
10272
        $deleted = 0;
10273
10274
        if (!empty($parentId) && !empty($parentField)) {
10275
            $this->db->begin();
10276
10277
            $sql = "SELECT rowid FROM " . $this->db->prefix() . $this->table_element;
10278
            $sql .= " WHERE " . $this->db->sanitize($parentField) . " = " . (int) $parentId;
10279
10280
            // Manage filter
10281
            $errormessage = '';
10282
            $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
10283
            if ($errormessage) {
10284
                $this->errors[] = $errormessage;
10285
                dol_syslog(__METHOD__ . ' ' . implode(',', $this->errors), LOG_ERR);
10286
                return -1;
10287
            }
10288
10289
            $resql = $this->db->query($sql);
10290
            if (!$resql) {
10291
                $this->errors[] = $this->db->lasterror();
10292
                $error++;
10293
            } else {
10294
                while ($obj = $this->db->fetch_object($resql)) {
10295
                    $result = $this->fetch($obj->rowid);    // @phpstan-ignore-line
10296
                    if ($result < 0) {
10297
                        $error++;
10298
                        $this->errors[] = $this->error;
10299
                    } else {
10300
                        $result = $this->delete($user); // @phpstan-ignore-line
0 ignored issues
show
Bug introduced by
The method delete() does not exist on DoliCore\Base\GenericDocument. Did you maybe mean delete_resource()? ( Ignorable by Annotation )

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

10300
                        /** @scrutinizer ignore-call */ 
10301
                        $result = $this->delete($user); // @phpstan-ignore-line

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

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

Loading history...
10301
                        if ($result < 0) {
10302
                            $error++;
10303
                            $this->errors[] = $this->error;
10304
                        } else {
10305
                            $deleted++;
10306
                        }
10307
                    }
10308
                }
10309
            }
10310
10311
            if (empty($error)) {
10312
                $this->db->commit();
10313
                return $deleted;
10314
            } else {
10315
                $this->error = implode(', ', $this->errors);
10316
                $this->db->rollback();
10317
                return $error * -1;
10318
            }
10319
        }
10320
10321
        return $deleted;
10322
    }
10323
10324
    /**
10325
     *  Delete a line of object in database
10326
     *
10327
     *  @param  User    $user       User that delete
10328
     *  @param  int     $idline     Id of line to delete
10329
     *  @param  int     $notrigger  0=launch triggers after, 1=disable triggers
10330
     *  @return int                 >0 if OK, <0 if KO
10331
     */
10332
    public function deleteLineCommon(User $user, $idline, $notrigger = 0)
10333
    {
10334
        $error = 0;
10335
10336
        $tmpforobjectclass = get_class($this);
10337
        $tmpforobjectlineclass = ucfirst($tmpforobjectclass) . 'Line';
10338
10339
        $this->db->begin();
10340
10341
        // Call trigger
10342
        $result = $this->call_trigger('LINE' . strtoupper($tmpforobjectclass) . '_DELETE', $user);
10343
        if ($result < 0) {
10344
            $error++;
10345
        }
10346
        // End call triggers
10347
10348
        if (empty($error)) {
10349
            $sql = "DELETE FROM " . $this->db->prefix() . $this->table_element_line;
10350
            $sql .= " WHERE rowid = " . ((int) $idline);
10351
10352
            $resql = $this->db->query($sql);
10353
            if (!$resql) {
10354
                $this->error = "Error " . $this->db->lasterror();
10355
                $error++;
10356
            }
10357
        }
10358
10359
        if (empty($error)) {
10360
            // Remove extrafields
10361
            $tmpobjectline = new $tmpforobjectlineclass($this->db);
10362
            if (!isset($tmpobjectline->isextrafieldmanaged) || !empty($tmpobjectline->isextrafieldmanaged)) {
10363
                $tmpobjectline->id = $idline;
10364
                $result = $tmpobjectline->deleteExtraFields();
10365
                if ($result < 0) {
10366
                    $error++;
10367
                    $this->error = "Error " . get_class($this) . "::deleteLineCommon deleteExtraFields error -4 " . $tmpobjectline->error;
10368
                }
10369
            }
10370
        }
10371
10372
        if (empty($error)) {
10373
            $this->db->commit();
10374
            return 1;
10375
        } else {
10376
            dol_syslog(get_class($this) . "::deleteLineCommon ERROR:" . $this->error, LOG_ERR);
10377
            $this->db->rollback();
10378
            return -1;
10379
        }
10380
    }
10381
10382
10383
    /**
10384
     *  Set to a status
10385
     *
10386
     *  @param  User    $user           Object user that modify
10387
     *  @param  int     $status         New status to set (often a constant like self::STATUS_XXX)
10388
     *  @param  int     $notrigger      1=Does not execute triggers, 0=Execute triggers
10389
     *  @param  string  $triggercode    Trigger code to use
10390
     *  @return int                     Return integer <0 if KO, >0 if OK
10391
     */
10392
    public function setStatusCommon($user, $status, $notrigger = 0, $triggercode = '')
10393
    {
10394
        $error = 0;
10395
10396
        $this->db->begin();
10397
10398
        $statusfield = 'status';
10399
        if (in_array($this->element, array('don', 'donation', 'shipping'))) {
10400
            $statusfield = 'fk_statut';
10401
        }
10402
10403
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
10404
        $sql .= " SET " . $statusfield . " = " . ((int) $status);
10405
        $sql .= " WHERE rowid = " . ((int) $this->id);
10406
10407
        if ($this->db->query($sql)) {
10408
            if (!$error) {
10409
                $this->oldcopy = clone $this;
0 ignored issues
show
Documentation Bug introduced by
It seems like clone $this of type DoliCore\Base\GenericDocument is incompatible with the declared type CommonObject of property $oldcopy.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
10410
            }
10411
10412
            if (!$error && !$notrigger) {
10413
                // Call trigger
10414
                $result = $this->call_trigger($triggercode, $user);
10415
                if ($result < 0) {
10416
                    $error++;
10417
                }
10418
            }
10419
10420
            if (!$error) {
10421
                $this->status = $status;
10422
                $this->db->commit();
10423
                return 1;
10424
            } else {
10425
                $this->db->rollback();
10426
                return -1;
10427
            }
10428
        } else {
10429
            $this->error = $this->db->error();
10430
            $this->db->rollback();
10431
            return -1;
10432
        }
10433
    }
10434
10435
    /**
10436
     *  Set to a signed status
10437
     *
10438
     *  @param  User    $user           Object user that modify
10439
     *  @param  int     $status         New status to set (often a constant like self::STATUS_XXX)
10440
     *  @param  int     $notrigger      1=Does not execute triggers, 0=Execute triggers
10441
     *  @param  string  $triggercode    Trigger code to use
10442
     *  @return int                     Return integer <0 if KO, >0 if OK
10443
     */
10444
    public function setSignedStatusCommon($user, $status, $notrigger = 0, $triggercode = '')
10445
    {
10446
        $error = 0;
10447
10448
        $this->db->begin();
10449
10450
        $statusfield = 'signed_status';
10451
10452
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
10453
        $sql .= " SET " . $statusfield . " = " . ((int) $status);
10454
        $sql .= " WHERE rowid = " . ((int) $this->id);
10455
10456
        if ($this->db->query($sql)) {
10457
            if (!$error) {
10458
                $this->oldcopy = clone $this;
0 ignored issues
show
Documentation Bug introduced by
It seems like clone $this of type DoliCore\Base\GenericDocument is incompatible with the declared type CommonObject of property $oldcopy.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
10459
            }
10460
10461
            if (!$error && !$notrigger) {
10462
                // Call trigger
10463
                $result = $this->call_trigger($triggercode, $user);
10464
                if ($result < 0) {
10465
                    $error++;
10466
                }
10467
            }
10468
10469
            if (!$error) {
10470
                $this->status = $status;
10471
                $this->db->commit();
10472
                return 1;
10473
            } else {
10474
                $this->db->rollback();
10475
                return -1;
10476
            }
10477
        } else {
10478
            $this->error = $this->db->error();
10479
            $this->db->rollback();
10480
            return -1;
10481
        }
10482
    }
10483
10484
10485
    /**
10486
     * Initialise object with example values
10487
     * Id must be 0 if object instance is a specimen
10488
     *
10489
     * @return int
10490
     */
10491
    public function initAsSpecimenCommon()
10492
    {
10493
        global $user;
10494
10495
        $this->id = 0;
10496
        $this->specimen = 1;
10497
        $fields = array(
10498
            'label' => 'This is label',
10499
            'ref' => 'ABCD1234',
10500
            'description' => 'This is a description',
10501
            'qty' => 123.12,
10502
            'note_public' => 'Public note',
10503
            'note_private' => 'Private note',
10504
            'date_creation' => (dol_now() - 3600 * 48),
10505
            'date_modification' => (dol_now() - 3600 * 24),
10506
            'fk_user_creat' => $user->id,
10507
            'fk_user_modif' => $user->id,
10508
            'date' => dol_now(),
10509
        );
10510
        foreach ($fields as $key => $value) {
10511
            if (array_key_exists($key, $this->fields)) {
10512
                $this->{$key} = $value;     // @phpstan-ignore-line
10513
            }
10514
        }
10515
10516
        // Force values to default values when known
10517
        if (property_exists($this, 'fields')) {
10518
            foreach ($this->fields as $key => $value) {
10519
                // If fields are already set, do nothing
10520
                if (array_key_exists($key, $fields)) {
10521
                    continue;
10522
                }
10523
10524
                if (!empty($value['default'])) {
10525
                    $this->$key = $value['default'];
10526
                }
10527
            }
10528
        }
10529
10530
        return 1;
10531
    }
10532
10533
10534
    /* Part for comments */
10535
10536
    /**
10537
     * Load comments linked with current task
10538
     *
10539
     * @return int<0,max>|-1        Returns the number of comments if OK, -1 if error
10540
     */
10541
    public function fetchComments()
10542
    {
10543
        require_once DOL_DOCUMENT_ROOT . '/core/class/comment.class.php';
10544
10545
        $comment = new Comment($this->db);
10546
        $result = $comment->fetchAllFor($this->element, $this->id);
10547
        if ($result < 0) {
10548
            $this->errors = array_merge($this->errors, $comment->errors);
10549
            return -1;
10550
        } else {
10551
            $this->comments = $comment->comments;
10552
        }
10553
        return count($this->comments);
10554
    }
10555
10556
    /**
10557
     * Return nb comments already posted
10558
     *
10559
     * @return int
10560
     */
10561
    public function getNbComments()
10562
    {
10563
        return count($this->comments);
10564
    }
10565
10566
    /**
10567
     * Trim object parameters
10568
     *
10569
     * @param string[] $parameters array of parameters to trim
10570
     * @return void
10571
     */
10572
    public function trimParameters($parameters)
10573
    {
10574
        if (!is_array($parameters)) {
10575
            return;
10576
        }
10577
        foreach ($parameters as $parameter) {
10578
            if (isset($this->$parameter)) {
10579
                $this->$parameter = trim($this->$parameter);
10580
            }
10581
        }
10582
    }
10583
10584
    /* Part for categories/tags */
10585
10586
    /**
10587
     * Sets object to given categories.
10588
     *
10589
     * Deletes object from existing categories not supplied.
10590
     * Adds it to non existing supplied categories.
10591
     * Existing categories are left untouch.
10592
     *
10593
     * @param   string      $type_categ     Category type ('customer', 'supplier', 'website_page', ...)
10594
     * @return  int                         Array of category objects or < 0 if KO
10595
     */
10596
    public function getCategoriesCommon($type_categ)
10597
    {
10598
        require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
10599
10600
        // Get current categories
10601
        $c = new Categorie($this->db);
10602
        $existing = $c->containing($this->id, $type_categ, 'id');
10603
10604
        return $existing;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $existing also could return the type Categorie[] which is incompatible with the documented return type integer.
Loading history...
10605
    }
10606
10607
    /**
10608
     * Sets object to given categories.
10609
     *
10610
     * Adds it to non existing supplied categories.
10611
     * Deletes object from existing categories not supplied (if remove_existing==true).
10612
     * Existing categories are left untouch.
10613
     *
10614
     * @param   int[]|int   $categories         Category ID or array of Categories IDs
10615
     * @param   string      $type_categ         Category type ('customer', 'supplier', 'website_page', ...) defined into const class Categorie type
10616
     * @param   boolean     $remove_existing    True: Remove existings categories from Object if not supplies by $categories, False: let them
10617
     * @return  int                             Return integer <0 if KO, >0 if OK
10618
     */
10619
    public function setCategoriesCommon($categories, $type_categ = '', $remove_existing = true)
10620
    {
10621
        // Handle single category
10622
        if (!is_array($categories)) {
10623
            $categories = array($categories);
10624
        }
10625
10626
        dol_syslog(get_class($this) . "::setCategoriesCommon Object Id:" . $this->id . ' type_categ:' . $type_categ . ' nb tag add:' . count($categories), LOG_DEBUG);
10627
10628
        require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
10629
10630
        if (empty($type_categ)) {
10631
            dol_syslog(__METHOD__ . ': Type ' . $type_categ . 'is an unknown category type. Done nothing.', LOG_ERR);
10632
            return -1;
10633
        }
10634
10635
        // Get current categories
10636
        $c = new Categorie($this->db);
10637
        $existing = $c->containing($this->id, $type_categ, 'id');
10638
        if ($remove_existing) {
10639
            // Diff
10640
            if (is_array($existing)) {
10641
                $to_del = array_diff($existing, $categories);
10642
                $to_add = array_diff($categories, $existing);
10643
            } else {
10644
                $to_del = array(); // Nothing to delete
10645
                $to_add = $categories;
10646
            }
10647
        } else {
10648
            $to_del = array(); // Nothing to delete
10649
            $to_add = array_diff($categories, $existing);
10650
        }
10651
10652
        $error = 0;
10653
        $ok = 0;
10654
10655
        // Process
10656
        foreach ($to_del as $del) {
10657
            if ($c->fetch($del) > 0) {
10658
                $result = $c->del_type($this, $type_categ);
10659
                if ($result < 0) {
10660
                    $error++;
10661
                    $this->error = $c->error;
10662
                    $this->errors = $c->errors;
10663
                    break;
10664
                } else {
10665
                    $ok += $result;
10666
                }
10667
            }
10668
        }
10669
        foreach ($to_add as $add) {
10670
            if ($c->fetch($add) > 0) {
10671
                $result = $c->add_type($this, $type_categ);
10672
                if ($result < 0) {
10673
                    $error++;
10674
                    $this->error = $c->error;
10675
                    $this->errors = $c->errors;
10676
                    break;
10677
                } else {
10678
                    $ok += $result;
10679
                }
10680
            }
10681
        }
10682
10683
        return $error ? (-1 * $error) : $ok;
10684
    }
10685
10686
    /**
10687
     * Copy related categories to another object
10688
     *
10689
     * @param  int      $fromId Id object source
10690
     * @param  int      $toId   Id object cible
10691
     * @param  string   $type   Type of category ('product', ...)
10692
     * @return int      Return integer < 0 if error, > 0 if ok
10693
     */
10694
    public function cloneCategories($fromId, $toId, $type = '')
10695
    {
10696
        $this->db->begin();
10697
10698
        if (empty($type)) {
10699
            $type = $this->table_element;
10700
        }
10701
10702
        require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
10703
        $categorystatic = new Categorie($this->db);
10704
10705
        $sql = "INSERT INTO " . $this->db->prefix() . "categorie_" . (empty($categorystatic->MAP_CAT_TABLE[$type]) ? $type : $categorystatic->MAP_CAT_TABLE[$type]) . " (fk_categorie, fk_product)";
10706
        $sql .= " SELECT fk_categorie, $toId FROM " . $this->db->prefix() . "categorie_" . (empty($categorystatic->MAP_CAT_TABLE[$type]) ? $type : $categorystatic->MAP_CAT_TABLE[$type]);
10707
        $sql .= " WHERE fk_product = " . ((int) $fromId);
10708
10709
        if (!$this->db->query($sql)) {
10710
            $this->error = $this->db->lasterror();
10711
            $this->db->rollback();
10712
            return -1;
10713
        }
10714
10715
        $this->db->commit();
10716
        return 1;
10717
    }
10718
10719
    /**
10720
     * Delete related files of object in database
10721
     *
10722
     * @param   integer     $mode       0=Use path to find record, 1=Use src_object_xxx fields (Mode 1 is recommended for new objects)
10723
     * @return  bool                    True if OK, False if KO
10724
     */
10725
    public function deleteEcmFiles($mode = 0)
10726
    {
10727
        global $conf;
10728
10729
        $this->db->begin();
10730
10731
        // Delete in database with mode 0
10732
        if ($mode == 0) {
10733
            switch ($this->element) {
10734
                case 'propal':
10735
                    $element = 'propale';
10736
                    break;
10737
                case 'product':
10738
                    $element = 'produit';
10739
                    break;
10740
                case 'order_supplier':
10741
                    $element = 'fournisseur/commande';
10742
                    break;
10743
                case 'invoice_supplier':
10744
                    // Special cases that need to use get_exdir to get real dir of object
10745
                    // In future, all object should use this to define path of documents.
10746
                    $element = 'fournisseur/facture/' . get_exdir($this->id, 2, 0, 1, $this, 'invoice_supplier');
10747
                    break;
10748
                case 'shipping':
10749
                    $element = 'expedition/sending';
10750
                    break;
10751
                case 'task':
10752
                case 'project_task':
10753
                    require_once DOL_DOCUMENT_ROOT . '/projet/class/task.class.php';
10754
10755
                    $project_result = $this->fetch_projet();
10756
                    if ($project_result >= 0) {
10757
                        $element = 'projet/' . dol_sanitizeFileName($this->project->ref) . '/';
10758
                    }
10759
                // no break
10760
                default:
10761
                    $element = $this->element;
10762
            }
10763
10764
            // Delete ecm_files_extrafields with mode 0 (using name)
10765
            $sql = "DELETE FROM " . $this->db->prefix() . "ecm_files_extrafields WHERE fk_object IN (";
10766
            $sql .= " SELECT rowid FROM " . $this->db->prefix() . "ecm_files WHERE filename LIKE '" . $this->db->escape($this->ref) . "%'";
10767
            $sql .= " AND filepath = '" . $this->db->escape($element) . "/" . $this->db->escape($this->ref) . "' AND entity = " . ((int) $conf->entity); // No need of getEntity here
10768
            $sql .= ")";
10769
10770
            if (!$this->db->query($sql)) {
10771
                $this->error = $this->db->lasterror();
10772
                $this->db->rollback();
10773
                return false;
10774
            }
10775
10776
            // Delete ecm_files with mode 0 (using name)
10777
            $sql = "DELETE FROM " . $this->db->prefix() . "ecm_files";
10778
            $sql .= " WHERE filename LIKE '" . $this->db->escape($this->ref) . "%'";
10779
            $sql .= " AND filepath = '" . $this->db->escape($element) . "/" . $this->db->escape($this->ref) . "' AND entity = " . ((int) $conf->entity); // No need of getEntity here
10780
10781
            if (!$this->db->query($sql)) {
10782
                $this->error = $this->db->lasterror();
10783
                $this->db->rollback();
10784
                return false;
10785
            }
10786
        }
10787
10788
        // Delete in database with mode 1
10789
        if ($mode == 1) {
10790
            $sql = 'DELETE FROM ' . $this->db->prefix() . "ecm_files_extrafields";
10791
            $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) . ")";
10792
            $resql = $this->db->query($sql);
10793
            if (!$resql) {
10794
                $this->error = $this->db->lasterror();
10795
                $this->db->rollback();
10796
                return false;
10797
            }
10798
10799
            $sql = 'DELETE FROM ' . $this->db->prefix() . "ecm_files";
10800
            $sql .= " WHERE src_object_type = '" . $this->db->escape($this->table_element . (empty($this->module) ? "" : "@" . $this->module)) . "' AND src_object_id = " . ((int) $this->id);
10801
            $resql = $this->db->query($sql);
10802
            if (!$resql) {
10803
                $this->error = $this->db->lasterror();
10804
                $this->db->rollback();
10805
                return false;
10806
            }
10807
        }
10808
10809
        $this->db->commit();
10810
        return true;
10811
    }
10812
}
10813