Passed
Push — master ( 284492...2c3033 )
by Alxarafe
27:10
created

Base/AlixarModel.php (2 issues)

1
<?php
2
3
4
namespace Alixar\Base;
5
6
7
use Alxarafe\Base\Table;
8
9
class AlixarModel extends Table
10
{
11
    /**
12
     * @var string    The type of originating object ('commande', 'facture', ...)
13
     * @see fetch_origin()
14
     */
15
    public $origin;
16
17
    /**
18
     * @var DoliDb        Database handler (result of a new DoliDB)
19
     */
20
    public $db;
21
22
    /**
23
     * @var int The object identifier
24
     */
25
    public $id;
26
27
    /**
28
     * @var string        Error string
29
     * @see             errors
30
     */
31
    public $error;
32
33
    /**
34
     * @var string[]    Array of error strings
35
     */
36
    public $errors = array();
37
38
    /**
39
     * @var string ID to identify managed object
40
     */
41
    public $element;
42
43
    /**
44
     * @var string Name of table without prefix where object is stored
45
     */
46
    public $table_element;
47
48
    /**
49
     * @var int    Name of subtable line
50
     */
51
    public $table_element_line = '';
52
53
    /**
54
     * @var string        Key value used to track if data is coming from import wizard
55
     */
56
    public $import_key;
57
58
    /**
59
     * @var mixed        Contains data to manage extrafields
60
     */
61
    public $array_options = array();
62
63
    /**
64
     * @var int[][]        Array of linked objects ids. Loaded by ->fetchObjectLinked
65
     */
66
    public $linkedObjectsIds;
67
68
    /**
69
     * @var mixed        Array of linked objects. Loaded by ->fetchObjectLinked
70
     */
71
    public $linkedObjects;
72
73
    /**
74
     * @var Object      To store a cloned copy of object before to edit it and keep track of old properties
75
     */
76
    public $oldcopy;
77
    /**
78
     * @var array<string,mixed>        Can be used to pass information when only object is provided to method
79
     */
80
    public $context = array();
81
82
83
    // Following vars are used by some objects only. We keep this property here in CommonObject to be able to provide common method using them.
84
    /**
85
     * @var string        Contains canvas name if record is an alternative canvas record
86
     */
87
    public $canvas;
88
    /**
89
     * @var Project The related project
90
     * @see fetch_projet()
91
     */
92
    public $project;
93
    /**
94
     * @var int The related project ID
95
     * @see setProject(), project
96
     */
97
    public $fk_project;
98
    /**
99
     * @deprecated
100
     * @see project
101
     */
102
    public $projet;
103
    /**
104
     * @var Contact a related contact
105
     * @see fetch_contact()
106
     */
107
    public $contact;
108
    /**
109
     * @var int The related contact ID
110
     * @see fetch_contact()
111
     */
112
    public $contact_id;
113
    /**
114
     * @var Societe A related thirdparty
115
     * @see fetch_thirdparty()
116
     */
117
    public $thirdparty;
118
    /**
119
     * @var User A related user
120
     * @see fetch_user()
121
     */
122
    public $user;
123
    /**
124
     * @var int    The id of originating object
125
     * @see fetch_origin()
126
     */
127
    public $origin_id;
128
    /**
129
     * @var string The object's reference
130
     */
131
    public $ref;
132
    /**
133
     * @var string The object's previous reference
134
     */
135
    public $ref_previous;
136
    /**
137
     * @var string The object's next reference
138
     */
139
    public $ref_next;
140
    /**
141
     * @var string An external reference for the object
142
     */
143
    public $ref_ext;
144
    /**
145
     * @var int The object's status
146
     * @see setStatut()
147
     */
148
    public $statut;
149
    /**
150
     * @var string
151
     * @see getFullAddress()
152
     */
153
    public $country;
154
    /**
155
     * @var int
156
     * @see getFullAddress(), country
157
     */
158
    public $country_id;
159
    /**
160
     * @var string
161
     * @see getFullAddress(), isInEEC(), country
162
     */
163
    public $country_code;
164
    /**
165
     * @var string
166
     * @see getFullAddress()
167
     */
168
    public $state;
169
    /**
170
     * @var int
171
     * @see getFullAddress(), state
172
     */
173
    public $state_id;
174
    /**
175
     * @var string
176
     * @see getFullAddress(), state
177
     */
178
    public $state_code;
179
    /**
180
     * @var string
181
     * @see getFullAddress(), region
182
     */
183
    public $region;
184
    /**
185
     * @var string
186
     * @see getFullAddress(), region
187
     */
188
    public $region_code;
189
    /**
190
     * @var int
191
     * @see fetch_barcode()
192
     */
193
    public $barcode_type;
194
    /**
195
     * @var string
196
     * @see fetch_barcode(), barcode_type
197
     */
198
    public $barcode_type_code;
199
    /**
200
     * @var string
201
     * @see fetch_barcode(), barcode_type
202
     */
203
    public $barcode_type_label;
204
    /**
205
     * @var string
206
     * @see fetch_barcode(), barcode_type
207
     */
208
    public $barcode_type_coder;
209
    /**
210
     * @var int Payment method ID (cheque, cash, ...)
211
     * @see setPaymentMethods()
212
     */
213
    public $mode_reglement_id;
214
    /**
215
     * @var int Payment terms ID
216
     * @see setPaymentTerms()
217
     */
218
    public $cond_reglement_id;
219
    /**
220
     * @var int Payment terms ID
221
     * @deprecated Kept for compatibility
222
     * @see cond_reglement_id;
223
     */
224
    public $cond_reglement;
225
    /**
226
     * @var int Delivery address ID
227
     * @deprecated
228
     * @see setDeliveryAddress()
229
     */
230
    public $fk_delivery_address;
231
    /**
232
     * @var int Shipping method ID
233
     * @see setShippingMethod()
234
     */
235
    public $shipping_method_id;
236
    /**
237
     * @var string
238
     * @see SetDocModel()
239
     */
240
    public $modelpdf;
241
    /**
242
     * @var int Bank account ID
243
     * @see SetBankAccount()
244
     */
245
    public $fk_account;
246
    /**
247
     * @var string Public note
248
     * @see update_note()
249
     */
250
    public $note_public;
251
    /**
252
     * @var string Private note
253
     * @see update_note()
254
     */
255
    public $note_private;
256
    /**
257
     * @deprecated
258
     * @see note_public
259
     */
260
    public $note;
261
    /**
262
     * @var float Total amount before taxes
263
     * @see update_price()
264
     */
265
    public $total_ht;
266
    /**
267
     * @var float Total VAT amount
268
     * @see update_price()
269
     */
270
    public $total_tva;
271
    /**
272
     * @var float Total local tax 1 amount
273
     * @see update_price()
274
     */
275
    public $total_localtax1;
276
    /**
277
     * @var float Total local tax 2 amount
278
     * @see update_price()
279
     */
280
    public $total_localtax2;
281
    /**
282
     * @var float Total amount with taxes
283
     * @see update_price()
284
     */
285
    public $total_ttc;
286
    /**
287
     * @var CommonObjectLine[]
288
     */
289
    public $lines;
290
    /**
291
     * @var mixed        Contains comments
292
     * @see fetchComments()
293
     */
294
    public $comments = array();
295
    /**
296
     * @var int
297
     * @see setIncoterms()
298
     */
299
    public $fk_incoterms;
300
    /**
301
     * @var string
302
     * @see SetIncoterms()
303
     */
304
    public $libelle_incoterms;
305
    /**
306
     * @var string
307
     * @see display_incoterms()
308
     */
309
    public $location_incoterms;
310
    public $name;
311
    public $lastname;
312
    public $firstname;
313
    public $civility_id;
314
    public $date_creation;
315
316
    // Dates
317
    public $date_validation;            // Date creation
318
    public $date_modification;        // Date validation
319
    /**
320
     * @var string        Column name of the ref field.
321
     */
322
    protected $table_ref_field = '';        // Date last change (tms field)
323
324
325
    // No constructor as it is an abstract class
326
327
    /**
328
     * Check an object id/ref exists
329
     * If you don't need/want to instantiate object and just need to know if object exists, use this method instead of fetch
330
     *
331
     * @param string $element String of element ('product', 'facture', ...)
332
     * @param int $id Id of object
333
     * @param string $ref Ref of object to check
334
     * @param string $ref_ext Ref ext of object to check
335
     * @return int                <0 if KO, 0 if OK but not found, >0 if OK and exists
336
     */
337
    static function isExistingObject($element, $id, $ref = '', $ref_ext = '')
338
    {
339
        global $db, $conf;
340
341
        $sql = "SELECT rowid, ref, ref_ext";
342
        $sql .= " FROM " . MAIN_DB_PREFIX . $element;
343
        $sql .= " WHERE entity IN (" . getEntity($element) . ")";
344
345
        if ($id > 0) $sql .= " AND rowid = " . $db->escape($id);
346
        else if ($ref) $sql .= " AND ref = '" . $db->escape($ref) . "'";
347
        else if ($ref_ext) $sql .= " AND ref_ext = '" . $db->escape($ref_ext) . "'";
348
        else {
349
            $error = 'ErrorWrongParameters';
350
            dol_print_error(get_class() . "::isExistingObject " . $error, LOG_ERR);
351
            return -1;
352
        }
353
        if ($ref || $ref_ext) $sql .= " AND entity = " . $conf->entity;
354
355
        dol_syslog(get_class() . "::isExistingObject", LOG_DEBUG);
356
        $resql = $db->query($sql);
357
        if ($resql) {
358
            $num = $db->num_rows($resql);
359
            if ($num > 0) return 1;
360
            else return 0;
361
        }
362
        return -1;
363
    }
364
365
    /**
366
     * Function used to replace a thirdparty id with another one.
367
     * This function is meant to be called from replaceThirdparty with the appropiate tables
368
     * Column name fk_soc MUST be used to identify thirdparties
369
     *
370
     * @param DoliDB $db Database handler
371
     * @param int $origin_id Old thirdparty id (the thirdparty to delete)
372
     * @param int $dest_id New thirdparty id (the thirdparty that will received element of the other)
373
     * @param string[] $tables Tables that need to be changed
374
     * @param int $ignoreerrors Ignore errors. Return true even if errors. We need this when replacement can fails like for categories (categorie of old thirdparty may already exists on new one)
375
     * @return bool                          True if success, False if error
376
     */
377
    public static function commonReplaceThirdparty(DoliDB $db, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
378
    {
379
        foreach ($tables as $table) {
380
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . $table . ' SET fk_soc = ' . $dest_id . ' WHERE fk_soc = ' . $origin_id;
381
382
            if (!$db->query($sql)) {
383
                if ($ignoreerrors) return true;        // TODO Not enough. If there is A-B on kept thirdarty and B-C on old one, we must get A-B-C after merge. Not A-B.
384
                //$this->errors = $db->lasterror();
385
                return false;
386
            }
387
        }
388
389
        return true;
390
    }
391
392
    /**
393
     * Method to output saved errors
394
     *
395
     * @return    string        String with errors
396
     */
397
    function errorsToString()
398
    {
399
        return $this->error . (is_array($this->errors) ? (($this->error != '' ? ', ' : '') . join(', ', $this->errors)) : '');
400
    }
401
402
    /**
403
     *    Return full address for banner
404
     *
405
     * @param string $htmlkey HTML id to make banner content unique
406
     * @param Object $object Object (thirdparty, thirdparty of contact for contact, null for a member)
407
     * @return        string                            Full address string
408
     */
409
    function getBannerAddress($htmlkey, $object)
410
    {
411
        global $conf, $langs;
412
413
        $countriesusingstate = array('AU', 'US', 'IN', 'GB', 'ES', 'UK', 'TR');    // See also option MAIN_FORCE_STATE_INTO_ADDRESS
414
415
        $contactid = 0;
416
        $thirdpartyid = 0;
417
        if ($this->element == 'societe') {
418
            $thirdpartyid = $this->id;
419
        }
420
        if ($this->element == 'contact') {
421
            $contactid = $this->id;
422
            $thirdpartyid = $object->fk_soc;
423
        }
424
        if ($this->element == 'user') {
425
            $contactid = $this->contact_id;
426
            $thirdpartyid = $object->fk_soc;
427
        }
428
429
        $out = '<!-- BEGIN part to show address block -->';
430
431
        $outdone = 0;
432
        $coords = $this->getFullAddress(1, ', ', $conf->global->MAIN_SHOW_REGION_IN_STATE_SELECT);
433
        if ($coords) {
434
            if (!empty($conf->use_javascript_ajax)) {
435
                $namecoords = $this->getFullName($langs, 1) . '<br>' . $coords;
436
                // hideonsmatphone because copyToClipboard call jquery dialog that does not work with jmobile
437
                $out .= '<a href="#" class="hideonsmartphone" onclick="return copyToClipboard(\'' . dol_escape_js($namecoords) . '\',\'' . dol_escape_js($langs->trans("HelpCopyToClipboard")) . '\');">';
438
                $out .= img_picto($langs->trans("Address"), 'object_address.png');
439
                $out .= '</a> ';
440
            }
441
            $out .= dol_print_address($coords, 'address_' . $htmlkey . '_' . $this->id, $this->element, $this->id, 1, ', ');
442
            $outdone++;
443
            $outdone++;
444
        }
445
446
        if (!in_array($this->country_code, $countriesusingstate) && empty($conf->global->MAIN_FORCE_STATE_INTO_ADDRESS)   // If MAIN_FORCE_STATE_INTO_ADDRESS is on, state is already returned previously with getFullAddress
447
            && empty($conf->global->SOCIETE_DISABLE_STATE) && $this->state) {
448
            if (!empty($conf->global->MAIN_SHOW_REGION_IN_STATE_SELECT) && $conf->global->MAIN_SHOW_REGION_IN_STATE_SELECT == 1 && $this->region) {
449
                $out .= ($outdone ? ' - ' : '') . $this->region . ' - ' . $this->state;
450
            } else {
451
                $out .= ($outdone ? ' - ' : '') . $this->state;
452
            }
453
            $outdone++;
454
        }
455
456
        if (!empty($this->phone) || !empty($this->phone_pro) || !empty($this->phone_mobile) || !empty($this->phone_perso) || !empty($this->fax) || !empty($this->office_phone) || !empty($this->user_mobile) || !empty($this->office_fax)) $out .= ($outdone ? '<br>' : '');
457
        if (!empty($this->phone) && empty($this->phone_pro)) {        // For objects that store pro phone into ->phone
458
            $out .= dol_print_phone($this->phone, $this->country_code, $contactid, $thirdpartyid, 'AC_TEL', '&nbsp;', 'phone', $langs->trans("PhonePro"));
459
            $outdone++;
460
        }
461
        if (!empty($this->phone_pro)) {
462
            $out .= dol_print_phone($this->phone_pro, $this->country_code, $contactid, $thirdpartyid, 'AC_TEL', '&nbsp;', 'phone', $langs->trans("PhonePro"));
463
            $outdone++;
464
        }
465
        if (!empty($this->phone_mobile)) {
466
            $out .= dol_print_phone($this->phone_mobile, $this->country_code, $contactid, $thirdpartyid, 'AC_TEL', '&nbsp;', 'mobile', $langs->trans("PhoneMobile"));
467
            $outdone++;
468
        }
469
        if (!empty($this->phone_perso)) {
470
            $out .= dol_print_phone($this->phone_perso, $this->country_code, $contactid, $thirdpartyid, 'AC_TEL', '&nbsp;', 'phone', $langs->trans("PhonePerso"));
471
            $outdone++;
472
        }
473
        if (!empty($this->office_phone)) {
474
            $out .= dol_print_phone($this->office_phone, $this->country_code, $contactid, $thirdpartyid, 'AC_TEL', '&nbsp;', 'phone', $langs->trans("PhonePro"));
475
            $outdone++;
476
        }
477
        if (!empty($this->user_mobile)) {
478
            $out .= dol_print_phone($this->user_mobile, $this->country_code, $contactid, $thirdpartyid, 'AC_TEL', '&nbsp;', 'mobile', $langs->trans("PhoneMobile"));
479
            $outdone++;
480
        }
481
        if (!empty($this->fax)) {
482
            $out .= dol_print_phone($this->fax, $this->country_code, $contactid, $thirdpartyid, 'AC_FAX', '&nbsp;', 'fax', $langs->trans("Fax"));
483
            $outdone++;
484
        }
485
        if (!empty($this->office_fax)) {
486
            $out .= dol_print_phone($this->office_fax, $this->country_code, $contactid, $thirdpartyid, 'AC_FAX', '&nbsp;', 'fax', $langs->trans("Fax"));
487
            $outdone++;
488
        }
489
490
        $out .= '<div style="clear: both;"></div>';
491
        $outdone = 0;
492
        if (!empty($this->email)) {
493
            $out .= dol_print_email($this->email, $this->id, $object->id, 'AC_EMAIL', 0, 0, 1);
494
            $outdone++;
495
        }
496
        if (!empty($this->url)) {
497
            $out .= dol_print_url($this->url, '_goout', 0, 1);
498
            $outdone++;
499
        }
500
        $out .= '<div style="clear: both;">';
501
        if (!empty($conf->socialnetworks->enabled)) {
502
            if ($this->skype) $out .= dol_print_socialnetworks($this->skype, $this->id, $object->id, 'skype');
503
            $outdone++;
504
            if ($this->jabberid) $out .= dol_print_socialnetworks($this->jabberid, $this->id, $object->id, 'jabber');
505
            $outdone++;
506
            if ($this->twitter) $out .= dol_print_socialnetworks($this->twitter, $this->id, $object->id, 'twitter');
507
            $outdone++;
508
            if ($this->facebook) $out .= dol_print_socialnetworks($this->facebook, $this->id, $object->id, 'facebook');
509
            $outdone++;
510
        }
511
        $out .= '</div>';
512
513
        $out .= '<!-- END Part to show address block -->';
514
515
        return $out;
516
    }
517
518
    /**
519
     *    Return full address of contact
520
     *
521
     * @param int $withcountry 1=Add country into address string
522
     * @param string $sep Separator to use to build string
523
     * @param int $withregion 1=Add region into address string
524
     * @return        string                            Full address string
525
     */
526
    function getFullAddress($withcountry = 0, $sep = "\n", $withregion = 0)
527
    {
528
        if ($withcountry && $this->country_id && (empty($this->country_code) || empty($this->country))) {
529
            require_once DOL_DOCUMENT_ROOT . '/core/lib/company.lib.php';
530
            $tmparray = getCountry($this->country_id, 'all');
531
            $this->country_code = $tmparray['code'];
532
            $this->country = $tmparray['label'];
533
        }
534
535
        if ($withregion && $this->state_id && (empty($this->state_code) || empty($this->state) || empty($this->region) || empty($this->region_cpde))) {
536
            require_once DOL_DOCUMENT_ROOT . '/core/lib/company.lib.php';
537
            $tmparray = getState($this->state_id, 'all', 0, 1);
538
            $this->state_code = $tmparray['code'];
539
            $this->state = $tmparray['label'];
540
            $this->region_code = $tmparray['region_code'];
541
            $this->region = $tmparray['region'];
542
        }
543
544
        return dol_format_address($this, $withcountry, $sep);
545
    }
546
547
    /**
548
     *    Return full name (civility+' '+name+' '+lastname)
549
     *
550
     * @param Translate $langs Language object for translation of civility (used only if option is 1)
551
     * @param int $option 0=No option, 1=Add civility
552
     * @param int $nameorder -1=Auto, 0=Lastname+Firstname, 1=Firstname+Lastname, 2=Firstname
553
     * @param int $maxlen Maximum length
554
     * @return    string                        String with full name
555
     */
556
    function getFullName($langs, $option = 0, $nameorder = -1, $maxlen = 0)
557
    {
558
        //print "lastname=".$this->lastname." name=".$this->name." nom=".$this->nom."<br>\n";
559
        $lastname = $this->lastname;
560
        $firstname = $this->firstname;
561
        if (empty($lastname)) $lastname = (isset($this->lastname) ? $this->lastname : (isset($this->name) ? $this->name : (isset($this->nom) ? $this->nom : (isset($this->societe) ? $this->societe : (isset($this->company) ? $this->company : '')))));
562
563
        $ret = '';
564
        if ($option && $this->civility_id) {
565
            if ($langs->transnoentitiesnoconv("Civility" . $this->civility_id) != "Civility" . $this->civility_id) $ret .= $langs->transnoentitiesnoconv("Civility" . $this->civility_id) . ' ';
566
            else $ret .= $this->civility_id . ' ';
567
        }
568
569
        $ret .= dolGetFirstLastname($firstname, $lastname, $nameorder);
570
571
        return dol_trunc($ret, $maxlen);
572
    }
573
574
575
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
576
577
    /**
578
     * Return the link of last main doc file for direct public download.
579
     *
580
     * @param string $modulepart Module related to document
581
     * @param int $initsharekey Init the share key if it was not yet defined
582
     * @param int $relativelink 0=Return full external link, 1=Return link relative to root of file
583
     * @return    string                        Link or empty string if there is no download link
584
     */
585
    function getLastMainDocLink($modulepart, $initsharekey = 0, $relativelink = 0)
586
    {
587
        global $user, $dolibarr_main_url_root;
588
589
        if (empty($this->last_main_doc)) {
590
            return '';        // No way to known which document name to use
591
        }
592
593
        include_once DOL_DOCUMENT_ROOT . '/ecm/class/ecmfiles.class.php';
594
        $ecmfile = new EcmFiles($this->db);
595
        $result = $ecmfile->fetch(0, '', $this->last_main_doc);
596
        if ($result < 0) {
597
            $this->error = $ecmfile->error;
598
            $this->errors = $ecmfile->errors;
599
            return -1;
600
        }
601
602
        if (empty($ecmfile->id)) {
603
            // Add entry into index
604
            if ($initsharekey) {
605
                require_once DOL_DOCUMENT_ROOT . '/core/lib/security2.lib.php';
606
                // TODO We can't, we dont' have full path of file, only last_main_doc adn ->element, so we must rebuild full path first
607
                /*
608
				$ecmfile->filepath = $rel_dir;
609
				$ecmfile->filename = $filename;
610
				$ecmfile->label = md5_file(dol_osencode($destfull));	// hash of file content
611
				$ecmfile->fullpath_orig = '';
612
				$ecmfile->gen_or_uploaded = 'generated';
613
				$ecmfile->description = '';    // indexed content
614
				$ecmfile->keyword = '';        // keyword content
615
				$ecmfile->share = getRandomPassword(true);
616
				$result = $ecmfile->create($user);
617
				if ($result < 0)
618
				{
619
					$this->error = $ecmfile->error;
620
					$this->errors = $ecmfile->errors;
621
				}
622
				*/
623
            } else return '';
624
        } elseif (empty($ecmfile->share)) {
625
            // Add entry into index
626
            if ($initsharekey) {
627
                require_once DOL_DOCUMENT_ROOT . '/core/lib/security2.lib.php';
628
                $ecmfile->share = getRandomPassword(true);
629
                $ecmfile->update($user);
630
            } else return '';
631
        }
632
633
        // Define $urlwithroot
634
        $urlwithouturlroot = preg_replace('/' . preg_quote(DOL_URL_ROOT, '/') . '$/i', '', trim($dolibarr_main_url_root));
635
        $urlwithroot = $urlwithouturlroot . DOL_URL_ROOT;        // This is to use external domain name found into config file
636
        //$urlwithroot=DOL_MAIN_URL_ROOT;					// This is to use same domain name than current
637
638
        $forcedownload = 0;
639
640
        $paramlink = '';
641
        //if (! empty($modulepart)) $paramlink.=($paramlink?'&':'').'modulepart='.$modulepart;		// For sharing with hash (so public files), modulepart is not required.
642
        //if (! empty($ecmfile->entity)) $paramlink.='&entity='.$ecmfile->entity; 					// For sharing with hash (so public files), entity is not required.
643
        //$paramlink.=($paramlink?'&':'').'file='.urlencode($filepath);								// No need of name of file for public link, we will use the hash
644
        if (!empty($ecmfile->share)) $paramlink .= ($paramlink ? '&' : '') . 'hashp=' . $ecmfile->share;            // Hash for public share
645
        if ($forcedownload) $paramlink .= ($paramlink ? '&' : '') . 'attachment=1';
646
647
        if ($relativelink) {
648
            $linktoreturn = 'document.php' . ($paramlink ? '?' . $paramlink : '');
649
        } else {
650
            $linktoreturn = $urlwithroot . '/document.php' . ($paramlink ? '?' . $paramlink : '');
651
        }
652
653
        // Here $ecmfile->share is defined
654
        return $linktoreturn;
655
    }
656
657
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
658
659
    /**
660
     *    Copy contact from one element to current
661
     *
662
     * @param CommonObject $objFrom Source element
663
     * @param string $source Nature of contact ('internal' or 'external')
664
     * @return   int                         >0 if OK, <0 if KO
665
     */
666
    function copy_linked_contact($objFrom, $source = 'internal')
667
    {
668
        // phpcs:enable
669
        $contacts = $objFrom->liste_contact(-1, $source);
670
        foreach ($contacts as $contact) {
671
            if ($this->add_contact($contact['id'], $contact['fk_c_type_contact'], $contact['source']) < 0) {
672
                $this->error = $this->db->lasterror();
673
                return -1;
674
            }
675
        }
676
        return 1;
677
    }
678
679
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
680
681
    /**
682
     *  Add a link between element $this->element and a contact
683
     *
684
     * @param int $fk_socpeople Id of thirdparty contact (if source = 'external') or id of user (if souce = 'internal') to link
685
     * @param int $type_contact Type of contact (code or id). Must be id or code found into table llx_c_type_contact. For example: SALESREPFOLL
686
     * @param string $source external=Contact extern (llx_socpeople), internal=Contact intern (llx_user)
687
     * @param int $notrigger Disable all triggers
688
     * @return int                        <0 if KO, >0 if OK
689
     */
690
    function add_contact($fk_socpeople, $type_contact, $source = 'external', $notrigger = 0)
691
    {
692
        // phpcs:enable
693
        global $user, $langs;
694
695
696
        dol_syslog(get_class($this) . "::add_contact $fk_socpeople, $type_contact, $source, $notrigger");
697
698
        // Check parameters
699
        if ($fk_socpeople <= 0) {
700
            $langs->load("errors");
701
            $this->error = $langs->trans("ErrorWrongValueForParameterX", "1");
702
            dol_syslog(get_class($this) . "::add_contact " . $this->error, LOG_ERR);
703
            return -1;
704
        }
705
        if (!$type_contact) {
706
            $langs->load("errors");
707
            $this->error = $langs->trans("ErrorWrongValueForParameterX", "2");
708
            dol_syslog(get_class($this) . "::add_contact " . $this->error, LOG_ERR);
709
            return -2;
710
        }
711
712
        $id_type_contact = 0;
713
        if (is_numeric($type_contact)) {
714
            $id_type_contact = $type_contact;
715
        } else {
716
            // We look for id type_contact
717
            $sql = "SELECT tc.rowid";
718
            $sql .= " FROM " . MAIN_DB_PREFIX . "c_type_contact as tc";
719
            $sql .= " WHERE tc.element='" . $this->db->escape($this->element) . "'";
720
            $sql .= " AND tc.source='" . $this->db->escape($source) . "'";
721
            $sql .= " AND tc.code='" . $this->db->escape($type_contact) . "' AND tc.active=1";
722
            //print $sql;
723
            $resql = $this->db->query($sql);
724
            if ($resql) {
725
                $obj = $this->db->fetch_object($resql);
726
                if ($obj) $id_type_contact = $obj->rowid;
727
            }
728
        }
729
730
        if ($id_type_contact == 0) {
731
            $this->error = 'CODE_NOT_VALID_FOR_THIS_ELEMENT';
732
            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");
733
            return -3;
734
        }
735
736
        $datecreate = dol_now();
737
738
        // Socpeople must have already been added by some trigger, then we have to check it to avoid DB_ERROR_RECORD_ALREADY_EXISTS error
739
        $TListeContacts = $this->liste_contact(-1, $source);
740
        $already_added = false;
741
        if (!empty($TListeContacts)) {
742
            foreach ($TListeContacts as $array_contact) {
743
                if ($array_contact['status'] == 4 && $array_contact['id'] == $fk_socpeople && $array_contact['fk_c_type_contact'] == $id_type_contact) {
744
                    $already_added = true;
745
                    break;
746
                }
747
            }
748
        }
749
750
        if (!$already_added) {
751
752
            $this->db->begin();
753
754
            // Insert into database
755
            $sql = "INSERT INTO " . MAIN_DB_PREFIX . "element_contact";
756
            $sql .= " (element_id, fk_socpeople, datecreate, statut, fk_c_type_contact) ";
757
            $sql .= " VALUES (" . $this->id . ", " . $fk_socpeople . " , ";
758
            $sql .= "'" . $this->db->idate($datecreate) . "'";
759
            $sql .= ", 4, " . $id_type_contact;
760
            $sql .= ")";
761
762
            $resql = $this->db->query($sql);
763
            if ($resql) {
764
                if (!$notrigger) {
765
                    $result = $this->call_trigger(strtoupper($this->element) . '_ADD_CONTACT', $user);
766
                    if ($result < 0) {
767
                        $this->db->rollback();
768
                        return -1;
769
                    }
770
                }
771
772
                $this->db->commit();
773
                return 1;
774
            } else {
775
                if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
776
                    $this->error = $this->db->errno();
777
                    $this->db->rollback();
778
                    echo 'err rollback';
779
                    return -2;
780
                } else {
781
                    $this->error = $this->db->error();
782
                    $this->db->rollback();
783
                    return -1;
784
                }
785
            }
786
        } else return 0;
787
    }
788
789
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
790
791
    /**
792
     *    Get array of all contacts for an object
793
     *
794
     * @param int $statut Status of links to get (-1=all)
795
     * @param string $source Source of contact: external or thirdparty (llx_socpeople) or internal (llx_user)
796
     * @param int $list 0:Return array contains all properties, 1:Return array contains just id
797
     * @param string $code Filter on this code of contact type ('SHIPPING', 'BILLING', ...)
798
     * @return    array|int                Array of contacts, -1 if error
799
     */
800
    function liste_contact($statut = -1, $source = 'external', $list = 0, $code = '')
801
    {
802
        // phpcs:enable
803
        global $langs;
804
805
        $tab = array();
806
807
        $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
808
        if ($source == 'internal') $sql .= ", '-1' as socid, t.statut as statuscontact, t.login, t.photo";
809
        if ($source == 'external' || $source == 'thirdparty') $sql .= ", t.fk_soc as socid, t.statut as statuscontact";
810
        $sql .= ", t.civility as civility, t.lastname as lastname, t.firstname, t.email";
811
        $sql .= ", tc.source, tc.element, tc.code, tc.libelle";
812
        $sql .= " FROM " . MAIN_DB_PREFIX . "c_type_contact tc";
813
        $sql .= ", " . MAIN_DB_PREFIX . "element_contact ec";
814
        if ($source == 'internal') $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "user t on ec.fk_socpeople = t.rowid";
815
        if ($source == 'external' || $source == 'thirdparty') $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "socpeople t on ec.fk_socpeople = t.rowid";
816
        $sql .= " WHERE ec.element_id =" . $this->id;
817
        $sql .= " AND ec.fk_c_type_contact=tc.rowid";
818
        $sql .= " AND tc.element='" . $this->db->escape($this->element) . "'";
819
        if ($code) $sql .= " AND tc.code = '" . $this->db->escape($code) . "'";
820
        if ($source == 'internal') $sql .= " AND tc.source = 'internal'";
821
        if ($source == 'external' || $source == 'thirdparty') $sql .= " AND tc.source = 'external'";
822
        $sql .= " AND tc.active=1";
823
        if ($statut >= 0) $sql .= " AND ec.statut = '" . $statut . "'";
824
        $sql .= " ORDER BY t.lastname ASC";
825
826
        dol_syslog(get_class($this) . "::liste_contact", LOG_DEBUG);
827
        $resql = $this->db->query($sql);
828
        if ($resql) {
829
            $num = $this->db->num_rows($resql);
830
            $i = 0;
831
            while ($i < $num) {
832
                $obj = $this->db->fetch_object($resql);
833
834
                if (!$list) {
835
                    $transkey = "TypeContact_" . $obj->element . "_" . $obj->source . "_" . $obj->code;
836
                    $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->libelle);
837
                    $tab[$i] = array('source' => $obj->source, 'socid' => $obj->socid, 'id' => $obj->id,
838
                        'nom' => $obj->lastname,      // For backward compatibility
839
                        'civility' => $obj->civility, 'lastname' => $obj->lastname, 'firstname' => $obj->firstname, 'email' => $obj->email, 'login' => $obj->login, 'photo' => $obj->photo, 'statuscontact' => $obj->statuscontact,
840
                        'rowid' => $obj->rowid, 'code' => $obj->code, 'libelle' => $libelle_type, 'status' => $obj->statuslink, 'fk_c_type_contact' => $obj->fk_c_type_contact);
841
                } else {
842
                    $tab[$i] = $obj->id;
843
                }
844
845
                $i++;
846
            }
847
848
            return $tab;
849
        } else {
850
            $this->error = $this->db->lasterror();
851
            dol_print_error($this->db);
852
            return -1;
853
        }
854
    }
855
856
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
857
858
    /**
859
     * Call trigger based on this instance.
860
     * Some context information may also be provided into array property this->context.
861
     * NB:  Error from trigger are stacked in interface->errors
862
     * NB2: If return code of triggers are < 0, action calling trigger should cancel all transaction.
863
     *
864
     * @param string $trigger_name trigger's name to execute
865
     * @param User $user Object user
866
     * @return  int                       Result of run_triggers
867
     */
868
    function call_trigger($trigger_name, $user)
869
    {
870
        // phpcs:enable
871
        global $langs, $conf;
872
873
        include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
874
        $interface = new Interfaces($this->db);
875
        $result = $interface->run_triggers($trigger_name, $this, $user, $langs, $conf);
876
877
        if ($result < 0) {
878
            if (!empty($this->errors)) {
879
                $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.
880
            } else {
881
                $this->errors = $interface->errors;
882
            }
883
        }
884
        return $result;
885
    }
886
887
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
888
889
    /**
890
     *    Delete a link to contact line
891
     *
892
     * @param int $rowid Id of contact link line to delete
893
     * @param int $notrigger Disable all triggers
894
     * @return   int                        >0 if OK, <0 if KO
895
     */
896
    function delete_contact($rowid, $notrigger = 0)
897
    {
898
        // phpcs:enable
899
        global $user;
900
901
902
        $this->db->begin();
903
904
        $sql = "DELETE FROM " . MAIN_DB_PREFIX . "element_contact";
905
        $sql .= " WHERE rowid =" . $rowid;
906
907
        dol_syslog(get_class($this) . "::delete_contact", LOG_DEBUG);
908
        if ($this->db->query($sql)) {
909
            if (!$notrigger) {
910
                $result = $this->call_trigger(strtoupper($this->element) . '_DELETE_CONTACT', $user);
911
                if ($result < 0) {
912
                    $this->db->rollback();
913
                    return -1;
914
                }
915
            }
916
917
            $this->db->commit();
918
            return 1;
919
        } else {
920
            $this->error = $this->db->lasterror();
921
            $this->db->rollback();
922
            return -1;
923
        }
924
    }
925
926
    /**
927
     *    Delete all links between an object $this and all its contacts
928
     *
929
     * @param string $source '' or 'internal' or 'external'
930
     * @param string $code Type of contact (code or id)
931
     * @return   int                    >0 if OK, <0 if KO
932
     */
933
    function delete_linked_contact($source = '', $code = '')
934
    {
935
        // phpcs:enable
936
        $temp = array();
937
        $typeContact = $this->liste_type_contact($source, '', 0, 0, $code);
938
939
        foreach ($typeContact as $key => $value) {
940
            array_push($temp, $key);
941
        }
942
        $listId = implode(",", $temp);
943
944
        $sql = "DELETE FROM " . MAIN_DB_PREFIX . "element_contact";
945
        $sql .= " WHERE element_id = " . $this->id;
946
        if ($listId)
947
            $sql .= " AND fk_c_type_contact IN (" . $listId . ")";
948
949
        dol_syslog(get_class($this) . "::delete_linked_contact", LOG_DEBUG);
950
        if ($this->db->query($sql)) {
951
            return 1;
952
        } else {
953
            $this->error = $this->db->lasterror();
954
            return -1;
955
        }
956
    }
957
958
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
959
960
    /**
961
     *      Return array with list of possible values for type of contacts
962
     *
963
     * @param string $source 'internal', 'external' or 'all'
964
     * @param string $order Sort order by : 'position', 'code', 'rowid'...
965
     * @param int $option 0=Return array id->label, 1=Return array code->label
966
     * @param int $activeonly 0=all status of contact, 1=only the active
967
     * @param string $code Type of contact (Example: 'CUSTOMER', 'SERVICE')
968
     * @return array            Array list of type of contacts (id->label if option=0, code->label if option=1)
969
     */
970
    function liste_type_contact($source = 'internal', $order = 'position', $option = 0, $activeonly = 0, $code = '')
971
    {
972
        // phpcs:enable
973
        global $langs;
974
975
        if (empty($order)) $order = 'position';
976
        if ($order == 'position') $order .= ',code';
977
978
        $tab = array();
979
        $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle, tc.position";
980
        $sql .= " FROM " . MAIN_DB_PREFIX . "c_type_contact as tc";
981
        $sql .= " WHERE tc.element='" . $this->db->escape($this->element) . "'";
982
        if ($activeonly == 1) $sql .= " AND tc.active=1"; // only the active types
983
        if (!empty($source) && $source != 'all') $sql .= " AND tc.source='" . $this->db->escape($source) . "'";
984
        if (!empty($code)) $sql .= " AND tc.code='" . $this->db->escape($code) . "'";
985
        $sql .= $this->db->order($order, 'ASC');
986
987
        //print "sql=".$sql;
988
        $resql = $this->db->query($sql);
989
        if ($resql) {
990
            $num = $this->db->num_rows($resql);
991
            $i = 0;
992
            while ($i < $num) {
993
                $obj = $this->db->fetch_object($resql);
994
995
                $transkey = "TypeContact_" . $this->element . "_" . $source . "_" . $obj->code;
996
                $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->libelle);
997
                if (empty($option)) $tab[$obj->rowid] = $libelle_type;
998
                else $tab[$obj->code] = $libelle_type;
999
                $i++;
1000
            }
1001
            return $tab;
1002
        } else {
1003
            $this->error = $this->db->lasterror();
1004
            //dol_print_error($this->db);
1005
            return null;
1006
        }
1007
    }
1008
1009
    /**
1010
     *        Update status of a contact linked to object
1011
     *
1012
     * @param int $rowid Id of link between object and contact
1013
     * @return    int                    <0 if KO, >=0 if OK
1014
     */
1015
    function swapContactStatus($rowid)
1016
    {
1017
        $sql = "SELECT ec.datecreate, ec.statut, ec.fk_socpeople, ec.fk_c_type_contact,";
1018
        $sql .= " tc.code, tc.libelle";
1019
        //$sql.= ", s.fk_soc";
1020
        $sql .= " FROM (" . MAIN_DB_PREFIX . "element_contact as ec, " . MAIN_DB_PREFIX . "c_type_contact as tc)";
1021
        //$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."socpeople as s ON ec.fk_socpeople=s.rowid";	// Si contact de type external, alors il est lie a une societe
1022
        $sql .= " WHERE ec.rowid =" . $rowid;
1023
        $sql .= " AND ec.fk_c_type_contact=tc.rowid";
1024
        $sql .= " AND tc.element = '" . $this->db->escape($this->element) . "'";
1025
1026
        dol_syslog(get_class($this) . "::swapContactStatus", LOG_DEBUG);
1027
        $resql = $this->db->query($sql);
1028
        if ($resql) {
1029
            $obj = $this->db->fetch_object($resql);
1030
            $newstatut = ($obj->statut == 4) ? 5 : 4;
1031
            $result = $this->update_contact($rowid, $newstatut);
1032
            $this->db->free($resql);
1033
            return $result;
1034
        } else {
1035
            $this->error = $this->db->error();
1036
            dol_print_error($this->db);
1037
            return -1;
1038
        }
1039
    }
1040
1041
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1042
1043
    /**
1044
     *      Update a link to contact line
1045
     *
1046
     * @param int $rowid Id of line contact-element
1047
     * @param int $statut New status of link
1048
     * @param int $type_contact_id Id of contact type (not modified if 0)
1049
     * @param int $fk_socpeople Id of soc_people to update (not modified if 0)
1050
     * @return int                        <0 if KO, >= 0 if OK
1051
     */
1052
    function update_contact($rowid, $statut, $type_contact_id = 0, $fk_socpeople = 0)
1053
    {
1054
        // phpcs:enable
1055
        // Insert into database
1056
        $sql = "UPDATE " . MAIN_DB_PREFIX . "element_contact set";
1057
        $sql .= " statut = " . $statut;
1058
        if ($type_contact_id) $sql .= ", fk_c_type_contact = '" . $type_contact_id . "'";
1059
        if ($fk_socpeople) $sql .= ", fk_socpeople = '" . $fk_socpeople . "'";
1060
        $sql .= " where rowid = " . $rowid;
1061
        $resql = $this->db->query($sql);
1062
        if ($resql) {
1063
            return 0;
1064
        } else {
1065
            $this->error = $this->db->lasterror();
1066
            return -1;
1067
        }
1068
    }
1069
1070
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1071
1072
    /**
1073
     *      Return id of contacts for a source and a contact code.
1074
     *      Example: contact client de facturation ('external', 'BILLING')
1075
     *      Example: contact client de livraison ('external', 'SHIPPING')
1076
     *      Example: contact interne suivi paiement ('internal', 'SALESREPFOLL')
1077
     *
1078
     * @param string $source 'external' or 'internal'
1079
     * @param string $code 'BILLING', 'SHIPPING', 'SALESREPFOLL', ...
1080
     * @param int $status limited to a certain status
1081
     * @return array            List of id for such contacts
1082
     */
1083
    function getIdContact($source, $code, $status = 0)
1084
    {
1085
        global $conf;
1086
1087
        $result = array();
1088
        $i = 0;
1089
        //cas particulier pour les expeditions
1090
        if ($this->element == 'shipping' && $this->origin_id != 0) {
1091
            $id = $this->origin_id;
1092
            $element = 'commande';
1093
        } else if ($this->element == 'reception' && $this->origin_id != 0) {
1094
            $id = $this->origin_id;
1095
            $element = 'order_supplier';
1096
        } else {
1097
            $id = $this->id;
1098
            $element = $this->element;
1099
        }
1100
1101
        $sql = "SELECT ec.fk_socpeople";
1102
        $sql .= " FROM " . MAIN_DB_PREFIX . "element_contact as ec,";
1103
        if ($source == 'internal') $sql .= " " . MAIN_DB_PREFIX . "user as c,";
1104
        if ($source == 'external') $sql .= " " . MAIN_DB_PREFIX . "socpeople as c,";
1105
        $sql .= " " . MAIN_DB_PREFIX . "c_type_contact as tc";
1106
        $sql .= " WHERE ec.element_id = " . $id;
1107
        $sql .= " AND ec.fk_socpeople = c.rowid";
1108
        if ($source == 'internal') $sql .= " AND c.entity IN (" . getEntity('user') . ")";
1109
        if ($source == 'external') $sql .= " AND c.entity IN (" . getEntity('societe') . ")";
1110
        $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1111
        $sql .= " AND tc.element = '" . $element . "'";
1112
        $sql .= " AND tc.source = '" . $source . "'";
1113
        $sql .= " AND tc.code = '" . $code . "'";
1114
        $sql .= " AND tc.active = 1";
1115
        if ($status) $sql .= " AND ec.statut = " . $status;
1116
1117
        dol_syslog(get_class($this) . "::getIdContact", LOG_DEBUG);
1118
        $resql = $this->db->query($sql);
1119
        if ($resql) {
1120
            while ($obj = $this->db->fetch_object($resql)) {
1121
                $result[$i] = $obj->fk_socpeople;
1122
                $i++;
1123
            }
1124
        } else {
1125
            $this->error = $this->db->error();
1126
            return null;
1127
        }
1128
1129
        return $result;
1130
    }
1131
1132
    /**
1133
     *        Load object contact with id=$this->contactid into $this->contact
1134
     *
1135
     * @param int $contactid Id du contact. Use this->contactid if empty.
1136
     * @return    int                        <0 if KO, >0 if OK
1137
     */
1138
    function fetch_contact($contactid = null)
1139
    {
1140
        // phpcs:enable
1141
        if (empty($contactid)) $contactid = $this->contactid;
1142
1143
        if (empty($contactid)) return 0;
1144
1145
        require_once DOL_DOCUMENT_ROOT . '/contact/class/contact.class.php';
1146
        $contact = new Contact($this->db);
1147
        $result = $contact->fetch($contactid);
1148
        $this->contact = $contact;
1149
        return $result;
1150
    }
1151
1152
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1153
1154
    /**
1155
     *        Load the third party of object, from id $this->socid or $this->fk_soc, into this->thirdparty
1156
     *
1157
     * @param int $force_thirdparty_id Force thirdparty id
1158
     * @return        int                                <0 if KO, >0 if OK
1159
     */
1160
    function fetch_thirdparty($force_thirdparty_id = 0)
1161
    {
1162
        // phpcs:enable
1163
        global $conf;
1164
1165
        if (empty($this->socid) && empty($this->fk_soc) && empty($this->fk_thirdparty) && empty($force_thirdparty_id))
1166
            return 0;
1167
1168
        require_once DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php';
1169
1170
        $idtofetch = isset($this->socid) ? $this->socid : (isset($this->fk_soc) ? $this->fk_soc : $this->fk_thirdparty);
1171
        if ($force_thirdparty_id)
1172
            $idtofetch = $force_thirdparty_id;
1173
1174
        if ($idtofetch) {
1175
            $thirdparty = new Societe($this->db);
1176
            $result = $thirdparty->fetch($idtofetch);
1177
            $this->thirdparty = $thirdparty;
1178
1179
            // Use first price level if level not defined for third party
1180
            if (!empty($conf->global->PRODUIT_MULTIPRICES) && empty($this->thirdparty->price_level)) {
1181
                $this->thirdparty->price_level = 1;
1182
            }
1183
1184
            return $result;
1185
        } else
1186
            return -1;
1187
    }
1188
1189
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1190
1191
    /**
1192
     * Looks for an object with ref matching the wildcard provided
1193
     * It does only work when $this->table_ref_field is set
1194
     *
1195
     * @param string $ref Wildcard
1196
     * @return int >1 = OK, 0 = Not found or table_ref_field not defined, <0 = KO
1197
     */
1198
    public function fetchOneLike($ref)
1199
    {
1200
        if (!$this->table_ref_field) {
1201
            return 0;
1202
        }
1203
1204
        $sql = 'SELECT rowid FROM ' . MAIN_DB_PREFIX . $this->table_element . ' WHERE ' . $this->table_ref_field . ' LIKE "' . $this->db->escape($ref) . '" LIMIT 1';
1205
1206
        $query = $this->db->query($sql);
1207
1208
        if (!$this->db->num_rows($query)) {
1209
            return 0;
1210
        }
1211
1212
        $result = $this->db->fetch_object($query);
1213
1214
        return $this->fetch($result->rowid);
1215
    }
1216
1217
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1218
1219
    /**
1220
     *    Load data for barcode into properties ->barcode_type*
1221
     *    Properties ->barcode_type that is id of barcode. Type is used to find other properties, but
1222
     *  if it is not defined, ->element must be defined to know default barcode type.
1223
     *
1224
     * @return        int            <0 if KO, 0 if can't guess type of barcode (ISBN, EAN13...), >0 if OK (all barcode properties loaded)
1225
     */
1226
    function fetch_barcode()
1227
    {
1228
        // phpcs:enable
1229
        global $conf;
1230
1231
        dol_syslog(get_class($this) . '::fetch_barcode this->element=' . $this->element . ' this->barcode_type=' . $this->barcode_type);
1232
1233
        $idtype = $this->barcode_type;
1234
        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
1235
        {
1236
            if ($this->element == 'product') $idtype = $conf->global->PRODUIT_DEFAULT_BARCODE_TYPE;
1237
            else if ($this->element == 'societe') $idtype = $conf->global->GENBARCODE_BARCODETYPE_THIRDPARTY;
1238
            else dol_syslog('Call fetch_barcode with barcode_type not defined and cant be guessed', LOG_WARNING);
1239
        }
1240
1241
        if ($idtype > 0) {
1242
            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
1243
            {
1244
                $sql = "SELECT rowid, code, libelle as label, coder";
1245
                $sql .= " FROM " . MAIN_DB_PREFIX . "c_barcode_type";
1246
                $sql .= " WHERE rowid = " . $idtype;
1247
                dol_syslog(get_class($this) . '::fetch_barcode', LOG_DEBUG);
1248
                $resql = $this->db->query($sql);
1249
                if ($resql) {
1250
                    $obj = $this->db->fetch_object($resql);
1251
                    $this->barcode_type = $obj->rowid;
1252
                    $this->barcode_type_code = $obj->code;
1253
                    $this->barcode_type_label = $obj->label;
1254
                    $this->barcode_type_coder = $obj->coder;
1255
                    return 1;
1256
                } else {
1257
                    dol_print_error($this->db);
1258
                    return -1;
1259
                }
1260
            }
1261
        }
1262
        return 0;
1263
    }
1264
1265
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1266
1267
    /**
1268
     *        Load the project with id $this->fk_project into this->project
1269
     *
1270
     * @return        int            <0 if KO, >=0 if OK
1271
     */
1272
    function fetch_projet()
1273
    {
1274
        // phpcs:enable
1275
        include_once DOL_DOCUMENT_ROOT . '/projet/class/project.class.php';
1276
1277
        if (empty($this->fk_project) && !empty($this->fk_projet)) $this->fk_project = $this->fk_projet;    // For backward compatibility
1278
        if (empty($this->fk_project)) return 0;
1279
1280
        $project = new Project($this->db);
1281
        $result = $project->fetch($this->fk_project);
1282
1283
        $this->projet = $project;    // deprecated
1284
        $this->project = $project;
1285
        return $result;
1286
    }
1287
1288
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1289
1290
    /**
1291
     *        Load the product with id $this->fk_product into this->product
1292
     *
1293
     * @return        int            <0 if KO, >=0 if OK
1294
     */
1295
    function fetch_product()
1296
    {
1297
        // phpcs:enable
1298
        include_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
1299
1300
        if (empty($this->fk_product)) return 0;
1301
1302
        $product = new Product($this->db);
1303
        $result = $product->fetch($this->fk_product);
1304
1305
        $this->product = $product;
1306
        return $result;
1307
    }
1308
1309
    /**
1310
     *        Load the user with id $userid into this->user
1311
     *
1312
     * @param int $userid Id du contact
1313
     * @return    int                        <0 if KO, >0 if OK
1314
     */
1315
    function fetch_user($userid)
1316
    {
1317
        // phpcs:enable
1318
        $user = new User($this->db);
1319
        $result = $user->fetch($userid);
1320
        $this->user = $user;
1321
        return $result;
1322
    }
1323
1324
    /**
1325
     *    Read linked origin object
1326
     *
1327
     * @return        void
1328
     */
1329
    function fetch_origin()
1330
    {
1331
        // phpcs:enable
1332
        if ($this->origin == 'shipping') $this->origin = 'expedition';
1333
        if ($this->origin == 'delivery') $this->origin = 'livraison';
1334
        if ($this->origin == 'order_supplier') $this->origin = 'commandeFournisseur';
1335
1336
        $origin = $this->origin;
1337
1338
        $classname = ucfirst($origin);
1339
        $this->$origin = new $classname($this->db);
1340
        $this->$origin->fetch($this->origin_id);
1341
    }
1342
1343
    /**
1344
     *  Load object from specific field
1345
     *
1346
     * @param string $table Table element or element line
1347
     * @param string $field Field selected
1348
     * @param string $key Import key
1349
     * @param string $element Element name
1350
     * @return    int                    <0 if KO, >0 if OK
1351
     */
1352
    function fetchObjectFrom($table, $field, $key, $element = null)
1353
    {
1354
        global $conf;
1355
1356
        $result = false;
1357
1358
        $sql = "SELECT rowid FROM " . MAIN_DB_PREFIX . $table;
1359
        $sql .= " WHERE " . $field . " = '" . $key . "'";
1360
        if (!empty($element)) {
1361
            $sql .= " AND entity IN (" . getEntity($element) . ")";
1362
        } else {
1363
            $sql .= " AND entity = " . $conf->entity;
1364
        }
1365
1366
        dol_syslog(get_class($this) . '::fetchObjectFrom', LOG_DEBUG);
1367
        $resql = $this->db->query($sql);
1368
        if ($resql) {
1369
            $row = $this->db->fetch_row($resql);
1370
            // Test for avoid error -1
1371
            if ($row[0] > 0) {
1372
                $result = $this->fetch($row[0]);
1373
            }
1374
        }
1375
1376
        return $result;
1377
    }
1378
1379
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1380
1381
    /**
1382
     *    Getter generic. Load value from a specific field
1383
     *
1384
     * @param string $table Table of element or element line
1385
     * @param int $id Element id
1386
     * @param string $field Field selected
1387
     * @return    int                    <0 if KO, >0 if OK
1388
     */
1389
    function getValueFrom($table, $id, $field)
1390
    {
1391
        $result = false;
1392
        if (!empty($id) && !empty($field) && !empty($table)) {
1393
            $sql = "SELECT " . $field . " FROM " . MAIN_DB_PREFIX . $table;
1394
            $sql .= " WHERE rowid = " . $id;
1395
1396
            dol_syslog(get_class($this) . '::getValueFrom', LOG_DEBUG);
1397
            $resql = $this->db->query($sql);
1398
            if ($resql) {
1399
                $row = $this->db->fetch_row($resql);
1400
                $result = $row[0];
1401
            }
1402
        }
1403
        return $result;
1404
    }
1405
1406
    /**
1407
     *    Setter generic. Update a specific field into database.
1408
     *  Warning: Trigger is run only if param trigkey is provided.
1409
     *
1410
     * @param string $field Field to update
1411
     * @param mixed $value New value
1412
     * @param string $table To force other table element or element line (should not be used)
1413
     * @param int $id To force other object id (should not be used)
1414
     * @param string $format Data format ('text', 'date'). 'text' is used if not defined
1415
     * @param string $id_field To force rowid field name. 'rowid' is used if not defined
1416
     * @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'
1417
     * @param string $trigkey Trigger key to run (in most cases something like 'XXX_MODIFY')
1418
     * @param string $fk_user_field Name of field to save user id making change
1419
     * @return    int                            <0 if KO, >0 if OK
1420
     * @see updateExtraField
1421
     */
1422
    function setValueFrom($field, $value, $table = '', $id = null, $format = '', $id_field = '', $fuser = null, $trigkey = '', $fk_user_field = 'fk_user_modif')
1423
    {
1424
        global $user, $langs, $conf;
1425
1426
        if (empty($table)) $table = $this->table_element;
1427
        if (empty($id)) $id = $this->id;
1428
        if (empty($format)) $format = 'text';
1429
        if (empty($id_field)) $id_field = 'rowid';
1430
1431
        $error = 0;
1432
1433
        $this->db->begin();
1434
1435
        // Special case
1436
        if ($table == 'product' && $field == 'note_private') $field = 'note';
1437
        if (in_array($table, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) $fk_user_field = 'fk_user_mod';
1438
1439
        $sql = "UPDATE " . MAIN_DB_PREFIX . $table . " SET ";
1440
1441
        if ($format == 'text') $sql .= $field . " = '" . $this->db->escape($value) . "'";
1442
        else if ($format == 'int') $sql .= $field . " = " . $this->db->escape($value);
1443
        else if ($format == 'date') $sql .= $field . " = " . ($value ? "'" . $this->db->idate($value) . "'" : "null");
1444
1445
        if ($fk_user_field) {
1446
            if (!empty($fuser) && is_object($fuser)) $sql .= ", " . $fk_user_field . " = " . $fuser->id;
1447
            elseif (empty($fuser) || $fuser != 'none') $sql .= ", " . $fk_user_field . " = " . $user->id;
1448
        }
1449
1450
        $sql .= " WHERE " . $id_field . " = " . $id;
1451
1452
        dol_syslog(get_class($this) . "::" . __FUNCTION__ . "", LOG_DEBUG);
1453
        $resql = $this->db->query($sql);
1454
        if ($resql) {
1455
            if ($trigkey) {
1456
                // call trigger with updated object values
1457
                if (empty($this->fields) && method_exists($this, 'fetch')) {
1458
                    $result = $this->fetch($id);
1459
                } else {
1460
                    $result = $this->fetchCommon($id);
1461
                }
1462
                if ($result >= 0) $result = $this->call_trigger($trigkey, (!empty($fuser) && is_object($fuser)) ? $fuser : $user);   // This may set this->errors
1463
                if ($result < 0) $error++;
1464
            }
1465
1466
            if (!$error) {
1467
                if (property_exists($this, $field)) $this->$field = $value;
1468
                $this->db->commit();
1469
                return 1;
1470
            } else {
1471
                $this->db->rollback();
1472
                return -2;
1473
            }
1474
        } else {
1475
            $this->error = $this->db->lasterror();
1476
            $this->db->rollback();
1477
            return -1;
1478
        }
1479
    }
1480
1481
    /**
1482
     * Load object in memory from the database
1483
     *
1484
     * @param int $id Id object
1485
     * @param string $ref Ref
1486
     * @param string $morewhere More SQL filters (' AND ...')
1487
     * @return    int                    <0 if KO, 0 if not found, >0 if OK
1488
     */
1489
    public function fetchCommon($id, $ref = null, $morewhere = '')
1490
    {
1491
        if (empty($id) && empty($ref) && empty($morewhere)) return -1;
1492
1493
        $sql = 'SELECT ' . $this->getFieldList();
1494
        $sql .= ' FROM ' . MAIN_DB_PREFIX . $this->table_element;
1495
1496
        if (!empty($id)) $sql .= ' WHERE rowid = ' . $id;
1497
        elseif (!empty($ref)) $sql .= " WHERE ref = " . $this->quote($ref, $this->fields['ref']);
1498
        else $sql .= ' WHERE 1 = 1';    // usage with empty id and empty ref is very rare
1499
        if ($morewhere) $sql .= $morewhere;
1500
        $sql .= ' LIMIT 1';    // This is a fetch, to be sure to get only one record
1501
1502
        $res = $this->db->query($sql);
1503
        if ($res) {
1504
            $obj = $this->db->fetch_object($res);
1505
            if ($obj) {
1506
                $this->setVarsFromFetchObj($obj);
1507
                return $this->id;
1508
            } else {
1509
                return 0;
1510
            }
1511
        } else {
1512
            $this->error = $this->db->lasterror();
1513
            $this->errors[] = $this->error;
1514
            return -1;
1515
        }
1516
    }
1517
1518
    /**
1519
     * Function to concat keys of fields
1520
     *
1521
     * @return string
1522
     */
1523
    protected function getFieldList()
1524
    {
1525
        $keys = array_keys($this->fields);
1526
        return implode(',', $keys);
1527
    }
1528
1529
    /**
1530
     * Add quote to field value if necessary
1531
     *
1532
     * @param string|int $value Value to protect
1533
     * @param array $fieldsentry Properties of field
1534
     * @return    string
1535
     */
1536
    protected function quote($value, $fieldsentry)
1537
    {
1538
        if (is_null($value)) return 'NULL';
1539
        else if (preg_match('/^(int|double|real)/i', $fieldsentry['type'])) return $this->db->escape("$value");
1540
        else return "'" . $this->db->escape($value) . "'";
1541
    }
1542
1543
    /**
1544
     * Function to load data from a SQL pointer into properties of current object $this
1545
     *
1546
     * @param stdClass $obj Contain data of object from database
1547
     * @return void
1548
     */
1549
    protected function setVarsFromFetchObj(&$obj)
1550
    {
1551
        foreach ($this->fields as $field => $info) {
1552
            if ($this->isDate($info)) {
1553
                if (empty($obj->{$field}) || $obj->{$field} === '0000-00-00 00:00:00' || $obj->{$field} === '1000-01-01 00:00:00') $this->{$field} = 0;
1554
                else $this->{$field} = strtotime($obj->{$field});
1555
            } elseif ($this->isArray($info)) {
1556
                if (!empty($obj->{$field})) {
1557
                    $this->{$field} = @unserialize($obj->{$field});
1558
                    // Hack for data not in UTF8
1559
                    if ($this->{$field} === false) @unserialize(utf8_decode($obj->{$field}));
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unserialize(). 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

1559
                    if ($this->{$field} === false) /** @scrutinizer ignore-unhandled */ @unserialize(utf8_decode($obj->{$field}));

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...
1560
                } else {
1561
                    $this->{$field} = array();
1562
                }
1563
            } elseif ($this->isInt($info)) {
1564
                if ($field == 'rowid') $this->id = (int)$obj->{$field};
1565
                else $this->{$field} = (int)$obj->{$field};
1566
            } elseif ($this->isFloat($info)) {
1567
                $this->{$field} = (double)$obj->{$field};
1568
            } elseif ($this->isNull($info)) {
1569
                $val = $obj->{$field};
1570
                // zero is not null
1571
                $this->{$field} = (is_null($val) || (empty($val) && $val !== 0 && $val !== '0') ? null : $val);
1572
            } else {
1573
                $this->{$field} = $obj->{$field};
1574
            }
1575
        }
1576
1577
        // If there is no 'ref' field, we force property ->ref to ->id for a better compatibility with common functions.
1578
        if (!isset($this->fields['ref']) && isset($this->id)) $this->ref = $this->id;
1579
    }
1580
1581
    /**
1582
     * Function test if type is date
1583
     *
1584
     * @param array $info content informations of field
1585
     * @return                  bool
1586
     */
1587
    public function isDate($info)
1588
    {
1589
        if (isset($info['type']) && ($info['type'] == 'date' || $info['type'] == 'datetime' || $info['type'] == 'timestamp')) return true;
1590
        else return false;
1591
    }
1592
1593
    /**
1594
     * Function test if type is array
1595
     *
1596
     * @param array $info content informations of field
1597
     * @return                  bool
1598
     */
1599
    protected function isArray($info)
1600
    {
1601
        if (is_array($info)) {
1602
            if (isset($info['type']) && $info['type'] == 'array') return true;
1603
            else return false;
1604
        } else return false;
1605
    }
1606
1607
    /**
1608
     * Function test if type is integer
1609
     *
1610
     * @param array $info content informations of field
1611
     * @return                  bool
1612
     */
1613
    public function isInt($info)
1614
    {
1615
        if (is_array($info)) {
1616
            if (isset($info['type']) && ($info['type'] == 'int' || preg_match('/^integer/i', $info['type']))) return true;
1617
            else return false;
1618
        } else return false;
1619
    }
1620
1621
    /**
1622
     * Function test if type is float
1623
     *
1624
     * @param array $info content informations of field
1625
     * @return                  bool
1626
     */
1627
    public function isFloat($info)
1628
    {
1629
        if (is_array($info)) {
1630
            if (isset($info['type']) && (preg_match('/^(double|real)/i', $info['type']))) return true;
1631
            else return false;
1632
        } else return false;
1633
    }
1634
1635
    /**
1636
     * Function test if type is null
1637
     *
1638
     * @param array $info content informations of field
1639
     * @return                  bool
1640
     */
1641
    protected function isNull($info)
1642
    {
1643
        if (is_array($info)) {
1644
            if (isset($info['type']) && $info['type'] == 'null') return true;
1645
            else return false;
1646
        } else return false;
1647
    }
1648
1649
    /**
1650
     *      Load properties id_previous and id_next by comparing $fieldid with $this->ref
1651
     *
1652
     * @param string $filter Optional filter. Example: " AND (t.field1 = 'aa' OR t.field2 = 'bb')"
1653
     * @param string $fieldid Name of field to use for the select MAX and MIN
1654
     * @param int $nodbprefix Do not include DB prefix to forge table name
1655
     * @return int                <0 if KO, >0 if OK
1656
     */
1657
    function load_previous_next_ref($filter, $fieldid, $nodbprefix = 0)
1658
    {
1659
        // phpcs:enable
1660
        global $conf, $user;
1661
1662
        if (!$this->table_element) {
1663
            dol_print_error('', get_class($this) . "::load_previous_next_ref was called on objet with property table_element not defined");
1664
            return -1;
1665
        }
1666
        if ($fieldid == 'none') return 1;
1667
1668
        // Security on socid
1669
        $socid = 0;
1670
        if ($user->societe_id > 0) $socid = $user->societe_id;
1671
1672
        // this->ismultientitymanaged contains
1673
        // 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
1674
        $alias = 's';
1675
        if ($this->element == 'societe') $alias = 'te';
1676
1677
        $sql = "SELECT MAX(te." . $fieldid . ")";
1678
        $sql .= " FROM " . (empty($nodbprefix) ? MAIN_DB_PREFIX : '') . $this->table_element . " as te";
1679
        if ($this->element == 'user' && !empty($conf->global->MULTICOMPANY_TRANSVERSE_MODE)) {
1680
            $sql .= "," . MAIN_DB_PREFIX . "usergroup_user as ug";
1681
        }
1682
        if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 2) $sql .= ", " . MAIN_DB_PREFIX . "societe as s";    // If we need to link to societe to limit select to entity
1683
        else if ($this->restrictiononfksoc == 1 && $this->element != 'societe' && !$user->rights->societe->client->voir && !$socid) $sql .= ", " . MAIN_DB_PREFIX . "societe as s";    // If we need to link to societe to limit select to socid
1684
        else if ($this->restrictiononfksoc == 2 && $this->element != 'societe' && !$user->rights->societe->client->voir && !$socid) $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe as s ON te.fk_soc = s.rowid";    // If we need to link to societe to limit select to socid
1685
        if ($this->restrictiononfksoc && !$user->rights->societe->client->voir && !$socid) $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON " . $alias . ".rowid = sc.fk_soc";
1686
        $sql .= " WHERE te." . $fieldid . " < '" . $this->db->escape($this->ref) . "'";  // ->ref must always be defined (set to id if field does not exists)
1687
        if ($this->restrictiononfksoc == 1 && !$user->rights->societe->client->voir && !$socid) $sql .= " AND sc.fk_user = " . $user->id;
1688
        if ($this->restrictiononfksoc == 2 && !$user->rights->societe->client->voir && !$socid) $sql .= " AND (sc.fk_user = " . $user->id . ' OR te.fk_soc IS NULL)';
1689
        if (!empty($filter)) {
1690
            if (!preg_match('/^\s*AND/i', $filter)) $sql .= " AND ";   // For backward compatibility
1691
            $sql .= $filter;
1692
        }
1693
        if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 2) $sql .= ' AND te.fk_soc = s.rowid';            // If we need to link to societe to limit select to entity
1694
        else if ($this->restrictiononfksoc == 1 && $this->element != 'societe' && !$user->rights->societe->client->voir && !$socid) $sql .= ' AND te.fk_soc = s.rowid';            // If we need to link to societe to limit select to socid
1695
        if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
1696
            if ($this->element == 'user' && !empty($conf->global->MULTICOMPANY_TRANSVERSE_MODE)) {
1697
                if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
1698
                    $sql .= " AND te.entity IS NOT NULL"; // Show all users
1699
                } else {
1700
                    $sql .= " AND ug.fk_user = te.rowid";
1701
                    $sql .= " AND ug.entity IN (" . getEntity($this->element) . ")";
1702
                }
1703
            } else {
1704
                $sql .= ' AND te.entity IN (' . getEntity($this->element) . ')';
1705
            }
1706
        }
1707
        if ($this->restrictiononfksoc == 1 && $socid && $this->element != 'societe') $sql .= ' AND te.fk_soc = ' . $socid;
1708
        if ($this->restrictiononfksoc == 2 && $socid && $this->element != 'societe') $sql .= ' AND (te.fk_soc = ' . $socid . ' OR te.fk_soc IS NULL)';
1709
        if ($this->restrictiononfksoc && $socid && $this->element == 'societe') $sql .= ' AND te.rowid = ' . $socid;
1710
        //print 'socid='.$socid.' restrictiononfksoc='.$this->restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
1711
1712
        $result = $this->db->query($sql);
1713
        if (!$result) {
1714
            $this->error = $this->db->lasterror();
1715
            return -1;
1716
        }
1717
        $row = $this->db->fetch_row($result);
1718
        $this->ref_previous = $row[0];
1719
1720
1721
        $sql = "SELECT MIN(te." . $fieldid . ")";
1722
        $sql .= " FROM " . (empty($nodbprefix) ? MAIN_DB_PREFIX : '') . $this->table_element . " as te";
1723
        if ($this->element == 'user' && !empty($conf->global->MULTICOMPANY_TRANSVERSE_MODE)) {
1724
            $sql .= "," . MAIN_DB_PREFIX . "usergroup_user as ug";
1725
        }
1726
        if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 2) $sql .= ", " . MAIN_DB_PREFIX . "societe as s";    // If we need to link to societe to limit select to entity
1727
        else if ($this->restrictiononfksoc == 1 && $this->element != 'societe' && !$user->rights->societe->client->voir && !$socid) $sql .= ", " . MAIN_DB_PREFIX . "societe as s";    // If we need to link to societe to limit select to socid
1728
        else if ($this->restrictiononfksoc == 2 && $this->element != 'societe' && !$user->rights->societe->client->voir && !$socid) $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe as s ON te.fk_soc = s.rowid";    // If we need to link to societe to limit select to socid
1729
        if ($this->restrictiononfksoc && !$user->rights->societe->client->voir && !$socid) $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON " . $alias . ".rowid = sc.fk_soc";
1730
        $sql .= " WHERE te." . $fieldid . " > '" . $this->db->escape($this->ref) . "'";  // ->ref must always be defined (set to id if field does not exists)
1731
        if ($this->restrictiononfksoc == 1 && !$user->rights->societe->client->voir && !$socid) $sql .= " AND sc.fk_user = " . $user->id;
1732
        if ($this->restrictiononfksoc == 2 && !$user->rights->societe->client->voir && !$socid) $sql .= " AND (sc.fk_user = " . $user->id . ' OR te.fk_soc IS NULL)';
1733
        if (!empty($filter)) {
1734
            if (!preg_match('/^\s*AND/i', $filter)) $sql .= " AND ";   // For backward compatibility
1735
            $sql .= $filter;
1736
        }
1737
        if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 2) $sql .= ' AND te.fk_soc = s.rowid';            // If we need to link to societe to limit select to entity
1738
        else if ($this->restrictiononfksoc == 1 && $this->element != 'societe' && !$user->rights->societe->client->voir && !$socid) $sql .= ' AND te.fk_soc = s.rowid';            // If we need to link to societe to limit select to socid
1739
        if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
1740
            if ($this->element == 'user' && !empty($conf->global->MULTICOMPANY_TRANSVERSE_MODE)) {
1741
                if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
1742
                    $sql .= " AND te.entity IS NOT NULL"; // Show all users
1743
                } else {
1744
                    $sql .= " AND ug.fk_user = te.rowid";
1745
                    $sql .= " AND ug.entity IN (" . getEntity($this->element) . ")";
1746
                }
1747
            } else {
1748
                $sql .= ' AND te.entity IN (' . getEntity($this->element) . ')';
1749
            }
1750
        }
1751
        if ($this->restrictiononfksoc == 1 && $socid && $this->element != 'societe') $sql .= ' AND te.fk_soc = ' . $socid;
1752
        if ($this->restrictiononfksoc == 2 && $socid && $this->element != 'societe') $sql .= ' AND (te.fk_soc = ' . $socid . ' OR te.fk_soc IS NULL)';
1753
        if ($this->restrictiononfksoc && $socid && $this->element == 'societe') $sql .= ' AND te.rowid = ' . $socid;
1754
        //print 'socid='.$socid.' restrictiononfksoc='.$this->restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
1755
        // 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
1756
1757
        $result = $this->db->query($sql);
1758
        if (!$result) {
1759
            $this->error = $this->db->lasterror();
1760
            return -2;
1761
        }
1762
        $row = $this->db->fetch_row($result);
1763
        $this->ref_next = $row[0];
1764
1765
        return 1;
1766
    }
1767
1768
1769
    // TODO: Move line related operations to CommonObjectLine?
1770
1771
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1772
1773
    /**
1774
     *      Return list of id of contacts of object
1775
     *
1776
     * @param string $source Source of contact: external (llx_socpeople) or internal (llx_user) or thirdparty (llx_societe)
1777
     * @return array                Array of id of contacts (if source=external or internal)
1778
     *                                    Array of id of third parties with at least one contact on object (if source=thirdparty)
1779
     */
1780
    function getListContactId($source = 'external')
1781
    {
1782
        $contactAlreadySelected = array();
1783
        $tab = $this->liste_contact(-1, $source);
1784
        $num = count($tab);
1785
        $i = 0;
1786
        while ($i < $num) {
1787
            if ($source == 'thirdparty') $contactAlreadySelected[$i] = $tab[$i]['socid'];
1788
            else  $contactAlreadySelected[$i] = $tab[$i]['id'];
1789
            $i++;
1790
        }
1791
        return $contactAlreadySelected;
1792
    }
1793
1794
    /**
1795
     *    Link element with a project
1796
     *
1797
     * @param int $projectid Project id to link element to
1798
     * @return        int                        <0 if KO, >0 if OK
1799
     */
1800
    function setProject($projectid)
1801
    {
1802
        if (!$this->table_element) {
1803
            dol_syslog(get_class($this) . "::setProject was called on objet with property table_element not defined", LOG_ERR);
1804
            return -1;
1805
        }
1806
1807
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element;
1808
        if ($this->table_element == 'actioncomm') {
1809
            if ($projectid) $sql .= ' SET fk_project = ' . $projectid;
1810
            else $sql .= ' SET fk_project = NULL';
1811
            $sql .= ' WHERE id = ' . $this->id;
1812
        } else {
1813
            if ($projectid) $sql .= ' SET fk_projet = ' . $projectid;
1814
            else $sql .= ' SET fk_projet = NULL';
1815
            $sql .= ' WHERE rowid = ' . $this->id;
1816
        }
1817
1818
        dol_syslog(get_class($this) . "::setProject", LOG_DEBUG);
1819
        if ($this->db->query($sql)) {
1820
            $this->fk_project = $projectid;
1821
            return 1;
1822
        } else {
1823
            dol_print_error($this->db);
1824
            return -1;
1825
        }
1826
    }
1827
1828
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1829
1830
    /**
1831
     *  Change the payments methods
1832
     *
1833
     * @param int $id Id of new payment method
1834
     * @return        int                >0 if OK, <0 if KO
1835
     */
1836
    function setPaymentMethods($id)
1837
    {
1838
        dol_syslog(get_class($this) . '::setPaymentMethods(' . $id . ')');
1839
        if ($this->statut >= 0 || $this->element == 'societe') {
1840
            // TODO uniformize field name
1841
            $fieldname = 'fk_mode_reglement';
1842
            if ($this->element == 'societe') $fieldname = 'mode_reglement';
1843
            if (get_class($this) == 'Fournisseur') $fieldname = 'mode_reglement_supplier';
1844
1845
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element;
1846
            $sql .= ' SET ' . $fieldname . ' = ' . $id;
1847
            $sql .= ' WHERE rowid=' . $this->id;
1848
1849
            if ($this->db->query($sql)) {
1850
                $this->mode_reglement_id = $id;
1851
                // for supplier
1852
                if (get_class($this) == 'Fournisseur') $this->mode_reglement_supplier_id = $id;
1853
                return 1;
1854
            } else {
1855
                dol_syslog(get_class($this) . '::setPaymentMethods Erreur ' . $sql . ' - ' . $this->db->error());
1856
                $this->error = $this->db->error();
1857
                return -1;
1858
            }
1859
        } else {
1860
            dol_syslog(get_class($this) . '::setPaymentMethods, status of the object is incompatible');
1861
            $this->error = 'Status of the object is incompatible ' . $this->statut;
1862
            return -2;
1863
        }
1864
    }
1865
1866
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1867
1868
    /**
1869
     *  Change the multicurrency code
1870
     *
1871
     * @param string $code multicurrency code
1872
     * @return        int                >0 if OK, <0 if KO
1873
     */
1874
    function setMulticurrencyCode($code)
1875
    {
1876
        dol_syslog(get_class($this) . '::setMulticurrencyCode(' . $id . ')');
1877
        if ($this->statut >= 0 || $this->element == 'societe') {
1878
            $fieldname = 'multicurrency_code';
1879
1880
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element;
1881
            $sql .= ' SET ' . $fieldname . " = '" . $this->db->escape($code) . "'";
1882
            $sql .= ' WHERE rowid=' . $this->id;
1883
1884
            if ($this->db->query($sql)) {
1885
                $this->multicurrency_code = $code;
1886
1887
                list($fk_multicurrency, $rate) = MultiCurrency::getIdAndTxFromCode($this->db, $code);
1888
                if ($rate) $this->setMulticurrencyRate($rate, 2);
1889
1890
                return 1;
1891
            } else {
1892
                dol_syslog(get_class($this) . '::setMulticurrencyCode Erreur ' . $sql . ' - ' . $this->db->error());
1893
                $this->error = $this->db->error();
1894
                return -1;
1895
            }
1896
        } else {
1897
            dol_syslog(get_class($this) . '::setMulticurrencyCode, status of the object is incompatible');
1898
            $this->error = 'Status of the object is incompatible ' . $this->statut;
1899
            return -2;
1900
        }
1901
    }
1902
1903
    /**
1904
     *  Change the multicurrency rate
1905
     *
1906
     * @param double $rate multicurrency rate
1907
     * @param int $mode mode 1 : amounts in company currency will be recalculated, mode 2 : amounts in foreign currency
1908
     * @return        int                >0 if OK, <0 if KO
1909
     */
1910
    function setMulticurrencyRate($rate, $mode = 1)
1911
    {
1912
        dol_syslog(get_class($this) . '::setMulticurrencyRate(' . $id . ')');
1913
        if ($this->statut >= 0 || $this->element == 'societe') {
1914
            $fieldname = 'multicurrency_tx';
1915
1916
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element;
1917
            $sql .= ' SET ' . $fieldname . ' = ' . $rate;
1918
            $sql .= ' WHERE rowid=' . $this->id;
1919
1920
            if ($this->db->query($sql)) {
1921
                $this->multicurrency_tx = $rate;
1922
1923
                // Update line price
1924
                if (!empty($this->lines)) {
1925
                    foreach ($this->lines as &$line) {
1926
                        if ($mode == 1) {
1927
                            $line->subprice = 0;
1928
                        }
1929
1930
                        switch ($this->element) {
1931
                            case 'propal':
1932
                                $this->updateline(
1933
                                    $line->id, $line->subprice, $line->qty, $line->remise_percent, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx,
1934
                                    ($line->description ? $line->description : $line->desc), 'HT', $line->info_bits, $line->special_code, $line->fk_parent_line,
1935
                                    $line->skip_update_total, $line->fk_fournprice, $line->pa_ht, $line->label, $line->product_type, $line->date_start,
1936
                                    $line->date_end, $line->array_options, $line->fk_unit, $line->multicurrency_subprice
1937
                                );
1938
                                break;
1939
                            case 'commande':
1940
                                $this->updateline(
1941
                                    $line->id, ($line->description ? $line->description : $line->desc), $line->subprice, $line->qty, $line->remise_percent,
1942
                                    $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, 'HT', $line->info_bits, $line->date_start, $line->date_end,
1943
                                    $line->product_type, $line->fk_parent_line, $line->skip_update_total, $line->fk_fournprice, $line->pa_ht, $line->label,
1944
                                    $line->special_code, $line->array_options, $line->fk_unit, $line->multicurrency_subprice
1945
                                );
1946
                                break;
1947
                            case 'facture':
1948
                                $this->updateline(
1949
                                    $line->id, ($line->description ? $line->description : $line->desc), $line->subprice, $line->qty, $line->remise_percent,
1950
                                    $line->date_start, $line->date_end, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, 'HT', $line->info_bits,
1951
                                    $line->product_type, $line->fk_parent_line, $line->skip_update_total, $line->fk_fournprice, $line->pa_ht, $line->label,
1952
                                    $line->special_code, $line->array_options, $line->situation_percent, $line->fk_unit, $line->multicurrency_subprice
1953
                                );
1954
                                break;
1955
                            case 'supplier_proposal':
1956
                                $this->updateline(
1957
                                    $line->id, $line->subprice, $line->qty, $line->remise_percent, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx,
1958
                                    ($line->description ? $line->description : $line->desc), 'HT', $line->info_bits, $line->special_code, $line->fk_parent_line,
1959
                                    $line->skip_update_total, $line->fk_fournprice, $line->pa_ht, $line->label, $line->product_type, $line->array_options,
1960
                                    $line->ref_fourn, $line->multicurrency_subprice
1961
                                );
1962
                                break;
1963
                            case 'order_supplier':
1964
                                $this->updateline(
1965
                                    $line->id, ($line->description ? $line->description : $line->desc), $line->subprice, $line->qty, $line->remise_percent,
1966
                                    $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, 'HT', $line->info_bits, $line->product_type, false,
1967
                                    $line->date_start, $line->date_end, $line->array_options, $line->fk_unit, $line->multicurrency_subprice
1968
                                );
1969
                                break;
1970
                            case 'invoice_supplier':
1971
                                $this->updateline(
1972
                                    $line->id, ($line->description ? $line->description : $line->desc), $line->subprice, $line->tva_tx, $line->localtax1_tx,
1973
                                    $line->localtax2_tx, $line->qty, 0, 'HT', $line->info_bits, $line->product_type, $line->remise_percent, false,
1974
                                    $line->date_start, $line->date_end, $line->array_options, $line->fk_unit, $line->multicurrency_subprice
1975
                                );
1976
                                break;
1977
                            default:
1978
                                dol_syslog(get_class($this) . '::setMulticurrencyRate no updateline defined', LOG_DEBUG);
1979
                                break;
1980
                        }
1981
                    }
1982
                }
1983
1984
                return 1;
1985
            } else {
1986
                dol_syslog(get_class($this) . '::setMulticurrencyRate Erreur ' . $sql . ' - ' . $this->db->error());
1987
                $this->error = $this->db->error();
1988
                return -1;
1989
            }
1990
        } else {
1991
            dol_syslog(get_class($this) . '::setMulticurrencyRate, status of the object is incompatible');
1992
            $this->error = 'Status of the object is incompatible ' . $this->statut;
1993
            return -2;
1994
        }
1995
    }
1996
1997
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1998
1999
    /**
2000
     *  Change the payments terms
2001
     *
2002
     * @param int $id Id of new payment terms
2003
     * @return        int                >0 if OK, <0 if KO
2004
     */
2005
    function setPaymentTerms($id)
2006
    {
2007
        dol_syslog(get_class($this) . '::setPaymentTerms(' . $id . ')');
2008
        if ($this->statut >= 0 || $this->element == 'societe') {
2009
            // TODO uniformize field name
2010
            $fieldname = 'fk_cond_reglement';
2011
            if ($this->element == 'societe') $fieldname = 'cond_reglement';
2012
            if (get_class($this) == 'Fournisseur') $fieldname = 'cond_reglement_supplier';
2013
2014
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element;
2015
            $sql .= ' SET ' . $fieldname . ' = ' . $id;
2016
            $sql .= ' WHERE rowid=' . $this->id;
2017
2018
            if ($this->db->query($sql)) {
2019
                $this->cond_reglement_id = $id;
2020
                // for supplier
2021
                if (get_class($this) == 'Fournisseur') $this->cond_reglement_supplier_id = $id;
2022
                $this->cond_reglement = $id;    // for compatibility
2023
                return 1;
2024
            } else {
2025
                dol_syslog(get_class($this) . '::setPaymentTerms Erreur ' . $sql . ' - ' . $this->db->error());
2026
                $this->error = $this->db->error();
2027
                return -1;
2028
            }
2029
        } else {
2030
            dol_syslog(get_class($this) . '::setPaymentTerms, status of the object is incompatible');
2031
            $this->error = 'Status of the object is incompatible ' . $this->statut;
2032
            return -2;
2033
        }
2034
    }
2035
2036
    /**
2037
     *    Define delivery address
2038
     * @param int $id Address id
2039
     * @return     int                <0 si ko, >0 si ok
2040
     * @deprecated
2041
     *
2042
     */
2043
    function setDeliveryAddress($id)
2044
    {
2045
        $fieldname = 'fk_delivery_address';
2046
        if ($this->element == 'delivery' || $this->element == 'shipping') $fieldname = 'fk_address';
2047
2048
        $sql = "UPDATE " . MAIN_DB_PREFIX . $this->table_element . " SET " . $fieldname . " = " . $id;
2049
        $sql .= " WHERE rowid = " . $this->id . " AND fk_statut = 0";
2050
2051
        if ($this->db->query($sql)) {
2052
            $this->fk_delivery_address = $id;
2053
            return 1;
2054
        } else {
2055
            $this->error = $this->db->error();
2056
            dol_syslog(get_class($this) . '::setDeliveryAddress Erreur ' . $sql . ' - ' . $this->error);
2057
            return -1;
2058
        }
2059
    }
2060
2061
    /**
2062
     *  Change the shipping method
2063
     *
2064
     * @param int $shipping_method_id Id of shipping method
2065
     * @param bool $notrigger false=launch triggers after, true=disable triggers
2066
     * @param User $userused Object user
2067
     *
2068
     * @return     int              1 if OK, 0 if KO
2069
     */
2070
    function setShippingMethod($shipping_method_id, $notrigger = false, $userused = null)
2071
    {
2072
        global $user;
2073
2074
        if (empty($userused)) $userused = $user;
2075
2076
        $error = 0;
2077
2078
        if (!$this->table_element) {
2079
            dol_syslog(get_class($this) . "::setShippingMethod was called on objet with property table_element not defined", LOG_ERR);
2080
            return -1;
2081
        }
2082
2083
        $this->db->begin();
2084
2085
        if ($shipping_method_id < 0) $shipping_method_id = 'NULL';
2086
        dol_syslog(get_class($this) . '::setShippingMethod(' . $shipping_method_id . ')');
2087
2088
        $sql = "UPDATE " . MAIN_DB_PREFIX . $this->table_element;
2089
        $sql .= " SET fk_shipping_method = " . $shipping_method_id;
2090
        $sql .= " WHERE rowid=" . $this->id;
2091
        $resql = $this->db->query($sql);
2092
        if (!$resql) {
2093
            dol_syslog(get_class($this) . '::setShippingMethod Error ', LOG_DEBUG);
2094
            $this->error = $this->db->lasterror();
2095
            $error++;
2096
        } else {
2097
            if (!$notrigger) {
2098
                // Call trigger
2099
                $this->context = array('shippingmethodupdate' => 1);
2100
                $result = $this->call_trigger(strtoupper(get_class($this)) . '_MODIFY', $userused);
2101
                if ($result < 0) $error++;
2102
                // End call trigger
2103
            }
2104
        }
2105
        if ($error) {
2106
            $this->db->rollback();
2107
            return -1;
2108
        } else {
2109
            $this->shipping_method_id = ($shipping_method_id == 'NULL') ? null : $shipping_method_id;
2110
            $this->db->commit();
2111
            return 1;
2112
        }
2113
    }
2114
2115
    /**
2116
     *  Change the warehouse
2117
     *
2118
     * @param int $warehouse_id Id of warehouse
2119
     * @return     int              1 if OK, 0 if KO
2120
     */
2121
    function setWarehouse($warehouse_id)
2122
    {
2123
        if (!$this->table_element) {
2124
            dol_syslog(get_class($this) . "::setWarehouse was called on objet with property table_element not defined", LOG_ERR);
2125
            return -1;
2126
        }
2127
        if ($warehouse_id < 0) $warehouse_id = 'NULL';
2128
        dol_syslog(get_class($this) . '::setWarehouse(' . $warehouse_id . ')');
2129
2130
        $sql = "UPDATE " . MAIN_DB_PREFIX . $this->table_element;
2131
        $sql .= " SET fk_warehouse = " . $warehouse_id;
2132
        $sql .= " WHERE rowid=" . $this->id;
2133
2134
        if ($this->db->query($sql)) {
2135
            $this->warehouse_id = ($warehouse_id == 'NULL') ? null : $warehouse_id;
2136
            return 1;
2137
        } else {
2138
            dol_syslog(get_class($this) . '::setWarehouse Error ', LOG_DEBUG);
2139
            $this->error = $this->db->error();
2140
            return 0;
2141
        }
2142
    }
2143
2144
    /**
2145
     *        Set last model used by doc generator
2146
     *
2147
     * @param User $user User object that make change
2148
     * @param string $modelpdf Modele name
2149
     * @return        int                    <0 if KO, >0 if OK
2150
     */
2151
    function setDocModel($user, $modelpdf)
2152
    {
2153
        if (!$this->table_element) {
2154
            dol_syslog(get_class($this) . "::setDocModel was called on objet with property table_element not defined", LOG_ERR);
2155
            return -1;
2156
        }
2157
2158
        $newmodelpdf = dol_trunc($modelpdf, 255);
2159
2160
        $sql = "UPDATE " . MAIN_DB_PREFIX . $this->table_element;
2161
        $sql .= " SET model_pdf = '" . $this->db->escape($newmodelpdf) . "'";
2162
        $sql .= " WHERE rowid = " . $this->id;
2163
        // if ($this->element == 'facture') $sql.= " AND fk_statut < 2";
2164
        // if ($this->element == 'propal')  $sql.= " AND fk_statut = 0";
2165
2166
        dol_syslog(get_class($this) . "::setDocModel", LOG_DEBUG);
2167
        $resql = $this->db->query($sql);
2168
        if ($resql) {
2169
            $this->modelpdf = $modelpdf;
2170
            return 1;
2171
        } else {
2172
            dol_print_error($this->db);
2173
            return 0;
2174
        }
2175
    }
2176
2177
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2178
2179
    /**
2180
     *  Change the bank account
2181
     *
2182
     * @param int $fk_account Id of bank account
2183
     * @param bool $notrigger false=launch triggers after, true=disable triggers
2184
     * @param User $userused Object user
2185
     * @return        int                1 if OK, 0 if KO
2186
     */
2187
    function setBankAccount($fk_account, $notrigger = false, $userused = null)
2188
    {
2189
        global $user;
2190
2191
        if (empty($userused)) $userused = $user;
2192
2193
        $error = 0;
2194
2195
        if (!$this->table_element) {
2196
            dol_syslog(get_class($this) . "::setBankAccount was called on objet with property table_element not defined", LOG_ERR);
2197
            return -1;
2198
        }
2199
        $this->db->begin();
2200
2201
        if ($fk_account < 0) $fk_account = 'NULL';
2202
        dol_syslog(get_class($this) . '::setBankAccount(' . $fk_account . ')');
2203
2204
        $sql = "UPDATE " . MAIN_DB_PREFIX . $this->table_element;
2205
        $sql .= " SET fk_account = " . $fk_account;
2206
        $sql .= " WHERE rowid=" . $this->id;
2207
2208
        $resql = $this->db->query($sql);
2209
        if (!$resql) {
2210
            dol_syslog(get_class($this) . '::setBankAccount Error ' . $sql . ' - ' . $this->db->error());
2211
            $this->error = $this->db->lasterror();
2212
            $error++;
2213
        } else {
2214
            if (!$notrigger) {
2215
                // Call trigger
2216
                $this->context = array('bankaccountupdate' => 1);
2217
                $result = $this->call_trigger(strtoupper(get_class($this)) . '_MODIFY', $userused);
2218
                if ($result < 0) $error++;
2219
                // End call trigger
2220
            }
2221
        }
2222
        if ($error) {
2223
            $this->db->rollback();
2224
            return -1;
2225
        } else {
2226
            $this->fk_account = ($fk_account == 'NULL') ? null : $fk_account;
2227
            $this->db->commit();
2228
            return 1;
2229
        }
2230
    }
2231
2232
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2233
2234
    /**
2235
     *    Update a line to have a lower rank
2236
     *
2237
     * @param int $rowid Id of line
2238
     * @param boolean $fk_parent_line Table with fk_parent_line field or not
2239
     * @return    void
2240
     */
2241
    function line_up($rowid, $fk_parent_line = true)
2242
    {
2243
        // phpcs:enable
2244
        $this->line_order(false, 'ASC', $fk_parent_line);
2245
2246
        // Get rang of line
2247
        $rang = $this->getRangOfLine($rowid);
2248
2249
        // Update position of line
2250
        $this->updateLineUp($rowid, $rang);
2251
    }
2252
2253
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2254
2255
    /**
2256
     *  Save a new position (field rang) for details lines.
2257
     *  You can choose to set position for lines with already a position or lines without any position defined.
2258
     *
2259
     * @param boolean $renum True to renum all already ordered lines, false to renum only not already ordered lines.
2260
     * @param string $rowidorder ASC or DESC
2261
     * @param boolean $fk_parent_line Table with fk_parent_line field or not
2262
     * @return        int                            <0 if KO, >0 if OK
2263
     */
2264
    function line_order($renum = false, $rowidorder = 'ASC', $fk_parent_line = true)
2265
    {
2266
        // phpcs:enable
2267
        if (!$this->table_element_line) {
2268
            dol_syslog(get_class($this) . "::line_order was called on objet with property table_element_line not defined", LOG_ERR);
2269
            return -1;
2270
        }
2271
        if (!$this->fk_element) {
2272
            dol_syslog(get_class($this) . "::line_order was called on objet with property fk_element not defined", LOG_ERR);
2273
            return -1;
2274
        }
2275
2276
        // Count number of lines to reorder (according to choice $renum)
2277
        $nl = 0;
2278
        $sql = 'SELECT count(rowid) FROM ' . MAIN_DB_PREFIX . $this->table_element_line;
2279
        $sql .= ' WHERE ' . $this->fk_element . '=' . $this->id;
2280
        if (!$renum) $sql .= ' AND rang = 0';
2281
        if ($renum) $sql .= ' AND rang <> 0';
2282
2283
        dol_syslog(get_class($this) . "::line_order", LOG_DEBUG);
2284
        $resql = $this->db->query($sql);
2285
        if ($resql) {
2286
            $row = $this->db->fetch_row($resql);
2287
            $nl = $row[0];
2288
        } else dol_print_error($this->db);
2289
        if ($nl > 0) {
2290
            // The goal of this part is to reorder all lines, with all children lines sharing the same
2291
            // counter that parents.
2292
            $rows = array();
2293
2294
            // We first search all lines that are parent lines (for multilevel details lines)
2295
            $sql = 'SELECT rowid FROM ' . MAIN_DB_PREFIX . $this->table_element_line;
2296
            $sql .= ' WHERE ' . $this->fk_element . ' = ' . $this->id;
2297
            if ($fk_parent_line) $sql .= ' AND fk_parent_line IS NULL';
2298
            $sql .= ' ORDER BY rang ASC, rowid ' . $rowidorder;
2299
2300
            dol_syslog(get_class($this) . "::line_order search all parent lines", LOG_DEBUG);
2301
            $resql = $this->db->query($sql);
2302
            if ($resql) {
2303
                $i = 0;
2304
                $num = $this->db->num_rows($resql);
2305
                while ($i < $num) {
2306
                    $row = $this->db->fetch_row($resql);
2307
                    $rows[] = $row[0];    // Add parent line into array rows
2308
                    $childrens = $this->getChildrenOfLine($row[0]);
2309
                    if (!empty($childrens)) {
2310
                        foreach ($childrens as $child) {
2311
                            array_push($rows, $child);
2312
                        }
2313
                    }
2314
                    $i++;
2315
                }
2316
2317
                // Now we set a new number for each lines (parent and children with children included into parent tree)
2318
                if (!empty($rows)) {
2319
                    foreach ($rows as $key => $row) {
2320
                        $this->updateRangOfLine($row, ($key + 1));
2321
                    }
2322
                }
2323
            } else {
2324
                dol_print_error($this->db);
2325
            }
2326
        }
2327
        return 1;
2328
    }
2329
2330
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2331
2332
    /**
2333
     *    Get children of line
2334
     *
2335
     * @param int $id Id of parent line
2336
     * @return    array            Array with list of children lines id
2337
     */
2338
    function getChildrenOfLine($id)
2339
    {
2340
        $rows = array();
2341
2342
        $sql = 'SELECT rowid FROM ' . MAIN_DB_PREFIX . $this->table_element_line;
2343
        $sql .= ' WHERE ' . $this->fk_element . ' = ' . $this->id;
2344
        $sql .= ' AND fk_parent_line = ' . $id;
2345
        $sql .= ' ORDER BY rang ASC';
2346
2347
        dol_syslog(get_class($this) . "::getChildrenOfLine search children lines for line " . $id . "", LOG_DEBUG);
2348
        $resql = $this->db->query($sql);
2349
        if ($resql) {
2350
            $i = 0;
2351
            $num = $this->db->num_rows($resql);
2352
            while ($i < $num) {
2353
                $row = $this->db->fetch_row($resql);
2354
                $rows[$i] = $row[0];
2355
                $i++;
2356
            }
2357
        }
2358
2359
        return $rows;
2360
    }
2361
2362
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2363
2364
    /**
2365
     *    Update position of line (rang)
2366
     *
2367
     * @param int $rowid Id of line
2368
     * @param int $rang Position
2369
     * @return    void
2370
     */
2371
    function updateRangOfLine($rowid, $rang)
2372
    {
2373
        $fieldposition = 'rang';
2374
        if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction'))) $fieldposition = 'position';
2375
2376
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element_line . ' SET ' . $fieldposition . ' = ' . $rang;
2377
        $sql .= ' WHERE rowid = ' . $rowid;
2378
2379
        dol_syslog(get_class($this) . "::updateRangOfLine", LOG_DEBUG);
2380
        if (!$this->db->query($sql)) {
2381
            dol_print_error($this->db);
2382
        }
2383
    }
2384
2385
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2386
2387
    /**
2388
     *    Get position of line (rang)
2389
     *
2390
     * @param int $rowid Id of line
2391
     * @return        int                Value of rang in table of lines
2392
     */
2393
    function getRangOfLine($rowid)
2394
    {
2395
        $sql = 'SELECT rang FROM ' . MAIN_DB_PREFIX . $this->table_element_line;
2396
        $sql .= ' WHERE rowid =' . $rowid;
2397
2398
        dol_syslog(get_class($this) . "::getRangOfLine", LOG_DEBUG);
2399
        $resql = $this->db->query($sql);
2400
        if ($resql) {
2401
            $row = $this->db->fetch_row($resql);
2402
            return $row[0];
2403
        }
2404
    }
2405
2406
    /**
2407
     *    Update position of line up (rang)
2408
     *
2409
     * @param int $rowid Id of line
2410
     * @param int $rang Position
2411
     * @return    void
2412
     */
2413
    function updateLineUp($rowid, $rang)
2414
    {
2415
        if ($rang > 1) {
2416
            $fieldposition = 'rang';
2417
            if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction'))) $fieldposition = 'position';
2418
2419
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element_line . ' SET ' . $fieldposition . ' = ' . $rang;
2420
            $sql .= ' WHERE ' . $this->fk_element . ' = ' . $this->id;
2421
            $sql .= ' AND rang = ' . ($rang - 1);
2422
            if ($this->db->query($sql)) {
2423
                $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element_line . ' SET ' . $fieldposition . ' = ' . ($rang - 1);
2424
                $sql .= ' WHERE rowid = ' . $rowid;
2425
                if (!$this->db->query($sql)) {
2426
                    dol_print_error($this->db);
2427
                }
2428
            } else {
2429
                dol_print_error($this->db);
2430
            }
2431
        }
2432
    }
2433
2434
    /**
2435
     *    Update a line to have a higher rank
2436
     *
2437
     * @param int $rowid Id of line
2438
     * @param boolean $fk_parent_line Table with fk_parent_line field or not
2439
     * @return    void
2440
     */
2441
    function line_down($rowid, $fk_parent_line = true)
2442
    {
2443
        // phpcs:enable
2444
        $this->line_order(false, 'ASC', $fk_parent_line);
2445
2446
        // Get rang of line
2447
        $rang = $this->getRangOfLine($rowid);
2448
2449
        // Get max value for rang
2450
        $max = $this->line_max();
2451
2452
        // Update position of line
2453
        $this->updateLineDown($rowid, $rang, $max);
2454
    }
2455
2456
    /**
2457
     *    Get max value used for position of line (rang)
2458
     *
2459
     * @param int $fk_parent_line Parent line id
2460
     * @return     int                        Max value of rang in table of lines
2461
     */
2462
    function line_max($fk_parent_line = 0)
2463
    {
2464
        // phpcs:enable
2465
        // Search the last rang with fk_parent_line
2466
        if ($fk_parent_line) {
2467
            $sql = 'SELECT max(rang) FROM ' . MAIN_DB_PREFIX . $this->table_element_line;
2468
            $sql .= ' WHERE ' . $this->fk_element . ' = ' . $this->id;
2469
            $sql .= ' AND fk_parent_line = ' . $fk_parent_line;
2470
2471
            dol_syslog(get_class($this) . "::line_max", LOG_DEBUG);
2472
            $resql = $this->db->query($sql);
2473
            if ($resql) {
2474
                $row = $this->db->fetch_row($resql);
2475
                if (!empty($row[0])) {
2476
                    return $row[0];
2477
                } else {
2478
                    return $this->getRangOfLine($fk_parent_line);
2479
                }
2480
            }
2481
        } // If not, search the last rang of element
2482
        else {
2483
            $sql = 'SELECT max(rang) FROM ' . MAIN_DB_PREFIX . $this->table_element_line;
2484
            $sql .= ' WHERE ' . $this->fk_element . ' = ' . $this->id;
2485
2486
            dol_syslog(get_class($this) . "::line_max", LOG_DEBUG);
2487
            $resql = $this->db->query($sql);
2488
            if ($resql) {
2489
                $row = $this->db->fetch_row($resql);
2490
                return $row[0];
2491
            }
2492
        }
2493
    }
2494
2495
    /**
2496
     *    Update position of line down (rang)
2497
     *
2498
     * @param int $rowid Id of line
2499
     * @param int $rang Position
2500
     * @param int $max Max
2501
     * @return    void
2502
     */
2503
    function updateLineDown($rowid, $rang, $max)
2504
    {
2505
        if ($rang < $max) {
2506
            $fieldposition = 'rang';
2507
            if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction'))) $fieldposition = 'position';
2508
2509
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element_line . ' SET ' . $fieldposition . ' = ' . $rang;
2510
            $sql .= ' WHERE ' . $this->fk_element . ' = ' . $this->id;
2511
            $sql .= ' AND rang = ' . ($rang + 1);
2512
            if ($this->db->query($sql)) {
2513
                $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element_line . ' SET ' . $fieldposition . ' = ' . ($rang + 1);
2514
                $sql .= ' WHERE rowid = ' . $rowid;
2515
                if (!$this->db->query($sql)) {
2516
                    dol_print_error($this->db);
2517
                }
2518
            } else {
2519
                dol_print_error($this->db);
2520
            }
2521
        }
2522
    }
2523
2524
    /**
2525
     *    Update position of line with ajax (rang)
2526
     *
2527
     * @param array $rows Array of rows
2528
     * @return    void
2529
     */
2530
    function line_ajaxorder($rows)
2531
    {
2532
        // phpcs:enable
2533
        $num = count($rows);
2534
        for ($i = 0; $i < $num; $i++) {
2535
            $this->updateRangOfLine($rows[$i], ($i + 1));
2536
        }
2537
    }
2538
2539
    /**
2540
     *    Get rowid of the line relative to its position
2541
     *
2542
     * @param int $rang Rang value
2543
     * @return     int                Rowid of the line
2544
     */
2545
    function getIdOfLine($rang)
2546
    {
2547
        $sql = 'SELECT rowid FROM ' . MAIN_DB_PREFIX . $this->table_element_line;
2548
        $sql .= ' WHERE ' . $this->fk_element . ' = ' . $this->id;
2549
        $sql .= ' AND rang = ' . $rang;
2550
        $resql = $this->db->query($sql);
2551
        if ($resql) {
2552
            $row = $this->db->fetch_row($resql);
2553
            return $row[0];
2554
        }
2555
    }
2556
2557
    /**
2558
     *  Update external ref of element
2559
     *
2560
     * @param string $ref_ext Update field ref_ext
2561
     * @return     int                    <0 if KO, >0 if OK
2562
     */
2563
    function update_ref_ext($ref_ext)
2564
    {
2565
        // phpcs:enable
2566
        if (!$this->table_element) {
2567
            dol_syslog(get_class($this) . "::update_ref_ext was called on objet with property table_element not defined", LOG_ERR);
2568
            return -1;
2569
        }
2570
2571
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element;
2572
        $sql .= " SET ref_ext = '" . $this->db->escape($ref_ext) . "'";
2573
        $sql .= " WHERE " . (isset($this->table_rowid) ? $this->table_rowid : 'rowid') . " = " . $this->id;
2574
2575
        dol_syslog(get_class($this) . "::update_ref_ext", LOG_DEBUG);
2576
        if ($this->db->query($sql)) {
2577
            $this->ref_ext = $ref_ext;
2578
            return 1;
2579
        } else {
2580
            $this->error = $this->db->error();
2581
            return -1;
2582
        }
2583
    }
2584
2585
    /**
2586
     *    Update public note (kept for backward compatibility)
2587
     *
2588
     * @param string $note New value for note
2589
     * @return     int                    <0 if KO, >0 if OK
2590
     * @deprecated
2591
     * @see update_note()
2592
     */
2593
    function update_note_public($note)
2594
    {
2595
        // phpcs:enable
2596
        return $this->update_note($note, '_public');
2597
    }
2598
2599
    /**
2600
     *  Update note of element
2601
     *
2602
     * @param string $note New value for note
2603
     * @param string $suffix '', '_public' or '_private'
2604
     * @return     int                    <0 if KO, >0 if OK
2605
     */
2606
    function update_note($note, $suffix = '')
2607
    {
2608
        // phpcs:enable
2609
        global $user;
2610
2611
        if (!$this->table_element) {
2612
            $this->error = 'update_note was called on objet with property table_element not defined';
2613
            dol_syslog(get_class($this) . "::update_note was called on objet with property table_element not defined", LOG_ERR);
2614
            return -1;
2615
        }
2616
        if (!in_array($suffix, array('', '_public', '_private'))) {
2617
            $this->error = 'update_note Parameter suffix must be empty, \'_private\' or \'_public\'';
2618
            dol_syslog(get_class($this) . "::update_note Parameter suffix must be empty, '_private' or '_public'", LOG_ERR);
2619
            return -2;
2620
        }
2621
        // Special cas
2622
        //var_dump($this->table_element);exit;
2623
        if ($this->table_element == 'product') $suffix = '';
2624
2625
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element;
2626
        $sql .= " SET note" . $suffix . " = " . (!empty($note) ? ("'" . $this->db->escape($note) . "'") : "NULL");
2627
        $sql .= " ," . (in_array($this->table_element, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment')) ? "fk_user_mod" : "fk_user_modif") . " = " . $user->id;
2628
        $sql .= " WHERE rowid =" . $this->id;
2629
2630
        dol_syslog(get_class($this) . "::update_note", LOG_DEBUG);
2631
        if ($this->db->query($sql)) {
2632
            if ($suffix == '_public') $this->note_public = $note;
2633
            else if ($suffix == '_private') $this->note_private = $note;
2634
            else {
2635
                $this->note = $note;      // deprecated
2636
                $this->note_private = $note;
2637
            }
2638
            return 1;
2639
        } else {
2640
            $this->error = $this->db->lasterror();
2641
            return -1;
2642
        }
2643
    }
2644
2645
    /**
2646
     *    Update total_ht, total_ttc, total_vat, total_localtax1, total_localtax2 for an object (sum of lines).
2647
     *  Must be called at end of methods addline or updateline.
2648
     *
2649
     * @param int $exclspec >0 = Exclude special product (product_type=9)
2650
     * @param string $roundingadjust 'none'=Do nothing, 'auto'=Use default method (MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND if defined, or '0'), '0'=Force mode total of rounding, '1'=Force mode rounding of total
2651
     * @param int $nodatabaseupdate 1=Do not update database. Update only properties of object.
2652
     * @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.
2653
     * @return    int                            <0 if KO, >0 if OK
2654
     */
2655
    function update_price($exclspec = 0, $roundingadjust = 'none', $nodatabaseupdate = 0, $seller = null)
2656
    {
2657
        // phpcs:enable
2658
        global $conf, $hookmanager, $action;
2659
2660
        // Some external module want no update price after a trigger because they have another method to calculate the total (ex: with an extrafield)
2661
        $MODULE = "";
2662
        if ($this->element == 'propal')
2663
            $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_PROPOSAL";
2664
        elseif ($this->element == 'order')
2665
            $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_ORDER";
2666
        elseif ($this->element == 'facture')
2667
            $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_INVOICE";
2668
        elseif ($this->element == 'facture_fourn')
2669
            $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_INVOICE";
2670
        elseif ($this->element == 'order_supplier')
2671
            $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_ORDER";
2672
        elseif ($this->element == 'supplier_proposal')
2673
            $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_PROPOSAL";
2674
2675
        if (!empty($MODULE)) {
2676
            if (!empty($conf->global->$MODULE)) {
2677
                $modsactivated = explode(',', $conf->global->$MODULE);
2678
                foreach ($modsactivated as $mod) {
2679
                    if ($conf->$mod->enabled)
2680
                        return 1; // update was disabled by specific setup
2681
                }
2682
            }
2683
        }
2684
2685
        include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
2686
2687
        if ($roundingadjust == '-1') $roundingadjust = 'auto';    // For backward compatibility
2688
2689
        $forcedroundingmode = $roundingadjust;
2690
        if ($forcedroundingmode == 'auto' && isset($conf->global->MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND)) $forcedroundingmode = $conf->global->MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND;
2691
        elseif ($forcedroundingmode == 'auto') $forcedroundingmode = '0';
2692
2693
        $error = 0;
2694
2695
        $multicurrency_tx = !empty($this->multicurrency_tx) ? $this->multicurrency_tx : 1;
2696
2697
        // Define constants to find lines to sum
2698
        $fieldtva = 'total_tva';
2699
        $fieldlocaltax1 = 'total_localtax1';
2700
        $fieldlocaltax2 = 'total_localtax2';
2701
        $fieldup = 'subprice';
2702
        if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
2703
            $fieldtva = 'tva';
2704
            $fieldup = 'pu_ht';
2705
        }
2706
        if ($this->element == 'expensereport') {
2707
            $fieldup = 'value_unit';
2708
        }
2709
2710
        $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,';
2711
        $sql .= ' tva_tx as vatrate, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, info_bits, product_type';
2712
        if ($this->table_element_line == 'facturedet') $sql .= ', situation_percent';
2713
        $sql .= ', multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
2714
        $sql .= ' FROM ' . MAIN_DB_PREFIX . $this->table_element_line;
2715
        $sql .= ' WHERE ' . $this->fk_element . ' = ' . $this->id;
2716
        if ($exclspec) {
2717
            $product_field = 'product_type';
2718
            if ($this->table_element_line == 'contratdet') $product_field = '';    // contratdet table has no product_type field
2719
            if ($product_field) $sql .= ' AND ' . $product_field . ' <> 9';
2720
        }
2721
        $sql .= ' ORDER by rowid';    // We want to be sure to always use same order of line to not change lines differently when option MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND is used
2722
2723
        dol_syslog(get_class($this) . "::update_price", LOG_DEBUG);
2724
        $resql = $this->db->query($sql);
2725
        if ($resql) {
2726
            $this->total_ht = 0;
2727
            $this->total_tva = 0;
2728
            $this->total_localtax1 = 0;
2729
            $this->total_localtax2 = 0;
2730
            $this->total_ttc = 0;
2731
            $total_ht_by_vats = array();
2732
            $total_tva_by_vats = array();
2733
            $total_ttc_by_vats = array();
2734
            $this->multicurrency_total_ht = 0;
2735
            $this->multicurrency_total_tva = 0;
2736
            $this->multicurrency_total_ttc = 0;
2737
2738
            $num = $this->db->num_rows($resql);
2739
            $i = 0;
2740
            while ($i < $num) {
2741
                $obj = $this->db->fetch_object($resql);
2742
2743
                // Note: There is no check on detail line and no check on total, if $forcedroundingmode = 'none'
2744
                $parameters = array('fk_element' => $obj->rowid);
2745
                $reshook = $hookmanager->executeHooks('changeRoundingMode', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2746
2747
                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'
2748
                {
2749
                    $localtax_array = array($obj->localtax1_type, $obj->localtax1_tx, $obj->localtax2_type, $obj->localtax2_tx);
2750
                    $tmpcal = calcul_price_total($obj->qty, $obj->up, $obj->remise_percent, $obj->vatrate, $obj->localtax1_tx, $obj->localtax2_tx, 0, 'HT', $obj->info_bits, $obj->product_type, $seller, $localtax_array, (isset($obj->situation_percent) ? $obj->situation_percent : 100), $multicurrency_tx);
2751
                    $diff = price2num($tmpcal[1] - $obj->total_tva, 'MT', 1);
2752
                    if ($diff) {
2753
                        $sqlfix = "UPDATE " . MAIN_DB_PREFIX . $this->table_element_line . " SET " . $fieldtva . " = " . $tmpcal[1] . ", total_ttc = " . $tmpcal[2] . " WHERE rowid = " . $obj->rowid;
2754
                        dol_syslog('We found unconsistent data into detailed line (difference of ' . $diff . ') 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);
2755
                        $resqlfix = $this->db->query($sqlfix);
2756
                        if (!$resqlfix) dol_print_error($this->db, 'Failed to update line');
2757
                        $obj->total_tva = $tmpcal[1];
2758
                        $obj->total_ttc = $tmpcal[2];
2759
                        //
2760
                    }
2761
                }
2762
2763
                $this->total_ht += $obj->total_ht;        // The field visible at end of line detail
2764
                $this->total_tva += $obj->total_tva;
2765
                $this->total_localtax1 += $obj->total_localtax1;
2766
                $this->total_localtax2 += $obj->total_localtax2;
2767
                $this->total_ttc += $obj->total_ttc;
2768
                $this->multicurrency_total_ht += $obj->multicurrency_total_ht;        // The field visible at end of line detail
2769
                $this->multicurrency_total_tva += $obj->multicurrency_total_tva;
2770
                $this->multicurrency_total_ttc += $obj->multicurrency_total_ttc;
2771
2772
                if (!isset($total_ht_by_vats[$obj->vatrate])) $total_ht_by_vats[$obj->vatrate] = 0;
2773
                if (!isset($total_tva_by_vats[$obj->vatrate])) $total_tva_by_vats[$obj->vatrate] = 0;
2774
                if (!isset($total_ttc_by_vats[$obj->vatrate])) $total_ttc_by_vats[$obj->vatrate] = 0;
2775
                $total_ht_by_vats[$obj->vatrate] += $obj->total_ht;
2776
                $total_tva_by_vats[$obj->vatrate] += $obj->total_tva;
2777
                $total_ttc_by_vats[$obj->vatrate] += $obj->total_ttc;
2778
2779
                if ($forcedroundingmode == '1')    // Check if we need adjustement onto line for vat. TODO This works on the company currency but not on multicurrency
2780
                {
2781
                    $tmpvat = price2num($total_ht_by_vats[$obj->vatrate] * $obj->vatrate / 100, 'MT', 1);
2782
                    $diff = price2num($total_tva_by_vats[$obj->vatrate] - $tmpvat, 'MT', 1);
2783
                    //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";
2784
                    if ($diff) {
2785
                        if (abs($diff) > 0.1) {
2786
                            dol_syslog('A rounding difference was detected into TOTAL but is too high to be corrected', LOG_WARNING);
2787
                            exit;
2788
                        }
2789
                        $sqlfix = "UPDATE " . MAIN_DB_PREFIX . $this->table_element_line . " SET " . $fieldtva . " = " . ($obj->total_tva - $diff) . ", total_ttc = " . ($obj->total_ttc - $diff) . " WHERE rowid = " . $obj->rowid;
2790
                        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);
2791
                        $resqlfix = $this->db->query($sqlfix);
2792
                        if (!$resqlfix) dol_print_error($this->db, 'Failed to update line');
2793
                        $this->total_tva -= $diff;
2794
                        $this->total_ttc -= $diff;
2795
                        $total_tva_by_vats[$obj->vatrate] -= $diff;
2796
                        $total_ttc_by_vats[$obj->vatrate] -= $diff;
2797
                    }
2798
                }
2799
2800
                $i++;
2801
            }
2802
2803
            // Add revenue stamp to total
2804
            $this->total_ttc += isset($this->revenuestamp) ? $this->revenuestamp : 0;
2805
            $this->multicurrency_total_ttc += isset($this->revenuestamp) ? ($this->revenuestamp * $multicurrency_tx) : 0;
2806
2807
            // Situations totals
2808
            if ($this->situation_cycle_ref && $this->situation_counter > 1 && method_exists($this, 'get_prev_sits') && $this->type != $this::TYPE_CREDIT_NOTE) {
2809
                $prev_sits = $this->get_prev_sits();
2810
2811
                foreach ($prev_sits as $sit) {                // $sit is an object Facture loaded with a fetch.
2812
                    $this->total_ht -= $sit->total_ht;
2813
                    $this->total_tva -= $sit->total_tva;
2814
                    $this->total_localtax1 -= $sit->total_localtax1;
2815
                    $this->total_localtax2 -= $sit->total_localtax2;
2816
                    $this->total_ttc -= $sit->total_ttc;
2817
                    $this->multicurrency_total_ht -= $sit->multicurrency_total_ht;
2818
                    $this->multicurrency_total_tva -= $sit->multicurrency_total_tva;
2819
                    $this->multicurrency_total_ttc -= $sit->multicurrency_total_ttc;
2820
                }
2821
            }
2822
2823
            $this->db->free($resql);
2824
2825
            // Now update global field total_ht, total_ttc and tva
2826
            $fieldht = 'total_ht';
2827
            $fieldtva = 'tva';
2828
            $fieldlocaltax1 = 'localtax1';
2829
            $fieldlocaltax2 = 'localtax2';
2830
            $fieldttc = 'total_ttc';
2831
            // Specific code for backward compatibility with old field names
2832
            if ($this->element == 'facture' || $this->element == 'facturerec') $fieldht = 'total';
2833
            if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') $fieldtva = 'total_tva';
2834
            if ($this->element == 'propal') $fieldttc = 'total';
2835
            if ($this->element == 'expensereport') $fieldtva = 'total_tva';
2836
            if ($this->element == 'supplier_proposal') $fieldttc = 'total';
2837
2838
            if (empty($nodatabaseupdate)) {
2839
                $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element . ' SET';
2840
                $sql .= " " . $fieldht . "='" . price2num($this->total_ht) . "',";
2841
                $sql .= " " . $fieldtva . "='" . price2num($this->total_tva) . "',";
2842
                $sql .= " " . $fieldlocaltax1 . "='" . price2num($this->total_localtax1) . "',";
2843
                $sql .= " " . $fieldlocaltax2 . "='" . price2num($this->total_localtax2) . "',";
2844
                $sql .= " " . $fieldttc . "='" . price2num($this->total_ttc) . "'";
2845
                $sql .= ", multicurrency_total_ht='" . price2num($this->multicurrency_total_ht, 'MT', 1) . "'";
2846
                $sql .= ", multicurrency_total_tva='" . price2num($this->multicurrency_total_tva, 'MT', 1) . "'";
2847
                $sql .= ", multicurrency_total_ttc='" . price2num($this->multicurrency_total_ttc, 'MT', 1) . "'";
2848
                $sql .= ' WHERE rowid = ' . $this->id;
2849
2850
2851
                dol_syslog(get_class($this) . "::update_price", LOG_DEBUG);
2852
                $resql = $this->db->query($sql);
2853
                if (!$resql) {
2854
                    $error++;
2855
                    $this->error = $this->db->lasterror();
2856
                    $this->errors[] = $this->db->lasterror();
2857
                }
2858
            }
2859
2860
            if (!$error) {
2861
                return 1;
2862
            } else {
2863
                return -1;
2864
            }
2865
        } else {
2866
            dol_print_error($this->db, 'Bad request in update_price');
2867
            return -1;
2868
        }
2869
    }
2870
2871
    /**
2872
     *    Add objects linked in llx_element_element.
2873
     *
2874
     * @param string $origin Linked element type
2875
     * @param int $origin_id Linked element id
2876
     * @return        int                    <=0 if KO, >0 if OK
2877
     * @see        fetchObjectLinked, updateObjectLinked, deleteObjectLinked
2878
     */
2879
    function add_object_linked($origin = null, $origin_id = null)
2880
    {
2881
        // phpcs:enable
2882
        $origin = (!empty($origin) ? $origin : $this->origin);
2883
        $origin_id = (!empty($origin_id) ? $origin_id : $this->origin_id);
2884
2885
        // Special case
2886
        if ($origin == 'order') $origin = 'commande';
2887
        if ($origin == 'invoice') $origin = 'facture';
2888
        if ($origin == 'invoice_template') $origin = 'facturerec';
2889
        if ($origin == 'supplierorder') $origin = 'order_supplier';
2890
        $this->db->begin();
2891
2892
        $sql = "INSERT INTO " . MAIN_DB_PREFIX . "element_element (";
2893
        $sql .= "fk_source";
2894
        $sql .= ", sourcetype";
2895
        $sql .= ", fk_target";
2896
        $sql .= ", targettype";
2897
        $sql .= ") VALUES (";
2898
        $sql .= $origin_id;
2899
        $sql .= ", '" . $this->db->escape($origin) . "'";
2900
        $sql .= ", " . $this->id;
2901
        $sql .= ", '" . $this->db->escape($this->element) . "'";
2902
        $sql .= ")";
2903
2904
        dol_syslog(get_class($this) . "::add_object_linked", LOG_DEBUG);
2905
        if ($this->db->query($sql)) {
2906
            $this->db->commit();
2907
            return 1;
2908
        } else {
2909
            $this->error = $this->db->lasterror();
2910
            $this->db->rollback();
2911
            return 0;
2912
        }
2913
    }
2914
2915
2916
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2917
2918
    /**
2919
     *    Fetch array of objects linked to current object (object of enabled modules only). Links are loaded into
2920
     *        this->linkedObjectsIds array and
2921
     *        this->linkedObjects array if $loadalsoobjects = 1
2922
     *  Possible usage for parameters:
2923
     *  - all parameters empty -> we look all link to current object (current object can be source or target)
2924
     *  - source id+type -> will get target list linked to source
2925
     *  - target id+type -> will get source list linked to target
2926
     *  - source id+type + target type -> will get target list of the type
2927
     *  - target id+type + target source -> will get source list of the type
2928
     *
2929
     * @param int $sourceid Object source id (if not defined, id of object)
2930
     * @param string $sourcetype Object source type (if not defined, element name of object)
2931
     * @param int $targetid Object target id (if not defined, id of object)
2932
     * @param string $targettype Object target type (if not defined, elemennt name of object)
2933
     * @param string $clause 'OR' or 'AND' clause used when both source id and target id are provided
2934
     * @param int $alsosametype 0=Return only links to object that differs from source type. 1=Include also link to objects of same type.
2935
     * @param string $orderby SQL 'ORDER BY' clause
2936
     * @param int $loadalsoobjects Load also array this->linkedObjects (Use 0 to increase performances)
2937
     * @return int                            <0 if KO, >0 if OK
2938
     * @see    add_object_linked, updateObjectLinked, deleteObjectLinked
2939
     */
2940
    function fetchObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $clause = 'OR', $alsosametype = 1, $orderby = 'sourcetype', $loadalsoobjects = 1)
2941
    {
2942
        global $conf;
2943
2944
        $this->linkedObjectsIds = array();
2945
        $this->linkedObjects = array();
2946
2947
        $justsource = false;
2948
        $justtarget = false;
2949
        $withtargettype = false;
2950
        $withsourcetype = false;
2951
2952
        if (!empty($sourceid) && !empty($sourcetype) && empty($targetid)) {
2953
            $justsource = true;  // the source (id and type) is a search criteria
2954
            if (!empty($targettype)) $withtargettype = true;
2955
        }
2956
        if (!empty($targetid) && !empty($targettype) && empty($sourceid)) {
2957
            $justtarget = true;  // the target (id and type) is a search criteria
2958
            if (!empty($sourcetype)) $withsourcetype = true;
2959
        }
2960
2961
        $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
2962
        $targetid = (!empty($targetid) ? $targetid : $this->id);
2963
        $sourcetype = (!empty($sourcetype) ? $sourcetype : $this->element);
2964
        $targettype = (!empty($targettype) ? $targettype : $this->element);
2965
2966
        /*if (empty($sourceid) && empty($targetid))
2967
		 {
2968
		 dol_syslog('Bad usage of function. No source nor target id defined (nor as parameter nor as object id)', LOG_ERR);
2969
		 return -1;
2970
		 }*/
2971
2972
        // Links between objects are stored in table element_element
2973
        $sql = 'SELECT rowid, fk_source, sourcetype, fk_target, targettype';
2974
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'element_element';
2975
        $sql .= " WHERE ";
2976
        if ($justsource || $justtarget) {
2977
            if ($justsource) {
2978
                $sql .= "fk_source = " . $sourceid . " AND sourcetype = '" . $sourcetype . "'";
2979
                if ($withtargettype) $sql .= " AND targettype = '" . $targettype . "'";
2980
            } else if ($justtarget) {
2981
                $sql .= "fk_target = " . $targetid . " AND targettype = '" . $targettype . "'";
2982
                if ($withsourcetype) $sql .= " AND sourcetype = '" . $sourcetype . "'";
2983
            }
2984
        } else {
2985
            $sql .= "(fk_source = " . $sourceid . " AND sourcetype = '" . $sourcetype . "')";
2986
            $sql .= " " . $clause . " (fk_target = " . $targetid . " AND targettype = '" . $targettype . "')";
2987
        }
2988
        $sql .= ' ORDER BY ' . $orderby;
2989
2990
        dol_syslog(get_class($this) . "::fetchObjectLink", LOG_DEBUG);
2991
        $resql = $this->db->query($sql);
2992
        if ($resql) {
2993
            $num = $this->db->num_rows($resql);
2994
            $i = 0;
2995
            while ($i < $num) {
2996
                $obj = $this->db->fetch_object($resql);
2997
                if ($justsource || $justtarget) {
2998
                    if ($justsource) {
2999
                        $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
3000
                    } else if ($justtarget) {
3001
                        $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
3002
                    }
3003
                } else {
3004
                    if ($obj->fk_source == $sourceid && $obj->sourcetype == $sourcetype) {
3005
                        $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
3006
                    }
3007
                    if ($obj->fk_target == $targetid && $obj->targettype == $targettype) {
3008
                        $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
3009
                    }
3010
                }
3011
                $i++;
3012
            }
3013
3014
            if (!empty($this->linkedObjectsIds)) {
3015
                $tmparray = $this->linkedObjectsIds;
3016
                foreach ($tmparray as $objecttype => $objectids)       // $objecttype is a module name ('facture', 'mymodule', ...) or a module name with a suffix ('project_task', 'mymodule_myobj', ...)
3017
                {
3018
                    // Parse element/subelement (ex: project_task, cabinetmed_consultation, ...)
3019
                    $module = $element = $subelement = $objecttype;
3020
                    if ($objecttype != 'supplier_proposal' && $objecttype != 'order_supplier' && $objecttype != 'invoice_supplier'
3021
                        && preg_match('/^([^_]+)_([^_]+)/i', $objecttype, $regs)) {
3022
                        $module = $element = $regs[1];
3023
                        $subelement = $regs[2];
3024
                    }
3025
3026
                    $classpath = $element . '/class';
3027
                    // To work with non standard classpath or module name
3028
                    if ($objecttype == 'facture') {
3029
                        $classpath = 'compta/facture/class';
3030
                    } else if ($objecttype == 'facturerec') {
3031
                        $classpath = 'compta/facture/class';
3032
                        $module = 'facture';
3033
                    } else if ($objecttype == 'propal') {
3034
                        $classpath = 'comm/propal/class';
3035
                    } else if ($objecttype == 'supplier_proposal') {
3036
                        $classpath = 'supplier_proposal/class';
3037
                    } else if ($objecttype == 'shipping') {
3038
                        $classpath = 'expedition/class';
3039
                        $subelement = 'expedition';
3040
                        $module = 'expedition_bon';
3041
                    } else if ($objecttype == 'delivery') {
3042
                        $classpath = 'livraison/class';
3043
                        $subelement = 'livraison';
3044
                        $module = 'livraison_bon';
3045
                    } else if ($objecttype == 'invoice_supplier' || $objecttype == 'order_supplier') {
3046
                        $classpath = 'fourn/class';
3047
                        $module = 'fournisseur';
3048
                    } else if ($objecttype == 'fichinter') {
3049
                        $classpath = 'fichinter/class';
3050
                        $subelement = 'fichinter';
3051
                        $module = 'ficheinter';
3052
                    } else if ($objecttype == 'subscription') {
3053
                        $classpath = 'adherents/class';
3054
                        $module = 'adherent';
3055
                    }
3056
3057
                    // Set classfile
3058
                    $classfile = strtolower($subelement);
3059
                    $classname = ucfirst($subelement);
3060
3061
                    if ($objecttype == 'order') {
3062
                        $classfile = 'commande';
3063
                        $classname = 'Commande';
3064
                    } else if ($objecttype == 'invoice_supplier') {
3065
                        $classfile = 'fournisseur.facture';
3066
                        $classname = 'FactureFournisseur';
3067
                    } else if ($objecttype == 'order_supplier') {
3068
                        $classfile = 'fournisseur.commande';
3069
                        $classname = 'CommandeFournisseur';
3070
                    } else if ($objecttype == 'supplier_proposal') {
3071
                        $classfile = 'supplier_proposal';
3072
                        $classname = 'SupplierProposal';
3073
                    } else if ($objecttype == 'facturerec') {
3074
                        $classfile = 'facture-rec';
3075
                        $classname = 'FactureRec';
3076
                    } else if ($objecttype == 'subscription') {
3077
                        $classfile = 'subscription';
3078
                        $classname = 'Subscription';
3079
                    }
3080
3081
                    // Here $module, $classfile and $classname are set
3082
                    if ($conf->$module->enabled && (($element != $this->element) || $alsosametype)) {
3083
                        if ($loadalsoobjects) {
3084
                            dol_include_once('/' . $classpath . '/' . $classfile . '.class.php');
3085
                            //print '/'.$classpath.'/'.$classfile.'.class.php '.class_exists($classname);
3086
                            if (class_exists($classname)) {
3087
                                foreach ($objectids as $i => $objectid)    // $i is rowid into llx_element_element
3088
                                {
3089
                                    $object = new $classname($this->db);
3090
                                    $ret = $object->fetch($objectid);
3091
                                    if ($ret >= 0) {
3092
                                        $this->linkedObjects[$objecttype][$i] = $object;
3093
                                    }
3094
                                }
3095
                            }
3096
                        }
3097
                    } else {
3098
                        unset($this->linkedObjectsIds[$objecttype]);
3099
                    }
3100
                }
3101
            }
3102
            return 1;
3103
        } else {
3104
            dol_print_error($this->db);
3105
            return -1;
3106
        }
3107
    }
3108
3109
    /**
3110
     *    Update object linked of a current object
3111
     *
3112
     * @param int $sourceid Object source id
3113
     * @param string $sourcetype Object source type
3114
     * @param int $targetid Object target id
3115
     * @param string $targettype Object target type
3116
     * @return                            int    >0 if OK, <0 if KO
3117
     * @see    add_object_linked, fetObjectLinked, deleteObjectLinked
3118
     */
3119
    function updateObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '')
3120
    {
3121
        $updatesource = false;
3122
        $updatetarget = false;
3123
3124
        if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) $updatesource = true;
3125
        else if (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) $updatetarget = true;
3126
3127
        $sql = "UPDATE " . MAIN_DB_PREFIX . "element_element SET ";
3128
        if ($updatesource) {
3129
            $sql .= "fk_source = " . $sourceid;
3130
            $sql .= ", sourcetype = '" . $this->db->escape($sourcetype) . "'";
3131
            $sql .= " WHERE fk_target = " . $this->id;
3132
            $sql .= " AND targettype = '" . $this->db->escape($this->element) . "'";
3133
        } else if ($updatetarget) {
3134
            $sql .= "fk_target = " . $targetid;
3135
            $sql .= ", targettype = '" . $this->db->escape($targettype) . "'";
3136
            $sql .= " WHERE fk_source = " . $this->id;
3137
            $sql .= " AND sourcetype = '" . $this->db->escape($this->element) . "'";
3138
        }
3139
3140
        dol_syslog(get_class($this) . "::updateObjectLinked", LOG_DEBUG);
3141
        if ($this->db->query($sql)) {
3142
            return 1;
3143
        } else {
3144
            $this->error = $this->db->lasterror();
3145
            return -1;
3146
        }
3147
    }
3148
3149
    /**
3150
     *    Delete all links between an object $this
3151
     *
3152
     * @param int $sourceid Object source id
3153
     * @param string $sourcetype Object source type
3154
     * @param int $targetid Object target id
3155
     * @param string $targettype Object target type
3156
     * @param int $rowid Row id of line to delete. If defined, other parameters are not used.
3157
     * @return                        int    >0 if OK, <0 if KO
3158
     * @see    add_object_linked, updateObjectLinked, fetchObjectLinked
3159
     */
3160
    function deleteObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $rowid = '')
3161
    {
3162
        $deletesource = false;
3163
        $deletetarget = false;
3164
3165
        if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) $deletesource = true;
3166
        else if (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) $deletetarget = true;
3167
3168
        $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
3169
        $sourcetype = (!empty($sourcetype) ? $sourcetype : $this->element);
3170
        $targetid = (!empty($targetid) ? $targetid : $this->id);
3171
        $targettype = (!empty($targettype) ? $targettype : $this->element);
3172
3173
        $sql = "DELETE FROM " . MAIN_DB_PREFIX . "element_element";
3174
        $sql .= " WHERE";
3175
        if ($rowid > 0) {
3176
            $sql .= " rowid = " . $rowid;
3177
        } else {
3178
            if ($deletesource) {
3179
                $sql .= " fk_source = " . $sourceid . " AND sourcetype = '" . $this->db->escape($sourcetype) . "'";
3180
                $sql .= " AND fk_target = " . $this->id . " AND targettype = '" . $this->db->escape($this->element) . "'";
3181
            } else if ($deletetarget) {
3182
                $sql .= " fk_target = " . $targetid . " AND targettype = '" . $this->db->escape($targettype) . "'";
3183
                $sql .= " AND fk_source = " . $this->id . " AND sourcetype = '" . $this->db->escape($this->element) . "'";
3184
            } else {
3185
                $sql .= " (fk_source = " . $this->id . " AND sourcetype = '" . $this->db->escape($this->element) . "')";
3186
                $sql .= " OR";
3187
                $sql .= " (fk_target = " . $this->id . " AND targettype = '" . $this->db->escape($this->element) . "')";
3188
            }
3189
        }
3190
3191
        dol_syslog(get_class($this) . "::deleteObjectLinked", LOG_DEBUG);
3192
        if ($this->db->query($sql)) {
3193
            return 1;
3194
        } else {
3195
            $this->error = $this->db->lasterror();
3196
            $this->errors[] = $this->error;
3197
            return -1;
3198
        }
3199
    }
3200
3201
3202
    // --------------------
3203
    // TODO: All functions here must be redesigned and moved as they are not business functions but output functions
3204
    // --------------------
3205
3206
    /* This is to show add lines */
3207
3208
    /**
3209
     *      Set status of an object
3210
     *
3211
     * @param int $status Status to set
3212
     * @param int $elementId Id of element to force (use this->id by default)
3213
     * @param string $elementType Type of element to force (use this->table_element by default)
3214
     * @param string $trigkey Trigger key to use for trigger
3215
     * @return int                        <0 if KO, >0 if OK
3216
     */
3217
    function setStatut($status, $elementId = null, $elementType = '', $trigkey = '')
3218
    {
3219
        global $user, $langs, $conf;
3220
3221
        $savElementId = $elementId;  // To be used later to know if we were using the method using the id of this or not.
3222
3223
        $elementId = (!empty($elementId) ? $elementId : $this->id);
3224
        $elementTable = (!empty($elementType) ? $elementType : $this->table_element);
3225
3226
        $this->db->begin();
3227
3228
        $fieldstatus = "fk_statut";
3229
        if ($elementTable == 'facture_rec') $fieldstatus = "suspended";
3230
        if ($elementTable == 'mailing') $fieldstatus = "statut";
3231
        if ($elementTable == 'cronjob') $fieldstatus = "status";
3232
        if ($elementTable == 'user') $fieldstatus = "statut";
3233
        if ($elementTable == 'expensereport') $fieldstatus = "fk_statut";
3234
        if ($elementTable == 'commande_fournisseur_dispatch') $fieldstatus = "status";
3235
3236
        $sql = "UPDATE " . MAIN_DB_PREFIX . $elementTable;
3237
        $sql .= " SET " . $fieldstatus . " = " . $status;
3238
        // If status = 1 = validated, update also fk_user_valid
3239
        if ($status == 1 && $elementTable == 'expensereport') $sql .= ", fk_user_valid = " . $user->id;
3240
        $sql .= " WHERE rowid=" . $elementId;
3241
3242
        dol_syslog(get_class($this) . "::setStatut", LOG_DEBUG);
3243
        if ($this->db->query($sql)) {
3244
            $error = 0;
3245
3246
            // Try autoset of trigkey
3247
            if (empty($trigkey)) {
3248
                if ($this->element == 'supplier_proposal' && $status == 2) $trigkey = 'SUPPLIER_PROPOSAL_SIGN';   // 2 = SupplierProposal::STATUS_SIGNED. Can't use constant into this generic class
3249
                if ($this->element == 'supplier_proposal' && $status == 3) $trigkey = 'SUPPLIER_PROPOSAL_REFUSE'; // 3 = SupplierProposal::STATUS_REFUSED. Can't use constant into this generic class
3250
                if ($this->element == 'supplier_proposal' && $status == 4) $trigkey = 'SUPPLIER_PROPOSAL_CLOSE';  // 4 = SupplierProposal::STATUS_CLOSED. Can't use constant into this generic class
3251
                if ($this->element == 'fichinter' && $status == 3) $trigkey = 'FICHINTER_CLASSIFY_DONE';
3252
                if ($this->element == 'fichinter' && $status == 2) $trigkey = 'FICHINTER_CLASSIFY_BILLED';
3253
                if ($this->element == 'fichinter' && $status == 1) $trigkey = 'FICHINTER_CLASSIFY_UNBILLED';
3254
            }
3255
3256
            if ($trigkey) {
3257
                // Appel des triggers
3258
                include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
3259
                $interface = new Interfaces($this->db);
3260
                $result = $interface->run_triggers($trigkey, $this, $user, $langs, $conf);
3261
                if ($result < 0) {
3262
                    $error++;
3263
                    $this->errors = $interface->errors;
3264
                }
3265
                // Fin appel triggers
3266
            }
3267
3268
            if (!$error) {
3269
                $this->db->commit();
3270
3271
                if (empty($savElementId))    // If the element we update was $this (so $elementId is null)
3272
                {
3273
                    $this->statut = $status;
3274
                    $this->status = $status;
3275
                }
3276
3277
                return 1;
3278
            } else {
3279
                $this->db->rollback();
3280
                dol_syslog(get_class($this) . "::setStatus " . $this->error, LOG_ERR);
3281
                return -1;
3282
            }
3283
        } else {
3284
            $this->error = $this->db->lasterror();
3285
            $this->db->rollback();
3286
            return -1;
3287
        }
3288
    }
3289
3290
3291
3292
    /* This is to show array of line of details */
3293
3294
    /**
3295
     *  Load type of canvas of an object if it exists
3296
     *
3297
     * @param int $id Record id
3298
     * @param string $ref Record ref
3299
     * @return        int                <0 if KO, 0 if nothing done, >0 if OK
3300
     */
3301
    function getCanvas($id = 0, $ref = '')
3302
    {
3303
        global $conf;
3304
3305
        if (empty($id) && empty($ref)) return 0;
3306
        if (!empty($conf->global->MAIN_DISABLE_CANVAS)) return 0;    // To increase speed. Not enabled by default.
3307
3308
        // Clean parameters
3309
        $ref = trim($ref);
3310
3311
        $sql = "SELECT rowid, canvas";
3312
        $sql .= " FROM " . MAIN_DB_PREFIX . $this->table_element;
3313
        $sql .= " WHERE entity IN (" . getEntity($this->element) . ")";
3314
        if (!empty($id)) $sql .= " AND rowid = " . $id;
3315
        if (!empty($ref)) $sql .= " AND ref = '" . $this->db->escape($ref) . "'";
3316
3317
        $resql = $this->db->query($sql);
3318
        if ($resql) {
3319
            $obj = $this->db->fetch_object($resql);
3320
            if ($obj) {
3321
                $this->canvas = $obj->canvas;
3322
                return 1;
3323
            } else return 0;
3324
        } else {
3325
            dol_print_error($this->db);
3326
            return -1;
3327
        }
3328
    }
3329
3330
    /**
3331
     *    Get special code of a line
3332
     *
3333
     * @param int $lineid Id of line
3334
     * @return    int                    Special code
3335
     */
3336
    function getSpecialCode($lineid)
3337
    {
3338
        $sql = 'SELECT special_code FROM ' . MAIN_DB_PREFIX . $this->table_element_line;
3339
        $sql .= ' WHERE rowid = ' . $lineid;
3340
        $resql = $this->db->query($sql);
3341
        if ($resql) {
3342
            $row = $this->db->fetch_row($resql);
3343
            return $row[0];
3344
        }
3345
    }
3346
3347
3348
    /* This is to show array of line of details of source object */
3349
3350
    /**
3351
     *  Function to say how many lines object contains
3352
     *
3353
     * @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
3354
     * @return    int                        <0 if KO, 0 if no predefined products, nb of lines with predefined products if found
3355
     */
3356
    function hasProductsOrServices($predefined = -1)
3357
    {
3358
        $nb = 0;
3359
3360
        foreach ($this->lines as $key => $val) {
3361
            $qualified = 0;
3362
            if ($predefined == -1) $qualified = 1;
3363
            if ($predefined == 1 && $val->fk_product > 0) $qualified = 1;
3364
            if ($predefined == 0 && $val->fk_product <= 0) $qualified = 1;
3365
            if ($predefined == 2 && $val->fk_product > 0 && $val->product_type == 0) $qualified = 1;
3366
            if ($predefined == 3 && $val->fk_product > 0 && $val->product_type == 1) $qualified = 1;
3367
            if ($qualified) $nb++;
3368
        }
3369
        dol_syslog(get_class($this) . '::hasProductsOrServices we found ' . $nb . ' qualified lines of products/servcies');
3370
        return $nb;
3371
    }
3372
3373
    /**
3374
     * Function that returns the total amount HT of discounts applied for all lines.
3375
     *
3376
     * @return    float
3377
     */
3378
    function getTotalDiscount()
3379
    {
3380
        $total_discount = 0.00;
3381
3382
        $sql = "SELECT subprice as pu_ht, qty, remise_percent, total_ht";
3383
        $sql .= " FROM " . MAIN_DB_PREFIX . $this->table_element . "det";
3384
        $sql .= " WHERE " . $this->fk_element . " = " . $this->id;
3385
3386
        dol_syslog(get_class($this) . '::getTotalDiscount', LOG_DEBUG);
3387
        $resql = $this->db->query($sql);
3388
        if ($resql) {
3389
            $num = $this->db->num_rows($resql);
3390
            $i = 0;
3391
            while ($i < $num) {
3392
                $obj = $this->db->fetch_object($resql);
3393
3394
                $pu_ht = $obj->pu_ht;
3395
                $qty = $obj->qty;
3396
                $total_ht = $obj->total_ht;
3397
3398
                $total_discount_line = floatval(price2num(($pu_ht * $qty) - $total_ht, 'MT'));
3399
                $total_discount += $total_discount_line;
3400
3401
                $i++;
3402
            }
3403
        }
3404
3405
        //print $total_discount; exit;
3406
        return price2num($total_discount);
3407
    }
3408
3409
3410
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3411
3412
    /**
3413
     * Return into unit=0, the calculated total of weight and volume of all lines * qty
3414
     * Calculate by adding weight and volume of each product line, so properties ->volume/volume_units/weight/weight_units must be loaded on line.
3415
     *
3416
     * @return  array                           array('weight'=>...,'volume'=>...)
3417
     */
3418
    function getTotalWeightVolume()
3419
    {
3420
        $totalWeight = 0;
3421
        $totalVolume = 0;
3422
        // defined for shipment only
3423
        $totalOrdered = '';
3424
        // defined for shipment only
3425
        $totalToShip = '';
3426
3427
        foreach ($this->lines as $line) {
3428
            if (isset($line->qty_asked)) {
3429
                if (empty($totalOrdered)) $totalOrdered = 0;  // Avoid warning because $totalOrdered is ''
3430
                $totalOrdered += $line->qty_asked;    // defined for shipment only
3431
            }
3432
            if (isset($line->qty_shipped)) {
3433
                if (empty($totalToShip)) $totalToShip = 0;    // Avoid warning because $totalToShip is ''
3434
                $totalToShip += $line->qty_shipped;   // defined for shipment only
3435
            } else if ($line->element == 'commandefournisseurdispatch' && isset($line->qty)) {
3436
                if (empty($totalToShip)) $totalToShip = 0;
3437
                $totalToShip += $line->qty;   // defined for reception only
3438
            }
3439
3440
            // Define qty, weight, volume, weight_units, volume_units
3441
            if ($this->element == 'shipping') {
3442
                // for shipments
3443
                $qty = $line->qty_shipped ? $line->qty_shipped : 0;
3444
            } else {
3445
                $qty = $line->qty ? $line->qty : 0;
3446
            }
3447
3448
            $weight = $line->weight ? $line->weight : 0;
3449
            ($weight == 0 && !empty($line->product->weight)) ? $weight = $line->product->weight : 0;
3450
            $volume = $line->volume ? $line->volume : 0;
3451
            ($volume == 0 && !empty($line->product->volume)) ? $volume = $line->product->volume : 0;
3452
3453
            $weight_units = $line->weight_units;
3454
            ($weight_units == 0 && !empty($line->product->weight_units)) ? $weight_units = $line->product->weight_units : 0;
3455
            $volume_units = $line->volume_units;
3456
            ($volume_units == 0 && !empty($line->product->volume_units)) ? $volume_units = $line->product->volume_units : 0;
3457
3458
            $weightUnit = 0;
3459
            $volumeUnit = 0;
3460
            if (!empty($weight_units)) $weightUnit = $weight_units;
3461
            if (!empty($volume_units)) $volumeUnit = $volume_units;
3462
3463
            if (empty($totalWeight)) $totalWeight = 0;  // Avoid warning because $totalWeight is ''
3464
            if (empty($totalVolume)) $totalVolume = 0;  // Avoid warning because $totalVolume is ''
3465
3466
            //var_dump($line->volume_units);
3467
            if ($weight_units < 50)   // >50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
3468
            {
3469
                $trueWeightUnit = pow(10, $weightUnit);
3470
                $totalWeight += $weight * $qty * $trueWeightUnit;
3471
            } else {
3472
                if ($weight_units == 99) {
3473
                    // conversion 1 Pound = 0.45359237 KG
3474
                    $trueWeightUnit = 0.45359237;
3475
                    $totalWeight += $weight * $qty * $trueWeightUnit;
3476
                } elseif ($weight_units == 98) {
3477
                    // conversion 1 Ounce = 0.0283495 KG
3478
                    $trueWeightUnit = 0.0283495;
3479
                    $totalWeight += $weight * $qty * $trueWeightUnit;
3480
                } else
3481
                    $totalWeight += $weight * $qty;   // This may be wrong if we mix different units
3482
            }
3483
            if ($volume_units < 50)   // >50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
3484
            {
3485
                //print $line->volume."x".$line->volume_units."x".($line->volume_units < 50)."x".$volumeUnit;
3486
                $trueVolumeUnit = pow(10, $volumeUnit);
3487
                //print $line->volume;
3488
                $totalVolume += $volume * $qty * $trueVolumeUnit;
3489
            } else {
3490
                $totalVolume += $volume * $qty;   // This may be wrong if we mix different units
3491
            }
3492
        }
3493
3494
        return array('weight' => $totalWeight, 'volume' => $totalVolume, 'ordered' => $totalOrdered, 'toship' => $totalToShip);
3495
    }
3496
3497
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3498
3499
    /**
3500
     *    Set extra parameters
3501
     *
3502
     * @return    int      <0 if KO, >0 if OK
3503
     */
3504
    function setExtraParameters()
3505
    {
3506
        $this->db->begin();
3507
3508
        $extraparams = (!empty($this->extraparams) ? json_encode($this->extraparams) : null);
3509
3510
        $sql = "UPDATE " . MAIN_DB_PREFIX . $this->table_element;
3511
        $sql .= " SET extraparams = " . (!empty($extraparams) ? "'" . $this->db->escape($extraparams) . "'" : "null");
3512
        $sql .= " WHERE rowid = " . $this->id;
3513
3514
        dol_syslog(get_class($this) . "::setExtraParameters", LOG_DEBUG);
3515
        $resql = $this->db->query($sql);
3516
        if (!$resql) {
3517
            $this->error = $this->db->lasterror();
3518
            $this->db->rollback();
3519
            return -1;
3520
        } else {
3521
            $this->db->commit();
3522
            return 1;
3523
        }
3524
    }
3525
3526
    /**
3527
     *    Return incoterms informations
3528
     *    TODO Use a cache for label get
3529
     *
3530
     * @return    string    incoterms info
3531
     */
3532
    function display_incoterms()
3533
    {
3534
        // phpcs:enable
3535
        $out = '';
3536
        $this->libelle_incoterms = '';
3537
        if (!empty($this->fk_incoterms)) {
3538
            $sql = 'SELECT code FROM ' . MAIN_DB_PREFIX . 'c_incoterms WHERE rowid = ' . (int)$this->fk_incoterms;
3539
            $result = $this->db->query($sql);
3540
            if ($result) {
3541
                $res = $this->db->fetch_object($result);
3542
                $out .= $res->code;
3543
            }
3544
        }
3545
3546
        $out .= (($res->code && $this->location_incoterms) ? ' - ' : '') . $this->location_incoterms;
3547
3548
        return $out;
3549
    }
3550
3551
    /**
3552
     *    Return incoterms informations for pdf display
3553
     *
3554
     * @return    string        incoterms info
3555
     */
3556
    function getIncotermsForPDF()
3557
    {
3558
        $sql = 'SELECT code FROM ' . MAIN_DB_PREFIX . 'c_incoterms WHERE rowid = ' . (int)$this->fk_incoterms;
3559
        $resql = $this->db->query($sql);
3560
        if ($resql) {
3561
            $num = $this->db->num_rows($resql);
3562
            if ($num > 0) {
3563
                $res = $this->db->fetch_object($resql);
3564
                return 'Incoterm : ' . $res->code . ' - ' . $this->location_incoterms;
3565
            } else {
3566
                return '';
3567
            }
3568
        } else {
3569
            $this->errors[] = $this->db->lasterror();
3570
            return false;
3571
        }
3572
    }
3573
3574
    /**
3575
     *    Define incoterms values of current object
3576
     *
3577
     * @param int $id_incoterm Id of incoterm to set or '' to remove
3578
     * @param string $location location of incoterm
3579
     * @return    int            <0 if KO, >0 if OK
3580
     */
3581
    function setIncoterms($id_incoterm, $location)
3582
    {
3583
        if ($this->id && $this->table_element) {
3584
            $sql = "UPDATE " . MAIN_DB_PREFIX . $this->table_element;
3585
            $sql .= " SET fk_incoterms = " . ($id_incoterm > 0 ? $id_incoterm : "null");
3586
            $sql .= ", location_incoterms = " . ($id_incoterm > 0 ? "'" . $this->db->escape($location) . "'" : "null");
3587
            $sql .= " WHERE rowid = " . $this->id;
3588
            dol_syslog(get_class($this) . '::setIncoterms', LOG_DEBUG);
3589
            $resql = $this->db->query($sql);
3590
            if ($resql) {
3591
                $this->fk_incoterms = $id_incoterm;
3592
                $this->location_incoterms = $location;
3593
3594
                $sql = 'SELECT libelle FROM ' . MAIN_DB_PREFIX . 'c_incoterms WHERE rowid = ' . (int)$this->fk_incoterms;
3595
                $res = $this->db->query($sql);
3596
                if ($res) {
3597
                    $obj = $this->db->fetch_object($res);
3598
                    $this->libelle_incoterms = $obj->libelle;
3599
                }
3600
                return 1;
3601
            } else {
3602
                $this->errors[] = $this->db->lasterror();
3603
                return -1;
3604
            }
3605
        } else return -1;
3606
    }
3607
3608
3609
    /* Functions common to commonobject and commonobjectline */
3610
3611
    /* For default values */
3612
3613
    /**
3614
     *    Show add free and predefined products/services form
3615
     *
3616
     * @param int $dateSelector 1=Show also date range input fields
3617
     * @param Societe $seller Object thirdparty who sell
3618
     * @param Societe $buyer Object thirdparty who buy
3619
     * @return    void
3620
     */
3621
    function formAddObjectLine($dateSelector, $seller, $buyer)
3622
    {
3623
        global $conf, $user, $langs, $object, $hookmanager;
3624
        global $form, $bcnd, $var;
3625
3626
        // Line extrafield
3627
        require_once DOL_DOCUMENT_ROOT . '/core/class/extrafields.class.php';
3628
        $extrafieldsline = new ExtraFields($this->db);
3629
        $extralabelslines = $extrafieldsline->fetch_name_optionals_label($this->table_element_line);
3630
3631
        // Output template part (modules that overwrite templates must declare this into descriptor)
3632
        // Use global variables + $dateSelector + $seller and $buyer
3633
        $dirtpls = array_merge($conf->modules_parts['tpl'], array('/core/tpl'));
3634
        foreach ($dirtpls as $reldir) {
3635
            $tpl = dol_buildpath($reldir . '/objectline_create.tpl.php');
3636
            if (empty($conf->file->strict_mode)) {
3637
                $res = @include $tpl;
3638
            } else {
3639
                $res = include $tpl; // for debug
3640
            }
3641
            if ($res) break;
3642
        }
3643
    }
3644
3645
3646
    /* For triggers */
3647
3648
3649
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3650
3651
    /**
3652
     *    Return HTML table for object lines
3653
     *    TODO Move this into an output class file (htmlline.class.php)
3654
     *    If lines are into a template, title must also be into a template
3655
     *    But for the moment we don't know if it's possible as we keep a method available on overloaded objects.
3656
     *
3657
     * @param string $action Action code
3658
     * @param string $seller Object of seller third party
3659
     * @param string $buyer Object of buyer third party
3660
     * @param int $selected Object line selected
3661
     * @param int $dateSelector 1=Show also date range input fields
3662
     * @return    void
3663
     */
3664
    function printObjectLines($action, $seller, $buyer, $selected = 0, $dateSelector = 0)
3665
    {
3666
        global $conf, $hookmanager, $langs, $user;
3667
        // TODO We should not use global var for this !
3668
        global $inputalsopricewithtax, $usemargins, $disableedit, $disablemove, $disableremove, $outputalsopricetotalwithtax;
3669
3670
        // Define usemargins
3671
        $usemargins = 0;
3672
        if (!empty($conf->margin->enabled) && !empty($this->element) && in_array($this->element, array('facture', 'propal', 'commande'))) $usemargins = 1;
3673
3674
        $num = count($this->lines);
3675
3676
        // Line extrafield
3677
        require_once DOL_DOCUMENT_ROOT . '/core/class/extrafields.class.php';
3678
        $extrafieldsline = new ExtraFields($this->db);
3679
        $extralabelslines = $extrafieldsline->fetch_name_optionals_label($this->table_element_line);
3680
3681
        $parameters = array('num' => $num, 'i' => $i, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'extrafieldsline' => $extrafieldsline);
3682
        $reshook = $hookmanager->executeHooks('printObjectLineTitle', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3683
        if (empty($reshook)) {
3684
            // Title line
3685
            print "<thead>\n";
3686
3687
            print '<tr class="liste_titre nodrag nodrop">';
3688
3689
            // Adds a line numbering column
3690
            if (!empty($conf->global->MAIN_VIEW_LINE_NUMBER)) print '<td class="linecolnum" align="center" width="5">&nbsp;</td>';
3691
3692
            // Description
3693
            print '<td class="linecoldescription">' . $langs->trans('Description') . '</td>';
3694
3695
            if ($this->element == 'supplier_proposal' || $this->element == 'order_supplier' || $this->element == 'invoice_supplier') {
3696
                print '<td class="linerefsupplier"><span id="title_fourn_ref">' . $langs->trans("SupplierRef") . '</span></td>';
3697
            }
3698
3699
            // VAT
3700
            print '<td class="linecolvat" align="right" width="80">' . $langs->trans('VAT') . '</td>';
3701
3702
            // Price HT
3703
            print '<td class="linecoluht" align="right" width="80">' . $langs->trans('PriceUHT') . '</td>';
3704
3705
            // Multicurrency
3706
            if (!empty($conf->multicurrency->enabled) && $this->multicurrency_code != $conf->currency) print '<td class="linecoluht_currency" align="right" width="80">' . $langs->trans('PriceUHTCurrency', $this->multicurrency_code) . '</td>';
3707
3708
            if ($inputalsopricewithtax) print '<td align="right" width="80">' . $langs->trans('PriceUTTC') . '</td>';
3709
3710
            // Qty
3711
            print '<td class="linecolqty" align="right">' . $langs->trans('Qty') . '</td>';
3712
3713
            if ($conf->global->PRODUCT_USE_UNITS) {
3714
                print '<td class="linecoluseunit" align="left">' . $langs->trans('Unit') . '</td>';
3715
            }
3716
3717
            // Reduction short
3718
            print '<td class="linecoldiscount" align="right">' . $langs->trans('ReductionShort') . '</td>';
3719
3720
            if ($this->situation_cycle_ref) {
3721
                print '<td class="linecolcycleref" align="right">' . $langs->trans('Progress') . '</td>';
3722
            }
3723
3724
            if ($usemargins && !empty($conf->margin->enabled) && empty($user->societe_id)) {
3725
                if (!empty($user->rights->margins->creer)) {
3726
                    if ($conf->global->MARGIN_TYPE == "1")
3727
                        print '<td class="linecolmargin1 margininfos" align="right" width="80">' . $langs->trans('BuyingPrice') . '</td>';
3728
                    else
3729
                        print '<td class="linecolmargin1 margininfos" align="right" width="80">' . $langs->trans('CostPrice') . '</td>';
3730
                }
3731
3732
                if (!empty($conf->global->DISPLAY_MARGIN_RATES) && $user->rights->margins->liretous)
3733
                    print '<td class="linecolmargin2 margininfos" align="right" width="50">' . $langs->trans('MarginRate') . '</td>';
3734
                if (!empty($conf->global->DISPLAY_MARK_RATES) && $user->rights->margins->liretous)
3735
                    print '<td class="linecolmargin2 margininfos" align="right" width="50">' . $langs->trans('MarkRate') . '</td>';
3736
            }
3737
3738
            // Total HT
3739
            print '<td class="linecolht" align="right">' . $langs->trans('TotalHTShort') . '</td>';
3740
3741
            // Multicurrency
3742
            if (!empty($conf->multicurrency->enabled) && $this->multicurrency_code != $conf->currency) print '<td class="linecoltotalht_currency" align="right">' . $langs->trans('TotalHTShortCurrency', $this->multicurrency_code) . '</td>';
3743
3744
            if ($outputalsopricetotalwithtax) print '<td align="right" width="80">' . $langs->trans('TotalTTCShort') . '</td>';
3745
3746
            print '<td class="linecoledit"></td>';  // No width to allow autodim
3747
3748
            print '<td class="linecoldelete" width="10"></td>';
3749
3750
            print '<td class="linecolmove" width="10"></td>';
3751
3752
            if ($action == 'selectlines') {
3753
                print '<td class="linecolcheckall" align="center">';
3754
                print '<input type="checkbox" class="linecheckboxtoggle" />';
3755
                print '<script type="text/javascript">$(document).ready(function() {$(".linecheckboxtoggle").click(function() {var checkBoxes = $(".linecheckbox");checkBoxes.prop("checked", this.checked);})});</script>';
3756
                print '</td>';
3757
            }
3758
3759
            print "</tr>\n";
3760
            print "</thead>\n";
3761
        }
3762
3763
        $var = true;
3764
        $i = 0;
3765
3766
        print "<tbody>\n";
3767
        foreach ($this->lines as $line) {
3768
            //Line extrafield
3769
            $line->fetch_optionals();
3770
3771
            //if (is_object($hookmanager) && (($line->product_type == 9 && ! empty($line->special_code)) || ! empty($line->fk_parent_line)))
3772
            if (is_object($hookmanager))   // Old code is commented on preceding line.
3773
            {
3774
                if (empty($line->fk_parent_line)) {
3775
                    $parameters = array('line' => $line, 'var' => $var, 'num' => $num, 'i' => $i, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'extrafieldsline' => $extrafieldsline);
3776
                    $reshook = $hookmanager->executeHooks('printObjectLine', $parameters, $this, $action);    // Note that $action and $object may have been modified by some hooks
3777
                } else {
3778
                    $parameters = array('line' => $line, 'var' => $var, 'num' => $num, 'i' => $i, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'extrafieldsline' => $extrafieldsline, 'fk_parent_line' => $line->fk_parent_line);
3779
                    $reshook = $hookmanager->executeHooks('printObjectSubLine', $parameters, $this, $action);    // Note that $action and $object may have been modified by some hooks
3780
                }
3781
            }
3782
            if (empty($reshook)) {
3783
                $this->printObjectLine($action, $line, $var, $num, $i, $dateSelector, $seller, $buyer, $selected, $extrafieldsline);
3784
            }
3785
3786
            $i++;
3787
        }
3788
        print "</tbody>\n";
3789
    }
3790
3791
3792
    /* Functions for extrafields */
3793
3794
3795
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3796
3797
    /**
3798
     *    Return HTML content of a detail line
3799
     *    TODO Move this into an output class file (htmlline.class.php)
3800
     *
3801
     * @param string $action GET/POST action
3802
     * @param CommonObjectLine $line Selected object line to output
3803
     * @param string $var Is it a an odd line (true)
3804
     * @param int $num Number of line (0)
3805
     * @param int $i I
3806
     * @param int $dateSelector 1=Show also date range input fields
3807
     * @param string $seller Object of seller third party
3808
     * @param string $buyer Object of buyer third party
3809
     * @param int $selected Object line selected
3810
     * @param int $extrafieldsline Object of extrafield line attribute
3811
     * @return    void
3812
     */
3813
    function printObjectLine($action, $line, $var, $num, $i, $dateSelector, $seller, $buyer, $selected = 0, $extrafieldsline = 0)
3814
    {
3815
        global $conf, $langs, $user, $object, $hookmanager;
3816
        global $form, $bc, $bcdd;
3817
        global $object_rights, $disableedit, $disablemove, $disableremove;   // TODO We should not use global var for this !
3818
3819
        $object_rights = $this->getRights();
3820
3821
        $element = $this->element;
3822
3823
        $text = '';
3824
        $description = '';
3825
        $type = 0;
3826
3827
        // Show product and description
3828
        $type = (!empty($line->product_type) ? $line->product_type : $line->fk_product_type);
3829
        // Try to enhance type detection using date_start and date_end for free lines where type was not saved.
3830
        if (!empty($line->date_start)) $type = 1; // deprecated
3831
        if (!empty($line->date_end)) $type = 1; // deprecated
3832
3833
        // Ligne en mode visu
3834
        if ($action != 'editline' || $selected != $line->id) {
3835
            // Product
3836
            if ($line->fk_product > 0) {
3837
                $product_static = new Product($this->db);
3838
                $product_static->fetch($line->fk_product);
3839
3840
                $product_static->ref = $line->ref; //can change ref in hook
3841
                $product_static->label = $line->label; //can change label in hook
3842
                $text = $product_static->getNomUrl(1);
3843
3844
                // Define output language and label
3845
                if (!empty($conf->global->MAIN_MULTILANGS)) {
3846
                    if (!is_object($this->thirdparty)) {
3847
                        dol_print_error('', 'Error: Method printObjectLine was called on an object and object->fetch_thirdparty was not done before');
3848
                        return;
3849
                    }
3850
3851
                    $prod = new Product($this->db);
3852
                    $prod->fetch($line->fk_product);
3853
3854
                    $outputlangs = $langs;
3855
                    $newlang = '';
3856
                    if (empty($newlang) && GETPOST('lang_id', 'aZ09')) $newlang = GETPOST('lang_id', 'aZ09');
3857
                    if (!empty($conf->global->PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE) && empty($newlang)) $newlang = $this->thirdparty->default_lang;        // For language to language of customer
3858
                    if (!empty($newlang)) {
3859
                        $outputlangs = new Translate("", $conf);
3860
                        $outputlangs->setDefaultLang($newlang);
3861
                    }
3862
3863
                    $label = (!empty($prod->multilangs[$outputlangs->defaultlang]["label"])) ? $prod->multilangs[$outputlangs->defaultlang]["label"] : $line->product_label;
3864
                } else {
3865
                    $label = $line->product_label;
3866
                }
3867
3868
                $text .= ' - ' . (!empty($line->label) ? $line->label : $label);
3869
                $description .= (!empty($conf->global->PRODUIT_DESC_IN_FORM) ? '' : dol_htmlentitiesbr($line->description));    // Description is what to show on popup. We shown nothing if already into desc.
3870
            }
3871
3872
            $line->pu_ttc = price2num($line->subprice * (1 + ($line->tva_tx / 100)), 'MU');
3873
3874
            // Output template part (modules that overwrite templates must declare this into descriptor)
3875
            // Use global variables + $dateSelector + $seller and $buyer
3876
            $dirtpls = array_merge($conf->modules_parts['tpl'], array('/core/tpl'));
3877
            foreach ($dirtpls as $reldir) {
3878
                $tpl = dol_buildpath($reldir . '/objectline_view.tpl.php');
3879
                if (empty($conf->file->strict_mode)) {
3880
                    $res = @include $tpl;
3881
                } else {
3882
                    $res = include $tpl; // for debug
3883
                }
3884
                if ($res) break;
3885
            }
3886
        }
3887
3888
        // Ligne en mode update
3889
        if ($this->statut == 0 && $action == 'editline' && $selected == $line->id) {
3890
            $label = (!empty($line->label) ? $line->label : (($line->fk_product > 0) ? $line->product_label : ''));
3891
            $placeholder = ' placeholder="' . $langs->trans("Label") . '"';
3892
3893
            $line->pu_ttc = price2num($line->subprice * (1 + ($line->tva_tx / 100)), 'MU');
3894
3895
            // Output template part (modules that overwrite templates must declare this into descriptor)
3896
            // Use global variables + $dateSelector + $seller and $buyer
3897
            $dirtpls = array_merge($conf->modules_parts['tpl'], array('/core/tpl'));
3898
            foreach ($dirtpls as $reldir) {
3899
                $tpl = dol_buildpath($reldir . '/objectline_edit.tpl.php');
3900
                if (empty($conf->file->strict_mode)) {
3901
                    $res = @include $tpl;
3902
                } else {
3903
                    $res = include $tpl; // for debug
3904
                }
3905
                if ($res) break;
3906
            }
3907
        }
3908
    }
3909
3910
    /**
3911
     * Returns the rights used for this class
3912
     * @return stdClass
3913
     */
3914
    public function getRights()
3915
    {
3916
        global $user;
3917
3918
        $element = $this->element;
3919
        if ($element == 'facturerec') $element = 'facture';
3920
3921
        return $user->rights->{$element};
3922
    }
3923
3924
    /**
3925
     *    Return HTML table table of source object lines
3926
     *  TODO Move this and previous function into output html class file (htmlline.class.php).
3927
     *  If lines are into a template, title must also be into a template
3928
     *  But for the moment we don't know if it's possible, so we keep the method available on overloaded objects.
3929
     *
3930
     * @param string $restrictlist ''=All lines, 'services'=Restrict to services only
3931
     * @return    void
3932
     */
3933
    function printOriginLinesList($restrictlist = '')
3934
    {
3935
        global $langs, $hookmanager, $conf;
3936
3937
        print '<tr class="liste_titre">';
3938
        print '<td>' . $langs->trans('Ref') . '</td>';
3939
        print '<td>' . $langs->trans('Description') . '</td>';
3940
        print '<td align="right">' . $langs->trans('VATRate') . '</td>';
3941
        print '<td align="right">' . $langs->trans('PriceUHT') . '</td>';
3942
        if (!empty($conf->multicurrency->enabled)) print '<td align="right">' . $langs->trans('PriceUHTCurrency') . '</td>';
3943
        print '<td align="right">' . $langs->trans('Qty') . '</td>';
3944
        if ($conf->global->PRODUCT_USE_UNITS) {
3945
            print '<td align="left">' . $langs->trans('Unit') . '</td>';
3946
        }
3947
        print '<td align="right">' . $langs->trans('ReductionShort') . '</td></tr>';
3948
3949
        $var = true;
3950
        $i = 0;
3951
3952
        if (!empty($this->lines)) {
3953
            foreach ($this->lines as $line) {
3954
                if (is_object($hookmanager) && (($line->product_type == 9 && !empty($line->special_code)) || !empty($line->fk_parent_line))) {
3955
                    if (empty($line->fk_parent_line)) {
3956
                        $parameters = array('line' => $line, 'var' => $var, 'i' => $i);
3957
                        $action = '';
3958
                        $hookmanager->executeHooks('printOriginObjectLine', $parameters, $this, $action);    // Note that $action and $object may have been modified by some hooks
3959
                    }
3960
                } else {
3961
                    $this->printOriginLine($line, $var, $restrictlist);
3962
                }
3963
3964
                $i++;
3965
            }
3966
        }
3967
    }
3968
3969
    /**
3970
     *    Return HTML with a line of table array of source object lines
3971
     *  TODO Move this and previous function into output html class file (htmlline.class.php).
3972
     *  If lines are into a template, title must also be into a template
3973
     *  But for the moment we don't know if it's possible as we keep a method available on overloaded objects.
3974
     *
3975
     * @param CommonObjectLine $line Line
3976
     * @param string $var Var
3977
     * @param string $restrictlist ''=All lines, 'services'=Restrict to services only (strike line if not)
3978
     * @return    void
3979
     */
3980
    function printOriginLine($line, $var, $restrictlist = '')
3981
    {
3982
        global $langs, $conf;
3983
3984
        //var_dump($line);
3985
        if (!empty($line->date_start)) {
3986
            $date_start = $line->date_start;
3987
        } else {
3988
            $date_start = $line->date_debut_prevue;
3989
            if ($line->date_debut_reel) $date_start = $line->date_debut_reel;
3990
        }
3991
        if (!empty($line->date_end)) {
3992
            $date_end = $line->date_end;
3993
        } else {
3994
            $date_end = $line->date_fin_prevue;
3995
            if ($line->date_fin_reel) $date_end = $line->date_fin_reel;
3996
        }
3997
3998
        $this->tpl['label'] = '';
3999
        if (!empty($line->fk_parent_line)) $this->tpl['label'] .= img_picto('', 'rightarrow');
4000
4001
        if (($line->info_bits & 2) == 2)  // TODO Not sure this is used for source object
4002
        {
4003
            $discount = new DiscountAbsolute($this->db);
4004
            $discount->fk_soc = $this->socid;
4005
            $this->tpl['label'] .= $discount->getNomUrl(0, 'discount');
4006
        } else if (!empty($line->fk_product)) {
4007
            $productstatic = new Product($this->db);
4008
            $productstatic->id = $line->fk_product;
4009
            $productstatic->ref = $line->ref;
4010
            $productstatic->type = $line->fk_product_type;
4011
            if (empty($productstatic->ref)) {
4012
                $line->fetch_product();
4013
                $productstatic = $line->product;
4014
            }
4015
4016
            $this->tpl['label'] .= $productstatic->getNomUrl(1);
4017
            $this->tpl['label'] .= ' - ' . (!empty($line->label) ? $line->label : $line->product_label);
4018
            // Dates
4019
            if ($line->product_type == 1 && ($date_start || $date_end)) {
4020
                $this->tpl['label'] .= get_date_range($date_start, $date_end);
4021
            }
4022
        } else {
4023
            $this->tpl['label'] .= ($line->product_type == -1 ? '&nbsp;' : ($line->product_type == 1 ? img_object($langs->trans(''), 'service') : img_object($langs->trans(''), 'product')));
4024
            if (!empty($line->desc)) {
4025
                $this->tpl['label'] .= $line->desc;
4026
            } else {
4027
                $this->tpl['label'] .= ($line->label ? '&nbsp;' . $line->label : '');
4028
            }
4029
4030
            // Dates
4031
            if ($line->product_type == 1 && ($date_start || $date_end)) {
4032
                $this->tpl['label'] .= get_date_range($date_start, $date_end);
4033
            }
4034
        }
4035
4036
        if (!empty($line->desc)) {
4037
            if ($line->desc == '(CREDIT_NOTE)')  // TODO Not sure this is used for source object
4038
            {
4039
                $discount = new DiscountAbsolute($this->db);
4040
                $discount->fetch($line->fk_remise_except);
4041
                $this->tpl['description'] = $langs->transnoentities("DiscountFromCreditNote", $discount->getNomUrl(0));
4042
            } elseif ($line->desc == '(DEPOSIT)')  // TODO Not sure this is used for source object
4043
            {
4044
                $discount = new DiscountAbsolute($this->db);
4045
                $discount->fetch($line->fk_remise_except);
4046
                $this->tpl['description'] = $langs->transnoentities("DiscountFromDeposit", $discount->getNomUrl(0));
4047
            } elseif ($line->desc == '(EXCESS RECEIVED)') {
4048
                $discount = new DiscountAbsolute($this->db);
4049
                $discount->fetch($line->fk_remise_except);
4050
                $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessReceived", $discount->getNomUrl(0));
4051
            } elseif ($line->desc == '(EXCESS PAID)') {
4052
                $discount = new DiscountAbsolute($this->db);
4053
                $discount->fetch($line->fk_remise_except);
4054
                $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessPaid", $discount->getNomUrl(0));
4055
            } else {
4056
                $this->tpl['description'] = dol_trunc($line->desc, 60);
4057
            }
4058
        } else {
4059
            $this->tpl['description'] = '&nbsp;';
4060
        }
4061
4062
        // VAT Rate
4063
        $this->tpl['vat_rate'] = vatrate($line->tva_tx, true);
4064
        $this->tpl['vat_rate'] .= (($line->info_bits & 1) == 1) ? '*' : '';
4065
        if (!empty($line->vat_src_code) && !preg_match('/\(/', $this->tpl['vat_rate'])) $this->tpl['vat_rate'] .= ' (' . $line->vat_src_code . ')';
4066
4067
        $this->tpl['price'] = price($line->subprice);
4068
        $this->tpl['multicurrency_price'] = price($line->multicurrency_subprice);
4069
        $this->tpl['qty'] = (($line->info_bits & 2) != 2) ? $line->qty : '&nbsp;';
4070
        if ($conf->global->PRODUCT_USE_UNITS) $this->tpl['unit'] = $langs->transnoentities($line->getLabelOfUnit('long'));
4071
        $this->tpl['remise_percent'] = (($line->info_bits & 2) != 2) ? vatrate($line->remise_percent, true) : '&nbsp;';
4072
4073
        // Is the line strike or not
4074
        $this->tpl['strike'] = 0;
4075
        if ($restrictlist == 'services' && $line->product_type != Product::TYPE_SERVICE) $this->tpl['strike'] = 1;
4076
4077
        // Output template part (modules that overwrite templates must declare this into descriptor)
4078
        // Use global variables + $dateSelector + $seller and $buyer
4079
        $dirtpls = array_merge($conf->modules_parts['tpl'], array('/core/tpl'));
4080
        foreach ($dirtpls as $reldir) {
4081
            $tpl = dol_buildpath($reldir . '/originproductline.tpl.php');
4082
            if (empty($conf->file->strict_mode)) {
4083
                $res = @include $tpl;
4084
            } else {
4085
                $res = include $tpl; // for debug
4086
            }
4087
            if ($res) break;
4088
        }
4089
    }
4090
4091
    /**
4092
     *    Add resources to the current object : add entry into llx_element_resources
4093
     *    Need $this->element & $this->id
4094
     *
4095
     * @param int $resource_id Resource id
4096
     * @param string $resource_type 'resource'
4097
     * @param int $busy Busy or not
4098
     * @param int $mandatory Mandatory or not
4099
     * @return        int                            <=0 if KO, >0 if OK
4100
     */
4101
    function add_element_resource($resource_id, $resource_type, $busy = 0, $mandatory = 0)
4102
    {
4103
        // phpcs:enable
4104
        $this->db->begin();
4105
4106
        $sql = "INSERT INTO " . MAIN_DB_PREFIX . "element_resources (";
4107
        $sql .= "resource_id";
4108
        $sql .= ", resource_type";
4109
        $sql .= ", element_id";
4110
        $sql .= ", element_type";
4111
        $sql .= ", busy";
4112
        $sql .= ", mandatory";
4113
        $sql .= ") VALUES (";
4114
        $sql .= $resource_id;
4115
        $sql .= ", '" . $this->db->escape($resource_type) . "'";
4116
        $sql .= ", '" . $this->db->escape($this->id) . "'";
4117
        $sql .= ", '" . $this->db->escape($this->element) . "'";
4118
        $sql .= ", '" . $this->db->escape($busy) . "'";
4119
        $sql .= ", '" . $this->db->escape($mandatory) . "'";
4120
        $sql .= ")";
4121
4122
        dol_syslog(get_class($this) . "::add_element_resource", LOG_DEBUG);
4123
        if ($this->db->query($sql)) {
4124
            $this->db->commit();
4125
            return 1;
4126
        } else {
4127
            $this->error = $this->db->lasterror();
4128
            $this->db->rollback();
4129
            return 0;
4130
        }
4131
    }
4132
4133
    /**
4134
     *    Delete a link to resource line
4135
     *
4136
     * @param int $rowid Id of resource line to delete
4137
     * @param int $element element name (for trigger) TODO: use $this->element into commonobject class
4138
     * @param int $notrigger Disable all triggers
4139
     * @return   int                        >0 if OK, <0 if KO
4140
     */
4141
    function delete_resource($rowid, $element, $notrigger = 0)
4142
    {
4143
        // phpcs:enable
4144
        global $user;
4145
4146
        $this->db->begin();
4147
4148
        $sql = "DELETE FROM " . MAIN_DB_PREFIX . "element_resources";
4149
        $sql .= " WHERE rowid=" . $rowid;
4150
4151
        dol_syslog(get_class($this) . "::delete_resource", LOG_DEBUG);
4152
4153
        $resql = $this->db->query($sql);
4154
        if (!$resql) {
4155
            $this->error = $this->db->lasterror();
4156
            $this->db->rollback();
4157
            return -1;
4158
        } else {
4159
            if (!$notrigger) {
4160
                $result = $this->call_trigger(strtoupper($element) . '_DELETE_RESOURCE', $user);
4161
                if ($result < 0) {
4162
                    $this->db->rollback();
4163
                    return -1;
4164
                }
4165
            }
4166
            $this->db->commit();
4167
            return 1;
4168
        }
4169
    }
4170
4171
    /**
4172
     * Overwrite magic function to solve problem of cloning object that are kept as references
4173
     *
4174
     * @return void
4175
     */
4176
    function __clone()
4177
    {
4178
        // Force a copy of this->lines, otherwise it will point to same object.
4179
        if (isset($this->lines) && is_array($this->lines)) {
4180
            $nboflines = count($this->lines);
4181
            for ($i = 0; $i < $nboflines; $i++) {
4182
                $this->lines[$i] = clone $this->lines[$i];
4183
            }
4184
        }
4185
    }
4186
4187
    /**
4188
     *  Build thumb
4189
     * @TODO Move this into files.lib.php
4190
     *
4191
     * @param string $file Path file in UTF8 to original file to create thumbs from.
4192
     * @return        void
4193
     */
4194
    function addThumbs($file)
4195
    {
4196
        global $maxwidthsmall, $maxheightsmall, $maxwidthmini, $maxheightmini, $quality;
4197
4198
        require_once DOL_DOCUMENT_ROOT . '/core/lib/images.lib.php';        // This define also $maxwidthsmall, $quality, ...
4199
4200
        $file_osencoded = dol_osencode($file);
4201
        if (file_exists($file_osencoded)) {
4202
            // Create small thumbs for company (Ratio is near 16/9)
4203
            // Used on logon for example
4204
            vignette($file_osencoded, $maxwidthsmall, $maxheightsmall, '_small', $quality);
4205
4206
            // Create mini thumbs for company (Ratio is near 16/9)
4207
            // Used on menu or for setup page for example
4208
            vignette($file_osencoded, $maxwidthmini, $maxheightmini, '_mini', $quality);
4209
        }
4210
    }
4211
4212
    /**
4213
     * Return the default value to use for a field when showing the create form of object.
4214
     * Return values in this order:
4215
     * 1) If parameter is available into POST, we return it first.
4216
     * 2) If not but an alternate value was provided as parameter of function, we return it.
4217
     * 3) If not but a constant $conf->global->OBJECTELEMENT_FIELDNAME is set, we return it (It is better to use the dedicated table).
4218
     * 4) Return value found into database (TODO No yet implemented)
4219
     *
4220
     * @param string $fieldname Name of field
4221
     * @param string $alternatevalue Alternate value to use
4222
     * @return  string|string[]                         Default value (can be an array if the GETPOST return an array)
4223
     **/
4224
    function getDefaultCreateValueFor($fieldname, $alternatevalue = null)
4225
    {
4226
        global $conf, $_POST;
4227
4228
        // If param here has been posted, we use this value first.
4229
        if (isset($_POST[$fieldname])) return GETPOST($fieldname, 2);
4230
4231
        if (isset($alternatevalue)) return $alternatevalue;
4232
4233
        $newelement = $this->element;
4234
        if ($newelement == 'facture') $newelement = 'invoice';
4235
        if ($newelement == 'commande') $newelement = 'order';
4236
        if (empty($newelement)) {
4237
            dol_syslog("Ask a default value using common method getDefaultCreateValueForField on an object with no property ->element defined. Return empty string.", LOG_WARNING);
4238
            return '';
4239
        }
4240
4241
        $keyforfieldname = strtoupper($newelement . '_DEFAULT_' . $fieldname);
4242
        //var_dump($keyforfieldname);
4243
        if (isset($conf->global->$keyforfieldname)) return $conf->global->$keyforfieldname;
4244
4245
        // TODO Ad here a scan into table llx_overwrite_default with a filter on $this->element and $fieldname
4246
    }
4247
4248
    /**
4249
     *  Function to get extra fields of an object into $this->array_options
4250
     *  This method is in most cases called by method fetch of objects but you can call it separately.
4251
     *
4252
     * @param int $rowid Id of line. Use the id of object if not defined. Deprecated. Function must be called without parameters.
4253
     * @param array $optionsArray Array resulting of call of extrafields->fetch_name_optionals_label(). Deprecated. Function must be called without parameters.
4254
     * @return    int                        <0 if error, 0 if no values of extrafield to find nor found, 1 if an attribute is found and value loaded
4255
     */
4256
    function fetch_optionals($rowid = null, $optionsArray = null)
4257
    {
4258
        // phpcs:enable
4259
        if (empty($rowid)) $rowid = $this->id;
4260
4261
        // To avoid SQL errors. Probably not the better solution though
4262
        if (!$this->table_element) {
4263
            return 0;
4264
        }
4265
4266
        $this->array_options = array();
4267
4268
        if (!is_array($optionsArray)) {
4269
            // If $extrafields is not a known object, we initialize it. Best practice is to have $extrafields defined into card.php or list.php page.
4270
            // TODO Use of existing $extrafield is not yet ready (must mutualize code that use extrafields in form first)
4271
            // global $extrafields;
4272
            //if (! is_object($extrafields))
4273
            //{
4274
            require_once DOL_DOCUMENT_ROOT . '/core/class/extrafields.class.php';
4275
            $extrafields = new ExtraFields($this->db);
4276
            //}
4277
4278
            // Load array of extrafields for elementype = $this->table_element
4279
            if (empty($extrafields->attributes[$this->table_element]['loaded'])) {
4280
                $extrafields->fetch_name_optionals_label($this->table_element);
4281
            }
4282
            $optionsArray = (!empty($extrafields->attributes[$this->table_element]['label']) ? $extrafields->attributes[$this->table_element]['label'] : null);
4283
        } else {
4284
            global $extrafields;
4285
            dol_syslog("Warning: fetch_optionals was called with param optionsArray defined when you should pass null now", LOG_WARNING);
4286
        }
4287
4288
        $table_element = $this->table_element;
4289
        if ($table_element == 'categorie') $table_element = 'categories'; // For compatibility
4290
4291
        // Request to get complementary values
4292
        if (is_array($optionsArray) && count($optionsArray) > 0) {
4293
            $sql = "SELECT rowid";
4294
            foreach ($optionsArray as $name => $label) {
4295
                if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || $extrafields->attributes[$this->table_element]['type'][$name] != 'separate') {
4296
                    $sql .= ", " . $name;
4297
                }
4298
            }
4299
            $sql .= " FROM " . MAIN_DB_PREFIX . $table_element . "_extrafields";
4300
            $sql .= " WHERE fk_object = " . $rowid;
4301
4302
            //dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG);		// Too verbose
4303
            $resql = $this->db->query($sql);
4304
            if ($resql) {
4305
                $this->array_options = array();
4306
                $numrows = $this->db->num_rows($resql);
4307
                if ($numrows) {
4308
                    $tab = $this->db->fetch_array($resql);
4309
4310
                    foreach ($tab as $key => $value) {
4311
                        // 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)
4312
                        if ($key != 'rowid' && $key != 'tms' && $key != 'fk_member' && !is_int($key)) {
4313
                            // we can add this attribute to object
4314
                            if (!empty($extrafields) && in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date', 'datetime'))) {
4315
                                //var_dump($extrafields->attributes[$this->table_element]['type'][$key]);
4316
                                $this->array_options["options_" . $key] = $this->db->jdate($value);
4317
                            } else {
4318
                                $this->array_options["options_" . $key] = $value;
4319
                            }
4320
4321
                            //var_dump('key '.$key.' '.$value.' type='.$extrafields->attributes[$this->table_element]['type'][$key].' '.$this->array_options["options_".$key]);
4322
                        }
4323
                    }
4324
                }
4325
4326
                $this->db->free($resql);
4327
4328
                if ($numrows) return $numrows;
4329
                else return 0;
4330
            } else {
4331
                dol_print_error($this->db);
4332
                return -1;
4333
            }
4334
        }
4335
        return 0;
4336
    }
4337
4338
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4339
4340
    /**
4341
     *    Delete all extra fields values for the current object.
4342
     *
4343
     * @return    int        <0 if KO, >0 if OK
4344
     */
4345
    function deleteExtraFields()
4346
    {
4347
        $this->db->begin();
4348
4349
        $table_element = $this->table_element;
4350
        if ($table_element == 'categorie') $table_element = 'categories'; // For compatibility
4351
4352
        $sql_del = "DELETE FROM " . MAIN_DB_PREFIX . $table_element . "_extrafields WHERE fk_object = " . $this->id;
4353
        dol_syslog(get_class($this) . "::deleteExtraFields delete", LOG_DEBUG);
4354
        $resql = $this->db->query($sql_del);
4355
        if (!$resql) {
4356
            $this->error = $this->db->lasterror();
4357
            $this->db->rollback();
4358
            return -1;
4359
        } else {
4360
            $this->db->commit();
4361
            return 1;
4362
        }
4363
    }
4364
4365
    /**
4366
     *    Update an extra field value for the current object.
4367
     *  Data to describe values to update are stored into $this->array_options=array('options_codeforfield1'=>'valueforfield1', 'options_codeforfield2'=>'valueforfield2', ...)
4368
     *
4369
     * @param string $key Key of the extrafield (without starting 'options_')
4370
     * @param string $trigger If defined, call also the trigger (for example COMPANY_MODIFY)
4371
     * @param User $userused Object user
4372
     * @return int                        -1=error, O=did nothing, 1=OK
4373
     * @see setValueFrom, insertExtraFields
4374
     */
4375
    function updateExtraField($key, $trigger = null, $userused = null)
4376
    {
4377
        global $conf, $langs, $user;
4378
4379
        if (empty($userused)) $userused = $user;
4380
4381
        $error = 0;
4382
4383
        if (!empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) return 0;    // For avoid conflicts if trigger used
4384
4385
        if (!empty($this->array_options) && isset($this->array_options["options_" . $key])) {
4386
            // Check parameters
4387
            $langs->load('admin');
4388
            require_once DOL_DOCUMENT_ROOT . '/core/class/extrafields.class.php';
4389
            $extrafields = new ExtraFields($this->db);
4390
            $target_extrafields = $extrafields->fetch_name_optionals_label($this->table_element);
4391
4392
            $value = $this->array_options["options_" . $key];
4393
4394
            $attributeType = $extrafields->attributes[$this->table_element]['type'][$key];
4395
            $attributeLabel = $extrafields->attributes[$this->table_element]['label'][$key];
4396
            $attributeParam = $extrafields->attributes[$this->table_element]['param'][$key];
4397
            $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$key];
4398
4399
            //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
4400
            //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
4401
4402
            switch ($attributeType) {
4403
                case 'int':
4404
                    if (!is_numeric($value) && $value != '') {
4405
                        $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
4406
                        return -1;
4407
                    } elseif ($value == '') {
4408
                        $this->array_options["options_" . $key] = null;
4409
                    }
4410
                    break;
4411
                case 'double':
4412
                    $value = price2num($value);
4413
                    if (!is_numeric($value) && $value != '') {
4414
                        dol_syslog($langs->trans("ExtraFieldHasWrongValue") . " sur " . $attributeLabel . "(" . $value . "is not '" . $attributeType . "')", LOG_DEBUG);
4415
                        $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
4416
                        return -1;
4417
                    } elseif ($value == '') {
4418
                        $this->array_options["options_" . $key] = null;
4419
                    }
4420
                    //dol_syslog("double value"." sur ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
4421
                    $this->array_options["options_" . $key] = $value;
4422
                    break;
4423
                /*case 'select':	// Not required, we chosed value='0' for undefined values
4424
             		if ($value=='-1')
4425
             		{
4426
             			$this->array_options[$key] = null;
4427
             		}
4428
             		break;*/
4429
                case 'price':
4430
                    $this->array_options["options_" . $key] = price2num($this->array_options["options_" . $key]);
4431
                    break;
4432
                case 'date':
4433
                    $this->array_options["options_" . $key] = $this->db->idate($this->array_options["options_" . $key]);
4434
                    break;
4435
                case 'datetime':
4436
                    $this->array_options["options_" . $key] = $this->db->idate($this->array_options["options_" . $key]);
4437
                    break;
4438
                case 'link':
4439
                    $param_list = array_keys($attributeParam['options']);
4440
                    // 0 : ObjectName
4441
                    // 1 : classPath
4442
                    $InfoFieldList = explode(":", $param_list[0]);
4443
                    dol_include_once($InfoFieldList[1]);
4444
                    if ($value) {
4445
                        $object = new $InfoFieldList[0]($this->db);
4446
                        $object->fetch(0, $value);
4447
                        $this->array_options["options_" . $key] = $object->id;
4448
                    }
4449
                    break;
4450
            }
4451
4452
            $this->db->begin();
4453
            $sql = "UPDATE " . MAIN_DB_PREFIX . $this->table_element . "_extrafields SET " . $key . "='" . $this->db->escape($this->array_options["options_" . $key]) . "'";
4454
            $sql .= " WHERE fk_object = " . $this->id;
4455
            $resql = $this->db->query($sql);
4456
            if (!$resql) {
4457
                $error++;
4458
                $this->error = $this->db->lasterror();
4459
            }
4460
4461
            if (!$error && $trigger) {
4462
                // Call trigger
4463
                $this->context = array('extrafieldupdate' => 1);
4464
                $result = $this->call_trigger($trigger, $userused);
4465
                if ($result < 0) $error++;
4466
                // End call trigger
4467
            }
4468
4469
            if ($error) {
4470
                dol_syslog(get_class($this) . "::" . __METHOD__ . $this->error, LOG_ERR);
4471
                $this->db->rollback();
4472
                return -1;
4473
            } else {
4474
                $this->db->commit();
4475
                return 1;
4476
            }
4477
        } else return 0;
4478
    }
4479
4480
    /**
4481
     * Return HTML string to put an input field into a page
4482
     * Code very similar with showInputField of extra fields
4483
     *
4484
     * @param array $val Array of properties for field to show
4485
     * @param string $key Key of attribute
4486
     * @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)
4487
     * @param string $moreparam To add more parameters on html input tag
4488
     * @param string $keysuffix Prefix string to add into name and id of field (can be used to avoid duplicate names)
4489
     * @param string $keyprefix Suffix string to add into name and id of field (can be used to avoid duplicate names)
4490
     * @param string|int $morecss Value for css to define style/length of field. May also be a numeric.
4491
     * @return string
4492
     */
4493
    function showInputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = 0)
4494
    {
4495
        global $conf, $langs, $form;
4496
4497
        if (!is_object($form)) {
4498
            require_once DOL_DOCUMENT_ROOT . '/core/class/html.form.class.php';
4499
            $form = new Form($this->db);
4500
        }
4501
4502
        $val = $this->fields[$key];
4503
4504
        $out = '';
4505
        $type = '';
4506
        $param = array();
4507
        $param['options'] = array();
4508
        $size = $this->fields[$key]['size'];
4509
        // Because we work on extrafields
4510
        if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
4511
            $param['options'] = array($reg[1] . ':' . $reg[2] => 'N');
4512
            $type = 'link';
4513
        } elseif (preg_match('/^link:(.*):(.*)/i', $val['type'], $reg)) {
4514
            $param['options'] = array($reg[1] . ':' . $reg[2] => 'N');
4515
            $type = 'link';
4516
        } elseif (preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
4517
            $param['options'] = array($reg[1] . ':' . $reg[2] . ':' . $reg[3] . ':' . $reg[4] => 'N');
4518
            $type = 'sellist';
4519
        } elseif (preg_match('/varchar\((\d+)\)/', $val['type'], $reg)) {
4520
            $param['options'] = array();
4521
            $type = 'varchar';
4522
            $size = $reg[1];
4523
        } elseif (preg_match('/varchar/', $val['type'])) {
4524
            $param['options'] = array();
4525
            $type = 'varchar';
4526
        } elseif (is_array($this->fields[$key]['arrayofkeyval'])) {
4527
            $param['options'] = $this->fields[$key]['arrayofkeyval'];
4528
            $type = 'select';
4529
        } else {
4530
            $param['options'] = array();
4531
            $type = $this->fields[$key]['type'];
4532
        }
4533
4534
        $label = $this->fields[$key]['label'];
4535
        //$elementtype=$this->fields[$key]['elementtype'];	// Seems not used
4536
        $default = $this->fields[$key]['default'];
4537
        $computed = $this->fields[$key]['computed'];
4538
        $unique = $this->fields[$key]['unique'];
4539
        $required = $this->fields[$key]['required'];
4540
4541
        $langfile = $this->fields[$key]['langfile'];
4542
        $list = $this->fields[$key]['list'];
4543
        $hidden = abs($this->fields[$key]['visible']) != 1 ? 1 : 0;
4544
4545
        $objectid = $this->id;
4546
4547
4548
        if ($computed) {
4549
            if (!preg_match('/^search_/', $keyprefix)) return '<span class="opacitymedium">' . $langs->trans("AutomaticallyCalculated") . '</span>';
4550
            else return '';
4551
        }
4552
4553
4554
        // Use in priority showsize from parameters, then $val['css'] then autodefine
4555
        if (empty($morecss) && !empty($val['css'])) {
4556
            $showsize = $val['css'];
4557
        }
4558
        if (empty($morecss)) {
4559
            if ($type == 'date') {
4560
                $morecss = 'minwidth100imp';
4561
            } elseif ($type == 'datetime') {
4562
                $morecss = 'minwidth200imp';
4563
            } elseif (in_array($type, array('int', 'integer', 'price')) || preg_match('/^double(\([0-9],[0-9]\)){0,1}/', $type)) {
4564
                $morecss = 'maxwidth75';
4565
            } elseif ($type == 'url') {
4566
                $morecss = 'minwidth400';
4567
            } elseif ($type == 'boolean') {
4568
                $morecss = '';
4569
            } else {
4570
                if (round($size) < 12) {
4571
                    $morecss = 'minwidth100';
4572
                } else if (round($size) <= 48) {
4573
                    $morecss = 'minwidth200';
4574
                } else {
4575
                    $morecss = 'minwidth400';
4576
                }
4577
            }
4578
        }
4579
4580
        if (in_array($type, array('date', 'datetime'))) {
4581
            $tmp = explode(',', $size);
4582
            $newsize = $tmp[0];
4583
4584
            $showtime = in_array($type, array('datetime')) ? 1 : 0;
4585
4586
            // Do not show current date when field not required (see selectDate() method)
4587
            if (!$required && $value == '') $value = '-1';
4588
4589
            // TODO Must also support $moreparam
4590
            $out = $form->selectDate($value, $keyprefix . $key . $keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1);
4591
        } elseif (in_array($type, array('int', 'integer'))) {
4592
            $tmp = explode(',', $size);
4593
            $newsize = $tmp[0];
4594
            $out = '<input type="text" class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" maxlength="' . $newsize . '" value="' . dol_escape_htmltag($value) . '"' . ($moreparam ? $moreparam : '') . '>';
4595
        } elseif (preg_match('/varchar/', $type)) {
4596
            $out = '<input type="text" class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" maxlength="' . $size . '" value="' . dol_escape_htmltag($value) . '"' . ($moreparam ? $moreparam : '') . '>';
4597
        } elseif (in_array($type, array('mail', 'phone', 'url'))) {
4598
            $out = '<input type="text" class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" value="' . dol_escape_htmltag($value) . '" ' . ($moreparam ? $moreparam : '') . '>';
4599
        } elseif ($type == 'text') {
4600
            if (!preg_match('/search_/', $keyprefix))        // If keyprefix is search_ or search_options_, we must just use a simple text field
4601
            {
4602
                require_once DOL_DOCUMENT_ROOT . '/core/class/doleditor.class.php';
4603
                $doleditor = new DolEditor($keyprefix . $key . $keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, false, ROWS_5, '90%');
4604
                $out = $doleditor->Create(1);
4605
            } else {
4606
                $out = '<input type="text" class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" value="' . dol_escape_htmltag($value) . '" ' . ($moreparam ? $moreparam : '') . '>';
4607
            }
4608
        } elseif ($type == 'html') {
4609
            if (!preg_match('/search_/', $keyprefix))        // If keyprefix is search_ or search_options_, we must just use a simple text field
4610
            {
4611
                require_once DOL_DOCUMENT_ROOT . '/core/class/doleditor.class.php';
4612
                $doleditor = new DolEditor($keyprefix . $key . $keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, !empty($conf->fckeditor->enabled) && $conf->global->FCKEDITOR_ENABLE_SOCIETE, ROWS_5, '90%');
4613
                $out = $doleditor->Create(1);
4614
            } else {
4615
                $out = '<input type="text" class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" value="' . dol_escape_htmltag($value) . '" ' . ($moreparam ? $moreparam : '') . '>';
4616
            }
4617
        } elseif ($type == 'boolean') {
4618
            $checked = '';
4619
            if (!empty($value)) {
4620
                $checked = ' checked value="1" ';
4621
            } else {
4622
                $checked = ' value="1" ';
4623
            }
4624
            $out = '<input type="checkbox" class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" ' . $checked . ' ' . ($moreparam ? $moreparam : '') . '>';
4625
        } elseif ($type == 'price') {
4626
            if (!empty($value)) {        // $value in memory is a php numeric, we format it into user number format.
4627
                $value = price($value);
4628
            }
4629
            $out = '<input type="text" class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" value="' . $value . '" ' . ($moreparam ? $moreparam : '') . '> ' . $langs->getCurrencySymbol($conf->currency);
4630
        } elseif (preg_match('/^double(\([0-9],[0-9]\)){0,1}/', $type)) {
4631
            if (!empty($value)) {        // $value in memory is a php numeric, we format it into user number format.
4632
                $value = price($value);
4633
            }
4634
            $out = '<input type="text" class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" value="' . $value . '" ' . ($moreparam ? $moreparam : '') . '> ';
4635
        } elseif ($type == 'select') {
4636
            $out = '';
4637
            if (!empty($conf->use_javascript_ajax) && !empty($conf->global->MAIN_EXTRAFIELDS_USE_SELECT2)) {
4638
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
4639
                $out .= ajax_combobox($keyprefix . $key . $keysuffix, array(), 0);
4640
            }
4641
4642
            $out .= '<select class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" ' . ($moreparam ? $moreparam : '') . '>';
4643
            if ((!isset($this->fields[$key]['default'])) || ($this->fields[$key]['notnull'] != 1)) $out .= '<option value="0">&nbsp;</option>';
4644
            foreach ($param['options'] as $key => $val) {
4645
                if ((string)$key == '') continue;
4646
                list($val, $parent) = explode('|', $val);
4647
                $out .= '<option value="' . $key . '"';
4648
                $out .= (((string)$value == (string)$key) ? ' selected' : '');
4649
                $out .= (!empty($parent) ? ' parent="' . $parent . '"' : '');
4650
                $out .= '>' . $val . '</option>';
4651
            }
4652
            $out .= '</select>';
4653
        } elseif ($type == 'sellist') {
4654
            $out = '';
4655
            if (!empty($conf->use_javascript_ajax) && !empty($conf->global->MAIN_EXTRAFIELDS_USE_SELECT2)) {
4656
                include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
4657
                $out .= ajax_combobox($keyprefix . $key . $keysuffix, array(), 0);
4658
            }
4659
4660
            $out .= '<select class="flat ' . $morecss . ' maxwidthonsmartphone" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" ' . ($moreparam ? $moreparam : '') . '>';
4661
            if (is_array($param['options'])) {
4662
                $param_list = array_keys($param['options']);
4663
                $InfoFieldList = explode(":", $param_list[0]);
4664
                $parentName = '';
4665
                $parentField = '';
4666
                // 0 : tableName
4667
                // 1 : label field name
4668
                // 2 : key fields name (if differ of rowid)
4669
                // 3 : key field parent (for dependent lists)
4670
                // 4 : where clause filter on column or table extrafield, syntax field='value' or extra.field=value
4671
                $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2] . ' as rowid');
4672
4673
4674
                if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
4675
                    if (strpos($InfoFieldList[4], 'extra.') !== false) {
4676
                        $keyList = 'main.' . $InfoFieldList[2] . ' as rowid';
4677
                    } else {
4678
                        $keyList = $InfoFieldList[2] . ' as rowid';
4679
                    }
4680
                }
4681
                if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
4682
                    list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
4683
                    $keyList .= ', ' . $parentField;
4684
                }
4685
4686
                $fields_label = explode('|', $InfoFieldList[1]);
4687
                if (is_array($fields_label)) {
4688
                    $keyList .= ', ';
4689
                    $keyList .= implode(', ', $fields_label);
4690
                }
4691
4692
                $sqlwhere = '';
4693
                $sql = 'SELECT ' . $keyList;
4694
                $sql .= ' FROM ' . MAIN_DB_PREFIX . $InfoFieldList[0];
4695
                if (!empty($InfoFieldList[4])) {
4696
                    // can use SELECT request
4697
                    if (strpos($InfoFieldList[4], '$SEL$') !== false) {
4698
                        $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
4699
                    }
4700
4701
                    // current object id can be use into filter
4702
                    if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
4703
                        $InfoFieldList[4] = str_replace('$ID$', $objectid, $InfoFieldList[4]);
4704
                    } else {
4705
                        $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
4706
                    }
4707
                    //We have to join on extrafield table
4708
                    if (strpos($InfoFieldList[4], 'extra') !== false) {
4709
                        $sql .= ' as main, ' . MAIN_DB_PREFIX . $InfoFieldList[0] . '_extrafields as extra';
4710
                        $sqlwhere .= ' WHERE extra.fk_object=main.' . $InfoFieldList[2] . ' AND ' . $InfoFieldList[4];
4711
                    } else {
4712
                        $sqlwhere .= ' WHERE ' . $InfoFieldList[4];
4713
                    }
4714
                } else {
4715
                    $sqlwhere .= ' WHERE 1=1';
4716
                }
4717
                // Some tables may have field, some other not. For the moment we disable it.
4718
                if (in_array($InfoFieldList[0], array('tablewithentity'))) {
4719
                    $sqlwhere .= ' AND entity = ' . $conf->entity;
4720
                }
4721
                $sql .= $sqlwhere;
4722
                //print $sql;
4723
4724
                $sql .= ' ORDER BY ' . implode(', ', $fields_label);
4725
4726
                dol_syslog(get_class($this) . '::showInputField type=sellist', LOG_DEBUG);
4727
                $resql = $this->db->query($sql);
4728
                if ($resql) {
4729
                    $out .= '<option value="0">&nbsp;</option>';
4730
                    $num = $this->db->num_rows($resql);
4731
                    $i = 0;
4732
                    while ($i < $num) {
4733
                        $labeltoshow = '';
4734
                        $obj = $this->db->fetch_object($resql);
4735
4736
                        // Several field into label (eq table:code|libelle:rowid)
4737
                        $notrans = false;
4738
                        $fields_label = explode('|', $InfoFieldList[1]);
4739
                        if (is_array($fields_label)) {
4740
                            $notrans = true;
4741
                            foreach ($fields_label as $field_toshow) {
4742
                                $labeltoshow .= $obj->$field_toshow . ' ';
4743
                            }
4744
                        } else {
4745
                            $labeltoshow = $obj->{$InfoFieldList[1]};
4746
                        }
4747
                        $labeltoshow = dol_trunc($labeltoshow, 45);
4748
4749
                        if ($value == $obj->rowid) {
4750
                            foreach ($fields_label as $field_toshow) {
4751
                                $translabel = $langs->trans($obj->$field_toshow);
4752
                                if ($translabel != $obj->$field_toshow) {
4753
                                    $labeltoshow = dol_trunc($translabel, 18) . ' ';
4754
                                } else {
4755
                                    $labeltoshow = dol_trunc($obj->$field_toshow, 18) . ' ';
4756
                                }
4757
                            }
4758
                            $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
4759
                        } else {
4760
                            if (!$notrans) {
4761
                                $translabel = $langs->trans($obj->{$InfoFieldList[1]});
4762
                                if ($translabel != $obj->{$InfoFieldList[1]}) {
4763
                                    $labeltoshow = dol_trunc($translabel, 18);
4764
                                } else {
4765
                                    $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]}, 18);
4766
                                }
4767
                            }
4768
                            if (empty($labeltoshow)) $labeltoshow = '(not defined)';
4769
                            if ($value == $obj->rowid) {
4770
                                $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
4771
                            }
4772
4773
                            if (!empty($InfoFieldList[3]) && $parentField) {
4774
                                $parent = $parentName . ':' . $obj->{$parentField};
4775
                            }
4776
4777
                            $out .= '<option value="' . $obj->rowid . '"';
4778
                            $out .= ($value == $obj->rowid ? ' selected' : '');
4779
                            $out .= (!empty($parent) ? ' parent="' . $parent . '"' : '');
4780
                            $out .= '>' . $labeltoshow . '</option>';
4781
                        }
4782
4783
                        $i++;
4784
                    }
4785
                    $this->db->free($resql);
4786
                } else {
4787
                    print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
4788
                }
4789
            }
4790
            $out .= '</select>';
4791
        } elseif ($type == 'checkbox') {
4792
            $value_arr = explode(',', $value);
4793
            $out = $form->multiselectarray($keyprefix . $key . $keysuffix, (empty($param['options']) ? null : $param['options']), $value_arr, '', 0, '', 0, '100%');
4794
        } elseif ($type == 'radio') {
4795
            $out = '';
4796
            foreach ($param['options'] as $keyopt => $val) {
4797
                $out .= '<input class="flat ' . $morecss . '" type="radio" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" ' . ($moreparam ? $moreparam : '');
4798
                $out .= ' value="' . $keyopt . '"';
4799
                $out .= ' id="' . $keyprefix . $key . $keysuffix . '_' . $keyopt . '"';
4800
                $out .= ($value == $keyopt ? 'checked' : '');
4801
                $out .= '/><label for="' . $keyprefix . $key . $keysuffix . '_' . $keyopt . '">' . $val . '</label><br>';
4802
            }
4803
        } elseif ($type == 'chkbxlst') {
4804
            if (is_array($value)) {
4805
                $value_arr = $value;
4806
            } else {
4807
                $value_arr = explode(',', $value);
4808
            }
4809
4810
            if (is_array($param['options'])) {
4811
                $param_list = array_keys($param['options']);
4812
                $InfoFieldList = explode(":", $param_list[0]);
4813
                $parentName = '';
4814
                $parentField = '';
4815
                // 0 : tableName
4816
                // 1 : label field name
4817
                // 2 : key fields name (if differ of rowid)
4818
                // 3 : key field parent (for dependent lists)
4819
                // 4 : where clause filter on column or table extrafield, syntax field='value' or extra.field=value
4820
                $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2] . ' as rowid');
4821
4822
                if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
4823
                    list ($parentName, $parentField) = explode('|', $InfoFieldList[3]);
4824
                    $keyList .= ', ' . $parentField;
4825
                }
4826
                if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
4827
                    if (strpos($InfoFieldList[4], 'extra.') !== false) {
4828
                        $keyList = 'main.' . $InfoFieldList[2] . ' as rowid';
4829
                    } else {
4830
                        $keyList = $InfoFieldList[2] . ' as rowid';
4831
                    }
4832
                }
4833
4834
                $fields_label = explode('|', $InfoFieldList[1]);
4835
                if (is_array($fields_label)) {
4836
                    $keyList .= ', ';
4837
                    $keyList .= implode(', ', $fields_label);
4838
                }
4839
4840
                $sqlwhere = '';
4841
                $sql = 'SELECT ' . $keyList;
4842
                $sql .= ' FROM ' . MAIN_DB_PREFIX . $InfoFieldList[0];
4843
                if (!empty($InfoFieldList[4])) {
4844
4845
                    // can use SELECT request
4846
                    if (strpos($InfoFieldList[4], '$SEL$') !== false) {
4847
                        $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
4848
                    }
4849
4850
                    // current object id can be use into filter
4851
                    if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
4852
                        $InfoFieldList[4] = str_replace('$ID$', $objectid, $InfoFieldList[4]);
4853
                    } else {
4854
                        $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
4855
                    }
4856
4857
                    // We have to join on extrafield table
4858
                    if (strpos($InfoFieldList[4], 'extra') !== false) {
4859
                        $sql .= ' as main, ' . MAIN_DB_PREFIX . $InfoFieldList[0] . '_extrafields as extra';
4860
                        $sqlwhere .= ' WHERE extra.fk_object=main.' . $InfoFieldList[2] . ' AND ' . $InfoFieldList[4];
4861
                    } else {
4862
                        $sqlwhere .= ' WHERE ' . $InfoFieldList[4];
4863
                    }
4864
                } else {
4865
                    $sqlwhere .= ' WHERE 1=1';
4866
                }
4867
                // Some tables may have field, some other not. For the moment we disable it.
4868
                if (in_array($InfoFieldList[0], array('tablewithentity'))) {
4869
                    $sqlwhere .= ' AND entity = ' . $conf->entity;
4870
                }
4871
                // $sql.=preg_replace('/^ AND /','',$sqlwhere);
4872
                // print $sql;
4873
4874
                $sql .= $sqlwhere;
4875
                dol_syslog(get_class($this) . '::showInputField type=chkbxlst', LOG_DEBUG);
4876
                $resql = $this->db->query($sql);
4877
                if ($resql) {
4878
                    $num = $this->db->num_rows($resql);
4879
                    $i = 0;
4880
4881
                    $data = array();
4882
4883
                    while ($i < $num) {
4884
                        $labeltoshow = '';
4885
                        $obj = $this->db->fetch_object($resql);
4886
4887
                        $notrans = false;
4888
                        // Several field into label (eq table:code|libelle:rowid)
4889
                        $fields_label = explode('|', $InfoFieldList[1]);
4890
                        if (is_array($fields_label)) {
4891
                            $notrans = true;
4892
                            foreach ($fields_label as $field_toshow) {
4893
                                $labeltoshow .= $obj->$field_toshow . ' ';
4894
                            }
4895
                        } else {
4896
                            $labeltoshow = $obj->{$InfoFieldList[1]};
4897
                        }
4898
                        $labeltoshow = dol_trunc($labeltoshow, 45);
4899
4900
                        if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
4901
                            foreach ($fields_label as $field_toshow) {
4902
                                $translabel = $langs->trans($obj->$field_toshow);
4903
                                if ($translabel != $obj->$field_toshow) {
4904
                                    $labeltoshow = dol_trunc($translabel, 18) . ' ';
4905
                                } else {
4906
                                    $labeltoshow = dol_trunc($obj->$field_toshow, 18) . ' ';
4907
                                }
4908
                            }
4909
4910
                            $data[$obj->rowid] = $labeltoshow;
4911
                        } else {
4912
                            if (!$notrans) {
4913
                                $translabel = $langs->trans($obj->{$InfoFieldList[1]});
4914
                                if ($translabel != $obj->{$InfoFieldList[1]}) {
4915
                                    $labeltoshow = dol_trunc($translabel, 18);
4916
                                } else {
4917
                                    $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]}, 18);
4918
                                }
4919
                            }
4920
                            if (empty($labeltoshow))
4921
                                $labeltoshow = '(not defined)';
4922
4923
                            if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
4924
                                $data[$obj->rowid] = $labeltoshow;
4925
                            }
4926
4927
                            if (!empty($InfoFieldList[3]) && $parentField) {
4928
                                $parent = $parentName . ':' . $obj->{$parentField};
4929
                            }
4930
4931
                            $data[$obj->rowid] = $labeltoshow;
4932
                        }
4933
4934
                        $i++;
4935
                    }
4936
                    $this->db->free($resql);
4937
4938
                    $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, '', 0, '', 0, '100%');
4939
                } else {
4940
                    print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
4941
                }
4942
            }
4943
        } elseif ($type == 'link') {
4944
            $param_list = array_keys($param['options']);                // $param_list='ObjectName:classPath'
4945
            $showempty = (($required && $default != '') ? 0 : 1);
4946
            $out = $form->selectForForms($param_list[0], $keyprefix . $key . $keysuffix, $value, $showempty);
4947
            if ($conf->global->MAIN_FEATURES_LEVEL >= 2) {
4948
                list($class, $classfile) = explode(':', $param_list[0]);
4949
                if (file_exists(dol_buildpath(dirname(dirname($classfile)) . '/card.php'))) $url_path = dol_buildpath(dirname(dirname($classfile)) . '/card.php', 1);
4950
                else $url_path = dol_buildpath(dirname(dirname($classfile)) . '/' . $class . '_card.php', 1);
4951
                $out .= '<a class="butActionNew" href="' . $url_path . '?action=create&backtopage=' . $_SERVER['PHP_SELF'] . '"><span class="fa fa-plus-circle valignmiddle"></span></a>';
4952
                // TODO Add Javascript code to add input fields contents to new elements urls
4953
            }
4954
        } elseif ($type == 'password') {
4955
            // If prefix is 'search_', field is used as a filter, we use a common text field.
4956
            $out = '<input type="' . ($keyprefix == 'search_' ? 'text' : 'password') . '" class="flat ' . $morecss . '" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '" value="' . $value . '" ' . ($moreparam ? $moreparam : '') . '>';
4957
        } elseif ($type == 'array') {
4958
            $newval = $val;
4959
            $newval['type'] = 'varchar(256)';
4960
4961
            $out = '';
4962
4963
            $inputs = array();
4964
            if (!empty($value)) {
4965
                foreach ($value as $option) {
4966
                    $out .= '<span><a class="' . dol_escape_htmltag($keyprefix . $key . $keysuffix) . '_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
4967
                    $out .= $this->showInputField($newval, $keyprefix . $key . $keysuffix . '[]', $option, $moreparam, '', '', $showsize) . '<br></span>';
4968
                }
4969
            }
4970
4971
            $out .= '<a id="' . dol_escape_htmltag($keyprefix . $key . $keysuffix) . '_add" href="javascript:;"><span class="fa fa-plus-circle valignmiddle"></span></a>';
4972
4973
            $newInput = '<span><a class="' . dol_escape_htmltag($keyprefix . $key . $keysuffix) . '_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
4974
            $newInput .= $this->showInputField($newval, $keyprefix . $key . $keysuffix . '[]', '', $moreparam, '', '', $showsize) . '<br></span>';
4975
4976
            if (!empty($conf->use_javascript_ajax)) {
4977
                $out .= '
4978
					<script type="text/javascript">
4979
					$(document).ready(function() {
4980
						$("a#' . dol_escape_js($keyprefix . $key . $keysuffix) . '_add").click(function() {
4981
							$("' . dol_escape_js($newInput) . '").insertBefore(this);
4982
						});
4983
4984
						$(document).on("click", "a.' . dol_escape_js($keyprefix . $key . $keysuffix) . '_del", function() {
4985
							$(this).parent().remove();
4986
						});
4987
					});
4988
					</script>';
4989
            }
4990
        }
4991
        if (!empty($hidden)) {
4992
            $out = '<input type="hidden" value="' . $value . '" name="' . $keyprefix . $key . $keysuffix . '" id="' . $keyprefix . $key . $keysuffix . '"/>';
4993
        }
4994
        /* Add comments
4995
		 if ($type == 'date') $out.=' (YYYY-MM-DD)';
4996
		 elseif ($type == 'datetime') $out.=' (YYYY-MM-DD HH:MM:SS)';
4997
		 */
4998
        return $out;
4999
    }
5000
5001
    /**
5002
     * Return HTML string to show a field into a page
5003
     * Code very similar with showOutputField of extra fields
5004
     *
5005
     * @param array $val Array of properties of field to show
5006
     * @param string $key Key of attribute
5007
     * @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)
5008
     * @param string $moreparam To add more parametes on html input tag
5009
     * @param string $keysuffix Prefix string to add into name and id of field (can be used to avoid duplicate names)
5010
     * @param string $keyprefix Suffix string to add into name and id of field (can be used to avoid duplicate names)
5011
     * @param mixed $showsize Value for css to define size. May also be a numeric.
5012
     * @return string
5013
     */
5014
    function showOutputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $showsize = 0)
5015
    {
5016
        global $conf, $langs, $form;
5017
5018
        if (!is_object($form)) {
5019
            require_once DOL_DOCUMENT_ROOT . '/core/class/html.form.class.php';
5020
            $form = new Form($this->db);
5021
        }
5022
5023
        $objectid = $this->id;
5024
        $label = $val['label'];
5025
        $type = $val['type'];
5026
        $size = $val['css'];
5027
5028
        // Convert var to be able to share same code than showOutputField of extrafields
5029
        if (preg_match('/varchar\((\d+)\)/', $type, $reg)) {
5030
            $type = 'varchar';        // convert varchar(xx) int varchar
5031
            $size = $reg[1];
5032
        } elseif (preg_match('/varchar/', $type)) $type = 'varchar';        // convert varchar(xx) int varchar
5033
        if (is_array($val['arrayofkeyval'])) $type = 'select';
5034
        if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) $type = 'link';
5035
5036
        $default = $val['default'];
5037
        $computed = $val['computed'];
5038
        $unique = $val['unique'];
5039
        $required = $val['required'];
5040
        $param = $val['param'];
5041
        if (is_array($val['arrayofkeyval'])) $param['options'] = $val['arrayofkeyval'];
5042
        if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
5043
            $type = 'link';
5044
            $param['options'] = array($reg[1] . ':' . $reg[2] => $reg[1] . ':' . $reg[2]);
5045
        }
5046
        $langfile = $val['langfile'];
5047
        $list = $val['list'];
5048
        $help = $val['help'];
5049
        $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)
5050
5051
        if ($hidden) return '';
5052
5053
        // If field is a computed field, value must become result of compute
5054
        if ($computed) {
5055
            // Make the eval of compute string
5056
            //var_dump($computed);
5057
            $value = dol_eval($computed, 1, 0);
5058
        }
5059
5060
        if (empty($showsize)) {
5061
            if ($type == 'date') {
5062
                //$showsize=10;
5063
                $showsize = 'minwidth100imp';
5064
            } elseif ($type == 'datetime') {
5065
                //$showsize=19;
5066
                $showsize = 'minwidth200imp';
5067
            } elseif (in_array($type, array('int', 'double', 'price'))) {
5068
                //$showsize=10;
5069
                $showsize = 'maxwidth75';
5070
            } elseif ($type == 'url') {
5071
                $showsize = 'minwidth400';
5072
            } elseif ($type == 'boolean') {
5073
                $showsize = '';
5074
            } else {
5075
                if (round($size) < 12) {
5076
                    $showsize = 'minwidth100';
5077
                } else if (round($size) <= 48) {
5078
                    $showsize = 'minwidth200';
5079
                } else {
5080
                    //$showsize=48;
5081
                    $showsize = 'minwidth400';
5082
                }
5083
            }
5084
        }
5085
5086
        // Format output value differently according to properties of field
5087
        if ($key == 'ref' && method_exists($this, 'getNomUrl')) $value = $this->getNomUrl(1, '', 0, '', 1);
5088
        elseif ($key == 'status' && method_exists($this, 'getLibStatut')) $value = $this->getLibStatut(3);
5089
        elseif ($type == 'date') {
5090
            if (!empty($value)) {
5091
                $value = dol_print_date($value, 'day');
5092
            } else {
5093
                $value = '';
5094
            }
5095
        } elseif ($type == 'datetime') {
5096
            if (!empty($value)) {
5097
                $value = dol_print_date($value, 'dayhour');
5098
            } else {
5099
                $value = '';
5100
            }
5101
        } elseif ($type == 'double') {
5102
            if (!empty($value)) {
5103
                $value = price($value);
5104
            }
5105
        } elseif ($type == 'boolean') {
5106
            $checked = '';
5107
            if (!empty($value)) {
5108
                $checked = ' checked ';
5109
            }
5110
            $value = '<input type="checkbox" ' . $checked . ' ' . ($moreparam ? $moreparam : '') . ' readonly disabled>';
5111
        } elseif ($type == 'mail') {
5112
            $value = dol_print_email($value, 0, 0, 0, 64, 1, 1);
5113
        } elseif ($type == 'url') {
5114
            $value = dol_print_url($value, '_blank', 32, 1);
5115
        } elseif ($type == 'phone') {
5116
            $value = dol_print_phone($value, '', 0, 0, '', '&nbsp;', 1);
5117
        } elseif ($type == 'price') {
5118
            $value = price($value, 0, $langs, 0, 0, -1, $conf->currency);
5119
        } elseif ($type == 'select') {
5120
            $value = $param['options'][$value];
5121
        } elseif ($type == 'sellist') {
5122
            $param_list = array_keys($param['options']);
5123
            $InfoFieldList = explode(":", $param_list[0]);
5124
5125
            $selectkey = "rowid";
5126
            $keyList = 'rowid';
5127
5128
            if (count($InfoFieldList) >= 3) {
5129
                $selectkey = $InfoFieldList[2];
5130
                $keyList = $InfoFieldList[2] . ' as rowid';
5131
            }
5132
5133
            $fields_label = explode('|', $InfoFieldList[1]);
5134
            if (is_array($fields_label)) {
5135
                $keyList .= ', ';
5136
                $keyList .= implode(', ', $fields_label);
5137
            }
5138
5139
            $sql = 'SELECT ' . $keyList;
5140
            $sql .= ' FROM ' . MAIN_DB_PREFIX . $InfoFieldList[0];
5141
            if (strpos($InfoFieldList[4], 'extra') !== false) {
5142
                $sql .= ' as main';
5143
            }
5144
            if ($selectkey == 'rowid' && empty($value)) {
5145
                $sql .= " WHERE " . $selectkey . "=0";
5146
            } elseif ($selectkey == 'rowid') {
5147
                $sql .= " WHERE " . $selectkey . "=" . $this->db->escape($value);
5148
            } else {
5149
                $sql .= " WHERE " . $selectkey . "='" . $this->db->escape($value) . "'";
5150
            }
5151
5152
            //$sql.= ' AND entity = '.$conf->entity;
5153
5154
            dol_syslog(get_class($this) . ':showOutputField:$type=sellist', LOG_DEBUG);
5155
            $resql = $this->db->query($sql);
5156
            if ($resql) {
5157
                $value = '';    // value was used, so now we reste it to use it to build final output
5158
5159
                $obj = $this->db->fetch_object($resql);
5160
5161
                // Several field into label (eq table:code|libelle:rowid)
5162
                $fields_label = explode('|', $InfoFieldList[1]);
5163
5164
                if (is_array($fields_label) && count($fields_label) > 1) {
5165
                    foreach ($fields_label as $field_toshow) {
5166
                        $translabel = '';
5167
                        if (!empty($obj->$field_toshow)) {
5168
                            $translabel = $langs->trans($obj->$field_toshow);
5169
                        }
5170
                        if ($translabel != $field_toshow) {
5171
                            $value .= dol_trunc($translabel, 18) . ' ';
5172
                        } else {
5173
                            $value .= $obj->$field_toshow . ' ';
5174
                        }
5175
                    }
5176
                } else {
5177
                    $translabel = '';
5178
                    if (!empty($obj->{$InfoFieldList[1]})) {
5179
                        $translabel = $langs->trans($obj->{$InfoFieldList[1]});
5180
                    }
5181
                    if ($translabel != $obj->{$InfoFieldList[1]}) {
5182
                        $value = dol_trunc($translabel, 18);
5183
                    } else {
5184
                        $value = $obj->{$InfoFieldList[1]};
5185
                    }
5186
                }
5187
            } else dol_syslog(get_class($this) . '::showOutputField error ' . $this->db->lasterror(), LOG_WARNING);
5188
        } elseif ($type == 'radio') {
5189
            $value = $param['options'][$value];
5190
        } elseif ($type == 'checkbox') {
5191
            $value_arr = explode(',', $value);
5192
            $value = '';
5193
            if (is_array($value_arr) && count($value_arr) > 0) {
5194
                foreach ($value_arr as $keyval => $valueval) {
5195
                    $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #aaa">' . $param['options'][$valueval] . '</li>';
5196
                }
5197
                $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
5198
            }
5199
        } elseif ($type == 'chkbxlst') {
5200
            $value_arr = explode(',', $value);
5201
5202
            $param_list = array_keys($param['options']);
5203
            $InfoFieldList = explode(":", $param_list[0]);
5204
5205
            $selectkey = "rowid";
5206
            $keyList = 'rowid';
5207
5208
            if (count($InfoFieldList) >= 3) {
5209
                $selectkey = $InfoFieldList[2];
5210
                $keyList = $InfoFieldList[2] . ' as rowid';
5211
            }
5212
5213
            $fields_label = explode('|', $InfoFieldList[1]);
5214
            if (is_array($fields_label)) {
5215
                $keyList .= ', ';
5216
                $keyList .= implode(', ', $fields_label);
5217
            }
5218
5219
            $sql = 'SELECT ' . $keyList;
5220
            $sql .= ' FROM ' . MAIN_DB_PREFIX . $InfoFieldList[0];
5221
            if (strpos($InfoFieldList[4], 'extra') !== false) {
5222
                $sql .= ' as main';
5223
            }
5224
            // $sql.= " WHERE ".$selectkey."='".$this->db->escape($value)."'";
5225
            // $sql.= ' AND entity = '.$conf->entity;
5226
5227
            dol_syslog(get_class($this) . ':showOutputField:$type=chkbxlst', LOG_DEBUG);
5228
            $resql = $this->db->query($sql);
5229
            if ($resql) {
5230
                $value = ''; // value was used, so now we reste it to use it to build final output
5231
                $toprint = array();
5232
                while ($obj = $this->db->fetch_object($resql)) {
5233
5234
                    // Several field into label (eq table:code|libelle:rowid)
5235
                    $fields_label = explode('|', $InfoFieldList[1]);
5236
                    if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
5237
                        if (is_array($fields_label) && count($fields_label) > 1) {
5238
                            foreach ($fields_label as $field_toshow) {
5239
                                $translabel = '';
5240
                                if (!empty($obj->$field_toshow)) {
5241
                                    $translabel = $langs->trans($obj->$field_toshow);
5242
                                }
5243
                                if ($translabel != $field_toshow) {
5244
                                    $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #aaa">' . dol_trunc($translabel, 18) . '</li>';
5245
                                } else {
5246
                                    $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #aaa">' . $obj->$field_toshow . '</li>';
5247
                                }
5248
                            }
5249
                        } else {
5250
                            $translabel = '';
5251
                            if (!empty($obj->{$InfoFieldList[1]})) {
5252
                                $translabel = $langs->trans($obj->{$InfoFieldList[1]});
5253
                            }
5254
                            if ($translabel != $obj->{$InfoFieldList[1]}) {
5255
                                $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #aaa">' . dol_trunc($translabel, 18) . '</li>';
5256
                            } else {
5257
                                $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #aaa">' . $obj->{$InfoFieldList[1]} . '</li>';
5258
                            }
5259
                        }
5260
                    }
5261
                }
5262
                $value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
5263
            } else {
5264
                dol_syslog(get_class($this) . '::showOutputField error ' . $this->db->lasterror(), LOG_WARNING);
5265
            }
5266
        } elseif ($type == 'link') {
5267
            $out = '';
5268
5269
            // only if something to display (perf)
5270
            if ($value) {
5271
                $param_list = array_keys($param['options']);                // $param_list='ObjectName:classPath'
5272
5273
                $InfoFieldList = explode(":", $param_list[0]);
5274
                $classname = $InfoFieldList[0];
5275
                $classpath = $InfoFieldList[1];
5276
                $getnomurlparam = (empty($InfoFieldList[2]) ? 3 : $InfoFieldList[2]);
5277
                if (!empty($classpath)) {
5278
                    dol_include_once($InfoFieldList[1]);
5279
                    if ($classname && class_exists($classname)) {
5280
                        $object = new $classname($this->db);
5281
                        $object->fetch($value);
5282
                        $value = $object->getNomUrl($getnomurlparam);
5283
                    }
5284
                } else {
5285
                    dol_syslog('Error bad setup of extrafield', LOG_WARNING);
5286
                    return 'Error bad setup of extrafield';
5287
                }
5288
            } else $value = '';
5289
        } elseif ($type == 'text' || $type == 'html') {
5290
            $value = dol_htmlentitiesbr($value);
5291
        } elseif ($type == 'password') {
5292
            $value = preg_replace('/./i', '*', $value);
5293
        } elseif ($type == 'array') {
5294
            $value = implode('<br>', $value);
5295
        }
5296
5297
        //print $type.'-'.$size;
5298
        $out = $value;
5299
5300
        return $out;
5301
    }
5302
5303
    /**
5304
     * Function to show lines of extrafields with output datas
5305
     *
5306
     * @param Extrafields $extrafields Extrafield Object
5307
     * @param string $mode Show output (view) or input (edit) for extrafield
5308
     * @param array $params Optional parameters. Example: array('style'=>'class="oddeven"', 'colspan'=>$colspan)
5309
     * @param string $keysuffix Suffix string to add after name and id of field (can be used to avoid duplicate names)
5310
     * @param string $keyprefix Prefix string to add before name and id of field (can be used to avoid duplicate names)
5311
     * @param string $onetrtd All fields in same tr td
5312
     * @return    string
5313
     */
5314
    function showOptionals($extrafields, $mode = 'view', $params = null, $keysuffix = '', $keyprefix = '', $onetrtd = 0)
5315
    {
5316
        global $db, $conf, $langs, $action, $form;
5317
5318
        if (!is_object($form)) $form = new Form($db);
5319
5320
        $out = '';
5321
5322
        if (is_array($extrafields->attributes[$this->table_element]['label']) && count($extrafields->attributes[$this->table_element]['label']) > 0) {
5323
            $out .= "\n";
5324
            $out .= '<!-- showOptionalsInput --> ';
5325
            $out .= "\n";
5326
5327
            $e = 0;
5328
            foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $label) {
5329
                // Show only the key field in params
5330
                if (is_array($params) && array_key_exists('onlykey', $params) && $key != $params['onlykey']) continue;
5331
5332
                $enabled = 1;
5333
                if ($enabled && isset($extrafields->attributes[$this->table_element]['list'][$key])) {
5334
                    $enabled = dol_eval($extrafields->attributes[$this->table_element]['list'][$key], 1);
5335
                }
5336
5337
                $perms = 1;
5338
                if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
5339
                    $perms = dol_eval($extrafields->attributes[$this->table_element]['perms'][$key], 1);
5340
                }
5341
5342
                if (($mode == 'create' || $mode == 'edit') && abs($enabled) != 1 && abs($enabled) != 3) continue;    // <> -1 and <> 1 and <> 3 = not visible on forms, only on list
5343
                if (empty($perms)) continue;
5344
5345
                // Load language if required
5346
                if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
5347
5348
                $colspan = '3';
5349
                if (is_array($params) && count($params) > 0) {
5350
                    if (array_key_exists('colspan', $params)) {
5351
                        $colspan = $params['colspan'];
5352
                    }
5353
                }
5354
5355
                switch ($mode) {
5356
                    case "view":
5357
                        $value = $this->array_options["options_" . $key . $keysuffix];
5358
                        break;
5359
                    case "edit":
5360
                        $getposttemp = GETPOST($keyprefix . 'options_' . $key . $keysuffix, 'none');                // GETPOST can get value from GET, POST or setup of default values.
5361
                        // GETPOST("options_" . $key) can be 'abc' or array(0=>'abc')
5362
                        if (is_array($getposttemp) || $getposttemp != '' || GETPOSTISSET($keyprefix . 'options_' . $key . $keysuffix)) {
5363
                            if (is_array($getposttemp)) {
5364
                                // $getposttemp is an array but following code expects a comma separated string
5365
                                $value = implode(",", $getposttemp);
5366
                            } else {
5367
                                $value = $getposttemp;
5368
                            }
5369
                        } else {
5370
                            $value = $this->array_options["options_" . $key];            // No GET, no POST, no default value, so we take value of object.
5371
                        }
5372
                        //var_dump($keyprefix.' - '.$key.' - '.$keysuffix.' - '.$keyprefix.'options_'.$key.$keysuffix.' - '.$this->array_options["options_".$key.$keysuffix].' - '.$getposttemp.' - '.$value);
5373
                        break;
5374
                }
5375
5376
                if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
5377
                    $out .= $extrafields->showSeparator($key, $this);
5378
                } else {
5379
                    $csstyle = '';
5380
                    $class = (!empty($extrafields->attributes[$this->table_element]['hidden'][$key]) ? 'hideobject ' : '');
5381
                    if (is_array($params) && count($params) > 0) {
5382
                        if (array_key_exists('style', $params)) {
5383
                            $csstyle = $params['style'];
5384
                        }
5385
                    }
5386
5387
                    // add html5 elements
5388
                    $domData = ' data-element="extrafield"';
5389
                    $domData .= ' data-targetelement="' . $this->element . '"';
5390
                    $domData .= ' data-targetid="' . $this->id . '"';
5391
5392
                    $html_id = !empty($this->id) ? 'extrarow-' . $this->element . '_' . $key . '_' . $this->id : '';
5393
5394
                    $out .= '<tr id="' . $html_id . '" ' . $csstyle . ' class="' . $class . $this->element . '_extras_' . $key . '" ' . $domData . ' >';
5395
5396
                    if (!empty($conf->global->MAIN_EXTRAFIELDS_USE_TWO_COLUMS) && ($e % 2) == 0) {
5397
                        if (!empty($conf->global->MAIN_EXTRAFIELDS_USE_TWO_COLUMS) && ($e % 2) == 0) {
5398
                            $colspan = '0';
5399
                        }
5400
                    }
5401
5402
                    if ($action == 'selectlines') {
5403
                        $colspan++;
5404
                    }
5405
5406
                    // Convert date into timestamp format (value in memory must be a timestamp)
5407
                    if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date', 'datetime'))) {
5408
                        $datenotinstring = $this->array_options['options_' . $key];
5409
                        if (!is_numeric($this->array_options['options_' . $key]))    // For backward compatibility
5410
                        {
5411
                            $datenotinstring = $this->db->jdate($datenotinstring);
5412
                        }
5413
                        $value = GETPOSTISSET($keyprefix . 'options_' . $key . $keysuffix) ? dol_mktime(GETPOST($keyprefix . 'options_' . $key . $keysuffix . "hour", 'int', 3), GETPOST($keyprefix . 'options_' . $key . $keysuffix . "min", 'int', 3), 0, GETPOST($keyprefix . 'options_' . $key . $keysuffix . "month", 'int', 3), GETPOST($keyprefix . 'options_' . $key . $keysuffix . "day", 'int', 3), GETPOST($keyprefix . 'options_' . $key . $keysuffix . "year", 'int', 3)) : $datenotinstring;
5414
                    }
5415
                    // Convert float submited string into real php numeric (value in memory must be a php numeric)
5416
                    if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('price', 'double'))) {
5417
                        $value = GETPOSTISSET($keyprefix . 'options_' . $key . $keysuffix) ? price2num(GETPOST($keyprefix . 'options_' . $key . $keysuffix, 'alpha', 3)) : $this->array_options['options_' . $key];
5418
                    }
5419
5420
                    $labeltoshow = $langs->trans($label);
5421
5422
                    $out .= '<td class="titlefield';
5423
                    if (GETPOST('action', 'none') == 'create') $out .= 'create';
5424
                    if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) $out .= ' fieldrequired';
5425
                    $out .= '">';
5426
                    if (!empty($extrafields->attributes[$object->table_element]['help'][$key])) $out .= $form->textwithpicto($labeltoshow, $extrafields->attributes[$object->table_element]['help'][$key]);
5427
                    else $out .= $labeltoshow;
5428
                    $out .= '</td>';
5429
5430
                    $html_id = !empty($this->id) ? $this->element . '_extras_' . $key . '_' . $this->id : '';
5431
                    $out .= '<td id="' . $html_id . '" class="' . $this->element . '_extras_' . $key . '" ' . ($colspan ? ' colspan="' . $colspan . '"' : '') . '>';
5432
5433
                    switch ($mode) {
5434
                        case "view":
5435
                            $out .= $extrafields->showOutputField($key, $value);
5436
                            break;
5437
                        case "edit":
5438
                            $out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', 0, $this->id);
5439
                            break;
5440
                    }
5441
5442
                    $out .= '</td>';
5443
5444
                    if (!empty($conf->global->MAIN_EXTRAFIELDS_USE_TWO_COLUMS) && (($e % 2) == 1)) $out .= '</tr>';
5445
                    else $out .= '</tr>';
5446
                    $e++;
5447
                }
5448
            }
5449
            $out .= "\n";
5450
            // Add code to manage list depending on others
5451
            if (!empty($conf->use_javascript_ajax)) {
5452
                $out .= '
5453
				<script type="text/javascript">
5454
				    jQuery(document).ready(function() {
5455
				    	function showOptions(child_list, parent_list)
5456
				    	{
5457
				    		var val = $("select[name=\"options_"+parent_list+"\"]").val();
5458
				    		var parentVal = parent_list + ":" + val;
5459
							if(val > 0) {
5460
					    		$("select[name=\""+child_list+"\"] option[parent]").hide();
5461
					    		$("select[name=\""+child_list+"\"] option[parent=\""+parentVal+"\"]").show();
5462
							} else {
5463
								$("select[name=\""+child_list+"\"] option").show();
5464
							}
5465
				    	}
5466
						function setListDependencies() {
5467
					    	jQuery("select option[parent]").parent().each(function() {
5468
					    		var child_list = $(this).attr("name");
5469
								var parent = $(this).find("option[parent]:first").attr("parent");
5470
								var infos = parent.split(":");
5471
								var parent_list = infos[0];
5472
								$("select[name=\""+parent_list+"\"]").change(function() {
5473
									showOptions(child_list, parent_list);
5474
								});
5475
					    	});
5476
						}
5477
5478
						setListDependencies();
5479
				    });
5480
				</script>' . "\n";
5481
                $out .= '<!-- /showOptionalsInput --> ' . "\n";
5482
            }
5483
        }
5484
        return $out;
5485
    }
5486
5487
    /**
5488
     * Get buy price to use for margin calculation. This function is called when buy price is unknown.
5489
     *     Set buy price = sell price if ForceBuyingPriceIfNull configured,
5490
     *   else if calculation MARGIN_TYPE = 'costprice' and costprice is defined, use costprice as buyprice
5491
     *     else if calculation MARGIN_TYPE = 'pmp' and pmp is calculated, use pmp as buyprice
5492
     *     else set min buy price as buy price
5493
     *
5494
     * @param float $unitPrice Product unit price
5495
     * @param float $discountPercent Line discount percent
5496
     * @param int $fk_product Product id
5497
     * @return    float                    <0 if KO, buyprice if OK
5498
     */
5499
    public function defineBuyPrice($unitPrice = 0.0, $discountPercent = 0.0, $fk_product = 0)
5500
    {
5501
        global $conf;
5502
5503
        $buyPrice = 0;
5504
5505
        if (($unitPrice > 0) && (isset($conf->global->ForceBuyingPriceIfNull) && $conf->global->ForceBuyingPriceIfNull == 1)) // In most cases, test here is false
5506
        {
5507
            $buyPrice = $unitPrice * (1 - $discountPercent / 100);
5508
        } else {
5509
            // Get cost price for margin calculation
5510
            if (!empty($fk_product)) {
5511
                if (isset($conf->global->MARGIN_TYPE) && $conf->global->MARGIN_TYPE == 'costprice') {
5512
                    require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
5513
                    $product = new Product($this->db);
5514
                    $result = $product->fetch($fk_product);
5515
                    if ($result <= 0) {
5516
                        $this->errors[] = 'ErrorProductIdDoesNotExists';
5517
                        return -1;
5518
                    }
5519
                    if ($product->cost_price > 0) {
5520
                        $buyPrice = $product->cost_price;
5521
                    } else if ($product->pmp > 0) {
5522
                        $buyPrice = $product->pmp;
5523
                    }
5524
                } else if (isset($conf->global->MARGIN_TYPE) && $conf->global->MARGIN_TYPE == 'pmp') {
5525
                    require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
5526
                    $product = new Product($this->db);
5527
                    $result = $product->fetch($fk_product);
5528
                    if ($result <= 0) {
5529
                        $this->errors[] = 'ErrorProductIdDoesNotExists';
5530
                        return -1;
5531
                    }
5532
                    if ($product->pmp > 0) {
5533
                        $buyPrice = $product->pmp;
5534
                    }
5535
                }
5536
5537
                if (empty($buyPrice) && isset($conf->global->MARGIN_TYPE) && in_array($conf->global->MARGIN_TYPE, array('1', 'pmp', 'costprice'))) {
5538
                    require_once DOL_DOCUMENT_ROOT . '/fourn/class/fournisseur.product.class.php';
5539
                    $productFournisseur = new ProductFournisseur($this->db);
5540
                    if (($result = $productFournisseur->find_min_price_product_fournisseur($fk_product)) > 0) {
5541
                        $buyPrice = $productFournisseur->fourn_unitprice;
5542
                    } else if ($result < 0) {
5543
                        $this->errors[] = $productFournisseur->error;
5544
                        return -2;
5545
                    }
5546
                }
5547
            }
5548
        }
5549
        return $buyPrice;
5550
    }
5551
5552
    /**
5553
     *  Show photos of an object (nbmax maximum), into several columns
5554
     *
5555
     * @param string $modulepart 'product', 'ticket', ...
5556
     * @param string $sdir Directory to scan (full absolute path)
5557
     * @param int $size 0=original size, 1='small' use thumbnail if possible
5558
     * @param int $nbmax Nombre maximum de photos (0=pas de max)
5559
     * @param int $nbbyrow Number of image per line or -1 to use div. Used only if size=1.
5560
     * @param int $showfilename 1=Show filename
5561
     * @param int $showaction 1=Show icon with action links (resize, delete)
5562
     * @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.
5563
     * @param int $maxWidth Max width of original image when size='small'
5564
     * @param int $nolink Do not add a href link to view enlarged imaged into a new tab
5565
     * @param int $notitle Do not add title tag on image
5566
     * @param int $usesharelink Use the public shared link of image (if not available, the 'nophoto' image will be shown instead)
5567
     * @return     string                    Html code to show photo. Number of photos shown is saved in this->nbphoto
5568
     */
5569
    function show_photos($modulepart, $sdir, $size = 0, $nbmax = 0, $nbbyrow = 5, $showfilename = 0, $showaction = 0, $maxHeight = 120, $maxWidth = 160, $nolink = 0, $notitle = 0, $usesharelink = 0)
5570
    {
5571
        // phpcs:enable
5572
        global $conf, $user, $langs;
5573
5574
        include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
5575
        include_once DOL_DOCUMENT_ROOT . '/core/lib/images.lib.php';
5576
5577
        $sortfield = 'position_name';
5578
        $sortorder = 'asc';
5579
5580
        $dir = $sdir . '/';
5581
        $pdir = '/';
5582
        if ($modulepart == 'ticket') {
5583
            $dir .= get_exdir(0, 0, 0, 0, $this, $modulepart) . $this->track_id . '/';
5584
            $pdir .= get_exdir(0, 0, 0, 0, $this, $modulepart) . $this->track_id . '/';
5585
        } else {
5586
            $dir .= get_exdir(0, 0, 0, 0, $this, $modulepart) . $this->ref . '/';
5587
            $pdir .= get_exdir(0, 0, 0, 0, $this, $modulepart) . $this->ref . '/';
5588
        }
5589
5590
        // For backward compatibility
5591
        if ($modulepart == 'product' && !empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO)) {
5592
            $dir = $sdir . '/' . get_exdir($this->id, 2, 0, 0, $this, $modulepart) . $this->id . "/photos/";
5593
            $pdir = '/' . get_exdir($this->id, 2, 0, 0, $this, $modulepart) . $this->id . "/photos/";
5594
        }
5595
5596
        // Defined relative dir to DOL_DATA_ROOT
5597
        $relativedir = '';
5598
        if ($dir) {
5599
            $relativedir = preg_replace('/^' . preg_quote(DOL_DATA_ROOT, '/') . '/', '', $dir);
5600
            $relativedir = preg_replace('/^[\\/]/', '', $relativedir);
5601
            $relativedir = preg_replace('/[\\/]$/', '', $relativedir);
5602
        }
5603
5604
        $dirthumb = $dir . 'thumbs/';
5605
        $pdirthumb = $pdir . 'thumbs/';
5606
5607
        $return = '<!-- Photo -->' . "\n";
5608
        $nbphoto = 0;
5609
5610
        $filearray = dol_dir_list($dir, "files", 0, '', '(\.meta|_preview.*\.png)$', $sortfield, (strtolower($sortorder) == 'desc' ? SORT_DESC : SORT_ASC), 1);
5611
5612
        /*if (! empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO))    // For backward compatiblity, we scan also old dirs
5613
		 {
5614
		 $filearrayold=dol_dir_list($dirold,"files",0,'','(\.meta|_preview.*\.png)$',$sortfield,(strtolower($sortorder)=='desc'?SORT_DESC:SORT_ASC),1);
5615
		 $filearray=array_merge($filearray, $filearrayold);
5616
		 }*/
5617
5618
        completeFileArrayWithDatabaseInfo($filearray, $relativedir);
5619
5620
        if (count($filearray)) {
5621
            if ($sortfield && $sortorder) {
5622
                $filearray = dol_sort_array($filearray, $sortfield, $sortorder);
5623
            }
5624
5625
            foreach ($filearray as $key => $val) {
5626
                $photo = '';
5627
                $file = $val['name'];
5628
5629
                //if (! utf8_check($file)) $file=utf8_encode($file);	// To be sure file is stored in UTF8 in memory
5630
5631
                //if (dol_is_file($dir.$file) && image_format_supported($file) >= 0)
5632
                if (image_format_supported($file) >= 0) {
5633
                    $nbphoto++;
5634
                    $photo = $file;
5635
                    $viewfilename = $file;
5636
5637
                    if ($size == 1 || $size == 'small') {   // Format vignette
5638
5639
                        // Find name of thumb file
5640
                        $photo_vignette = basename(getImageFileNameForSize($dir . $file, '_small'));
5641
                        if (!dol_is_file($dirthumb . $photo_vignette)) $photo_vignette = '';
5642
5643
                        // Get filesize of original file
5644
                        $imgarray = dol_getImageSize($dir . $photo);
5645
5646
                        if ($nbbyrow > 0) {
5647
                            if ($nbphoto == 1) $return .= '<table width="100%" valign="top" align="center" border="0" cellpadding="2" cellspacing="2">';
5648
5649
                            if ($nbphoto % $nbbyrow == 1) $return .= '<tr align=center valign=middle border=1>';
5650
                            $return .= '<td width="' . ceil(100 / $nbbyrow) . '%" class="photo">';
5651
                        } else if ($nbbyrow < 0) $return .= '<div class="inline-block">';
5652
5653
                        $return .= "\n";
5654
5655
                        $relativefile = preg_replace('/^\//', '', $pdir . $photo);
5656
                        if (empty($nolink)) {
5657
                            $urladvanced = getAdvancedPreviewUrl($modulepart, $relativefile, 0, 'entity=' . $this->entity);
5658
                            if ($urladvanced) $return .= '<a href="' . $urladvanced . '">';
5659
                            else $return .= '<a href="' . DOL_URL_ROOT . '/viewimage.php?modulepart=' . $modulepart . '&entity=' . $this->entity . '&file=' . urlencode($pdir . $photo) . '" class="aphoto" target="_blank">';
5660
                        }
5661
5662
                        // Show image (width height=$maxHeight)
5663
                        // Si fichier vignette disponible et image source trop grande, on utilise la vignette, sinon on utilise photo origine
5664
                        $alt = $langs->transnoentitiesnoconv('File') . ': ' . $relativefile;
5665
                        $alt .= ' - ' . $langs->transnoentitiesnoconv('Size') . ': ' . $imgarray['width'] . 'x' . $imgarray['height'];
5666
                        if ($notitle) $alt = '';
5667
5668
                        if ($usesharelink) {
5669
                            if ($val['share']) {
5670
                                if (empty($maxHeight) || $photo_vignette && $imgarray['height'] > $maxHeight) {
5671
                                    $return .= '<!-- Show original file (thumb not yet available with shared links) -->';
5672
                                    $return .= '<img class="photo photowithmargin" border="0" height="' . $maxHeight . '" src="' . DOL_URL_ROOT . '/viewimage.php?hashp=' . urlencode($val['share']) . '" title="' . dol_escape_htmltag($alt) . '">';
5673
                                } else {
5674
                                    $return .= '<!-- Show original file -->';
5675
                                    $return .= '<img class="photo photowithmargin" border="0" height="' . $maxHeight . '" src="' . DOL_URL_ROOT . '/viewimage.php?hashp=' . urlencode($val['share']) . '" title="' . dol_escape_htmltag($alt) . '">';
5676
                                }
5677
                            } else {
5678
                                $return .= '<!-- Show nophoto file (because file is not shared) -->';
5679
                                $return .= '<img class="photo photowithmargin" border="0" height="' . $maxHeight . '" src="' . DOL_URL_ROOT . '/public/theme/common/nophoto.png" title="' . dol_escape_htmltag($alt) . '">';
5680
                            }
5681
                        } else {
5682
                            if (empty($maxHeight) || $photo_vignette && $imgarray['height'] > $maxHeight) {
5683
                                $return .= '<!-- Show thumb -->';
5684
                                $return .= '<img class="photo photowithmargin" border="0" height="' . $maxHeight . '" src="' . DOL_URL_ROOT . '/viewimage.php?modulepart=' . $modulepart . '&entity=' . $this->entity . '&file=' . urlencode($pdirthumb . $photo_vignette) . '" title="' . dol_escape_htmltag($alt) . '">';
5685
                            } else {
5686
                                $return .= '<!-- Show original file -->';
5687
                                $return .= '<img class="photo photowithmargin" border="0" height="' . $maxHeight . '" src="' . DOL_URL_ROOT . '/viewimage.php?modulepart=' . $modulepart . '&entity=' . $this->entity . '&file=' . urlencode($pdir . $photo) . '" title="' . dol_escape_htmltag($alt) . '">';
5688
                            }
5689
                        }
5690
5691
                        if (empty($nolink)) $return .= '</a>';
5692
                        $return .= "\n";
5693
5694
                        if ($showfilename) $return .= '<br>' . $viewfilename;
5695
                        if ($showaction) {
5696
                            $return .= '<br>';
5697
                            // On propose la generation de la vignette si elle n'existe pas et si la taille est superieure aux limites
5698
                            if ($photo_vignette && (image_format_supported($photo) > 0) && ($this->imgWidth > $maxWidth || $this->imgHeight > $maxHeight)) {
5699
                                $return .= '<a href="' . $_SERVER["PHP_SELF"] . '?id=' . $this->id . '&amp;action=addthumb&amp;file=' . urlencode($pdir . $viewfilename) . '">' . img_picto($langs->trans('GenerateThumb'), 'refresh') . '&nbsp;&nbsp;</a>';
5700
                            }
5701
                            // Special cas for product
5702
                            if ($modulepart == 'product' && ($user->rights->produit->creer || $user->rights->service->creer)) {
5703
                                // Link to resize
5704
                                $return .= '<a href="' . DOL_URL_ROOT . '/core/photos_resize.php?modulepart=' . urlencode('produit|service') . '&id=' . $this->id . '&amp;file=' . urlencode($pdir . $viewfilename) . '" title="' . dol_escape_htmltag($langs->trans("Resize")) . '">' . img_picto($langs->trans("Resize"), 'resize', '') . '</a> &nbsp; ';
5705
5706
                                // Link to delete
5707
                                $return .= '<a href="' . $_SERVER["PHP_SELF"] . '?id=' . $this->id . '&amp;action=delete&amp;file=' . urlencode($pdir . $viewfilename) . '">';
5708
                                $return .= img_delete() . '</a>';
5709
                            }
5710
                        }
5711
                        $return .= "\n";
5712
5713
                        if ($nbbyrow > 0) {
5714
                            $return .= '</td>';
5715
                            if (($nbphoto % $nbbyrow) == 0) $return .= '</tr>';
5716
                        } else if ($nbbyrow < 0) $return .= '</div>';
5717
                    }
5718
5719
                    if (empty($size)) {     // Format origine
5720
                        $return .= '<img class="photo photowithmargin" border="0" src="' . DOL_URL_ROOT . '/viewimage.php?modulepart=' . $modulepart . '&entity=' . $this->entity . '&file=' . urlencode($pdir . $photo) . '">';
5721
5722
                        if ($showfilename) $return .= '<br>' . $viewfilename;
5723
                        if ($showaction) {
5724
                            // Special case for product
5725
                            if ($modulepart == 'product' && ($user->rights->produit->creer || $user->rights->service->creer)) {
5726
                                // Link to resize
5727
                                $return .= '<a href="' . DOL_URL_ROOT . '/core/photos_resize.php?modulepart=' . urlencode('produit|service') . '&id=' . $this->id . '&amp;file=' . urlencode($pdir . $viewfilename) . '" title="' . dol_escape_htmltag($langs->trans("Resize")) . '">' . img_picto($langs->trans("Resize"), 'resize', '') . '</a> &nbsp; ';
5728
5729
                                // Link to delete
5730
                                $return .= '<a href="' . $_SERVER["PHP_SELF"] . '?id=' . $this->id . '&amp;action=delete&amp;file=' . urlencode($pdir . $viewfilename) . '">';
5731
                                $return .= img_delete() . '</a>';
5732
                            }
5733
                        }
5734
                    }
5735
5736
                    // On continue ou on arrete de boucler ?
5737
                    if ($nbmax && $nbphoto >= $nbmax) break;
5738
                }
5739
            }
5740
5741
            if ($size == 1 || $size == 'small') {
5742
                if ($nbbyrow > 0) {
5743
                    // Ferme tableau
5744
                    while ($nbphoto % $nbbyrow) {
5745
                        $return .= '<td width="' . ceil(100 / $nbbyrow) . '%">&nbsp;</td>';
5746
                        $nbphoto++;
5747
                    }
5748
5749
                    if ($nbphoto) $return .= '</table>';
5750
                }
5751
            }
5752
        }
5753
5754
        $this->nbphoto = $nbphoto;
5755
5756
        return $return;
5757
    }
5758
5759
    /**
5760
     * Function test if type is text
5761
     *
5762
     * @param array $info content informations of field
5763
     * @return                  bool
5764
     */
5765
    public function isText($info)
5766
    {
5767
        if (is_array($info)) {
5768
            if (isset($info['type']) && $info['type'] == 'text') return true;
5769
            else return false;
5770
        } else return false;
5771
    }
5772
5773
    /**
5774
     * Create object into database
5775
     *
5776
     * @param User $user User that creates
5777
     * @param bool $notrigger false=launch triggers after, true=disable triggers
5778
     * @return int             <0 if KO, Id of created object if OK
5779
     */
5780
    public function createCommon(User $user, $notrigger = false)
5781
    {
5782
        global $langs;
5783
5784
        $error = 0;
5785
5786
        $now = dol_now();
5787
5788
        $fieldvalues = $this->setSaveQuery();
5789
        if (array_key_exists('date_creation', $fieldvalues) && empty($fieldvalues['date_creation'])) $fieldvalues['date_creation'] = $this->db->idate($now);
5790
        if (array_key_exists('fk_user_creat', $fieldvalues) && !($fieldvalues['fk_user_creat'] > 0)) $fieldvalues['fk_user_creat'] = $user->id;
5791
        unset($fieldvalues['rowid']);    // The field 'rowid' is reserved field name for autoincrement field so we don't need it into insert.
5792
5793
        $keys = array();
5794
        $values = array();
5795
        foreach ($fieldvalues as $k => $v) {
5796
            $keys[$k] = $k;
5797
            $value = $this->fields[$k];
5798
            $values[$k] = $this->quote($v, $value);
5799
        }
5800
5801
        // Clean and check mandatory
5802
        foreach ($keys as $key) {
5803
            // If field is an implicit foreign key field
5804
            if (preg_match('/^integer:/i', $this->fields[$key]['type']) && $values[$key] == '-1') $values[$key] = '';
5805
            if (!empty($this->fields[$key]['foreignkey']) && $values[$key] == '-1') $values[$key] = '';
5806
5807
            //var_dump($key.'-'.$values[$key].'-'.($this->fields[$key]['notnull'] == 1));
5808
            if (isset($this->fields[$key]['notnull']) && $this->fields[$key]['notnull'] == 1 && !isset($values[$key]) && is_null($val['default'])) {
5809
                $error++;
5810
                $this->errors[] = $langs->trans("ErrorFieldRequired", $this->fields[$key]['label']);
5811
            }
5812
5813
            // If field is an implicit foreign key field
5814
            if (preg_match('/^integer:/i', $this->fields[$key]['type']) && empty($values[$key])) $values[$key] = 'null';
5815
            if (!empty($this->fields[$key]['foreignkey']) && empty($values[$key])) $values[$key] = 'null';
5816
        }
5817
5818
        if ($error) return -1;
5819
5820
        $this->db->begin();
5821
5822
        if (!$error) {
5823
            $sql = 'INSERT INTO ' . MAIN_DB_PREFIX . $this->table_element;
5824
            $sql .= ' (' . implode(", ", $keys) . ')';
5825
            $sql .= ' VALUES (' . implode(", ", $values) . ')';
5826
5827
            $res = $this->db->query($sql);
5828
            if ($res === false) {
5829
                $error++;
5830
                $this->errors[] = $this->db->lasterror();
5831
            }
5832
        }
5833
5834
        if (!$error) {
5835
            $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX . $this->table_element);
5836
        }
5837
5838
        // Create extrafields
5839
        if (!$error) {
5840
            $result = $this->insertExtraFields();
5841
            if ($result < 0) $error++;
5842
        }
5843
5844
        // Triggers
5845
        if (!$error && !$notrigger) {
5846
            // Call triggers
5847
            $result = $this->call_trigger(strtoupper(get_class($this)) . '_CREATE', $user);
5848
            if ($result < 0) {
5849
                $error++;
5850
            }
5851
            // End call triggers
5852
        }
5853
5854
        // Commit or rollback
5855
        if ($error) {
5856
            $this->db->rollback();
5857
            return -1;
5858
        } else {
5859
            $this->db->commit();
5860
            return $this->id;
5861
        }
5862
    }
5863
5864
    /**
5865
     * Function to prepare the values to insert.
5866
     * Note $this->${field} are set by the page that make the createCommon or the updateCommon.
5867
     *
5868
     * @return array
5869
     */
5870
    protected function setSaveQuery()
5871
    {
5872
        global $conf;
5873
5874
        $queryarray = array();
5875
        foreach ($this->fields as $field => $info)    // Loop on definition of fields
5876
        {
5877
            // Depending on field type ('datetime', ...)
5878
            if ($this->isDate($info)) {
5879
                if (empty($this->{$field})) {
5880
                    $queryarray[$field] = null;
5881
                } else {
5882
                    $queryarray[$field] = $this->db->idate($this->{$field});
5883
                }
5884
            } else if ($this->isArray($info)) {
5885
                if (!empty($this->{$field})) {
5886
                    if (!is_array($this->{$field})) {
5887
                        $this->{$field} = array($this->{$field});
5888
                    }
5889
                    $queryarray[$field] = serialize($this->{$field});
5890
                } else {
5891
                    $queryarray[$field] = null;
5892
                }
5893
            } else if ($this->isInt($info)) {
5894
                if ($field == 'entity' && is_null($this->{$field})) $queryarray[$field] = $conf->entity;
5895
                else {
5896
                    $queryarray[$field] = (int)price2num($this->{$field});
5897
                    if (empty($queryarray[$field])) $queryarray[$field] = 0;        // May be reset to null later if property 'notnull' is -1 for this field.
5898
                }
5899
            } else if ($this->isFloat($info)) {
5900
                $queryarray[$field] = (double)price2num($this->{$field});
5901
                if (empty($queryarray[$field])) $queryarray[$field] = 0;
5902
            } else {
5903
                $queryarray[$field] = $this->{$field};
5904
            }
5905
5906
            if ($info['type'] == 'timestamp' && empty($queryarray[$field])) unset($queryarray[$field]);
5907
            if (!empty($info['notnull']) && $info['notnull'] == -1 && empty($queryarray[$field])) $queryarray[$field] = null;
5908
        }
5909
5910
        return $queryarray;
5911
    }
5912
5913
    /**
5914
     *    Add/Update all extra fields values for the current object.
5915
     *  Data to describe values to insert/update are stored into $this->array_options=array('options_codeforfield1'=>'valueforfield1', 'options_codeforfield2'=>'valueforfield2', ...)
5916
     *  This function delete record with all extrafields and insert them again from the array $this->array_options.
5917
     *
5918
     * @param string $trigger If defined, call also the trigger (for example COMPANY_MODIFY)
5919
     * @param User $userused Object user
5920
     * @return int                        -1=error, O=did nothing, 1=OK
5921
     * @see updateExtraField, setValueFrom
5922
     */
5923
    function insertExtraFields($trigger = '', $userused = null)
5924
    {
5925
        global $conf, $langs, $user;
5926
5927
        if (empty($userused)) $userused = $user;
5928
5929
        $error = 0;
5930
5931
        if (!empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) return 0;    // For avoid conflicts if trigger used
5932
5933
        if (!empty($this->array_options)) {
5934
            // Check parameters
5935
            $langs->load('admin');
5936
            require_once DOL_DOCUMENT_ROOT . '/core/class/extrafields.class.php';
5937
            $extrafields = new ExtraFields($this->db);
5938
            $target_extrafields = $extrafields->fetch_name_optionals_label($this->table_element);
5939
5940
            //Eliminate copied source object extra_fields that do not exist in target object
5941
            $new_array_options = array();
5942
            foreach ($this->array_options as $key => $value) {
5943
                if (in_array(substr($key, 8), array_keys($target_extrafields)))    // We remove the 'options_' from $key for test
5944
                    $new_array_options[$key] = $value;
5945
                elseif (in_array($key, array_keys($target_extrafields)))        // We test on $key that does not contains the 'options_' prefix
5946
                    $new_array_options['options_' . $key] = $value;
5947
            }
5948
5949
            foreach ($new_array_options as $key => $value) {
5950
                $attributeKey = substr($key, 8);   // Remove 'options_' prefix
5951
                $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
5952
                $attributeLabel = $extrafields->attributes[$this->table_element]['label'][$attributeKey];
5953
                $attributeParam = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
5954
                $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
5955
5956
                if ($attributeRequired) {
5957
                    $mandatorypb = false;
5958
                    if ($attributeType == 'link' && $this->array_options[$key] == '-1') $mandatorypb = true;
5959
                    if ($this->array_options[$key] === '') $mandatorypb = true;
5960
                    if ($mandatorypb) {
5961
                        dol_syslog($this->error);
5962
                        $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
5963
                        return -1;
5964
                    }
5965
                }
5966
5967
                //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
5968
                //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
5969
5970
                switch ($attributeType) {
5971
                    case 'int':
5972
                        if (!is_numeric($value) && $value != '') {
5973
                            $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
5974
                            return -1;
5975
                        } elseif ($value == '') {
5976
                            $new_array_options[$key] = null;
5977
                        }
5978
                        break;
5979
                    case 'double':
5980
                        $value = price2num($value);
5981
                        if (!is_numeric($value) && $value != '') {
5982
                            dol_syslog($langs->trans("ExtraFieldHasWrongValue") . " sur " . $attributeLabel . "(" . $value . "is not '" . $attributeType . "')", LOG_DEBUG);
5983
                            $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
5984
                            return -1;
5985
                        } elseif ($value == '') {
5986
                            $new_array_options[$key] = null;
5987
                        }
5988
                        //dol_syslog("double value"." sur ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
5989
                        $new_array_options[$key] = $value;
5990
                        break;
5991
                    /*case 'select':	// Not required, we chosed value='0' for undefined values
5992
             			if ($value=='-1')
5993
             			{
5994
             				$this->array_options[$key] = null;
5995
             			}
5996
             			break;*/
5997
                    case 'password':
5998
                        $algo = '';
5999
                        if ($this->array_options[$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
6000
                            // If there is an encryption choice, we use it to crypt data before insert
6001
                            $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
6002
                            $algo = reset($tmparrays);
6003
                            if ($algo != '') {
6004
                                //global $action;		// $action may be 'create', 'update', 'update_extras'...
6005
                                //var_dump($action);
6006
                                //var_dump($this->oldcopy);exit;
6007
                                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
6008
                                {
6009
                                    //var_dump($this->oldcopy->array_options[$key]); var_dump($this->array_options[$key]);
6010
                                    if ($this->array_options[$key] == $this->oldcopy->array_options[$key])    // If old value crypted in database is same than submited new value, it means we don't change it, so we don't update.
6011
                                    {
6012
                                        $new_array_options[$key] = $this->array_options[$key];    // Value is kept
6013
                                    } else {
6014
                                        // var_dump($algo);
6015
                                        $newvalue = dol_hash($this->array_options[$key], $algo);
6016
                                        $new_array_options[$key] = $newvalue;
6017
                                    }
6018
                                } else {
6019
                                    $new_array_options[$key] = $this->array_options[$key];    // Value is kept
6020
                                }
6021
                            }
6022
                        } else    // Common usage
6023
                        {
6024
                            $new_array_options[$key] = $this->array_options[$key];
6025
                        }
6026
                        break;
6027
                    case 'price':
6028
                        $new_array_options[$key] = price2num($this->array_options[$key]);
6029
                        break;
6030
                    case 'date':
6031
                        $new_array_options[$key] = $this->db->idate($this->array_options[$key]);
6032
                        break;
6033
                    case 'datetime':
6034
                        // If data is a string instead of a timestamp, we convert it
6035
                        if (!is_int($this->array_options[$key])) {
6036
                            $this->array_options[$key] = strtotime($this->array_options[$key]);
6037
                        }
6038
                        $new_array_options[$key] = $this->db->idate($this->array_options[$key]);
6039
                        break;
6040
                    case 'link':
6041
                        $param_list = array_keys($attributeParam['options']);
6042
                        // 0 : ObjectName
6043
                        // 1 : classPath
6044
                        $InfoFieldList = explode(":", $param_list[0]);
6045
                        dol_include_once($InfoFieldList[1]);
6046
                        if ($InfoFieldList[0] && class_exists($InfoFieldList[0])) {
6047
                            if ($value == '-1')    // -1 is key for no defined in combo list of objects
6048
                            {
6049
                                $new_array_options[$key] = '';
6050
                            } elseif ($value) {
6051
                                $object = new $InfoFieldList[0]($this->db);
6052
                                if (is_numeric($value)) $res = $object->fetch($value);
6053
                                else $res = $object->fetch('', $value);
6054
6055
                                if ($res > 0) $new_array_options[$key] = $object->id;
6056
                                else {
6057
                                    $this->error = "Id/Ref '" . $value . "' for object '" . $object->element . "' not found";
6058
                                    $this->db->rollback();
6059
                                    return -1;
6060
                                }
6061
                            }
6062
                        } else {
6063
                            dol_syslog('Error bad setup of extrafield', LOG_WARNING);
6064
                        }
6065
                        break;
6066
                }
6067
            }
6068
6069
            $this->db->begin();
6070
6071
            $table_element = $this->table_element;
6072
            if ($table_element == 'categorie') $table_element = 'categories'; // For compatibility
6073
6074
            $sql_del = "DELETE FROM " . MAIN_DB_PREFIX . $table_element . "_extrafields WHERE fk_object = " . $this->id;
6075
            dol_syslog(get_class($this) . "::insertExtraFields delete", LOG_DEBUG);
6076
            $this->db->query($sql_del);
6077
6078
            $sql = "INSERT INTO " . MAIN_DB_PREFIX . $table_element . "_extrafields (fk_object";
6079
            foreach ($new_array_options as $key => $value) {
6080
                $attributeKey = substr($key, 8);   // Remove 'options_' prefix
6081
                // Add field of attribut
6082
                if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] != 'separate') // Only for other type than separator
6083
                    $sql .= "," . $attributeKey;
6084
            }
6085
            $sql .= ") VALUES (" . $this->id;
6086
6087
            foreach ($new_array_options as $key => $value) {
6088
                $attributeKey = substr($key, 8);   // Remove 'options_' prefix
6089
                // Add field of attribute
6090
                if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] != 'separate') // Only for other type than separator)
6091
                {
6092
                    if ($new_array_options[$key] != '') {
6093
                        $sql .= ",'" . $this->db->escape($new_array_options[$key]) . "'";
6094
                    } else {
6095
                        $sql .= ",null";
6096
                    }
6097
                }
6098
            }
6099
            $sql .= ")";
6100
6101
            dol_syslog(get_class($this) . "::insertExtraFields insert", LOG_DEBUG);
6102
            $resql = $this->db->query($sql);
6103
            if (!$resql) {
6104
                $this->error = $this->db->lasterror();
6105
                $error++;
6106
            }
6107
6108
            if (!$error && $trigger) {
6109
                // Call trigger
6110
                $this->context = array('extrafieldaddupdate' => 1);
6111
                $result = $this->call_trigger($trigger, $userused);
6112
                if ($result < 0) $error++;
6113
                // End call trigger
6114
            }
6115
6116
            if ($error) {
6117
                $this->db->rollback();
6118
                return -1;
6119
            } else {
6120
                $this->db->commit();
6121
                return 1;
6122
            }
6123
        } else return 0;
6124
    }
6125
6126
    /**
6127
     * Update object into database
6128
     *
6129
     * @param User $user User that modifies
6130
     * @param bool $notrigger false=launch triggers after, true=disable triggers
6131
     * @return int                <0 if KO, >0 if OK
6132
     */
6133
    public function updateCommon(User $user, $notrigger = false)
6134
    {
6135
        global $conf, $langs;
6136
6137
        $error = 0;
6138
6139
        $now = dol_now();
6140
6141
        $fieldvalues = $this->setSaveQuery();
6142
        if (array_key_exists('date_modification', $fieldvalues) && empty($fieldvalues['date_modification'])) $fieldvalues['date_modification'] = $this->db->idate($now);
6143
        if (array_key_exists('fk_user_modif', $fieldvalues) && !($fieldvalues['fk_user_modif'] > 0)) $fieldvalues['fk_user_modif'] = $user->id;
6144
        unset($fieldvalues['rowid']);    // The field 'rowid' is reserved field name for autoincrement field so we don't need it into update.
6145
6146
        $keys = array();
6147
        $values = array();
6148
        foreach ($fieldvalues as $k => $v) {
6149
            $keys[$k] = $k;
6150
            $value = $this->fields[$k];
6151
            $values[$k] = $this->quote($v, $value);
6152
            $tmp[] = $k . '=' . $this->quote($v, $this->fields[$k]);
6153
        }
6154
6155
        // Clean and check mandatory
6156
        foreach ($keys as $key) {
6157
            if (preg_match('/^integer:/i', $this->fields[$key]['type']) && $values[$key] == '-1') $values[$key] = '';        // This is an implicit foreign key field
6158
            if (!empty($this->fields[$key]['foreignkey']) && $values[$key] == '-1') $values[$key] = '';                    // This is an explicit foreign key field
6159
6160
            //var_dump($key.'-'.$values[$key].'-'.($this->fields[$key]['notnull'] == 1));
6161
            /*
6162
			if ($this->fields[$key]['notnull'] == 1 && empty($values[$key]))
6163
			{
6164
				$error++;
6165
				$this->errors[]=$langs->trans("ErrorFieldRequired", $this->fields[$key]['label']);
6166
			}*/
6167
        }
6168
6169
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element . ' SET ' . implode(',', $tmp) . ' WHERE rowid=' . $this->id;
6170
6171
        $this->db->begin();
6172
        if (!$error) {
6173
            $res = $this->db->query($sql);
6174
            if ($res === false) {
6175
                $error++;
6176
                $this->errors[] = $this->db->lasterror();
6177
            }
6178
        }
6179
6180
        // Update extrafield
6181
        if (!$error && empty($conf->global->MAIN_EXTRAFIELDS_DISABLED) && is_array($this->array_options) && count($this->array_options) > 0) {
6182
            $result = $this->insertExtraFields();
6183
            if ($result < 0) {
6184
                $error++;
6185
            }
6186
        }
6187
6188
        // Triggers
6189
        if (!$error && !$notrigger) {
6190
            // Call triggers
6191
            $result = $this->call_trigger(strtoupper(get_class($this)) . '_MODIFY', $user);
6192
            if ($result < 0) {
6193
                $error++;
6194
            } //Do also here what you must do to rollback action if trigger fail
6195
            // End call triggers
6196
        }
6197
6198
        // Commit or rollback
6199
        if ($error) {
6200
            $this->db->rollback();
6201
            return -1;
6202
        } else {
6203
            $this->db->commit();
6204
            return $this->id;
6205
        }
6206
    }
6207
6208
    /**
6209
     * Delete object in database
6210
     *
6211
     * @param User $user User that deletes
6212
     * @param bool $notrigger false=launch triggers after, true=disable triggers
6213
     * @param int $forcechilddeletion 0=no, 1=Force deletion of children
6214
     * @return    int                            <=0 if KO, >0 if OK
6215
     */
6216
    public function deleteCommon(User $user, $notrigger = false, $forcechilddeletion = 0)
6217
    {
6218
        $error = 0;
6219
6220
        $this->db->begin();
6221
6222
        if ($forcechilddeletion) {
6223
            foreach ($this->childtables as $table) {
6224
                $sql = 'DELETE FROM ' . MAIN_DB_PREFIX . $table . ' WHERE ' . $this->fk_element . ' = ' . $this->id;
6225
                $resql = $this->db->query($sql);
6226
                if (!$resql) {
6227
                    $this->error = $this->db->lasterror();
6228
                    $this->errors[] = $this->error;
6229
                    $this->db->rollback();
6230
                    return -1;
6231
                }
6232
            }
6233
        } elseif (!empty($this->fk_element) && !empty($this->childtables))    // If object has childs linked with a foreign key field, we check all child tables.
6234
        {
6235
            $objectisused = $this->isObjectUsed($this->id);
6236
            if (!empty($objectisused)) {
6237
                dol_syslog(get_class($this) . "::deleteCommon Can't delete record as it has some child", LOG_WARNING);
6238
                $this->error = 'ErrorRecordHasChildren';
6239
                $this->errors[] = $this->error;
6240
                $this->db->rollback();
6241
                return 0;
6242
            }
6243
        }
6244
6245
        if (!$error) {
6246
            if (!$notrigger) {
6247
                // Call triggers
6248
                $result = $this->call_trigger(strtoupper(get_class($this)) . '_DELETE', $user);
6249
                if ($result < 0) {
6250
                    $error++;
6251
                } // Do also here what you must do to rollback action if trigger fail
6252
                // End call triggers
6253
            }
6254
        }
6255
6256
        if (!$error && !empty($this->isextrafieldmanaged)) {
6257
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . $this->table_element . "_extrafields";
6258
            $sql .= " WHERE fk_object=" . $this->id;
6259
6260
            $resql = $this->db->query($sql);
6261
            if (!$resql) {
6262
                $this->errors[] = $this->db->lasterror();
6263
                $error++;
6264
            }
6265
        }
6266
6267
        if (!$error) {
6268
            $sql = 'DELETE FROM ' . MAIN_DB_PREFIX . $this->table_element . ' WHERE rowid=' . $this->id;
6269
6270
            $res = $this->db->query($sql);
6271
            if ($res === false) {
6272
                $error++;
6273
                $this->errors[] = $this->db->lasterror();
6274
            }
6275
        }
6276
6277
        // Commit or rollback
6278
        if ($error) {
6279
            $this->db->rollback();
6280
            return -1;
6281
        } else {
6282
            $this->db->commit();
6283
            return 1;
6284
        }
6285
    }
6286
6287
    /**
6288
     *  Function to check if an object is used by others.
6289
     *  Check is done into this->childtables. There is no check into llx_element_element.
6290
     *
6291
     * @param int $id Force id of object
6292
     * @return    int                    <0 if KO, 0 if not used, >0 if already used
6293
     */
6294
    function isObjectUsed($id = 0)
6295
    {
6296
        global $langs;
6297
6298
        if (empty($id)) $id = $this->id;
6299
6300
        // Check parameters
6301
        if (!isset($this->childtables) || !is_array($this->childtables) || count($this->childtables) == 0) {
6302
            dol_print_error('Called isObjectUsed on a class with property this->childtables not defined');
6303
            return -1;
6304
        }
6305
6306
        $arraytoscan = $this->childtables;
6307
        // For backward compatibility, we check if array is old format array('table1', 'table2', ...)
6308
        $tmparray = array_keys($this->childtables);
6309
        if (is_numeric($tmparray[0])) {
6310
            $arraytoscan = array_flip($this->childtables);
6311
        }
6312
6313
        // Test if child exists
6314
        $haschild = 0;
6315
        foreach ($arraytoscan as $table => $elementname) {
6316
            //print $id.'-'.$table.'-'.$elementname.'<br>';
6317
            // Check if third party can be deleted
6318
            $sql = "SELECT COUNT(*) as nb from " . MAIN_DB_PREFIX . $table;
6319
            $sql .= " WHERE " . $this->fk_element . " = " . $id;
6320
            $resql = $this->db->query($sql);
6321
            if ($resql) {
6322
                $obj = $this->db->fetch_object($resql);
6323
                if ($obj->nb > 0) {
6324
                    $langs->load("errors");
6325
                    //print 'Found into table '.$table.', type '.$langs->transnoentitiesnoconv($elementname).', haschild='.$haschild;
6326
                    $haschild += $obj->nb;
6327
                    if (is_numeric($elementname))    // old usage
6328
                    {
6329
                        $this->errors[] = $langs->trans("ErrorRecordHasAtLeastOneChildOfType", $table);
6330
                    } else    // new usage: $elementname=Translation key
6331
                    {
6332
                        $this->errors[] = $langs->trans("ErrorRecordHasAtLeastOneChildOfType", $langs->transnoentitiesnoconv($elementname));
6333
                    }
6334
                    break;    // We found at least one, we stop here
6335
                }
6336
            } else {
6337
                $this->errors[] = $this->db->lasterror();
6338
                return -1;
6339
            }
6340
        }
6341
        if ($haschild > 0) {
6342
            $this->errors[] = "ErrorRecordHasChildren";
6343
            return $haschild;
6344
        } else return 0;
6345
    }
6346
6347
    /**
6348
     * Initialise object with example values
6349
     * Id must be 0 if object instance is a specimen
6350
     *
6351
     * @return void
6352
     */
6353
    public function initAsSpecimenCommon()
6354
    {
6355
        $this->id = 0;
6356
6357
        // TODO...
6358
    }
6359
6360
    /**
6361
     * Load comments linked with current task
6362
     * @return boolean    1 if ok
6363
     */
6364
    public function fetchComments()
6365
    {
6366
        require_once DOL_DOCUMENT_ROOT . '/core/class/comment.class.php';
6367
6368
        $comment = new Comment($this->db);
6369
        $result = $comment->fetchAllFor($this->element, $this->id);
6370
        if ($result < 0) {
6371
            $this->errors = array_merge($this->errors, $comment->errors);
6372
            return -1;
6373
        } else {
6374
            $this->comments = $comment->comments;
6375
        }
6376
        return count($this->comments);
6377
    }
6378
6379
    /**
6380
     * Return nb comments already posted
6381
     *
6382
     * @return int
6383
     */
6384
    public function getNbComments()
6385
    {
6386
        return count($this->comments);
6387
    }
6388
6389
6390
    /* Part for comments */
6391
6392
    /**
6393
     * Trim object parameters
6394
     * @param string[] $parameters array of parameters to trim
6395
     *
6396
     * @return void
6397
     */
6398
    public function trimParameters($parameters)
6399
    {
6400
        if (!is_array($parameters)) return;
6401
        foreach ($parameters as $parameter) {
6402
            if (isset($this->$parameter)) {
6403
                $this->$parameter = trim($this->$parameter);
6404
            }
6405
        }
6406
    }
6407
6408
    /**
6409
     * Common function for all objects extending CommonObject for generating documents
6410
     *
6411
     * @param string $modelspath Relative folder where generators are placed
6412
     * @param string $modele Generator to use. Caller must set it to obj->modelpdf or GETPOST('modelpdf') for example.
6413
     * @param Translate $outputlangs Output language to use
6414
     * @param int $hidedetails 1 to hide details. 0 by default
6415
     * @param int $hidedesc 1 to hide product description. 0 by default
6416
     * @param int $hideref 1 to hide product reference. 0 by default
6417
     * @param null|array $moreparams Array to provide more information
6418
     * @return    int                        >0 if OK, <0 if KO
6419
     * @see    addFileIntoDatabaseIndex
6420
     */
6421
    protected function commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams = null)
6422
    {
6423
        global $conf, $langs, $user;
6424
6425
        $srctemplatepath = '';
6426
6427
        // Increase limit for PDF build
6428
        $err = error_reporting();
6429
        error_reporting(0);
6430
        @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

6430
        /** @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...
6431
        error_reporting($err);
6432
6433
        // If selected model is a filename template (then $modele="modelname" or "modelname:filename")
6434
        $tmp = explode(':', $modele, 2);
6435
        if (!empty($tmp[1])) {
6436
            $modele = $tmp[0];
6437
            $srctemplatepath = $tmp[1];
6438
        }
6439
6440
        // Search template files
6441
        $file = '';
6442
        $classname = '';
6443
        $filefound = 0;
6444
        $dirmodels = array('/');
6445
        if (is_array($conf->modules_parts['models'])) $dirmodels = array_merge($dirmodels, $conf->modules_parts['models']);
6446
        foreach ($dirmodels as $reldir) {
6447
            foreach (array('doc', 'pdf') as $prefix) {
6448
                if (in_array(get_class($this), array('Adherent'))) $file = $prefix . "_" . $modele . ".class.php";     // Member module use prefix_module.class.php
6449
                else $file = $prefix . "_" . $modele . ".modules.php";
6450
6451
                // On verifie l'emplacement du modele
6452
                $file = dol_buildpath($reldir . $modelspath . $file, 0);
6453
                if (file_exists($file)) {
6454
                    $filefound = 1;
6455
                    $classname = $prefix . '_' . $modele;
6456
                    break;
6457
                }
6458
            }
6459
            if ($filefound) break;
6460
        }
6461
6462
        // If generator was found
6463
        if ($filefound) {
6464
            global $db;  // Required to solve a conception default in commonstickergenerator.class.php making an include of code using $db
6465
6466
            require_once $file;
6467
6468
            $obj = new $classname($this->db);
6469
6470
            // If generator is ODT, we must have srctemplatepath defined, if not we set it.
6471
            if ($obj->type == 'odt' && empty($srctemplatepath)) {
6472
                $varfortemplatedir = $obj->scandir;
6473
                if ($varfortemplatedir && !empty($conf->global->$varfortemplatedir)) {
6474
                    $dirtoscan = $conf->global->$varfortemplatedir;
6475
6476
                    $listoffiles = array();
6477
6478
                    // Now we add first model found in directories scanned
6479
                    $listofdir = explode(',', $dirtoscan);
6480
                    foreach ($listofdir as $key => $tmpdir) {
6481
                        $tmpdir = trim($tmpdir);
6482
                        $tmpdir = preg_replace('/DOL_DATA_ROOT/', DOL_DATA_ROOT, $tmpdir);
6483
                        if (!$tmpdir) {
6484
                            unset($listofdir[$key]);
6485
                            continue;
6486
                        }
6487
                        if (is_dir($tmpdir)) {
6488
                            $tmpfiles = dol_dir_list($tmpdir, 'files', 0, '\.od(s|t)$', '', 'name', SORT_ASC, 0);
6489
                            if (count($tmpfiles)) $listoffiles = array_merge($listoffiles, $tmpfiles);
6490
                        }
6491
                    }
6492
6493
                    if (count($listoffiles)) {
6494
                        foreach ($listoffiles as $record) {
6495
                            $srctemplatepath = $record['fullname'];
6496
                            break;
6497
                        }
6498
                    }
6499
                }
6500
6501
                if (empty($srctemplatepath)) {
6502
                    $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotDefined';
6503
                    return -1;
6504
                }
6505
            }
6506
6507
            if ($obj->type == 'odt' && !empty($srctemplatepath)) {
6508
                if (!dol_is_file($srctemplatepath)) {
6509
                    $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotFound';
6510
                    return -1;
6511
                }
6512
            }
6513
6514
            // We save charset_output to restore it because write_file can change it if needed for
6515
            // output format that does not support UTF8.
6516
            $sav_charset_output = $outputlangs->charset_output;
6517
6518
            if (in_array(get_class($this), array('Adherent'))) {
6519
                $arrayofrecords = array();   // The write_file of templates of adherent class need this var
6520
                $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, 'member', 1, $moreparams);
6521
            } else {
6522
                $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, $hidedetails, $hidedesc, $hideref, $moreparams);
6523
            }
6524
            // After call of write_file $obj->result['fullpath'] is set with generated file. It will be used to update the ECM database index.
6525
6526
            if ($resultwritefile > 0) {
6527
                $outputlangs->charset_output = $sav_charset_output;
6528
6529
                // We delete old preview
6530
                require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
6531
                dol_delete_preview($this);
6532
6533
                // Index file in database
6534
                if (!empty($obj->result['fullpath'])) {
6535
                    $destfull = $obj->result['fullpath'];
6536
                    $upload_dir = dirname($destfull);
6537
                    $destfile = basename($destfull);
6538
                    $rel_dir = preg_replace('/^' . preg_quote(DOL_DATA_ROOT, '/') . '/', '', $upload_dir);
6539
6540
                    if (!preg_match('/[\\/]temp[\\/]|[\\/]thumbs|\.meta$/', $rel_dir))     // If not a tmp dir
6541
                    {
6542
                        $filename = basename($destfile);
6543
                        $rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
6544
                        $rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
6545
6546
                        include_once DOL_DOCUMENT_ROOT . '/ecm/class/ecmfiles.class.php';
6547
                        $ecmfile = new EcmFiles($this->db);
6548
                        $result = $ecmfile->fetch(0, '', ($rel_dir ? $rel_dir . '/' : '') . $filename);
6549
6550
                        // Set the public "share" key
6551
                        $setsharekey = false;
6552
                        if ($this->element == 'propal') {
6553
                            $useonlinesignature = $conf->global->MAIN_FEATURES_LEVEL;    // Replace this with 1 when feature to make online signature is ok
6554
                            if ($useonlinesignature) $setsharekey = true;
6555
                            if (!empty($conf->global->PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD)) $setsharekey = true;
6556
                        }
6557
                        if ($this->element == 'commande' && !empty($conf->global->ORDER_ALLOW_EXTERNAL_DOWNLOAD)) $setsharekey = true;
6558
                        if ($this->element == 'facture' && !empty($conf->global->INVOICE_ALLOW_EXTERNAL_DOWNLOAD)) $setsharekey = true;
6559
                        if ($this->element == 'bank_account' && !empty($conf->global->BANK_ACCOUNT_ALLOW_EXTERNAL_DOWNLOAD)) $setsharekey = true;
6560
6561
                        if ($setsharekey) {
6562
                            if (empty($ecmfile->share))    // Because object not found or share not set yet
6563
                            {
6564
                                require_once DOL_DOCUMENT_ROOT . '/core/lib/security2.lib.php';
6565
                                $ecmfile->share = getRandomPassword(true);
6566
                            }
6567
                        }
6568
6569
                        if ($result > 0) {
6570
                            $ecmfile->label = md5_file(dol_osencode($destfull));    // hash of file content
6571
                            $ecmfile->fullpath_orig = '';
6572
                            $ecmfile->gen_or_uploaded = 'generated';
6573
                            $ecmfile->description = '';    // indexed content
6574
                            $ecmfile->keyword = '';        // keyword content
6575
                            $result = $ecmfile->update($user);
6576
                            if ($result < 0) {
6577
                                setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
6578
                            }
6579
                        } else {
6580
                            $ecmfile->entity = $conf->entity;
6581
                            $ecmfile->filepath = $rel_dir;
6582
                            $ecmfile->filename = $filename;
6583
                            $ecmfile->label = md5_file(dol_osencode($destfull));    // hash of file content
6584
                            $ecmfile->fullpath_orig = '';
6585
                            $ecmfile->gen_or_uploaded = 'generated';
6586
                            $ecmfile->description = '';    // indexed content
6587
                            $ecmfile->keyword = '';        // keyword content
6588
                            $ecmfile->src_object_type = $this->table_element;
6589
                            $ecmfile->src_object_id = $this->id;
6590
6591
                            $result = $ecmfile->create($user);
6592
                            if ($result < 0) {
6593
                                setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
6594
                            }
6595
                        }
6596
6597
                        /*$this->result['fullname']=$destfull;
6598
						$this->result['filepath']=$ecmfile->filepath;
6599
						$this->result['filename']=$ecmfile->filename;*/
6600
                        //var_dump($obj->update_main_doc_field);exit;
6601
6602
                        // Update the last_main_doc field into main object (if documenent generator has property ->update_main_doc_field set)
6603
                        $update_main_doc_field = 0;
6604
                        if (!empty($obj->update_main_doc_field)) $update_main_doc_field = 1;
6605
                        if ($update_main_doc_field && !empty($this->table_element)) {
6606
                            $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element . " SET last_main_doc = '" . ($ecmfile->filepath . '/' . $ecmfile->filename) . "'";
6607
                            $sql .= ' WHERE rowid = ' . $this->id;
6608
                            $resql = $this->db->query($sql);
6609
                            if (!$resql) dol_print_error($this->db);
6610
                        }
6611
                    }
6612
                } else {
6613
                    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);
6614
                }
6615
6616
                // Success in building document. We build meta file.
6617
                dol_meta_create($this);
6618
6619
                return 1;
6620
            } else {
6621
                $outputlangs->charset_output = $sav_charset_output;
6622
                dol_print_error($this->db, "Error generating document for " . __CLASS__ . ". Error: " . $obj->error, $obj->errors);
6623
                return -1;
6624
            }
6625
        } else {
6626
            $this->error = $langs->trans("Error") . " " . $langs->trans("ErrorFileDoesNotExists", $file);
6627
            dol_print_error('', $this->error);
6628
            return -1;
6629
        }
6630
    }
6631
6632
    /**
6633
     * Function test if is indexed
6634
     *
6635
     * @param array $info content informations of field
6636
     * @return                  bool
6637
     */
6638
    protected function isIndex($info)
6639
    {
6640
        if (is_array($info)) {
6641
            if (isset($info['index']) && $info['index'] == true) return true;
6642
            else return false;
6643
        } else return false;
6644
    }
6645
}