Issues (2497)

Dolibarr/Core/Base/GenericDocument.php (43 issues)

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 Commande;
49
use CommandeFournisseur;
50
use CommandeFournisseurLigne;
51
use Comment;
52
use CommonObject;
53
use CommonObjectLine;
54
use Contact;
55
use DiscountAbsolute;
56
use DolEditor;
57
use DoliCore\Lib\ExtraFields;
58
use DoliCore\Model\Interfaces;
59
use DoliDB;
60
use DoliModules\Billing\Model\OrderLine;
61
use DoliModules\Category\Model\Categorie;
62
use DoliModules\Company\Model\Company;
63
use EcmFiles;
64
use Facture;
65
use FactureFournisseur;
66
use FactureLigne;
67
use DoliCore\Form\Form;
68
use MultiCurrency;
69
use Product;
70
use ProductFournisseur;
71
use Project;
72
use Propal;
73
use PropaleLigne;
74
use Societe;
75
use SupplierInvoiceLine;
76
use SupplierProposal;
77
use SupplierProposalLine;
78
use Translate;
0 ignored issues
show
This use statement conflicts with another class in this namespace, DoliCore\Base\Translate. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

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

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...
1856
1857
            // Use first price level if level not defined for third party
1858
            if (getDolGlobalString('PRODUIT_MULTIPRICES') && empty($this->thirdparty->price_level)) {
1859
                $this->thirdparty->price_level = 1;
1860
            }
1861
1862
            return $result;
1863
        } else {
1864
            return -1;
1865
        }
1866
    }
1867
1868
1869
    /**
1870
     * Looks for an object with ref matching the wildcard provided
1871
     * It does only work when $this->table_ref_field is set
1872
     *
1873
     * @param   string  $ref    Wildcard
1874
     * @return  int             >1 = OK, 0 = Not found or table_ref_field not defined, <0 = KO
1875
     */
1876
    public function fetchOneLike($ref)
1877
    {
1878
        if (!$this->table_ref_field) {
1879
            return 0;
1880
        }
1881
1882
        $sql = "SELECT rowid FROM " . $this->db->prefix() . $this->table_element;
1883
        $sql .= " WHERE " . $this->table_ref_field . " LIKE '" . $this->db->escape($ref) . "'"; // no escapeforlike here
1884
        $sql .= " LIMIT 1";
1885
1886
        $query = $this->db->query($sql);
1887
1888
        if (!$this->db->num_rows($query)) {
1889
            return 0;
1890
        }
1891
1892
        $result = $this->db->fetch_object($query);
1893
1894
        if (method_exists($this, 'fetch')) {
1895
            return $this->fetch($result->rowid);
1896
        } else {
1897
            $this->error = 'Fetch method not implemented on ' . get_class($this);
1898
            dol_syslog(get_class($this) . '::fetchOneLike Error=' . $this->error, LOG_ERR);
1899
            array_push($this->errors, $this->error);
1900
            return -1;
1901
        }
1902
    }
1903
1904
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1905
    /**
1906
     *  Load data for barcode into properties ->barcode_type*
1907
     *  Properties ->barcode_type that is id of barcode. Type is used to find other properties, but
1908
     *  if it is not defined, ->element must be defined to know default barcode type.
1909
     *
1910
     *  @return     int         Return integer <0 if KO, 0 if can't guess type of barcode (ISBN, EAN13...), >0 if OK (all barcode properties loaded)
1911
     */
1912
    public function fetch_barcode()
1913
    {
1914
        // phpcs:enable
1915
        global $conf;
1916
1917
        dol_syslog(get_class($this) . '::fetch_barcode this->element=' . $this->element . ' this->barcode_type=' . $this->barcode_type);
1918
1919
        $idtype = $this->barcode_type;
1920
        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
1921
            if ($this->element == 'product' && getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE')) {
1922
                $idtype = getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE');
1923
            } elseif ($this->element == 'societe') {
1924
                $idtype = getDolGlobalString('GENBARCODE_BARCODETYPE_THIRDPARTY');
1925
            } else {
1926
                dol_syslog('Call fetch_barcode with barcode_type not defined and cannot be guessed', LOG_WARNING);
1927
            }
1928
        }
1929
1930
        if ($idtype > 0) {
1931
            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
1932
                $sql = "SELECT rowid, code, libelle as label, coder";
1933
                $sql .= " FROM " . $this->db->prefix() . "c_barcode_type";
1934
                $sql .= " WHERE rowid = " . ((int) $idtype);
1935
                dol_syslog(get_class($this) . '::fetch_barcode', LOG_DEBUG);
1936
                $resql = $this->db->query($sql);
1937
                if ($resql) {
1938
                    $obj = $this->db->fetch_object($resql);
1939
                    $this->barcode_type       = $obj->rowid;
1940
                    $this->barcode_type_code  = $obj->code;
1941
                    $this->barcode_type_label = $obj->label;
1942
                    $this->barcode_type_coder = $obj->coder;
1943
                    return 1;
1944
                } else {
1945
                    dol_print_error($this->db);
1946
                    return -1;
1947
                }
1948
            }
1949
        }
1950
        return 0;
1951
    }
1952
1953
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1954
    /**
1955
     *      Load the project with id $this->fk_project into this->project
1956
     *
1957
     *      @return     int         Return integer <0 if KO, >=0 if OK
1958
     */
1959
    public function fetch_project()
1960
    {
1961
        // phpcs:enable
1962
        return $this->fetch_projet();
1963
    }
1964
1965
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1966
    /**
1967
     *      Load the project with id $this->fk_project into this->project
1968
     *
1969
     *      @return     int         Return integer <0 if KO, >=0 if OK
1970
     */
1971
    public function fetch_projet()
1972
    {
1973
        // phpcs:enable
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
2000
        if (empty($this->fk_product)) {
2001
            return 0;
2002
        }
2003
2004
        $product = new Product($this->db);
2005
        $result = $product->fetch($this->fk_product);
2006
2007
        $this->product = $product;
2008
        return $result;
2009
    }
2010
2011
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2012
    /**
2013
     *      Load the user with id $userid into this->user
2014
     *
2015
     *      @param  int     $userid         Id du contact
2016
     *      @return int                     Return integer <0 if KO, >0 if OK
2017
     */
2018
    public function fetch_user($userid)
2019
    {
2020
        // phpcs:enable
2021
        $user = new User($this->db);
2022
        $result = $user->fetch($userid);
2023
        $this->user = $user;
2024
        return $result;
2025
    }
2026
2027
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2028
    /**
2029
     *  Read linked origin object.
2030
     *  Set ->origin_object
2031
     *  Set also ->expedition or ->livraison or ->commandFournisseur (deprecated)
2032
     *
2033
     *  @return     void
2034
     */
2035
    public function fetch_origin()
2036
    {
2037
        // phpcs:enable
2038
        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

2038
        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...
2039
            $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

2039
            /** @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...
2040
        }
2041
        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

2041
        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...
2042
            $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

2042
            /** @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...
2043
        }
2044
        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

2044
        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...
2045
            $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

2045
            /** @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...
2046
        }
2047
2048
        $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

2048
        $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...
2049
2050
        $classname = ucfirst($origin);
2051
        $this->origin_object = new $classname($this->db);
2052
        $this->origin_object->fetch($this->origin_id);
2053
2054
        // TODO Remove this line
2055
        $this->$origin = $this->origin_object;
2056
    }
2057
2058
    /**
2059
     *  Load object from specific field
2060
     *
2061
     *  @param  string  $table      Table element or element line
2062
     *  @param  string  $field      Field selected
2063
     *  @param  string  $key        Import key
2064
     *  @param  string  $element    Element name
2065
     *  @return int|false           Return -1 or false if KO, >0 if OK
2066
     */
2067
    public function fetchObjectFrom($table, $field, $key, $element = null)
2068
    {
2069
        global $conf;
2070
2071
        $result = false;
2072
2073
        $sql = "SELECT rowid FROM " . $this->db->prefix() . $table;
2074
        $sql .= " WHERE " . $field . " = '" . $this->db->escape($key) . "'";
2075
        if (!empty($element)) {
2076
            $sql .= " AND entity IN (" . getEntity($element) . ")";
2077
        } else {
2078
            $sql .= " AND entity = " . ((int) $conf->entity);
2079
        }
2080
2081
        dol_syslog(get_class($this) . '::fetchObjectFrom', LOG_DEBUG);
2082
        $resql = $this->db->query($sql);
2083
        if ($resql) {
2084
            $obj = $this->db->fetch_object($resql);
2085
            // Test for avoid error -1
2086
            if ($obj) {
2087
                if (method_exists($this, 'fetch')) {
2088
                    $result = $this->fetch($obj->rowid);
2089
                } else {
2090
                    $this->error = 'fetch() method not implemented on ' . get_class($this);
2091
                    dol_syslog(get_class($this) . '::fetchOneLike Error=' . $this->error, LOG_ERR);
2092
                    array_push($this->errors, $this->error);
2093
                    $result = -1;
2094
                }
2095
            }
2096
        }
2097
2098
        return $result;
2099
    }
2100
2101
    /**
2102
     *  Getter generic. Load value from a specific field
2103
     *
2104
     *  @param  string  $table      Table of element or element line
2105
     *  @param  int     $id         Element id
2106
     *  @param  string  $field      Field selected
2107
     *  @return int                 Return integer <0 if KO, >0 if OK
2108
     */
2109
    public function getValueFrom($table, $id, $field)
2110
    {
2111
        $result = false;
2112
        if (!empty($id) && !empty($field) && !empty($table)) {
2113
            $sql = "SELECT " . $field . " FROM " . $this->db->prefix() . $table;
2114
            $sql .= " WHERE rowid = " . ((int) $id);
2115
2116
            dol_syslog(get_class($this) . '::getValueFrom', LOG_DEBUG);
2117
            $resql = $this->db->query($sql);
2118
            if ($resql) {
2119
                $row = $this->db->fetch_row($resql);
2120
                $result = $row[0];
2121
            }
2122
        }
2123
        return $result;
2124
    }
2125
2126
    /**
2127
     *  Setter generic. Update a specific field into database.
2128
     *  Warning: Trigger is run only if param trigkey is provided.
2129
     *
2130
     *  @param  string      $field          Field to update
2131
     *  @param  mixed       $value          New value
2132
     *  @param  string      $table          To force other table element or element line (should not be used)
2133
     *  @param  int         $id             To force other object id (should not be used)
2134
     *  @param  string      $format         Data format ('text', 'int', 'date'). 'text' is used if not defined
2135
     *  @param  string      $id_field       To force rowid field name. 'rowid' is used if not defined
2136
     *  @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'
2137
     *  @param  string      $trigkey        Trigger key to run (in most cases something like 'XXX_MODIFY')
2138
     *  @param  string      $fk_user_field  Name of field to save user id making change
2139
     *  @return int                         Return integer <0 if KO, >0 if OK
2140
     *  @see updateExtraField()
2141
     */
2142
    public function setValueFrom($field, $value, $table = '', $id = null, $format = '', $id_field = '', $fuser = null, $trigkey = '', $fk_user_field = 'fk_user_modif')
2143
    {
2144
        global $user;
2145
2146
        if (empty($table)) {
2147
            $table = $this->table_element;
2148
        }
2149
        if (empty($id)) {
2150
            $id = $this->id;
2151
        }
2152
        if (empty($format)) {
2153
            $format = 'text';
2154
        }
2155
        if (empty($id_field)) {
2156
            $id_field = 'rowid';
2157
        }
2158
2159
        // Special case
2160
        if ($table == 'product' && $field == 'note_private') {
2161
            $field = 'note';
2162
        }
2163
2164
        if (in_array($table, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
2165
            $fk_user_field = 'fk_user_mod';
2166
        }
2167
        if (in_array($table, array('prelevement_bons'))) {  // TODO Add a field fk_user_modif into llx_prelevement_bons
2168
            $fk_user_field = '';
2169
        }
2170
2171
        if ($trigkey) {
2172
            $oldvalue = null;
2173
2174
            $sql = "SELECT " . $field;
2175
            $sql .= " FROM " . MAIN_DB_PREFIX . $table;
2176
            $sql .= " WHERE " . $id_field . " = " . ((int) $id);
2177
2178
            $resql = $this->db->query($sql);
2179
            if ($resql) {
2180
                if ($obj = $this->db->fetch_object($resql)) {
2181
                    if ($format == 'date') {
2182
                        $oldvalue = $this->db->jdate($obj->$field);
2183
                    } else {
2184
                        $oldvalue = $obj->$field;
2185
                    }
2186
                }
2187
            } else {
2188
                $this->error = $this->db->lasterror();
2189
                return -1;
2190
            }
2191
        }
2192
2193
        $error = 0;
2194
2195
        dol_syslog(__METHOD__, LOG_DEBUG);
2196
2197
        $this->db->begin();
2198
2199
        $sql = "UPDATE " . $this->db->prefix() . $table . " SET ";
2200
2201
        if ($format == 'text') {
2202
            $sql .= $field . " = '" . $this->db->escape($value) . "'";
2203
        } elseif ($format == 'int') {
2204
            $sql .= $field . " = " . ((int) $value);
2205
        } elseif ($format == 'date') {
2206
            $sql .= $field . " = " . ($value ? "'" . $this->db->idate($value) . "'" : "null");
2207
        } elseif ($format == 'dategmt') {
2208
            $sql .= $field . " = " . ($value ? "'" . $this->db->idate($value, 'gmt') . "'" : "null");
2209
        }
2210
2211
        if ($fk_user_field) {
2212
            if (!empty($fuser) && is_object($fuser)) {
2213
                $sql .= ", " . $fk_user_field . " = " . ((int) $fuser->id);
2214
            } elseif (empty($fuser) || $fuser != 'none') {
2215
                $sql .= ", " . $fk_user_field . " = " . ((int) $user->id);
2216
            }
2217
        }
2218
2219
        $sql .= " WHERE " . $id_field . " = " . ((int) $id);
2220
2221
        $resql = $this->db->query($sql);
2222
        if ($resql) {
2223
            if ($trigkey) {
2224
                // call trigger with updated object values
2225
                if (method_exists($this, 'fetch')) {
2226
                    $result = $this->fetch($id);
2227
                } else {
2228
                    $result = $this->fetchCommon($id);
2229
                }
2230
                $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...
2231
                if (property_exists($this->oldcopy, $field)) {
2232
                    $this->oldcopy->$field = $oldvalue;
2233
                }
2234
2235
                if ($result >= 0) {
2236
                    $result = $this->call_trigger($trigkey, (!empty($fuser) && is_object($fuser)) ? $fuser : $user); // This may set this->errors
2237
                }
2238
                if ($result < 0) {
2239
                    $error++;
2240
                }
2241
            }
2242
2243
            if (!$error) {
2244
                if (property_exists($this, $field)) {
2245
                    $this->$field = $value;
2246
                }
2247
                $this->db->commit();
2248
                return 1;
2249
            } else {
2250
                $this->db->rollback();
2251
                return -2;
2252
            }
2253
        } else {
2254
            if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
2255
                $this->error = 'DB_ERROR_RECORD_ALREADY_EXISTS';
2256
            } else {
2257
                $this->error = $this->db->lasterror();
2258
            }
2259
            $this->db->rollback();
2260
            return -1;
2261
        }
2262
    }
2263
2264
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2265
    /**
2266
     *      Load properties id_previous and id_next by comparing $fieldid with $this->ref
2267
     *
2268
     *      @param  string  $filter     Optional SQL filter. Example: "(t.field1 = 'aa' OR t.field2 = 'bb')". Do not allow user input data here.
2269
     *                                  Use SQL and not Universal Search Filter. @TODO Replace this with an USF string after changing all ->next_prev_filter
2270
     *      @param  string  $fieldid    Name of field to use for the select MAX and MIN
2271
     *      @param  int     $nodbprefix Do not include DB prefix to forge table name
2272
     *      @return int                 Return integer <0 if KO, >0 if OK
2273
     */
2274
    public function load_previous_next_ref($filter, $fieldid, $nodbprefix = 0)
2275
    {
2276
        // phpcs:enable
2277
        global $conf, $user;
2278
2279
        if (!$this->table_element) {
2280
            dol_print_error(null, get_class($this) . "::load_previous_next_ref was called on object with property table_element not defined");
2281
            return -1;
2282
        }
2283
        if ($fieldid == 'none') {
2284
            return 1;
2285
        }
2286
2287
        // For backward compatibility
2288
        if (in_array($this->table_element, array('facture_rec', 'facture_fourn_rec')) && $fieldid == 'title') {
2289
            $fieldid = 'titre';
2290
        }
2291
2292
        // Security on socid
2293
        $socid = 0;
2294
        if ($user->socid > 0) {
2295
            $socid = $user->socid;
2296
        }
2297
2298
        // this->ismultientitymanaged contains
2299
        // 0=No test on entity, 1=Test with field entity, 'field@table'=Test with link by field@table
2300
        $aliastablesociete = 's';
2301
        if ($this->element == 'societe') {
2302
            $aliastablesociete = 'te'; // te as table_element
2303
        }
2304
        $restrictiononfksoc = empty($this->restrictiononfksoc) ? 0 : $this->restrictiononfksoc;
2305
        $sql = "SELECT MAX(te." . $fieldid . ")";
2306
        $sql .= " FROM " . (empty($nodbprefix) ? $this->db->prefix() : '') . $this->table_element . " as te";
2307
        if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2308
            $tmparray = explode('@', $this->ismultientitymanaged);
2309
            $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
2310
        } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2311
            $sql .= ", " . $this->db->prefix() . "societe as s"; // If we need to link to societe to limit select to socid
2312
        } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2313
            $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
2314
        }
2315
        if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2316
            $sql .= " LEFT JOIN " . $this->db->prefix() . "societe_commerciaux as sc ON " . $aliastablesociete . ".rowid = sc.fk_soc";
2317
        }
2318
        if ($fieldid == 'rowid') {
2319
            $sql .= " WHERE te." . $fieldid . " < " . ((int) $this->id);
2320
        } else {
2321
            $sql .= " WHERE te." . $fieldid . " < '" . $this->db->escape($this->ref) . "'"; // ->ref must always be defined (set to id if field does not exists)
2322
        }
2323
        if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2324
            $sql .= " AND sc.fk_user = " . ((int) $user->id);
2325
        }
2326
        if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2327
            $sql .= " AND (sc.fk_user = " . ((int) $user->id) . ' OR te.fk_soc IS NULL)';
2328
        }
2329
        if (!empty($filter)) {
2330
            if (!preg_match('/^\s*AND/i', $filter)) {
2331
                $sql .= " AND ";
2332
            }
2333
            $sql .= $filter;
2334
        }
2335
        if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2336
            $tmparray = explode('@', $this->ismultientitymanaged);
2337
            $sql .= " AND te." . $tmparray[0] . " = " . ($tmparray[1] == "societe" ? "s" : "parenttable") . ".rowid"; // If we need to link to this table to limit select to entity
2338
        } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2339
            $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2340
        }
2341
        if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2342
            if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2343
                if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2344
                    $sql .= " AND te.entity IS NOT NULL"; // Show all users
2345
                } else {
2346
                    $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM " . $this->db->prefix() . "usergroup_user as ug WHERE ug.entity IN (" . getEntity('usergroup') . "))";
2347
                }
2348
            } else {
2349
                $sql .= ' AND te.entity IN (' . getEntity($this->element) . ')';
2350
            }
2351
        }
2352
        if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2353
            $tmparray = explode('@', $this->ismultientitymanaged);
2354
            $sql .= ' AND parenttable.entity IN (' . getEntity($tmparray[1]) . ')';
2355
        }
2356
        if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2357
            $sql .= ' AND te.fk_soc = ' . ((int) $socid);
2358
        }
2359
        if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2360
            $sql .= ' AND (te.fk_soc = ' . ((int) $socid) . ' OR te.fk_soc IS NULL)';
2361
        }
2362
        if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2363
            $sql .= ' AND te.rowid = ' . ((int) $socid);
2364
        }
2365
        //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2366
2367
        $result = $this->db->query($sql);
2368
        if (!$result) {
2369
            $this->error = $this->db->lasterror();
2370
            return -1;
2371
        }
2372
        $row = $this->db->fetch_row($result);
2373
        $this->ref_previous = $row[0];
2374
2375
        $sql = "SELECT MIN(te." . $fieldid . ")";
2376
        $sql .= " FROM " . (empty($nodbprefix) ? $this->db->prefix() : '') . $this->table_element . " as te";
2377
        if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2378
            $tmparray = explode('@', $this->ismultientitymanaged);
2379
            $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
2380
        } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2381
            $sql .= ", " . $this->db->prefix() . "societe as s"; // If we need to link to societe to limit select to socid
2382
        } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2383
            $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
2384
        }
2385
        if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2386
            $sql .= " LEFT JOIN " . $this->db->prefix() . "societe_commerciaux as sc ON " . $aliastablesociete . ".rowid = sc.fk_soc";
2387
        }
2388
        if ($fieldid == 'rowid') {
2389
            $sql .= " WHERE te." . $fieldid . " > " . ((int) $this->id);
2390
        } else {
2391
            $sql .= " WHERE te." . $fieldid . " > '" . $this->db->escape($this->ref) . "'"; // ->ref must always be defined (set to id if field does not exists)
2392
        }
2393
        if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2394
            $sql .= " AND (sc.fk_user = " . ((int) $user->id);
2395
            if (getDolGlobalInt('MAIN_SEE_SUBORDINATES')) {
2396
                $userschilds = $user->getAllChildIds();
2397
                $sql .= " OR sc.fk_user IN (" . $this->db->sanitize(implode(',', $userschilds)) . ")";
2398
            }
2399
            $sql .= ')';
2400
        }
2401
        if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2402
            $sql .= " AND (sc.fk_user = " . ((int) $user->id) . ' OR te.fk_soc IS NULL)';
2403
        }
2404
        if (!empty($filter)) {
2405
            if (!preg_match('/^\s*AND/i', $filter)) {
2406
                $sql .= " AND "; // For backward compatibility
2407
            }
2408
            $sql .= $filter;
2409
        }
2410
        if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2411
            $tmparray = explode('@', $this->ismultientitymanaged);
2412
            $sql .= " AND te." . $tmparray[0] . " = " . ($tmparray[1] == "societe" ? "s" : "parenttable") . ".rowid"; // If we need to link to this table to limit select to entity
2413
        } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2414
            $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2415
        }
2416
        if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2417
            if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2418
                if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2419
                    $sql .= " AND te.entity IS NOT NULL"; // Show all users
2420
                } else {
2421
                    $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM " . $this->db->prefix() . "usergroup_user as ug WHERE ug.entity IN (" . getEntity('usergroup') . "))";
2422
                }
2423
            } else {
2424
                $sql .= ' AND te.entity IN (' . getEntity($this->element) . ')';
2425
            }
2426
        }
2427
        if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2428
            $tmparray = explode('@', $this->ismultientitymanaged);
2429
            $sql .= ' AND parenttable.entity IN (' . getEntity($tmparray[1]) . ')';
2430
        }
2431
        if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2432
            $sql .= ' AND te.fk_soc = ' . ((int) $socid);
2433
        }
2434
        if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2435
            $sql .= ' AND (te.fk_soc = ' . ((int) $socid) . ' OR te.fk_soc IS NULL)';
2436
        }
2437
        if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2438
            $sql .= ' AND te.rowid = ' . ((int) $socid);
2439
        }
2440
        //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2441
        // 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
2442
2443
        $result = $this->db->query($sql);
2444
        if (!$result) {
2445
            $this->error = $this->db->lasterror();
2446
            return -2;
2447
        }
2448
        $row = $this->db->fetch_row($result);
2449
        $this->ref_next = $row[0];
2450
2451
        return 1;
2452
    }
2453
2454
2455
    /**
2456
     *      Return list of id of contacts of object
2457
     *
2458
     *      @param  string  $source     Source of contact: external (llx_socpeople) or internal (llx_user) or thirdparty (llx_societe)
2459
     *      @return array               Array of id of contacts (if source=external or internal)
2460
     *                                  Array of id of third parties with at least one contact on object (if source=thirdparty)
2461
     */
2462
    public function getListContactId($source = 'external')
2463
    {
2464
        $contactAlreadySelected = array();
2465
        $tab = $this->liste_contact(-1, $source);
2466
        $num = count($tab);
2467
        $i = 0;
2468
        while ($i < $num) {
2469
            if ($source == 'thirdparty') {
2470
                $contactAlreadySelected[$i] = $tab[$i]['socid'];
2471
            } else {
2472
                $contactAlreadySelected[$i] = $tab[$i]['id'];
2473
            }
2474
            $i++;
2475
        }
2476
        return $contactAlreadySelected;
2477
    }
2478
2479
2480
    /**
2481
     *  Link element with a project
2482
     *
2483
     *  @param      int     $projectid      Project id to link element to
2484
     *  @param      int     $notrigger      Disable the trigger
2485
     *  @return     int                     Return integer <0 if KO, >0 if OK
2486
     */
2487
    public function setProject($projectid, $notrigger = 0)
2488
    {
2489
        global $user;
2490
        $error = 0;
2491
2492
        if (!$this->table_element) {
2493
            dol_syslog(get_class($this) . "::setProject was called on object with property table_element not defined", LOG_ERR);
2494
            return -1;
2495
        }
2496
2497
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
2498
        if (!empty($this->fields['fk_project'])) {      // Common case
2499
            if ($projectid) {
2500
                $sql .= " SET fk_project = " . ((int) $projectid);
2501
            } else {
2502
                $sql .= " SET fk_project = NULL";
2503
            }
2504
            $sql .= ' WHERE rowid = ' . ((int) $this->id);
2505
        } elseif ($this->table_element == 'actioncomm') {   // Special case for actioncomm
2506
            if ($projectid) {
2507
                $sql .= " SET fk_project = " . ((int) $projectid);
2508
            } else {
2509
                $sql .= " SET fk_project = NULL";
2510
            }
2511
            $sql .= ' WHERE id = ' . ((int) $this->id);
2512
        } else { // Special case for old architecture objects
2513
            if ($projectid) {
2514
                $sql .= ' SET fk_projet = ' . ((int) $projectid);
2515
            } else {
2516
                $sql .= ' SET fk_projet = NULL';
2517
            }
2518
            $sql .= " WHERE rowid = " . ((int) $this->id);
2519
        }
2520
2521
        $this->db->begin();
2522
2523
        dol_syslog(get_class($this) . "::setProject", LOG_DEBUG);
2524
        if ($this->db->query($sql)) {
2525
            $this->fk_project = ((int) $projectid);
2526
        } else {
2527
            dol_print_error($this->db);
2528
            $error++;
2529
        }
2530
2531
        // Triggers
2532
        if (!$error && !$notrigger) {
2533
            // Call triggers
2534
            $result = $this->call_trigger(strtoupper($this->element) . '_MODIFY', $user);
2535
            if ($result < 0) {
2536
                $error++;
2537
            } //Do also here what you must do to rollback action if trigger fail
2538
            // End call triggers
2539
        }
2540
2541
        // Commit or rollback
2542
        if ($error) {
2543
            $this->db->rollback();
2544
            return -1;
2545
        } else {
2546
            $this->db->commit();
2547
            return 1;
2548
        }
2549
    }
2550
2551
    /**
2552
     *  Change the payments methods
2553
     *
2554
     *  @param      int     $id     Id of new payment method
2555
     *  @return     int             >0 if OK, <0 if KO
2556
     */
2557
    public function setPaymentMethods($id)
2558
    {
2559
        global $user;
2560
2561
        $error = 0;
2562
        $notrigger = 0;
2563
2564
        dol_syslog(get_class($this) . '::setPaymentMethods(' . $id . ')');
2565
2566
        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

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

2613
            $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...
2614
            return -2;
2615
        }
2616
    }
2617
2618
    /**
2619
     *  Change the multicurrency code
2620
     *
2621
     *  @param      string  $code   multicurrency code
2622
     *  @return     int             >0 if OK, <0 if KO
2623
     */
2624
    public function setMulticurrencyCode($code)
2625
    {
2626
        dol_syslog(get_class($this) . '::setMulticurrencyCode(' . $code . ')');
2627
        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

2627
        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...
2628
            $fieldname = 'multicurrency_code';
2629
2630
            $sql = 'UPDATE ' . $this->db->prefix() . $this->table_element;
2631
            $sql .= " SET " . $fieldname . " = '" . $this->db->escape($code) . "'";
2632
            $sql .= ' WHERE rowid=' . ((int) $this->id);
2633
2634
            if ($this->db->query($sql)) {
2635
                $this->multicurrency_code = $code;
2636
2637
                [$fk_multicurrency, $rate] = MultiCurrency::getIdAndTxFromCode($this->db, $code);
2638
                if ($rate) {
2639
                    $this->setMulticurrencyRate($rate, 2);
2640
                }
2641
2642
                return 1;
2643
            } else {
2644
                dol_syslog(get_class($this) . '::setMulticurrencyCode Error ' . $sql . ' - ' . $this->db->error());
2645
                $this->error = $this->db->error();
2646
                return -1;
2647
            }
2648
        } else {
2649
            dol_syslog(get_class($this) . '::setMulticurrencyCode, status of the object is incompatible');
2650
            $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

2650
            $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...
2651
            return -2;
2652
        }
2653
    }
2654
2655
    /**
2656
     *  Change the multicurrency rate
2657
     *
2658
     *  @param      double  $rate   multicurrency rate
2659
     *  @param      int     $mode   mode 1 : amounts in company currency will be recalculated, mode 2 : amounts in foreign currency will be recalculated
2660
     *  @return     int             >0 if OK, <0 if KO
2661
     */
2662
    public function setMulticurrencyRate($rate, $mode = 1)
2663
    {
2664
        dol_syslog(get_class($this) . '::setMulticurrencyRate(' . $rate . ', ' . $mode . ')');
2665
        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

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

2692
                                $this->/** @scrutinizer ignore-call */ 
2693
                                       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...
2693
                                    $line->id,
2694
                                    $line->subprice,
2695
                                    $line->qty,
2696
                                    $line->remise_percent,
2697
                                    $line->tva_tx,
2698
                                    $line->localtax1_tx,
2699
                                    $line->localtax2_tx,
2700
                                    ($line->description ? $line->description : $line->desc),
2701
                                    'HT',
2702
                                    $line->info_bits,
2703
                                    $line->special_code,
2704
                                    $line->fk_parent_line,
2705
                                    $line->skip_update_total,
2706
                                    $line->fk_fournprice,
2707
                                    $line->pa_ht,
2708
                                    $line->label,
2709
                                    $line->product_type,
2710
                                    $line->date_start,
2711
                                    $line->date_end,
2712
                                    $line->array_options,
2713
                                    $line->fk_unit,
2714
                                    $line->multicurrency_subprice
2715
                                );
2716
                                break;
2717
                            case 'commande':
2718
                                /** @var Commande $this */
2719
                                /** @var OrderLine $line */
2720
                                $this->updateline(
2721
                                    $line->id,
2722
                                    ($line->description ? $line->description : $line->desc),
2723
                                    $line->subprice,
2724
                                    $line->qty,
2725
                                    $line->remise_percent,
2726
                                    $line->tva_tx,
2727
                                    $line->localtax1_tx,
2728
                                    $line->localtax2_tx,
2729
                                    'HT',
2730
                                    $line->info_bits,
2731
                                    $line->date_start,
2732
                                    $line->date_end,
2733
                                    $line->product_type,
2734
                                    $line->fk_parent_line,
2735
                                    $line->skip_update_total,
2736
                                    $line->fk_fournprice,
2737
                                    $line->pa_ht,
2738
                                    $line->label,
2739
                                    $line->special_code,
2740
                                    $line->array_options,
2741
                                    $line->fk_unit,
2742
                                    $line->multicurrency_subprice
2743
                                );
2744
                                break;
2745
                            case 'facture':
2746
                                /** @var Facture $this */
2747
                                /** @var FactureLigne $line */
2748
                                $this->updateline(
2749
                                    $line->id,
2750
                                    ($line->description ? $line->description : $line->desc),
2751
                                    $line->subprice,
2752
                                    $line->qty,
2753
                                    $line->remise_percent,
2754
                                    $line->date_start,
2755
                                    $line->date_end,
2756
                                    $line->tva_tx,
2757
                                    $line->localtax1_tx,
2758
                                    $line->localtax2_tx,
2759
                                    'HT',
2760
                                    $line->info_bits,
2761
                                    $line->product_type,
2762
                                    $line->fk_parent_line,
2763
                                    $line->skip_update_total,
2764
                                    $line->fk_fournprice,
2765
                                    $line->pa_ht,
2766
                                    $line->label,
2767
                                    $line->special_code,
2768
                                    $line->array_options,
2769
                                    $line->situation_percent,
2770
                                    $line->fk_unit,
2771
                                    $line->multicurrency_subprice
2772
                                );
2773
                                break;
2774
                            case 'supplier_proposal':
2775
                                /** @var SupplierProposal $this */
2776
                                /** @var SupplierProposalLine $line */
2777
                                $this->updateline(
2778
                                    $line->id,
2779
                                    $line->subprice,
2780
                                    $line->qty,
2781
                                    $line->remise_percent,
2782
                                    $line->tva_tx,
2783
                                    $line->localtax1_tx,
2784
                                    $line->localtax2_tx,
2785
                                    ($line->description ? $line->description : $line->desc),
2786
                                    'HT',
2787
                                    $line->info_bits,
2788
                                    $line->special_code,
2789
                                    $line->fk_parent_line,
2790
                                    $line->skip_update_total,
2791
                                    $line->fk_fournprice,
2792
                                    $line->pa_ht,
2793
                                    $line->label,
2794
                                    $line->product_type,
2795
                                    $line->array_options,
2796
                                    $line->ref_fourn,
2797
                                    $line->multicurrency_subprice
2798
                                );
2799
                                break;
2800
                            case 'order_supplier':
2801
                                /** @var CommandeFournisseur $this */
2802
                                /** @var CommandeFournisseurLigne $line */
2803
                                $this->updateline(
2804
                                    $line->id,
2805
                                    ($line->description ? $line->description : $line->desc),
2806
                                    $line->subprice,
2807
                                    $line->qty,
2808
                                    $line->remise_percent,
2809
                                    $line->tva_tx,
2810
                                    $line->localtax1_tx,
2811
                                    $line->localtax2_tx,
2812
                                    'HT',
2813
                                    $line->info_bits,
2814
                                    $line->product_type,
2815
                                    false,
2816
                                    $line->date_start,
2817
                                    $line->date_end,
2818
                                    $line->array_options,
2819
                                    $line->fk_unit,
2820
                                    $line->multicurrency_subprice,
2821
                                    $line->ref_supplier
2822
                                );
2823
                                break;
2824
                            case 'invoice_supplier':
2825
                                /** @var FactureFournisseur $this */
2826
                                /** @var SupplierInvoiceLine $line */
2827
                                $this->updateline(
2828
                                    $line->id,
2829
                                    ($line->description ? $line->description : $line->desc),
2830
                                    $line->subprice,
2831
                                    $line->tva_tx,
2832
                                    $line->localtax1_tx,
2833
                                    $line->localtax2_tx,
2834
                                    $line->qty,
2835
                                    0,
2836
                                    'HT',
2837
                                    $line->info_bits,
2838
                                    $line->product_type,
2839
                                    $line->remise_percent,
2840
                                    false,
2841
                                    $line->date_start,
2842
                                    $line->date_end,
2843
                                    $line->array_options,
2844
                                    $line->fk_unit,
2845
                                    $line->multicurrency_subprice,
2846
                                    $line->ref_supplier
2847
                                );
2848
                                break;
2849
                            default:
2850
                                dol_syslog(get_class($this) . '::setMulticurrencyRate no updateline defined', LOG_DEBUG);
2851
                                break;
2852
                        }
2853
                    }
2854
                }
2855
2856
                return 1;
2857
            } else {
2858
                dol_syslog(get_class($this) . '::setMulticurrencyRate Error ' . $sql . ' - ' . $this->db->error());
2859
                $this->error = $this->db->error();
2860
                return -1;
2861
            }
2862
        } else {
2863
            dol_syslog(get_class($this) . '::setMulticurrencyRate, status of the object is incompatible');
2864
            $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

2864
            $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...
2865
            return -2;
2866
        }
2867
    }
2868
2869
    /**
2870
     *  Change the payments terms
2871
     *
2872
     *  @param      int     $id                 Id of new payment terms
2873
     *  @param      float   $deposit_percent    % of deposit if needed by payment terms
2874
     *  @return     int                         >0 if OK, <0 if KO
2875
     */
2876
    public function setPaymentTerms($id, $deposit_percent = null)
2877
    {
2878
        dol_syslog(get_class($this) . '::setPaymentTerms(' . $id . ', ' . var_export($deposit_percent, true) . ')');
2879
        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

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

2910
                /** @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...
2911
                $this->deposit_percent = $deposit_percent;
2912
                return 1;
2913
            } else {
2914
                dol_syslog(get_class($this) . '::setPaymentTerms Error ' . $sql . ' - ' . $this->db->error());
2915
                $this->error = $this->db->error();
2916
                return -1;
2917
            }
2918
        } else {
2919
            dol_syslog(get_class($this) . '::setPaymentTerms, status of the object is incompatible');
2920
            $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

2920
            $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...
2921
            return -2;
2922
        }
2923
    }
2924
2925
    /**
2926
     *  Change the transport mode methods
2927
     *
2928
     *  @param      int     $id     Id of transport mode
2929
     *  @return     int             >0 if OK, <0 if KO
2930
     */
2931
    public function setTransportMode($id)
2932
    {
2933
        dol_syslog(get_class($this) . '::setTransportMode(' . $id . ')');
2934
        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

2934
        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...
2935
            $fieldname = 'fk_transport_mode';
2936
            if ($this->element == 'societe') {
2937
                $fieldname = 'transport_mode';
2938
            }
2939
            if (get_class($this) == 'Fournisseur') {
2940
                $fieldname = 'transport_mode_supplier';
2941
            }
2942
2943
            $sql = 'UPDATE ' . $this->db->prefix() . $this->table_element;
2944
            $sql .= " SET " . $fieldname . " = " . (($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
2945
            $sql .= ' WHERE rowid=' . ((int) $this->id);
2946
2947
            if ($this->db->query($sql)) {
2948
                $this->transport_mode_id = $id;
2949
                // for supplier
2950
                if (get_class($this) == 'Fournisseur') {
2951
                    $this->transport_mode_supplier_id = $id;
2952
                }
2953
                return 1;
2954
            } else {
2955
                dol_syslog(get_class($this) . '::setTransportMode Error ' . $sql . ' - ' . $this->db->error());
2956
                $this->error = $this->db->error();
2957
                return -1;
2958
            }
2959
        } else {
2960
            dol_syslog(get_class($this) . '::setTransportMode, status of the object is incompatible');
2961
            $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

2961
            $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...
2962
            return -2;
2963
        }
2964
    }
2965
2966
    /**
2967
     *  Change the retained warranty payments terms
2968
     *
2969
     *  @param      int     $id     Id of new payment terms
2970
     *  @return     int             >0 if OK, <0 if KO
2971
     */
2972
    public function setRetainedWarrantyPaymentTerms($id)
2973
    {
2974
        dol_syslog(get_class($this) . '::setRetainedWarrantyPaymentTerms(' . $id . ')');
2975
        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

2975
        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...
2976
            $fieldname = 'retained_warranty_fk_cond_reglement';
2977
2978
            $sql = 'UPDATE ' . $this->db->prefix() . $this->table_element;
2979
            $sql .= " SET " . $fieldname . " = " . ((int) $id);
2980
            $sql .= ' WHERE rowid=' . ((int) $this->id);
2981
2982
            if ($this->db->query($sql)) {
2983
                $this->retained_warranty_fk_cond_reglement = $id;
2984
                return 1;
2985
            } else {
2986
                dol_syslog(get_class($this) . '::setRetainedWarrantyPaymentTerms Error ' . $sql . ' - ' . $this->db->error());
2987
                $this->error = $this->db->error();
2988
                return -1;
2989
            }
2990
        } else {
2991
            dol_syslog(get_class($this) . '::setRetainedWarrantyPaymentTerms, status of the object is incompatible');
2992
            $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

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

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

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

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

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

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

10282
                        /** @scrutinizer ignore-call */ 
10283
                        $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...
10283
                        if ($result < 0) {
10284
                            $error++;
10285
                            $this->errors[] = $this->error;
10286
                        } else {
10287
                            $deleted++;
10288
                        }
10289
                    }
10290
                }
10291
            }
10292
10293
            if (empty($error)) {
10294
                $this->db->commit();
10295
                return $deleted;
10296
            } else {
10297
                $this->error = implode(', ', $this->errors);
10298
                $this->db->rollback();
10299
                return $error * -1;
10300
            }
10301
        }
10302
10303
        return $deleted;
10304
    }
10305
10306
    /**
10307
     *  Delete a line of object in database
10308
     *
10309
     *  @param  User    $user       User that delete
10310
     *  @param  int     $idline     Id of line to delete
10311
     *  @param  int     $notrigger  0=launch triggers after, 1=disable triggers
10312
     *  @return int                 >0 if OK, <0 if KO
10313
     */
10314
    public function deleteLineCommon(User $user, $idline, $notrigger = 0)
10315
    {
10316
        $error = 0;
10317
10318
        $tmpforobjectclass = get_class($this);
10319
        $tmpforobjectlineclass = ucfirst($tmpforobjectclass) . 'Line';
10320
10321
        $this->db->begin();
10322
10323
        // Call trigger
10324
        $result = $this->call_trigger('LINE' . strtoupper($tmpforobjectclass) . '_DELETE', $user);
10325
        if ($result < 0) {
10326
            $error++;
10327
        }
10328
        // End call triggers
10329
10330
        if (empty($error)) {
10331
            $sql = "DELETE FROM " . $this->db->prefix() . $this->table_element_line;
10332
            $sql .= " WHERE rowid = " . ((int) $idline);
10333
10334
            $resql = $this->db->query($sql);
10335
            if (!$resql) {
10336
                $this->error = "Error " . $this->db->lasterror();
10337
                $error++;
10338
            }
10339
        }
10340
10341
        if (empty($error)) {
10342
            // Remove extrafields
10343
            $tmpobjectline = new $tmpforobjectlineclass($this->db);
10344
            if (!isset($tmpobjectline->isextrafieldmanaged) || !empty($tmpobjectline->isextrafieldmanaged)) {
10345
                $tmpobjectline->id = $idline;
10346
                $result = $tmpobjectline->deleteExtraFields();
10347
                if ($result < 0) {
10348
                    $error++;
10349
                    $this->error = "Error " . get_class($this) . "::deleteLineCommon deleteExtraFields error -4 " . $tmpobjectline->error;
10350
                }
10351
            }
10352
        }
10353
10354
        if (empty($error)) {
10355
            $this->db->commit();
10356
            return 1;
10357
        } else {
10358
            dol_syslog(get_class($this) . "::deleteLineCommon ERROR:" . $this->error, LOG_ERR);
10359
            $this->db->rollback();
10360
            return -1;
10361
        }
10362
    }
10363
10364
10365
    /**
10366
     *  Set to a status
10367
     *
10368
     *  @param  User    $user           Object user that modify
10369
     *  @param  int     $status         New status to set (often a constant like self::STATUS_XXX)
10370
     *  @param  int     $notrigger      1=Does not execute triggers, 0=Execute triggers
10371
     *  @param  string  $triggercode    Trigger code to use
10372
     *  @return int                     Return integer <0 if KO, >0 if OK
10373
     */
10374
    public function setStatusCommon($user, $status, $notrigger = 0, $triggercode = '')
10375
    {
10376
        $error = 0;
10377
10378
        $this->db->begin();
10379
10380
        $statusfield = 'status';
10381
        if (in_array($this->element, array('don', 'donation', 'shipping'))) {
10382
            $statusfield = 'fk_statut';
10383
        }
10384
10385
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
10386
        $sql .= " SET " . $statusfield . " = " . ((int) $status);
10387
        $sql .= " WHERE rowid = " . ((int) $this->id);
10388
10389
        if ($this->db->query($sql)) {
10390
            if (!$error) {
10391
                $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...
10392
            }
10393
10394
            if (!$error && !$notrigger) {
10395
                // Call trigger
10396
                $result = $this->call_trigger($triggercode, $user);
10397
                if ($result < 0) {
10398
                    $error++;
10399
                }
10400
            }
10401
10402
            if (!$error) {
10403
                $this->status = $status;
10404
                $this->db->commit();
10405
                return 1;
10406
            } else {
10407
                $this->db->rollback();
10408
                return -1;
10409
            }
10410
        } else {
10411
            $this->error = $this->db->error();
10412
            $this->db->rollback();
10413
            return -1;
10414
        }
10415
    }
10416
10417
    /**
10418
     *  Set to a signed status
10419
     *
10420
     *  @param  User    $user           Object user that modify
10421
     *  @param  int     $status         New status to set (often a constant like self::STATUS_XXX)
10422
     *  @param  int     $notrigger      1=Does not execute triggers, 0=Execute triggers
10423
     *  @param  string  $triggercode    Trigger code to use
10424
     *  @return int                     Return integer <0 if KO, >0 if OK
10425
     */
10426
    public function setSignedStatusCommon($user, $status, $notrigger = 0, $triggercode = '')
10427
    {
10428
        $error = 0;
10429
10430
        $this->db->begin();
10431
10432
        $statusfield = 'signed_status';
10433
10434
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
10435
        $sql .= " SET " . $statusfield . " = " . ((int) $status);
10436
        $sql .= " WHERE rowid = " . ((int) $this->id);
10437
10438
        if ($this->db->query($sql)) {
10439
            if (!$error) {
10440
                $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...
10441
            }
10442
10443
            if (!$error && !$notrigger) {
10444
                // Call trigger
10445
                $result = $this->call_trigger($triggercode, $user);
10446
                if ($result < 0) {
10447
                    $error++;
10448
                }
10449
            }
10450
10451
            if (!$error) {
10452
                $this->status = $status;
10453
                $this->db->commit();
10454
                return 1;
10455
            } else {
10456
                $this->db->rollback();
10457
                return -1;
10458
            }
10459
        } else {
10460
            $this->error = $this->db->error();
10461
            $this->db->rollback();
10462
            return -1;
10463
        }
10464
    }
10465
10466
10467
    /**
10468
     * Initialise object with example values
10469
     * Id must be 0 if object instance is a specimen
10470
     *
10471
     * @return int
10472
     */
10473
    public function initAsSpecimenCommon()
10474
    {
10475
        global $user;
10476
10477
        $this->id = 0;
10478
        $this->specimen = 1;
10479
        $fields = array(
10480
            'label' => 'This is label',
10481
            'ref' => 'ABCD1234',
10482
            'description' => 'This is a description',
10483
            'qty' => 123.12,
10484
            'note_public' => 'Public note',
10485
            'note_private' => 'Private note',
10486
            'date_creation' => (dol_now() - 3600 * 48),
10487
            'date_modification' => (dol_now() - 3600 * 24),
10488
            'fk_user_creat' => $user->id,
10489
            'fk_user_modif' => $user->id,
10490
            'date' => dol_now(),
10491
        );
10492
        foreach ($fields as $key => $value) {
10493
            if (array_key_exists($key, $this->fields)) {
10494
                $this->{$key} = $value;     // @phpstan-ignore-line
10495
            }
10496
        }
10497
10498
        // Force values to default values when known
10499
        if (property_exists($this, 'fields')) {
10500
            foreach ($this->fields as $key => $value) {
10501
                // If fields are already set, do nothing
10502
                if (array_key_exists($key, $fields)) {
10503
                    continue;
10504
                }
10505
10506
                if (!empty($value['default'])) {
10507
                    $this->$key = $value['default'];
10508
                }
10509
            }
10510
        }
10511
10512
        return 1;
10513
    }
10514
10515
10516
    /* Part for comments */
10517
10518
    /**
10519
     * Load comments linked with current task
10520
     *
10521
     * @return int<0,max>|-1        Returns the number of comments if OK, -1 if error
10522
     */
10523
    public function fetchComments()
10524
    {
10525
        require_once DOL_DOCUMENT_ROOT . '/core/class/comment.class.php';
10526
10527
        $comment = new Comment($this->db);
10528
        $result = $comment->fetchAllFor($this->element, $this->id);
10529
        if ($result < 0) {
10530
            $this->errors = array_merge($this->errors, $comment->errors);
10531
            return -1;
10532
        } else {
10533
            $this->comments = $comment->comments;
10534
        }
10535
        return count($this->comments);
10536
    }
10537
10538
    /**
10539
     * Return nb comments already posted
10540
     *
10541
     * @return int
10542
     */
10543
    public function getNbComments()
10544
    {
10545
        return count($this->comments);
10546
    }
10547
10548
    /**
10549
     * Trim object parameters
10550
     *
10551
     * @param string[] $parameters array of parameters to trim
10552
     * @return void
10553
     */
10554
    public function trimParameters($parameters)
10555
    {
10556
        if (!is_array($parameters)) {
10557
            return;
10558
        }
10559
        foreach ($parameters as $parameter) {
10560
            if (isset($this->$parameter)) {
10561
                $this->$parameter = trim($this->$parameter);
10562
            }
10563
        }
10564
    }
10565
10566
    /* Part for categories/tags */
10567
10568
    /**
10569
     * Sets object to given categories.
10570
     *
10571
     * Deletes object from existing categories not supplied.
10572
     * Adds it to non existing supplied categories.
10573
     * Existing categories are left untouch.
10574
     *
10575
     * @param   string      $type_categ     Category type ('customer', 'supplier', 'website_page', ...)
10576
     * @return  int                         Array of category objects or < 0 if KO
10577
     */
10578
    public function getCategoriesCommon($type_categ)
10579
    {
10580
10581
        // Get current categories
10582
        $c = new Categorie($this->db);
10583
        $existing = $c->containing($this->id, $type_categ, 'id');
10584
10585
        return $existing;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $existing also could return the type DoliModules\Category\Model\Categorie[] which is incompatible with the documented return type integer.
Loading history...
10586
    }
10587
10588
    /**
10589
     * Sets object to given categories.
10590
     *
10591
     * Adds it to non existing supplied categories.
10592
     * Deletes object from existing categories not supplied (if remove_existing==true).
10593
     * Existing categories are left untouch.
10594
     *
10595
     * @param   int[]|int   $categories         Category ID or array of Categories IDs
10596
     * @param   string      $type_categ         Category type ('customer', 'supplier', 'website_page', ...) defined into const class Categorie type
10597
     * @param   boolean     $remove_existing    True: Remove existings categories from Object if not supplies by $categories, False: let them
10598
     * @return  int                             Return integer <0 if KO, >0 if OK
10599
     */
10600
    public function setCategoriesCommon($categories, $type_categ = '', $remove_existing = true)
10601
    {
10602
        // Handle single category
10603
        if (!is_array($categories)) {
10604
            $categories = array($categories);
10605
        }
10606
10607
        dol_syslog(get_class($this) . "::setCategoriesCommon Object Id:" . $this->id . ' type_categ:' . $type_categ . ' nb tag add:' . count($categories), LOG_DEBUG);
10608
10609
10610
        if (empty($type_categ)) {
10611
            dol_syslog(__METHOD__ . ': Type ' . $type_categ . 'is an unknown category type. Done nothing.', LOG_ERR);
10612
            return -1;
10613
        }
10614
10615
        // Get current categories
10616
        $c = new Categorie($this->db);
10617
        $existing = $c->containing($this->id, $type_categ, 'id');
10618
        if ($remove_existing) {
10619
            // Diff
10620
            if (is_array($existing)) {
10621
                $to_del = array_diff($existing, $categories);
10622
                $to_add = array_diff($categories, $existing);
10623
            } else {
10624
                $to_del = array(); // Nothing to delete
10625
                $to_add = $categories;
10626
            }
10627
        } else {
10628
            $to_del = array(); // Nothing to delete
10629
            $to_add = array_diff($categories, $existing);
10630
        }
10631
10632
        $error = 0;
10633
        $ok = 0;
10634
10635
        // Process
10636
        foreach ($to_del as $del) {
10637
            if ($c->fetch($del) > 0) {
10638
                $result = $c->del_type($this, $type_categ);
10639
                if ($result < 0) {
10640
                    $error++;
10641
                    $this->error = $c->error;
10642
                    $this->errors = $c->errors;
10643
                    break;
10644
                } else {
10645
                    $ok += $result;
10646
                }
10647
            }
10648
        }
10649
        foreach ($to_add as $add) {
10650
            if ($c->fetch($add) > 0) {
10651
                $result = $c->add_type($this, $type_categ);
10652
                if ($result < 0) {
10653
                    $error++;
10654
                    $this->error = $c->error;
10655
                    $this->errors = $c->errors;
10656
                    break;
10657
                } else {
10658
                    $ok += $result;
10659
                }
10660
            }
10661
        }
10662
10663
        return $error ? (-1 * $error) : $ok;
10664
    }
10665
10666
    /**
10667
     * Copy related categories to another object
10668
     *
10669
     * @param  int      $fromId Id object source
10670
     * @param  int      $toId   Id object cible
10671
     * @param  string   $type   Type of category ('product', ...)
10672
     * @return int      Return integer < 0 if error, > 0 if ok
10673
     */
10674
    public function cloneCategories($fromId, $toId, $type = '')
10675
    {
10676
        $this->db->begin();
10677
10678
        if (empty($type)) {
10679
            $type = $this->table_element;
10680
        }
10681
10682
        $categorystatic = new Categorie($this->db);
10683
10684
        $sql = "INSERT INTO " . $this->db->prefix() . "categorie_" . (empty($categorystatic->MAP_CAT_TABLE[$type]) ? $type : $categorystatic->MAP_CAT_TABLE[$type]) . " (fk_categorie, fk_product)";
10685
        $sql .= " SELECT fk_categorie, $toId FROM " . $this->db->prefix() . "categorie_" . (empty($categorystatic->MAP_CAT_TABLE[$type]) ? $type : $categorystatic->MAP_CAT_TABLE[$type]);
10686
        $sql .= " WHERE fk_product = " . ((int) $fromId);
10687
10688
        if (!$this->db->query($sql)) {
10689
            $this->error = $this->db->lasterror();
10690
            $this->db->rollback();
10691
            return -1;
10692
        }
10693
10694
        $this->db->commit();
10695
        return 1;
10696
    }
10697
10698
    /**
10699
     * Delete related files of object in database
10700
     *
10701
     * @param   integer     $mode       0=Use path to find record, 1=Use src_object_xxx fields (Mode 1 is recommended for new objects)
10702
     * @return  bool                    True if OK, False if KO
10703
     */
10704
    public function deleteEcmFiles($mode = 0)
10705
    {
10706
        global $conf;
10707
10708
        $this->db->begin();
10709
10710
        // Delete in database with mode 0
10711
        if ($mode == 0) {
10712
            switch ($this->element) {
10713
                case 'propal':
10714
                    $element = 'propale';
10715
                    break;
10716
                case 'product':
10717
                    $element = 'produit';
10718
                    break;
10719
                case 'order_supplier':
10720
                    $element = 'fournisseur/commande';
10721
                    break;
10722
                case 'invoice_supplier':
10723
                    // Special cases that need to use get_exdir to get real dir of object
10724
                    // In future, all object should use this to define path of documents.
10725
                    $element = 'fournisseur/facture/' . get_exdir($this->id, 2, 0, 1, $this, 'invoice_supplier');
10726
                    break;
10727
                case 'shipping':
10728
                    $element = 'expedition/sending';
10729
                    break;
10730
                case 'task':
10731
                case 'project_task':
10732
            $project_result = $this->fetch_projet();
10733
                    if ($project_result >= 0) {
10734
                        $element = 'projet/' . dol_sanitizeFileName($this->project->ref) . '/';
10735
                    }
10736
                // no break
10737
                default:
10738
                    $element = $this->element;
10739
            }
10740
10741
            // Delete ecm_files_extrafields with mode 0 (using name)
10742
            $sql = "DELETE FROM " . $this->db->prefix() . "ecm_files_extrafields WHERE fk_object IN (";
10743
            $sql .= " SELECT rowid FROM " . $this->db->prefix() . "ecm_files WHERE filename LIKE '" . $this->db->escape($this->ref) . "%'";
10744
            $sql .= " AND filepath = '" . $this->db->escape($element) . "/" . $this->db->escape($this->ref) . "' AND entity = " . ((int) $conf->entity); // No need of getEntity here
10745
            $sql .= ")";
10746
10747
            if (!$this->db->query($sql)) {
10748
                $this->error = $this->db->lasterror();
10749
                $this->db->rollback();
10750
                return false;
10751
            }
10752
10753
            // Delete ecm_files with mode 0 (using name)
10754
            $sql = "DELETE FROM " . $this->db->prefix() . "ecm_files";
10755
            $sql .= " WHERE filename LIKE '" . $this->db->escape($this->ref) . "%'";
10756
            $sql .= " AND filepath = '" . $this->db->escape($element) . "/" . $this->db->escape($this->ref) . "' AND entity = " . ((int) $conf->entity); // No need of getEntity here
10757
10758
            if (!$this->db->query($sql)) {
10759
                $this->error = $this->db->lasterror();
10760
                $this->db->rollback();
10761
                return false;
10762
            }
10763
        }
10764
10765
        // Delete in database with mode 1
10766
        if ($mode == 1) {
10767
            $sql = 'DELETE FROM ' . $this->db->prefix() . "ecm_files_extrafields";
10768
            $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) . ")";
10769
            $resql = $this->db->query($sql);
10770
            if (!$resql) {
10771
                $this->error = $this->db->lasterror();
10772
                $this->db->rollback();
10773
                return false;
10774
            }
10775
10776
            $sql = 'DELETE FROM ' . $this->db->prefix() . "ecm_files";
10777
            $sql .= " WHERE src_object_type = '" . $this->db->escape($this->table_element . (empty($this->module) ? "" : "@" . $this->module)) . "' AND src_object_id = " . ((int) $this->id);
10778
            $resql = $this->db->query($sql);
10779
            if (!$resql) {
10780
                $this->error = $this->db->lasterror();
10781
                $this->db->rollback();
10782
                return false;
10783
            }
10784
        }
10785
10786
        $this->db->commit();
10787
        return true;
10788
    }
10789
}
10790