Passed
Pull Request — dev (#6)
by Rafael
79:24 queued 24:08
created

CommonObject   F

Complexity

Total Complexity 2285

Size/Duplication

Total Lines 10847
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 5537
dl 0
loc 10847
rs 0.8
c 0
b 0
f 0
wmc 2285

149 Methods

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

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

1173
                /** @scrutinizer ignore-call */ 
1174
                $value = $this->getNomUrl(1, '', 0, '', 1);
Loading history...
1174
            }
1175
        } elseif ($key == 'status' && method_exists($this, 'getLibStatut')) {
1176
            $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

1176
            /** @scrutinizer ignore-call */ 
1177
            $value = $this->getLibStatut(3);
Loading history...
1177
        } elseif ($type == 'date') {
1178
            if (!empty($value)) {
1179
                $value = dol_print_date($value, 'day'); // We suppose dates without time are always gmt (storage of course + output)
1180
            } else {
1181
                $value = '';
1182
            }
1183
        } elseif ($type == 'datetime' || $type == 'timestamp') {
1184
            if (!empty($value)) {
1185
                $value = dol_print_date($value, 'dayhour', 'tzuserrel');
1186
            } else {
1187
                $value = '';
1188
            }
1189
        } elseif ($type == 'duration') {
1190
            include_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php';
1191
            if (!is_null($value) && $value !== '') {
1192
                $value = convertSecondToTime($value, 'allhourmin');
1193
            }
1194
        } elseif ($type == 'double' || $type == 'real') {
1195
            if (!is_null($value) && $value !== '') {
1196
                $value = price($value);
1197
            }
1198
        } elseif ($type == 'boolean') {
1199
            $checked = '';
1200
            if (!empty($value)) {
1201
                $checked = ' checked ';
1202
            }
1203
            if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2) {
1204
                $value = '<input type="checkbox" ' . $checked . ' ' . ($moreparam ? $moreparam : '') . ' readonly disabled>';
1205
            } else {
1206
                $value = yn($value ? 1 : 0);
1207
            }
1208
        } elseif ($type == 'mail' || $type == 'email') {
1209
            $value = dol_print_email($value, 0, 0, 0, 64, 1, 1);
1210
        } elseif ($type == 'url') {
1211
            $value = dol_print_url($value, '_blank', 32, 1);
1212
        } elseif ($type == 'phone') {
1213
            $value = dol_print_phone($value, '', 0, 0, '', '&nbsp;', 'phone');
1214
        } elseif ($type == 'ip') {
1215
            $value = dol_print_ip($value, 0);
1216
        } elseif ($type == 'price') {
1217
            if (!is_null($value) && $value !== '') {
1218
                $value = price($value, 0, $langs, 0, 0, -1, $conf->currency);
1219
            }
1220
        } elseif ($type == 'select') {
1221
            $value = isset($param['options'][(string)$value]) ? $param['options'][(string)$value] : '';
1222
            if (strpos($value, "|") !== false) {
1223
                $value = $langs->trans(explode('|', $value)[0]);
1224
            } elseif (!is_numeric($value)) {
1225
                $value = $langs->trans($value);
1226
            }
1227
        } elseif ($type == 'sellist') {
1228
            $param_list = array_keys($param['options']);
1229
            $InfoFieldList = explode(":", $param_list[0]);
1230
1231
            $selectkey = "rowid";
1232
            $keyList = 'rowid';
1233
1234
            if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
1235
                $selectkey = $InfoFieldList[2];
1236
                $keyList = $InfoFieldList[2] . ' as rowid';
1237
            }
1238
1239
            $fields_label = explode('|', $InfoFieldList[1]);
1240
            if (is_array($fields_label)) {
1241
                $keyList .= ', ';
1242
                $keyList .= implode(', ', $fields_label);
1243
            }
1244
1245
            $filter_categorie = false;
1246
            if (count($InfoFieldList) > 5) {
1247
                if ($InfoFieldList[0] == 'categorie') {
1248
                    $filter_categorie = true;
1249
                }
1250
            }
1251
1252
            $sql = "SELECT " . $keyList;
1253
            $sql .= ' FROM ' . $this->db->prefix() . $InfoFieldList[0];
1254
            if (strpos($InfoFieldList[4], 'extra') !== false) {
1255
                $sql .= ' as main';
1256
            }
1257
            if ($selectkey == 'rowid' && empty($value)) {
1258
                $sql .= " WHERE " . $selectkey . " = 0";
1259
            } elseif ($selectkey == 'rowid') {
1260
                $sql .= " WHERE " . $selectkey . " = " . ((int)$value);
1261
            } else {
1262
                $sql .= " WHERE " . $selectkey . " = '" . $this->db->escape($value) . "'";
1263
            }
1264
1265
            //$sql.= ' AND entity = '.$conf->entity;
1266
1267
            dol_syslog(get_class($this) . ':showOutputField:$type=sellist', LOG_DEBUG);
1268
            $resql = $this->db->query($sql);
1269
            if ($resql) {
1270
                if (!$filter_categorie) {
1271
                    $value = ''; // value was used, so now we reste it to use it to build final output
1272
                    $numrows = $this->db->num_rows($resql);
1273
                    if ($numrows) {
1274
                        $obj = $this->db->fetch_object($resql);
1275
1276
                        // Several field into label (eq table:code|libelle:rowid)
1277
                        $fields_label = explode('|', $InfoFieldList[1]);
1278
1279
                        if (is_array($fields_label) && count($fields_label) > 1) {
1280
                            foreach ($fields_label as $field_toshow) {
1281
                                $translabel = '';
1282
                                if (!empty($obj->$field_toshow)) {
1283
                                    $translabel = $langs->trans($obj->$field_toshow);
1284
                                }
1285
                                if ($translabel != $field_toshow) {
1286
                                    $value .= dol_trunc($translabel, 18) . ' ';
1287
                                } else {
1288
                                    $value .= $obj->$field_toshow . ' ';
1289
                                }
1290
                            }
1291
                        } else {
1292
                            $translabel = '';
1293
                            if (!empty($obj->{$InfoFieldList[1]})) {
1294
                                $translabel = $langs->trans($obj->{$InfoFieldList[1]});
1295
                            }
1296
                            if ($translabel != $obj->{$InfoFieldList[1]}) {
1297
                                $value = dol_trunc($translabel, 18);
1298
                            } else {
1299
                                $value = $obj->{$InfoFieldList[1]};
1300
                            }
1301
                        }
1302
                    }
1303
                } else {
1304
                    $toprint = array();
1305
                    $obj = $this->db->fetch_object($resql);
1306
                    $c = new Categorie($this->db);
1307
                    $c->fetch($obj->rowid);
1308
                    $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
1309
                    foreach ($ways as $way) {
1310
                        $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
1311
                    }
1312
                    $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
1313
                }
1314
            } else {
1315
                dol_syslog(get_class($this) . '::showOutputField error ' . $this->db->lasterror(), LOG_WARNING);
1316
            }
1317
        } elseif ($type == 'radio') {
1318
            $value = $param['options'][(string)$value];
1319
        } elseif ($type == 'checkbox') {
1320
            $value_arr = explode(',', (string)$value);
1321
            $value = '';
1322
            if (is_array($value_arr) && count($value_arr) > 0) {
1323
                $toprint = array();
1324
                foreach ($value_arr as $keyval => $valueval) {
1325
                    if (!empty($valueval)) {
1326
                        $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $param['options'][$valueval] . '</li>';
1327
                    }
1328
                }
1329
                if (!empty($toprint)) {
1330
                    $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
1331
                }
1332
            }
1333
        } elseif ($type == 'chkbxlst') {
1334
            $value_arr = (isset($value) ? explode(',', $value) : array());
1335
1336
            $param_list = array_keys($param['options']);
1337
            $InfoFieldList = explode(":", $param_list[0]);
1338
1339
            $selectkey = "rowid";
1340
            $keyList = 'rowid';
1341
1342
            if (count($InfoFieldList) >= 3) {
1343
                $selectkey = $InfoFieldList[2];
1344
                $keyList = $InfoFieldList[2] . ' as rowid';
1345
            }
1346
1347
            $fields_label = explode('|', $InfoFieldList[1]);
1348
            if (is_array($fields_label)) {
1349
                $keyList .= ', ';
1350
                $keyList .= implode(', ', $fields_label);
1351
            }
1352
1353
            $filter_categorie = false;
1354
            if (count($InfoFieldList) > 5) {
1355
                if ($InfoFieldList[0] == 'categorie') {
1356
                    $filter_categorie = true;
1357
                }
1358
            }
1359
1360
            $sql = "SELECT " . $keyList;
1361
            $sql .= ' FROM ' . $this->db->prefix() . $InfoFieldList[0];
1362
            if (strpos($InfoFieldList[4], 'extra') !== false) {
1363
                $sql .= ' as main';
1364
            }
1365
            // $sql.= " WHERE ".$selectkey."='".$this->db->escape($value)."'";
1366
            // $sql.= ' AND entity = '.$conf->entity;
1367
1368
            dol_syslog(get_class($this) . ':showOutputField:$type=chkbxlst', LOG_DEBUG);
1369
            $resql = $this->db->query($sql);
1370
            if ($resql) {
1371
                if (!$filter_categorie) {
1372
                    $value = ''; // value was used, so now we reset it to use it to build final output
1373
                    $toprint = array();
1374
                    while ($obj = $this->db->fetch_object($resql)) {
1375
                        // Several field into label (eq table:code|libelle:rowid)
1376
                        $fields_label = explode('|', $InfoFieldList[1]);
1377
                        if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
1378
                            if (is_array($fields_label) && count($fields_label) > 1) {
1379
                                foreach ($fields_label as $field_toshow) {
1380
                                    $translabel = '';
1381
                                    if (!empty($obj->$field_toshow)) {
1382
                                        $translabel = $langs->trans($obj->$field_toshow);
1383
                                    }
1384
                                    if ($translabel != $field_toshow) {
1385
                                        $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
1386
                                    } else {
1387
                                        $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->$field_toshow . '</li>';
1388
                                    }
1389
                                }
1390
                            } else {
1391
                                $translabel = '';
1392
                                if (!empty($obj->{$InfoFieldList[1]})) {
1393
                                    $translabel = $langs->trans($obj->{$InfoFieldList[1]});
1394
                                }
1395
                                if ($translabel != $obj->{$InfoFieldList[1]}) {
1396
                                    $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . dol_trunc($translabel, 18) . '</li>';
1397
                                } else {
1398
                                    $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">' . $obj->{$InfoFieldList[1]} . '</li>';
1399
                                }
1400
                            }
1401
                        }
1402
                    }
1403
                } else {
1404
                    $toprint = array();
1405
                    while ($obj = $this->db->fetch_object($resql)) {
1406
                        if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
1407
                            $c = new Categorie($this->db);
1408
                            $c->fetch($obj->rowid);
1409
                            $ways = $c->print_all_ways(); // $ways[0] = "ccc2 >> ccc2a >> ccc2a1" with html formatted text
1410
                            foreach ($ways as $way) {
1411
                                $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories"' . ($c->color ? ' style="background: #' . $c->color . ';"' : ' style="background: #aaa"') . '>' . img_object('', 'category') . ' ' . $way . '</li>';
1412
                            }
1413
                        }
1414
                    }
1415
                }
1416
                $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
1417
            } else {
1418
                dol_syslog(get_class($this) . '::showOutputField error ' . $this->db->lasterror(), LOG_WARNING);
1419
            }
1420
        } elseif ($type == 'link') {
1421
            $out = '';
1422
1423
            // only if something to display (perf)
1424
            if ($value) {
1425
                $param_list = array_keys($param['options']);
1426
                // Example: $param_list='ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
1427
                // Example: $param_list='ObjectClass:PathToClass:#getnomurlparam1=-1#getnomurlparam2=customer'
1428
1429
                $InfoFieldList = explode(":", $param_list[0]);
1430
1431
                $classname = $InfoFieldList[0];
1432
                $classpath = $InfoFieldList[1];
1433
1434
                // Set $getnomurlparam1 et getnomurlparam2
1435
                $getnomurlparam = 3;
1436
                $getnomurlparam2 = '';
1437
                $regtmp = array();
1438
                if (preg_match('/#getnomurlparam1=([^#]*)/', $param_list[0], $regtmp)) {
1439
                    $getnomurlparam = $regtmp[1];
1440
                }
1441
                if (preg_match('/#getnomurlparam2=([^#]*)/', $param_list[0], $regtmp)) {
1442
                    $getnomurlparam2 = $regtmp[1];
1443
                }
1444
1445
                if (!empty($classpath)) {
1446
                    dol_include_once($InfoFieldList[1]);
1447
                    if ($classname && class_exists($classname)) {
1448
                        $object = new $classname($this->db);
1449
                        if ($object->element === 'product') {   // Special case for product because default valut of fetch are wrong
1450
                            $result = $object->fetch($value, '', '', '', 0, 1, 1);
1451
                        } else {
1452
                            $result = $object->fetch($value);
1453
                        }
1454
                        if ($result > 0) {
1455
                            if ($object->element === 'product') {
1456
                                $get_name_url_param_arr = array($getnomurlparam, $getnomurlparam2, 0, -1, 0, '', 0);
1457
                                if (isset($val['get_name_url_params'])) {
1458
                                    $get_name_url_params = explode(':', $val['get_name_url_params']);
1459
                                    if (!empty($get_name_url_params)) {
1460
                                        $param_num_max = count($get_name_url_param_arr) - 1;
1461
                                        foreach ($get_name_url_params as $param_num => $param_value) {
1462
                                            if ($param_num > $param_num_max) {
1463
                                                break;
1464
                                            }
1465
                                            $get_name_url_param_arr[$param_num] = $param_value;
1466
                                        }
1467
                                    }
1468
                                }
1469
1470
                                /**
1471
                                 * @var Product $object
1472
                                 */
1473
                                $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]);
1474
                            } else {
1475
                                $value = $object->getNomUrl($getnomurlparam, $getnomurlparam2);
1476
                            }
1477
                        } else {
1478
                            $value = '';
1479
                        }
1480
                    }
1481
                } else {
1482
                    dol_syslog('Error bad setup of extrafield', LOG_WARNING);
1483
                    return 'Error bad setup of extrafield';
1484
                }
1485
            } else {
1486
                $value = '';
1487
            }
1488
        } elseif ($type == 'password') {
1489
            $value = '<span class="opacitymedium">' . $langs->trans("Encrypted") . '</span>';
1490
            //$value = preg_replace('/./i', '*', $value);
1491
        } elseif ($type == 'array') {
1492
            if (is_array($value)) {
1493
                $value = implode('<br>', $value);
1494
            } else {
1495
                dol_syslog(__METHOD__ . ' Expected array from dol_eval, but got ' . gettype($value), LOG_ERR);
1496
                return 'Error unexpected result from code evaluation';
1497
            }
1498
        } else {    // text|html|varchar
1499
            $value = dol_htmlentitiesbr($value);
1500
        }
1501
1502
        //print $type.'-'.$size.'-'.$value;
1503
        $out = $value;
1504
1505
        return is_null($out) ? '' : $out;
1506
    }
1507
1508
1509
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1510
1511
    /**
1512
     * Method to output saved errors
1513
     *
1514
     * @return  string      String with errors
1515
     */
1516
    public function errorsToString()
1517
    {
1518
        return $this->error . (is_array($this->errors) ? (($this->error != '' ? ', ' : '') . implode(', ', $this->errors)) : '');
1519
    }
1520
1521
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1522
1523
    /**
1524
     * Return customer ref for screen output.
1525
     *
1526
     * @param string $objref Customer ref
1527
     * @return string                     Customer ref formatted
1528
     */
1529
    public function getFormatedCustomerRef($objref)
1530
    {
1531
        global $hookmanager;
1532
1533
        $parameters = array('objref' => $objref);
1534
        $action = '';
1535
        $reshook = $hookmanager->executeHooks('getFormatedCustomerRef', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1536
        if ($reshook > 0) {
1537
            return $hookmanager->resArray['objref'];
1538
        }
1539
        return $objref . (isset($hookmanager->resArray['objref']) ? $hookmanager->resArray['objref'] : '');
1540
    }
1541
1542
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1543
1544
    /**
1545
     * Return supplier ref for screen output.
1546
     *
1547
     * @param string $objref Supplier ref
1548
     * @return string                     Supplier ref formatted
1549
     */
1550
    public function getFormatedSupplierRef($objref)
1551
    {
1552
        global $hookmanager;
1553
1554
        $parameters = array('objref' => $objref);
1555
        $action = '';
1556
        $reshook = $hookmanager->executeHooks('getFormatedSupplierRef', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1557
        if ($reshook > 0) {
1558
            return $hookmanager->resArray['objref'];
1559
        }
1560
        return $objref . (isset($hookmanager->resArray['objref']) ? $hookmanager->resArray['objref'] : '');
1561
    }
1562
1563
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1564
1565
    /**
1566
     *  Return full address of contact
1567
     *
1568
     * @param int<0,1> $withcountry 1=Add country into address string
1569
     * @param string $sep Separator to use to build string
1570
     * @param int<0,1> $withregion 1=Add region into address string
1571
     * @param string $extralangcode User extralanguages as value
1572
     * @return     string                          Full address string
1573
     */
1574
    public function getFullAddress($withcountry = 0, $sep = "\n", $withregion = 0, $extralangcode = '')
1575
    {
1576
        if ($withcountry && $this->country_id && (empty($this->country_code) || empty($this->country))) {
1577
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/company.lib.php';
1578
            $tmparray = getCountry($this->country_id, 'all');
1579
            $this->country_code = $tmparray['code'];
1580
            $this->country = $tmparray['label'];
1581
        }
1582
1583
        if ($withregion && $this->state_id && (empty($this->state_code) || empty($this->state) || empty($this->region) || empty($this->region_code))) {
1584
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/company.lib.php';
1585
            $tmparray = getState($this->state_id, 'all', null, 1);
1586
            $this->state_code = $tmparray['code'];
1587
            $this->state = $tmparray['label'];
1588
            $this->region_code = $tmparray['region_code'];
1589
            $this->region = $tmparray['region'];
1590
        }
1591
1592
        return dol_format_address($this, $withcountry, $sep, null, 0, $extralangcode);
1593
    }
1594
1595
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1596
1597
    /**
1598
     * Return the link of last main doc file for direct public download.
1599
     *
1600
     * @param string $modulepart Module related to document
1601
     * @param int $initsharekey Init the share key if it was not yet defined
1602
     * @param int $relativelink 0=Return full external link, 1=Return link relative to root of file
1603
     * @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...
1604
     */
1605
    public function getLastMainDocLink($modulepart, $initsharekey = 0, $relativelink = 0)
1606
    {
1607
        global $user, $dolibarr_main_url_root;
1608
1609
        if (empty($this->last_main_doc)) {
1610
            return ''; // No way to known which document name to use
1611
        }
1612
1613
        $ecmfile = new EcmFiles($this->db);
1614
        $result = $ecmfile->fetch(0, '', $this->last_main_doc);
1615
        if ($result < 0) {
1616
            $this->error = $ecmfile->error;
1617
            $this->errors = $ecmfile->errors;
1618
            return -1;
1619
        }
1620
1621
        if (empty($ecmfile->id)) {  // No entry into file index already exists, we should initialize the shared key manually.
1622
            // Add entry into index
1623
            if ($initsharekey) {
1624
                require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/security2.lib.php';
1625
1626
                // 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
1627
                /*
1628
                $ecmfile->filepath = $rel_dir;
1629
                $ecmfile->filename = $filename;
1630
                $ecmfile->label = md5_file(dol_osencode($destfull));    // hash of file content
1631
                $ecmfile->fullpath_orig = '';
1632
                $ecmfile->gen_or_uploaded = 'generated';
1633
                $ecmfile->description = '';    // indexed content
1634
                $ecmfile->keywords = '';        // keyword content
1635
                $ecmfile->share = getRandomPassword(true);
1636
                $result = $ecmfile->create($user);
1637
                if ($result < 0)
1638
                {
1639
                    $this->error = $ecmfile->error;
1640
                    $this->errors = $ecmfile->errors;
1641
                }
1642
                */
1643
            } else {
1644
                return '';
1645
            }
1646
        } elseif (empty($ecmfile->share)) { // Entry into file index already exists but no share key is defined.
1647
            // Add entry into index
1648
            if ($initsharekey) {
1649
                require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/security2.lib.php';
1650
                $ecmfile->share = getRandomPassword(true);
1651
                $ecmfile->update($user);
1652
            } else {
1653
                return '';
1654
            }
1655
        }
1656
        // Define $urlwithroot
1657
        $urlwithouturlroot = preg_replace('/' . preg_quote(DOL_URL_ROOT, '/') . '$/i', '', trim($dolibarr_main_url_root));
1658
        // This is to use external domain name found into config file
1659
        //if (DOL_URL_ROOT && ! preg_match('/\/$/', $urlwithouturlroot) && ! preg_match('/^\//', DOL_URL_ROOT)) $urlwithroot=$urlwithouturlroot.'/'.DOL_URL_ROOT;
1660
        //else
1661
        $urlwithroot = $urlwithouturlroot . DOL_URL_ROOT;
1662
        //$urlwithroot=DOL_MAIN_URL_ROOT;                   // This is to use same domain name than current
1663
1664
        $forcedownload = 0;
1665
1666
        $paramlink = '';
1667
        //if (!empty($modulepart)) $paramlink.=($paramlink?'&':'').'modulepart='.$modulepart;       // For sharing with hash (so public files), modulepart is not required.
1668
        //if (!empty($ecmfile->entity)) $paramlink.='&entity='.$ecmfile->entity;                    // For sharing with hash (so public files), entity is not required.
1669
        //$paramlink.=($paramlink?'&':'').'file='.urlencode($filepath);                             // No need of name of file for public link, we will use the hash
1670
        if (!empty($ecmfile->share)) {
1671
            $paramlink .= ($paramlink ? '&' : '') . 'hashp=' . $ecmfile->share; // Hash for public share
1672
        }
1673
        if ($forcedownload) {
1674
            $paramlink .= ($paramlink ? '&' : '') . 'attachment=1';
1675
        }
1676
1677
        if ($relativelink) {
1678
            $linktoreturn = 'document.php' . ($paramlink ? '?' . $paramlink : '');
1679
        } else {
1680
            $linktoreturn = $urlwithroot . '/document.php' . ($paramlink ? '?' . $paramlink : '');
1681
        }
1682
1683
        // Here $ecmfile->share is defined
1684
        return $linktoreturn;
1685
    }
1686
1687
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1688
1689
    /**
1690
     *    Copy contact from one element to current
1691
     *
1692
     * @param CommonObject $objFrom Source element
1693
     * @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...
1694
     * @return   int                         >0 if OK, <0 if KO
1695
     */
1696
    public function copy_linked_contact($objFrom, $source = 'internal')
1697
    {
1698
        // phpcs:enable
1699
        $contacts = $objFrom->liste_contact(-1, $source);
1700
        foreach ($contacts as $contact) {
1701
            if ($this->add_contact($contact['id'], $contact['fk_c_type_contact'], $contact['source']) < 0) {
1702
                return -1;
1703
            }
1704
        }
1705
        return 1;
1706
    }
1707
1708
    /**
1709
     *    Get array of all contacts for an object
1710
     *
1711
     * @param int $statusoflink Status of links to get (-1=all). Not used.
1712
     * @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...
1713
     * @param int<0,1> $list 0:Returned array contains all properties, 1:Return array contains just id
1714
     * @param string $code Filter on this code of contact type ('SHIPPING', 'BILLING', ...)
1715
     * @param int $status Status of user or company
1716
     * @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.
1717
     * @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
1718
     */
1719
    public function liste_contact($statusoflink = -1, $source = 'external', $list = 0, $code = '', $status = -1, $arrayoftcids = array())
1720
    {
1721
        // phpcs:enable
1722
        global $langs;
1723
1724
        $tab = array();
1725
1726
        $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
1727
        if ($source == 'internal') {
1728
            $sql .= ", '-1' as socid, t.statut as statuscontact, t.login, t.photo, t.gender";
1729
        }
1730
        if ($source == 'external' || $source == 'thirdparty') {
1731
            $sql .= ", t.fk_soc as socid, t.statut as statuscontact";
1732
        }
1733
        $sql .= ", t.civility as civility, t.lastname as lastname, t.firstname, t.email";
1734
        $sql .= ", tc.source, tc.element, tc.code, tc.libelle as type_label";
1735
        $sql .= " FROM " . $this->db->prefix() . "c_type_contact tc,";
1736
        $sql .= " " . $this->db->prefix() . "element_contact ec";
1737
        if ($source == 'internal') {    // internal contact (user)
1738
            $sql .= " LEFT JOIN " . $this->db->prefix() . "user t on ec.fk_socpeople = t.rowid";
1739
        }
1740
        if ($source == 'external' || $source == 'thirdparty') { // external contact (socpeople)
1741
            $sql .= " LEFT JOIN " . $this->db->prefix() . "socpeople t on ec.fk_socpeople = t.rowid";
1742
        }
1743
        $sql .= " WHERE ec.element_id = " . ((int)$this->id);
1744
        $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1745
        $sql .= " AND tc.element = '" . $this->db->escape($this->element) . "'";
1746
        if ($code) {
1747
            $sql .= " AND tc.code = '" . $this->db->escape($code) . "'";
1748
        }
1749
        if ($source == 'internal') {
1750
            $sql .= " AND tc.source = 'internal'";
1751
            if ($status >= 0) {
1752
                $sql .= " AND t.statut = " . ((int)$status);
1753
            }
1754
        }
1755
        if ($source == 'external' || $source == 'thirdparty') {
1756
            $sql .= " AND tc.source = 'external'";
1757
            if ($status >= 0) {
1758
                $sql .= " AND t.statut = " . ((int)$status); // t is llx_socpeople
1759
            }
1760
        }
1761
        $sql .= " AND tc.active = 1";
1762
        if ($statusoflink >= 0) {
1763
            $sql .= " AND ec.statut = " . ((int)$statusoflink);
1764
        }
1765
        $sql .= " ORDER BY t.lastname ASC";
1766
1767
        dol_syslog(get_class($this) . "::liste_contact", LOG_DEBUG);
1768
        $resql = $this->db->query($sql);
1769
        if ($resql) {
1770
            $num = $this->db->num_rows($resql);
1771
            $i = 0;
1772
            while ($i < $num) {
1773
                $obj = $this->db->fetch_object($resql);
1774
1775
                if (!$list) {
1776
                    $transkey = "TypeContact_" . $obj->element . "_" . $obj->source . "_" . $obj->code;
1777
                    $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1778
                    $tab[$i] = array(
1779
                        'parentId' => $this->id,
1780
                        'source' => $obj->source,
1781
                        'socid' => $obj->socid,
1782
                        'id' => $obj->id,
1783
                        'nom' => $obj->lastname, // For backward compatibility
1784
                        'civility' => $obj->civility,
1785
                        'lastname' => $obj->lastname,
1786
                        'firstname' => $obj->firstname,
1787
                        'email' => $obj->email,
1788
                        'login' => (empty($obj->login) ? '' : $obj->login),
1789
                        'photo' => (empty($obj->photo) ? '' : $obj->photo),
1790
                        'gender' => (empty($obj->gender) ? '' : $obj->gender),
1791
                        'statuscontact' => $obj->statuscontact,
1792
                        'rowid' => $obj->rowid,
1793
                        'code' => $obj->code,
1794
                        'libelle' => $libelle_type,
1795
                        'status' => $obj->statuslink,
1796
                        'fk_c_type_contact' => $obj->fk_c_type_contact
1797
                    );
1798
                } else {
1799
                    $tab[$i] = $obj->id;
1800
                }
1801
1802
                $i++;
1803
            }
1804
1805
            return $tab;
1806
        } else {
1807
            $this->error = $this->db->lasterror();
1808
            dol_print_error($this->db);
1809
            return -1;
1810
        }
1811
    }
1812
1813
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1814
1815
    /**
1816
     *  Add a link between element $this->element and a contact
1817
     *
1818
     * @param int $fk_socpeople Id of thirdparty contact (if source = 'external') or id of user (if source = 'internal') to link
1819
     * @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
1820
     * @param string $source external=Contact extern (llx_socpeople), internal=Contact intern (llx_user)
1821
     * @param int $notrigger Disable all triggers
1822
     * @return int                             Return integer <0 if KO, 0 if already added or code not valid, >0 if OK
1823
     */
1824
    public function add_contact($fk_socpeople, $type_contact, $source = 'external', $notrigger = 0)
1825
    {
1826
        // phpcs:enable
1827
        global $user, $langs;
1828
1829
1830
        dol_syslog(get_class($this) . "::add_contact $fk_socpeople, $type_contact, $source, $notrigger");
1831
1832
        // Check parameters
1833
        if ($fk_socpeople <= 0) {
1834
            $langs->load("errors");
1835
            $this->error = $langs->trans("ErrorWrongValueForParameterX", "1");
1836
            dol_syslog(get_class($this) . "::add_contact " . $this->error, LOG_ERR);
1837
            return -1;
1838
        }
1839
        if (!$type_contact) {
1840
            $langs->load("errors");
1841
            $this->error = $langs->trans("ErrorWrongValueForParameterX", "2");
1842
            dol_syslog(get_class($this) . "::add_contact " . $this->error, LOG_ERR);
1843
            return -2;
1844
        }
1845
1846
        $id_type_contact = 0;
1847
        if (is_numeric($type_contact)) {
1848
            $id_type_contact = $type_contact;
1849
        } else {
1850
            // We look for id type_contact
1851
            $sql = "SELECT tc.rowid";
1852
            $sql .= " FROM " . $this->db->prefix() . "c_type_contact as tc";
1853
            $sql .= " WHERE tc.element='" . $this->db->escape($this->element) . "'";
1854
            $sql .= " AND tc.source='" . $this->db->escape($source) . "'";
1855
            $sql .= " AND tc.code='" . $this->db->escape($type_contact) . "' AND tc.active=1";
1856
            //print $sql;
1857
            $resql = $this->db->query($sql);
1858
            if ($resql) {
1859
                $obj = $this->db->fetch_object($resql);
1860
                if ($obj) {
1861
                    $id_type_contact = $obj->rowid;
1862
                }
1863
            }
1864
        }
1865
1866
        if ($id_type_contact == 0) {
1867
            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");
1868
            return 0;
1869
        }
1870
1871
        $datecreate = dol_now();
1872
1873
        // Socpeople must have already been added by some trigger, then we have to check it to avoid DB_ERROR_RECORD_ALREADY_EXISTS error
1874
        $TListeContacts = $this->liste_contact(-1, $source);
1875
        $already_added = false;
1876
        if (is_array($TListeContacts) && !empty($TListeContacts)) {
1877
            foreach ($TListeContacts as $array_contact) {
1878
                if ($array_contact['status'] == 4 && $array_contact['id'] == $fk_socpeople && $array_contact['fk_c_type_contact'] == $id_type_contact) {
1879
                    $already_added = true;
1880
                    break;
1881
                }
1882
            }
1883
        }
1884
1885
        if (!$already_added) {
1886
            $this->db->begin();
1887
1888
            // Insert into database
1889
            $sql = "INSERT INTO " . $this->db->prefix() . "element_contact";
1890
            $sql .= " (element_id, fk_socpeople, datecreate, statut, fk_c_type_contact) ";
1891
            $sql .= " VALUES (" . $this->id . ", " . ((int)$fk_socpeople) . " , ";
1892
            $sql .= "'" . $this->db->idate($datecreate) . "'";
1893
            $sql .= ", 4, " . ((int)$id_type_contact);
1894
            $sql .= ")";
1895
1896
            $resql = $this->db->query($sql);
1897
            if ($resql) {
1898
                if (!$notrigger) {
1899
                    $result = $this->call_trigger(strtoupper($this->element) . '_ADD_CONTACT', $user);
1900
                    if ($result < 0) {
1901
                        $this->db->rollback();
1902
                        return -1;
1903
                    }
1904
                }
1905
1906
                $this->db->commit();
1907
                return 1;
1908
            } else {
1909
                if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1910
                    $this->error = $this->db->errno();
1911
                    $this->db->rollback();
1912
                    return -2;
1913
                } else {
1914
                    $this->error = $this->db->lasterror();
1915
                    $this->db->rollback();
1916
                    return -1;
1917
                }
1918
            }
1919
        } else {
1920
            return 0;
1921
        }
1922
    }
1923
1924
    /**
1925
     * Call trigger based on this instance.
1926
     * Some context information may also be provided into array property this->context.
1927
     * NB:  Error from trigger are stacked in interface->errors
1928
     * NB2: If return code of triggers are < 0, action calling trigger should cancel all transaction.
1929
     *
1930
     * @param string $triggerName trigger's name to execute
1931
     * @param User $user Object user
1932
     * @return  int                       Result of run_triggers
1933
     */
1934
    public function call_trigger($triggerName, $user)
1935
    {
1936
        // phpcs:enable
1937
        global $langs, $conf;
1938
1939
        if (!empty(self::TRIGGER_PREFIX) && strpos($triggerName, self::TRIGGER_PREFIX . '_') !== 0) {
1940
            dol_print_error(null, 'The trigger "' . $triggerName . '" does not start with "' . self::TRIGGER_PREFIX . '_" as required.');
1941
            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...
1942
        }
1943
        if (!is_object($langs)) {   // If lang was not defined, we set it. It is required by run_triggers().
1944
            include_once DOL_DOCUMENT_ROOT . '/core/class/translate.class.php';
1945
            $langs = new Translate('', $conf);
1946
        }
1947
1948
        include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
1949
        $interface = new Interfaces($this->db);
1950
        $result = $interface->run_triggers($triggerName, $this, $user, $langs, $conf);
1951
1952
        if ($result < 0) {
1953
            if (!empty($this->errors)) {
1954
                $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.
1955
            } else {
1956
                $this->errors = $interface->errors;
1957
            }
1958
        }
1959
        return $result;
1960
    }
1961
1962
    /**
1963
     *    Delete a link to contact line
1964
     *
1965
     * @param int $rowid Id of contact link line to delete
1966
     * @param int $notrigger Disable all triggers
1967
     * @return   int                     >0 if OK, <0 if KO
1968
     */
1969
    public function delete_contact($rowid, $notrigger = 0)
1970
    {
1971
        // phpcs:enable
1972
        global $user;
1973
1974
        $error = 0;
1975
1976
        $this->db->begin();
1977
1978
        if (!$error && empty($notrigger)) {
1979
            // Call trigger
1980
            $this->context['contact_id'] = ((int)$rowid);
1981
            $result = $this->call_trigger(strtoupper($this->element) . '_DELETE_CONTACT', $user);
1982
            if ($result < 0) {
1983
                $error++;
1984
            }
1985
            // End call triggers
1986
        }
1987
1988
        if (!$error) {
1989
            dol_syslog(get_class($this) . "::delete_contact", LOG_DEBUG);
1990
1991
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . "element_contact";
1992
            $sql .= " WHERE rowid = " . ((int)$rowid);
1993
1994
            $result = $this->db->query($sql);
1995
            if (!$result) {
1996
                $error++;
1997
                $this->errors[] = $this->db->lasterror();
1998
            }
1999
        }
2000
2001
        if (!$error) {
2002
            $this->db->commit();
2003
            return 1;
2004
        } else {
2005
            $this->error = $this->db->lasterror();
2006
            $this->db->rollback();
2007
            return -1;
2008
        }
2009
    }
2010
2011
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2012
2013
    /**
2014
     *    Delete all links between an object $this and all its contacts in llx_element_contact
2015
     *
2016
     * @param string $source '' or 'internal' or 'external'
2017
     * @param string $code Type of contact (code or id)
2018
     * @return   int                 Return integer <0 if KO, 0=Nothing done, >0 if OK
2019
     */
2020
    public function delete_linked_contact($source = '', $code = '')
2021
    {
2022
        // phpcs:enable
2023
        $listId = '';
2024
        $temp = array();
2025
        $typeContact = $this->liste_type_contact($source, '', 0, 0, $code);
2026
2027
        if (!empty($typeContact)) {
2028
            foreach ($typeContact as $key => $value) {
2029
                array_push($temp, $key);
2030
            }
2031
            $listId = implode(",", $temp);
2032
        }
2033
2034
        // If $listId is empty, we have not criteria on fk_c_type_contact so we will delete record on element_id for
2035
        // any type or record instead of only the ones of the current object. So we do nothing in such a case.
2036
        if (empty($listId)) {
2037
            return 0;
2038
        }
2039
2040
        $sql = "DELETE FROM " . $this->db->prefix() . "element_contact";
2041
        $sql .= " WHERE element_id = " . ((int)$this->id);
2042
        $sql .= " AND fk_c_type_contact IN (" . $this->db->sanitize($listId) . ")";
2043
2044
        dol_syslog(get_class($this) . "::delete_linked_contact", LOG_DEBUG);
2045
        if ($this->db->query($sql)) {
2046
            return 1;
2047
        } else {
2048
            $this->error = $this->db->lasterror();
2049
            return -1;
2050
        }
2051
    }
2052
2053
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2054
2055
    /**
2056
     *      Return array with list of possible values for type of contacts
2057
     *
2058
     * @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...
2059
     * @param string $order Sort order by : 'position', 'code', 'rowid'...
2060
     * @param int<0,1> $option 0=Return array id->label, 1=Return array code->label
2061
     * @param int<0,1> $activeonly 0=all status of contact, 1=only the active
2062
     * @param string $code Type of contact (Example: 'CUSTOMER', 'SERVICE')
2063
     * @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
2064
     */
2065
    public function liste_type_contact($source = 'internal', $order = 'position', $option = 0, $activeonly = 0, $code = '')
2066
    {
2067
        // phpcs:enable
2068
        global $langs;
2069
2070
        if (empty($order)) {
2071
            $order = 'position';
2072
        }
2073
        if ($order == 'position') {
2074
            $order .= ',code';
2075
        }
2076
2077
        $tab = array();
2078
        $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position";
2079
        $sql .= " FROM " . $this->db->prefix() . "c_type_contact as tc";
2080
        $sql .= " WHERE tc.element='" . $this->db->escape($this->element) . "'";
2081
        if ($activeonly == 1) {
2082
            $sql .= " AND tc.active=1"; // only the active types
2083
        }
2084
        if (!empty($source) && $source != 'all') {
2085
            $sql .= " AND tc.source='" . $this->db->escape($source) . "'";
2086
        }
2087
        if (!empty($code)) {
2088
            $sql .= " AND tc.code='" . $this->db->escape($code) . "'";
2089
        }
2090
        $sql .= $this->db->order($order, 'ASC');
2091
2092
        //print "sql=".$sql;
2093
        $resql = $this->db->query($sql);
2094
        if ($resql) {
2095
            $num = $this->db->num_rows($resql);
2096
            $i = 0;
2097
            while ($i < $num) {
2098
                $obj = $this->db->fetch_object($resql);
2099
2100
                $transkey = "TypeContact_" . $this->element . "_" . $source . "_" . $obj->code;
2101
                $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
2102
                if (empty($option)) {
2103
                    $tab[$obj->rowid] = $libelle_type;
2104
                } else {
2105
                    $tab[$obj->code] = $libelle_type;
2106
                }
2107
                $i++;
2108
            }
2109
            return $tab;
2110
        } else {
2111
            $this->error = $this->db->lasterror();
2112
            //dol_print_error($this->db);
2113
            return null;
2114
        }
2115
    }
2116
2117
    /**
2118
     *      Update status of a contact linked to object
2119
     *
2120
     * @param int $rowid Id of link between object and contact
2121
     * @return int                 Return integer <0 if KO, >=0 if OK
2122
     */
2123
    public function swapContactStatus($rowid)
2124
    {
2125
        $sql = "SELECT ec.datecreate, ec.statut, ec.fk_socpeople, ec.fk_c_type_contact,";
2126
        $sql .= " tc.code, tc.libelle as type_label";
2127
        $sql .= " FROM (" . $this->db->prefix() . "element_contact as ec, " . $this->db->prefix() . "c_type_contact as tc)";
2128
        $sql .= " WHERE ec.rowid =" . ((int)$rowid);
2129
        $sql .= " AND ec.fk_c_type_contact = tc.rowid";
2130
        $sql .= " AND tc.element = '" . $this->db->escape($this->element) . "'";
2131
2132
        dol_syslog(get_class($this) . "::swapContactStatus", LOG_DEBUG);
2133
        $resql = $this->db->query($sql);
2134
        if ($resql) {
2135
            $obj = $this->db->fetch_object($resql);
2136
            $newstatut = ($obj->statut == 4) ? 5 : 4;
2137
            $result = $this->update_contact($rowid, $newstatut);
2138
            $this->db->free($resql);
2139
            return $result;
2140
        } else {
2141
            $this->error = $this->db->error();
2142
            dol_print_error($this->db);
2143
            return -1;
2144
        }
2145
    }
2146
2147
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2148
2149
    /**
2150
     *      Update a link to contact line
2151
     *
2152
     * @param int $rowid Id of line contact-element
2153
     * @param int $statut New status of link
2154
     * @param int $type_contact_id Id of contact type (not modified if 0)
2155
     * @param int $fk_socpeople Id of soc_people to update (not modified if 0)
2156
     * @return int                         Return integer <0 if KO, >= 0 if OK
2157
     */
2158
    public function update_contact($rowid, $statut, $type_contact_id = 0, $fk_socpeople = 0)
2159
    {
2160
        // phpcs:enable
2161
        // Insert into database
2162
        $sql = "UPDATE " . $this->db->prefix() . "element_contact set";
2163
        $sql .= " statut = " . $statut;
2164
        if ($type_contact_id) {
2165
            $sql .= ", fk_c_type_contact = " . ((int)$type_contact_id);
2166
        }
2167
        if ($fk_socpeople) {
2168
            $sql .= ", fk_socpeople = " . ((int)$fk_socpeople);
2169
        }
2170
        $sql .= " where rowid = " . ((int)$rowid);
2171
        $resql = $this->db->query($sql);
2172
        if ($resql) {
2173
            return 0;
2174
        } else {
2175
            $this->error = $this->db->lasterror();
2176
            return -1;
2177
        }
2178
    }
2179
2180
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2181
2182
    /**
2183
     *      Return array with list of possible values for type of contacts
2184
     *
2185
     * @param string $source 'internal', 'external' or 'all'
2186
     * @param int<0,1> $option 0=Return array id->label, 1=Return array code->label
2187
     * @param int<0,1> $activeonly 0=all status of contact, 1=only the active
2188
     * @param string $code Type of contact (Example: 'CUSTOMER', 'SERVICE')
2189
     * @param string $element Filter on 1 element type
2190
     * @param string $excludeelement Exclude 1 element type. Example: 'agenda'
2191
     * @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
2192
     */
2193
    public function listeTypeContacts($source = 'internal', $option = 0, $activeonly = 0, $code = '', $element = '', $excludeelement = '')
2194
    {
2195
        global $langs, $conf;
2196
2197
        $langs->loadLangs(array('bills', 'contracts', 'interventions', 'orders', 'projects', 'propal', 'ticket', 'agenda'));
2198
2199
        $tab = array();
2200
2201
        $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position, tc.element";
2202
        $sql .= " FROM " . $this->db->prefix() . "c_type_contact as tc";
2203
2204
        $sqlWhere = array();
2205
        if (!empty($element)) {
2206
            $sqlWhere[] = " tc.element='" . $this->db->escape($element) . "'";
2207
        }
2208
        if (!empty($excludeelement)) {
2209
            $sqlWhere[] = " tc.element <> '" . $this->db->escape($excludeelement) . "'";
2210
        }
2211
2212
        if ($activeonly == 1) {
2213
            $sqlWhere[] = " tc.active=1"; // only the active types
2214
        }
2215
2216
        if (!empty($source) && $source != 'all') {
2217
            $sqlWhere[] = " tc.source='" . $this->db->escape($source) . "'";
2218
        }
2219
2220
        if (!empty($code)) {
2221
            $sqlWhere[] = " tc.code='" . $this->db->escape($code) . "'";
2222
        }
2223
2224
        if (count($sqlWhere) > 0) {
2225
            $sql .= " WHERE " . implode(' AND ', $sqlWhere);
2226
        }
2227
2228
        $sql .= $this->db->order('tc.element, tc.position', 'ASC');
2229
2230
        dol_syslog(__METHOD__, LOG_DEBUG);
2231
        $resql = $this->db->query($sql);
2232
        if ($resql) {
2233
            $num = $this->db->num_rows($resql);
2234
            if ($num > 0) {
2235
                $langs->loadLangs(array("propal", "orders", "bills", "suppliers", "contracts", "supplier_proposal"));
2236
2237
                while ($obj = $this->db->fetch_object($resql)) {
2238
                    $modulename = $obj->element;
2239
                    if (strpos($obj->element, 'project') !== false) {
2240
                        $modulename = 'projet';
2241
                    } elseif ($obj->element == 'contrat') {
2242
                        $element = 'contract';
2243
                    } elseif ($obj->element == 'action') {
2244
                        $modulename = 'agenda';
2245
                    } elseif (strpos($obj->element, 'supplier') !== false && $obj->element != 'supplier_proposal') {
2246
                        $modulename = 'fournisseur';
2247
                    }
2248
                    if (!empty($conf->{$modulename}->enabled)) {
2249
                        $libelle_element = $langs->trans('ContactDefault_' . $obj->element);
2250
                        $tmpelement = $obj->element;
2251
                        $transkey = "TypeContact_" . $tmpelement . "_" . $source . "_" . $obj->code;
2252
                        $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
2253
                        $tab[$obj->rowid] = $libelle_element . ' - ' . $libelle_type;
2254
                    }
2255
                }
2256
            }
2257
            return $tab;
2258
        } else {
2259
            $this->error = $this->db->lasterror();
2260
            return null;
2261
        }
2262
    }
2263
2264
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2265
2266
    /**
2267
     *      Return id of contacts for a source and a contact code.
2268
     *      Example: contact client de facturation ('external', 'BILLING')
2269
     *      Example: contact client de livraison ('external', 'SHIPPING')
2270
     *      Example: contact interne suivi paiement ('internal', 'SALESREPFOLL')
2271
     *
2272
     * @param string $source 'external' or 'internal'
2273
     * @param string $code 'BILLING', 'SHIPPING', 'SALESREPFOLL', ...
2274
     * @param int $status limited to a certain status
2275
     * @return int[]|null          List of id for such contacts, or null if error
2276
     */
2277
    public function getIdContact($source, $code, $status = 0)
2278
    {
2279
        global $conf;
2280
2281
        $result = array();
2282
        $i = 0;
2283
        // Particular case for shipping
2284
        if ($this->element == 'shipping' && $this->origin_id != 0) {
2285
            $id = $this->origin_id;
2286
            $element = 'commande';
2287
        } elseif ($this->element == 'reception' && $this->origin_id != 0) {
2288
            $id = $this->origin_id;
2289
            $element = 'order_supplier';
2290
        } else {
2291
            $id = $this->id;
2292
            $element = $this->element;
2293
        }
2294
2295
        $sql = "SELECT ec.fk_socpeople";
2296
        $sql .= " FROM " . $this->db->prefix() . "element_contact as ec,";
2297
        if ($source == 'internal') {
2298
            $sql .= " " . $this->db->prefix() . "user as c,";
2299
        }
2300
        if ($source == 'external') {
2301
            $sql .= " " . $this->db->prefix() . "socpeople as c,";
2302
        }
2303
        $sql .= " " . $this->db->prefix() . "c_type_contact as tc";
2304
        $sql .= " WHERE ec.element_id = " . ((int)$id);
2305
        $sql .= " AND ec.fk_socpeople = c.rowid";
2306
        if ($source == 'internal') {
2307
            $sql .= " AND c.entity IN (" . getEntity('user') . ")";
2308
        }
2309
        if ($source == 'external') {
2310
            $sql .= " AND c.entity IN (" . getEntity('societe') . ")";
2311
        }
2312
        $sql .= " AND ec.fk_c_type_contact = tc.rowid";
2313
        $sql .= " AND tc.element = '" . $this->db->escape($element) . "'";
2314
        $sql .= " AND tc.source = '" . $this->db->escape($source) . "'";
2315
        if ($code) {
2316
            $sql .= " AND tc.code = '" . $this->db->escape($code) . "'";
2317
        }
2318
        $sql .= " AND tc.active = 1";
2319
        if ($status) {
2320
            $sql .= " AND ec.statut = " . ((int)$status);
2321
        }
2322
2323
        dol_syslog(get_class($this) . "::getIdContact", LOG_DEBUG);
2324
        $resql = $this->db->query($sql);
2325
        if ($resql) {
2326
            while ($obj = $this->db->fetch_object($resql)) {
2327
                $result[$i] = $obj->fk_socpeople;
2328
                $i++;
2329
            }
2330
        } else {
2331
            $this->error = $this->db->error();
2332
            return null;
2333
        }
2334
2335
        return $result;
2336
    }
2337
2338
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2339
2340
    /**
2341
     *      Load object contact with id=$this->contact_id into $this->contact
2342
     *
2343
     * @param  ?int $contactid Id du contact. Use this->contact_id if empty.
2344
     * @return int<-1,1>               Return integer <0 if KO, >0 if OK
2345
     */
2346
    public function fetch_contact($contactid = null)
2347
    {
2348
        // phpcs:enable
2349
        if (empty($contactid)) {
2350
            $contactid = $this->contact_id;
2351
        }
2352
2353
        if (empty($contactid)) {
2354
            return 0;
2355
        }
2356
2357
        $contact = new Contact($this->db);
2358
        $result = $contact->fetch($contactid);
2359
        $this->contact = $contact;
2360
        return $result;
2361
    }
2362
2363
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2364
2365
    /**
2366
     *      Load the third party of object, from id $this->socid or $this->fk_soc, into this->thirdparty
2367
     *
2368
     * @param int<0,1> $force_thirdparty_id Force thirdparty id
2369
     * @return     int<-1,1>                       Return integer <0 if KO, >0 if OK
2370
     * @phan-suppress PhanUndeclaredProperty
2371
     */
2372
    public function fetch_thirdparty($force_thirdparty_id = 0)
2373
    {
2374
        // phpcs:enable
2375
        if (empty($this->socid) && empty($this->fk_soc) && empty($force_thirdparty_id)) {
2376
            return 0;
2377
        }
2378
2379
        $idtofetch = isset($this->socid) ? $this->socid : (isset($this->fk_soc) ? $this->fk_soc : 0);
2380
        if ($force_thirdparty_id) {
2381
            $idtofetch = $force_thirdparty_id;
2382
        }
2383
2384
        if ($idtofetch) {
2385
            $thirdparty = new Societe($this->db);
2386
            $result = $thirdparty->fetch($idtofetch);
2387
            if ($result < 0) {
2388
                $this->errors = array_merge($this->errors, $thirdparty->errors);
2389
            }
2390
            $this->thirdparty = $thirdparty;
2391
2392
            // Use first price level if level not defined for third party
2393
            if (getDolGlobalString('PRODUIT_MULTIPRICES') && empty($this->thirdparty->price_level)) {
2394
                $this->thirdparty->price_level = 1;
2395
            }
2396
2397
            return $result;
2398
        } else {
2399
            return -1;
2400
        }
2401
    }
2402
2403
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2404
2405
    /**
2406
     * Looks for an object with ref matching the wildcard provided
2407
     * It does only work when $this->table_ref_field is set
2408
     *
2409
     * @param string $ref Wildcard
2410
     * @return  int<-1,1>       >1 = OK, 0 = Not found or table_ref_field not defined, <0 = KO
2411
     */
2412
    public function fetchOneLike($ref)
2413
    {
2414
        if (!$this->table_ref_field) {
2415
            return 0;
2416
        }
2417
2418
        $sql = "SELECT rowid FROM " . $this->db->prefix() . $this->table_element;
2419
        $sql .= " WHERE " . $this->table_ref_field . " LIKE '" . $this->db->escape($ref) . "'"; // no escapeforlike here
2420
        $sql .= " LIMIT 1";
2421
2422
        $query = $this->db->query($sql);
2423
2424
        if (!$this->db->num_rows($query)) {
2425
            return 0;
2426
        }
2427
2428
        $result = $this->db->fetch_object($query);
2429
2430
        if (method_exists($this, 'fetch')) {
2431
            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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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