CommonObject   F
last analyzed

Complexity

Total Complexity 2285

Size/Duplication

Total Lines 10844
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 5535
dl 0
loc 10844
rs 0.8
c 0
b 0
f 0
wmc 2285

149 Methods

Rating   Name   Duplication   Size   Complexity  
C fetch_barcode() 0 39 12
A fetch_project() 0 4 1
A fetchOneLike() 0 25 4
D add_contact() 0 97 18
F liste_contact() 0 91 21
A copy_linked_contact() 0 10 3
A getListContactId() 0 15 3
F setStatut() 0 118 40
C line_order() 0 76 15
A fetch_origin() 0 20 6
F printObjectLine() 0 107 35
A __clone() 0 8 5
A isFloat() 0 10 4
F fetch_optionals() 0 130 45
F show_photos() 0 222 72
F showInputField() 0 742 221
F deleteCommon() 0 126 27
A fetch_projet() 0 17 4
F updateCommon() 0 101 22
F getIdContact() 0 59 13
F setMulticurrencyRate() 0 204 20
B delete_contact() 0 39 7
A emtpyObjectVars() 0 4 2
D setVarsFromFetchObj() 0 51 26
A add_element_resource() 0 29 2
C hasProductsOrServices() 0 27 14
A getIdOfLine() 0 17 3
F update_price() 0 303 65
A isForcedToNullIfZero() 0 10 4
C updateObjectLinked() 0 54 16
F insertExtraLanguages() 0 105 23
A line_down() 0 13 1
A update_note_public() 0 4 1
A getTotalDiscount() 0 33 4
F validateField() 0 202 51
C liste_type_contact() 0 49 11
A updateLineDown() 0 19 5
F printOriginLine() 0 129 38
A getElementType() 0 7 3
A fetchObjectFrom() 0 32 5
A updateRangOfLine() 0 20 3
A getFieldError() 0 6 2
A delete_resource() 0 27 4
D defineBuyPrice() 0 49 18
F getTotalWeightVolume() 0 94 33
C setValuesForExtraLanguages() 0 72 15
A update_ref_ext() 0 19 4
A line_ajaxorder() 0 6 2
A isText() 0 10 4
A fetch_user() 0 7 1
F add_object_linked() 0 72 14
B setTransportMode() 0 32 9
F isObjectUsed() 0 76 26
F printObjectLines() 0 71 15
B getCanvas() 0 36 8
B getDefaultCreateValueFor() 0 33 7
A swapContactStatus() 0 21 3
F insertExtraFields() 0 312 83
A setExtraField() 0 3 1
A getFieldList() 0 16 5
A line_up() 0 10 1
B setBankAccount() 0 53 10
B fetchLinesCommon() 0 48 7
A getSpecialCode() 0 11 3
A isDate() 0 6 5
B fetchValuesForExtraLanguages() 0 60 10
A isDuration() 0 10 4
A quote() 0 16 6
F showOptionals() 0 315 116
D listeTypeContacts() 0 68 18
A clearFieldError() 0 4 1
F update_note() 0 76 19
F setValueFrom() 0 118 35
A fetchNoCompute() 0 13 1
F createCommon() 0 177 60
A formAddObjectLine() 0 28 6
D setSaveQuery() 0 60 26
B deleteByParentField() 0 55 9
A clearObjectLinkedCache() 0 7 3
F fetchObjectLinked() 0 143 54
B fetch_thirdparty() 0 28 11
C fetchCommon() 0 54 15
F setPaymentTerms() 0 46 14
F updateExtraField() 0 296 59
A getValueFrom() 0 15 5
A setDeliveryAddress() 0 17 4
A fetch_contact() 0 15 3
B printOriginLinesList() 0 38 8
A delThumbs() 0 6 1
A getJSListDependancies() 0 69 1
A setFieldError() 0 8 2
A getDataToShowPhoto() 0 4 1
A addThumbs() 0 22 2
B line_max() 0 38 6
C setProject() 0 61 12
F setPaymentMethods() 0 58 15
B setShippingMethod() 0 48 9
A isInt() 0 10 4
A setWarehouse() 0 22 5
A update_contact() 0 19 4
B getRights() 0 17 7
A getChildrenOfLine() 0 27 6
A setDocModel() 0 21 3
A fetch_product() 0 15 2
A setRetainedWarrantyPaymentTerms() 0 22 4
A updateLineUp() 0 19 5
F load_previous_next_ref() 0 198 111
A getRangOfLine() 0 18 3
A setExtraParameters() 0 19 4
A updateExtraLanguages() 0 15 3
A setMulticurrencyCode() 0 28 5
A delete_linked_contact() 0 30 5
A getExtraField() 0 3 1
F showOutputField() 0 435 146
C getLastMainDocLink() 0 80 14
A deleteAllItemsLinkedByObjectID() 0 16 5
A commonReplaceProduct() 0 15 4
A getCountOfItemsLinkedByObjectID() 0 19 6
B getFullAddress() 0 19 11
A isEmpty() 0 3 1
A getFormatedSupplierRef() 0 11 3
A setErrorsFromObject() 0 7 3
B isExistingObject() 0 34 8
A getTooltipContentArray() 0 3 1
B getAllItemsLinkedByObjectID() 0 22 7
A commonReplaceThirdparty() 0 15 4
D getTooltipContent() 0 68 20
A errorsToString() 0 3 3
A getFormatedCustomerRef() 0 11 3
A call_trigger() 0 24 6
A fetchComments() 0 12 2
A cloneCategories() 0 22 5
A getNbComments() 0 3 1
F indexFile() 0 108 31
B deleteLineCommon() 0 48 9
A isIndex() 0 10 4
A deleteExtraFields() 0 27 4
A trimParameters() 0 8 4
F commonGenerateDocument() 0 182 33
A getCategoriesCommon() 0 7 1
A isArray() 0 10 4
B setSignedStatusCommon() 0 37 7
A canBeNull() 0 10 4
B initAsSpecimenCommon() 0 40 7
C deleteEcmFiles() 0 85 17
C setCategoriesCommon() 0 63 12
B setStatusCommon() 0 43 9
A deprecatedProperties() 0 12 1
F deleteObjectLinked() 0 67 22

How to fix   Complexity   

Complex Class

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

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

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

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

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

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

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

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

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

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

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

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

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

2507
        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...
2508
            $this->fk_project = $this->fk_projet; // For backward compatibility
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$fk_projet has been deprecated: Use $fk_project instead. ( Ignorable by Annotation )

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3809
                /** @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...
3810
                $this->deposit_percent = $deposit_percent;
3811
                return 1;
3812
            } else {
3813
                dol_syslog(get_only_class($this) . '::setPaymentTerms Error ' . $sql . ' - ' . $this->db->error());
3814
                $this->error = $this->db->error();
3815
                return -1;
3816
            }
3817
        } else {
3818
            dol_syslog(get_only_class($this) . '::setPaymentTerms, status of the object is incompatible');
3819
            $this->error = 'Status of the object is incompatible ' . $this->status;
3820
            return -2;
3821
        }
3822
    }
3823
3824
    /**
3825
     *  Change the transport mode methods
3826
     *
3827
     * @param int $id Id of transport mode
3828
     * @return     int             >0 if OK, <0 if KO
3829
     */
3830
    public function setTransportMode($id)
3831
    {
3832
        dol_syslog(get_only_class($this) . '::setTransportMode(' . $id . ')');
3833
        if ($this->status >= 0 || $this->element == 'societe') {
3834
            $fieldname = 'fk_transport_mode';
3835
            if ($this->element == 'societe') {
3836
                $fieldname = 'transport_mode';
3837
            }
3838
            if (get_only_class($this) == 'Fournisseur') {
3839
                $fieldname = 'transport_mode_supplier';
3840
            }
3841
3842
            $sql = 'UPDATE ' . $this->db->prefix() . $this->table_element;
3843
            $sql .= " SET " . $fieldname . " = " . (($id > 0 || $id == '0') ? ((int)$id) : 'NULL');
3844
            $sql .= ' WHERE rowid=' . ((int)$this->id);
3845
3846
            if ($this->db->query($sql)) {
3847
                $this->transport_mode_id = $id;
3848
                // for supplier
3849
                if (get_only_class($this) == 'Fournisseur') {
3850
                    $this->transport_mode_supplier_id = $id;
3851
                }
3852
                return 1;
3853
            } else {
3854
                dol_syslog(get_only_class($this) . '::setTransportMode Error ' . $sql . ' - ' . $this->db->error());
3855
                $this->error = $this->db->error();
3856
                return -1;
3857
            }
3858
        } else {
3859
            dol_syslog(get_only_class($this) . '::setTransportMode, status of the object is incompatible');
3860
            $this->error = 'Status of the object is incompatible ' . $this->status;
3861
            return -2;
3862
        }
3863
    }
3864
3865
    /**
3866
     *  Change the retained warranty payments terms
3867
     *
3868
     * @param int $id Id of new payment terms
3869
     * @return     int             >0 if OK, <0 if KO
3870
     */
3871
    public function setRetainedWarrantyPaymentTerms($id)
3872
    {
3873
        dol_syslog(get_only_class($this) . '::setRetainedWarrantyPaymentTerms(' . $id . ')');
3874
        if ($this->status >= 0 || $this->element == 'societe') {
3875
            $fieldname = 'retained_warranty_fk_cond_reglement';
3876
3877
            $sql = 'UPDATE ' . $this->db->prefix() . $this->table_element;
3878
            $sql .= " SET " . $fieldname . " = " . ((int)$id);
3879
            $sql .= ' WHERE rowid=' . ((int)$this->id);
3880
3881
            if ($this->db->query($sql)) {
3882
                $this->retained_warranty_fk_cond_reglement = $id;
3883
                return 1;
3884
            } else {
3885
                dol_syslog(get_only_class($this) . '::setRetainedWarrantyPaymentTerms Error ' . $sql . ' - ' . $this->db->error());
3886
                $this->error = $this->db->error();
3887
                return -1;
3888
            }
3889
        } else {
3890
            dol_syslog(get_only_class($this) . '::setRetainedWarrantyPaymentTerms, status of the object is incompatible');
3891
            $this->error = 'Status of the object is incompatible ' . $this->status;
3892
            return -2;
3893
        }
3894
    }
3895
3896
    /**
3897
     *  Define delivery address
3898
     * @param int $id Address id
3899
     * @return     int             Return integer <0 si ko, >0 si ok
3900
     * @deprecated
3901
     *
3902
     */
3903
    public function setDeliveryAddress($id)
3904
    {
3905
        $fieldname = 'fk_delivery_address';
3906
        if ($this->element == 'delivery' || $this->element == 'shipping') {
3907
            $fieldname = 'fk_address';
3908
        }
3909
3910
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element . " SET " . $fieldname . " = " . ((int)$id);
3911
        $sql .= " WHERE rowid = " . ((int)$this->id) . " AND fk_statut = 0";
3912
3913
        if ($this->db->query($sql)) {
3914
            $this->fk_delivery_address = $id;
3915
            return 1;
3916
        } else {
3917
            $this->error = $this->db->error();
3918
            dol_syslog(get_only_class($this) . '::setDeliveryAddress Error ' . $this->error);
3919
            return -1;
3920
        }
3921
    }
3922
3923
    /**
3924
     *  Change the shipping method
3925
     *
3926
     * @param int $shipping_method_id Id of shipping method
3927
     * @param int $notrigger 0=launch triggers after, 1=disable triggers
3928
     * @param User $userused Object user
3929
     * @return     int                             1 if OK, 0 if KO
3930
     */
3931
    public function setShippingMethod($shipping_method_id, $notrigger = 0, $userused = null)
3932
    {
3933
        global $user;
3934
3935
        if (empty($userused)) {
3936
            $userused = $user;
3937
        }
3938
3939
        $error = 0;
3940
3941
        if (!$this->table_element) {
3942
            dol_syslog(get_only_class($this) . "::setShippingMethod was called on object with property table_element not defined", LOG_ERR);
3943
            return -1;
3944
        }
3945
3946
        $this->db->begin();
3947
3948
        if ($shipping_method_id < 0) {
3949
            $shipping_method_id = 'NULL';
3950
        }
3951
        dol_syslog(get_only_class($this) . '::setShippingMethod(' . $shipping_method_id . ')');
3952
3953
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
3954
        $sql .= " SET fk_shipping_method = " . ((int)$shipping_method_id);
3955
        $sql .= " WHERE rowid=" . ((int)$this->id);
3956
        $resql = $this->db->query($sql);
3957
        if (!$resql) {
3958
            dol_syslog(get_only_class($this) . '::setShippingMethod Error ', LOG_DEBUG);
3959
            $this->error = $this->db->lasterror();
3960
            $error++;
3961
        } else {
3962
            if (!$notrigger) {
3963
                // Call trigger
3964
                $this->context = array('shippingmethodupdate' => 1);
3965
                $result = $this->call_trigger(strtoupper(get_only_class($this)) . '_MODIFY', $userused);
3966
                if ($result < 0) {
3967
                    $error++;
3968
                }
3969
                // End call trigger
3970
            }
3971
        }
3972
        if ($error) {
3973
            $this->db->rollback();
3974
            return -1;
3975
        } else {
3976
            $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...
3977
            $this->db->commit();
3978
            return 1;
3979
        }
3980
    }
3981
3982
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3983
3984
    /**
3985
     *  Change the warehouse
3986
     *
3987
     * @param int $warehouse_id Id of warehouse
3988
     * @return     int              1 if OK, 0 if KO
3989
     */
3990
    public function setWarehouse($warehouse_id)
3991
    {
3992
        if (!$this->table_element) {
3993
            dol_syslog(get_only_class($this) . "::setWarehouse was called on object with property table_element not defined", LOG_ERR);
3994
            return -1;
3995
        }
3996
        if ($warehouse_id < 0) {
3997
            $warehouse_id = 'NULL';
3998
        }
3999
        dol_syslog(get_only_class($this) . '::setWarehouse(' . $warehouse_id . ')');
4000
4001
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
4002
        $sql .= " SET fk_warehouse = " . ((int)$warehouse_id);
4003
        $sql .= " WHERE rowid=" . ((int)$this->id);
4004
4005
        if ($this->db->query($sql)) {
4006
            $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...
4007
            return 1;
4008
        } else {
4009
            dol_syslog(get_only_class($this) . '::setWarehouse Error ', LOG_DEBUG);
4010
            $this->error = $this->db->error();
4011
            return 0;
4012
        }
4013
    }
4014
4015
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4016
4017
    /**
4018
     *      Set last model used by doc generator
4019
     *
4020
     * @param User $user User object that make change
4021
     * @param string $modelpdf Modele name
4022
     * @return     int                 Return integer <0 if KO, >0 if OK
4023
     */
4024
    public function setDocModel($user, $modelpdf)
4025
    {
4026
        if (!$this->table_element) {
4027
            dol_syslog(get_only_class($this) . "::setDocModel was called on object with property table_element not defined", LOG_ERR);
4028
            return -1;
4029
        }
4030
4031
        $newmodelpdf = dol_trunc($modelpdf, 255);
4032
4033
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
4034
        $sql .= " SET model_pdf = '" . $this->db->escape($newmodelpdf) . "'";
4035
        $sql .= " WHERE rowid = " . ((int)$this->id);
4036
4037
        dol_syslog(get_only_class($this) . "::setDocModel", LOG_DEBUG);
4038
        $resql = $this->db->query($sql);
4039
        if ($resql) {
4040
            $this->model_pdf = $modelpdf;
4041
            return 1;
4042
        } else {
4043
            dol_print_error($this->db);
4044
            return 0;
4045
        }
4046
    }
4047
4048
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4049
4050
    /**
4051
     *  Change the bank account
4052
     *
4053
     * @param int $fk_account Id of bank account
4054
     * @param int $notrigger 0=launch triggers after, 1=disable triggers
4055
     * @param User $userused Object user
4056
     * @return     int                     1 if OK, 0 if KO
4057
     */
4058
    public function setBankAccount($fk_account, $notrigger = 0, $userused = null)
4059
    {
4060
        global $user;
4061
4062
        if (empty($userused)) {
4063
            $userused = $user;
4064
        }
4065
4066
        $error = 0;
4067
4068
        if (!$this->table_element) {
4069
            dol_syslog(get_only_class($this) . "::setBankAccount was called on object with property table_element not defined", LOG_ERR);
4070
            return -1;
4071
        }
4072
        $this->db->begin();
4073
4074
        if ($fk_account < 0) {
4075
            $fk_account = 'NULL';
4076
        }
4077
        dol_syslog(get_only_class($this) . '::setBankAccount(' . $fk_account . ')');
4078
4079
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
4080
        $sql .= " SET fk_account = " . ((int)$fk_account);
4081
        $sql .= " WHERE rowid=" . ((int)$this->id);
4082
4083
        $resql = $this->db->query($sql);
4084
        if (!$resql) {
4085
            dol_syslog(get_only_class($this) . '::setBankAccount Error ' . $sql . ' - ' . $this->db->error());
4086
            $this->error = $this->db->lasterror();
4087
            $error++;
4088
        } else {
4089
            if (!$notrigger) {
4090
                // Call trigger
4091
                $this->context['bankaccountupdate'] = 1;
4092
                $triggerName = strtoupper(get_only_class($this)) . '_MODIFY';
4093
                // Special cases
4094
                if ($triggerName == 'FACTUREREC_MODIFY') {
4095
                    $triggerName = 'BILLREC_MODIFY';
4096
                }
4097
                $result = $this->call_trigger($triggerName, $userused);
4098
                if ($result < 0) {
4099
                    $error++;
4100
                }
4101
                // End call trigger
4102
            }
4103
        }
4104
        if ($error) {
4105
            $this->db->rollback();
4106
            return -1;
4107
        } else {
4108
            $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...
4109
            $this->db->commit();
4110
            return 1;
4111
        }
4112
    }
4113
4114
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4115
4116
    /**
4117
     *  Update a line to have a lower rank
4118
     *
4119
     * @param int $rowid Id of line
4120
     * @param boolean $fk_parent_line Table with fk_parent_line field or not
4121
     * @return void
4122
     */
4123
    public function line_up($rowid, $fk_parent_line = true)
4124
    {
4125
        // phpcs:enable
4126
        $this->line_order(false, 'ASC', $fk_parent_line);
4127
4128
        // Get rang of line
4129
        $rang = $this->getRangOfLine($rowid);
4130
4131
        // Update position of line
4132
        $this->updateLineUp($rowid, $rang);
4133
    }
4134
4135
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4136
4137
    /**
4138
     *  Save a new position (field rang) for details lines.
4139
     *  You can choose to set position for lines with already a position or lines without any position defined.
4140
     *
4141
     * @param boolean $renum True to renum all already ordered lines, false to renum only not already ordered lines.
4142
     * @param string $rowidorder ASC or DESC
4143
     * @param boolean $fk_parent_line Table with fk_parent_line field or not
4144
     * @return     int                            Return integer <0 if KO, >0 if OK
4145
     */
4146
    public function line_order($renum = false, $rowidorder = 'ASC', $fk_parent_line = true)
4147
    {
4148
        // phpcs:enable
4149
        if (!$this->table_element_line) {
4150
            dol_syslog(get_only_class($this) . "::line_order was called on object with property table_element_line not defined", LOG_ERR);
4151
            return -1;
4152
        }
4153
        if (!$this->fk_element) {
4154
            dol_syslog(get_only_class($this) . "::line_order was called on object with property fk_element not defined", LOG_ERR);
4155
            return -1;
4156
        }
4157
4158
        $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
4159
        if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
4160
            $fieldposition = 'position';
4161
        }
4162
4163
        // Count number of lines to reorder (according to choice $renum)
4164
        $nl = 0;
4165
        $sql = "SELECT count(rowid) FROM " . $this->db->prefix() . $this->table_element_line;
4166
        $sql .= " WHERE " . $this->fk_element . " = " . ((int)$this->id);
4167
        if (!$renum) {
4168
            $sql .= " AND " . $fieldposition . " = 0";
4169
        }
4170
        if ($renum) {
4171
            $sql .= " AND " . $fieldposition . " <> 0";
4172
        }
4173
4174
        dol_syslog(get_only_class($this) . "::line_order", LOG_DEBUG);
4175
        $resql = $this->db->query($sql);
4176
        if ($resql) {
4177
            $row = $this->db->fetch_row($resql);
4178
            $nl = $row[0];
4179
        } else {
4180
            dol_print_error($this->db);
4181
        }
4182
        if ($nl > 0) {
4183
            // The goal of this part is to reorder all lines, with all children lines sharing the same counter that parents.
4184
            $rows = array();
4185
4186
            // We first search all lines that are parent lines (for multilevel details lines)
4187
            $sql = "SELECT rowid FROM " . $this->db->prefix() . $this->table_element_line;
4188
            $sql .= " WHERE " . $this->fk_element . " = " . ((int)$this->id);
4189
            if ($fk_parent_line) {
4190
                $sql .= ' AND fk_parent_line IS NULL';
4191
            }
4192
            $sql .= " ORDER BY " . $fieldposition . " ASC, rowid " . $rowidorder;
4193
4194
            dol_syslog(get_only_class($this) . "::line_order search all parent lines", LOG_DEBUG);
4195
            $resql = $this->db->query($sql);
4196
            if ($resql) {
4197
                $i = 0;
4198
                $num = $this->db->num_rows($resql);
4199
                while ($i < $num) {
4200
                    $row = $this->db->fetch_row($resql);
4201
                    $rows[] = $row[0]; // Add parent line into array rows
4202
                    $children = $this->getChildrenOfLine($row[0]);
4203
                    if (!empty($children)) {
4204
                        foreach ($children as $child) {
4205
                            array_push($rows, $child);
4206
                        }
4207
                    }
4208
                    $i++;
4209
                }
4210
4211
                // Now we set a new number for each lines (parent and children with children included into parent tree)
4212
                if (!empty($rows)) {
4213
                    foreach ($rows as $key => $row) {
4214
                        $this->updateRangOfLine($row, ($key + 1));
4215
                    }
4216
                }
4217
            } else {
4218
                dol_print_error($this->db);
4219
            }
4220
        }
4221
        return 1;
4222
    }
4223
4224
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4225
4226
    /**
4227
     *  Get children of line
4228
     *
4229
     * @param int $id Id of parent line
4230
     * @param int<0,1> $includealltree 0 = 1st level child, 1 = All level child
4231
     * @return int[]                       Array with list of children lines id
4232
     */
4233
    public function getChildrenOfLine($id, $includealltree = 0)
4234
    {
4235
        $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
4236
        if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
4237
            $fieldposition = 'position';
4238
        }
4239
4240
        $rows = array();
4241
4242
        $sql = "SELECT rowid FROM " . $this->db->prefix() . $this->table_element_line;
4243
        $sql .= " WHERE " . $this->fk_element . " = " . ((int)$this->id);
4244
        $sql .= ' AND fk_parent_line = ' . ((int)$id);
4245
        $sql .= " ORDER BY " . $fieldposition . " ASC";
4246
4247
        dol_syslog(get_only_class($this) . "::getChildrenOfLine search children lines for line " . $id, LOG_DEBUG);
4248
        $resql = $this->db->query($sql);
4249
        if ($resql) {
4250
            if ($this->db->num_rows($resql) > 0) {
4251
                while ($row = $this->db->fetch_row($resql)) {
4252
                    $rows[] = $row[0];
4253
                    if ($includealltree) {
4254
                        $rows = array_merge($rows, $this->getChildrenOfLine($row[0], $includealltree));
4255
                    }
4256
                }
4257
            }
4258
        }
4259
        return $rows;
4260
    }
4261
4262
    /**
4263
     *  Update position of line (rang)
4264
     *
4265
     * @param int $rowid Id of line
4266
     * @param int $rang Position
4267
     * @return int<-1,1>           Return integer <0 if KO, >0 if OK
4268
     */
4269
    public function updateRangOfLine($rowid, $rang)
4270
    {
4271
        global $hookmanager;
4272
        $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
4273
        if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
4274
            $fieldposition = 'position';
4275
        }
4276
4277
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element_line . " SET " . $fieldposition . " = " . ((int)$rang);
4278
        $sql .= ' WHERE rowid = ' . ((int)$rowid);
4279
4280
        dol_syslog(get_only_class($this) . "::updateRangOfLine", LOG_DEBUG);
4281
        if (!$this->db->query($sql)) {
4282
            dol_print_error($this->db);
4283
            return -1;
4284
        } else {
4285
            $parameters = array('rowid' => $rowid, 'rang' => $rang, 'fieldposition' => $fieldposition);
4286
            $action = '';
4287
            $reshook = $hookmanager->executeHooks('afterRankOfLineUpdate', $parameters, $this, $action);
4288
            return 1;
4289
        }
4290
    }
4291
4292
    /**
4293
     *  Get position of line (rang)
4294
     *
4295
     * @param int $rowid Id of line
4296
     * @return     int                 Value of rang in table of lines
4297
     */
4298
    public function getRangOfLine($rowid)
4299
    {
4300
        $fieldposition = 'rang';
4301
        if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
4302
            $fieldposition = 'position';
4303
        }
4304
4305
        $sql = "SELECT " . $fieldposition . " FROM " . $this->db->prefix() . $this->table_element_line;
4306
        $sql .= " WHERE rowid = " . ((int)$rowid);
4307
4308
        dol_syslog(get_only_class($this) . "::getRangOfLine", LOG_DEBUG);
4309
        $resql = $this->db->query($sql);
4310
        if ($resql) {
4311
            $row = $this->db->fetch_row($resql);
4312
            return $row[0];
4313
        }
4314
4315
        return 0;
4316
    }
4317
4318
    /**
4319
     *  Update position of line up (rang)
4320
     *
4321
     * @param int $rowid Id of line
4322
     * @param int $rang Position
4323
     * @return void
4324
     */
4325
    public function updateLineUp($rowid, $rang)
4326
    {
4327
        if ($rang > 1) {
4328
            $fieldposition = 'rang';
4329
            if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
4330
                $fieldposition = 'position';
4331
            }
4332
4333
            $sql = "UPDATE " . $this->db->prefix() . $this->table_element_line . " SET " . $fieldposition . " = " . ((int)$rang);
4334
            $sql .= " WHERE " . $this->fk_element . " = " . ((int)$this->id);
4335
            $sql .= " AND " . $fieldposition . " = " . ((int)($rang - 1));
4336
            if ($this->db->query($sql)) {
4337
                $sql = "UPDATE " . $this->db->prefix() . $this->table_element_line . " SET " . $fieldposition . " = " . ((int)($rang - 1));
4338
                $sql .= ' WHERE rowid = ' . ((int)$rowid);
4339
                if (!$this->db->query($sql)) {
4340
                    dol_print_error($this->db);
4341
                }
4342
            } else {
4343
                dol_print_error($this->db);
4344
            }
4345
        }
4346
    }
4347
4348
    /**
4349
     *  Update a line to have a higher rank
4350
     *
4351
     * @param int $rowid Id of line
4352
     * @param boolean $fk_parent_line Table with fk_parent_line field or not
4353
     * @return void
4354
     */
4355
    public function line_down($rowid, $fk_parent_line = true)
4356
    {
4357
        // phpcs:enable
4358
        $this->line_order(false, 'ASC', $fk_parent_line);
4359
4360
        // Get rang of line
4361
        $rang = $this->getRangOfLine($rowid);
4362
4363
        // Get max value for rang
4364
        $max = $this->line_max();
4365
4366
        // Update position of line
4367
        $this->updateLineDown($rowid, $rang, $max);
4368
    }
4369
4370
    /**
4371
     *  Get max value used for position of line (rang)
4372
     *
4373
     * @param int $fk_parent_line Parent line id
4374
     * @return     int                         Max value of rang in table of lines
4375
     */
4376
    public function line_max($fk_parent_line = 0)
4377
    {
4378
        // phpcs:enable
4379
        $positionfield = 'rang';
4380
        if (in_array($this->table_element, array('bom_bom', 'product_attribute'))) {
4381
            $positionfield = 'position';
4382
        }
4383
4384
        // Search the last rang with fk_parent_line
4385
        if ($fk_parent_line) {
4386
            $sql = "SELECT max(" . $positionfield . ") FROM " . $this->db->prefix() . $this->table_element_line;
4387
            $sql .= " WHERE " . $this->fk_element . " = " . ((int)$this->id);
4388
            $sql .= " AND fk_parent_line = " . ((int)$fk_parent_line);
4389
4390
            dol_syslog(get_only_class($this) . "::line_max", LOG_DEBUG);
4391
            $resql = $this->db->query($sql);
4392
            if ($resql) {
4393
                $row = $this->db->fetch_row($resql);
4394
                if (!empty($row[0])) {
4395
                    return $row[0];
4396
                } else {
4397
                    return $this->getRangOfLine($fk_parent_line);
4398
                }
4399
            }
4400
        } else {
4401
            // If not, search the last rang of element
4402
            $sql = "SELECT max(" . $positionfield . ") FROM " . $this->db->prefix() . $this->table_element_line;
4403
            $sql .= " WHERE " . $this->fk_element . " = " . ((int)$this->id);
4404
4405
            dol_syslog(get_only_class($this) . "::line_max", LOG_DEBUG);
4406
            $resql = $this->db->query($sql);
4407
            if ($resql) {
4408
                $row = $this->db->fetch_row($resql);
4409
                return $row[0];
4410
            }
4411
        }
4412
4413
        return 0;
4414
    }
4415
4416
    /**
4417
     *  Update position of line down (rang)
4418
     *
4419
     * @param int $rowid Id of line
4420
     * @param int $rang Position
4421
     * @param int $max Max
4422
     * @return void
4423
     */
4424
    public function updateLineDown($rowid, $rang, $max)
4425
    {
4426
        if ($rang < $max) {
4427
            $fieldposition = 'rang';
4428
            if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
4429
                $fieldposition = 'position';
4430
            }
4431
4432
            $sql = "UPDATE " . $this->db->prefix() . $this->table_element_line . " SET " . $fieldposition . " = " . ((int)$rang);
4433
            $sql .= " WHERE " . $this->fk_element . " = " . ((int)$this->id);
4434
            $sql .= " AND " . $fieldposition . " = " . ((int)($rang + 1));
4435
            if ($this->db->query($sql)) {
4436
                $sql = "UPDATE " . $this->db->prefix() . $this->table_element_line . " SET " . $fieldposition . " = " . ((int)($rang + 1));
4437
                $sql .= ' WHERE rowid = ' . ((int)$rowid);
4438
                if (!$this->db->query($sql)) {
4439
                    dol_print_error($this->db);
4440
                }
4441
            } else {
4442
                dol_print_error($this->db);
4443
            }
4444
        }
4445
    }
4446
4447
    /**
4448
     *  Update position of line with ajax (rang)
4449
     *
4450
     * @param int[] $rows Array of rows
4451
     * @return void
4452
     */
4453
    public function line_ajaxorder($rows)
4454
    {
4455
        // phpcs:enable
4456
        $num = count($rows);
4457
        for ($i = 0; $i < $num; $i++) {
4458
            $this->updateRangOfLine($rows[$i], ($i + 1));
4459
        }
4460
    }
4461
4462
    /**
4463
     *  Get rowid of the line relative to its position
4464
     *
4465
     * @param int $rang Rang value
4466
     * @return     int                 Rowid of the line
4467
     */
4468
    public function getIdOfLine($rang)
4469
    {
4470
        $fieldposition = 'rang';
4471
        if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
4472
            $fieldposition = 'position';
4473
        }
4474
4475
        $sql = "SELECT rowid FROM " . $this->db->prefix() . $this->table_element_line;
4476
        $sql .= " WHERE " . $this->fk_element . " = " . ((int)$this->id);
4477
        $sql .= " AND " . $fieldposition . " = " . ((int)$rang);
4478
        $resql = $this->db->query($sql);
4479
        if ($resql) {
4480
            $row = $this->db->fetch_row($resql);
4481
            return $row[0];
4482
        }
4483
4484
        return 0;
4485
    }
4486
4487
    /**
4488
     *  Update external ref of element
4489
     *
4490
     * @param string $ref_ext Update field ref_ext
4491
     * @return     int                     Return integer <0 if KO, >0 if OK
4492
     */
4493
    public function update_ref_ext($ref_ext)
4494
    {
4495
        // phpcs:enable
4496
        if (!$this->table_element) {
4497
            dol_syslog(get_only_class($this) . "::update_ref_ext was called on object with property table_element not defined", LOG_ERR);
4498
            return -1;
4499
        }
4500
4501
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
4502
        $sql .= " SET ref_ext = '" . $this->db->escape($ref_ext) . "'";
4503
        $sql .= " WHERE " . (isset($this->table_rowid) ? $this->table_rowid : 'rowid') . " = " . ((int)$this->id);
0 ignored issues
show
Bug Best Practice introduced by
The property table_rowid does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
4504
4505
        dol_syslog(get_only_class($this) . "::update_ref_ext", LOG_DEBUG);
4506
        if ($this->db->query($sql)) {
4507
            $this->ref_ext = $ref_ext;
4508
            return 1;
4509
        } else {
4510
            $this->error = $this->db->error();
4511
            return -1;
4512
        }
4513
    }
4514
4515
    /**
4516
     *  Update public note (kept for backward compatibility)
4517
     *
4518
     * @param string $note New value for note
4519
     * @return     int                      Return integer <0 if KO, >0 if OK
4520
     * @deprecated
4521
     * @see update_note()
4522
     */
4523
    public function update_note_public($note)
4524
    {
4525
        // phpcs:enable
4526
        return $this->update_note($note, '_public');
4527
    }
4528
4529
    /**
4530
     *  Update note of element
4531
     *
4532
     * @param string $note New value for note
4533
     * @param string $suffix '', '_public' or '_private'
4534
     * @param int $notrigger 1=Does not execute triggers, 0=execute triggers
4535
     * @return     int                     Return integer <0 if KO, >0 if OK
4536
     */
4537
    public function update_note($note, $suffix = '', $notrigger = 0)
4538
    {
4539
        // phpcs:enable
4540
        global $user;
4541
4542
        if (!$this->table_element) {
4543
            $this->error = 'update_note was called on object with property table_element not defined';
4544
            dol_syslog(get_only_class($this) . "::update_note was called on object with property table_element not defined", LOG_ERR);
4545
            return -1;
4546
        }
4547
        if (!in_array($suffix, array('', '_public', '_private'))) {
4548
            $this->error = 'update_note Parameter suffix must be empty, \'_private\' or \'_public\'';
4549
            dol_syslog(get_only_class($this) . "::update_note Parameter suffix must be empty, '_private' or '_public'", LOG_ERR);
4550
            return -2;
4551
        }
4552
4553
        $newsuffix = $suffix;
4554
4555
        // Special case
4556
        if ($this->table_element == 'product' && $newsuffix == '_private') {
4557
            $newsuffix = '';
4558
        }
4559
        if (in_array($this->table_element, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
4560
            $fieldusermod = "fk_user_mod";
4561
        } elseif ($this->table_element == 'ecm_files') {
4562
            $fieldusermod = "fk_user_m";
4563
        } else {
4564
            $fieldusermod = "fk_user_modif";
4565
        }
4566
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element;
4567
        $sql .= " SET note" . $newsuffix . " = " . (!empty($note) ? ("'" . $this->db->escape($note) . "'") : "NULL");
4568
        $sql .= ", " . $fieldusermod . " = " . ((int)$user->id);
4569
        $sql .= " WHERE rowid = " . ((int)$this->id);
4570
4571
        dol_syslog(get_only_class($this) . "::update_note", LOG_DEBUG);
4572
        if ($this->db->query($sql)) {
4573
            if ($suffix == '_public') {
4574
                $this->note_public = $note;
4575
            } elseif ($suffix == '_private') {
4576
                $this->note_private = $note;
4577
            } else {
4578
                $this->note = $note; // deprecated
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$note has been deprecated: Use $note_private instead. ( Ignorable by Annotation )

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

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

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

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

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

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

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

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

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

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

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

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

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

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