Passed
Branch develop (59f205)
by
unknown
29:12
created

CommonObject::setValueFrom()   F

Complexity

Conditions 28
Paths > 20000

Size

Total Lines 85
Code Lines 55

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 28
eloc 55
nc 37120
nop 9
dl 0
loc 85
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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

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

1659
		return $this->/** @scrutinizer ignore-call */ fetch($result->rowid);

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

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

Loading history...
1660
	}
1661
1662
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1663
	/**
1664
	 *	Load data for barcode into properties ->barcode_type*
1665
	 *	Properties ->barcode_type that is id of barcode. Type is used to find other properties, but
1666
	 *  if it is not defined, ->element must be defined to know default barcode type.
1667
	 *
1668
	 *	@return		int			<0 if KO, 0 if can't guess type of barcode (ISBN, EAN13...), >0 if OK (all barcode properties loaded)
1669
	 */
1670
	public function fetch_barcode()
1671
	{
1672
		// phpcs:enable
1673
		global $conf;
1674
1675
		dol_syslog(get_class($this).'::fetch_barcode this->element='.$this->element.' this->barcode_type='.$this->barcode_type);
1676
1677
		$idtype = $this->barcode_type;
1678
		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
1679
			if ($this->element == 'product') {
1680
				$idtype = $conf->global->PRODUIT_DEFAULT_BARCODE_TYPE;
1681
			} elseif ($this->element == 'societe') {
1682
				$idtype = $conf->global->GENBARCODE_BARCODETYPE_THIRDPARTY;
1683
			} else {
1684
				dol_syslog('Call fetch_barcode with barcode_type not defined and cant be guessed', LOG_WARNING);
1685
			}
1686
		}
1687
1688
		if ($idtype > 0) {
1689
			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
1690
				$sql = "SELECT rowid, code, libelle as label, coder";
1691
				$sql .= " FROM ".MAIN_DB_PREFIX."c_barcode_type";
1692
				$sql .= " WHERE rowid = ".$idtype;
1693
				dol_syslog(get_class($this).'::fetch_barcode', LOG_DEBUG);
1694
				$resql = $this->db->query($sql);
1695
				if ($resql) {
1696
					$obj = $this->db->fetch_object($resql);
1697
					$this->barcode_type       = $obj->rowid;
1698
					$this->barcode_type_code  = $obj->code;
1699
					$this->barcode_type_label = $obj->label;
1700
					$this->barcode_type_coder = $obj->coder;
1701
					return 1;
1702
				} else {
1703
					dol_print_error($this->db);
1704
					return -1;
1705
				}
1706
			}
1707
		}
1708
		return 0;
1709
	}
1710
1711
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1712
	/**
1713
	 *		Load the project with id $this->fk_project into this->project
1714
	 *
1715
	 *		@return		int			<0 if KO, >=0 if OK
1716
	 */
1717
	public function fetch_projet()
1718
	{
1719
		// phpcs:enable
1720
		include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
1721
1722
		if (empty($this->fk_project) && !empty($this->fk_projet)) {
1723
			$this->fk_project = $this->fk_projet; // For backward compatibility
1724
		}
1725
		if (empty($this->fk_project)) {
1726
			return 0;
1727
		}
1728
1729
		$project = new Project($this->db);
1730
		$result = $project->fetch($this->fk_project);
1731
1732
		$this->projet = $project; // deprecated
1 ignored issue
show
Deprecated Code introduced by
The property CommonObject::$projet has been deprecated. ( Ignorable by Annotation )

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

1732
		/** @scrutinizer ignore-deprecated */ $this->projet = $project; // deprecated
Loading history...
1733
		$this->project = $project;
1734
		return $result;
1735
	}
1736
1737
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1738
	/**
1739
	 *		Load the product with id $this->fk_product into this->product
1740
	 *
1741
	 *		@return		int			<0 if KO, >=0 if OK
1742
	 */
1743
	public function fetch_product()
1744
	{
1745
		// phpcs:enable
1746
		include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
1747
1748
		if (empty($this->fk_product)) {
1749
			return 0;
1750
		}
1751
1752
		$product = new Product($this->db);
1753
		$result = $product->fetch($this->fk_product);
1754
1755
		$this->product = $product;
1756
		return $result;
1757
	}
1758
1759
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1760
	/**
1761
	 *		Load the user with id $userid into this->user
1762
	 *
1763
	 *		@param	int		$userid 		Id du contact
1764
	 *		@return	int						<0 if KO, >0 if OK
1765
	 */
1766
	public function fetch_user($userid)
1767
	{
1768
		// phpcs:enable
1769
		$user = new User($this->db);
1770
		$result = $user->fetch($userid);
1771
		$this->user = $user;
1772
		return $result;
1773
	}
1774
1775
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1776
	/**
1777
	 *	Read linked origin object
1778
	 *
1779
	 *	@return		void
1780
	 */
1781
	public function fetch_origin()
1782
	{
1783
		// phpcs:enable
1784
		if ($this->origin == 'shipping') {
1785
			$this->origin = 'expedition';
1786
		}
1787
		if ($this->origin == 'delivery') {
1788
			$this->origin = 'livraison';
1789
		}
1790
		if ($this->origin == 'order_supplier') {
1791
			$this->origin = 'commandeFournisseur';
1792
		}
1793
1794
		$origin = $this->origin;
1795
1796
		$classname = ucfirst($origin);
1797
		$this->$origin = new $classname($this->db);
1798
		$this->$origin->fetch($this->origin_id);
1799
	}
1800
1801
	/**
1802
	 *  Load object from specific field
1803
	 *
1804
	 *  @param	string	$table		Table element or element line
1805
	 *  @param	string	$field		Field selected
1806
	 *  @param	string	$key		Import key
1807
	 *  @param	string	$element	Element name
1808
	 *	@return	int					<0 if KO, >0 if OK
1809
	 */
1810
	public function fetchObjectFrom($table, $field, $key, $element = null)
1811
	{
1812
		global $conf;
1813
1814
		$result = false;
1815
1816
		$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX.$table;
1817
		$sql .= " WHERE ".$field." = '".$key."'";
1818
		if (!empty($element)) {
1819
			$sql .= " AND entity IN (".getEntity($element).")";
1820
		} else {
1821
			$sql .= " AND entity = ".$conf->entity;
1822
		}
1823
1824
		dol_syslog(get_class($this).'::fetchObjectFrom', LOG_DEBUG);
1825
		$resql = $this->db->query($sql);
1826
		if ($resql) {
1827
			$row = $this->db->fetch_row($resql);
1828
			// Test for avoid error -1
1829
			if ($row[0] > 0) {
1830
				$result = $this->fetch($row[0]);
1831
			}
1832
		}
1833
1834
		return $result;
1835
	}
1836
1837
	/**
1838
	 *	Getter generic. Load value from a specific field
1839
	 *
1840
	 *	@param	string	$table		Table of element or element line
1841
	 *	@param	int		$id			Element id
1842
	 *	@param	string	$field		Field selected
1843
	 *	@return	int					<0 if KO, >0 if OK
1844
	 */
1845
	public function getValueFrom($table, $id, $field)
1846
	{
1847
		$result = false;
1848
		if (!empty($id) && !empty($field) && !empty($table)) {
1849
			$sql = "SELECT ".$field." FROM ".MAIN_DB_PREFIX.$table;
1850
			$sql .= " WHERE rowid = ".$id;
1851
1852
			dol_syslog(get_class($this).'::getValueFrom', LOG_DEBUG);
1853
			$resql = $this->db->query($sql);
1854
			if ($resql) {
1855
				$row = $this->db->fetch_row($resql);
1856
				$result = $row[0];
1857
			}
1858
		}
1859
		return $result;
1860
	}
1861
1862
	/**
1863
	 *	Setter generic. Update a specific field into database.
1864
	 *  Warning: Trigger is run only if param trigkey is provided.
1865
	 *
1866
	 *	@param	string		$field			Field to update
1867
	 *	@param	mixed		$value			New value
1868
	 *	@param	string		$table			To force other table element or element line (should not be used)
1869
	 *	@param	int			$id				To force other object id (should not be used)
1870
	 *	@param	string		$format			Data format ('text', 'date'). 'text' is used if not defined
1871
	 *	@param	string		$id_field		To force rowid field name. 'rowid' is used if not defined
1872
	 *	@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'
1873
	 *  @param  string      $trigkey    	Trigger key to run (in most cases something like 'XXX_MODIFY')
1874
	 *  @param	string		$fk_user_field	Name of field to save user id making change
1875
	 *	@return	int							<0 if KO, >0 if OK
1876
	 *  @see updateExtraField()
1877
	 */
1878
	public function setValueFrom($field, $value, $table = '', $id = null, $format = '', $id_field = '', $fuser = null, $trigkey = '', $fk_user_field = 'fk_user_modif')
1879
	{
1880
		global $user, $langs, $conf;
1881
1882
		if (empty($table)) {
1883
			$table = $this->table_element;
1884
		}
1885
		if (empty($id)) {
1886
			$id = $this->id;
1887
		}
1888
		if (empty($format)) {
1889
			$format = 'text';
1890
		}
1891
		if (empty($id_field)) {
1892
			$id_field = 'rowid';
1893
		}
1894
1895
		$error = 0;
1896
1897
		$this->db->begin();
1898
1899
		// Special case
1900
		if ($table == 'product' && $field == 'note_private') {
1901
			$field = 'note';
1902
		}
1903
		if (in_array($table, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
1904
			$fk_user_field = 'fk_user_mod';
1905
		}
1906
1907
		$sql = "UPDATE ".MAIN_DB_PREFIX.$table." SET ";
1908
1909
		if ($format == 'text') {
1910
			$sql .= $field." = '".$this->db->escape($value)."'";
1911
		} elseif ($format == 'int') {
1912
			$sql .= $field." = ".$this->db->escape($value);
1913
		} elseif ($format == 'date') {
1914
			$sql .= $field." = ".($value ? "'".$this->db->idate($value)."'" : "null");
1915
		}
1916
1917
		if ($fk_user_field) {
1918
			if (!empty($fuser) && is_object($fuser)) {
1919
				$sql .= ", ".$fk_user_field." = ".$fuser->id;
1920
			} elseif (empty($fuser) || $fuser != 'none') {
1921
				$sql .= ", ".$fk_user_field." = ".$user->id;
1922
			}
1923
		}
1924
1925
		$sql .= " WHERE ".$id_field." = ".$id;
1926
1927
		dol_syslog(__METHOD__."", LOG_DEBUG);
1928
		$resql = $this->db->query($sql);
1929
		if ($resql) {
1930
			if ($trigkey) {
1931
				// call trigger with updated object values
1932
				if (empty($this->fields) && method_exists($this, 'fetch')) {
1933
					$result = $this->fetch($id);
1934
				} else {
1935
					$result = $this->fetchCommon($id);
1936
				}
1937
				if ($result >= 0) {
1938
					$result = $this->call_trigger($trigkey, (!empty($fuser) && is_object($fuser)) ? $fuser : $user); // This may set this->errors
1939
				}
1940
				if ($result < 0) {
1941
					$error++;
1942
				}
1943
			}
1944
1945
			if (!$error) {
1946
				if (property_exists($this, $field)) {
1947
					$this->$field = $value;
1948
				}
1949
				$this->db->commit();
1950
				return 1;
1951
			} else {
1952
				$this->db->rollback();
1953
				return -2;
1954
			}
1955
		} else {
1956
			if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1957
				$this->error = 'DB_ERROR_RECORD_ALREADY_EXISTS';
1958
			} else {
1959
				$this->error = $this->db->lasterror();
1960
			}
1961
			$this->db->rollback();
1962
			return -1;
1963
		}
1964
	}
1965
1966
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1967
	/**
1968
	 *      Load properties id_previous and id_next by comparing $fieldid with $this->ref
1969
	 *
1970
	 *      @param	string	$filter		Optional filter. Example: " AND (t.field1 = 'aa' OR t.field2 = 'bb')"
1971
	 *	 	@param  string	$fieldid   	Name of field to use for the select MAX and MIN
1972
	 *		@param	int		$nodbprefix	Do not include DB prefix to forge table name
1973
	 *      @return int         		<0 if KO, >0 if OK
1974
	 */
1975
	public function load_previous_next_ref($filter, $fieldid, $nodbprefix = 0)
1976
	{
1977
		// phpcs:enable
1978
		global $conf, $user;
1979
1980
		if (!$this->table_element) {
1981
			dol_print_error('', get_class($this)."::load_previous_next_ref was called on objet with property table_element not defined");
1982
			return -1;
1983
		}
1984
		if ($fieldid == 'none') {
1985
			return 1;
1986
		}
1987
1988
		// Security on socid
1989
		$socid = 0;
1990
		if ($user->socid > 0) {
1991
			$socid = $user->socid;
1992
		}
1993
1994
		// this->ismultientitymanaged contains
1995
		// 0=No test on entity, 1=Test with field entity, 'field@table'=Test with link by field@table
1996
		$aliastablesociete = 's';
1997
		if ($this->element == 'societe') {
1998
			$aliastablesociete = 'te'; // te as table_element
1999
		}
2000
2001
		$sql = "SELECT MAX(te.".$fieldid.")";
2002
		$sql .= " FROM ".(empty($nodbprefix) ?MAIN_DB_PREFIX:'').$this->table_element." as te";
2003
		if ($this->element == 'user' && !empty($conf->global->MULTICOMPANY_TRANSVERSE_MODE)) {
2004
			$sql .= ",".MAIN_DB_PREFIX."usergroup_user as ug";
2005
		}
2006
		if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2007
			$tmparray = explode('@', $this->ismultientitymanaged);
2008
			$sql .= ", ".MAIN_DB_PREFIX.$tmparray[1]." as ".($tmparray[1] == 'societe' ? 's' : 'parenttable'); // If we need to link to this table to limit select to entity
2009
		} elseif ($this->restrictiononfksoc == 1 && $this->element != 'societe' && !$user->rights->societe->client->voir && !$socid) {
2010
			$sql .= ", ".MAIN_DB_PREFIX."societe as s"; // If we need to link to societe to limit select to socid
2011
		} elseif ($this->restrictiononfksoc == 2 && $this->element != 'societe' && !$user->rights->societe->client->voir && !$socid) {
2012
			$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
2013
		}
2014
		if ($this->restrictiononfksoc && !$user->rights->societe->client->voir && !$socid) {
2015
			$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
2016
		}
2017
		$sql .= " WHERE te.".$fieldid." < '".$this->db->escape($fieldid == 'rowid' ? $this->id : $this->ref)."'"; // ->ref must always be defined (set to id if field does not exists)
2018
		if ($this->restrictiononfksoc == 1 && !$user->rights->societe->client->voir && !$socid) {
2019
			$sql .= " AND sc.fk_user = ".$user->id;
2020
		}
2021
		if ($this->restrictiononfksoc == 2 && !$user->rights->societe->client->voir && !$socid) {
2022
			$sql .= " AND (sc.fk_user = ".$user->id.' OR te.fk_soc IS NULL)';
2023
		}
2024
		if (!empty($filter)) {
2025
			if (!preg_match('/^\s*AND/i', $filter)) {
2026
				$sql .= " AND "; // For backward compatibility
2027
			}
2028
			$sql .= $filter;
2029
		}
2030
		if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2031
			$tmparray = explode('@', $this->ismultientitymanaged);
2032
			$sql .= ' AND te.'.$tmparray[0].' = '.($tmparray[1] == 'societe' ? 's' : 'parenttable').'.rowid'; // If we need to link to this table to limit select to entity
2033
		} elseif ($this->restrictiononfksoc == 1 && $this->element != 'societe' && !$user->rights->societe->client->voir && !$socid) {
2034
			$sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2035
		}
2036
		if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2037
			if ($this->element == 'user' && !empty($conf->global->MULTICOMPANY_TRANSVERSE_MODE)) {
2038
				if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2039
					$sql .= " AND te.entity IS NOT NULL"; // Show all users
2040
				} else {
2041
					$sql .= " AND ug.fk_user = te.rowid";
2042
					$sql .= " AND ug.entity IN (".getEntity($this->element).")";
2043
				}
2044
			} else {
2045
				$sql .= ' AND te.entity IN ('.getEntity($this->element).')';
2046
			}
2047
		}
2048
		if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2049
			$tmparray = explode('@', $this->ismultientitymanaged);
2050
			$sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
2051
		}
2052
		if ($this->restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2053
			$sql .= ' AND te.fk_soc = '.$socid;
2054
		}
2055
		if ($this->restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2056
			$sql .= ' AND (te.fk_soc = '.$socid.' OR te.fk_soc IS NULL)';
2057
		}
2058
		if ($this->restrictiononfksoc && $socid && $this->element == 'societe') {
2059
			$sql .= ' AND te.rowid = '.$socid;
2060
		}
2061
		//print 'socid='.$socid.' restrictiononfksoc='.$this->restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2062
2063
		$result = $this->db->query($sql);
2064
		if (!$result) {
2065
			$this->error = $this->db->lasterror();
2066
			return -1;
2067
		}
2068
		$row = $this->db->fetch_row($result);
2069
		$this->ref_previous = $row[0];
2070
2071
		$sql = "SELECT MIN(te.".$fieldid.")";
2072
		$sql .= " FROM ".(empty($nodbprefix) ?MAIN_DB_PREFIX:'').$this->table_element." as te";
2073
		if ($this->element == 'user' && !empty($conf->global->MULTICOMPANY_TRANSVERSE_MODE)) {
2074
			$sql .= ",".MAIN_DB_PREFIX."usergroup_user as ug";
2075
		}
2076
		if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2077
			$tmparray = explode('@', $this->ismultientitymanaged);
2078
			$sql .= ", ".MAIN_DB_PREFIX.$tmparray[1]." as ".($tmparray[1] == 'societe' ? 's' : 'parenttable'); // If we need to link to this table to limit select to entity
2079
		} elseif ($this->restrictiononfksoc == 1 && $this->element != 'societe' && !$user->rights->societe->client->voir && !$socid) {
2080
			$sql .= ", ".MAIN_DB_PREFIX."societe as s"; // If we need to link to societe to limit select to socid
2081
		} elseif ($this->restrictiononfksoc == 2 && $this->element != 'societe' && !$user->rights->societe->client->voir && !$socid) {
2082
			$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
2083
		}
2084
		if ($this->restrictiononfksoc && !$user->rights->societe->client->voir && !$socid) {
2085
			$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
2086
		}
2087
		$sql .= " WHERE te.".$fieldid." > '".$this->db->escape($fieldid == 'rowid' ? $this->id : $this->ref)."'"; // ->ref must always be defined (set to id if field does not exists)
2088
		if ($this->restrictiononfksoc == 1 && !$user->rights->societe->client->voir && !$socid) {
2089
			$sql .= " AND sc.fk_user = ".$user->id;
2090
		}
2091
		if ($this->restrictiononfksoc == 2 && !$user->rights->societe->client->voir && !$socid) {
2092
			$sql .= " AND (sc.fk_user = ".$user->id.' OR te.fk_soc IS NULL)';
2093
		}
2094
		if (!empty($filter)) {
2095
			if (!preg_match('/^\s*AND/i', $filter)) {
2096
				$sql .= " AND "; // For backward compatibility
2097
			}
2098
			$sql .= $filter;
2099
		}
2100
		if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2101
			$tmparray = explode('@', $this->ismultientitymanaged);
2102
			$sql .= ' AND te.'.$tmparray[0].' = '.($tmparray[1] == 'societe' ? 's' : 'parenttable').'.rowid'; // If we need to link to this table to limit select to entity
2103
		} elseif ($this->restrictiononfksoc == 1 && $this->element != 'societe' && !$user->rights->societe->client->voir && !$socid) {
2104
			$sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2105
		}
2106
		if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2107
			if ($this->element == 'user' && !empty($conf->global->MULTICOMPANY_TRANSVERSE_MODE)) {
2108
				if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2109
					$sql .= " AND te.entity IS NOT NULL"; // Show all users
2110
				} else {
2111
					$sql .= " AND ug.fk_user = te.rowid";
2112
					$sql .= " AND ug.entity IN (".getEntity($this->element).")";
2113
				}
2114
			} else {
2115
				$sql .= ' AND te.entity IN ('.getEntity($this->element).')';
2116
			}
2117
		}
2118
		if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2119
			$tmparray = explode('@', $this->ismultientitymanaged);
2120
			$sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
2121
		}
2122
		if ($this->restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2123
			$sql .= ' AND te.fk_soc = '.$socid;
2124
		}
2125
		if ($this->restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2126
			$sql .= ' AND (te.fk_soc = '.$socid.' OR te.fk_soc IS NULL)';
2127
		}
2128
		if ($this->restrictiononfksoc && $socid && $this->element == 'societe') {
2129
			$sql .= ' AND te.rowid = '.$socid;
2130
		}
2131
		//print 'socid='.$socid.' restrictiononfksoc='.$this->restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2132
		// 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
2133
2134
		$result = $this->db->query($sql);
2135
		if (!$result) {
2136
			$this->error = $this->db->lasterror();
2137
			return -2;
2138
		}
2139
		$row = $this->db->fetch_row($result);
2140
		$this->ref_next = $row[0];
2141
2142
		return 1;
2143
	}
2144
2145
2146
	/**
2147
	 *      Return list of id of contacts of object
2148
	 *
2149
	 *      @param	string	$source     Source of contact: external (llx_socpeople) or internal (llx_user) or thirdparty (llx_societe)
2150
	 *      @return array				Array of id of contacts (if source=external or internal)
2151
	 * 									Array of id of third parties with at least one contact on object (if source=thirdparty)
2152
	 */
2153
	public function getListContactId($source = 'external')
2154
	{
2155
		$contactAlreadySelected = array();
2156
		$tab = $this->liste_contact(-1, $source);
2157
		$num = count($tab);
2158
		$i = 0;
2159
		while ($i < $num) {
2160
			if ($source == 'thirdparty') {
2161
				$contactAlreadySelected[$i] = $tab[$i]['socid'];
2162
			} else {
2163
				$contactAlreadySelected[$i] = $tab[$i]['id'];
2164
			}
2165
			$i++;
2166
		}
2167
		return $contactAlreadySelected;
2168
	}
2169
2170
2171
	/**
2172
	 *	Link element with a project
2173
	 *
2174
	 *	@param     	int		$projectid		Project id to link element to
2175
	 *	@return		int						<0 if KO, >0 if OK
2176
	 */
2177
	public function setProject($projectid)
2178
	{
2179
		if (!$this->table_element) {
2180
			dol_syslog(get_class($this)."::setProject was called on objet with property table_element not defined", LOG_ERR);
2181
			return -1;
2182
		}
2183
2184
		$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2185
		if (!empty($this->fields['fk_project'])) {		// Common case
2186
			if ($projectid) {
2187
				$sql .= ' SET fk_project = '.$projectid;
2188
			} else {
2189
				$sql .= ' SET fk_project = NULL';
2190
			}
2191
			$sql .= ' WHERE rowid = '.$this->id;
2192
		} elseif ($this->table_element == 'actioncomm') {	// Special case for actioncomm
2193
			if ($projectid) {
2194
				$sql .= ' SET fk_project = '.$projectid;
2195
			} else {
2196
				$sql .= ' SET fk_project = NULL';
2197
			}
2198
			$sql .= ' WHERE id = '.$this->id;
2199
		} else // Special case for old architecture objects
2200
		{
2201
			if ($projectid) {
2202
				$sql .= ' SET fk_projet = '.$projectid;
2203
			} else {
2204
				$sql .= ' SET fk_projet = NULL';
2205
			}
2206
			$sql .= ' WHERE rowid = '.$this->id;
2207
		}
2208
2209
		dol_syslog(get_class($this)."::setProject", LOG_DEBUG);
2210
		if ($this->db->query($sql)) {
2211
			$this->fk_project = $projectid;
2212
			return 1;
2213
		} else {
2214
			dol_print_error($this->db);
2215
			return -1;
2216
		}
2217
	}
2218
2219
	/**
2220
	 *  Change the payments methods
2221
	 *
2222
	 *  @param		int		$id		Id of new payment method
2223
	 *  @return		int				>0 if OK, <0 if KO
2224
	 */
2225
	public function setPaymentMethods($id)
2226
	{
2227
		dol_syslog(get_class($this).'::setPaymentMethods('.$id.')');
2228
		if ($this->statut >= 0 || $this->element == 'societe') {
2229
			// TODO uniformize field name
2230
			$fieldname = 'fk_mode_reglement';
2231
			if ($this->element == 'societe') {
2232
				$fieldname = 'mode_reglement';
2233
			}
2234
			if (get_class($this) == 'Fournisseur') {
2235
				$fieldname = 'mode_reglement_supplier';
2236
			}
2237
			if (get_class($this) == 'Tva') {
2238
				$fieldname = 'fk_typepayment';
2239
			}
2240
			if (get_class($this) == 'Salary') {
2241
				$fieldname = 'fk_typepayment';
2242
			}
2243
2244
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2245
			$sql .= ' SET '.$fieldname.' = '.(($id > 0 || $id == '0') ? $id : 'NULL');
2246
			$sql .= ' WHERE rowid='.$this->id;
2247
2248
			if ($this->db->query($sql)) {
2249
				$this->mode_reglement_id = $id;
2250
				// for supplier
2251
				if (get_class($this) == 'Fournisseur') {
2252
					$this->mode_reglement_supplier_id = $id;
2253
				}
2254
				return 1;
2255
			} else {
2256
				dol_syslog(get_class($this).'::setPaymentMethods Error '.$sql.' - '.$this->db->error());
2257
				$this->error = $this->db->error();
2258
				return -1;
2259
			}
2260
		} else {
2261
			dol_syslog(get_class($this).'::setPaymentMethods, status of the object is incompatible');
2262
			$this->error = 'Status of the object is incompatible '.$this->statut;
2263
			return -2;
2264
		}
2265
	}
2266
2267
	/**
2268
	 *  Change the multicurrency code
2269
	 *
2270
	 *  @param		string	$code	multicurrency code
2271
	 *  @return		int				>0 if OK, <0 if KO
2272
	 */
2273
	public function setMulticurrencyCode($code)
2274
	{
2275
		dol_syslog(get_class($this).'::setMulticurrencyCode('.$code.')');
2276
		if ($this->statut >= 0 || $this->element == 'societe') {
2277
			$fieldname = 'multicurrency_code';
2278
2279
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2280
			$sql .= ' SET '.$fieldname." = '".$this->db->escape($code)."'";
2281
			$sql .= ' WHERE rowid='.$this->id;
2282
2283
			if ($this->db->query($sql)) {
2284
				$this->multicurrency_code = $code;
2285
2286
				list($fk_multicurrency, $rate) = MultiCurrency::getIdAndTxFromCode($this->db, $code);
2287
				if ($rate) {
2288
					$this->setMulticurrencyRate($rate, 2);
2289
				}
2290
2291
				return 1;
2292
			} else {
2293
				dol_syslog(get_class($this).'::setMulticurrencyCode Error '.$sql.' - '.$this->db->error());
2294
				$this->error = $this->db->error();
2295
				return -1;
2296
			}
2297
		} else {
2298
			dol_syslog(get_class($this).'::setMulticurrencyCode, status of the object is incompatible');
2299
			$this->error = 'Status of the object is incompatible '.$this->statut;
2300
			return -2;
2301
		}
2302
	}
2303
2304
	/**
2305
	 *  Change the multicurrency rate
2306
	 *
2307
	 *  @param		double	$rate	multicurrency rate
2308
	 *  @param		int		$mode	mode 1 : amounts in company currency will be recalculated, mode 2 : amounts in foreign currency will be recalculated
2309
	 *  @return		int				>0 if OK, <0 if KO
2310
	 */
2311
	public function setMulticurrencyRate($rate, $mode = 1)
2312
	{
2313
		dol_syslog(get_class($this).'::setMulticurrencyRate('.$rate.','.$mode.')');
2314
		if ($this->statut >= 0 || $this->element == 'societe') {
2315
			$fieldname = 'multicurrency_tx';
2316
2317
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2318
			$sql .= ' SET '.$fieldname.' = '.$rate;
2319
			$sql .= ' WHERE rowid='.$this->id;
2320
2321
			if ($this->db->query($sql)) {
2322
				$this->multicurrency_tx = $rate;
2323
2324
				// Update line price
2325
				if (!empty($this->lines)) {
2326
					foreach ($this->lines as &$line) {
2327
						// Amounts in company currency will be recalculated
2328
						if ($mode == 1) {
2329
							$line->subprice = 0;
2330
						}
2331
2332
						// Amounts in foreign currency will be recalculated
2333
						if ($mode == 2) {
2334
							$line->multicurrency_subprice = 0;
2335
						}
2336
2337
						switch ($this->element) {
2338
							case 'propal':
2339
								$this->updateline(
1 ignored issue
show
Bug introduced by
The method updateline() does not exist on CommonObject. Did you maybe mean updateLineUp()? ( Ignorable by Annotation )

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

2339
								$this->/** @scrutinizer ignore-call */ 
2340
               updateline(

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

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

Loading history...
2340
									$line->id,
2341
									$line->subprice,
2342
									$line->qty,
2343
									$line->remise_percent,
2344
									$line->tva_tx,
2345
									$line->localtax1_tx,
2346
									$line->localtax2_tx,
2347
									($line->description ? $line->description : $line->desc),
2348
									'HT',
2349
									$line->info_bits,
2350
									$line->special_code,
2351
									$line->fk_parent_line,
2352
									$line->skip_update_total,
2353
									$line->fk_fournprice,
2354
									$line->pa_ht,
2355
									$line->label,
2356
									$line->product_type,
2357
									$line->date_start,
2358
									$line->date_end,
2359
									$line->array_options,
2360
									$line->fk_unit,
2361
									$line->multicurrency_subprice
2362
								);
2363
								break;
2364
							case 'commande':
2365
								$this->updateline(
2366
									$line->id,
2367
									($line->description ? $line->description : $line->desc),
2368
									$line->subprice,
2369
									$line->qty,
2370
									$line->remise_percent,
2371
									$line->tva_tx,
2372
									$line->localtax1_tx,
2373
									$line->localtax2_tx,
2374
									'HT',
2375
									$line->info_bits,
2376
									$line->date_start,
2377
									$line->date_end,
2378
									$line->product_type,
2379
									$line->fk_parent_line,
2380
									$line->skip_update_total,
2381
									$line->fk_fournprice,
2382
									$line->pa_ht,
2383
									$line->label,
2384
									$line->special_code,
2385
									$line->array_options,
2386
									$line->fk_unit,
2387
									$line->multicurrency_subprice
2388
								);
2389
								break;
2390
							case 'facture':
2391
								$this->updateline(
2392
									$line->id,
2393
									($line->description ? $line->description : $line->desc),
2394
									$line->subprice,
2395
									$line->qty,
2396
									$line->remise_percent,
2397
									$line->date_start,
2398
									$line->date_end,
2399
									$line->tva_tx,
2400
									$line->localtax1_tx,
2401
									$line->localtax2_tx,
2402
									'HT',
2403
									$line->info_bits,
2404
									$line->product_type,
2405
									$line->fk_parent_line,
2406
									$line->skip_update_total,
2407
									$line->fk_fournprice,
2408
									$line->pa_ht,
2409
									$line->label,
2410
									$line->special_code,
2411
									$line->array_options,
2412
									$line->situation_percent,
2413
									$line->fk_unit,
2414
									$line->multicurrency_subprice
2415
								);
2416
								break;
2417
							case 'supplier_proposal':
2418
								$this->updateline(
2419
									$line->id,
2420
									$line->subprice,
2421
									$line->qty,
2422
									$line->remise_percent,
2423
									$line->tva_tx,
2424
									$line->localtax1_tx,
2425
									$line->localtax2_tx,
2426
									($line->description ? $line->description : $line->desc),
2427
									'HT',
2428
									$line->info_bits,
2429
									$line->special_code,
2430
									$line->fk_parent_line,
2431
									$line->skip_update_total,
2432
									$line->fk_fournprice,
2433
									$line->pa_ht,
2434
									$line->label,
2435
									$line->product_type,
2436
									$line->array_options,
2437
									$line->ref_fourn,
2438
									$line->multicurrency_subprice
2439
								);
2440
								break;
2441
							case 'order_supplier':
2442
								$this->updateline(
2443
									$line->id,
2444
									($line->description ? $line->description : $line->desc),
2445
									$line->subprice,
2446
									$line->qty,
2447
									$line->remise_percent,
2448
									$line->tva_tx,
2449
									$line->localtax1_tx,
2450
									$line->localtax2_tx,
2451
									'HT',
2452
									$line->info_bits,
2453
									$line->product_type,
2454
									false,
2455
									$line->date_start,
2456
									$line->date_end,
2457
									$line->array_options,
2458
									$line->fk_unit,
2459
									$line->multicurrency_subprice,
2460
									$line->ref_supplier
2461
								);
2462
								break;
2463
							case 'invoice_supplier':
2464
								$this->updateline(
2465
									$line->id,
2466
									($line->description ? $line->description : $line->desc),
2467
									$line->subprice,
2468
									$line->tva_tx,
2469
									$line->localtax1_tx,
2470
									$line->localtax2_tx,
2471
									$line->qty,
2472
									0,
2473
									'HT',
2474
									$line->info_bits,
2475
									$line->product_type,
2476
									$line->remise_percent,
2477
									false,
2478
									$line->date_start,
2479
									$line->date_end,
2480
									$line->array_options,
2481
									$line->fk_unit,
2482
									$line->multicurrency_subprice,
2483
									$line->ref_supplier
2484
								);
2485
								break;
2486
							default:
2487
								dol_syslog(get_class($this).'::setMulticurrencyRate no updateline defined', LOG_DEBUG);
2488
								break;
2489
						}
2490
					}
2491
				}
2492
2493
				return 1;
2494
			} else {
2495
				dol_syslog(get_class($this).'::setMulticurrencyRate Error '.$sql.' - '.$this->db->error());
2496
				$this->error = $this->db->error();
2497
				return -1;
2498
			}
2499
		} else {
2500
			dol_syslog(get_class($this).'::setMulticurrencyRate, status of the object is incompatible');
2501
			$this->error = 'Status of the object is incompatible '.$this->statut;
2502
			return -2;
2503
		}
2504
	}
2505
2506
	/**
2507
	 *  Change the payments terms
2508
	 *
2509
	 *  @param		int		$id		Id of new payment terms
2510
	 *  @return		int				>0 if OK, <0 if KO
2511
	 */
2512
	public function setPaymentTerms($id)
2513
	{
2514
		dol_syslog(get_class($this).'::setPaymentTerms('.$id.')');
2515
		if ($this->statut >= 0 || $this->element == 'societe') {
2516
			// TODO uniformize field name
2517
			$fieldname = 'fk_cond_reglement';
2518
			if ($this->element == 'societe') {
2519
				$fieldname = 'cond_reglement';
2520
			}
2521
			if (get_class($this) == 'Fournisseur') {
2522
				$fieldname = 'cond_reglement_supplier';
2523
			}
2524
2525
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2526
			$sql .= ' SET '.$fieldname.' = '.(($id > 0 || $id == '0') ? $id : 'NULL');
2527
			$sql .= ' WHERE rowid='.$this->id;
2528
2529
			if ($this->db->query($sql)) {
2530
				$this->cond_reglement_id = $id;
2531
				// for supplier
2532
				if (get_class($this) == 'Fournisseur') {
2533
					$this->cond_reglement_supplier_id = $id;
2534
				}
2535
				$this->cond_reglement = $id; // for compatibility
2536
				return 1;
2537
			} else {
2538
				dol_syslog(get_class($this).'::setPaymentTerms Error '.$sql.' - '.$this->db->error());
2539
				$this->error = $this->db->error();
2540
				return -1;
2541
			}
2542
		} else {
2543
			dol_syslog(get_class($this).'::setPaymentTerms, status of the object is incompatible');
2544
			$this->error = 'Status of the object is incompatible '.$this->statut;
2545
			return -2;
2546
		}
2547
	}
2548
2549
	/**
2550
	 *  Change the transport mode methods
2551
	 *
2552
	 *  @param		int		$id		Id of new payment method
2553
	 *  @return		int				>0 if OK, <0 if KO
2554
	 */
2555
	public function setTransportMode($id)
2556
	{
2557
		dol_syslog(get_class($this).'::setTransportMode('.$id.')');
2558
		if ($this->statut >= 0 || $this->element == 'societe') {
2559
			$fieldname = 'fk_transport_mode';
2560
			if ($this->element == 'societe') {
2561
				$fieldname = 'transport_mode';
2562
			}
2563
			if (get_class($this) == 'Fournisseur') {
2564
				$fieldname = 'transport_mode_supplier';
2565
			}
2566
2567
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2568
			$sql .= ' SET '.$fieldname.' = '.(($id > 0 || $id == '0') ? $id : 'NULL');
2569
			$sql .= ' WHERE rowid='.$this->id;
2570
2571
			if ($this->db->query($sql)) {
2572
				$this->transport_mode_id = $id;
2573
				// for supplier
2574
				if (get_class($this) == 'Fournisseur') {
2575
					$this->transport_mode_supplier_id = $id;
2576
				}
2577
				return 1;
2578
			} else {
2579
				dol_syslog(get_class($this).'::setTransportMode Error '.$sql.' - '.$this->db->error());
2580
				$this->error = $this->db->error();
2581
				return -1;
2582
			}
2583
		} else {
2584
			dol_syslog(get_class($this).'::setTransportMode, status of the object is incompatible');
2585
			$this->error = 'Status of the object is incompatible '.$this->statut;
2586
			return -2;
2587
		}
2588
	}
2589
2590
	/**
2591
	 *  Change the retained warranty payments terms
2592
	 *
2593
	 *  @param		int		$id		Id of new payment terms
2594
	 *  @return		int				>0 if OK, <0 if KO
2595
	 */
2596
	public function setRetainedWarrantyPaymentTerms($id)
2597
	{
2598
		dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms('.$id.')');
2599
		if ($this->statut >= 0 || $this->element == 'societe') {
2600
			$fieldname = 'retained_warranty_fk_cond_reglement';
2601
2602
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2603
			$sql .= ' SET '.$fieldname.' = '.$id;
2604
			$sql .= ' WHERE rowid='.$this->id;
2605
2606
			if ($this->db->query($sql)) {
2607
				$this->retained_warranty_fk_cond_reglement = $id;
2608
				return 1;
2609
			} else {
2610
				dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms Error '.$sql.' - '.$this->db->error());
2611
				$this->error = $this->db->error();
2612
				return -1;
2613
			}
2614
		} else {
2615
			dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms, status of the object is incompatible');
2616
			$this->error = 'Status of the object is incompatible '.$this->statut;
2617
			return -2;
2618
		}
2619
	}
2620
2621
	/**
2622
	 *	Define delivery address
2623
	 *  @deprecated
2624
	 *
2625
	 *	@param      int		$id		Address id
2626
	 *	@return     int				<0 si ko, >0 si ok
2627
	 */
2628
	public function setDeliveryAddress($id)
2629
	{
2630
		$fieldname = 'fk_delivery_address';
2631
		if ($this->element == 'delivery' || $this->element == 'shipping') {
2632
			$fieldname = 'fk_address';
2633
		}
2634
2635
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET ".$fieldname." = ".$id;
2636
		$sql .= " WHERE rowid = ".$this->id." AND fk_statut = 0";
2637
2638
		if ($this->db->query($sql)) {
2639
			$this->fk_delivery_address = $id;
1 ignored issue
show
Deprecated Code introduced by
The property CommonObject::$fk_delivery_address has been deprecated. ( Ignorable by Annotation )

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

2639
			/** @scrutinizer ignore-deprecated */ $this->fk_delivery_address = $id;
Loading history...
2640
			return 1;
2641
		} else {
2642
			$this->error = $this->db->error();
2643
			dol_syslog(get_class($this).'::setDeliveryAddress Error '.$sql.' - '.$this->error);
2644
			return -1;
2645
		}
2646
	}
2647
2648
2649
	/**
2650
	 *  Change the shipping method
2651
	 *
2652
	 *  @param      int     $shipping_method_id     Id of shipping method
2653
	 *  @param      bool    $notrigger              false=launch triggers after, true=disable triggers
2654
	 *  @param      User	$userused               Object user
2655
	 *
2656
	 *  @return     int              1 if OK, 0 if KO
2657
	 */
2658
	public function setShippingMethod($shipping_method_id, $notrigger = false, $userused = null)
2659
	{
2660
		global $user;
2661
2662
		if (empty($userused)) {
2663
			$userused = $user;
2664
		}
2665
2666
		$error = 0;
2667
2668
		if (!$this->table_element) {
2669
			dol_syslog(get_class($this)."::setShippingMethod was called on objet with property table_element not defined", LOG_ERR);
2670
			return -1;
2671
		}
2672
2673
		$this->db->begin();
2674
2675
		if ($shipping_method_id < 0) {
2676
			$shipping_method_id = 'NULL';
2677
		}
2678
		dol_syslog(get_class($this).'::setShippingMethod('.$shipping_method_id.')');
2679
2680
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2681
		$sql .= " SET fk_shipping_method = ".$shipping_method_id;
2682
		$sql .= " WHERE rowid=".$this->id;
2683
		$resql = $this->db->query($sql);
2684
		if (!$resql) {
2685
			dol_syslog(get_class($this).'::setShippingMethod Error ', LOG_DEBUG);
2686
			$this->error = $this->db->lasterror();
2687
			$error++;
2688
		} else {
2689
			if (!$notrigger) {
2690
				// Call trigger
2691
				$this->context = array('shippingmethodupdate'=>1);
2692
				$result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $userused);
2693
				if ($result < 0) {
2694
					$error++;
2695
				}
2696
				// End call trigger
2697
			}
2698
		}
2699
		if ($error) {
2700
			$this->db->rollback();
2701
			return -1;
2702
		} else {
2703
			$this->shipping_method_id = ($shipping_method_id == 'NULL') ?null:$shipping_method_id;
2704
			$this->db->commit();
2705
			return 1;
2706
		}
2707
	}
2708
2709
2710
	/**
2711
	 *  Change the warehouse
2712
	 *
2713
	 *  @param      int     $warehouse_id     Id of warehouse
2714
	 *  @return     int              1 if OK, 0 if KO
2715
	 */
2716
	public function setWarehouse($warehouse_id)
2717
	{
2718
		if (!$this->table_element) {
2719
			dol_syslog(get_class($this)."::setWarehouse was called on objet with property table_element not defined", LOG_ERR);
2720
			return -1;
2721
		}
2722
		if ($warehouse_id < 0) {
2723
			$warehouse_id = 'NULL';
2724
		}
2725
		dol_syslog(get_class($this).'::setWarehouse('.$warehouse_id.')');
2726
2727
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2728
		$sql .= " SET fk_warehouse = ".$warehouse_id;
2729
		$sql .= " WHERE rowid=".$this->id;
2730
2731
		if ($this->db->query($sql)) {
2732
			$this->warehouse_id = ($warehouse_id == 'NULL') ?null:$warehouse_id;
2733
			return 1;
2734
		} else {
2735
			dol_syslog(get_class($this).'::setWarehouse Error ', LOG_DEBUG);
2736
			$this->error = $this->db->error();
2737
			return 0;
2738
		}
2739
	}
2740
2741
2742
	/**
2743
	 *		Set last model used by doc generator
2744
	 *
2745
	 *		@param		User	$user		User object that make change
2746
	 *		@param		string	$modelpdf	Modele name
2747
	 *		@return		int					<0 if KO, >0 if OK
2748
	 */
2749
	public function setDocModel($user, $modelpdf)
2750
	{
2751
		if (!$this->table_element) {
2752
			dol_syslog(get_class($this)."::setDocModel was called on objet with property table_element not defined", LOG_ERR);
2753
			return -1;
2754
		}
2755
2756
		$newmodelpdf = dol_trunc($modelpdf, 255);
2757
2758
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2759
		$sql .= " SET model_pdf = '".$this->db->escape($newmodelpdf)."'";
2760
		$sql .= " WHERE rowid = ".$this->id;
2761
2762
		dol_syslog(get_class($this)."::setDocModel", LOG_DEBUG);
2763
		$resql = $this->db->query($sql);
2764
		if ($resql) {
2765
			$this->model_pdf = $modelpdf;
2766
			$this->modelpdf = $modelpdf; // For bakward compatibility
2767
			return 1;
2768
		} else {
2769
			dol_print_error($this->db);
2770
			return 0;
2771
		}
2772
	}
2773
2774
2775
	/**
2776
	 *  Change the bank account
2777
	 *
2778
	 *  @param		int		$fk_account		Id of bank account
2779
	 *  @param      bool    $notrigger      false=launch triggers after, true=disable triggers
2780
	 *  @param      User	$userused		Object user
2781
	 *  @return		int				1 if OK, 0 if KO
2782
	 */
2783
	public function setBankAccount($fk_account, $notrigger = false, $userused = null)
2784
	{
2785
		global $user;
2786
2787
		if (empty($userused)) {
2788
			$userused = $user;
2789
		}
2790
2791
		$error = 0;
2792
2793
		if (!$this->table_element) {
2794
			dol_syslog(get_class($this)."::setBankAccount was called on objet with property table_element not defined", LOG_ERR);
2795
			return -1;
2796
		}
2797
		$this->db->begin();
2798
2799
		if ($fk_account < 0) {
2800
			$fk_account = 'NULL';
2801
		}
2802
		dol_syslog(get_class($this).'::setBankAccount('.$fk_account.')');
2803
2804
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2805
		$sql .= " SET fk_account = ".$fk_account;
2806
		$sql .= " WHERE rowid=".$this->id;
2807
2808
		$resql = $this->db->query($sql);
2809
		if (!$resql) {
2810
			dol_syslog(get_class($this).'::setBankAccount Error '.$sql.' - '.$this->db->error());
2811
			$this->error = $this->db->lasterror();
2812
			$error++;
2813
		} else {
2814
			if (!$notrigger) {
2815
				// Call trigger
2816
				$this->context = array('bankaccountupdate'=>1);
2817
				$result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $userused);
2818
				if ($result < 0) {
2819
					$error++;
2820
				}
2821
				// End call trigger
2822
			}
2823
		}
2824
		if ($error) {
2825
			$this->db->rollback();
2826
			return -1;
2827
		} else {
2828
			$this->fk_account = ($fk_account == 'NULL') ?null:$fk_account;
2829
			$this->db->commit();
2830
			return 1;
2831
		}
2832
	}
2833
2834
2835
	// TODO: Move line related operations to CommonObjectLine?
2836
2837
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2838
	/**
2839
	 *  Save a new position (field rang) for details lines.
2840
	 *  You can choose to set position for lines with already a position or lines without any position defined.
2841
	 *
2842
	 * 	@param		boolean		$renum			   True to renum all already ordered lines, false to renum only not already ordered lines.
2843
	 * 	@param		string		$rowidorder		   ASC or DESC
2844
	 * 	@param		boolean		$fk_parent_line    Table with fk_parent_line field or not
2845
	 * 	@return		int                            <0 if KO, >0 if OK
2846
	 */
2847
	public function line_order($renum = false, $rowidorder = 'ASC', $fk_parent_line = true)
2848
	{
2849
		// phpcs:enable
2850
		if (!$this->table_element_line) {
2851
			dol_syslog(get_class($this)."::line_order was called on objet with property table_element_line not defined", LOG_ERR);
2852
			return -1;
2853
		}
2854
		if (!$this->fk_element) {
2855
			dol_syslog(get_class($this)."::line_order was called on objet with property fk_element not defined", LOG_ERR);
2856
			return -1;
2857
		}
2858
2859
		// Count number of lines to reorder (according to choice $renum)
2860
		$nl = 0;
2861
		$sql = 'SELECT count(rowid) FROM '.MAIN_DB_PREFIX.$this->table_element_line;
2862
		$sql .= ' WHERE '.$this->fk_element.'='.$this->id;
2863
		if (!$renum) {
2864
			$sql .= ' AND rang = 0';
2865
		}
2866
		if ($renum) {
2867
			$sql .= ' AND rang <> 0';
2868
		}
2869
2870
		dol_syslog(get_class($this)."::line_order", LOG_DEBUG);
2871
		$resql = $this->db->query($sql);
2872
		if ($resql) {
2873
			$row = $this->db->fetch_row($resql);
2874
			$nl = $row[0];
2875
		} else {
2876
			dol_print_error($this->db);
2877
		}
2878
		if ($nl > 0) {
2879
			// The goal of this part is to reorder all lines, with all children lines sharing the same counter that parents.
2880
			$rows = array();
2881
2882
			// We first search all lines that are parent lines (for multilevel details lines)
2883
			$sql = 'SELECT rowid FROM '.MAIN_DB_PREFIX.$this->table_element_line;
2884
			$sql .= ' WHERE '.$this->fk_element.' = '.$this->id;
2885
			if ($fk_parent_line) {
2886
				$sql .= ' AND fk_parent_line IS NULL';
2887
			}
2888
			$sql .= ' ORDER BY rang ASC, rowid '.$rowidorder;
2889
2890
			dol_syslog(get_class($this)."::line_order search all parent lines", LOG_DEBUG);
2891
			$resql = $this->db->query($sql);
2892
			if ($resql) {
2893
				$i = 0;
2894
				$num = $this->db->num_rows($resql);
2895
				while ($i < $num) {
2896
					$row = $this->db->fetch_row($resql);
2897
					$rows[] = $row[0]; // Add parent line into array rows
2898
					$childrens = $this->getChildrenOfLine($row[0]);
2899
					if (!empty($childrens)) {
2900
						foreach ($childrens as $child) {
2901
							array_push($rows, $child);
2902
						}
2903
					}
2904
					$i++;
2905
				}
2906
2907
				// Now we set a new number for each lines (parent and children with children included into parent tree)
2908
				if (!empty($rows)) {
2909
					foreach ($rows as $key => $row) {
2910
						$this->updateRangOfLine($row, ($key + 1));
2911
					}
2912
				}
2913
			} else {
2914
				dol_print_error($this->db);
2915
			}
2916
		}
2917
		return 1;
2918
	}
2919
2920
	/**
2921
	 * 	Get children of line
2922
	 *
2923
	 * 	@param	int		$id		            Id of parent line
2924
	 * 	@param	int		$includealltree		0 = 1st level child, 1 = All level child
2925
	 * 	@return	array			            Array with list of children lines id
2926
	 */
2927
	public function getChildrenOfLine($id, $includealltree = 0)
2928
	{
2929
		$rows = array();
2930
2931
		$sql = 'SELECT rowid FROM '.MAIN_DB_PREFIX.$this->table_element_line;
2932
		$sql .= ' WHERE '.$this->fk_element.' = '.$this->id;
2933
		$sql .= ' AND fk_parent_line = '.$id;
2934
		$sql .= ' ORDER BY rang ASC';
2935
2936
		dol_syslog(get_class($this)."::getChildrenOfLine search children lines for line ".$id."", LOG_DEBUG);
2937
		$resql = $this->db->query($sql);
2938
		if ($resql) {
2939
			if ($this->db->num_rows($resql) > 0) {
2940
				while ($row = $this->db->fetch_row($resql)) {
2941
					$rows[] = $row[0];
2942
					if (!empty($includealltree)) {
2943
						$rows = array_merge($rows, $this->getChildrenOfLine($row[0]), $includealltree);
2944
					}
2945
				}
2946
			}
2947
		}
2948
		return $rows;
2949
	}
2950
2951
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2952
	/**
2953
	 * 	Update a line to have a lower rank
2954
	 *
2955
	 * 	@param 	int			$rowid				Id of line
2956
	 * 	@param	boolean		$fk_parent_line		Table with fk_parent_line field or not
2957
	 * 	@return	void
2958
	 */
2959
	public function line_up($rowid, $fk_parent_line = true)
2960
	{
2961
		// phpcs:enable
2962
		$this->line_order(false, 'ASC', $fk_parent_line);
2963
2964
		// Get rang of line
2965
		$rang = $this->getRangOfLine($rowid);
2966
2967
		// Update position of line
2968
		$this->updateLineUp($rowid, $rang);
2969
	}
2970
2971
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2972
	/**
2973
	 * 	Update a line to have a higher rank
2974
	 *
2975
	 * 	@param	int			$rowid				Id of line
2976
	 * 	@param	boolean		$fk_parent_line		Table with fk_parent_line field or not
2977
	 * 	@return	void
2978
	 */
2979
	public function line_down($rowid, $fk_parent_line = true)
2980
	{
2981
		// phpcs:enable
2982
		$this->line_order(false, 'ASC', $fk_parent_line);
2983
2984
		// Get rang of line
2985
		$rang = $this->getRangOfLine($rowid);
2986
2987
		// Get max value for rang
2988
		$max = $this->line_max();
2989
2990
		// Update position of line
2991
		$this->updateLineDown($rowid, $rang, $max);
2992
	}
2993
2994
	/**
2995
	 * 	Update position of line (rang)
2996
	 *
2997
	 * 	@param	int		$rowid		Id of line
2998
	 * 	@param	int		$rang		Position
2999
	 * 	@return	void
3000
	 */
3001
	public function updateRangOfLine($rowid, $rang)
3002
	{
3003
		$fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3004
		if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction'))) {
3005
			$fieldposition = 'position';
3006
		}
3007
3008
		$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element_line.' SET '.$fieldposition.' = '.$rang;
3009
		$sql .= ' WHERE rowid = '.$rowid;
3010
3011
		dol_syslog(get_class($this)."::updateRangOfLine", LOG_DEBUG);
3012
		if (!$this->db->query($sql)) {
3013
			dol_print_error($this->db);
3014
		}
3015
	}
3016
3017
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3018
	/**
3019
	 * 	Update position of line with ajax (rang)
3020
	 *
3021
	 * 	@param	array	$rows	Array of rows
3022
	 * 	@return	void
3023
	 */
3024
	public function line_ajaxorder($rows)
3025
	{
3026
		// phpcs:enable
3027
		$num = count($rows);
3028
		for ($i = 0; $i < $num; $i++) {
3029
			$this->updateRangOfLine($rows[$i], ($i + 1));
3030
		}
3031
	}
3032
3033
	/**
3034
	 * 	Update position of line up (rang)
3035
	 *
3036
	 * 	@param	int		$rowid		Id of line
3037
	 * 	@param	int		$rang		Position
3038
	 * 	@return	void
3039
	 */
3040
	public function updateLineUp($rowid, $rang)
3041
	{
3042
		if ($rang > 1) {
3043
			$fieldposition = 'rang';
3044
			if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction'))) {
3045
				$fieldposition = 'position';
3046
			}
3047
3048
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element_line.' SET '.$fieldposition.' = '.$rang;
3049
			$sql .= ' WHERE '.$this->fk_element.' = '.$this->id;
3050
			$sql .= ' AND rang = '.($rang - 1);
3051
			if ($this->db->query($sql)) {
3052
				$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element_line.' SET '.$fieldposition.' = '.($rang - 1);
3053
				$sql .= ' WHERE rowid = '.$rowid;
3054
				if (!$this->db->query($sql)) {
3055
					dol_print_error($this->db);
3056
				}
3057
			} else {
3058
				dol_print_error($this->db);
3059
			}
3060
		}
3061
	}
3062
3063
	/**
3064
	 * 	Update position of line down (rang)
3065
	 *
3066
	 * 	@param	int		$rowid		Id of line
3067
	 * 	@param	int		$rang		Position
3068
	 * 	@param	int		$max		Max
3069
	 * 	@return	void
3070
	 */
3071
	public function updateLineDown($rowid, $rang, $max)
3072
	{
3073
		if ($rang < $max) {
3074
			$fieldposition = 'rang';
3075
			if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction'))) {
3076
				$fieldposition = 'position';
3077
			}
3078
3079
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element_line.' SET '.$fieldposition.' = '.$rang;
3080
			$sql .= ' WHERE '.$this->fk_element.' = '.$this->id;
3081
			$sql .= ' AND rang = '.($rang + 1);
3082
			if ($this->db->query($sql)) {
3083
				$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element_line.' SET '.$fieldposition.' = '.($rang + 1);
3084
				$sql .= ' WHERE rowid = '.$rowid;
3085
				if (!$this->db->query($sql)) {
3086
					dol_print_error($this->db);
3087
				}
3088
			} else {
3089
				dol_print_error($this->db);
3090
			}
3091
		}
3092
	}
3093
3094
	/**
3095
	 * 	Get position of line (rang)
3096
	 *
3097
	 * 	@param		int		$rowid		Id of line
3098
	 *  @return		int     			Value of rang in table of lines
3099
	 */
3100
	public function getRangOfLine($rowid)
3101
	{
3102
		$sql = 'SELECT rang FROM '.MAIN_DB_PREFIX.$this->table_element_line;
3103
		$sql .= ' WHERE rowid ='.$rowid;
3104
3105
		dol_syslog(get_class($this)."::getRangOfLine", LOG_DEBUG);
3106
		$resql = $this->db->query($sql);
3107
		if ($resql) {
3108
			$row = $this->db->fetch_row($resql);
3109
			return $row[0];
3110
		}
3111
	}
3112
3113
	/**
3114
	 * 	Get rowid of the line relative to its position
3115
	 *
3116
	 * 	@param		int		$rang		Rang value
3117
	 *  @return     int     			Rowid of the line
3118
	 */
3119
	public function getIdOfLine($rang)
3120
	{
3121
		$sql = 'SELECT rowid FROM '.MAIN_DB_PREFIX.$this->table_element_line;
3122
		$sql .= ' WHERE '.$this->fk_element.' = '.$this->id;
3123
		$sql .= ' AND rang = '.$rang;
3124
		$resql = $this->db->query($sql);
3125
		if ($resql) {
3126
			$row = $this->db->fetch_row($resql);
3127
			return $row[0];
3128
		}
3129
	}
3130
3131
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3132
	/**
3133
	 * 	Get max value used for position of line (rang)
3134
	 *
3135
	 * 	@param		int		$fk_parent_line		Parent line id
3136
	 *  @return     int  			   			Max value of rang in table of lines
3137
	 */
3138
	public function line_max($fk_parent_line = 0)
3139
	{
3140
		// phpcs:enable
3141
		$positionfield = 'rang';
3142
		if ($this->table_element == 'bom_bom') {
3143
			$positionfield = 'position';
3144
		}
3145
3146
		// Search the last rang with fk_parent_line
3147
		if ($fk_parent_line) {
3148
			$sql = 'SELECT max('.$positionfield.') FROM '.MAIN_DB_PREFIX.$this->table_element_line;
3149
			$sql .= ' WHERE '.$this->fk_element.' = '.$this->id;
3150
			$sql .= ' AND fk_parent_line = '.$fk_parent_line;
3151
3152
			dol_syslog(get_class($this)."::line_max", LOG_DEBUG);
3153
			$resql = $this->db->query($sql);
3154
			if ($resql) {
3155
				$row = $this->db->fetch_row($resql);
3156
				if (!empty($row[0])) {
3157
					return $row[0];
3158
				} else {
3159
					return $this->getRangOfLine($fk_parent_line);
3160
				}
3161
			}
3162
		} else {
3163
			// If not, search the last rang of element
3164
			$sql = 'SELECT max('.$positionfield.') FROM '.MAIN_DB_PREFIX.$this->table_element_line;
3165
			$sql .= ' WHERE '.$this->fk_element.' = '.$this->id;
3166
3167
			dol_syslog(get_class($this)."::line_max", LOG_DEBUG);
3168
			$resql = $this->db->query($sql);
3169
			if ($resql) {
3170
				$row = $this->db->fetch_row($resql);
3171
				return $row[0];
3172
			}
3173
		}
3174
	}
3175
3176
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3177
	/**
3178
	 *  Update external ref of element
3179
	 *
3180
	 *  @param      string		$ref_ext	Update field ref_ext
3181
	 *  @return     int      		   		<0 if KO, >0 if OK
3182
	 */
3183
	public function update_ref_ext($ref_ext)
3184
	{
3185
		// phpcs:enable
3186
		if (!$this->table_element) {
3187
			dol_syslog(get_class($this)."::update_ref_ext was called on objet with property table_element not defined", LOG_ERR);
3188
			return -1;
3189
		}
3190
3191
		$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
3192
		$sql .= " SET ref_ext = '".$this->db->escape($ref_ext)."'";
3193
		$sql .= " WHERE ".(isset($this->table_rowid) ? $this->table_rowid : 'rowid')." = ".$this->id;
3194
3195
		dol_syslog(get_class($this)."::update_ref_ext", LOG_DEBUG);
3196
		if ($this->db->query($sql)) {
3197
			$this->ref_ext = $ref_ext;
3198
			return 1;
3199
		} else {
3200
			$this->error = $this->db->error();
3201
			return -1;
3202
		}
3203
	}
3204
3205
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3206
	/**
3207
	 *  Update note of element
3208
	 *
3209
	 *  @param      string		$note		New value for note
3210
	 *  @param		string		$suffix		'', '_public' or '_private'
3211
	 *  @return     int      		   		<0 if KO, >0 if OK
3212
	 */
3213
	public function update_note($note, $suffix = '')
3214
	{
3215
		// phpcs:enable
3216
		global $user;
3217
3218
		if (!$this->table_element) {
3219
			$this->error = 'update_note was called on objet with property table_element not defined';
3220
			dol_syslog(get_class($this)."::update_note was called on objet with property table_element not defined", LOG_ERR);
3221
			return -1;
3222
		}
3223
		if (!in_array($suffix, array('', '_public', '_private'))) {
3224
			$this->error = 'update_note Parameter suffix must be empty, \'_private\' or \'_public\'';
3225
			dol_syslog(get_class($this)."::update_note Parameter suffix must be empty, '_private' or '_public'", LOG_ERR);
3226
			return -2;
3227
		}
3228
3229
		$newsuffix = $suffix;
3230
3231
		// Special cas
3232
		if ($this->table_element == 'product' && $newsuffix == '_private') {
3233
			$newsuffix = '';
3234
		}
3235
		if (in_array($this->table_element, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
3236
			$fieldusermod =  "fk_user_mod";
3237
		} elseif ($this->table_element == 'ecm_files') {
3238
			$fieldusermod = "fk_user_m";
3239
		} else {
3240
			$fieldusermod = "fk_user_modif";
3241
		}
3242
		$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
3243
		$sql .= " SET note".$newsuffix." = ".(!empty($note) ? ("'".$this->db->escape($note)."'") : "NULL");
3244
		$sql .= " ,".$fieldusermod." = ".$user->id;
3245
		$sql .= " WHERE rowid =".$this->id;
3246
3247
		dol_syslog(get_class($this)."::update_note", LOG_DEBUG);
3248
		if ($this->db->query($sql)) {
3249
			if ($suffix == '_public') {
3250
				$this->note_public = $note;
3251
			} elseif ($suffix == '_private') {
3252
				$this->note_private = $note;
3253
			} else {
3254
				$this->note = $note; // deprecated
3255
				$this->note_private = $note;
3256
			}
3257
			return 1;
3258
		} else {
3259
			$this->error = $this->db->lasterror();
3260
			return -1;
3261
		}
3262
	}
3263
3264
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3265
	/**
3266
	 * 	Update public note (kept for backward compatibility)
3267
	 *
3268
	 * @param      string		$note		New value for note
3269
	 * @return     int      		   		<0 if KO, >0 if OK
3270
	 * @deprecated
3271
	 * @see update_note()
3272
	 */
3273
	public function update_note_public($note)
3274
	{
3275
		// phpcs:enable
3276
		return $this->update_note($note, '_public');
3277
	}
3278
3279
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3280
	/**
3281
	 *	Update total_ht, total_ttc, total_vat, total_localtax1, total_localtax2 for an object (sum of lines).
3282
	 *  Must be called at end of methods addline or updateline.
3283
	 *
3284
	 *	@param	int		$exclspec          	>0 = Exclude special product (product_type=9)
3285
	 *  @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
3286
	 *  @param	int		$nodatabaseupdate	1=Do not update database. Update only properties of object.
3287
	 *  @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.
3288
	 *	@return	int    			           	<0 if KO, >0 if OK
3289
	 */
3290
	public function update_price($exclspec = 0, $roundingadjust = 'none', $nodatabaseupdate = 0, $seller = null)
3291
	{
3292
		// phpcs:enable
3293
		global $conf, $hookmanager, $action;
3294
3295
		$parameters = array('exclspec' => $exclspec, 'roundingadjust' => $roundingadjust, 'nodatabaseupdate' => $nodatabaseupdate, 'seller' => $seller);
3296
		$reshook = $hookmanager->executeHooks('updateTotalPrice', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3297
		if ($reshook > 0) {
3298
			return 1; // replacement code
3299
		} elseif ($reshook < 0) {
3300
			return -1; // failure
3301
		} // reshook = 0 => execute normal code
3302
3303
		// Some external module want no update price after a trigger because they have another method to calculate the total (ex: with an extrafield)
3304
		$MODULE = "";
3305
		if ($this->element == 'propal') {
3306
			$MODULE = "MODULE_DISALLOW_UPDATE_PRICE_PROPOSAL";
3307
		} elseif ($this->element == 'commande' || $this->element == 'order') {
3308
			$MODULE = "MODULE_DISALLOW_UPDATE_PRICE_ORDER";
3309
		} elseif ($this->element == 'facture' || $this->element == 'invoice') {
3310
			$MODULE = "MODULE_DISALLOW_UPDATE_PRICE_INVOICE";
3311
		} elseif ($this->element == 'facture_fourn' || $this->element == 'supplier_invoice' || $this->element == 'invoice_supplier') {
3312
			$MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_INVOICE";
3313
		} elseif ($this->element == 'order_supplier' || $this->element == 'supplier_order') {
3314
			$MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_ORDER";
3315
		} elseif ($this->element == 'supplier_proposal') {
3316
			$MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_PROPOSAL";
3317
		}
3318
3319
		if (!empty($MODULE)) {
3320
			if (!empty($conf->global->$MODULE)) {
3321
				$modsactivated = explode(',', $conf->global->$MODULE);
3322
				foreach ($modsactivated as $mod) {
3323
					if ($conf->$mod->enabled) {
3324
						return 1; // update was disabled by specific setup
3325
					}
3326
				}
3327
			}
3328
		}
3329
3330
		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
3331
3332
		if ($roundingadjust == '-1') {
3333
			$roundingadjust = 'auto'; // For backward compatibility
3334
		}
3335
3336
		$forcedroundingmode = $roundingadjust;
3337
		if ($forcedroundingmode == 'auto' && isset($conf->global->MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND)) {
3338
			$forcedroundingmode = $conf->global->MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND;
3339
		} elseif ($forcedroundingmode == 'auto') {
3340
			$forcedroundingmode = '0';
3341
		}
3342
3343
		$error = 0;
3344
3345
		$multicurrency_tx = !empty($this->multicurrency_tx) ? $this->multicurrency_tx : 1;
3346
3347
		// Define constants to find lines to sum
3348
		$fieldtva = 'total_tva';
3349
		$fieldlocaltax1 = 'total_localtax1';
3350
		$fieldlocaltax2 = 'total_localtax2';
3351
		$fieldup = 'subprice';
3352
		if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
3353
			$fieldtva = 'tva';
3354
			$fieldup = 'pu_ht';
3355
		}
3356
		if ($this->element == 'expensereport') {
3357
			$fieldup = 'value_unit';
3358
		}
3359
3360
		$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,';
3361
		$sql .= ' tva_tx as vatrate, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, info_bits, product_type';
3362
		if ($this->table_element_line == 'facturedet') {
3363
			$sql .= ', situation_percent';
3364
		}
3365
		$sql .= ', multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
3366
		$sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element_line;
3367
		$sql .= ' WHERE '.$this->fk_element.' = '.$this->id;
3368
		if ($exclspec) {
3369
			$product_field = 'product_type';
3370
			if ($this->table_element_line == 'contratdet') {
3371
				$product_field = ''; // contratdet table has no product_type field
3372
			}
3373
			if ($product_field) {
3374
				$sql .= ' AND '.$product_field.' <> 9';
3375
			}
3376
		}
3377
		$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
3378
3379
		dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
3380
		$resql = $this->db->query($sql);
3381
		if ($resql) {
3382
			$this->total_ht  = 0;
3383
			$this->total_tva = 0;
3384
			$this->total_localtax1 = 0;
3385
			$this->total_localtax2 = 0;
3386
			$this->total_ttc = 0;
3387
			$total_ht_by_vats  = array();
3388
			$total_tva_by_vats = array();
3389
			$total_ttc_by_vats = array();
3390
			$this->multicurrency_total_ht = 0;
3391
			$this->multicurrency_total_tva	= 0;
3392
			$this->multicurrency_total_ttc	= 0;
3393
3394
			$num = $this->db->num_rows($resql);
3395
			$i = 0;
3396
			while ($i < $num) {
3397
				$obj = $this->db->fetch_object($resql);
3398
3399
				// Note: There is no check on detail line and no check on total, if $forcedroundingmode = 'none'
3400
				$parameters = array('fk_element' => $obj->rowid);
3401
				$reshook = $hookmanager->executeHooks('changeRoundingMode', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3402
3403
				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'
3404
					// This part of code is to fix data. We should not call it too often.
3405
					$localtax_array = array($obj->localtax1_type, $obj->localtax1_tx, $obj->localtax2_type, $obj->localtax2_tx);
3406
					$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);
3407
3408
					$diff_when_using_price_ht = price2num($tmpcal[1] - $obj->total_tva, 'MT', 1); // If price was set with tax price adn unit price HT has a low number of digits, then we may have a diff on recalculation from unit price HT.
3409
					$diff_on_current_total = price2num($obj->total_ttc - $obj->total_ht - $obj->total_tva - $obj->total_localtax1 - $obj->total_localtax2, 'MT', 1);
3410
					//var_dump($obj->total_ht.' '.$obj->total_tva.' '.$obj->total_localtax1.' '.$obj->total_localtax2.' =? '.$obj->total_ttc);
3411
					//var_dump($diff_when_using_price_ht.' '.$diff_on_current_total);
3412
3413
					if ($diff_when_using_price_ht && $diff_on_current_total) {
3414
						$sqlfix = "UPDATE ".MAIN_DB_PREFIX.$this->table_element_line." SET ".$fieldtva." = ".$tmpcal[1].", total_ttc = ".$tmpcal[2]." WHERE rowid = ".$obj->rowid;
3415
						dol_syslog('We found unconsistent data into detailed line (diff_when_using_price_ht = '.$diff_when_using_price_ht.' and diff_on_current_total = '.$diff_on_current_total.') for line rowid = '.$obj->rowid." (total vat of line calculated=".$tmpcal[1].", database=".$obj->total_tva."). We fix the total_vat and total_ttc of line by running sqlfix = ".$sqlfix, LOG_WARNING);
3416
						$resqlfix = $this->db->query($sqlfix);
3417
						if (!$resqlfix) {
3418
							dol_print_error($this->db, 'Failed to update line');
3419
						}
3420
						$obj->total_tva = $tmpcal[1];
3421
						$obj->total_ttc = $tmpcal[2];
3422
					}
3423
				}
3424
3425
				$this->total_ht        += $obj->total_ht; // The field visible at end of line detail
3426
				$this->total_tva       += $obj->total_tva;
3427
				$this->total_localtax1 += $obj->total_localtax1;
3428
				$this->total_localtax2 += $obj->total_localtax2;
3429
				$this->total_ttc       += $obj->total_ttc;
3430
				$this->multicurrency_total_ht        += $obj->multicurrency_total_ht; // The field visible at end of line detail
3431
				$this->multicurrency_total_tva       += $obj->multicurrency_total_tva;
3432
				$this->multicurrency_total_ttc       += $obj->multicurrency_total_ttc;
3433
3434
				if (!isset($total_ht_by_vats[$obj->vatrate])) {
3435
					$total_ht_by_vats[$obj->vatrate] = 0;
3436
				}
3437
				if (!isset($total_tva_by_vats[$obj->vatrate])) {
3438
					$total_tva_by_vats[$obj->vatrate] = 0;
3439
				}
3440
				if (!isset($total_ttc_by_vats[$obj->vatrate])) {
3441
					$total_ttc_by_vats[$obj->vatrate] = 0;
3442
				}
3443
				$total_ht_by_vats[$obj->vatrate]  += $obj->total_ht;
3444
				$total_tva_by_vats[$obj->vatrate] += $obj->total_tva;
3445
				$total_ttc_by_vats[$obj->vatrate] += $obj->total_ttc;
3446
3447
				if ($forcedroundingmode == '1') {	// Check if we need adjustement onto line for vat. TODO This works on the company currency but not on multicurrency
3448
					$tmpvat = price2num($total_ht_by_vats[$obj->vatrate] * $obj->vatrate / 100, 'MT', 1);
3449
					$diff = price2num($total_tva_by_vats[$obj->vatrate] - $tmpvat, 'MT', 1);
3450
					//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";
3451
					if ($diff) {
3452
						if (abs($diff) > 0.1) {
3453
							$errmsg = 'A rounding difference was detected into TOTAL but is too high to be corrected. Some data in your line may be corrupted. Try to edit each line manually.';
3454
							dol_syslog($errmsg, LOG_WARNING);
3455
							dol_print_error('', $errmsg);
3456
							exit;
3457
						}
3458
						$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;
3459
						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);
3460
								$resqlfix = $this->db->query($sqlfix);
3461
						if (!$resqlfix) {
3462
							dol_print_error($this->db, 'Failed to update line');
3463
						}
3464
								$this->total_tva -= $diff;
3465
								$this->total_ttc -= $diff;
3466
								$total_tva_by_vats[$obj->vatrate] -= $diff;
3467
								$total_ttc_by_vats[$obj->vatrate] -= $diff;
3468
					}
3469
				}
3470
3471
				$i++;
3472
			}
3473
3474
			// Add revenue stamp to total
3475
			$this->total_ttc += isset($this->revenuestamp) ? $this->revenuestamp : 0;
3476
			$this->multicurrency_total_ttc += isset($this->revenuestamp) ? ($this->revenuestamp * $multicurrency_tx) : 0;
3477
3478
			// Situations totals
3479
			if (!empty($this->situation_cycle_ref) && $this->situation_counter > 1 && method_exists($this, 'get_prev_sits') && $this->type != $this::TYPE_CREDIT_NOTE) {
3480
				$prev_sits = $this->get_prev_sits();
3481
3482
				foreach ($prev_sits as $sit) {				// $sit is an object Facture loaded with a fetch.
3483
					$this->total_ht -= $sit->total_ht;
3484
					$this->total_tva -= $sit->total_tva;
3485
					$this->total_localtax1 -= $sit->total_localtax1;
3486
					$this->total_localtax2 -= $sit->total_localtax2;
3487
					$this->total_ttc -= $sit->total_ttc;
3488
					$this->multicurrency_total_ht -= $sit->multicurrency_total_ht;
3489
					$this->multicurrency_total_tva -= $sit->multicurrency_total_tva;
3490
					$this->multicurrency_total_ttc -= $sit->multicurrency_total_ttc;
3491
				}
3492
			}
3493
3494
			$this->db->free($resql);
3495
3496
			// Now update global field total_ht, total_ttc and tva
3497
			$fieldht = 'total_ht';
3498
			$fieldtva = 'tva';
3499
			$fieldlocaltax1 = 'localtax1';
3500
			$fieldlocaltax2 = 'localtax2';
3501
			$fieldttc = 'total_ttc';
3502
			// Specific code for backward compatibility with old field names
3503
			if ($this->element == 'facture' || $this->element == 'facturerec') {
3504
				$fieldht = 'total';
3505
			}
3506
			if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
3507
				$fieldtva = 'total_tva';
3508
			}
3509
			if ($this->element == 'propal') {
3510
				$fieldtva = 'total_tva';
3511
			}
3512
			if ($this->element == 'expensereport') {
3513
				$fieldtva = 'total_tva';
3514
			}
3515
			if ($this->element == 'supplier_proposal') {
3516
				$fieldtva = 'total_tva';
3517
			}
3518
			if ($this->element == 'commande') {
3519
				$fieldtva = 'total_tva';
3520
			}
3521
			if ($this->element == 'order_supplier') {
3522
				$fieldtva = 'total_tva';
3523
			}
3524
3525
			if (empty($nodatabaseupdate)) {
3526
				$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element.' SET';
3527
				$sql .= " ".$fieldht."='".price2num($this->total_ht)."',";
3528
				$sql .= " ".$fieldtva."='".price2num($this->total_tva)."',";
3529
				$sql .= " ".$fieldlocaltax1."='".price2num($this->total_localtax1)."',";
3530
				$sql .= " ".$fieldlocaltax2."='".price2num($this->total_localtax2)."',";
3531
				$sql .= " ".$fieldttc."='".price2num($this->total_ttc)."'";
3532
						$sql .= ", multicurrency_total_ht='".price2num($this->multicurrency_total_ht, 'MT', 1)."'";
3533
						$sql .= ", multicurrency_total_tva='".price2num($this->multicurrency_total_tva, 'MT', 1)."'";
3534
						$sql .= ", multicurrency_total_ttc='".price2num($this->multicurrency_total_ttc, 'MT', 1)."'";
3535
				$sql .= ' WHERE rowid = '.$this->id;
3536
3537
3538
				dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
3539
				$resql = $this->db->query($sql);
3540
				if (!$resql) {
3541
					$error++;
3542
					$this->error = $this->db->lasterror();
3543
					$this->errors[] = $this->db->lasterror();
3544
				}
3545
			}
3546
3547
			if (!$error) {
3548
				return 1;
3549
			} else {
3550
				return -1;
3551
			}
3552
		} else {
3553
			dol_print_error($this->db, 'Bad request in update_price');
3554
			return -1;
3555
		}
3556
	}
3557
3558
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3559
	/**
3560
	 *	Add objects linked in llx_element_element.
3561
	 *
3562
	 *	@param		string	$origin		Linked element type
3563
	 *	@param		int		$origin_id	Linked element id
3564
	 * 	@param		User	$f_user		User that create
3565
	 * 	@param		int		$notrigger	1=Does not execute triggers, 0= execute triggers
3566
	 *	@return		int					<=0 if KO, >0 if OK
3567
	 *	@see		fetchObjectLinked(), updateObjectLinked(), deleteObjectLinked()
3568
	 */
3569
	public function add_object_linked($origin = null, $origin_id = null, $f_user = null, $notrigger = 0)
3570
	{
3571
		// phpcs:enable
3572
		global $user;
3573
		$origin = (!empty($origin) ? $origin : $this->origin);
3574
		$origin_id = (!empty($origin_id) ? $origin_id : $this->origin_id);
3575
		$f_user = isset($f_user) ? $f_user : $user;
3576
3577
		// Special case
3578
		if ($origin == 'order') {
3579
			$origin = 'commande';
3580
		}
3581
		if ($origin == 'invoice') {
3582
			$origin = 'facture';
3583
		}
3584
		if ($origin == 'invoice_template') {
3585
			$origin = 'facturerec';
3586
		}
3587
		if ($origin == 'supplierorder') {
3588
			$origin = 'order_supplier';
3589
		}
3590
		$this->db->begin();
3591
		$error = 0;
3592
3593
		$sql = "INSERT INTO " . MAIN_DB_PREFIX . "element_element (";
3594
		$sql .= "fk_source";
3595
		$sql .= ", sourcetype";
3596
		$sql .= ", fk_target";
3597
		$sql .= ", targettype";
3598
		$sql .= ") VALUES (";
3599
		$sql .= $origin_id;
3600
		$sql .= ", '" . $this->db->escape($origin) . "'";
3601
		$sql .= ", " . $this->id;
3602
		$sql .= ", '" . $this->db->escape($this->element) . "'";
3603
		$sql .= ")";
3604
3605
		dol_syslog(get_class($this) . "::add_object_linked", LOG_DEBUG);
3606
		if ($this->db->query($sql)) {
3607
			if (!$notrigger) {
3608
				// Call trigger
3609
				$this->context['link_origin'] = $origin;
3610
				$this->context['link_origin_id'] = $origin_id;
3611
				$result = $this->call_trigger('OBJECT_LINK_INSERT', $f_user);
3612
				if ($result < 0) {
3613
					$error++;
3614
				}
3615
				// End call triggers
3616
			}
3617
		} else {
3618
			$this->error = $this->db->lasterror();
3619
			$error++;
3620
		}
3621
3622
		if (!$error) {
3623
			$this->db->commit();
3624
			return 1;
3625
		} else {
3626
			$this->db->rollback();
3627
			return 0;
3628
		}
3629
	}
3630
3631
	/**
3632
	 *	Fetch array of objects linked to current object (object of enabled modules only). Links are loaded into
3633
	 *		this->linkedObjectsIds array +
3634
	 *		this->linkedObjects array if $loadalsoobjects = 1
3635
	 *  Possible usage for parameters:
3636
	 *  - all parameters empty -> we look all link to current object (current object can be source or target)
3637
	 *  - source id+type -> will get target list linked to source
3638
	 *  - target id+type -> will get source list linked to target
3639
	 *  - source id+type + target type -> will get target list of the type
3640
	 *  - target id+type + target source -> will get source list of the type
3641
	 *
3642
	 *	@param	int		$sourceid			Object source id (if not defined, id of object)
3643
	 *	@param  string	$sourcetype			Object source type (if not defined, element name of object)
3644
	 *	@param  int		$targetid			Object target id (if not defined, id of object)
3645
	 *	@param  string	$targettype			Object target type (if not defined, elemennt name of object)
3646
	 *	@param  string	$clause				'OR' or 'AND' clause used when both source id and target id are provided
3647
	 *  @param  int		$alsosametype		0=Return only links to object that differs from source type. 1=Include also link to objects of same type.
3648
	 *  @param  string	$orderby			SQL 'ORDER BY' clause
3649
	 *  @param	int		$loadalsoobjects	Load also array this->linkedObjects (Use 0 to increase performances)
3650
	 *	@return int							<0 if KO, >0 if OK
3651
	 *  @see	add_object_linked(), updateObjectLinked(), deleteObjectLinked()
3652
	 */
3653
	public function fetchObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $clause = 'OR', $alsosametype = 1, $orderby = 'sourcetype', $loadalsoobjects = 1)
3654
	{
3655
		global $conf;
3656
3657
		$this->linkedObjectsIds = array();
3658
		$this->linkedObjects = array();
3659
3660
		$justsource = false;
3661
		$justtarget = false;
3662
		$withtargettype = false;
3663
		$withsourcetype = false;
3664
3665
		if (!empty($sourceid) && !empty($sourcetype) && empty($targetid)) {
3666
			$justsource = true; // the source (id and type) is a search criteria
3667
			if (!empty($targettype)) {
3668
				$withtargettype = true;
3669
			}
3670
		}
3671
		if (!empty($targetid) && !empty($targettype) && empty($sourceid)) {
3672
			$justtarget = true; // the target (id and type) is a search criteria
3673
			if (!empty($sourcetype)) {
3674
				$withsourcetype = true;
3675
			}
3676
		}
3677
3678
		$sourceid = (!empty($sourceid) ? $sourceid : $this->id);
3679
		$targetid = (!empty($targetid) ? $targetid : $this->id);
3680
		$sourcetype = (!empty($sourcetype) ? $sourcetype : $this->element);
3681
		$targettype = (!empty($targettype) ? $targettype : $this->element);
3682
3683
		/*if (empty($sourceid) && empty($targetid))
3684
		 {
3685
		 dol_syslog('Bad usage of function. No source nor target id defined (nor as parameter nor as object id)', LOG_ERR);
3686
		 return -1;
3687
		 }*/
3688
3689
		// Links between objects are stored in table element_element
3690
		$sql = 'SELECT rowid, fk_source, sourcetype, fk_target, targettype';
3691
		$sql .= ' FROM '.MAIN_DB_PREFIX.'element_element';
3692
		$sql .= " WHERE ";
3693
		if ($justsource || $justtarget) {
3694
			if ($justsource) {
3695
				$sql .= "fk_source = ".$sourceid." AND sourcetype = '".$this->db->escape($sourcetype)."'";
3696
				if ($withtargettype) {
3697
					$sql .= " AND targettype = '".$this->db->escape($targettype)."'";
3698
				}
3699
			} elseif ($justtarget) {
3700
				$sql .= "fk_target = ".$targetid." AND targettype = '".$this->db->escape($targettype)."'";
3701
				if ($withsourcetype) {
3702
					$sql .= " AND sourcetype = '".$this->db->escape($sourcetype)."'";
3703
				}
3704
			}
3705
		} else {
3706
			$sql .= "(fk_source = ".$sourceid." AND sourcetype = '".$this->db->escape($sourcetype)."')";
3707
			$sql .= " ".$clause." (fk_target = ".$targetid." AND targettype = '".$this->db->escape($targettype)."')";
3708
		}
3709
		$sql .= ' ORDER BY '.$orderby;
3710
3711
		dol_syslog(get_class($this)."::fetchObjectLink", LOG_DEBUG);
3712
		$resql = $this->db->query($sql);
3713
		if ($resql) {
3714
			$num = $this->db->num_rows($resql);
3715
			$i = 0;
3716
			while ($i < $num) {
3717
				$obj = $this->db->fetch_object($resql);
3718
				if ($justsource || $justtarget) {
3719
					if ($justsource) {
3720
						$this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
3721
					} elseif ($justtarget) {
3722
						$this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
3723
					}
3724
				} else {
3725
					if ($obj->fk_source == $sourceid && $obj->sourcetype == $sourcetype) {
3726
						$this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
3727
					}
3728
					if ($obj->fk_target == $targetid && $obj->targettype == $targettype) {
3729
						$this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
3730
					}
3731
				}
3732
				$i++;
3733
			}
3734
3735
			if (!empty($this->linkedObjectsIds)) {
3736
				$tmparray = $this->linkedObjectsIds;
3737
				foreach ($tmparray as $objecttype => $objectids) {       // $objecttype is a module name ('facture', 'mymodule', ...) or a module name with a suffix ('project_task', 'mymodule_myobj', ...)
3738
					// Parse element/subelement (ex: project_task, cabinetmed_consultation, ...)
3739
					$module = $element = $subelement = $objecttype;
3740
					$regs = array();
3741
					if ($objecttype != 'supplier_proposal' && $objecttype != 'order_supplier' && $objecttype != 'invoice_supplier'
3742
						&& preg_match('/^([^_]+)_([^_]+)/i', $objecttype, $regs)) {
3743
						$module = $element = $regs[1];
3744
						$subelement = $regs[2];
3745
					}
3746
3747
					$classpath = $element.'/class';
3748
					// To work with non standard classpath or module name
3749
					if ($objecttype == 'facture') {
3750
						$classpath = 'compta/facture/class';
3751
					} elseif ($objecttype == 'facturerec') {
3752
						$classpath = 'compta/facture/class';
3753
						$module = 'facture';
3754
					} elseif ($objecttype == 'propal') {
3755
						$classpath = 'comm/propal/class';
3756
					} elseif ($objecttype == 'supplier_proposal') {
3757
						$classpath = 'supplier_proposal/class';
3758
					} elseif ($objecttype == 'shipping') {
3759
						$classpath = 'expedition/class';
3760
						$subelement = 'expedition';
3761
						$module = 'expedition_bon';
3762
					} elseif ($objecttype == 'delivery') {
3763
						$classpath = 'delivery/class';
3764
						$subelement = 'delivery';
3765
						$module = 'delivery_note';
3766
					} elseif ($objecttype == 'invoice_supplier' || $objecttype == 'order_supplier') {
3767
						$classpath = 'fourn/class';
3768
						$module = 'fournisseur';
3769
					} elseif ($objecttype == 'fichinter') {
3770
						$classpath = 'fichinter/class';
3771
						$subelement = 'fichinter';
3772
						$module = 'ficheinter';
3773
					} elseif ($objecttype == 'subscription') {
3774
						$classpath = 'adherents/class';
3775
						$module = 'adherent';
3776
					} elseif ($objecttype == 'contact') {
3777
						 $module = 'societe';
3778
					}
3779
3780
					// Set classfile
3781
					$classfile = strtolower($subelement);
3782
					$classname = ucfirst($subelement);
3783
3784
					if ($objecttype == 'order') {
3785
						$classfile = 'commande';
3786
						$classname = 'Commande';
3787
					} elseif ($objecttype == 'invoice_supplier') {
3788
						$classfile = 'fournisseur.facture';
3789
						$classname = 'FactureFournisseur';
3790
					} elseif ($objecttype == 'order_supplier') {
3791
						$classfile = 'fournisseur.commande';
3792
						$classname = 'CommandeFournisseur';
3793
					} elseif ($objecttype == 'supplier_proposal') {
3794
						$classfile = 'supplier_proposal';
3795
						$classname = 'SupplierProposal';
3796
					} elseif ($objecttype == 'facturerec') {
3797
						$classfile = 'facture-rec';
3798
						$classname = 'FactureRec';
3799
					} elseif ($objecttype == 'subscription') {
3800
						$classfile = 'subscription';
3801
						$classname = 'Subscription';
3802
					} elseif ($objecttype == 'project' || $objecttype == 'projet') {
3803
						$classpath = 'projet/class';
3804
						$classfile = 'project';
3805
						$classname = 'Project';
3806
					}
3807
3808
					// Here $module, $classfile and $classname are set
3809
					if ($conf->$module->enabled && (($element != $this->element) || $alsosametype)) {
3810
						if ($loadalsoobjects) {
3811
							dol_include_once('/'.$classpath.'/'.$classfile.'.class.php');
3812
							//print '/'.$classpath.'/'.$classfile.'.class.php '.class_exists($classname);
3813
							if (class_exists($classname)) {
3814
								foreach ($objectids as $i => $objectid) {	// $i is rowid into llx_element_element
3815
									$object = new $classname($this->db);
3816
									$ret = $object->fetch($objectid);
3817
									if ($ret >= 0) {
3818
										$this->linkedObjects[$objecttype][$i] = $object;
3819
									}
3820
								}
3821
							}
3822
						}
3823
					} else {
3824
						unset($this->linkedObjectsIds[$objecttype]);
3825
					}
3826
				}
3827
			}
3828
			return 1;
3829
		} else {
3830
			dol_print_error($this->db);
3831
			return -1;
3832
		}
3833
	}
3834
3835
	/**
3836
	 *	Update object linked of a current object
3837
	 *
3838
	 *	@param	int		$sourceid		Object source id
3839
	 *	@param  string	$sourcetype		Object source type
3840
	 *	@param  int		$targetid		Object target id
3841
	 *	@param  string	$targettype		Object target type
3842
	 * 	@param	User	$f_user			User that create
3843
	 * 	@param	int		$notrigger		1=Does not execute triggers, 0= execute triggers
3844
	 *	@return							int	>0 if OK, <0 if KO
3845
	 *	@see	add_object_linked(), fetObjectLinked(), deleteObjectLinked()
3846
	 */
3847
	public function updateObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $f_user = null, $notrigger = 0)
3848
	{
3849
		global $user;
3850
		$updatesource = false;
3851
		$updatetarget = false;
3852
		$f_user = isset($f_user) ? $f_user : $user;
3853
3854
		if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
3855
			$updatesource = true;
3856
		} elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
3857
			$updatetarget = true;
3858
		}
3859
3860
		$this->db->begin();
3861
		$error = 0;
3862
3863
		$sql = "UPDATE " . MAIN_DB_PREFIX . "element_element SET ";
3864
		if ($updatesource) {
3865
			$sql .= "fk_source = " . $sourceid;
3866
			$sql .= ", sourcetype = '" . $this->db->escape($sourcetype) . "'";
3867
			$sql .= " WHERE fk_target = " . $this->id;
3868
			$sql .= " AND targettype = '" . $this->db->escape($this->element) . "'";
3869
		} elseif ($updatetarget) {
3870
			$sql .= "fk_target = " . $targetid;
3871
			$sql .= ", targettype = '" . $this->db->escape($targettype) . "'";
3872
			$sql .= " WHERE fk_source = " . $this->id;
3873
			$sql .= " AND sourcetype = '" . $this->db->escape($this->element) . "'";
3874
		}
3875
3876
		dol_syslog(get_class($this) . "::updateObjectLinked", LOG_DEBUG);
3877
		if ($this->db->query($sql)) {
3878
			if (!$notrigger) {
3879
				// Call trigger
3880
				$this->context['link_source_id'] = $sourceid;
3881
				$this->context['link_source_type'] = $sourcetype;
3882
				$this->context['link_target_id'] = $targetid;
3883
				$this->context['link_target_type'] = $targettype;
3884
				$result = $this->call_trigger('OBJECT_LINK_UPDATE', $f_user);
3885
				if ($result < 0) {
3886
					$error++;
3887
				}
3888
				// End call triggers
3889
			}
3890
		} else {
3891
			$this->error = $this->db->lasterror();
3892
			$error++;
3893
		}
3894
3895
		if (!$error) {
3896
			$this->db->commit();
3897
			return 1;
3898
		} else {
3899
			$this->db->rollback();
3900
			return -1;
3901
		}
3902
	}
3903
3904
	/**
3905
	 *	Delete all links between an object $this
3906
	 *
3907
	 *	@param	int		$sourceid		Object source id
3908
	 *	@param  string	$sourcetype		Object source type
3909
	 *	@param  int		$targetid		Object target id
3910
	 *	@param  string	$targettype		Object target type
3911
	 *  @param	int		$rowid			Row id of line to delete. If defined, other parameters are not used.
3912
	 * 	@param	User	$f_user			User that create
3913
	 * 	@param	int		$notrigger		1=Does not execute triggers, 0= execute triggers
3914
	 *	@return     					int	>0 if OK, <0 if KO
3915
	 *	@see	add_object_linked(), updateObjectLinked(), fetchObjectLinked()
3916
	 */
3917
	public function deleteObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $rowid = '', $f_user = null, $notrigger = 0)
3918
	{
3919
		global $user;
3920
		$deletesource = false;
3921
		$deletetarget = false;
3922
		$f_user = isset($f_user) ? $f_user : $user;
3923
3924
		if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
3925
			$deletesource = true;
3926
		} elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
3927
			$deletetarget = true;
3928
		}
3929
3930
		$sourceid = (!empty($sourceid) ? $sourceid : $this->id);
3931
		$sourcetype = (!empty($sourcetype) ? $sourcetype : $this->element);
3932
		$targetid = (!empty($targetid) ? $targetid : $this->id);
3933
		$targettype = (!empty($targettype) ? $targettype : $this->element);
3934
		$this->db->begin();
3935
		$error = 0;
3936
3937
		if (!$notrigger) {
3938
			// Call trigger
3939
			$this->context['link_id'] = $rowid;
3940
			$this->context['link_source_id'] = $sourceid;
3941
			$this->context['link_source_type'] = $sourcetype;
3942
			$this->context['link_target_id'] = $targetid;
3943
			$this->context['link_target_type'] = $targettype;
3944
			$result = $this->call_trigger('OBJECT_LINK_DELETE', $f_user);
3945
			if ($result < 0) {
3946
				$error++;
3947
			}
3948
			// End call triggers
3949
		}
3950
3951
		if (!$error) {
3952
			$sql = "DELETE FROM " . MAIN_DB_PREFIX . "element_element";
3953
			$sql .= " WHERE";
3954
			if ($rowid > 0) {
3955
				$sql .= " rowid = " . $rowid;
3956
			} else {
3957
				if ($deletesource) {
3958
					$sql .= " fk_source = " . $sourceid . " AND sourcetype = '" . $this->db->escape($sourcetype) . "'";
3959
					$sql .= " AND fk_target = " . $this->id . " AND targettype = '" . $this->db->escape($this->element) . "'";
3960
				} elseif ($deletetarget) {
3961
					$sql .= " fk_target = " . $targetid . " AND targettype = '" . $this->db->escape($targettype) . "'";
3962
					$sql .= " AND fk_source = " . $this->id . " AND sourcetype = '" . $this->db->escape($this->element) . "'";
3963
				} else {
3964
					$sql .= " (fk_source = " . $this->id . " AND sourcetype = '" . $this->db->escape($this->element) . "')";
3965
					$sql .= " OR";
3966
					$sql .= " (fk_target = " . $this->id . " AND targettype = '" . $this->db->escape($this->element) . "')";
3967
				}
3968
			}
3969
3970
			dol_syslog(get_class($this) . "::deleteObjectLinked", LOG_DEBUG);
3971
			if (!$this->db->query($sql)) {
3972
				$this->error = $this->db->lasterror();
3973
				$this->errors[] = $this->error;
3974
				$error++;
3975
			}
3976
		}
3977
3978
		if (!$error) {
3979
			$this->db->commit();
3980
			return 1;
3981
		} else {
3982
			$this->db->rollback();
3983
			return 0;
3984
		}
3985
	}
3986
3987
	/**
3988
	 * Function used to get an array with all items linked to an object id in association table
3989
	 *
3990
	 * @param	int		$fk_object_where		id of object we need to get linked items
3991
	 * @param	string	$field_select			name of field we need to get a list
3992
	 * @param	string	$field_where			name of field of object we need to get linked items
3993
	 * @param	string	$table_element			name of association table
3994
	 * @return 	array							Array of record
3995
	 */
3996
	public static function getAllItemsLinkedByObjectID($fk_object_where, $field_select, $field_where, $table_element)
3997
	{
3998
		if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
3999
			return -1;
4000
		}
4001
4002
		global $db;
4003
4004
		$sql = 'SELECT '.$field_select.' FROM '.MAIN_DB_PREFIX.$table_element.' WHERE '.$field_where.' = '.((int) $fk_object_where);
4005
		$resql = $db->query($sql);
4006
4007
		$TRes = array();
4008
		if (!empty($resql)) {
4009
			while ($res = $db->fetch_object($resql)) {
4010
				$TRes[] = $res->{$field_select};
4011
			}
4012
		}
4013
4014
		return $TRes;
4015
	}
4016
4017
	/**
4018
	 * Function used to remove all items linked to an object id in association table
4019
	 *
4020
	 * @param	int		$fk_object_where		id of object we need to remove linked items
4021
	 * @param	string	$field_where			name of field of object we need to delete linked items
4022
	 * @param	string	$table_element			name of association table
4023
	 * @return 	int								<0 if KO, 0 if nothing done, >0 if OK and something done
4024
	 */
4025
	public static function deleteAllItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
4026
	{
4027
		if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4028
			return -1;
4029
		}
4030
4031
		global $db;
4032
4033
		$sql = 'DELETE FROM '.MAIN_DB_PREFIX.$table_element.' WHERE '.$field_where.' = '.$fk_object_where;
4034
		$resql = $db->query($sql);
4035
4036
		if (empty($resql)) {
4037
			return 0;
4038
		}
4039
4040
		return 1;
4041
	}
4042
4043
	/**
4044
	 *      Set status of an object
4045
	 *
4046
	 *      @param	int		$status			Status to set
4047
	 *      @param	int		$elementId		Id of element to force (use this->id by default)
4048
	 *      @param	string	$elementType	Type of element to force (use this->table_element by default)
4049
	 *      @param	string	$trigkey		Trigger key to use for trigger
4050
	 *      @return int						<0 if KO, >0 if OK
4051
	 */
4052
	public function setStatut($status, $elementId = null, $elementType = '', $trigkey = '')
4053
	{
4054
		global $user, $langs, $conf;
4055
4056
		$savElementId = $elementId; // To be used later to know if we were using the method using the id of this or not.
4057
4058
		$elementId = (!empty($elementId) ? $elementId : $this->id);
4059
		$elementTable = (!empty($elementType) ? $elementType : $this->table_element);
4060
4061
		$this->db->begin();
4062
4063
		$fieldstatus = "fk_statut";
4064
		if ($elementTable == 'facture_rec') {
4065
			$fieldstatus = "suspended";
4066
		}
4067
		if ($elementTable == 'mailing') {
4068
			$fieldstatus = "statut";
4069
		}
4070
		if ($elementTable == 'cronjob') {
4071
			$fieldstatus = "status";
4072
		}
4073
		if ($elementTable == 'user') {
4074
			$fieldstatus = "statut";
4075
		}
4076
		if ($elementTable == 'expensereport') {
4077
			$fieldstatus = "fk_statut";
4078
		}
4079
		if ($elementTable == 'commande_fournisseur_dispatch') {
4080
			$fieldstatus = "status";
4081
		}
4082
		if (is_array($this->fields) && array_key_exists('status', $this->fields)) {
4083
			$fieldstatus = 'status';
4084
		}
4085
4086
		$sql = "UPDATE ".MAIN_DB_PREFIX.$elementTable;
4087
		$sql .= " SET ".$fieldstatus." = ".$status;
4088
		// If status = 1 = validated, update also fk_user_valid
4089
		if ($status == 1 && $elementTable == 'expensereport') {
4090
			$sql .= ", fk_user_valid = ".$user->id;
4091
		}
4092
		$sql .= " WHERE rowid=".$elementId;
4093
4094
		dol_syslog(get_class($this)."::setStatut", LOG_DEBUG);
4095
		if ($this->db->query($sql)) {
4096
			$error = 0;
4097
4098
			// Try autoset of trigkey
4099
			if (empty($trigkey)) {
4100
				if ($this->element == 'supplier_proposal' && $status == 2) {
4101
					$trigkey = 'SUPPLIER_PROPOSAL_SIGN'; // 2 = SupplierProposal::STATUS_SIGNED. Can't use constant into this generic class
4102
				}
4103
				if ($this->element == 'supplier_proposal' && $status == 3) {
4104
					$trigkey = 'SUPPLIER_PROPOSAL_REFUSE'; // 3 = SupplierProposal::STATUS_REFUSED. Can't use constant into this generic class
4105
				}
4106
				if ($this->element == 'supplier_proposal' && $status == 4) {
4107
					$trigkey = 'SUPPLIER_PROPOSAL_CLOSE'; // 4 = SupplierProposal::STATUS_CLOSED. Can't use constant into this generic class
4108
				}
4109
				if ($this->element == 'fichinter' && $status == 3) {
4110
					$trigkey = 'FICHINTER_CLASSIFY_DONE';
4111
				}
4112
				if ($this->element == 'fichinter' && $status == 2) {
4113
					$trigkey = 'FICHINTER_CLASSIFY_BILLED';
4114
				}
4115
				if ($this->element == 'fichinter' && $status == 1) {
4116
					$trigkey = 'FICHINTER_CLASSIFY_UNBILLED';
4117
				}
4118
			}
4119
4120
			if ($trigkey) {
4121
				// Call trigger
4122
				$result = $this->call_trigger($trigkey, $user);
4123
				if ($result < 0) {
4124
					$error++;
4125
				}
4126
				// End call triggers
4127
			}
4128
4129
			if (!$error) {
4130
				$this->db->commit();
4131
4132
				if (empty($savElementId)) {    // If the element we update was $this (so $elementId is null)
4133
					$this->statut = $status;
4134
					$this->status = $status;
4135
				}
4136
4137
				return 1;
4138
			} else {
4139
				$this->db->rollback();
4140
				dol_syslog(get_class($this)."::setStatut ".$this->error, LOG_ERR);
4141
				return -1;
4142
			}
4143
		} else {
4144
			$this->error = $this->db->lasterror();
4145
			$this->db->rollback();
4146
			return -1;
4147
		}
4148
	}
4149
4150
4151
	/**
4152
	 *  Load type of canvas of an object if it exists
4153
	 *
4154
	 *  @param      int		$id     Record id
4155
	 *  @param      string	$ref    Record ref
4156
	 *  @return		int				<0 if KO, 0 if nothing done, >0 if OK
4157
	 */
4158
	public function getCanvas($id = 0, $ref = '')
4159
	{
4160
		global $conf;
4161
4162
		if (empty($id) && empty($ref)) {
4163
			return 0;
4164
		}
4165
		if (!empty($conf->global->MAIN_DISABLE_CANVAS)) {
4166
			return 0; // To increase speed. Not enabled by default.
4167
		}
4168
4169
		// Clean parameters
4170
		$ref = trim($ref);
4171
4172
		$sql = "SELECT rowid, canvas";
4173
		$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
4174
		$sql .= " WHERE entity IN (".getEntity($this->element).")";
4175
		if (!empty($id)) {
4176
			$sql .= " AND rowid = ".$id;
4177
		}
4178
		if (!empty($ref)) {
4179
			$sql .= " AND ref = '".$this->db->escape($ref)."'";
4180
		}
4181
4182
		$resql = $this->db->query($sql);
4183
		if ($resql) {
4184
			$obj = $this->db->fetch_object($resql);
4185
			if ($obj) {
4186
				$this->canvas = $obj->canvas;
4187
				return 1;
4188
			} else {
4189
				return 0;
4190
			}
4191
		} else {
4192
			dol_print_error($this->db);
4193
			return -1;
4194
		}
4195
	}
4196
4197
4198
	/**
4199
	 * 	Get special code of a line
4200
	 *
4201
	 * 	@param	int		$lineid		Id of line
4202
	 * 	@return	int					Special code
4203
	 */
4204
	public function getSpecialCode($lineid)
4205
	{
4206
		$sql = 'SELECT special_code FROM '.MAIN_DB_PREFIX.$this->table_element_line;
4207
		$sql .= ' WHERE rowid = '.$lineid;
4208
		$resql = $this->db->query($sql);
4209
		if ($resql) {
4210
			$row = $this->db->fetch_row($resql);
4211
			return $row[0];
4212
		}
4213
	}
4214
4215
	/**
4216
	 *  Function to check if an object is used by others.
4217
	 *  Check is done into this->childtables. There is no check into llx_element_element.
4218
	 *
4219
	 *  @param	int		$id			Force id of object
4220
	 *  @return	int					<0 if KO, 0 if not used, >0 if already used
4221
	 */
4222
	public function isObjectUsed($id = 0)
4223
	{
4224
		global $langs;
4225
4226
		if (empty($id)) {
4227
			$id = $this->id;
4228
		}
4229
4230
		// Check parameters
4231
		if (!isset($this->childtables) || !is_array($this->childtables) || count($this->childtables) == 0) {
4232
			dol_print_error('Called isObjectUsed on a class with property this->childtables not defined');
4233
			return -1;
4234
		}
4235
4236
		$arraytoscan = $this->childtables;
4237
		// For backward compatibility, we check if array is old format array('table1', 'table2', ...)
4238
		$tmparray = array_keys($this->childtables);
4239
		if (is_numeric($tmparray[0])) {
4240
			$arraytoscan = array_flip($this->childtables);
4241
		}
4242
4243
		// Test if child exists
4244
		$haschild = 0;
4245
		foreach ($arraytoscan as $table => $elementname) {
4246
			//print $id.'-'.$table.'-'.$elementname.'<br>';
4247
			// Check if third party can be deleted
4248
			$sql = "SELECT COUNT(*) as nb from ".MAIN_DB_PREFIX.$table;
4249
			$sql .= " WHERE ".$this->fk_element." = ".$id;
4250
			$resql = $this->db->query($sql);
4251
			if ($resql) {
4252
				$obj = $this->db->fetch_object($resql);
4253
				if ($obj->nb > 0) {
4254
					$langs->load("errors");
4255
					//print 'Found into table '.$table.', type '.$langs->transnoentitiesnoconv($elementname).', haschild='.$haschild;
4256
					$haschild += $obj->nb;
4257
					if (is_numeric($elementname)) {	// old usage
4258
						$this->errors[] = $langs->trans("ErrorRecordHasAtLeastOneChildOfType", $table);
4259
					} else // new usage: $elementname=Translation key
4260
					{
4261
						$this->errors[] = $langs->trans("ErrorRecordHasAtLeastOneChildOfType", $langs->transnoentitiesnoconv($elementname));
4262
					}
4263
					break; // We found at least one, we stop here
4264
				}
4265
			} else {
4266
				$this->errors[] = $this->db->lasterror();
4267
				return -1;
4268
			}
4269
		}
4270
		if ($haschild > 0) {
4271
			$this->errors[] = "ErrorRecordHasChildren";
4272
			return $haschild;
4273
		} else {
4274
			return 0;
4275
		}
4276
	}
4277
4278
	/**
4279
	 *  Function to say how many lines object contains
4280
	 *
4281
	 *	@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
4282
	 *  @return	int						<0 if KO, 0 if no predefined products, nb of lines with predefined products if found
4283
	 */
4284
	public function hasProductsOrServices($predefined = -1)
4285
	{
4286
		$nb = 0;
4287
4288
		foreach ($this->lines as $key => $val) {
4289
			$qualified = 0;
4290
			if ($predefined == -1) {
4291
				$qualified = 1;
4292
			}
4293
			if ($predefined == 1 && $val->fk_product > 0) {
4294
				$qualified = 1;
4295
			}
4296
			if ($predefined == 0 && $val->fk_product <= 0) {
4297
				$qualified = 1;
4298
			}
4299
			if ($predefined == 2 && $val->fk_product > 0 && $val->product_type == 0) {
4300
				$qualified = 1;
4301
			}
4302
			if ($predefined == 3 && $val->fk_product > 0 && $val->product_type == 1) {
4303
				$qualified = 1;
4304
			}
4305
			if ($qualified) {
4306
				$nb++;
4307
			}
4308
		}
4309
		dol_syslog(get_class($this).'::hasProductsOrServices we found '.$nb.' qualified lines of products/servcies');
4310
		return $nb;
4311
	}
4312
4313
	/**
4314
	 * Function that returns the total amount HT of discounts applied for all lines.
4315
	 *
4316
	 * @return 	float
4317
	 */
4318
	public function getTotalDiscount()
4319
	{
4320
		$total_discount = 0.00;
4321
4322
		$sql = "SELECT subprice as pu_ht, qty, remise_percent, total_ht";
4323
		$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element."det";
4324
		$sql .= " WHERE ".$this->fk_element." = ".$this->id;
4325
4326
		dol_syslog(get_class($this).'::getTotalDiscount', LOG_DEBUG);
4327
		$resql = $this->db->query($sql);
4328
		if ($resql) {
4329
			$num = $this->db->num_rows($resql);
4330
			$i = 0;
4331
			while ($i < $num) {
4332
				$obj = $this->db->fetch_object($resql);
4333
4334
				$pu_ht = $obj->pu_ht;
4335
				$qty = $obj->qty;
4336
				$total_ht = $obj->total_ht;
4337
4338
				$total_discount_line = floatval(price2num(($pu_ht * $qty) - $total_ht, 'MT'));
4339
				$total_discount += $total_discount_line;
4340
4341
				$i++;
4342
			}
4343
		}
4344
4345
		//print $total_discount; exit;
4346
		return price2num($total_discount);
4347
	}
4348
4349
4350
	/**
4351
	 * Return into unit=0, the calculated total of weight and volume of all lines * qty
4352
	 * Calculate by adding weight and volume of each product line, so properties ->volume/volume_units/weight/weight_units must be loaded on line.
4353
	 *
4354
	 * @return  array                           array('weight'=>...,'volume'=>...)
4355
	 */
4356
	public function getTotalWeightVolume()
4357
	{
4358
		$totalWeight = 0;
4359
		$totalVolume = 0;
4360
		// defined for shipment only
4361
		$totalOrdered = '';
4362
		// defined for shipment only
4363
		$totalToShip = '';
4364
4365
		foreach ($this->lines as $line) {
4366
			if (isset($line->qty_asked)) {
4367
				if (empty($totalOrdered)) {
4368
					$totalOrdered = 0; // Avoid warning because $totalOrdered is ''
4369
				}
4370
				$totalOrdered += $line->qty_asked; // defined for shipment only
4371
			}
4372
			if (isset($line->qty_shipped)) {
4373
				if (empty($totalToShip)) {
4374
					$totalToShip = 0; // Avoid warning because $totalToShip is ''
4375
				}
4376
				$totalToShip += $line->qty_shipped; // defined for shipment only
4377
			} elseif ($line->element == 'commandefournisseurdispatch' && isset($line->qty)) {
4378
				if (empty($totalToShip)) {
4379
					$totalToShip = 0;
4380
				}
4381
				$totalToShip += $line->qty; // defined for reception only
4382
			}
4383
4384
			// Define qty, weight, volume, weight_units, volume_units
4385
			if ($this->element == 'shipping') {
4386
				// for shipments
4387
				$qty = $line->qty_shipped ? $line->qty_shipped : 0;
4388
			} else {
4389
				$qty = $line->qty ? $line->qty : 0;
4390
			}
4391
4392
			$weight = $line->weight ? $line->weight : 0;
4393
			($weight == 0 && !empty($line->product->weight)) ? $weight = $line->product->weight : 0;
4394
			$volume = $line->volume ? $line->volume : 0;
4395
			($volume == 0 && !empty($line->product->volume)) ? $volume = $line->product->volume : 0;
4396
4397
			$weight_units = $line->weight_units;
4398
			($weight_units == 0 && !empty($line->product->weight_units)) ? $weight_units = $line->product->weight_units : 0;
4399
			$volume_units = $line->volume_units;
4400
			($volume_units == 0 && !empty($line->product->volume_units)) ? $volume_units = $line->product->volume_units : 0;
4401
4402
			$weightUnit = 0;
4403
			$volumeUnit = 0;
4404
			if (!empty($weight_units)) {
4405
				$weightUnit = $weight_units;
4406
			}
4407
			if (!empty($volume_units)) {
4408
				$volumeUnit = $volume_units;
4409
			}
4410
4411
			if (empty($totalWeight)) {
4412
				$totalWeight = 0; // Avoid warning because $totalWeight is ''
4413
			}
4414
			if (empty($totalVolume)) {
4415
				$totalVolume = 0; // Avoid warning because $totalVolume is ''
4416
			}
4417
4418
			//var_dump($line->volume_units);
4419
			if ($weight_units < 50) {   // < 50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
4420
				$trueWeightUnit = pow(10, $weightUnit);
4421
				$totalWeight += $weight * $qty * $trueWeightUnit;
4422
			} else {
4423
				if ($weight_units == 99) {
4424
					// conversion 1 Pound = 0.45359237 KG
4425
					$trueWeightUnit = 0.45359237;
4426
					$totalWeight += $weight * $qty * $trueWeightUnit;
4427
				} elseif ($weight_units == 98) {
4428
					// conversion 1 Ounce = 0.0283495 KG
4429
					$trueWeightUnit = 0.0283495;
4430
					$totalWeight += $weight * $qty * $trueWeightUnit;
4431
				} else {
4432
					$totalWeight += $weight * $qty; // This may be wrong if we mix different units
4433
				}
4434
			}
4435
			if ($volume_units < 50) {   // >50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
4436
				//print $line->volume."x".$line->volume_units."x".($line->volume_units < 50)."x".$volumeUnit;
4437
				$trueVolumeUnit = pow(10, $volumeUnit);
4438
				//print $line->volume;
4439
				$totalVolume += $volume * $qty * $trueVolumeUnit;
4440
			} else {
4441
				$totalVolume += $volume * $qty; // This may be wrong if we mix different units
4442
			}
4443
		}
4444
4445
		return array('weight'=>$totalWeight, 'volume'=>$totalVolume, 'ordered'=>$totalOrdered, 'toship'=>$totalToShip);
4446
	}
4447
4448
4449
	/**
4450
	 *	Set extra parameters
4451
	 *
4452
	 *	@return	int      <0 if KO, >0 if OK
4453
	 */
4454
	public function setExtraParameters()
4455
	{
4456
		$this->db->begin();
4457
4458
		$extraparams = (!empty($this->extraparams) ? json_encode($this->extraparams) : null);
4459
4460
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
4461
		$sql .= " SET extraparams = ".(!empty($extraparams) ? "'".$this->db->escape($extraparams)."'" : "null");
4462
		$sql .= " WHERE rowid = ".$this->id;
4463
4464
		dol_syslog(get_class($this)."::setExtraParameters", LOG_DEBUG);
4465
		$resql = $this->db->query($sql);
4466
		if (!$resql) {
4467
			$this->error = $this->db->lasterror();
4468
			$this->db->rollback();
4469
			return -1;
4470
		} else {
4471
			$this->db->commit();
4472
			return 1;
4473
		}
4474
	}
4475
4476
4477
	// --------------------
4478
	// TODO: All functions here must be redesigned and moved as they are not business functions but output functions
4479
	// --------------------
4480
4481
	/* This is to show add lines */
4482
4483
	/**
4484
	 *	Show add free and predefined products/services form
4485
	 *
4486
	 *  @param	int		        $dateSelector       1=Show also date range input fields
4487
	 *  @param	Societe			$seller				Object thirdparty who sell
4488
	 *  @param	Societe			$buyer				Object thirdparty who buy
4489
	 *  @param	string			$defaulttpldir		Directory where to find the template
4490
	 *	@return	void
4491
	 */
4492
	public function formAddObjectLine($dateSelector, $seller, $buyer, $defaulttpldir = '/core/tpl')
4493
	{
4494
		global $conf, $user, $langs, $object, $hookmanager, $extrafields;
4495
		global $form;
4496
4497
		// Line extrafield
4498
		if (!is_object($extrafields)) {
4499
			require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
4500
			$extrafields = new ExtraFields($this->db);
4501
		}
4502
		$extrafields->fetch_name_optionals_label($this->table_element_line);
4503
4504
		// Output template part (modules that overwrite templates must declare this into descriptor)
4505
		// Use global variables + $dateSelector + $seller and $buyer
4506
		// Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook 'formAddObjectLine'.
4507
		$dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
4508
		foreach ($dirtpls as $module => $reldir) {
4509
			if (!empty($module)) {
4510
				$tpl = dol_buildpath($reldir.'/objectline_create.tpl.php');
4511
			} else {
4512
				$tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_create.tpl.php';
4513
			}
4514
4515
			if (empty($conf->file->strict_mode)) {
4516
				$res = @include $tpl;
4517
			} else {
4518
				$res = include $tpl; // for debug
4519
			}
4520
			if ($res) {
4521
				break;
4522
			}
4523
		}
4524
	}
4525
4526
4527
4528
	/* This is to show array of line of details */
4529
4530
4531
	/**
4532
	 *	Return HTML table for object lines
4533
	 *	TODO Move this into an output class file (htmlline.class.php)
4534
	 *	If lines are into a template, title must also be into a template
4535
	 *	But for the moment we don't know if it's possible as we keep a method available on overloaded objects.
4536
	 *
4537
	 *	@param	string		$action				Action code
4538
	 *	@param  string		$seller            	Object of seller third party
4539
	 *	@param  string  	$buyer             	Object of buyer third party
4540
	 *	@param	int			$selected		   	Object line selected
4541
	 *	@param  int	    	$dateSelector      	1=Show also date range input fields
4542
	 *  @param	string		$defaulttpldir		Directory where to find the template
4543
	 *	@return	void
4544
	 */
4545
	public function printObjectLines($action, $seller, $buyer, $selected = 0, $dateSelector = 0, $defaulttpldir = '/core/tpl')
4546
	{
4547
		global $conf, $hookmanager, $langs, $user, $form, $extrafields, $object;
4548
		// TODO We should not use global var for this
4549
		global $inputalsopricewithtax, $usemargins, $disableedit, $disablemove, $disableremove, $outputalsopricetotalwithtax;
4550
4551
		// Define usemargins
4552
		$usemargins = 0;
4553
		if (!empty($conf->margin->enabled) && !empty($this->element) && in_array($this->element, array('facture', 'facturerec', 'propal', 'commande'))) {
4554
			$usemargins = 1;
4555
		}
4556
4557
		$num = count($this->lines);
4558
4559
		// Line extrafield
4560
		if (!is_object($extrafields)) {
4561
			require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
4562
			$extrafields = new ExtraFields($this->db);
4563
		}
4564
		$extrafields->fetch_name_optionals_label($this->table_element_line);
4565
4566
		$parameters = array('num'=>$num, 'dateSelector'=>$dateSelector, 'seller'=>$seller, 'buyer'=>$buyer, 'selected'=>$selected, 'table_element_line'=>$this->table_element_line);
4567
		$reshook = $hookmanager->executeHooks('printObjectLineTitle', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4568
		if (empty($reshook)) {
4569
			// Output template part (modules that overwrite templates must declare this into descriptor)
4570
			// Use global variables + $dateSelector + $seller and $buyer
4571
			// Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook.
4572
			$dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
4573
			foreach ($dirtpls as $module => $reldir) {
4574
				if (!empty($module)) {
4575
					$tpl = dol_buildpath($reldir.'/objectline_title.tpl.php');
4576
				} else {
4577
					$tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_title.tpl.php';
4578
				}
4579
				if (empty($conf->file->strict_mode)) {
4580
					$res = @include $tpl;
4581
				} else {
4582
					$res = include $tpl; // for debug
4583
				}
4584
				if ($res) {
4585
					break;
4586
				}
4587
			}
4588
		}
4589
4590
		$i = 0;
4591
4592
		print "<!-- begin printObjectLines() --><tbody>\n";
4593
		foreach ($this->lines as $line) {
4594
			//Line extrafield
4595
			$line->fetch_optionals();
4596
4597
			//if (is_object($hookmanager) && (($line->product_type == 9 && ! empty($line->special_code)) || ! empty($line->fk_parent_line)))
4598
			if (is_object($hookmanager)) {   // Old code is commented on preceding line.
4599
				if (empty($line->fk_parent_line)) {
4600
					$parameters = array('line'=>$line, 'num'=>$num, 'i'=>$i, 'dateSelector'=>$dateSelector, 'seller'=>$seller, 'buyer'=>$buyer, 'selected'=>$selected, 'table_element_line'=>$line->table_element);
4601
					$reshook = $hookmanager->executeHooks('printObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4602
				} else {
4603
					$parameters = array('line'=>$line, 'num'=>$num, 'i'=>$i, 'dateSelector'=>$dateSelector, 'seller'=>$seller, 'buyer'=>$buyer, 'selected'=>$selected, 'table_element_line'=>$line->table_element, 'fk_parent_line'=>$line->fk_parent_line);
4604
					$reshook = $hookmanager->executeHooks('printObjectSubLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4605
				}
4606
			}
4607
			if (empty($reshook)) {
4608
				$this->printObjectLine($action, $line, '', $num, $i, $dateSelector, $seller, $buyer, $selected, $extrafields, $defaulttpldir);
4609
			}
4610
4611
			$i++;
4612
		}
4613
		print "</tbody><!-- end printObjectLines() -->\n";
4614
	}
4615
4616
	/**
4617
	 *	Return HTML content of a detail line
4618
	 *	TODO Move this into an output class file (htmlline.class.php)
4619
	 *
4620
	 *	@param	string      		$action				GET/POST action
4621
	 *	@param  CommonObjectLine 	$line			    Selected object line to output
4622
	 *	@param  string	    		$var               	Is it a an odd line (true)
4623
	 *	@param  int		    		$num               	Number of line (0)
4624
	 *	@param  int		    		$i					I
4625
	 *	@param  int		    		$dateSelector      	1=Show also date range input fields
4626
	 *	@param  string	    		$seller            	Object of seller third party
4627
	 *	@param  string	    		$buyer             	Object of buyer third party
4628
	 *	@param	int					$selected		   	Object line selected
4629
	 *  @param  Extrafields			$extrafields		Object of extrafields
4630
	 *  @param	string				$defaulttpldir		Directory where to find the template (deprecated)
4631
	 *	@return	void
4632
	 */
4633
	public function printObjectLine($action, $line, $var, $num, $i, $dateSelector, $seller, $buyer, $selected = 0, $extrafields = null, $defaulttpldir = '/core/tpl')
4634
	{
4635
		global $conf, $langs, $user, $object, $hookmanager;
4636
		global $form;
4637
		global $object_rights, $disableedit, $disablemove, $disableremove; // TODO We should not use global var for this !
4638
4639
		$object_rights = $this->getRights();
4640
4641
		$element = $this->element;
4642
4643
		$text = '';
4644
		$description = '';
4645
4646
		// Line in view mode
4647
		if ($action != 'editline' || $selected != $line->id) {
4648
			// Product
4649
			if ($line->fk_product > 0) {
4650
				$product_static = new Product($this->db);
4651
				$product_static->fetch($line->fk_product);
4652
4653
				$product_static->ref = $line->ref; //can change ref in hook
4654
				$product_static->label = $line->label; //can change label in hook
4655
4656
				$text = $product_static->getNomUrl(1);
4657
4658
				// Define output language and label
4659
				if (!empty($conf->global->MAIN_MULTILANGS)) {
4660
					if (property_exists($this, 'socid') && !is_object($this->thirdparty)) {
4661
						dol_print_error('', 'Error: Method printObjectLine was called on an object and object->fetch_thirdparty was not done before');
4662
						return;
4663
					}
4664
4665
					$prod = new Product($this->db);
4666
					$prod->fetch($line->fk_product);
4667
4668
					$outputlangs = $langs;
4669
					$newlang = '';
4670
					if (empty($newlang) && GETPOST('lang_id', 'aZ09')) {
4671
						$newlang = GETPOST('lang_id', 'aZ09');
4672
					}
4673
					if (!empty($conf->global->PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE) && empty($newlang) && is_object($this->thirdparty)) {
4674
						$newlang = $this->thirdparty->default_lang; // To use language of customer
4675
					}
4676
					if (!empty($newlang)) {
4677
						$outputlangs = new Translate("", $conf);
4678
						$outputlangs->setDefaultLang($newlang);
4679
					}
4680
4681
					$label = (!empty($prod->multilangs[$outputlangs->defaultlang]["label"])) ? $prod->multilangs[$outputlangs->defaultlang]["label"] : $line->product_label;
4682
				} else {
4683
					$label = $line->product_label;
4684
				}
4685
4686
				$text .= ' - '.(!empty($line->label) ? $line->label : $label);
4687
				$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.
4688
			}
4689
4690
			$line->pu_ttc = price2num($line->subprice * (1 + ($line->tva_tx / 100)), 'MU');
4691
4692
			// Output template part (modules that overwrite templates must declare this into descriptor)
4693
			// Use global variables + $dateSelector + $seller and $buyer
4694
			// Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
4695
			$dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
4696
			foreach ($dirtpls as $module => $reldir) {
4697
				if (!empty($module)) {
4698
					$tpl = dol_buildpath($reldir.'/objectline_view.tpl.php');
4699
				} else {
4700
					$tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_view.tpl.php';
4701
				}
4702
4703
				if (empty($conf->file->strict_mode)) {
4704
					$res = @include $tpl;
4705
				} else {
4706
					$res = include $tpl; // for debug
4707
				}
4708
				if ($res) {
4709
					break;
4710
				}
4711
			}
4712
		}
4713
4714
		// Line in update mode
4715
		if ($this->statut == 0 && $action == 'editline' && $selected == $line->id) {
4716
			$label = (!empty($line->label) ? $line->label : (($line->fk_product > 0) ? $line->product_label : ''));
4717
4718
			$line->pu_ttc = price2num($line->subprice * (1 + ($line->tva_tx / 100)), 'MU');
4719
4720
			// Output template part (modules that overwrite templates must declare this into descriptor)
4721
			// Use global variables + $dateSelector + $seller and $buyer
4722
			// Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
4723
			$dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
4724
			foreach ($dirtpls as $module => $reldir) {
4725
				if (!empty($module)) {
4726
					$tpl = dol_buildpath($reldir.'/objectline_edit.tpl.php');
4727
				} else {
4728
					$tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_edit.tpl.php';
4729
				}
4730
4731
				if (empty($conf->file->strict_mode)) {
4732
					$res = @include $tpl;
4733
				} else {
4734
					$res = include $tpl; // for debug
4735
				}
4736
				if ($res) {
4737
					break;
4738
				}
4739
			}
4740
		}
4741
	}
4742
4743
4744
	/* This is to show array of line of details of source object */
4745
4746
4747
	/**
4748
	 * 	Return HTML table table of source object lines
4749
	 *  TODO Move this and previous function into output html class file (htmlline.class.php).
4750
	 *  If lines are into a template, title must also be into a template
4751
	 *  But for the moment we don't know if it's possible, so we keep the method available on overloaded objects.
4752
	 *
4753
	 *	@param	string		$restrictlist		''=All lines, 'services'=Restrict to services only
4754
	 *  @param  array       $selectedLines      Array of lines id for selected lines
4755
	 *  @return	void
4756
	 */
4757
	public function printOriginLinesList($restrictlist = '', $selectedLines = array())
4758
	{
4759
		global $langs, $hookmanager, $conf, $form;
4760
4761
		print '<tr class="liste_titre">';
4762
		print '<td>'.$langs->trans('Ref').'</td>';
4763
		print '<td>'.$langs->trans('Description').'</td>';
4764
		print '<td class="right">'.$langs->trans('VATRate').'</td>';
4765
		print '<td class="right">'.$langs->trans('PriceUHT').'</td>';
4766
		if (!empty($conf->multicurrency->enabled)) {
4767
			print '<td class="right">'.$langs->trans('PriceUHTCurrency').'</td>';
4768
		}
4769
		print '<td class="right">'.$langs->trans('Qty').'</td>';
4770
		if (!empty($conf->global->PRODUCT_USE_UNITS)) {
4771
			print '<td class="left">'.$langs->trans('Unit').'</td>';
4772
		}
4773
		print '<td class="right">'.$langs->trans('ReductionShort').'</td>';
4774
		print '<td class="center">'.$form->showCheckAddButtons('checkforselect', 1).'</td>';
4775
		print '</tr>';
4776
		$i = 0;
4777
4778
		if (!empty($this->lines)) {
4779
			foreach ($this->lines as $line) {
4780
				if (is_object($hookmanager) && (($line->product_type == 9 && !empty($line->special_code)) || !empty($line->fk_parent_line))) {
4781
					if (empty($line->fk_parent_line)) {
4782
						$parameters = array('line'=>$line, 'i'=>$i);
4783
						$action = '';
4784
						$hookmanager->executeHooks('printOriginObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4785
					}
4786
				} else {
4787
					$this->printOriginLine($line, '', $restrictlist, '/core/tpl', $selectedLines);
4788
				}
4789
4790
				$i++;
4791
			}
4792
		}
4793
	}
4794
4795
	/**
4796
	 * 	Return HTML with a line of table array of source object lines
4797
	 *  TODO Move this and previous function into output html class file (htmlline.class.php).
4798
	 *  If lines are into a template, title must also be into a template
4799
	 *  But for the moment we don't know if it's possible as we keep a method available on overloaded objects.
4800
	 *
4801
	 * 	@param	CommonObjectLine	$line				Line
4802
	 * 	@param	string				$var				Var
4803
	 *	@param	string				$restrictlist		''=All lines, 'services'=Restrict to services only (strike line if not)
4804
	 *  @param	string				$defaulttpldir		Directory where to find the template
4805
	 *  @param  array       		$selectedLines      Array of lines id for selected lines
4806
	 * 	@return	void
4807
	 */
4808
	public function printOriginLine($line, $var, $restrictlist = '', $defaulttpldir = '/core/tpl', $selectedLines = array())
4809
	{
4810
		global $langs, $conf;
4811
4812
		//var_dump($line);
4813
		if (!empty($line->date_start)) {
4814
			$date_start = $line->date_start;
4815
		} else {
4816
			$date_start = $line->date_debut_prevue;
0 ignored issues
show
Bug introduced by
The property date_debut_prevue does not seem to exist on CommonObjectLine.
Loading history...
4817
			if ($line->date_debut_reel) {
0 ignored issues
show
Bug introduced by
The property date_debut_reel does not seem to exist on CommonObjectLine.
Loading history...
4818
				$date_start = $line->date_debut_reel;
4819
			}
4820
		}
4821
		if (!empty($line->date_end)) {
4822
			$date_end = $line->date_end;
4823
		} else {
4824
			$date_end = $line->date_fin_prevue;
0 ignored issues
show
Bug introduced by
The property date_fin_prevue does not seem to exist on CommonObjectLine.
Loading history...
4825
			if ($line->date_fin_reel) {
0 ignored issues
show
Bug introduced by
The property date_fin_reel does not seem to exist on CommonObjectLine.
Loading history...
4826
				$date_end = $line->date_fin_reel;
4827
			}
4828
		}
4829
4830
		$this->tpl['id'] = $line->id;
4831
4832
		$this->tpl['label'] = '';
4833
		if (!empty($line->fk_parent_line)) {
4834
			$this->tpl['label'] .= img_picto('', 'rightarrow');
4835
		}
4836
4837
		if (($line->info_bits & 2) == 2) {  // TODO Not sure this is used for source object
4838
			$discount = new DiscountAbsolute($this->db);
4839
			$discount->fk_soc = $this->socid;
4840
			$this->tpl['label'] .= $discount->getNomUrl(0, 'discount');
4841
		} elseif (!empty($line->fk_product)) {
4842
			$productstatic = new Product($this->db);
4843
			$productstatic->id = $line->fk_product;
4844
			$productstatic->ref = $line->ref;
4845
			$productstatic->type = $line->fk_product_type;
4846
			if (empty($productstatic->ref)) {
4847
				$line->fetch_product();
4848
				$productstatic = $line->product;
4849
			}
4850
4851
			$this->tpl['label'] .= $productstatic->getNomUrl(1);
4852
			$this->tpl['label'] .= ' - '.(!empty($line->label) ? $line->label : $line->product_label);
4853
			// Dates
4854
			if ($line->product_type == 1 && ($date_start || $date_end)) {
4855
				$this->tpl['label'] .= get_date_range($date_start, $date_end);
4856
			}
4857
		} else {
4858
			$this->tpl['label'] .= ($line->product_type == -1 ? '&nbsp;' : ($line->product_type == 1 ? img_object($langs->trans(''), 'service') : img_object($langs->trans(''), 'product')));
4859
			if (!empty($line->desc)) {
4860
				$this->tpl['label'] .= $line->desc;
4861
			} else {
4862
				$this->tpl['label'] .= ($line->label ? '&nbsp;'.$line->label : '');
4863
			}
4864
4865
			// Dates
4866
			if ($line->product_type == 1 && ($date_start || $date_end)) {
4867
				$this->tpl['label'] .= get_date_range($date_start, $date_end);
4868
			}
4869
		}
4870
4871
		if (!empty($line->desc)) {
4872
			if ($line->desc == '(CREDIT_NOTE)') {  // TODO Not sure this is used for source object
4873
				$discount = new DiscountAbsolute($this->db);
4874
				$discount->fetch($line->fk_remise_except);
4875
				$this->tpl['description'] = $langs->transnoentities("DiscountFromCreditNote", $discount->getNomUrl(0));
4876
			} elseif ($line->desc == '(DEPOSIT)') {  // TODO Not sure this is used for source object
4877
				$discount = new DiscountAbsolute($this->db);
4878
				$discount->fetch($line->fk_remise_except);
4879
				$this->tpl['description'] = $langs->transnoentities("DiscountFromDeposit", $discount->getNomUrl(0));
4880
			} elseif ($line->desc == '(EXCESS RECEIVED)') {
4881
				$discount = new DiscountAbsolute($this->db);
4882
				$discount->fetch($line->fk_remise_except);
4883
				$this->tpl['description'] = $langs->transnoentities("DiscountFromExcessReceived", $discount->getNomUrl(0));
4884
			} elseif ($line->desc == '(EXCESS PAID)') {
4885
				$discount = new DiscountAbsolute($this->db);
4886
				$discount->fetch($line->fk_remise_except);
4887
				$this->tpl['description'] = $langs->transnoentities("DiscountFromExcessPaid", $discount->getNomUrl(0));
4888
			} else {
4889
				$this->tpl['description'] = dol_trunc($line->desc, 60);
4890
			}
4891
		} else {
4892
			$this->tpl['description'] = '&nbsp;';
4893
		}
4894
4895
		// VAT Rate
4896
		$this->tpl['vat_rate'] = vatrate($line->tva_tx, true);
4897
		$this->tpl['vat_rate'] .= (($line->info_bits & 1) == 1) ? '*' : '';
4898
		if (!empty($line->vat_src_code) && !preg_match('/\(/', $this->tpl['vat_rate'])) {
4899
			$this->tpl['vat_rate'] .= ' ('.$line->vat_src_code.')';
4900
		}
4901
4902
		$this->tpl['price'] = price($line->subprice);
4903
		$this->tpl['multicurrency_price'] = price($line->multicurrency_subprice);
4904
		$this->tpl['qty'] = (($line->info_bits & 2) != 2) ? $line->qty : '&nbsp;';
4905
		if (!empty($conf->global->PRODUCT_USE_UNITS)) {
4906
			$this->tpl['unit'] = $langs->transnoentities($line->getLabelOfUnit('long'));
4907
		}
4908
		$this->tpl['remise_percent'] = (($line->info_bits & 2) != 2) ? vatrate($line->remise_percent, true) : '&nbsp;';
4909
4910
		// Is the line strike or not
4911
		$this->tpl['strike'] = 0;
4912
		if ($restrictlist == 'services' && $line->product_type != Product::TYPE_SERVICE) {
4913
			$this->tpl['strike'] = 1;
4914
		}
4915
4916
		// Output template part (modules that overwrite templates must declare this into descriptor)
4917
		// Use global variables + $dateSelector + $seller and $buyer
4918
		$dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
4919
		foreach ($dirtpls as $module => $reldir) {
4920
			if (!empty($module)) {
4921
				$tpl = dol_buildpath($reldir.'/originproductline.tpl.php');
4922
			} else {
4923
				$tpl = DOL_DOCUMENT_ROOT.$reldir.'/originproductline.tpl.php';
4924
			}
4925
4926
			if (empty($conf->file->strict_mode)) {
4927
				$res = @include $tpl;
4928
			} else {
4929
				$res = include $tpl; // for debug
4930
			}
4931
			if ($res) {
4932
				break;
4933
			}
4934
		}
4935
	}
4936
4937
4938
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4939
	/**
4940
	 *	Add resources to the current object : add entry into llx_element_resources
4941
	 *	Need $this->element & $this->id
4942
	 *
4943
	 *	@param		int		$resource_id		Resource id
4944
	 *	@param		string	$resource_type		'resource'
4945
	 *	@param		int		$busy				Busy or not
4946
	 *	@param		int		$mandatory			Mandatory or not
4947
	 *	@return		int							<=0 if KO, >0 if OK
4948
	 */
4949
	public function add_element_resource($resource_id, $resource_type, $busy = 0, $mandatory = 0)
4950
	{
4951
		// phpcs:enable
4952
		$this->db->begin();
4953
4954
		$sql = "INSERT INTO ".MAIN_DB_PREFIX."element_resources (";
4955
		$sql .= "resource_id";
4956
		$sql .= ", resource_type";
4957
		$sql .= ", element_id";
4958
		$sql .= ", element_type";
4959
		$sql .= ", busy";
4960
		$sql .= ", mandatory";
4961
		$sql .= ") VALUES (";
4962
		$sql .= $resource_id;
4963
		$sql .= ", '".$this->db->escape($resource_type)."'";
4964
		$sql .= ", '".$this->db->escape($this->id)."'";
4965
		$sql .= ", '".$this->db->escape($this->element)."'";
4966
		$sql .= ", '".$this->db->escape($busy)."'";
4967
		$sql .= ", '".$this->db->escape($mandatory)."'";
4968
		$sql .= ")";
4969
4970
		dol_syslog(get_class($this)."::add_element_resource", LOG_DEBUG);
4971
		if ($this->db->query($sql)) {
4972
			$this->db->commit();
4973
			return 1;
4974
		} else {
4975
			$this->error = $this->db->lasterror();
4976
			$this->db->rollback();
4977
			return  0;
4978
		}
4979
	}
4980
4981
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4982
	/**
4983
	 *    Delete a link to resource line
4984
	 *
4985
	 *    @param	int		$rowid			Id of resource line to delete
4986
	 *    @param	int		$element		element name (for trigger) TODO: use $this->element into commonobject class
4987
	 *    @param	int		$notrigger		Disable all triggers
4988
	 *    @return   int						>0 if OK, <0 if KO
4989
	 */
4990
	public function delete_resource($rowid, $element, $notrigger = 0)
4991
	{
4992
		// phpcs:enable
4993
		global $user;
4994
4995
		$this->db->begin();
4996
4997
		$sql = "DELETE FROM ".MAIN_DB_PREFIX."element_resources";
4998
		$sql .= " WHERE rowid=".$rowid;
4999
5000
		dol_syslog(get_class($this)."::delete_resource", LOG_DEBUG);
5001
5002
		$resql = $this->db->query($sql);
5003
		if (!$resql) {
5004
			$this->error = $this->db->lasterror();
5005
			$this->db->rollback();
5006
			return -1;
5007
		} else {
5008
			if (!$notrigger) {
5009
				$result = $this->call_trigger(strtoupper($element).'_DELETE_RESOURCE', $user);
5010
				if ($result < 0) {
5011
					$this->db->rollback();
5012
					return -1;
5013
				}
5014
			}
5015
			$this->db->commit();
5016
			return 1;
5017
		}
5018
	}
5019
5020
5021
	/**
5022
	 * Overwrite magic function to solve problem of cloning object that are kept as references
5023
	 *
5024
	 * @return void
5025
	 */
5026
	public function __clone()
5027
	{
5028
		// Force a copy of this->lines, otherwise it will point to same object.
5029
		if (isset($this->lines) && is_array($this->lines)) {
5030
			$nboflines = count($this->lines);
5031
			for ($i = 0; $i < $nboflines; $i++) {
5032
				$this->lines[$i] = clone $this->lines[$i];
5033
			}
5034
		}
5035
	}
5036
5037
	/**
5038
	 * Common function for all objects extending CommonObject for generating documents
5039
	 *
5040
	 * @param 	string 		$modelspath 	Relative folder where generators are placed
5041
	 * @param 	string 		$modele 		Generator to use. Caller must set it to obj->model_pdf or GETPOST('model_pdf','alpha') for example.
5042
	 * @param 	Translate 	$outputlangs 	Output language to use
5043
	 * @param 	int 		$hidedetails 	1 to hide details. 0 by default
5044
	 * @param 	int 		$hidedesc 		1 to hide product description. 0 by default
5045
	 * @param 	int 		$hideref 		1 to hide product reference. 0 by default
5046
	 * @param   null|array  $moreparams     Array to provide more information
5047
	 * @return 	int 						>0 if OK, <0 if KO
5048
	 * @see	addFileIntoDatabaseIndex()
5049
	 */
5050
	protected function commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams = null)
5051
	{
5052
		global $conf, $langs, $user, $hookmanager, $action;
5053
5054
		$srctemplatepath = '';
5055
5056
		$parameters = array('modelspath'=>$modelspath, 'modele'=>$modele, 'outputlangs'=>$outputlangs, 'hidedetails'=>$hidedetails, 'hidedesc'=>$hidedesc, 'hideref'=>$hideref, 'moreparams'=>$moreparams);
5057
		$reshook = $hookmanager->executeHooks('commonGenerateDocument', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5058
5059
		if (empty($reshook)) {
5060
			dol_syslog("commonGenerateDocument modele=".$modele." outputlangs->defaultlang=".(is_object($outputlangs) ? $outputlangs->defaultlang : 'null'));
5061
5062
			if (empty($modele)) {
5063
				$this->error = 'BadValueForParameterModele';
5064
				return -1;
5065
			}
5066
5067
			// Increase limit for PDF build
5068
			$err = error_reporting();
5069
			error_reporting(0);
5070
			@set_time_limit(120);
5071
			error_reporting($err);
5072
5073
			// If selected model is a filename template (then $modele="modelname" or "modelname:filename")
5074
			$tmp = explode(':', $modele, 2);
5075
			if (!empty($tmp[1])) {
5076
				$modele = $tmp[0];
5077
				$srctemplatepath = $tmp[1];
5078
			}
5079
5080
			// Search template files
5081
			$file = '';
5082
			$classname = '';
5083
			$filefound = '';
5084
			$dirmodels = array('/');
5085
			if (is_array($conf->modules_parts['models'])) {
5086
				$dirmodels = array_merge($dirmodels, $conf->modules_parts['models']);
5087
			}
5088
			foreach ($dirmodels as $reldir) {
5089
				foreach (array('doc', 'pdf') as $prefix) {
5090
					if (in_array(get_class($this), array('Adherent'))) {
5091
						// Member module use prefix_modele.class.php
5092
						$file = $prefix."_".$modele.".class.php";
5093
					} else {
5094
						// Other module use prefix_modele.modules.php
5095
						$file = $prefix."_".$modele.".modules.php";
5096
					}
5097
5098
					// On verifie l'emplacement du modele
5099
					$file = dol_buildpath($reldir.$modelspath.$file, 0);
5100
					if (file_exists($file)) {
5101
						$filefound = $file;
5102
						$classname = $prefix.'_'.$modele;
5103
						break;
5104
					}
5105
				}
5106
				if ($filefound) {
5107
					break;
5108
				}
5109
			}
5110
5111
			// If generator was found
5112
			if ($filefound) {
5113
				global $db; // Required to solve a conception default making an include of code using $db instead of $this->db just after.
5114
5115
				require_once $file;
5116
5117
				$obj = new $classname($this->db);
5118
5119
				// If generator is ODT, we must have srctemplatepath defined, if not we set it.
5120
				if ($obj->type == 'odt' && empty($srctemplatepath)) {
5121
					$varfortemplatedir = $obj->scandir;
5122
					if ($varfortemplatedir && !empty($conf->global->$varfortemplatedir)) {
5123
						$dirtoscan = $conf->global->$varfortemplatedir;
5124
5125
						$listoffiles = array();
5126
5127
						// Now we add first model found in directories scanned
5128
						$listofdir = explode(',', $dirtoscan);
5129
						foreach ($listofdir as $key => $tmpdir) {
5130
							$tmpdir = trim($tmpdir);
5131
							$tmpdir = preg_replace('/DOL_DATA_ROOT/', DOL_DATA_ROOT, $tmpdir);
5132
							if (!$tmpdir) {
5133
								unset($listofdir[$key]);
5134
								continue;
5135
							}
5136
							if (is_dir($tmpdir)) {
5137
								$tmpfiles = dol_dir_list($tmpdir, 'files', 0, '\.od(s|t)$', '', 'name', SORT_ASC, 0);
5138
								if (count($tmpfiles)) {
5139
									$listoffiles = array_merge($listoffiles, $tmpfiles);
5140
								}
5141
							}
5142
						}
5143
5144
						if (count($listoffiles)) {
5145
							foreach ($listoffiles as $record) {
5146
								$srctemplatepath = $record['fullname'];
5147
								break;
5148
							}
5149
						}
5150
					}
5151
5152
					if (empty($srctemplatepath)) {
5153
						$this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotDefined';
5154
						return -1;
5155
					}
5156
				}
5157
5158
				if ($obj->type == 'odt' && !empty($srctemplatepath)) {
5159
					if (!dol_is_file($srctemplatepath)) {
5160
						dol_syslog("Failed to locate template file ".$srctemplatepath, LOG_WARNING);
5161
						$this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotFound';
5162
						return -1;
5163
					}
5164
				}
5165
5166
				// We save charset_output to restore it because write_file can change it if needed for
5167
				// output format that does not support UTF8.
5168
				$sav_charset_output = $outputlangs->charset_output;
5169
5170
				if (in_array(get_class($this), array('Adherent'))) {
5171
					$arrayofrecords = array(); // The write_file of templates of adherent class need this var
5172
					$resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, 'member', 1, $moreparams);
5173
				} else {
5174
					 $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, $hidedetails, $hidedesc, $hideref, $moreparams);
5175
				}
5176
				// After call of write_file $obj->result['fullpath'] is set with generated file. It will be used to update the ECM database index.
5177
5178
				if ($resultwritefile > 0) {
5179
					$outputlangs->charset_output = $sav_charset_output;
5180
5181
					// We delete old preview
5182
					require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
5183
					dol_delete_preview($this);
5184
5185
					// Index file in database
5186
					if (!empty($obj->result['fullpath'])) {
5187
						$destfull = $obj->result['fullpath'];
5188
						$upload_dir = dirname($destfull);
5189
						$destfile = basename($destfull);
5190
						$rel_dir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $upload_dir);
5191
5192
						if (!preg_match('/[\\/]temp[\\/]|[\\/]thumbs|\.meta$/', $rel_dir)) {     // If not a tmp dir
5193
							$filename = basename($destfile);
5194
							$rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
5195
							$rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
5196
5197
							include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
5198
							$ecmfile = new EcmFiles($this->db);
5199
							$result = $ecmfile->fetch(0, '', ($rel_dir ? $rel_dir.'/' : '').$filename);
5200
5201
							 // Set the public "share" key
5202
							$setsharekey = false;
5203
							if ($this->element == 'propal') {
5204
								$useonlinesignature = $conf->global->MAIN_FEATURES_LEVEL; // Replace this with 1 when feature to make online signature is ok
5205
								if ($useonlinesignature) {
5206
									$setsharekey = true;
5207
								}
5208
								if (!empty($conf->global->PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD)) {
5209
									$setsharekey = true;
5210
								}
5211
							}
5212
							if ($this->element == 'commande' && !empty($conf->global->ORDER_ALLOW_EXTERNAL_DOWNLOAD)) {
5213
								$setsharekey = true;
5214
							}
5215
							if ($this->element == 'facture' && !empty($conf->global->INVOICE_ALLOW_EXTERNAL_DOWNLOAD)) {
5216
								$setsharekey = true;
5217
							}
5218
							if ($this->element == 'bank_account' && !empty($conf->global->BANK_ACCOUNT_ALLOW_EXTERNAL_DOWNLOAD)) {
5219
								$setsharekey = true;
5220
							}
5221
							if ($this->element == 'contrat' && !empty($conf->global->CONTRACT_ALLOW_EXTERNAL_DOWNLOAD)) {
5222
								$setsharekey = true;
5223
							}
5224
5225
							if ($setsharekey) {
5226
								if (empty($ecmfile->share)) {	// Because object not found or share not set yet
5227
									require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
5228
									$ecmfile->share = getRandomPassword(true);
5229
								}
5230
							}
5231
5232
							if ($result > 0) {
5233
								$ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
5234
								$ecmfile->fullpath_orig = '';
5235
								$ecmfile->gen_or_uploaded = 'generated';
5236
								$ecmfile->description = ''; // indexed content
5237
								$ecmfile->keyword = ''; // keyword content
5238
								$result = $ecmfile->update($user);
5239
								if ($result < 0) {
5240
									setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
5241
								}
5242
							} else {
5243
								$ecmfile->entity = $conf->entity;
5244
								$ecmfile->filepath = $rel_dir;
5245
								$ecmfile->filename = $filename;
5246
								$ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
5247
								$ecmfile->fullpath_orig = '';
5248
								$ecmfile->gen_or_uploaded = 'generated';
5249
								$ecmfile->description = ''; // indexed content
5250
								$ecmfile->keyword = ''; // keyword content
5251
								$ecmfile->src_object_type = $this->table_element;
5252
								$ecmfile->src_object_id   = $this->id;
5253
5254
								$result = $ecmfile->create($user);
5255
								if ($result < 0) {
5256
									setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
5257
								}
5258
							}
5259
5260
							/*$this->result['fullname']=$destfull;
5261
							$this->result['filepath']=$ecmfile->filepath;
5262
							$this->result['filename']=$ecmfile->filename;*/
5263
							//var_dump($obj->update_main_doc_field);exit;
5264
5265
							// Update the last_main_doc field into main object (if document generator has property ->update_main_doc_field set)
5266
							$update_main_doc_field = 0;
5267
							if (!empty($obj->update_main_doc_field)) {
5268
								$update_main_doc_field = 1;
5269
							}
5270
							if ($update_main_doc_field && !empty($this->table_element)) {
5271
								$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element." SET last_main_doc = '".$this->db->escape($ecmfile->filepath.'/'.$ecmfile->filename)."'";
5272
								$sql .= ' WHERE rowid = '.$this->id;
5273
5274
								$resql = $this->db->query($sql);
5275
								if (!$resql) {
5276
									dol_print_error($this->db);
5277
								} else {
5278
									$this->last_main_doc = $ecmfile->filepath.'/'.$ecmfile->filename;
5279
								}
5280
							}
5281
						}
5282
					} else {
5283
						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);
5284
					}
5285
5286
					// Success in building document. We build meta file.
5287
					dol_meta_create($this);
5288
5289
					return 1;
5290
				} else {
5291
					$outputlangs->charset_output = $sav_charset_output;
5292
					dol_print_error($this->db, "Error generating document for ".__CLASS__.". Error: ".$obj->error, $obj->errors);
5293
					return -1;
5294
				}
5295
			} else {
5296
				if (!$filefound) {
5297
					$this->error = $langs->trans("Error").' Failed to load doc generator with modelpaths='.$modelspath.' - modele='.$modele;
5298
					dol_print_error('', $this->error);
5299
				} else {
5300
					$this->error = $langs->trans("Error")." ".$langs->trans("ErrorFileDoesNotExists", $filefound);
5301
					dol_print_error('', $this->error);
5302
				}
5303
				return -1;
5304
			}
5305
		} else {
5306
			return $reshook;
5307
		}
5308
	}
5309
5310
	/**
5311
	 *  Build thumb
5312
	 *  @todo Move this into files.lib.php
5313
	 *
5314
	 *  @param      string	$file           Path file in UTF8 to original file to create thumbs from.
5315
	 *	@return		void
5316
	 */
5317
	public function addThumbs($file)
5318
	{
5319
		global $maxwidthsmall, $maxheightsmall, $maxwidthmini, $maxheightmini, $quality;
5320
5321
		require_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php'; // This define also $maxwidthsmall, $quality, ...
5322
5323
		$file_osencoded = dol_osencode($file);
5324
		if (file_exists($file_osencoded)) {
5325
			// Create small thumbs for company (Ratio is near 16/9)
5326
			// Used on logon for example
5327
			vignette($file_osencoded, $maxwidthsmall, $maxheightsmall, '_small', $quality);
5328
5329
			// Create mini thumbs for company (Ratio is near 16/9)
5330
			// Used on menu or for setup page for example
5331
			vignette($file_osencoded, $maxwidthmini, $maxheightmini, '_mini', $quality);
5332
		}
5333
	}
5334
5335
5336
	/* Functions common to commonobject and commonobjectline */
5337
5338
	/* For default values */
5339
5340
	/**
5341
	 * Return the default value to use for a field when showing the create form of object.
5342
	 * Return values in this order:
5343
	 * 1) If parameter is available into POST, we return it first.
5344
	 * 2) If not but an alternate value was provided as parameter of function, we return it.
5345
	 * 3) If not but a constant $conf->global->OBJECTELEMENT_FIELDNAME is set, we return it (It is better to use the dedicated table).
5346
	 * 4) Return value found into database (TODO No yet implemented)
5347
	 *
5348
	 * @param   string              $fieldname          Name of field
5349
	 * @param   string              $alternatevalue     Alternate value to use
5350
	 * @return  string|string[]                         Default value (can be an array if the GETPOST return an array)
5351
	 **/
5352
	public function getDefaultCreateValueFor($fieldname, $alternatevalue = null)
5353
	{
5354
		global $conf, $_POST;
5355
5356
		// If param here has been posted, we use this value first.
5357
		if (GETPOSTISSET($fieldname)) {
5358
			return GETPOST($fieldname, 'alphanohtml', 3);
5359
		}
5360
5361
		if (isset($alternatevalue)) {
5362
			return $alternatevalue;
5363
		}
5364
5365
		$newelement = $this->element;
5366
		if ($newelement == 'facture') {
5367
			$newelement = 'invoice';
5368
		}
5369
		if ($newelement == 'commande') {
5370
			$newelement = 'order';
5371
		}
5372
		if (empty($newelement)) {
5373
			dol_syslog("Ask a default value using common method getDefaultCreateValueForField on an object with no property ->element defined. Return empty string.", LOG_WARNING);
5374
			return '';
5375
		}
5376
5377
		$keyforfieldname = strtoupper($newelement.'_DEFAULT_'.$fieldname);
5378
		//var_dump($keyforfieldname);
5379
		if (isset($conf->global->$keyforfieldname)) {
5380
			return $conf->global->$keyforfieldname;
5381
		}
5382
5383
		// TODO Ad here a scan into table llx_overwrite_default with a filter on $this->element and $fieldname
5384
	}
5385
5386
5387
	/* For triggers */
5388
5389
5390
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5391
	/**
5392
	 * Call trigger based on this instance.
5393
	 * Some context information may also be provided into array property this->context.
5394
	 * NB:  Error from trigger are stacked in interface->errors
5395
	 * NB2: If return code of triggers are < 0, action calling trigger should cancel all transaction.
5396
	 *
5397
	 * @param   string    $triggerName   trigger's name to execute
5398
	 * @param   User      $user           Object user
5399
	 * @return  int                       Result of run_triggers
5400
	 */
5401
	public function call_trigger($triggerName, $user)
5402
	{
5403
		// phpcs:enable
5404
		global $langs, $conf;
5405
5406
		if (!is_object($langs)) {	// If lang was not defined, we set it. It is required by run_triggers.
5407
			include_once DOL_DOCUMENT_ROOT.'/core/class/translate.class.php';
5408
			$langs = new Translate('', $conf);
5409
		}
5410
5411
		include_once DOL_DOCUMENT_ROOT.'/core/class/interfaces.class.php';
5412
		$interface = new Interfaces($this->db);
5413
		$result = $interface->run_triggers($triggerName, $this, $user, $langs, $conf);
5414
5415
		if ($result < 0) {
5416
			if (!empty($this->errors)) {
5417
				$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.
5418
			} else {
5419
				$this->errors = $interface->errors;
5420
			}
5421
		}
5422
		return $result;
5423
	}
5424
5425
5426
	/* Functions for data in other language */
5427
5428
5429
	/**
5430
	 *  Function to get alternative languages of a data into $this->array_languages
5431
	 *  This method is NOT called by method fetch of objects but must be called separately.
5432
	 *
5433
	 *  @return	int						<0 if error, 0 if no values of alternative languages to find nor found, 1 if a value was found and loaded
5434
	 *  @see fetch_optionnals()
5435
	 */
5436
	public function fetchValuesForExtraLanguages()
5437
	{
5438
		// To avoid SQL errors. Probably not the better solution though
5439
		if (!$this->element) {
5440
			return 0;
5441
		}
5442
		if (!($this->id > 0)) {
5443
			return 0;
5444
		}
5445
		if (is_array($this->array_languages)) {
5446
			return 1;
5447
		}
5448
5449
		$this->array_languages = array();
5450
5451
		$element = $this->element;
5452
		if ($element == 'categorie') {
5453
			$element = 'categories'; // For compatibility
5454
		}
5455
5456
		// Request to get translation values for object
5457
		$sql = "SELECT rowid, property, lang , value";
5458
		$sql .= " FROM ".MAIN_DB_PREFIX."object_lang";
5459
		$sql .= " WHERE type_object = '".$this->db->escape($element)."'";
5460
		$sql .= " AND fk_object = ".$this->id;
5461
5462
		//dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG);		// Too verbose
5463
		$resql = $this->db->query($sql);
5464
		if ($resql) {
5465
			$numrows = $this->db->num_rows($resql);
5466
			if ($numrows) {
5467
				$i = 0;
5468
				while ($i < $numrows) {
5469
					$obj = $this->db->fetch_object($resql);
5470
					$key = $obj->property;
5471
					$value = $obj->value;
5472
					$codelang = $obj->lang;
5473
					$type = $this->fields[$key]['type'];
5474
5475
					// we can add this attribute to object
5476
					if (preg_match('/date/', $type)) {
5477
						$this->array_languages[$key][$codelang] = $this->db->jdate($value);
5478
					} else {
5479
						$this->array_languages[$key][$codelang] = $value;
5480
					}
5481
5482
					$i++;
5483
				}
5484
			}
5485
5486
			$this->db->free($resql);
5487
5488
			if ($numrows) {
5489
				return $numrows;
5490
			} else {
5491
				return 0;
5492
			}
5493
		} else {
5494
			dol_print_error($this->db);
5495
			return -1;
5496
		}
5497
	}
5498
5499
	/**
5500
	 * Fill array_options property of object by extrafields value (using for data sent by forms)
5501
	 *
5502
	 * @param	string	$onlykey		Only the following key is filled. When we make update of only one language field ($action = 'update_languages'), calling page must set this to avoid to have other languages being reset.
5503
	 * @return	int						1 if array_options set, 0 if no value, -1 if error (field required missing for example)
5504
	 */
5505
	public function setValuesForExtraLanguages($onlykey = '')
5506
	{
5507
		global $_POST, $langs;
5508
5509
		// Get extra fields
5510
		foreach ($_POST as $postfieldkey => $postfieldvalue) {
5511
			$tmparray = explode('-', $postfieldkey);
5512
			if ($tmparray[0] != 'field') {
5513
				continue;
5514
			}
5515
5516
			$element = $tmparray[1];
5517
			$key = $tmparray[2];
5518
			$codelang = $tmparray[3];
5519
			//var_dump("postfieldkey=".$postfieldkey." element=".$element." key=".$key." codelang=".$codelang);
5520
5521
			if (!empty($onlykey) && $key != $onlykey) {
5522
				continue;
5523
			}
5524
			if ($element != $this->element) {
5525
				continue;
5526
			}
5527
5528
			$key_type = $this->fields[$key]['type'];
5529
5530
			$enabled = 1;
5531
			if (isset($this->fields[$key]['enabled'])) {
5532
				$enabled = dol_eval($this->fields[$key]['enabled'], 1);
5533
			}
5534
			/*$perms = 1;
5535
			if (isset($this->fields[$key]['perms']))
5536
			{
5537
				$perms = dol_eval($this->fields[$key]['perms'], 1);
5538
			}*/
5539
			if (empty($enabled)) {
5540
				continue;
5541
			}
5542
			//if (empty($perms)) continue;
5543
5544
			if (in_array($key_type, array('date'))) {
5545
				// Clean parameters
5546
				// TODO GMT date in memory must be GMT so we should add gm=true in parameters
5547
				$value_key = dol_mktime(0, 0, 0, $_POST[$postfieldkey."month"], $_POST[$postfieldkey."day"], $_POST[$postfieldkey."year"]);
5548
			} elseif (in_array($key_type, array('datetime'))) {
5549
				// Clean parameters
5550
				// TODO GMT date in memory must be GMT so we should add gm=true in parameters
5551
				$value_key = dol_mktime($_POST[$postfieldkey."hour"], $_POST[$postfieldkey."min"], 0, $_POST[$postfieldkey."month"], $_POST[$postfieldkey."day"], $_POST[$postfieldkey."year"]);
5552
			} elseif (in_array($key_type, array('checkbox', 'chkbxlst'))) {
5553
				$value_arr = GETPOST($postfieldkey, 'array'); // check if an array
5554
				if (!empty($value_arr)) {
5555
					$value_key = implode(',', $value_arr);
5556
				} else {
5557
					$value_key = '';
5558
				}
5559
			} elseif (in_array($key_type, array('price', 'double'))) {
5560
				$value_arr = GETPOST($postfieldkey, 'alpha');
5561
				$value_key = price2num($value_arr);
5562
			} else {
5563
				$value_key = GETPOST($postfieldkey);
5564
				if (in_array($key_type, array('link')) && $value_key == '-1') {
5565
					$value_key = '';
5566
				}
5567
			}
5568
5569
			$this->array_languages[$key][$codelang] = $value_key;
5570
5571
			/*if ($nofillrequired) {
5572
				$langs->load('errors');
5573
				setEventMessages($langs->trans('ErrorFieldsRequired').' : '.implode(', ', $error_field_required), null, 'errors');
5574
				return -1;
5575
			}*/
5576
		}
5577
5578
		return 1;
5579
	}
5580
5581
5582
	/* Functions for extrafields */
5583
5584
	/**
5585
	 * Function to make a fetch but set environment to avoid to load computed values before.
5586
	 *
5587
	 * @param	int		$id			ID of object
5588
	 * @return	int					>0 if OK, 0 if not found, <0 if KO
5589
	 */
5590
	public function fetchNoCompute($id)
5591
	{
5592
		global $conf;
5593
5594
		$savDisableCompute = $conf->disable_compute;
5595
		$conf->disable_compute = 1;
5596
5597
		$ret = $this->fetch($id);
5598
5599
		$conf->disable_compute = $savDisableCompute;
5600
5601
		return $ret;
5602
	}
5603
5604
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5605
	/**
5606
	 *  Function to get extra fields of an object into $this->array_options
5607
	 *  This method is in most cases called by method fetch of objects but you can call it separately.
5608
	 *
5609
	 *  @param	int		$rowid			Id of line. Use the id of object if not defined. Deprecated. Function must be called without parameters.
5610
	 *  @param  array	$optionsArray   Array resulting of call of extrafields->fetch_name_optionals_label(). Deprecated. Function must be called without parameters.
5611
	 *  @return	int						<0 if error, 0 if no values of extrafield to find nor found, 1 if an attribute is found and value loaded
5612
	 *  @see fetchValuesForExtraLanguages()
5613
	 */
5614
	public function fetch_optionals($rowid = null, $optionsArray = null)
5615
	{
5616
		// phpcs:enable
5617
		global $conf, $extrafields;
5618
5619
		if (empty($rowid)) {
5620
			$rowid = $this->id;
5621
		}
5622
		if (empty($rowid) && isset($this->rowid)) {
5623
			$rowid = $this->rowid; // deprecated
5624
		}
5625
5626
		// To avoid SQL errors. Probably not the better solution though
5627
		if (!$this->table_element) {
5628
			return 0;
5629
		}
5630
5631
		$this->array_options = array();
5632
5633
		if (!is_array($optionsArray)) {
5634
			// If $extrafields is not a known object, we initialize it. Best practice is to have $extrafields defined into card.php or list.php page.
5635
			if (!isset($extrafields) || !is_object($extrafields)) {
5636
				require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5637
				$extrafields = new ExtraFields($this->db);
5638
			}
5639
5640
			// Load array of extrafields for elementype = $this->table_element
5641
			if (empty($extrafields->attributes[$this->table_element]['loaded'])) {
5642
				$extrafields->fetch_name_optionals_label($this->table_element);
5643
			}
5644
			$optionsArray = (!empty($extrafields->attributes[$this->table_element]['label']) ? $extrafields->attributes[$this->table_element]['label'] : null);
5645
		} else {
5646
			global $extrafields;
5647
			dol_syslog("Warning: fetch_optionals was called with param optionsArray defined when you should pass null now", LOG_WARNING);
5648
		}
5649
5650
		$table_element = $this->table_element;
5651
		if ($table_element == 'categorie') {
5652
			$table_element = 'categories'; // For compatibility
5653
		}
5654
5655
		// Request to get complementary values
5656
		if (is_array($optionsArray) && count($optionsArray) > 0) {
5657
			$sql = "SELECT rowid";
5658
			foreach ($optionsArray as $name => $label) {
5659
				if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || $extrafields->attributes[$this->table_element]['type'][$name] != 'separate') {
5660
					$sql .= ", ".$name;
5661
				}
5662
			}
5663
			$sql .= " FROM ".MAIN_DB_PREFIX.$table_element."_extrafields";
5664
			$sql .= " WHERE fk_object = ".((int) $rowid);
5665
5666
			//dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG);		// Too verbose
5667
			$resql = $this->db->query($sql);
5668
			if ($resql) {
5669
				$numrows = $this->db->num_rows($resql);
5670
				if ($numrows) {
5671
					$tab = $this->db->fetch_array($resql);
5672
5673
					foreach ($tab as $key => $value) {
5674
						// 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)
5675
						if ($key != 'rowid' && $key != 'tms' && $key != 'fk_member' && !is_int($key)) {
5676
							// we can add this attribute to object
5677
							if (!empty($extrafields) && in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date', 'datetime'))) {
5678
								//var_dump($extrafields->attributes[$this->table_element]['type'][$key]);
5679
								$this->array_options["options_".$key] = $this->db->jdate($value);
5680
							} else {
5681
								$this->array_options["options_".$key] = $value;
5682
							}
5683
5684
							//var_dump('key '.$key.' '.$value.' type='.$extrafields->attributes[$this->table_element]['type'][$key].' '.$this->array_options["options_".$key]);
5685
						}
5686
					}
5687
5688
					// If field is a computed field, value must become result of compute
5689
					foreach ($tab as $key => $value) {
5690
						if (!empty($extrafields) && !empty($extrafields->attributes[$this->table_element]['computed'][$key])) {
5691
							//var_dump($conf->disable_compute);
5692
							if (empty($conf->disable_compute)) {
5693
								$this->array_options["options_".$key] = dol_eval($extrafields->attributes[$this->table_element]['computed'][$key], 1, 0);
5694
							}
5695
						}
5696
					}
5697
				}
5698
5699
				$this->db->free($resql);
5700
5701
				if ($numrows) {
5702
					return $numrows;
5703
				} else {
5704
					return 0;
5705
				}
5706
			} else {
5707
				dol_print_error($this->db);
5708
				return -1;
5709
			}
5710
		}
5711
		return 0;
5712
	}
5713
5714
	/**
5715
	 *	Delete all extra fields values for the current object.
5716
	 *
5717
	 *  @return	int		<0 if KO, >0 if OK
5718
	 *  @see deleteExtraLanguages(), insertExtraField(), updateExtraField(), setValueFrom()
5719
	 */
5720
	public function deleteExtraFields()
5721
	{
5722
		global $conf;
5723
5724
		if (!empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) {
5725
			return 0;
5726
		}
5727
5728
		$this->db->begin();
5729
5730
		$table_element = $this->table_element;
5731
		if ($table_element == 'categorie') {
5732
			$table_element = 'categories'; // For compatibility
5733
		}
5734
5735
		$sql_del = "DELETE FROM ".MAIN_DB_PREFIX.$table_element."_extrafields WHERE fk_object = ".$this->id;
5736
		dol_syslog(get_class($this)."::deleteExtraFields delete", LOG_DEBUG);
5737
		$resql = $this->db->query($sql_del);
5738
		if (!$resql) {
5739
			$this->error = $this->db->lasterror();
5740
			$this->db->rollback();
5741
			return -1;
5742
		} else {
5743
			$this->db->commit();
5744
			return 1;
5745
		}
5746
	}
5747
5748
	/**
5749
	 *	Add/Update all extra fields values for the current object.
5750
	 *  Data to describe values to insert/update are stored into $this->array_options=array('options_codeforfield1'=>'valueforfield1', 'options_codeforfield2'=>'valueforfield2', ...)
5751
	 *  This function delete record with all extrafields and insert them again from the array $this->array_options.
5752
	 *
5753
	 *  @param	string		$trigger		If defined, call also the trigger (for example COMPANY_MODIFY)
5754
	 *  @param	User		$userused		Object user
5755
	 *  @return int 						-1=error, O=did nothing, 1=OK
5756
	 *  @see insertExtraLanguages(), updateExtraField(), deleteExtraField(), setValueFrom()
5757
	 */
5758
	public function insertExtraFields($trigger = '', $userused = null)
5759
	{
5760
		global $conf, $langs, $user;
5761
5762
		if (!empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) {
5763
			return 0;
5764
		}
5765
5766
		if (empty($userused)) {
5767
			$userused = $user;
5768
		}
5769
5770
		$error = 0;
5771
5772
		if (!empty($this->array_options)) {
5773
			// Check parameters
5774
			$langs->load('admin');
5775
			require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5776
			$extrafields = new ExtraFields($this->db);
5777
			$target_extrafields = $extrafields->fetch_name_optionals_label($this->table_element);
5778
5779
			// Eliminate copied source object extra fields that do not exist in target object
5780
			$new_array_options = array();
5781
			foreach ($this->array_options as $key => $value) {
5782
				if (in_array(substr($key, 8), array_keys($target_extrafields))) {	// We remove the 'options_' from $key for test
5783
					$new_array_options[$key] = $value;
5784
				} elseif (in_array($key, array_keys($target_extrafields))) {		// We test on $key that does not contains the 'options_' prefix
5785
					$new_array_options['options_'.$key] = $value;
5786
				}
5787
			}
5788
5789
			foreach ($new_array_options as $key => $value) {
5790
				$attributeKey      = substr($key, 8); // Remove 'options_' prefix
5791
				$attributeType     = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
5792
				$attributeLabel    = $extrafields->attributes[$this->table_element]['label'][$attributeKey];
5793
				$attributeParam    = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
5794
				$attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
5795
				$attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$attributeKey];
5796
5797
				// Similar code than into insertExtraFields
5798
				if ($attributeRequired) {
5799
					$mandatorypb = false;
5800
					if ($attributeType == 'link' && $this->array_options[$key] == '-1') {
5801
						$mandatorypb = true;
5802
					}
5803
					if ($this->array_options[$key] === '') {
5804
						$mandatorypb = true;
5805
					}
5806
					if ($attributeType == 'sellist' && $this->array_options[$key] == '0') {
5807
						$mandatorypb = true;
5808
					}
5809
					if ($mandatorypb) {
5810
						dol_syslog("Mandatory extra field ".$key." is empty");
5811
						$this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
5812
						return -1;
5813
					}
5814
				}
5815
5816
				//dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
5817
				//dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
5818
5819
				if (!empty($attrfieldcomputed)) {
5820
					if (!empty($conf->global->MAIN_STORE_COMPUTED_EXTRAFIELDS)) {
5821
						$value = dol_eval($attrfieldcomputed, 1, 0);
5822
						dol_syslog($langs->trans("Extrafieldcomputed")." sur ".$attributeLabel."(".$value.")", LOG_DEBUG);
5823
						$new_array_options[$key] = $value;
5824
					} else {
5825
						$new_array_options[$key] = null;
5826
					}
5827
				}
5828
5829
				switch ($attributeType) {
5830
					case 'int':
5831
						if (!is_numeric($value) && $value != '') {
5832
							$this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
5833
							return -1;
5834
						} elseif ($value == '') {
5835
							$new_array_options[$key] = null;
5836
						}
5837
						break;
5838
					case 'price':
5839
					case 'double':
5840
						$value = price2num($value);
5841
						if (!is_numeric($value) && $value != '') {
5842
							dol_syslog($langs->trans("ExtraFieldHasWrongValue")." for ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
5843
							$this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
5844
							return -1;
5845
						} elseif ($value == '') {
5846
							$new_array_options[$key] = null;
5847
						}
5848
						//dol_syslog("double value"." sur ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
5849
						$new_array_options[$key] = $value;
5850
						break;
5851
					/*case 'select':	// Not required, we chosed value='0' for undefined values
5852
						 if ($value=='-1')
5853
						 {
5854
							 $this->array_options[$key] = null;
5855
						 }
5856
						 break;*/
5857
					case 'password':
5858
						$algo = '';
5859
						if ($this->array_options[$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
5860
							// If there is an encryption choice, we use it to crypt data before insert
5861
							$tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
5862
							$algo = reset($tmparrays);
5863
							if ($algo != '') {
5864
								//global $action;		// $action may be 'create', 'update', 'update_extras'...
5865
								//var_dump($action);
5866
								//var_dump($this->oldcopy);exit;
5867
								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
5868
									//var_dump($this->oldcopy->array_options[$key]); var_dump($this->array_options[$key]);
5869
									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.
5870
										$new_array_options[$key] = $this->array_options[$key]; // Value is kept
5871
									} else {
5872
										// var_dump($algo);
5873
										$newvalue = dol_hash($this->array_options[$key], $algo);
5874
										$new_array_options[$key] = $newvalue;
5875
									}
5876
								} else {
5877
									$new_array_options[$key] = $this->array_options[$key]; // Value is kept
5878
								}
5879
							}
5880
						} else // Common usage
5881
						{
5882
							$new_array_options[$key] = $this->array_options[$key];
5883
						}
5884
						break;
5885
					case 'date':
5886
					case 'datetime':
5887
						// If data is a string instead of a timestamp, we convert it
5888
						if (!is_int($this->array_options[$key])) {
5889
							$this->array_options[$key] = strtotime($this->array_options[$key]);
5890
						}
5891
						$new_array_options[$key] = $this->db->idate($this->array_options[$key]);
5892
						break;
5893
					case 'link':
5894
						$param_list = array_keys($attributeParam['options']);
5895
						// 0 : ObjectName
5896
						// 1 : classPath
5897
						$InfoFieldList = explode(":", $param_list[0]);
5898
						dol_include_once($InfoFieldList[1]);
5899
						if ($InfoFieldList[0] && class_exists($InfoFieldList[0])) {
5900
							if ($value == '-1') {	// -1 is key for no defined in combo list of objects
5901
								$new_array_options[$key] = '';
5902
							} elseif ($value) {
5903
								$object = new $InfoFieldList[0]($this->db);
5904
								if (is_numeric($value)) {
5905
									$res = $object->fetch($value); // Common case
5906
								} else {
5907
									$res = $object->fetch('', $value); // For compatibility
5908
								}
5909
5910
								if ($res > 0) {
5911
									$new_array_options[$key] = $object->id;
5912
								} else {
5913
									$this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
5914
									$this->db->rollback();
5915
									return -1;
5916
								}
5917
							}
5918
						} else {
5919
							dol_syslog('Error bad setup of extrafield', LOG_WARNING);
5920
						}
5921
						break;
5922
				}
5923
			}
5924
5925
			$this->db->begin();
5926
5927
			$table_element = $this->table_element;
5928
			if ($table_element == 'categorie') {
5929
				$table_element = 'categories'; // For compatibility
5930
			}
5931
5932
			dol_syslog(get_class($this)."::insertExtraFields delete then insert", LOG_DEBUG);
5933
5934
			$sql_del = "DELETE FROM ".MAIN_DB_PREFIX.$table_element."_extrafields WHERE fk_object = ".$this->id;
5935
			$this->db->query($sql_del);
5936
5937
			$sql = "INSERT INTO ".MAIN_DB_PREFIX.$table_element."_extrafields (fk_object";
5938
			foreach ($new_array_options as $key => $value) {
5939
				$attributeKey = substr($key, 8); // Remove 'options_' prefix
5940
				// Add field of attribut
5941
				if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] != 'separate') { // Only for other type than separator
5942
					$sql .= ",".$attributeKey;
5943
				}
5944
			}
5945
			// We must insert a default value for fields for other entities that are mandatory to avoid not null error
5946
			if (!empty($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities']) && is_array($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'])) {
5947
				foreach ($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'] as $tmpkey => $tmpval) {
5948
					if (!isset($extrafields->attributes[$this->table_element]['type'][$tmpkey])) {    // If field not already added previously
5949
						$sql .= ",".$tmpkey;
5950
					}
5951
				}
5952
			}
5953
			$sql .= ") VALUES (".$this->id;
5954
5955
			foreach ($new_array_options as $key => $value) {
5956
				$attributeKey = substr($key, 8); // Remove 'options_' prefix
5957
				// Add field of attribute
5958
				if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] != 'separate') { // Only for other type than separator)
5959
					if ($new_array_options[$key] != '' || $new_array_options[$key] == '0') {
5960
						$sql .= ",'".$this->db->escape($new_array_options[$key])."'";
5961
					} else {
5962
						$sql .= ",null";
5963
					}
5964
				}
5965
			}
5966
			// We must insert a default value for fields for other entities that are mandatory to avoid not null error
5967
			if (!empty($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities']) && is_array($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'])) {
5968
				foreach ($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'] as $tmpkey => $tmpval) {
5969
					if (!isset($extrafields->attributes[$this->table_element]['type'][$tmpkey])) {   // If field not already added previously
5970
						if (in_array($tmpval, array('int', 'double', 'price'))) {
5971
							$sql .= ", 0";
5972
						} else {
5973
							$sql .= ", ''";
5974
						}
5975
					}
5976
				}
5977
			}
5978
5979
			$sql .= ")";
5980
5981
			$resql = $this->db->query($sql);
5982
			if (!$resql) {
5983
				$this->error = $this->db->lasterror();
5984
				$error++;
5985
			}
5986
5987
			if (!$error && $trigger) {
5988
				// Call trigger
5989
				$this->context = array('extrafieldaddupdate'=>1);
5990
				$result = $this->call_trigger($trigger, $userused);
5991
				if ($result < 0) {
5992
					$error++;
5993
				}
5994
				// End call trigger
5995
			}
5996
5997
			if ($error) {
5998
				$this->db->rollback();
5999
				return -1;
6000
			} else {
6001
				$this->db->commit();
6002
				return 1;
6003
			}
6004
		} else {
6005
			return 0;
6006
		}
6007
	}
6008
6009
	/**
6010
	 *	Add/Update all extra fields values for the current object.
6011
	 *  Data to describe values to insert/update are stored into $this->array_options=array('options_codeforfield1'=>'valueforfield1', 'options_codeforfield2'=>'valueforfield2', ...)
6012
	 *  This function delete record with all extrafields and insert them again from the array $this->array_options.
6013
	 *
6014
	 *  @param	string		$trigger		If defined, call also the trigger (for example COMPANY_MODIFY)
6015
	 *  @param	User		$userused		Object user
6016
	 *  @return int 						-1=error, O=did nothing, 1=OK
6017
	 *  @see insertExtraFields(), updateExtraField(), setValueFrom()
6018
	 */
6019
	public function insertExtraLanguages($trigger = '', $userused = null)
6020
	{
6021
		global $conf, $langs, $user;
6022
6023
		if (empty($userused)) {
6024
			$userused = $user;
6025
		}
6026
6027
		$error = 0;
6028
6029
		if (!empty($conf->global->MAIN_EXTRALANGUAGES_DISABLED)) {
6030
			return 0; // For avoid conflicts if trigger used
6031
		}
6032
6033
		if (is_array($this->array_languages)) {
6034
			$new_array_languages = $this->array_languages;
6035
6036
			foreach ($new_array_languages as $key => $value) {
6037
				$attributeKey      = $key;
6038
				$attributeType     = $this->fields[$attributeKey]['type'];
6039
				$attributeLabel    = $this->fields[$attributeKey]['label'];
6040
6041
				//dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
6042
				//dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
6043
6044
				switch ($attributeType) {
6045
					case 'int':
6046
						if (!is_numeric($value) && $value != '') {
6047
							$this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
6048
							return -1;
6049
						} elseif ($value == '') {
6050
							$new_array_languages[$key] = null;
6051
						}
6052
						break;
6053
					case 'double':
6054
						$value = price2num($value);
6055
						if (!is_numeric($value) && $value != '') {
6056
							dol_syslog($langs->trans("ExtraLanguageHasWrongValue")." sur ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
6057
							$this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
6058
							return -1;
6059
						} elseif ($value == '') {
6060
							$new_array_languages[$key] = null;
6061
						}
6062
						//dol_syslog("double value"." sur ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
6063
						$new_array_languages[$key] = $value;
6064
						break;
6065
						/*case 'select':	// Not required, we chosed value='0' for undefined values
6066
						 if ($value=='-1')
6067
						 {
6068
						 $this->array_options[$key] = null;
6069
						 }
6070
						 break;*/
6071
				}
6072
			}
6073
6074
			$this->db->begin();
6075
6076
			$table_element = $this->table_element;
6077
			if ($table_element == 'categorie') {
6078
				$table_element = 'categories'; // For compatibility
6079
			}
6080
6081
			dol_syslog(get_class($this)."::insertExtraLanguages delete then insert", LOG_DEBUG);
6082
6083
			foreach ($new_array_languages as $key => $langcodearray) {	// $key = 'name', 'town', ...
6084
				foreach ($langcodearray as $langcode => $value) {
6085
					$sql_del = "DELETE FROM ".MAIN_DB_PREFIX."object_lang";
6086
					$sql_del .= " WHERE fk_object = ".$this->id." AND property = '".$this->db->escape($key)."' AND type_object = '".$this->db->escape($table_element)."'";
6087
					$sql_del .= " AND lang = '".$this->db->escape($langcode)."'";
6088
					$this->db->query($sql_del);
6089
6090
					if ($value !== '') {
6091
						$sql = "INSERT INTO ".MAIN_DB_PREFIX."object_lang (fk_object, property, type_object, lang, value";
6092
						$sql .= ") VALUES (".$this->id.", '".$this->db->escape($key)."', '".$this->db->escape($table_element)."', '".$this->db->escape($langcode)."', '".$this->db->escape($value)."'";
6093
						$sql .= ")";
6094
6095
						$resql = $this->db->query($sql);
6096
						if (!$resql) {
6097
							$this->error = $this->db->lasterror();
6098
							$error++;
6099
							break;
6100
						}
6101
					}
6102
				}
6103
			}
6104
6105
			if (!$error && $trigger) {
6106
				// Call trigger
6107
				$this->context = array('extralanguagesaddupdate'=>1);
6108
				$result = $this->call_trigger($trigger, $userused);
6109
				if ($result < 0) {
6110
					$error++;
6111
				}
6112
				// End call trigger
6113
			}
6114
6115
			if ($error) {
6116
				$this->db->rollback();
6117
				return -1;
6118
			} else {
6119
				$this->db->commit();
6120
				return 1;
6121
			}
6122
		} else {
6123
			return 0;
6124
		}
6125
	}
6126
6127
	/**
6128
	 *	Update 1 extra field value for the current object. Keep other fields unchanged.
6129
	 *  Data to describe values to update are stored into $this->array_options=array('options_codeforfield1'=>'valueforfield1', 'options_codeforfield2'=>'valueforfield2', ...)
6130
	 *
6131
	 *  @param  string      $key    		Key of the extrafield to update (without starting 'options_')
6132
	 *  @param	string		$trigger		If defined, call also the trigger (for example COMPANY_MODIFY)
6133
	 *  @param	User		$userused		Object user
6134
	 *  @return int                 		-1=error, O=did nothing, 1=OK
6135
	 *  @see updateExtraLanguages(), insertExtraFields(), deleteExtraFields(), setValueFrom()
6136
	 */
6137
	public function updateExtraField($key, $trigger = null, $userused = null)
6138
	{
6139
		global $conf, $langs, $user;
6140
6141
		if (!empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) {
6142
			return 0;
6143
		}
6144
6145
		if (empty($userused)) {
6146
			$userused = $user;
6147
		}
6148
6149
		$error = 0;
6150
6151
		if (!empty($this->array_options) && isset($this->array_options["options_".$key])) {
6152
			// Check parameters
6153
			$langs->load('admin');
6154
			require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6155
			$extrafields = new ExtraFields($this->db);
6156
			$extrafields->fetch_name_optionals_label($this->table_element);
6157
6158
			$value = $this->array_options["options_".$key];
6159
6160
			$attributeType     = $extrafields->attributes[$this->table_element]['type'][$key];
6161
			$attributeLabel    = $extrafields->attributes[$this->table_element]['label'][$key];
6162
			$attributeParam    = $extrafields->attributes[$this->table_element]['param'][$key];
6163
			$attributeRequired = $extrafields->attributes[$this->table_element]['required'][$key];
6164
			$attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$key];
6165
6166
			// Similar code than into insertExtraFields
6167
			if ($attributeRequired) {
6168
				$mandatorypb = false;
6169
				if ($attributeType == 'link' && $this->array_options["options_".$key] == '-1') {
6170
					$mandatorypb = true;
6171
				}
6172
				if ($this->array_options["options_".$key] === '') {
6173
					$mandatorypb = true;
6174
				}
6175
				if ($mandatorypb) {
6176
					dol_syslog("Mandatory extra field options_".$key." is empty");
6177
					$this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
6178
					return -1;
6179
				}
6180
			}
6181
6182
			//dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
6183
			//dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
6184
6185
			if (!empty($attrfieldcomputed)) {
6186
				if (!empty($conf->global->MAIN_STORE_COMPUTED_EXTRAFIELDS)) {
6187
					$value = dol_eval($attrfieldcomputed, 1, 0);
6188
					dol_syslog($langs->trans("Extrafieldcomputed")." sur ".$attributeLabel."(".$value.")", LOG_DEBUG);
6189
					$this->array_options["options_".$key] = $value;
6190
				} else {
6191
					$this->array_options["options_".$key] = null;
6192
				}
6193
			}
6194
6195
			switch ($attributeType) {
6196
				case 'int':
6197
					if (!is_numeric($value) && $value != '') {
6198
						$this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
6199
						return -1;
6200
					} elseif ($value === '') {
6201
						$this->array_options["options_".$key] = null;
6202
					}
6203
					break;
6204
				case 'double':
6205
					$value = price2num($value);
6206
					if (!is_numeric($value) && $value != '') {
6207
						dol_syslog($langs->trans("ExtraFieldHasWrongValue")." sur ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
6208
						$this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
6209
						return -1;
6210
					} elseif ($value === '') {
6211
						$this->array_options["options_".$key] = null;
6212
					}
6213
					//dol_syslog("double value"." sur ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
6214
					$this->array_options["options_".$key] = $value;
6215
					break;
6216
				/*case 'select':	// Not required, we chosed value='0' for undefined values
6217
					 if ($value=='-1')
6218
					 {
6219
						 $this->array_options[$key] = null;
6220
					 }
6221
					 break;*/
6222
				case 'price':
6223
					$this->array_options["options_".$key] = price2num($this->array_options["options_".$key]);
6224
					break;
6225
				case 'date':
6226
					$this->array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key]);
6227
					break;
6228
				case 'datetime':
6229
					$this->array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key]);
6230
					break;
6231
				/*
6232
				case 'link':
6233
					$param_list = array_keys($attributeParam['options']);
6234
					// 0 : ObjectName
6235
					// 1 : classPath
6236
					$InfoFieldList = explode(":", $param_list[0]);
6237
					dol_include_once($InfoFieldList[1]);
6238
					if ($InfoFieldList[0] && class_exists($InfoFieldList[0]))
6239
					{
6240
						if ($value == '-1')	// -1 is key for no defined in combo list of objects
6241
						{
6242
							$new_array_options[$key] = '';
6243
						} elseif ($value) {
6244
							$object = new $InfoFieldList[0]($this->db);
6245
							if (is_numeric($value)) $res = $object->fetch($value);	// Common case
6246
							else $res = $object->fetch('', $value);					// For compatibility
6247
6248
							if ($res > 0) $new_array_options[$key] = $object->id;
6249
							else {
6250
								$this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
6251
								$this->db->rollback();
6252
								return -1;
6253
							}
6254
						}
6255
					} else {
6256
						dol_syslog('Error bad setup of extrafield', LOG_WARNING);
6257
					}
6258
					break;
6259
				*/
6260
			}
6261
6262
			$this->db->begin();
6263
6264
			$linealreadyfound = 0;
6265
6266
			// Check if there is already a line for this object (in most cases, it is, but sometimes it is not, for example when extra field has been created after), so we must keep this overload)
6267
			$sql = "SELECT COUNT(rowid) as nb FROM ".MAIN_DB_PREFIX.$this->table_element."_extrafields WHERE fk_object = ".$this->id;
6268
			$resql = $this->db->query($sql);
6269
			if ($resql) {
6270
				$tmpobj = $this->db->fetch_object($resql);
6271
				if ($tmpobj) {
6272
					$linealreadyfound = $tmpobj->nb;
6273
				}
6274
			}
6275
6276
			if ($linealreadyfound) {
6277
				$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element."_extrafields SET ".$key." = '".$this->db->escape($this->array_options["options_".$key])."'";
6278
				$sql .= " WHERE fk_object = ".$this->id;
6279
			} else {
6280
				$result = $this->insertExtraFields('', $user);
6281
				if ($result < 0) {
6282
					$error++;
6283
				}
6284
			}
6285
6286
			$resql = $this->db->query($sql);
6287
			if (!$resql) {
6288
				$error++;
6289
				$this->error = $this->db->lasterror();
6290
			}
6291
			if (!$error && $trigger) {
6292
				// Call trigger
6293
				$this->context = array('extrafieldupdate'=>1);
6294
				$result = $this->call_trigger($trigger, $userused);
6295
				if ($result < 0) {
6296
					$error++;
6297
				}
6298
				// End call trigger
6299
			}
6300
6301
			if ($error) {
6302
				dol_syslog(__METHOD__.$this->error, LOG_ERR);
6303
				$this->db->rollback();
6304
				return -1;
6305
			} else {
6306
				$this->db->commit();
6307
				return 1;
6308
			}
6309
		} else {
6310
			return 0;
6311
		}
6312
	}
6313
6314
	/**
6315
	 *	Update an extra language value for the current object.
6316
	 *  Data to describe values to update are stored into $this->array_options=array('options_codeforfield1'=>'valueforfield1', 'options_codeforfield2'=>'valueforfield2', ...)
6317
	 *
6318
	 *  @param  string      $key    		Key of the extrafield (without starting 'options_')
6319
	 *  @param	string		$trigger		If defined, call also the trigger (for example COMPANY_MODIFY)
6320
	 *  @param	User		$userused		Object user
6321
	 *  @return int                 		-1=error, O=did nothing, 1=OK
6322
	 *  @see updateExtraFields(), insertExtraLanguages()
6323
	 */
6324
	public function updateExtraLanguages($key, $trigger = null, $userused = null)
6325
	{
6326
		global $conf, $langs, $user;
6327
6328
		if (empty($userused)) {
6329
			$userused = $user;
6330
		}
6331
6332
		$error = 0;
6333
6334
		if (!empty($conf->global->MAIN_EXTRALANGUAGES_DISABLED)) {
6335
			return 0; // For avoid conflicts if trigger used
6336
		}
6337
6338
		return 0;
6339
	}
6340
6341
6342
	/**
6343
	 * Return HTML string to put an input field into a page
6344
	 * Code very similar with showInputField of extra fields
6345
	 *
6346
	 * @param  array   		$val	       Array of properties for field to show (used only if ->fields not defined)
6347
	 * @param  string  		$key           Key of attribute
6348
	 * @param  string|array	$value         Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array)
6349
	 * @param  string  		$moreparam     To add more parameters on html input tag
6350
	 * @param  string  		$keysuffix     Prefix string to add into name and id of field (can be used to avoid duplicate names)
6351
	 * @param  string  		$keyprefix     Suffix string to add into name and id of field (can be used to avoid duplicate names)
6352
	 * @param  string|int	$morecss       Value for css to define style/length of field. May also be a numeric.
6353
	 * @param  int			$nonewbutton   Force to not show the new button on field that are links to object
6354
	 * @return string
6355
	 */
6356
	public function showInputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = 0, $nonewbutton = 0)
6357
	{
6358
		global $conf, $langs, $form;
6359
6360
		if (!is_object($form)) {
6361
			require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
6362
			$form = new Form($this->db);
6363
		}
6364
6365
		if (!empty($this->fields)) {
6366
			$val = $this->fields[$key];
6367
		}
6368
6369
		$out = '';
6370
		$type = '';
6371
		$isDependList=0;
6372
		$param = array();
6373
		$param['options'] = array();
6374
		$reg = array();
6375
		$size = $this->fields[$key]['size'];
6376
		// Because we work on extrafields
6377
		if (preg_match('/^(integer|link):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
6378
			$param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
6379
			$type = 'link';
6380
		} elseif (preg_match('/^(integer|link):(.*):(.*):(.*)/i', $val['type'], $reg)) {
6381
			$param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
6382
			$type = 'link';
6383
		} elseif (preg_match('/^(integer|link):(.*):(.*)/i', $val['type'], $reg)) {
6384
			$param['options'] = array($reg[2].':'.$reg[3] => 'N');
6385
			$type = 'link';
6386
		} elseif (preg_match('/^(sellist):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
6387
			$param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
6388
			$type = 'sellist';
6389
		} elseif (preg_match('/^(sellist):(.*):(.*):(.*)/i', $val['type'], $reg)) {
6390
			$param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
6391
			$type = 'sellist';
6392
		} elseif (preg_match('/^(sellist):(.*):(.*)/i', $val['type'], $reg)) {
6393
			$param['options'] = array($reg[2].':'.$reg[3] => 'N');
6394
			$type = 'sellist';
6395
		} elseif (preg_match('/varchar\((\d+)\)/', $val['type'], $reg)) {
6396
			$param['options'] = array();
6397
			$type = 'varchar';
6398
			$size = $reg[1];
6399
		} elseif (preg_match('/varchar/', $val['type'])) {
6400
			$param['options'] = array();
6401
			$type = 'varchar';
6402
		} elseif (is_array($this->fields[$key]['arrayofkeyval'])) {
6403
			$param['options'] = $this->fields[$key]['arrayofkeyval'];
6404
			$type = 'select';
6405
		} else {
6406
			$param['options'] = array();
6407
			$type = $this->fields[$key]['type'];
6408
		}
6409
6410
6411
		$label = $this->fields[$key]['label'];
6412
		//$elementtype=$this->fields[$key]['elementtype'];	// Seems not used
6413
		$default = $this->fields[$key]['default'];
6414
		$computed = $this->fields[$key]['computed'];
6415
		$unique = $this->fields[$key]['unique'];
6416
		$required = $this->fields[$key]['required'];
6417
		$autofocusoncreate = $this->fields[$key]['autofocusoncreate'];
6418
6419
		$langfile = $this->fields[$key]['langfile'];
6420
		$list = $this->fields[$key]['list'];
6421
		$hidden = (in_array(abs($this->fields[$key]['visible']), array(0, 2)) ? 1 : 0);
6422
6423
		$objectid = $this->id;
6424
6425
		if ($computed) {
6426
			if (!preg_match('/^search_/', $keyprefix)) {
6427
				return '<span class="opacitymedium">'.$langs->trans("AutomaticallyCalculated").'</span>';
6428
			} else {
6429
				return '';
6430
			}
6431
		}
6432
6433
		// Set value of $morecss. For this, we use in priority showsize from parameters, then $val['css'] then autodefine
6434
		if (empty($morecss) && !empty($val['css'])) {
6435
			$morecss = $val['css'];
6436
		} elseif (empty($morecss)) {
6437
			if ($type == 'date') {
6438
				$morecss = 'minwidth100imp';
6439
			} elseif ($type == 'datetime' || $type == 'link') {	// link means an foreign key to another primary id
6440
				$morecss = 'minwidth200imp';
6441
			} elseif (in_array($type, array('int', 'integer', 'price')) || preg_match('/^double(\([0-9],[0-9]\)){0,1}/', $type)) {
6442
				$morecss = 'maxwidth75';
6443
			} elseif ($type == 'url') {
6444
				$morecss = 'minwidth400';
6445
			} elseif ($type == 'boolean') {
6446
				$morecss = '';
6447
			} else {
6448
				if (round($size) < 12) {
6449
					$morecss = 'minwidth100';
6450
				} elseif (round($size) <= 48) {
6451
					$morecss = 'minwidth200';
6452
				} else {
6453
					$morecss = 'minwidth400';
6454
				}
6455
			}
6456
		}
6457
6458
		if (in_array($type, array('date', 'datetime'))) {
6459
			$tmp = explode(',', $size);
6460
			$newsize = $tmp[0];
6461
6462
			$showtime = in_array($type, array('datetime')) ? 1 : 0;
6463
6464
			// Do not show current date when field not required (see selectDate() method)
6465
			if (!$required && $value == '') {
6466
				$value = '-1';
6467
			}
6468
6469
			// TODO Must also support $moreparam
6470
			$out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1);
6471
		} elseif (in_array($type, array('duration'))) {
6472
			$out = $form->select_duration($keyprefix.$key.$keysuffix, $value, 0, 'text', 0, 1);
6473
		} elseif (in_array($type, array('int', 'integer'))) {
6474
			$tmp = explode(',', $size);
6475
			$newsize = $tmp[0];
6476
			$out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" maxlength="'.$newsize.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
6477
		} elseif (in_array($type, array('real'))) {
6478
			$out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
6479
		} elseif (preg_match('/varchar/', $type)) {
6480
			$out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" maxlength="'.$size.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
6481
		} elseif (in_array($type, array('mail', 'phone', 'url'))) {
6482
			$out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
6483
		} elseif (preg_match('/^text/', $type)) {
6484
			if (!preg_match('/search_/', $keyprefix)) {		// If keyprefix is search_ or search_options_, we must just use a simple text field
6485
				require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
6486
				$doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, false, ROWS_5, '90%');
6487
				$out = $doleditor->Create(1);
6488
			} else {
6489
				$out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
6490
			}
6491
		} elseif (preg_match('/^html/', $type)) {
6492
			if (!preg_match('/search_/', $keyprefix)) {		// If keyprefix is search_ or search_options_, we must just use a simple text field
6493
				require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
6494
				$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%');
6495
				$out = $doleditor->Create(1);
6496
			} else {
6497
				$out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
6498
			}
6499
		} elseif ($type == 'boolean') {
6500
			$checked = '';
6501
			if (!empty($value)) {
6502
				$checked = ' checked value="1" ';
6503
			} else {
6504
				$checked = ' value="1" ';
6505
			}
6506
			$out = '<input type="checkbox" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.$checked.' '.($moreparam ? $moreparam : '').'>';
6507
		} elseif ($type == 'price') {
6508
			if (!empty($value)) {		// $value in memory is a php numeric, we format it into user number format.
6509
				$value = price($value);
6510
			}
6511
			$out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'> '.$langs->getCurrencySymbol($conf->currency);
6512
		} elseif (preg_match('/^double(\([0-9],[0-9]\)){0,1}/', $type)) {
6513
			if (!empty($value)) {		// $value in memory is a php numeric, we format it into user number format.
6514
				$value = price($value);
6515
			}
6516
			$out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'> ';
6517
		} elseif ($type == 'select') {
6518
			$out = '';
6519
			if (!empty($conf->use_javascript_ajax) && !empty($conf->global->MAIN_EXTRAFIELDS_USE_SELECT2)) {
6520
				include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
6521
				$out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
6522
			}
6523
6524
			$out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
6525
			if ((!isset($this->fields[$key]['default'])) || ($this->fields[$key]['notnull'] != 1)) {
6526
				$out .= '<option value="0">&nbsp;</option>';
6527
			}
6528
			foreach ($param['options'] as $key => $val) {
0 ignored issues
show
introduced by
$val is overwriting one of the parameters of this function.
Loading history...
introduced by
$key is overwriting one of the parameters of this function.
Loading history...
6529
				if ((string) $key == '') {
6530
					continue;
6531
				}
6532
				list($val, $parent) = explode('|', $val);
6533
				$out .= '<option value="'.$key.'"';
6534
				$out .= (((string) $value == (string) $key) ? ' selected' : '');
6535
				$out .= (!empty($parent) ? ' parent="'.$parent.'"' : '');
6536
				$out .= '>'.$val.'</option>';
6537
			}
6538
			$out .= '</select>';
6539
		} elseif ($type == 'sellist') {
6540
			$out = '';
6541
			if (!empty($conf->use_javascript_ajax) && !empty($conf->global->MAIN_EXTRAFIELDS_USE_SELECT2)) {
6542
				include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
6543
				$out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
6544
			}
6545
6546
			$out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
6547
			if (is_array($param['options'])) {
6548
				$param_list = array_keys($param['options']);
6549
				$InfoFieldList = explode(":", $param_list[0]);
6550
				$parentName = '';
6551
				$parentField = '';
6552
				// 0 : tableName
6553
				// 1 : label field name
6554
				// 2 : key fields name (if differ of rowid)
6555
				// 3 : key field parent (for dependent lists)
6556
				// 4 : where clause filter on column or table extrafield, syntax field='value' or extra.field=value
6557
				$keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
6558
6559
				if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
6560
					if (strpos($InfoFieldList[4], 'extra.') !== false) {
6561
						$keyList = 'main.'.$InfoFieldList[2].' as rowid';
6562
					} else {
6563
						$keyList = $InfoFieldList[2].' as rowid';
6564
					}
6565
				}
6566
				if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
6567
					list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
6568
					$keyList .= ', '.$parentField;
6569
				}
6570
6571
				$fields_label = explode('|', $InfoFieldList[1]);
6572
				if (is_array($fields_label)) {
6573
					$keyList .= ', ';
6574
					$keyList .= implode(', ', $fields_label);
6575
				}
6576
6577
				$sqlwhere = '';
6578
				$sql = 'SELECT '.$keyList;
6579
				$sql .= ' FROM '.MAIN_DB_PREFIX.$InfoFieldList[0];
6580
				if (!empty($InfoFieldList[4])) {
6581
					// can use SELECT request
6582
					if (strpos($InfoFieldList[4], '$SEL$') !== false) {
6583
						$InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
6584
					}
6585
6586
					// current object id can be use into filter
6587
					if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
6588
						$InfoFieldList[4] = str_replace('$ID$', $objectid, $InfoFieldList[4]);
6589
					} else {
6590
						$InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
6591
					}
6592
					//We have to join on extrafield table
6593
					if (strpos($InfoFieldList[4], 'extra') !== false) {
6594
						$sql .= ' as main, '.MAIN_DB_PREFIX.$InfoFieldList[0].'_extrafields as extra';
6595
						$sqlwhere .= ' WHERE extra.fk_object=main.'.$InfoFieldList[2].' AND '.$InfoFieldList[4];
6596
					} else {
6597
						$sqlwhere .= ' WHERE '.$InfoFieldList[4];
6598
					}
6599
				} else {
6600
					$sqlwhere .= ' WHERE 1=1';
6601
				}
6602
				// Some tables may have field, some other not. For the moment we disable it.
6603
				if (in_array($InfoFieldList[0], array('tablewithentity'))) {
6604
					$sqlwhere .= ' AND entity = '.$conf->entity;
6605
				}
6606
				$sql .= $sqlwhere;
6607
				//print $sql;
6608
6609
				$sql .= ' ORDER BY '.implode(', ', $fields_label);
6610
6611
				dol_syslog(get_class($this).'::showInputField type=sellist', LOG_DEBUG);
6612
				$resql = $this->db->query($sql);
6613
				if ($resql) {
6614
					$out .= '<option value="0">&nbsp;</option>';
6615
					$num = $this->db->num_rows($resql);
6616
					$i = 0;
6617
					while ($i < $num) {
6618
						$labeltoshow = '';
6619
						$obj = $this->db->fetch_object($resql);
6620
6621
						// Several field into label (eq table:code|libelle:rowid)
6622
						$notrans = false;
6623
						$fields_label = explode('|', $InfoFieldList[1]);
6624
						if (count($fields_label) > 1) {
6625
							$notrans = true;
6626
							foreach ($fields_label as $field_toshow) {
6627
								$labeltoshow .= $obj->$field_toshow.' ';
6628
							}
6629
						} else {
6630
							$labeltoshow = $obj->{$InfoFieldList[1]};
6631
						}
6632
						$labeltoshow = dol_trunc($labeltoshow, 45);
6633
6634
						if ($value == $obj->rowid) {
6635
							foreach ($fields_label as $field_toshow) {
6636
								$translabel = $langs->trans($obj->$field_toshow);
6637
								if ($translabel != $obj->$field_toshow) {
6638
									$labeltoshow = dol_trunc($translabel, 18).' ';
6639
								} else {
6640
									$labeltoshow = dol_trunc($obj->$field_toshow, 18).' ';
6641
								}
6642
							}
6643
							$out .= '<option value="'.$obj->rowid.'" selected>'.$labeltoshow.'</option>';
6644
						} else {
6645
							if (!$notrans) {
6646
								$translabel = $langs->trans($obj->{$InfoFieldList[1]});
6647
								if ($translabel != $obj->{$InfoFieldList[1]}) {
6648
									$labeltoshow = dol_trunc($translabel, 18);
6649
								} else {
6650
									$labeltoshow = dol_trunc($obj->{$InfoFieldList[1]}, 18);
6651
								}
6652
							}
6653
							if (empty($labeltoshow)) {
6654
								$labeltoshow = '(not defined)';
6655
							}
6656
							if ($value == $obj->rowid) {
6657
								$out .= '<option value="'.$obj->rowid.'" selected>'.$labeltoshow.'</option>';
6658
							}
6659
6660
							if (!empty($InfoFieldList[3]) && $parentField) {
6661
								$parent = $parentName.':'.$obj->{$parentField};
6662
								$isDependList=1;
6663
							}
6664
6665
							$out .= '<option value="'.$obj->rowid.'"';
6666
							$out .= ($value == $obj->rowid ? ' selected' : '');
6667
							$out .= (!empty($parent) ? ' parent="'.$parent.'"' : '');
6668
							$out .= '>'.$labeltoshow.'</option>';
6669
						}
6670
6671
						$i++;
6672
					}
6673
					$this->db->free($resql);
6674
				} else {
6675
					print 'Error in request '.$sql.' '.$this->db->lasterror().'. Check setup of extra parameters.<br>';
6676
				}
6677
			}
6678
			$out .= '</select>';
6679
		} elseif ($type == 'checkbox') {
6680
			$value_arr = explode(',', $value);
6681
			$out = $form->multiselectarray($keyprefix.$key.$keysuffix, (empty($param['options']) ?null:$param['options']), $value_arr, '', 0, '', 0, '100%');
6682
		} elseif ($type == 'radio') {
6683
			$out = '';
6684
			foreach ($param['options'] as $keyopt => $val) {
0 ignored issues
show
introduced by
$val is overwriting one of the parameters of this function.
Loading history...
6685
				$out .= '<input class="flat '.$morecss.'" type="radio" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '');
6686
				$out .= ' value="'.$keyopt.'"';
6687
				$out .= ' id="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'"';
6688
				$out .= ($value == $keyopt ? 'checked' : '');
6689
				$out .= '/><label for="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'">'.$val.'</label><br>';
6690
			}
6691
		} elseif ($type == 'chkbxlst') {
6692
			if (is_array($value)) {
6693
				$value_arr = $value;
6694
			} else {
6695
				$value_arr = explode(',', $value);
6696
			}
6697
6698
			if (is_array($param['options'])) {
6699
				$param_list = array_keys($param['options']);
6700
				$InfoFieldList = explode(":", $param_list[0]);
6701
				$parentName = '';
6702
				$parentField = '';
6703
				// 0 : tableName
6704
				// 1 : label field name
6705
				// 2 : key fields name (if differ of rowid)
6706
				// 3 : key field parent (for dependent lists)
6707
				// 4 : where clause filter on column or table extrafield, syntax field='value' or extra.field=value
6708
				$keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
6709
6710
				if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
6711
					list ($parentName, $parentField) = explode('|', $InfoFieldList[3]);
6712
					$keyList .= ', '.$parentField;
6713
				}
6714
				if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
6715
					if (strpos($InfoFieldList[4], 'extra.') !== false) {
6716
						$keyList = 'main.'.$InfoFieldList[2].' as rowid';
6717
					} else {
6718
						$keyList = $InfoFieldList[2].' as rowid';
6719
					}
6720
				}
6721
6722
				$fields_label = explode('|', $InfoFieldList[1]);
6723
				if (is_array($fields_label)) {
6724
					$keyList .= ', ';
6725
					$keyList .= implode(', ', $fields_label);
6726
				}
6727
6728
				$sqlwhere = '';
6729
				$sql = 'SELECT '.$keyList;
6730
				$sql .= ' FROM '.MAIN_DB_PREFIX.$InfoFieldList[0];
6731
				if (!empty($InfoFieldList[4])) {
6732
					// can use SELECT request
6733
					if (strpos($InfoFieldList[4], '$SEL$') !== false) {
6734
						$InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
6735
					}
6736
6737
					// current object id can be use into filter
6738
					if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
6739
						$InfoFieldList[4] = str_replace('$ID$', $objectid, $InfoFieldList[4]);
6740
					} else {
6741
						$InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
6742
					}
6743
6744
					// We have to join on extrafield table
6745
					if (strpos($InfoFieldList[4], 'extra') !== false) {
6746
						$sql .= ' as main, '.MAIN_DB_PREFIX.$InfoFieldList[0].'_extrafields as extra';
6747
						$sqlwhere .= ' WHERE extra.fk_object=main.'.$InfoFieldList[2].' AND '.$InfoFieldList[4];
6748
					} else {
6749
						$sqlwhere .= ' WHERE '.$InfoFieldList[4];
6750
					}
6751
				} else {
6752
					$sqlwhere .= ' WHERE 1=1';
6753
				}
6754
				// Some tables may have field, some other not. For the moment we disable it.
6755
				if (in_array($InfoFieldList[0], array('tablewithentity'))) {
6756
					$sqlwhere .= ' AND entity = '.$conf->entity;
6757
				}
6758
				// $sql.=preg_replace('/^ AND /','',$sqlwhere);
6759
				// print $sql;
6760
6761
				$sql .= $sqlwhere;
6762
				dol_syslog(get_class($this).'::showInputField type=chkbxlst', LOG_DEBUG);
6763
				$resql = $this->db->query($sql);
6764
				if ($resql) {
6765
					$num = $this->db->num_rows($resql);
6766
					$i = 0;
6767
6768
					$data = array();
6769
6770
					while ($i < $num) {
6771
						$labeltoshow = '';
6772
						$obj = $this->db->fetch_object($resql);
6773
6774
						$notrans = false;
6775
						// Several field into label (eq table:code|libelle:rowid)
6776
						$fields_label = explode('|', $InfoFieldList[1]);
6777
						if (count($fields_label) > 1) {
6778
							$notrans = true;
6779
							foreach ($fields_label as $field_toshow) {
6780
								$labeltoshow .= $obj->$field_toshow.' ';
6781
							}
6782
						} else {
6783
							$labeltoshow = $obj->{$InfoFieldList[1]};
6784
						}
6785
						$labeltoshow = dol_trunc($labeltoshow, 45);
6786
6787
						if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
6788
							foreach ($fields_label as $field_toshow) {
6789
								$translabel = $langs->trans($obj->$field_toshow);
6790
								if ($translabel != $obj->$field_toshow) {
6791
									$labeltoshow = dol_trunc($translabel, 18).' ';
6792
								} else {
6793
									$labeltoshow = dol_trunc($obj->$field_toshow, 18).' ';
6794
								}
6795
							}
6796
6797
							$data[$obj->rowid] = $labeltoshow;
6798
						} else {
6799
							if (!$notrans) {
6800
								$translabel = $langs->trans($obj->{$InfoFieldList[1]});
6801
								if ($translabel != $obj->{$InfoFieldList[1]}) {
6802
									$labeltoshow = dol_trunc($translabel, 18);
6803
								} else {
6804
									$labeltoshow = dol_trunc($obj->{$InfoFieldList[1]}, 18);
6805
								}
6806
							}
6807
							if (empty($labeltoshow)) {
6808
								$labeltoshow = '(not defined)';
6809
							}
6810
6811
							if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
6812
								$data[$obj->rowid] = $labeltoshow;
6813
							}
6814
6815
							if (!empty($InfoFieldList[3]) && $parentField) {
6816
								$parent = $parentName.':'.$obj->{$parentField};
6817
								$isDependList=1;
6818
							}
6819
6820
							$data[$obj->rowid] = $labeltoshow;
6821
						}
6822
6823
						$i++;
6824
					}
6825
					$this->db->free($resql);
6826
6827
					$out = $form->multiselectarray($keyprefix.$key.$keysuffix, $data, $value_arr, '', 0, '', 0, '100%');
6828
				} else {
6829
					print 'Error in request '.$sql.' '.$this->db->lasterror().'. Check setup of extra parameters.<br>';
6830
				}
6831
			}
6832
		} elseif ($type == 'link') {
6833
			$param_list = array_keys($param['options']); // $param_list='ObjectName:classPath[:AddCreateButtonOrNot[:Filter]]'
6834
			$param_list_array = explode(':', $param_list[0]);
6835
			$showempty = (($required && $default != '') ? 0 : 1);
6836
6837
			if (!preg_match('/search_/', $keyprefix)) {
6838
				if (!empty($param_list_array[2])) {		// If the entry into $fields is set to add a create button
6839
					if (!empty($this->fields[$key]['picto'])) {
6840
						$morecss .= ' widthcentpercentminusxx';
6841
					} else {
6842
						$morecss .= ' widthcentpercentminusx';
6843
					}
6844
				} else {
6845
					if (!empty($this->fields[$key]['picto'])) {
6846
						$morecss .= ' widthcentpercentminusx';
6847
					}
6848
				}
6849
			}
6850
6851
			$out = $form->selectForForms($param_list[0], $keyprefix.$key.$keysuffix, $value, $showempty, '', '', $morecss, $moreparam, 0, empty($val['disabled']) ? 0 : 1);
6852
6853
			if (!empty($param_list_array[2])) {		// If the entry into $fields is set to add a create button
6854
				if (!GETPOSTISSET('backtopage') && empty($val['disabled']) && empty($nonewbutton)) {	// To avoid to open several times the 'Create Object' button and to avoid to have button if field is protected by a "disabled".
6855
					list($class, $classfile) = explode(':', $param_list[0]);
6856
					if (file_exists(dol_buildpath(dirname(dirname($classfile)).'/card.php'))) {
6857
						$url_path = dol_buildpath(dirname(dirname($classfile)).'/card.php', 1);
6858
					} else {
6859
						$url_path = dol_buildpath(dirname(dirname($classfile)).'/'.strtolower($class).'_card.php', 1);
6860
					}
6861
					$paramforthenewlink = '';
6862
					$paramforthenewlink .= (GETPOSTISSET('action') ? '&action='.GETPOST('action', 'aZ09') : '');
6863
					$paramforthenewlink .= (GETPOSTISSET('id') ? '&id='.GETPOST('id', 'int') : '');
6864
					$paramforthenewlink .= '&fk_'.strtolower($class).'=--IDFORBACKTOPAGE--';
6865
					// TODO Add Javascript code to add input fields already filled into $paramforthenewlink so we won't loose them when going back to main page
6866
					$out .= '<a class="butActionNew" title="'.$langs->trans("New").'" href="'.$url_path.'?action=create&backtopage='.urlencode($_SERVER['PHP_SELF'].($paramforthenewlink ? '?'.$paramforthenewlink : '')).'"><span class="fa fa-plus-circle valignmiddle"></span></a>';
6867
				}
6868
			}
6869
		} elseif ($type == 'password') {
6870
			// If prefix is 'search_', field is used as a filter, we use a common text field.
6871
			$out = '<input type="'.($keyprefix == 'search_' ? 'text' : 'password').'" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'>';
6872
		} elseif ($type == 'array') {
6873
			$newval = $val;
6874
			$newval['type'] = 'varchar(256)';
6875
6876
			$out = '';
6877
			if (!empty($value)) {
6878
				foreach ($value as $option) {
6879
					$out .= '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
6880
					$out .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', $option, $moreparam, '', '', $morecss).'<br></span>';
6881
				}
6882
			}
6883
			$out .= '<a id="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_add" href="javascript:;"><span class="fa fa-plus-circle valignmiddle"></span></a>';
6884
6885
			$newInput = '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
6886
			$newInput .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', '', $moreparam, '', '', $morecss).'<br></span>';
6887
6888
			if (!empty($conf->use_javascript_ajax)) {
6889
				$out .= '
6890
					<script>
6891
					$(document).ready(function() {
6892
						$("a#'.dol_escape_js($keyprefix.$key.$keysuffix).'_add").click(function() {
6893
							$("'.dol_escape_js($newInput).'").insertBefore(this);
6894
						});
6895
6896
						$(document).on("click", "a.'.dol_escape_js($keyprefix.$key.$keysuffix).'_del", function() {
6897
							$(this).parent().remove();
6898
						});
6899
					});
6900
					</script>';
6901
			}
6902
		}
6903
		if (!empty($hidden)) {
6904
			$out = '<input type="hidden" value="'.$value.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'"/>';
6905
		}
6906
6907
		if ($isDependList==1) {
0 ignored issues
show
introduced by
The condition $isDependList == 1 is always false.
Loading history...
6908
			$out .= $this->getJSListDependancies('_common');
6909
		}
6910
		/* Add comments
6911
		 if ($type == 'date') $out.=' (YYYY-MM-DD)';
6912
		 elseif ($type == 'datetime') $out.=' (YYYY-MM-DD HH:MM:SS)';
6913
		 */
6914
		return $out;
6915
	}
6916
6917
	/**
6918
	 * Return HTML string to show a field into a page
6919
	 * Code very similar with showOutputField of extra fields
6920
	 *
6921
	 * @param  array   $val		       Array of properties of field to show
6922
	 * @param  string  $key            Key of attribute
6923
	 * @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)
6924
	 * @param  string  $moreparam      To add more parametes on html input tag
6925
	 * @param  string  $keysuffix      Prefix string to add into name and id of field (can be used to avoid duplicate names)
6926
	 * @param  string  $keyprefix      Suffix string to add into name and id of field (can be used to avoid duplicate names)
6927
	 * @param  mixed   $morecss        Value for css to define size. May also be a numeric.
6928
	 * @return string
6929
	 */
6930
	public function showOutputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = '')
6931
	{
6932
		global $conf, $langs, $form;
6933
6934
		if (!is_object($form)) {
6935
			require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
6936
			$form = new Form($this->db);
6937
		}
6938
6939
		$objectid = $this->id;
6940
		$label = $val['label'];
6941
		$type  = $val['type'];
6942
		$size  = $val['css'];
6943
		$reg = array();
6944
6945
		// Convert var to be able to share same code than showOutputField of extrafields
6946
		if (preg_match('/varchar\((\d+)\)/', $type, $reg)) {
6947
			$type = 'varchar'; // convert varchar(xx) int varchar
6948
			$size = $reg[1];
6949
		} elseif (preg_match('/varchar/', $type)) {
6950
			$type = 'varchar'; // convert varchar(xx) int varchar
6951
		}
6952
		if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
6953
			$type = 'select';
6954
		}
6955
		if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
6956
			$type = 'link';
6957
		}
6958
6959
		$default = $val['default'];
6960
		$computed = $val['computed'];
6961
		$unique = $val['unique'];
6962
		$required = $val['required'];
6963
		$param = array();
6964
		$param['options'] = array();
6965
6966
		if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
6967
			$param['options'] = $val['arrayofkeyval'];
6968
		}
6969
		if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
6970
			$type = 'link';
6971
			$param['options'] = array($reg[1].':'.$reg[2]=>$reg[1].':'.$reg[2]);
6972
		} elseif (preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
6973
			$param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
6974
			$type = 'sellist';
6975
		} elseif (preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
6976
			$param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
6977
			$type = 'sellist';
6978
		} elseif (preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
6979
			$param['options'] = array($reg[1].':'.$reg[2] => 'N');
6980
			$type = 'sellist';
6981
		}
6982
6983
		$langfile = $val['langfile'];
6984
		$list = $val['list'];
6985
		$help = $val['help'];
6986
		$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)
6987
6988
		if ($hidden) {
6989
			return '';
6990
		}
6991
6992
		// If field is a computed field, value must become result of compute
6993
		if ($computed) {
6994
			// Make the eval of compute string
6995
			//var_dump($computed);
6996
			$value = dol_eval($computed, 1, 0);
6997
		}
6998
6999
		if (empty($morecss)) {
7000
			if ($type == 'date') {
7001
				$morecss = 'minwidth100imp';
7002
			} elseif ($type == 'datetime' || $type == 'timestamp') {
7003
				$morecss = 'minwidth200imp';
7004
			} elseif (in_array($type, array('int', 'double', 'price'))) {
7005
				$morecss = 'maxwidth75';
7006
			} elseif ($type == 'url') {
7007
				$morecss = 'minwidth400';
7008
			} elseif ($type == 'boolean') {
7009
				$morecss = '';
7010
			} else {
7011
				if (round($size) < 12) {
7012
					$morecss = 'minwidth100';
7013
				} elseif (round($size) <= 48) {
7014
					$morecss = 'minwidth200';
7015
				} else {
7016
					$morecss = 'minwidth400';
7017
				}
7018
			}
7019
		}
7020
7021
		// Format output value differently according to properties of field
7022
		if ($key == 'ref' && method_exists($this, 'getNomUrl')) {
7023
			$value = $this->getNomUrl(1, '', 0, '', 1);
7024
		} elseif ($key == 'status' && method_exists($this, 'getLibStatut')) {
7025
			$value = $this->getLibStatut(3);
7026
		} elseif ($type == 'date') {
7027
			if (!empty($value)) {
7028
				$value = dol_print_date($value, 'day');
7029
			} else {
7030
				$value = '';
7031
			}
7032
		} elseif ($type == 'datetime' || $type == 'timestamp') {
7033
			if (!empty($value)) {
7034
				$value = dol_print_date($value, 'dayhour');
7035
			} else {
7036
				$value = '';
7037
			}
7038
		} elseif ($type == 'duration') {
7039
			include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
7040
			if (!is_null($value) && $value !== '') {
7041
				$value = convertSecondToTime($value, 'allhourmin');
7042
			}
7043
		} elseif ($type == 'double' || $type == 'real') {
7044
			if (!is_null($value) && $value !== '') {
7045
				$value = price($value);
7046
			}
7047
		} elseif ($type == 'boolean') {
7048
			$checked = '';
7049
			if (!empty($value)) {
7050
				$checked = ' checked ';
7051
			}
7052
			$value = '<input type="checkbox" '.$checked.' '.($moreparam ? $moreparam : '').' readonly disabled>';
7053
		} elseif ($type == 'mail') {
7054
			$value = dol_print_email($value, 0, 0, 0, 64, 1, 1);
7055
		} elseif ($type == 'url') {
7056
			$value = dol_print_url($value, '_blank', 32, 1);
7057
		} elseif ($type == 'phone') {
7058
			$value = dol_print_phone($value, '', 0, 0, '', '&nbsp;', 1);
7059
		} elseif ($type == 'price') {
7060
			if (!is_null($value) && $value !== '') {
7061
				$value = price($value, 0, $langs, 0, 0, -1, $conf->currency);
7062
			}
7063
		} elseif ($type == 'select') {
7064
			$value = $param['options'][$value];
7065
		} elseif ($type == 'sellist') {
7066
			$param_list = array_keys($param['options']);
7067
			$InfoFieldList = explode(":", $param_list[0]);
7068
7069
			$selectkey = "rowid";
7070
			$keyList = 'rowid';
7071
7072
			if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
7073
				$selectkey = $InfoFieldList[2];
7074
				$keyList = $InfoFieldList[2].' as rowid';
7075
			}
7076
7077
			$fields_label = explode('|', $InfoFieldList[1]);
7078
			if (is_array($fields_label)) {
7079
				$keyList .= ', ';
7080
				$keyList .= implode(', ', $fields_label);
7081
			}
7082
7083
			$sql = 'SELECT '.$keyList;
7084
			$sql .= ' FROM '.MAIN_DB_PREFIX.$InfoFieldList[0];
7085
			if (strpos($InfoFieldList[4], 'extra') !== false) {
7086
				$sql .= ' as main';
7087
			}
7088
			if ($selectkey == 'rowid' && empty($value)) {
7089
				$sql .= " WHERE ".$selectkey."=0";
7090
			} elseif ($selectkey == 'rowid') {
7091
				$sql .= " WHERE ".$selectkey."=".$this->db->escape($value);
7092
			} else {
7093
				$sql .= " WHERE ".$selectkey."='".$this->db->escape($value)."'";
7094
			}
7095
7096
			//$sql.= ' AND entity = '.$conf->entity;
7097
7098
			dol_syslog(get_class($this).':showOutputField:$type=sellist', LOG_DEBUG);
7099
			$resql = $this->db->query($sql);
7100
			if ($resql) {
7101
				$value = ''; // value was used, so now we reste it to use it to build final output
7102
7103
				$obj = $this->db->fetch_object($resql);
7104
7105
				// Several field into label (eq table:code|libelle:rowid)
7106
				$fields_label = explode('|', $InfoFieldList[1]);
7107
7108
				if (is_array($fields_label) && count($fields_label) > 1) {
7109
					foreach ($fields_label as $field_toshow) {
7110
						$translabel = '';
7111
						if (!empty($obj->$field_toshow)) {
7112
							$translabel = $langs->trans($obj->$field_toshow);
7113
						}
7114
						if ($translabel != $field_toshow) {
7115
							$value .= dol_trunc($translabel, 18).' ';
7116
						} else {
7117
							$value .= $obj->$field_toshow.' ';
7118
						}
7119
					}
7120
				} else {
7121
					$translabel = '';
7122
					if (!empty($obj->{$InfoFieldList[1]})) {
7123
						$translabel = $langs->trans($obj->{$InfoFieldList[1]});
7124
					}
7125
					if ($translabel != $obj->{$InfoFieldList[1]}) {
7126
						$value = dol_trunc($translabel, 18);
7127
					} else {
7128
						$value = $obj->{$InfoFieldList[1]};
7129
					}
7130
				}
7131
			} else {
7132
				dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
7133
			}
7134
		} elseif ($type == 'radio') {
7135
			$value = $param['options'][$value];
7136
		} elseif ($type == 'checkbox') {
7137
			$value_arr = explode(',', $value);
7138
			$value = '';
7139
			if (is_array($value_arr) && count($value_arr) > 0) {
7140
				$toprint = array();
7141
				foreach ($value_arr as $keyval => $valueval) {
7142
					$toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">'.$param['options'][$valueval].'</li>';
7143
				}
7144
				$value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
7145
			}
7146
		} elseif ($type == 'chkbxlst') {
7147
			$value_arr = explode(',', $value);
7148
7149
			$param_list = array_keys($param['options']);
7150
			$InfoFieldList = explode(":", $param_list[0]);
7151
7152
			$selectkey = "rowid";
7153
			$keyList = 'rowid';
7154
7155
			if (count($InfoFieldList) >= 3) {
7156
				$selectkey = $InfoFieldList[2];
7157
				$keyList = $InfoFieldList[2].' as rowid';
7158
			}
7159
7160
			$fields_label = explode('|', $InfoFieldList[1]);
7161
			if (is_array($fields_label)) {
7162
				$keyList .= ', ';
7163
				$keyList .= implode(', ', $fields_label);
7164
			}
7165
7166
			$sql = 'SELECT '.$keyList;
7167
			$sql .= ' FROM '.MAIN_DB_PREFIX.$InfoFieldList[0];
7168
			if (strpos($InfoFieldList[4], 'extra') !== false) {
7169
				$sql .= ' as main';
7170
			}
7171
			// $sql.= " WHERE ".$selectkey."='".$this->db->escape($value)."'";
7172
			// $sql.= ' AND entity = '.$conf->entity;
7173
7174
			dol_syslog(get_class($this).':showOutputField:$type=chkbxlst', LOG_DEBUG);
7175
			$resql = $this->db->query($sql);
7176
			if ($resql) {
7177
				$value = ''; // value was used, so now we reste it to use it to build final output
7178
				$toprint = array();
7179
				while ($obj = $this->db->fetch_object($resql)) {
7180
					// Several field into label (eq table:code|libelle:rowid)
7181
					$fields_label = explode('|', $InfoFieldList[1]);
7182
					if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
7183
						if (is_array($fields_label) && count($fields_label) > 1) {
7184
							foreach ($fields_label as $field_toshow) {
7185
								$translabel = '';
7186
								if (!empty($obj->$field_toshow)) {
7187
									$translabel = $langs->trans($obj->$field_toshow);
7188
								}
7189
								if ($translabel != $field_toshow) {
7190
									$toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">'.dol_trunc($translabel, 18).'</li>';
7191
								} else {
7192
									$toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">'.$obj->$field_toshow.'</li>';
7193
								}
7194
							}
7195
						} else {
7196
							$translabel = '';
7197
							if (!empty($obj->{$InfoFieldList[1]})) {
7198
								$translabel = $langs->trans($obj->{$InfoFieldList[1]});
7199
							}
7200
							if ($translabel != $obj->{$InfoFieldList[1]}) {
7201
								$toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">'.dol_trunc($translabel, 18).'</li>';
7202
							} else {
7203
								$toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">'.$obj->{$InfoFieldList[1]}.'</li>';
7204
							}
7205
						}
7206
					}
7207
				}
7208
				$value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
7209
			} else {
7210
				dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
7211
			}
7212
		} elseif ($type == 'link') {
7213
			$out = '';
7214
7215
			// only if something to display (perf)
7216
			if ($value) {
7217
				$param_list = array_keys($param['options']); // $param_list='ObjectName:classPath'
7218
7219
				$InfoFieldList = explode(":", $param_list[0]);
7220
				$classname = $InfoFieldList[0];
7221
				$classpath = $InfoFieldList[1];
7222
				$getnomurlparam = (empty($InfoFieldList[2]) ? 3 : $InfoFieldList[2]);
7223
				if (!empty($classpath)) {
7224
					dol_include_once($InfoFieldList[1]);
7225
					if ($classname && class_exists($classname)) {
7226
						$object = new $classname($this->db);
7227
						$object->fetch($value);
7228
						$value = $object->getNomUrl($getnomurlparam);
7229
					}
7230
				} else {
7231
					dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7232
					return 'Error bad setup of extrafield';
7233
				}
7234
			} else {
7235
				$value = '';
7236
			}
7237
		} elseif (preg_match('/^(text|html)/', $type)) {
7238
			$value = dol_htmlentitiesbr($value);
7239
		} elseif ($type == 'password') {
7240
			$value = preg_replace('/./i', '*', $value);
7241
		} elseif ($type == 'array') {
7242
			$value = implode('<br>', $value);
7243
		}
7244
7245
		//print $type.'-'.$size.'-'.$value;
7246
		$out = $value;
7247
7248
		return $out;
7249
	}
7250
7251
7252
	/**
7253
	 * Function to show lines of extrafields with output datas.
7254
	 * This function is responsible to output the <tr> and <td> according to correct number of columns received into $params['colspan'] or <div> according to $display_type
7255
	 *
7256
	 * @param 	Extrafields $extrafields    Extrafield Object
7257
	 * @param 	string      $mode           Show output ('view') or input ('create' or 'edit') for extrafield
7258
	 * @param 	array       $params         Optional parameters. Example: array('style'=>'class="oddeven"', 'colspan'=>$colspan)
7259
	 * @param 	string      $keysuffix      Suffix string to add after name and id of field (can be used to avoid duplicate names)
7260
	 * @param 	string      $keyprefix      Prefix string to add before name and id of field (can be used to avoid duplicate names)
7261
	 * @param	string		$onetrtd		All fields in same tr td. Used by objectline_create.tpl.php for example.
7262
	 * @param	string		$display_type	"card" for form display, "line" for document line display (extrafields on propal line, order line, etc...)
7263
	 * @return 	string
7264
	 */
7265
	public function showOptionals($extrafields, $mode = 'view', $params = null, $keysuffix = '', $keyprefix = '', $onetrtd = 0, $display_type = 'card')
7266
	{
7267
		global $db, $conf, $langs, $action, $form, $hookmanager;
7268
7269
		if (!is_object($form)) {
7270
			$form = new Form($db);
7271
		}
7272
7273
		$out = '';
7274
7275
		$parameters = array();
7276
		$reshook = $hookmanager->executeHooks('showOptionals', $parameters, $this, $action); // Note that $action and $object may have been modified by hook
7277
		if (empty($reshook)) {
7278
			if (is_array($extrafields->attributes[$this->table_element]['label']) && count($extrafields->attributes[$this->table_element]['label']) > 0) {
7279
				$out .= "\n";
7280
				$out .= '<!-- showOptionals --> ';
7281
				$out .= "\n";
7282
7283
				$extrafields_collapse_num = '';
7284
				$e = 0;
7285
				foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $label) {
7286
					// Show only the key field in params
7287
					if (is_array($params) && array_key_exists('onlykey', $params) && $key != $params['onlykey']) {
7288
						continue;
7289
					}
7290
7291
					// Test on 'enabled' ('enabled' is different than 'list' = 'visibility')
7292
					$enabled = 1;
7293
					if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
7294
						$enabled = dol_eval($extrafields->attributes[$this->table_element]['enabled'][$key], 1);
7295
					}
7296
					if (empty($enabled)) {
7297
						continue;
7298
					}
7299
7300
					$visibility = 1;
7301
					if ($visibility && isset($extrafields->attributes[$this->table_element]['list'][$key])) {
7302
						$visibility = dol_eval($extrafields->attributes[$this->table_element]['list'][$key], 1);
7303
					}
7304
7305
					$perms = 1;
7306
					if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
7307
						$perms = dol_eval($extrafields->attributes[$this->table_element]['perms'][$key], 1);
7308
					}
7309
7310
					if (($mode == 'create') && abs($visibility) != 1 && abs($visibility) != 3) {
7311
						continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list
7312
					} elseif (($mode == 'edit') && abs($visibility) != 1 && abs($visibility) != 3 && abs($visibility) != 4) {
7313
						continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list and <> 4 = not visible at the creation
7314
					} elseif ($mode == 'view' && empty($visibility)) {
7315
						continue;
7316
					}
7317
					if (empty($perms)) {
7318
						continue;
7319
					}
7320
					// Load language if required
7321
					if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
7322
						$langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
7323
					}
7324
7325
					$colspan = '';
7326
					if (is_array($params) && count($params) > 0 && $display_type=='card') {
7327
						if (array_key_exists('cols', $params)) {
7328
							$colspan = $params['cols'];
7329
						} elseif (array_key_exists('colspan', $params)) {	// For backward compatibility. Use cols instead now.
7330
							$reg = array();
7331
							if (preg_match('/colspan="(\d+)"/', $params['colspan'], $reg)) {
7332
								$colspan = $reg[1];
7333
							} else {
7334
								$colspan = $params['colspan'];
7335
							}
7336
						}
7337
					}
7338
7339
					switch ($mode) {
7340
						case "view":
7341
							$value = $this->array_options["options_".$key.$keysuffix]; // Value may be clean or formated later
7342
							break;
7343
						case "create":
7344
						case "edit":
7345
							// We get the value of property found with GETPOST so it takes into account:
7346
							// default values overwrite, restore back to list link, ... (but not 'default value in database' of field)
7347
							$check = 'alphanohtml';
7348
							if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text'))) {
7349
								$check = 'restricthtml';
7350
							}
7351
							$getposttemp = GETPOST($keyprefix.'options_'.$key.$keysuffix, $check, 3); // GETPOST can get value from GET, POST or setup of default values overwrite.
7352
							// GETPOST("options_" . $key) can be 'abc' or array(0=>'abc')
7353
							if (is_array($getposttemp) || $getposttemp != '' || GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix)) {
7354
								if (is_array($getposttemp)) {
7355
									// $getposttemp is an array but following code expects a comma separated string
7356
									$value = implode(",", $getposttemp);
7357
								} else {
7358
									$value = $getposttemp;
7359
								}
7360
							} else {
7361
								$value = (!empty($this->array_options["options_".$key]) ? $this->array_options["options_".$key] : ''); // No GET, no POST, no default value, so we take value of object.
7362
							}
7363
							//var_dump($keyprefix.' - '.$key.' - '.$keysuffix.' - '.$keyprefix.'options_'.$key.$keysuffix.' - '.$this->array_options["options_".$key.$keysuffix].' - '.$getposttemp.' - '.$value);
7364
							break;
7365
					}
7366
7367
					// Output value of the current field
7368
					if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
7369
						$extrafields_collapse_num = '';
7370
						$extrafield_param = $extrafields->attributes[$this->table_element]['param'][$key];
7371
						if (!empty($extrafield_param) && is_array($extrafield_param)) {
7372
							$extrafield_param_list = array_keys($extrafield_param['options']);
7373
7374
							if (count($extrafield_param_list) > 0) {
7375
								$extrafield_collapse_display_value = intval($extrafield_param_list[0]);
7376
7377
								if ($extrafield_collapse_display_value == 1 || $extrafield_collapse_display_value == 2) {
7378
									$extrafields_collapse_num = $extrafields->attributes[$this->table_element]['pos'][$key];
7379
								}
7380
							}
7381
						}
7382
7383
						$out .= $extrafields->showSeparator($key, $this, ($colspan + 1), $display_type);
7384
					} else {
7385
						$class = (!empty($extrafields->attributes[$this->table_element]['hidden'][$key]) ? 'hideobject ' : '');
7386
						$csstyle = '';
7387
						if (is_array($params) && count($params) > 0) {
7388
							if (array_key_exists('class', $params)) {
7389
								$class .= $params['class'].' ';
7390
							}
7391
							if (array_key_exists('style', $params)) {
7392
								$csstyle = $params['style'];
7393
							}
7394
						}
7395
7396
						// add html5 elements
7397
						$domData  = ' data-element="extrafield"';
7398
						$domData .= ' data-targetelement="'.$this->element.'"';
7399
						$domData .= ' data-targetid="'.$this->id.'"';
7400
7401
						$html_id = (empty($this->id) ? '' : 'extrarow-'.$this->element.'_'.$key.'_'.$this->id);
7402
						if ($display_type=='card') {
7403
							if (!empty($conf->global->MAIN_EXTRAFIELDS_USE_TWO_COLUMS) && ($e % 2) == 0) {
7404
								$colspan = '0';
7405
							}
7406
7407
							if ($action == 'selectlines') {
7408
								$colspan++;
7409
							}
7410
						}
7411
7412
						// Convert date into timestamp format (value in memory must be a timestamp)
7413
						if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date', 'datetime'))) {
7414
							$datenotinstring = $this->array_options['options_'.$key];
7415
							if (!is_numeric($this->array_options['options_'.$key])) {	// For backward compatibility
7416
								$datenotinstring = $this->db->jdate($datenotinstring);
7417
							}
7418
							$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;
7419
						}
7420
						// Convert float submited string into real php numeric (value in memory must be a php numeric)
7421
						if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('price', 'double'))) {
7422
							$value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? price2num($value) : $this->array_options['options_'.$key];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $value does not seem to be defined for all execution paths leading up to this point.
Loading history...
7423
						}
7424
7425
						// HTML, text, select, integer and varchar: take into account default value in database if in create mode
7426
						if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text', 'varchar', 'select', 'int'))) {
7427
							if ($action == 'create') {
7428
								$value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? $value : $extrafields->attributes[$this->table_element]['default'][$key];
7429
							}
7430
						}
7431
7432
						$labeltoshow = $langs->trans($label);
7433
						$helptoshow = $langs->trans($extrafields->attributes[$this->table_element]['help'][$key]);
7434
7435
						if ($display_type == 'card') {
7436
							$out .= '<tr '.($html_id ? 'id="'.$html_id.'" ' : '').$csstyle.' class="valuefieldcreate '.$class.$this->element.'_extras_'.$key.' trextrafields_collapse'.$extrafields_collapse_num.(!empty($this->id)?'_'.$this->id:'').'" '.$domData.' >';
7437
							$out .= '<td class="wordbreak';
7438
						} elseif ($display_type == 'line') {
7439
							$out .= '<div '.($html_id ? 'id="'.$html_id.'" ' : '').$csstyle.' class="valuefieldlinecreate '.$class.$this->element.'_extras_'.$key.' trextrafields_collapse'.$extrafields_collapse_num.(!empty($this->id)?'_'.$this->id:'').'" '.$domData.' >';
7440
							$out .= '<div style="display: inline-block; padding-right:4px" class="wordbreak';
7441
						}
7442
						//$out .= "titlefield";
7443
						//if (GETPOST('action', 'restricthtml') == 'create') $out.='create';
7444
						// BUG #11554 : For public page, use red dot for required fields, instead of bold label
7445
						$tpl_context = isset($params["tpl_context"]) ? $params["tpl_context"] : "none";
7446
						if ($tpl_context == "public") {	// Public page : red dot instead of fieldrequired characters
7447
							$out .= '">';
7448
							if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
7449
								$out .= $form->textwithpicto($labeltoshow, $helptoshow);
7450
							} else {
7451
								$out .= $labeltoshow;
7452
							}
7453
							if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
7454
								$out .= '&nbsp;<font color="red">*</font>';
7455
							}
7456
						} else {
7457
							if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
7458
								$out .= ' fieldrequired';
7459
							}
7460
							$out .= '">';
7461
							if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
7462
								$out .= $form->textwithpicto($labeltoshow, $helptoshow);
7463
							} else {
7464
								$out .= $labeltoshow;
7465
							}
7466
						}
7467
7468
						$out .= ($display_type == 'card' ? '</td>' : '</div>');
7469
7470
						$html_id = !empty($this->id) ? $this->element.'_extras_'.$key.'_'.$this->id : '';
7471
						if ($display_type == 'card') {
7472
							$out .= '<td '.($html_id ? 'id="'.$html_id.'" ' : '').' class="'.$this->element.'_extras_'.$key.'" '.($colspan ? ' colspan="'.$colspan.'"' : '').'>';
7473
						} elseif ($display_type == 'line') {
7474
							$out .= '<div '.($html_id ? 'id="'.$html_id.'" ' : '').' style="display: inline-block" class="'.$this->element.'_extras_'.$key.'">';
7475
						}
7476
7477
						switch ($mode) {
7478
							case "view":
7479
								$out .= $extrafields->showOutputField($key, $value);
7480
								break;
7481
							case "create":
7482
								$out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', 0, $this->id, $this->table_element);
7483
								break;
7484
							case "edit":
7485
								$out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', 0, $this->id, $this->table_element);
7486
								break;
7487
						}
7488
7489
						$out .= ($display_type=='card' ? '</td>' : '</div>');
7490
7491
						/*for($ii = 0; $ii < ($colspan - 1); $ii++)
7492
						{
7493
							$out .='<td class="'.$this->element.'_extras_'.$key.'"></td>';
7494
						}*/
7495
7496
						if (!empty($conf->global->MAIN_EXTRAFIELDS_USE_TWO_COLUMS) && (($e % 2) == 1)) {
7497
							$out .= ($display_type=='card' ? '</tr>' : '</div>');
7498
						} else {
7499
							$out .= ($display_type=='card' ? '</tr>' : '</div>');
7500
						}
7501
						$e++;
7502
					}
7503
				}
7504
				$out .= "\n";
7505
				// Add code to manage list depending on others
7506
				if (!empty($conf->use_javascript_ajax)) {
7507
					$out .= $this->getJSListDependancies();
7508
				}
7509
7510
				$out .= '<!-- /showOptionals --> '."\n";
7511
			}
7512
		}
7513
7514
		$out .= $hookmanager->resPrint;
7515
7516
		return $out;
7517
	}
7518
7519
	/**
7520
	 * @param 	string 	$type	Type for prefix
7521
	 * @return 	string			Javacript code to manage dependency
7522
	 */
7523
	public function getJSListDependancies($type = '_extra')
7524
	{
7525
		$out = '
7526
					<script>
7527
					jQuery(document).ready(function() {
7528
						function showOptions'.$type.'(child_list, parent_list, orig_select)
7529
						{
7530
							var val = $("select[name=\""+parent_list+"\"]").val();
7531
							var parentVal = parent_list + ":" + val;
7532
							if(typeof val == "string"){
7533
				    		    if(val != "") {
7534
					    			var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
7535
									$("select[name=\""+child_list+"\"] option[parent]").remove();
7536
									$("select[name=\""+child_list+"\"]").append(options);
7537
								} else {
7538
									var options = orig_select.find("option[parent]").clone();
7539
									$("select[name=\""+child_list+"\"] option[parent]").remove();
7540
									$("select[name=\""+child_list+"\"]").append(options);
7541
								}
7542
				    		} else if(val > 0) {
7543
								var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
7544
								$("select[name=\""+child_list+"\"] option[parent]").remove();
7545
								$("select[name=\""+child_list+"\"]").append(options);
7546
							} else {
7547
								var options = orig_select.find("option[parent]").clone();
7548
								$("select[name=\""+child_list+"\"] option[parent]").remove();
7549
								$("select[name=\""+child_list+"\"]").append(options);
7550
							}
7551
						}
7552
						function setListDependencies'.$type.'() {
7553
							jQuery("select option[parent]").parent().each(function() {
7554
								var orig_select = {};
7555
								var child_list = $(this).attr("name");
7556
								orig_select[child_list] = $(this).clone();
7557
								var parent = $(this).find("option[parent]:first").attr("parent");
7558
								var infos = parent.split(":");
7559
								var parent_list = infos[0];
7560
7561
								//Hide daughters lists
7562
								if ($("#"+child_list).val() == 0 && $("#"+parent_list).val() == 0){
7563
								    $("#"+child_list).hide();
7564
								//Show mother lists
7565
								} else if ($("#"+parent_list).val() != 0){
7566
								    $("#"+parent_list).show();
7567
								}
7568
								//Show the child list if the parent list value is selected
7569
								$("select[name=\""+parent_list+"\"]").click(function() {
7570
								    if ($(this).val() != 0){
7571
								        $("#"+child_list).show()
7572
									}
7573
								});
7574
7575
								//When we change parent list
7576
								$("select[name=\""+parent_list+"\"]").change(function() {
7577
									showOptions'.$type.'(child_list, parent_list, orig_select[child_list]);
7578
									//Select the value 0 on child list after a change on the parent list
7579
									$("#"+child_list).val(0).trigger("change");
7580
									//Hide child lists if the parent value is set to 0
7581
									if ($(this).val() == 0){
7582
								   		$("#"+child_list).hide();
7583
									}
7584
								});
7585
							});
7586
						}
7587
7588
						setListDependencies'.$type.'();
7589
					});
7590
					</script>'."\n";
7591
		return $out;
7592
	}
7593
7594
	/**
7595
	 * Returns the rights used for this class
7596
	 * @return stdClass
7597
	 */
7598
	public function getRights()
7599
	{
7600
		global $user;
7601
7602
		$element = $this->element;
7603
		if ($element == 'facturerec') {
7604
			$element = 'facture';
7605
		}
7606
7607
		return $user->rights->{$element};
7608
	}
7609
7610
	/**
7611
	 * Function used to replace a thirdparty id with another one.
7612
	 * This function is meant to be called from replaceThirdparty with the appropiate tables
7613
	 * Column name fk_soc MUST be used to identify thirdparties
7614
	 *
7615
	 * @param  DoliDB 	   $db 			  Database handler
7616
	 * @param  int 		   $origin_id     Old thirdparty id (the thirdparty to delete)
7617
	 * @param  int 		   $dest_id       New thirdparty id (the thirdparty that will received element of the other)
7618
	 * @param  string[]    $tables        Tables that need to be changed
7619
	 * @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)
7620
	 * @return bool						  True if success, False if error
7621
	 */
7622
	public static function commonReplaceThirdparty(DoliDB $db, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
7623
	{
7624
		foreach ($tables as $table) {
7625
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$table.' SET fk_soc = '.$dest_id.' WHERE fk_soc = '.$origin_id;
7626
7627
			if (!$db->query($sql)) {
7628
				if ($ignoreerrors) {
7629
					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.
7630
				}
7631
				//$this->errors = $db->lasterror();
7632
				return false;
7633
			}
7634
		}
7635
7636
		return true;
7637
	}
7638
7639
	/**
7640
	 * Get buy price to use for margin calculation. This function is called when buy price is unknown.
7641
	 *	 Set buy price = sell price if ForceBuyingPriceIfNull configured,
7642
	 *   else if calculation MARGIN_TYPE = 'costprice' and costprice is defined, use costprice as buyprice
7643
	 *	 else if calculation MARGIN_TYPE = 'pmp' and pmp is calculated, use pmp as buyprice
7644
	 *	 else set min buy price as buy price
7645
	 *
7646
	 * @param float		$unitPrice		 Product unit price
7647
	 * @param float		$discountPercent Line discount percent
7648
	 * @param int		$fk_product		 Product id
7649
	 * @return	float                    <0 if KO, buyprice if OK
7650
	 */
7651
	public function defineBuyPrice($unitPrice = 0.0, $discountPercent = 0.0, $fk_product = 0)
7652
	{
7653
		global $conf;
7654
7655
		$buyPrice = 0;
7656
7657
		if (($unitPrice > 0) && (isset($conf->global->ForceBuyingPriceIfNull) && $conf->global->ForceBuyingPriceIfNull == 1)) { // In most cases, test here is false
7658
			$buyPrice = $unitPrice * (1 - $discountPercent / 100);
7659
		} else {
7660
			// Get cost price for margin calculation
7661
			if (!empty($fk_product)) {
7662
				if (isset($conf->global->MARGIN_TYPE) && $conf->global->MARGIN_TYPE == 'costprice') {
7663
					require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
7664
					$product = new Product($this->db);
7665
					$result = $product->fetch($fk_product);
7666
					if ($result <= 0) {
7667
						$this->errors[] = 'ErrorProductIdDoesNotExists';
7668
						return -1;
7669
					}
7670
					if ($product->cost_price > 0) {
7671
						$buyPrice = $product->cost_price;
7672
					} elseif ($product->pmp > 0) {
7673
						$buyPrice = $product->pmp;
7674
					}
7675
				} elseif (isset($conf->global->MARGIN_TYPE) && $conf->global->MARGIN_TYPE == 'pmp') {
7676
					require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
7677
					$product = new Product($this->db);
7678
					$result = $product->fetch($fk_product);
7679
					if ($result <= 0) {
7680
						$this->errors[] = 'ErrorProductIdDoesNotExists';
7681
						return -1;
7682
					}
7683
					if ($product->pmp > 0) {
7684
						$buyPrice = $product->pmp;
7685
					}
7686
				}
7687
7688
				if (empty($buyPrice) && isset($conf->global->MARGIN_TYPE) && in_array($conf->global->MARGIN_TYPE, array('1', 'pmp', 'costprice'))) {
7689
					require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
7690
					$productFournisseur = new ProductFournisseur($this->db);
7691
					if (($result = $productFournisseur->find_min_price_product_fournisseur($fk_product)) > 0) {
7692
						$buyPrice = $productFournisseur->fourn_unitprice;
7693
					} elseif ($result < 0) {
7694
						$this->errors[] = $productFournisseur->error;
7695
						return -2;
7696
					}
7697
				}
7698
			}
7699
		}
7700
		return $buyPrice;
7701
	}
7702
7703
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
7704
	/**
7705
	 *  Show photos of an object (nbmax maximum), into several columns
7706
	 *
7707
	 *  @param		string	$modulepart		'product', 'ticket', ...
7708
	 *  @param      string	$sdir        	Directory to scan (full absolute path)
7709
	 *  @param      int		$size        	0=original size, 1='small' use thumbnail if possible
7710
	 *  @param      int		$nbmax       	Nombre maximum de photos (0=pas de max)
7711
	 *  @param      int		$nbbyrow     	Number of image per line or -1 to use div. Used only if size=1.
7712
	 * 	@param		int		$showfilename	1=Show filename
7713
	 * 	@param		int		$showaction		1=Show icon with action links (resize, delete)
7714
	 * 	@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.
7715
	 * 	@param		int		$maxWidth		Max width of original image when size='small'
7716
	 *  @param      int     $nolink         Do not add a href link to view enlarged imaged into a new tab
7717
	 *  @param      int     $notitle        Do not add title tag on image
7718
	 *  @param		int		$usesharelink	Use the public shared link of image (if not available, the 'nophoto' image will be shown instead)
7719
	 *  @return     string					Html code to show photo. Number of photos shown is saved in this->nbphoto
7720
	 */
7721
	public 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)
7722
	{
7723
		// phpcs:enable
7724
		global $conf, $user, $langs;
7725
7726
		include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
7727
		include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
7728
7729
		$sortfield = 'position_name';
7730
		$sortorder = 'asc';
7731
7732
		$dir = $sdir.'/';
7733
		$pdir = '/';
7734
7735
		$dir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
7736
		$pdir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
7737
7738
		// For backward compatibility
7739
		if ($modulepart == 'product') {
7740
			if (!empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO)) {
7741
				$dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
7742
				$pdir = '/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
7743
			}
7744
		}
7745
7746
		// Defined relative dir to DOL_DATA_ROOT
7747
		$relativedir = '';
7748
		if ($dir) {
7749
			$relativedir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $dir);
7750
			$relativedir = preg_replace('/^[\\/]/', '', $relativedir);
7751
			$relativedir = preg_replace('/[\\/]$/', '', $relativedir);
7752
		}
7753
7754
		$dirthumb = $dir.'thumbs/';
7755
		$pdirthumb = $pdir.'thumbs/';
7756
7757
		$return = '<!-- Photo -->'."\n";
7758
		$nbphoto = 0;
7759
7760
		$filearray = dol_dir_list($dir, "files", 0, '', '(\.meta|_preview.*\.png)$', $sortfield, (strtolower($sortorder) == 'desc' ?SORT_DESC:SORT_ASC), 1);
0 ignored issues
show
introduced by
The condition strtolower($sortorder) == 'desc' is always false.
Loading history...
7761
7762
		/*if (! empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO))    // For backward compatiblity, we scan also old dirs
7763
		 {
7764
		 $filearrayold=dol_dir_list($dirold,"files",0,'','(\.meta|_preview.*\.png)$',$sortfield,(strtolower($sortorder)=='desc'?SORT_DESC:SORT_ASC),1);
7765
		 $filearray=array_merge($filearray, $filearrayold);
7766
		 }*/
7767
7768
		completeFileArrayWithDatabaseInfo($filearray, $relativedir);
7769
7770
		if (count($filearray)) {
7771
			if ($sortfield && $sortorder) {
7772
				$filearray = dol_sort_array($filearray, $sortfield, $sortorder);
7773
			}
7774
7775
			foreach ($filearray as $key => $val) {
7776
				$photo = '';
7777
				$file = $val['name'];
7778
7779
				//if (! utf8_check($file)) $file=utf8_encode($file);	// To be sure file is stored in UTF8 in memory
7780
7781
				//if (dol_is_file($dir.$file) && image_format_supported($file) >= 0)
7782
				if (image_format_supported($file) >= 0) {
7783
					$nbphoto++;
7784
					$photo = $file;
7785
					$viewfilename = $file;
7786
7787
					if ($size == 1 || $size == 'small') {   // Format vignette
7788
						// Find name of thumb file
7789
						$photo_vignette = basename(getImageFileNameForSize($dir.$file, '_small'));
7790
						if (!dol_is_file($dirthumb.$photo_vignette)) {
7791
							$photo_vignette = '';
7792
						}
7793
7794
						// Get filesize of original file
7795
						$imgarray = dol_getImageSize($dir.$photo);
7796
7797
						if ($nbbyrow > 0) {
7798
							if ($nbphoto == 1) {
7799
								$return .= '<table class="valigntop center centpercent" style="border: 0; padding: 2px; border-spacing: 2px; border-collapse: separate;">';
7800
							}
7801
7802
							if ($nbphoto % $nbbyrow == 1) {
7803
								$return .= '<tr class="center valignmiddle" style="border: 1px">';
7804
							}
7805
							$return .= '<td style="width: '.ceil(100 / $nbbyrow).'%" class="photo">';
7806
						} elseif ($nbbyrow < 0) {
7807
							$return .= '<div class="inline-block">';
7808
						}
7809
7810
						$return .= "\n";
7811
7812
						$relativefile = preg_replace('/^\//', '', $pdir.$photo);
7813
						if (empty($nolink)) {
7814
							$urladvanced = getAdvancedPreviewUrl($modulepart, $relativefile, 0, 'entity='.$this->entity);
7815
							if ($urladvanced) {
7816
								$return .= '<a href="'.$urladvanced.'">';
7817
							} else {
7818
								$return .= '<a href="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'" class="aphoto" target="_blank">';
7819
							}
7820
						}
7821
7822
						// Show image (width height=$maxHeight)
7823
						// Si fichier vignette disponible et image source trop grande, on utilise la vignette, sinon on utilise photo origine
7824
						$alt = $langs->transnoentitiesnoconv('File').': '.$relativefile;
7825
						$alt .= ' - '.$langs->transnoentitiesnoconv('Size').': '.$imgarray['width'].'x'.$imgarray['height'];
7826
						if ($notitle) {
7827
							$alt = '';
7828
						}
7829
7830
						if ($usesharelink) {
7831
							if ($val['share']) {
7832
								if (empty($maxHeight) || $photo_vignette && $imgarray['height'] > $maxHeight) {
7833
									$return .= '<!-- Show original file (thumb not yet available with shared links) -->';
7834
									$return .= '<img class="photo photowithmargin" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/viewimage.php?hashp='.urlencode($val['share']).'" title="'.dol_escape_htmltag($alt).'">';
7835
								} else {
7836
									$return .= '<!-- Show original file -->';
7837
									$return .= '<img class="photo photowithmargin" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/viewimage.php?hashp='.urlencode($val['share']).'" title="'.dol_escape_htmltag($alt).'">';
7838
								}
7839
							} else {
7840
								$return .= '<!-- Show nophoto file (because file is not shared) -->';
7841
								$return .= '<img class="photo photowithmargin" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/public/theme/common/nophoto.png" title="'.dol_escape_htmltag($alt).'">';
7842
							}
7843
						} else {
7844
							if (empty($maxHeight) || $photo_vignette && $imgarray['height'] > $maxHeight) {
7845
								$return .= '<!-- Show thumb -->';
7846
								$return .= '<img class="photo photowithmargin maxwidth150onsmartphone" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdirthumb.$photo_vignette).'" title="'.dol_escape_htmltag($alt).'">';
7847
							} else {
7848
								$return .= '<!-- Show original file -->';
7849
								$return .= '<img class="photo photowithmargin" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'" title="'.dol_escape_htmltag($alt).'">';
7850
							}
7851
						}
7852
7853
						if (empty($nolink)) {
7854
							$return .= '</a>';
7855
						}
7856
						$return .= "\n";
7857
7858
						if ($showfilename) {
7859
							$return .= '<br>'.$viewfilename;
7860
						}
7861
						if ($showaction) {
7862
							$return .= '<br>';
7863
							// On propose la generation de la vignette si elle n'existe pas et si la taille est superieure aux limites
7864
							if ($photo_vignette && (image_format_supported($photo) > 0) && ($this->imgWidth > $maxWidth || $this->imgHeight > $maxHeight)) {
7865
								$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>';
7866
							}
7867
							// Special cas for product
7868
							if ($modulepart == 'product' && ($user->rights->produit->creer || $user->rights->service->creer)) {
7869
								// Link to resize
7870
								$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; ';
7871
7872
								// Link to delete
7873
								$return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&amp;action=delete&amp;token='.newToken().'&amp;file='.urlencode($pdir.$viewfilename).'">';
7874
								$return .= img_delete().'</a>';
7875
							}
7876
						}
7877
						$return .= "\n";
7878
7879
						if ($nbbyrow > 0) {
7880
							$return .= '</td>';
7881
							if (($nbphoto % $nbbyrow) == 0) {
7882
								$return .= '</tr>';
7883
							}
7884
						} elseif ($nbbyrow < 0) {
7885
							$return .= '</div>';
7886
						}
7887
					}
7888
7889
					if (empty($size)) {     // Format origine
7890
						$return .= '<img class="photo photowithmargin" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'">';
7891
7892
						if ($showfilename) {
7893
							$return .= '<br>'.$viewfilename;
7894
						}
7895
						if ($showaction) {
7896
							// Special case for product
7897
							if ($modulepart == 'product' && ($user->rights->produit->creer || $user->rights->service->creer)) {
7898
								// Link to resize
7899
								$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; ';
7900
7901
								// Link to delete
7902
								$return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&amp;action=delete&amp;token='.newToken().'&amp;file='.urlencode($pdir.$viewfilename).'">';
7903
								$return .= img_delete().'</a>';
7904
							}
7905
						}
7906
					}
7907
7908
					// On continue ou on arrete de boucler ?
7909
					if ($nbmax && $nbphoto >= $nbmax) {
7910
						break;
7911
					}
7912
				}
7913
			}
7914
7915
			if ($size == 1 || $size == 'small') {
7916
				if ($nbbyrow > 0) {
7917
					// Ferme tableau
7918
					while ($nbphoto % $nbbyrow) {
7919
						$return .= '<td style="width: '.ceil(100 / $nbbyrow).'%">&nbsp;</td>';
7920
						$nbphoto++;
7921
					}
7922
7923
					if ($nbphoto) {
7924
						$return .= '</table>';
7925
					}
7926
				}
7927
			}
7928
		}
7929
7930
		$this->nbphoto = $nbphoto;
7931
7932
		return $return;
7933
	}
7934
7935
7936
	/**
7937
	 * Function test if type is array
7938
	 *
7939
	 * @param   array   $info   content informations of field
7940
	 * @return  bool			true if array
7941
	 */
7942
	protected function isArray($info)
7943
	{
7944
		if (is_array($info)) {
7945
			if (isset($info['type']) && $info['type'] == 'array') {
7946
				return true;
7947
			} else {
7948
				return false;
7949
			}
7950
		}
7951
		return false;
7952
	}
7953
7954
	/**
7955
	 * Function test if type is date
7956
	 *
7957
	 * @param   array   $info   content informations of field
7958
	 * @return  bool			true if date
7959
	 */
7960
	public function isDate($info)
7961
	{
7962
		if (isset($info['type']) && ($info['type'] == 'date' || $info['type'] == 'datetime' || $info['type'] == 'timestamp')) {
7963
			return true;
7964
		}
7965
		return false;
7966
	}
7967
7968
	/**
7969
	 * Function test if type is duration
7970
	 *
7971
	 * @param   array   $info   content informations of field
7972
	 * @return  bool			true if field of type duration
7973
	 */
7974
	public function isDuration($info)
7975
	{
7976
		if (is_array($info)) {
7977
			if (isset($info['type']) && ($info['type'] == 'duration')) {
7978
				return true;
7979
			} else {
7980
				return false;
7981
			}
7982
		} else {
7983
			return false;
7984
		}
7985
	}
7986
7987
	/**
7988
	 * Function test if type is integer
7989
	 *
7990
	 * @param   array   $info   content informations of field
7991
	 * @return  bool			true if integer
7992
	 */
7993
	public function isInt($info)
7994
	{
7995
		if (is_array($info)) {
7996
			if (isset($info['type']) && ($info['type'] == 'int' || preg_match('/^integer/i', $info['type']))) {
7997
				return true;
7998
			} else {
7999
				return false;
8000
			}
8001
		} else {
8002
			return false;
8003
		}
8004
	}
8005
8006
	/**
8007
	 * Function test if type is float
8008
	 *
8009
	 * @param   array   $info   content informations of field
8010
	 * @return  bool			true if float
8011
	 */
8012
	public function isFloat($info)
8013
	{
8014
		if (is_array($info)) {
8015
			if (isset($info['type']) && (preg_match('/^(double|real|price)/i', $info['type']))) {
8016
				return true;
8017
			} else {
8018
				return false;
8019
			}
8020
		}
8021
		return false;
8022
	}
8023
8024
	/**
8025
	 * Function test if type is text
8026
	 *
8027
	 * @param   array   $info   content informations of field
8028
	 * @return  bool			true if type text
8029
	 */
8030
	public function isText($info)
8031
	{
8032
		if (is_array($info)) {
8033
			if (isset($info['type']) && $info['type'] == 'text') {
8034
				return true;
8035
			} else {
8036
				return false;
8037
			}
8038
		}
8039
		return false;
8040
	}
8041
8042
	/**
8043
	 * Function test if field can be null
8044
	 *
8045
	 * @param   array   $info   content informations of field
8046
	 * @return  bool			true if it can be null
8047
	 */
8048
	protected function canBeNull($info)
8049
	{
8050
		if (is_array($info)) {
8051
			if (isset($info['notnull']) && $info['notnull'] != '1') {
8052
				return true;
8053
			} else {
8054
				return false;
8055
			}
8056
		}
8057
		return true;
8058
	}
8059
8060
	/**
8061
	 * Function test if field is forced to null if zero or empty
8062
	 *
8063
	 * @param   array   $info   content informations of field
8064
	 * @return  bool			true if forced to null
8065
	 */
8066
	protected function isForcedToNullIfZero($info)
8067
	{
8068
		if (is_array($info)) {
8069
			if (isset($info['notnull']) && $info['notnull'] == '-1') {
8070
				return true;
8071
			} else {
8072
				return false;
8073
			}
8074
		}
8075
		return false;
8076
	}
8077
8078
	/**
8079
	 * Function test if is indexed
8080
	 *
8081
	 * @param   array   $info   content informations of field
8082
	 * @return                  bool
8083
	 */
8084
	protected function isIndex($info)
8085
	{
8086
		if (is_array($info)) {
8087
			if (isset($info['index']) && $info['index'] == true) {
8088
				return true;
8089
			} else {
8090
				return false;
8091
			}
8092
		}
8093
		return false;
8094
	}
8095
8096
8097
	/**
8098
	 * Function to prepare a part of the query for insert.
8099
	 * Note $this->${field} are set by the page that make the createCommon or the updateCommon.
8100
	 * $this->${field} should be a clean value. The page can run
8101
	 *
8102
	 * @return array
8103
	 */
8104
	protected function setSaveQuery()
8105
	{
8106
		global $conf;
8107
8108
		$queryarray = array();
8109
		foreach ($this->fields as $field => $info) {	// Loop on definition of fields
8110
			// Depending on field type ('datetime', ...)
8111
			if ($this->isDate($info)) {
8112
				if (empty($this->{$field})) {
8113
					$queryarray[$field] = null;
8114
				} else {
8115
					$queryarray[$field] = $this->db->idate($this->{$field});
8116
				}
8117
			} elseif ($this->isArray($info)) {
8118
				if (!empty($this->{$field})) {
8119
					if (!is_array($this->{$field})) {
8120
						$this->{$field} = array($this->{$field});
8121
					}
8122
					$queryarray[$field] = serialize($this->{$field});
8123
				} else {
8124
					$queryarray[$field] = null;
8125
				}
8126
			} elseif ($this->isDuration($info)) {
8127
				// $this->{$field} may be null, '', 0, '0', 123, '123'
8128
				if ((isset($this->{$field}) && $this->{$field} != '') || !empty($info['notnull'])) {
8129
					if (!isset($this->{$field})) {
8130
						$queryarray[$field] = 0;
8131
					} else {
8132
						$queryarray[$field] = (int) $this->{$field};		// If '0', it may be set to null later if $info['notnull'] == -1
8133
					}
8134
				} else {
8135
					$queryarray[$field] = null;
8136
				}
8137
			} elseif ($this->isInt($info) || $this->isFloat($info)) {
8138
				if ($field == 'entity' && is_null($this->{$field})) {
8139
					$queryarray[$field] = $conf->entity;
8140
				} else {
8141
					// $this->{$field} may be null, '', 0, '0', 123, '123'
8142
					if ((isset($this->{$field}) && $this->{$field} != '') || !empty($info['notnull'])) {
8143
						if (!isset($this->{$field})) {
8144
							$queryarray[$field] = 0;
8145
						} elseif ($this->isInt($info)) {
8146
							$queryarray[$field] = (int) $this->{$field};	// If '0', it may be set to null later if $info['notnull'] == -1
8147
						} elseif ($this->isFloat($info)) {
8148
							$queryarray[$field] = (double) $this->{$field};	// If '0', it may be set to null later if $info['notnull'] == -1
8149
						}
8150
					} else {
8151
						$queryarray[$field] = null;
8152
					}
8153
				}
8154
			} else {
8155
				$queryarray[$field] = $this->{$field};
8156
			}
8157
8158
			if ($info['type'] == 'timestamp' && empty($queryarray[$field])) {
8159
				unset($queryarray[$field]);
8160
			}
8161
			if (!empty($info['notnull']) && $info['notnull'] == -1 && empty($queryarray[$field])) {
8162
				$queryarray[$field] = null; // May force 0 to null
8163
			}
8164
		}
8165
8166
		return $queryarray;
8167
	}
8168
8169
	/**
8170
	 * Function to load data from a SQL pointer into properties of current object $this
8171
	 *
8172
	 * @param   stdClass    $obj    Contain data of object from database
8173
	 * @return void
8174
	 */
8175
	public function setVarsFromFetchObj(&$obj)
8176
	{
8177
		foreach ($this->fields as $field => $info) {
8178
			if ($this->isDate($info)) {
8179
				if (empty($obj->{$field}) || $obj->{$field} === '0000-00-00 00:00:00' || $obj->{$field} === '1000-01-01 00:00:00') {
8180
					$this->{$field} = 0;
8181
				} else {
8182
					$this->{$field} = strtotime($obj->{$field});
8183
				}
8184
			} elseif ($this->isArray($info)) {
8185
				if (!empty($obj->{$field})) {
8186
					$this->{$field} = @unserialize($obj->{$field});
8187
					// Hack for data not in UTF8
8188
					if ($this->{$field } === false) {
8189
						@unserialize(utf8_decode($obj->{$field}));
8190
					}
8191
				} else {
8192
					$this->{$field} = array();
8193
				}
8194
			} elseif ($this->isInt($info)) {
8195
				if ($field == 'rowid') {
8196
					$this->id = (int) $obj->{$field};
8197
				} else {
8198
					if ($this->isForcedToNullIfZero($info)) {
8199
						if (empty($obj->{$field})) {
8200
							$this->{$field} = null;
8201
						} else {
8202
							$this->{$field} = (double) $obj->{$field};
8203
						}
8204
					} else {
8205
						if (!is_null($obj->{$field}) || (isset($info['notnull']) && $info['notnull'] == 1)) {
8206
							$this->{$field} = (int) $obj->{$field};
8207
						} else {
8208
							$this->{$field} = null;
8209
						}
8210
					}
8211
				}
8212
			} elseif ($this->isFloat($info)) {
8213
				if ($this->isForcedToNullIfZero($info)) {
8214
					if (empty($obj->{$field})) {
8215
						$this->{$field} = null;
8216
					} else {
8217
						$this->{$field} = (double) $obj->{$field};
8218
					}
8219
				} else {
8220
					if (!is_null($obj->{$field}) || (isset($info['notnull']) && $info['notnull'] == 1)) {
8221
						$this->{$field} = (double) $obj->{$field};
8222
					} else {
8223
						$this->{$field} = null;
8224
					}
8225
				}
8226
			} else {
8227
				$this->{$field} = $obj->{$field};
8228
			}
8229
		}
8230
8231
		// If there is no 'ref' field, we force property ->ref to ->id for a better compatibility with common functions.
8232
		if (!isset($this->fields['ref']) && isset($this->id)) {
8233
			$this->ref = $this->id;
8234
		}
8235
	}
8236
8237
	/**
8238
	 * Function to concat keys of fields
8239
	 *
8240
	 * @return string
8241
	 */
8242
	protected function getFieldList()
8243
	{
8244
		$keys = array_keys($this->fields);
8245
		return implode(',', $keys);
8246
	}
8247
8248
	/**
8249
	 * Add quote to field value if necessary
8250
	 *
8251
	 * @param 	string|int	$value			Value to protect
8252
	 * @param	array		$fieldsentry	Properties of field
8253
	 * @return 	string
8254
	 */
8255
	protected function quote($value, $fieldsentry)
8256
	{
8257
		if (is_null($value)) {
1 ignored issue
show
introduced by
The condition is_null($value) is always false.
Loading history...
8258
			return 'NULL';
8259
		} elseif (preg_match('/^(int|double|real|price)/i', $fieldsentry['type'])) {
8260
			return $this->db->escape("$value");
8261
		} elseif ($fieldsentry['type'] == 'boolean') {
8262
			if ($value) {
8263
				return 'true';
8264
			} else {
8265
				return 'false';
8266
			}
8267
		} else {
8268
			return "'".$this->db->escape($value)."'";
8269
		}
8270
	}
8271
8272
8273
	/**
8274
	 * Create object into database
8275
	 *
8276
	 * @param  User $user      User that creates
8277
	 * @param  bool $notrigger false=launch triggers after, true=disable triggers
8278
	 * @return int             <0 if KO, Id of created object if OK
8279
	 */
8280
	public function createCommon(User $user, $notrigger = false)
8281
	{
8282
		global $langs;
8283
		dol_syslog(get_class($this)."::createCommon create", LOG_DEBUG);
8284
8285
		$error = 0;
8286
8287
		$now = dol_now();
8288
8289
		$fieldvalues = $this->setSaveQuery();
8290
8291
		if (array_key_exists('date_creation', $fieldvalues) && empty($fieldvalues['date_creation'])) {
8292
			$fieldvalues['date_creation'] = $this->db->idate($now);
8293
		}
8294
		if (array_key_exists('fk_user_creat', $fieldvalues) && !($fieldvalues['fk_user_creat'] > 0)) {
8295
			$fieldvalues['fk_user_creat'] = $user->id;
8296
		}
8297
		unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into insert.
8298
		if (array_key_exists('ref', $fieldvalues)) {
8299
			$fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
8300
		}
8301
8302
		$keys = array();
8303
		$values = array(); // Array to store string forged for SQL syntax
8304
		foreach ($fieldvalues as $k => $v) {
8305
			$keys[$k] = $k;
8306
			$value = $this->fields[$k];
8307
			$values[$k] = $this->quote($v, $value); // May return string 'NULL' if $value is null
8308
		}
8309
8310
		// Clean and check mandatory
8311
		foreach ($keys as $key) {
8312
			// If field is an implicit foreign key field
8313
			if (preg_match('/^integer:/i', $this->fields[$key]['type']) && $values[$key] == '-1') {
8314
				$values[$key] = '';
8315
			}
8316
			if (!empty($this->fields[$key]['foreignkey']) && $values[$key] == '-1') {
8317
				$values[$key] = '';
8318
			}
8319
8320
			if (isset($this->fields[$key]['notnull']) && $this->fields[$key]['notnull'] == 1 && (!isset($values[$key]) || $values[$key] === 'NULL') && is_null($this->fields[$key]['default'])) {
8321
				$error++;
8322
				$this->errors[] = $langs->trans("ErrorFieldRequired", $this->fields[$key]['label']);
8323
			}
8324
8325
			// If value is null and there is a default value for field
8326
			if (isset($this->fields[$key]['notnull']) && $this->fields[$key]['notnull'] == 1 && (!isset($values[$key]) || $values[$key] === 'NULL') && !is_null($this->fields[$key]['default'])) {
8327
				$values[$key] = $this->fields[$key]['default'];
8328
			}
8329
8330
			// If field is an implicit foreign key field
8331
			if (preg_match('/^integer:/i', $this->fields[$key]['type']) && empty($values[$key])) {
8332
				if (isset($this->fields[$key]['default'])) {
8333
					$values[$key] = $this->fields[$key]['default'];
8334
				} else {
8335
					$values[$key] = 'null';
8336
				}
8337
			}
8338
			if (!empty($this->fields[$key]['foreignkey']) && empty($values[$key])) {
8339
				$values[$key] = 'null';
8340
			}
8341
		}
8342
8343
		if ($error) {
8344
			return -1;
8345
		}
8346
8347
		$this->db->begin();
8348
8349
		if (!$error) {
8350
			$sql = 'INSERT INTO '.MAIN_DB_PREFIX.$this->table_element;
8351
			$sql .= ' ('.implode(", ", $keys).')';
8352
			$sql .= ' VALUES ('.implode(", ", $values).')';
8353
8354
			$res = $this->db->query($sql);
8355
			if ($res === false) {
0 ignored issues
show
introduced by
The condition $res === false is always false.
Loading history...
8356
				$error++;
8357
				$this->errors[] = $this->db->lasterror();
8358
			}
8359
		}
8360
8361
		if (!$error) {
8362
			$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
8363
		}
8364
8365
		// If we have a field ref with a default value of (PROV)
8366
		if (!$error) {
8367
			if (key_exists('ref', $this->fields) && $this->fields['ref']['notnull'] > 0 && !is_null($this->fields['ref']['default']) && $this->fields['ref']['default'] == '(PROV)') {
8368
				$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET ref = '(PROV".$this->id.")' WHERE (ref = '(PROV)' OR ref = '') AND rowid = ".$this->id;
8369
				$resqlupdate = $this->db->query($sql);
8370
8371
				if ($resqlupdate === false) {
0 ignored issues
show
introduced by
The condition $resqlupdate === false is always false.
Loading history...
8372
					$error++;
8373
					$this->errors[] = $this->db->lasterror();
8374
				} else {
8375
					$this->ref = '(PROV'.$this->id.')';
8376
				}
8377
			}
8378
		}
8379
8380
		// Create extrafields
8381
		if (!$error) {
8382
			$result = $this->insertExtraFields();
8383
			if ($result < 0) {
8384
				$error++;
8385
			}
8386
		}
8387
8388
		// Create lines
8389
		if (!empty($this->table_element_line) && !empty($this->fk_element)) {
8390
			$num = (is_array($this->lines) ? count($this->lines) : 0);
8391
			for ($i = 0; $i < $num; $i++) {
8392
				$line = $this->lines[$i];
8393
8394
				$keyforparent = $this->fk_element;
8395
				$line->$keyforparent = $this->id;
8396
8397
				// Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
8398
				//if (! is_object($line)) $line=json_decode(json_encode($line), false);  // convert recursively array into object.
8399
				if (!is_object($line)) {
8400
					$line = (object) $line;
8401
				}
8402
8403
				$result = $line->create($user, 1);
8404
				if ($result < 0) {
8405
					$this->error = $this->db->lasterror();
8406
					$this->db->rollback();
8407
					return -1;
8408
				}
8409
			}
8410
		}
8411
8412
		// Triggers
8413
		if (!$error && !$notrigger) {
8414
			// Call triggers
8415
			$result = $this->call_trigger(strtoupper(get_class($this)).'_CREATE', $user);
8416
			if ($result < 0) {
8417
				$error++;
8418
			}
8419
			// End call triggers
8420
		}
8421
8422
		// Commit or rollback
8423
		if ($error) {
8424
			$this->db->rollback();
8425
			return -1;
8426
		} else {
8427
			$this->db->commit();
8428
			return $this->id;
8429
		}
8430
	}
8431
8432
8433
	/**
8434
	 * Load object in memory from the database
8435
	 *
8436
	 * @param	int    $id				Id object
8437
	 * @param	string $ref				Ref
8438
	 * @param	string	$morewhere		More SQL filters (' AND ...')
8439
	 * @return 	int         			<0 if KO, 0 if not found, >0 if OK
8440
	 */
8441
	public function fetchCommon($id, $ref = null, $morewhere = '')
8442
	{
8443
		if (empty($id) && empty($ref) && empty($morewhere)) {
8444
			return -1;
8445
		}
8446
8447
		$fieldlist = $this->getFieldList();
8448
		if (empty($fieldlist)) {
8449
			return 0;
8450
		}
8451
8452
		$sql = 'SELECT '.$fieldlist;
8453
		$sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element;
8454
8455
		if (!empty($id)) {
8456
			$sql .= ' WHERE rowid = '.$id;
8457
		} elseif (!empty($ref)) {
8458
			$sql .= " WHERE ref = ".$this->quote($ref, $this->fields['ref']);
8459
		} else {
8460
			$sql .= ' WHERE 1 = 1'; // usage with empty id and empty ref is very rare
8461
		}
8462
		if (empty($id) && isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
8463
			$sql .= ' AND entity IN ('.getEntity($this->table_element).')';
8464
		}
8465
		if ($morewhere) {
8466
			$sql .= $morewhere;
8467
		}
8468
		$sql .= ' LIMIT 1'; // This is a fetch, to be sure to get only one record
8469
8470
		$res = $this->db->query($sql);
8471
		if ($res) {
8472
			$obj = $this->db->fetch_object($res);
8473
			if ($obj) {
8474
				$this->setVarsFromFetchObj($obj);
8475
8476
				// Retrieve all extrafield
8477
				// fetch optionals attributes and labels
8478
				$this->fetch_optionals();
8479
8480
				return $this->id;
8481
			} else {
8482
				return 0;
8483
			}
8484
		} else {
8485
			$this->error = $this->db->lasterror();
8486
			$this->errors[] = $this->error;
8487
			return -1;
8488
		}
8489
	}
8490
8491
	/**
8492
	 * Load object in memory from the database
8493
	 *
8494
	 * @param	string	$morewhere		More SQL filters (' AND ...')
8495
	 * @return 	int         			<0 if KO, 0 if not found, >0 if OK
8496
	 */
8497
	public function fetchLinesCommon($morewhere = '')
8498
	{
8499
		$objectlineclassname = get_class($this).'Line';
8500
		if (!class_exists($objectlineclassname)) {
8501
			$this->error = 'Error, class '.$objectlineclassname.' not found during call of fetchLinesCommon';
8502
			return -1;
8503
		}
8504
8505
		$objectline = new $objectlineclassname($this->db);
8506
8507
		$sql = 'SELECT '.$objectline->getFieldList();
8508
		$sql .= ' FROM '.MAIN_DB_PREFIX.$objectline->table_element;
8509
		$sql .= ' WHERE fk_'.$this->element.' = '.$this->id;
8510
		if ($morewhere) {
8511
			$sql .= $morewhere;
8512
		}
8513
		if (isset($objectline->fields['position'])) {
8514
			$sql .= $this->db->order('position', 'ASC');
8515
		}
8516
8517
		$resql = $this->db->query($sql);
8518
		if ($resql) {
8519
			$num_rows = $this->db->num_rows($resql);
8520
			$i = 0;
8521
			while ($i < $num_rows) {
8522
				$obj = $this->db->fetch_object($resql);
8523
				if ($obj) {
8524
					$newline = new $objectlineclassname($this->db);
8525
					$newline->setVarsFromFetchObj($obj);
8526
8527
					$this->lines[] = $newline;
8528
				}
8529
				$i++;
8530
			}
8531
8532
			return 1;
8533
		} else {
8534
			$this->error = $this->db->lasterror();
8535
			$this->errors[] = $this->error;
8536
			return -1;
8537
		}
8538
	}
8539
8540
	/**
8541
	 * Update object into database
8542
	 *
8543
	 * @param  User $user      	User that modifies
8544
	 * @param  bool $notrigger 	false=launch triggers after, true=disable triggers
8545
	 * @return int             	<0 if KO, >0 if OK
8546
	 */
8547
	public function updateCommon(User $user, $notrigger = false)
8548
	{
8549
		global $conf, $langs;
8550
		dol_syslog(get_class($this)."::updateCommon update", LOG_DEBUG);
8551
8552
		$error = 0;
8553
8554
		$now = dol_now();
8555
8556
		$fieldvalues = $this->setSaveQuery();
8557
8558
		if (array_key_exists('date_modification', $fieldvalues) && empty($fieldvalues['date_modification'])) {
8559
			$fieldvalues['date_modification'] = $this->db->idate($now);
8560
		}
8561
		if (array_key_exists('fk_user_modif', $fieldvalues) && !($fieldvalues['fk_user_modif'] > 0)) {
8562
			$fieldvalues['fk_user_modif'] = $user->id;
8563
		}
8564
		unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into update.
8565
		if (array_key_exists('ref', $fieldvalues)) {
8566
			$fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
8567
		}
8568
8569
		// Add quotes and escape on fields with type string
8570
		$keys = array();
8571
		$values = array();
8572
		$tmp = array();
8573
		foreach ($fieldvalues as $k => $v) {
8574
			$keys[$k] = $k;
8575
			$value = $this->fields[$k];
8576
			$values[$k] = $this->quote($v, $value);
8577
			$tmp[] = $k.'='.$this->quote($v, $this->fields[$k]);
8578
		}
8579
8580
		// Clean and check mandatory fields
8581
		foreach ($keys as $key) {
8582
			if (preg_match('/^integer:/i', $this->fields[$key]['type']) && $values[$key] == '-1') {
8583
				$values[$key] = ''; // This is an implicit foreign key field
8584
			}
8585
			if (!empty($this->fields[$key]['foreignkey']) && $values[$key] == '-1') {
8586
				$values[$key] = ''; // This is an explicit foreign key field
8587
			}
8588
8589
			//var_dump($key.'-'.$values[$key].'-'.($this->fields[$key]['notnull'] == 1));
8590
			/*
8591
			if ($this->fields[$key]['notnull'] == 1 && empty($values[$key]))
8592
			{
8593
				$error++;
8594
				$this->errors[]=$langs->trans("ErrorFieldRequired", $this->fields[$key]['label']);
8595
			}*/
8596
		}
8597
8598
		$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element.' SET '.implode(', ', $tmp).' WHERE rowid='.$this->id;
8599
8600
		$this->db->begin();
8601
		if (!$error) {
8602
			$res = $this->db->query($sql);
8603
			if ($res === false) {
0 ignored issues
show
introduced by
The condition $res === false is always false.
Loading history...
8604
				$error++;
8605
				$this->errors[] = $this->db->lasterror();
8606
			}
8607
		}
8608
8609
		// Update extrafield
8610
		if (!$error) {
8611
			$result = $this->insertExtraFields();
8612
			if ($result < 0) {
8613
				$error++;
8614
			}
8615
		}
8616
8617
		// Triggers
8618
		if (!$error && !$notrigger) {
8619
			// Call triggers
8620
			$result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $user);
8621
			if ($result < 0) {
8622
				$error++;
8623
			} //Do also here what you must do to rollback action if trigger fail
8624
			// End call triggers
8625
		}
8626
8627
		// Commit or rollback
8628
		if ($error) {
8629
			$this->db->rollback();
8630
			return -1;
8631
		} else {
8632
			$this->db->commit();
8633
			return $this->id;
8634
		}
8635
	}
8636
8637
	/**
8638
	 * Delete object in database
8639
	 *
8640
	 * @param 	User 	$user       			User that deletes
8641
	 * @param 	bool 	$notrigger  			false=launch triggers after, true=disable triggers
8642
	 * @param	int		$forcechilddeletion		0=no, 1=Force deletion of children
8643
	 * @return 	int             				<=0 if KO, >0 if OK
8644
	 */
8645
	public function deleteCommon(User $user, $notrigger = false, $forcechilddeletion = 0)
8646
	{
8647
		dol_syslog(get_class($this)."::deleteCommon delete", LOG_DEBUG);
8648
8649
		$error = 0;
8650
8651
		$this->db->begin();
8652
8653
		if ($forcechilddeletion) {	// Force also delete of childtables that should lock deletion in standard case when option force is off
8654
			foreach ($this->childtables as $table) {
8655
				$sql = 'DELETE FROM '.MAIN_DB_PREFIX.$table.' WHERE '.$this->fk_element.' = '.$this->id;
8656
				$resql = $this->db->query($sql);
8657
				if (!$resql) {
8658
					$this->error = $this->db->lasterror();
8659
					$this->errors[] = $this->error;
8660
					$this->db->rollback();
8661
					return -1;
8662
				}
8663
			}
8664
		} elseif (!empty($this->fk_element) && !empty($this->childtables)) {	// If object has childs linked with a foreign key field, we check all child tables.
8665
			$objectisused = $this->isObjectUsed($this->id);
8666
			if (!empty($objectisused)) {
8667
				dol_syslog(get_class($this)."::deleteCommon Can't delete record as it has some child", LOG_WARNING);
8668
				$this->error = 'ErrorRecordHasChildren';
8669
				$this->errors[] = $this->error;
8670
				$this->db->rollback();
8671
				return 0;
8672
			}
8673
		}
8674
8675
		// Delete cascade first
8676
		if (is_array($this->childtablesoncascade) && !empty($this->childtablesoncascade)) {
8677
			foreach ($this->childtablesoncascade as $table) {
8678
				$deleteFromObject = explode(':', $table);
8679
				if (count($deleteFromObject) >= 2) {
8680
					$className = str_replace('@', '', $deleteFromObject[0]);
8681
					$filePath = $deleteFromObject[1];
8682
					$columnName = $deleteFromObject[2];
8683
					if (dol_include_once($filePath)) {
8684
						$childObject = new $className($this->db);
8685
						if (method_exists($childObject, 'deleteByParentField')) {
8686
							$result = $childObject->deleteByParentField($this->id, $columnName);
8687
							if ($result < 0) {
8688
								$error++;
8689
								$this->errors[] = $childObject->error;
8690
								break;
8691
							}
8692
						} else {
8693
							$error++;
8694
							$this->errors[] = "You defined a cascade delete on an object $childObject but there is no method deleteByParentField for it";
8695
							break;
8696
						}
8697
					} else {
8698
						$error++;
8699
						$this->errors[] = 'Cannot include child class file '.$filePath;
8700
						break;
8701
					}
8702
				} else {
8703
					// Delete record in child table
8704
					$sql = 'DELETE FROM '.MAIN_DB_PREFIX.$table.' WHERE '.$this->fk_element.' = '.$this->id;
8705
8706
					$resql = $this->db->query($sql);
8707
					if (!$resql) {
8708
						$error++;
8709
						$this->error = $this->db->lasterror();
8710
						$this->errors[] = $this->error;
8711
						break;
8712
					}
8713
				}
8714
			}
8715
		}
8716
8717
		if (!$error) {
8718
			if (!$notrigger) {
8719
				// Call triggers
8720
				$result = $this->call_trigger(strtoupper(get_class($this)).'_DELETE', $user);
8721
				if ($result < 0) {
8722
					$error++;
8723
				} // Do also here what you must do to rollback action if trigger fail
8724
				// End call triggers
8725
			}
8726
		}
8727
8728
		// Delete llx_ecm_files
8729
		if (!$error) {
8730
			$res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
8731
			if (!$res) {
0 ignored issues
show
introduced by
The condition $res is always false.
Loading history...
8732
				$error++;
8733
			}
8734
		}
8735
8736
		if (!$error && !empty($this->isextrafieldmanaged)) {
8737
			$result = $this->deleteExtraFields();
8738
			if ($result < 0) {
8739
				$error++;
8740
			}
8741
		}
8742
8743
		if (!$error) {
8744
			$sql = 'DELETE FROM '.MAIN_DB_PREFIX.$this->table_element.' WHERE rowid='.$this->id;
8745
8746
			$res = $this->db->query($sql);
8747
			if ($res === false) {
0 ignored issues
show
introduced by
The condition $res === false is always false.
Loading history...
8748
				$error++;
8749
				$this->errors[] = $this->db->lasterror();
8750
			}
8751
		}
8752
8753
		// Commit or rollback
8754
		if ($error) {
8755
			$this->db->rollback();
8756
			return -1;
8757
		} else {
8758
			$this->db->commit();
8759
			return 1;
8760
		}
8761
	}
8762
8763
	/**
8764
	 * Delete all child object from a parent ID
8765
	 *
8766
	 * @param		int		$parentId      Parent Id
8767
	 * @param		string	$parentField   Name of Foreign key parent column
8768
	 * @return		int						<0 if KO, >0 if OK
8769
	 * @throws Exception
8770
	 */
8771
	public function deleteByParentField($parentId = 0, $parentField = '')
8772
	{
8773
		global $user;
8774
8775
		$error = 0;
8776
		$deleted = 0;
8777
8778
		if (!empty($parentId) && !empty($parentField)) {
8779
			$this->db->begin();
8780
8781
			$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX.$this->table_element;
8782
			$sql .= ' WHERE '.$parentField.' = '.(int) $parentId;
8783
8784
			$resql = $this->db->query($sql);
8785
			if (!$resql) {
8786
				$this->errors[] = $this->db->lasterror();
8787
				$error++;
8788
			} else {
8789
				while ($obj = $this->db->fetch_object($resql)) {
8790
					$result = $this->fetch($obj->rowid);
8791
					if ($result < 0) {
8792
						$error++;
8793
						$this->errors[] = $this->error;
8794
					} else {
8795
						if (get_class($this) == 'Contact') { // TODO special code because delete() for contact has not been standardized like other delete.
8796
							$result = $this->delete();
8797
						} else {
8798
							$result = $this->delete($user);
8799
						}
8800
						if ($result < 0) {
8801
							$error++;
8802
							$this->errors[] = $this->error;
8803
						} else {
8804
							$deleted++;
8805
						}
8806
					}
8807
				}
8808
			}
8809
8810
			if (empty($error)) {
8811
				$this->db->commit();
8812
				return $deleted;
8813
			} else {
8814
				$this->error = implode(', ', $this->errors);
8815
				$this->db->rollback();
8816
				return $error * -1;
8817
			}
8818
		}
8819
8820
		return $deleted;
8821
	}
8822
8823
	/**
8824
	 *  Delete a line of object in database
8825
	 *
8826
	 *	@param  User	$user       User that delete
8827
	 *  @param	int		$idline		Id of line to delete
8828
	 *  @param 	bool 	$notrigger  false=launch triggers after, true=disable triggers
8829
	 *  @return int         		>0 if OK, <0 if KO
8830
	 */
8831
	public function deleteLineCommon(User $user, $idline, $notrigger = false)
8832
	{
8833
		global $conf;
8834
8835
		$error = 0;
8836
8837
		$tmpforobjectclass = get_class($this);
8838
		$tmpforobjectlineclass = ucfirst($tmpforobjectclass).'Line';
8839
8840
		// Call trigger
8841
		$result = $this->call_trigger('LINE'.strtoupper($tmpforobjectclass).'_DELETE', $user);
8842
		if ($result < 0) {
8843
			return -1;
8844
		}
8845
		// End call triggers
8846
8847
		$this->db->begin();
8848
8849
		$sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element_line;
8850
		$sql .= " WHERE rowid=".$idline;
8851
8852
		dol_syslog(get_class($this)."::deleteLineCommon", LOG_DEBUG);
8853
		$resql = $this->db->query($sql);
8854
		if (!$resql) {
8855
			$this->error = "Error ".$this->db->lasterror();
8856
			$error++;
8857
		}
8858
8859
		if (empty($error)) {
8860
			// Remove extrafields
8861
			$tmpobjectline = new $tmpforobjectlineclass($this->db);
8862
			if (!isset($tmpobjectline->isextrafieldmanaged) || !empty($tmpobjectline->isextrafieldmanaged)) {
8863
				$tmpobjectline->id = $idline;
8864
				$result = $tmpobjectline->deleteExtraFields();
8865
				if ($result < 0) {
8866
					$error++;
8867
					$this->error = "Error ".get_class($this)."::deleteLineCommon deleteExtraFields error -4 ".$tmpobjectline->error;
8868
				}
8869
			}
8870
		}
8871
8872
		if (empty($error)) {
8873
			$this->db->commit();
8874
			return 1;
8875
		} else {
8876
			dol_syslog(get_class($this)."::deleteLineCommon ERROR:".$this->error, LOG_ERR);
8877
			$this->db->rollback();
8878
			return -1;
8879
		}
8880
	}
8881
8882
8883
	/**
8884
	 *	Set to a status
8885
	 *
8886
	 *	@param	User	$user			Object user that modify
8887
	 *  @param	int		$status			New status to set (often a constant like self::STATUS_XXX)
8888
	 *  @param	int		$notrigger		1=Does not execute triggers, 0=Execute triggers
8889
	 *  @param  string  $triggercode    Trigger code to use
8890
	 *	@return	int						<0 if KO, >0 if OK
8891
	 */
8892
	public function setStatusCommon($user, $status, $notrigger = 0, $triggercode = '')
8893
	{
8894
		$error = 0;
8895
8896
		$this->db->begin();
8897
8898
		$statusfield = 'status';
8899
		if ($this->element == 'don' || $this->element == 'donation') {
8900
			$statusfield = 'fk_statut';
8901
		}
8902
8903
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
8904
		$sql .= " SET ".$statusfield." = ".((int) $status);
8905
		$sql .= " WHERE rowid = ".$this->id;
8906
8907
		if ($this->db->query($sql)) {
8908
			if (!$error) {
8909
				$this->oldcopy = clone $this;
8910
			}
8911
8912
			if (!$error && !$notrigger) {
8913
				// Call trigger
8914
				$result = $this->call_trigger($triggercode, $user);
8915
				if ($result < 0) {
8916
					$error++;
8917
				}
8918
			}
8919
8920
			if (!$error) {
8921
				$this->status = $status;
8922
				$this->db->commit();
8923
				return 1;
8924
			} else {
8925
				$this->db->rollback();
8926
				return -1;
8927
			}
8928
		} else {
8929
			$this->error = $this->db->error();
8930
			$this->db->rollback();
8931
			return -1;
8932
		}
8933
	}
8934
8935
8936
	/**
8937
	 * Initialise object with example values
8938
	 * Id must be 0 if object instance is a specimen
8939
	 *
8940
	 * @return int
8941
	 */
8942
	public function initAsSpecimenCommon()
8943
	{
8944
		global $user;
8945
8946
		$this->id = 0;
8947
		$this->specimen = 1;
8948
		$fields = array(
8949
			'label' => 'This is label',
8950
			'ref' => 'ABCD1234',
8951
			'description' => 'This is a description',
8952
			'qty' => 123.12,
8953
			'note_public' => 'Public note',
8954
			'note_private' => 'Private note',
8955
			'date_creation' => (dol_now() - 3600 * 48),
8956
			'date_modification' => (dol_now() - 3600 * 24),
8957
			'fk_user_creat' => $user->id,
8958
			'fk_user_modif' => $user->id,
8959
			'date' => dol_now(),
8960
		);
8961
		foreach ($fields as $key => $value) {
8962
			if (array_key_exists($key, $this->fields)) {
8963
				$this->{$key} = $value;
8964
			}
8965
		}
8966
		return 1;
8967
	}
8968
8969
8970
	/* Part for comments */
8971
8972
	/**
8973
	 * Load comments linked with current task
8974
	 *	@return boolean	1 if ok
8975
	 */
8976
	public function fetchComments()
8977
	{
8978
		require_once DOL_DOCUMENT_ROOT.'/core/class/comment.class.php';
8979
8980
		$comment = new Comment($this->db);
8981
		$result = $comment->fetchAllFor($this->element, $this->id);
8982
		if ($result < 0) {
8983
			$this->errors = array_merge($this->errors, $comment->errors);
8984
			return -1;
8985
		} else {
8986
			$this->comments = $comment->comments;
8987
		}
8988
		return count($this->comments);
8989
	}
8990
8991
	/**
8992
	 * Return nb comments already posted
8993
	 *
8994
	 * @return int
8995
	 */
8996
	public function getNbComments()
8997
	{
8998
		return count($this->comments);
8999
	}
9000
9001
	/**
9002
	 * Trim object parameters
9003
	 *
9004
	 * @param string[] $parameters array of parameters to trim
9005
	 * @return void
9006
	 */
9007
	public function trimParameters($parameters)
9008
	{
9009
		if (!is_array($parameters)) {
9010
			return;
9011
		}
9012
		foreach ($parameters as $parameter) {
9013
			if (isset($this->$parameter)) {
9014
				$this->$parameter = trim($this->$parameter);
9015
			}
9016
		}
9017
	}
9018
9019
	/* Part for categories/tags */
9020
9021
	/**
9022
	 * Sets object to given categories.
9023
	 *
9024
	 * Deletes object from existing categories not supplied.
9025
	 * Adds it to non existing supplied categories.
9026
	 * Existing categories are left untouch.
9027
	 *
9028
	 * @param 	string 		$type_categ 	Category type ('customer', 'supplier', 'website_page', ...)
9029
	 * @return	int							Array of category objects or < 0 if KO
9030
	 */
9031
	public function getCategoriesCommon($type_categ)
9032
	{
9033
		require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
9034
9035
		// Get current categories
9036
		$c = new Categorie($this->db);
9037
		$existing = $c->containing($this->id, $type_categ, 'id');
9038
9039
		return $existing;
9040
	}
9041
9042
	/**
9043
	 * Sets object to given categories.
9044
	 *
9045
	 * Adds it to non existing supplied categories.
9046
	 * Deletes object from existing categories not supplied (if remove_existing==true).
9047
	 * Existing categories are left untouch.
9048
	 *
9049
	 * @param 	int[]|int 	$categories 		Category ID or array of Categories IDs
9050
	 * @param 	string 		$type_categ 		Category type ('customer', 'supplier', 'website_page', ...) definied into const class Categorie type
9051
	 * @param 	boolean		$remove_existing 	True: Remove existings categories from Object if not supplies by $categories, False: let them
9052
	 * @return	int							<0 if KO, >0 if OK
9053
	 */
9054
	public function setCategoriesCommon($categories, $type_categ = '', $remove_existing = true)
9055
	{
9056
		dol_syslog(get_class($this)."::setCategoriesCommon Oject Id:".$this->id.' type_categ:'.$type_categ.' nb tag add:'.count($categories), LOG_DEBUG);
9057
9058
		require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
9059
9060
		if (empty($type_categ)) {
9061
			dol_syslog(__METHOD__.': Type '.$type_categ.'is an unknown category type. Done nothing.', LOG_ERR);
9062
			return -1;
9063
		}
9064
9065
		// Handle single category
9066
		if (!is_array($categories)) {
9067
			$categories = array($categories);
9068
		}
9069
9070
		// Get current categories
9071
		$c = new Categorie($this->db);
9072
		$existing = $c->containing($this->id, $type_categ, 'id');
9073
		if ($remove_existing) {
9074
			// Diff
9075
			if (is_array($existing)) {
0 ignored issues
show
introduced by
The condition is_array($existing) is always false.
Loading history...
9076
				$to_del = array_diff($existing, $categories);
9077
				$to_add = array_diff($categories, $existing);
9078
			} else {
9079
				$to_del = array(); // Nothing to delete
9080
				$to_add = $categories;
9081
			}
9082
		} else {
9083
			$to_del = array(); // Nothing to delete
9084
			$to_add = array_diff($categories, $existing);
9085
		}
9086
9087
		$error = 0;
9088
		$ok=0;
9089
9090
		// Process
9091
		foreach ($to_del as $del) {
9092
			if ($c->fetch($del) > 0) {
9093
				$result=$c->del_type($this, $type_categ);
9094
				if ($result < 0) {
9095
					$error++;
9096
					$this->error = $c->error;
9097
					$this->errors = $c->errors;
9098
					break;
9099
				} else {
9100
					$ok+=$result;
9101
				}
9102
			}
9103
		}
9104
		foreach ($to_add as $add) {
9105
			if ($c->fetch($add) > 0) {
9106
				$result = $c->add_type($this, $type_categ);
9107
				if ($result < 0) {
9108
					$error++;
9109
					$this->error = $c->error;
9110
					$this->errors = $c->errors;
9111
					break;
9112
				} else {
9113
					$ok+=$result;
9114
				}
9115
			}
9116
		}
9117
9118
		return $error ? -1 * $error : $ok;
9119
	}
9120
9121
	/**
9122
	 * Copy related categories to another object
9123
	 *
9124
	 * @param  int		$fromId	Id object source
9125
	 * @param  int		$toId	Id object cible
9126
	 * @param  string	$type	Type of category ('product', ...)
9127
	 * @return int      < 0 if error, > 0 if ok
9128
	 */
9129
	public function cloneCategories($fromId, $toId, $type = '')
9130
	{
9131
		$this->db->begin();
9132
9133
		if (empty($type)) {
9134
			$type = $this->table_element;
9135
		}
9136
9137
		require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
9138
		$categorystatic = new Categorie($this->db);
9139
9140
		$sql = "INSERT INTO ".MAIN_DB_PREFIX."categorie_".(empty($categorystatic->MAP_CAT_TABLE[$type]) ? $type : $categorystatic->MAP_CAT_TABLE[$type])." (fk_categorie, fk_product)";
0 ignored issues
show
Bug introduced by
The property MAP_CAT_TABLE is declared protected in Categorie and cannot be accessed from this context.
Loading history...
9141
		$sql .= " SELECT fk_categorie, $toId FROM ".MAIN_DB_PREFIX."categorie_".(empty($categorystatic->MAP_CAT_TABLE[$type]) ? $type : $categorystatic->MAP_CAT_TABLE[$type]);
9142
		$sql .= " WHERE fk_product = ".((int) $fromId);
9143
9144
		if (!$this->db->query($sql)) {
9145
			$this->error = $this->db->lasterror();
9146
			$this->db->rollback();
9147
			return -1;
9148
		}
9149
9150
		$this->db->commit();
9151
		return 1;
9152
	}
9153
9154
	/**
9155
	 * Delete related files of object in database
9156
	 *
9157
	 * @param	integer		$mode		0=Use path to find record, 1=Use src_object_xxx fields (Mode 1 is recommanded for new objects)
9158
	 * @return 	bool					True if OK, False if KO
9159
	 */
9160
	public function deleteEcmFiles($mode = 0)
9161
	{
9162
		global $conf;
9163
9164
		$this->db->begin();
9165
9166
		// Delete in database with mode 0
9167
		if ($mode == 0) {
9168
			switch ($this->element) {
9169
				case 'propal':
9170
					$element = 'propale';
9171
					break;
9172
				case 'product':
9173
					$element = 'produit';
9174
					break;
9175
				case 'order_supplier':
9176
					$element = 'fournisseur/commande';
9177
					break;
9178
				case 'invoice_supplier':
9179
					$element = 'fournisseur/facture/'.get_exdir($this->id, 2, 0, 1, $this, 'invoice_supplier');
9180
					break;
9181
				case 'shipping':
9182
					$element = 'expedition/sending';
9183
					break;
9184
				default:
9185
					$element = $this->element;
9186
			}
9187
9188
			// Delete ecm_files extrafields
9189
			$sql = "DELETE FROM ".MAIN_DB_PREFIX."ecm_files_extrafields WHERE fk_object IN (";
9190
			$sql .= " SELECT rowid FROM ".MAIN_DB_PREFIX."ecm_files WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
9191
			$sql .= " AND filepath = '".$this->db->escape($element)."/".$this->db->escape($this->ref)."' AND entity = ".$conf->entity; // No need of getEntity here
9192
			$sql .= ")";
9193
9194
			if (!$this->db->query($sql)) {
9195
				$this->error = $this->db->lasterror();
9196
				$this->db->rollback();
9197
				return false;
9198
			}
9199
9200
			// Delete ecm_files
9201
			$sql = "DELETE FROM ".MAIN_DB_PREFIX."ecm_files";
9202
			$sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
9203
			$sql .= " AND filepath = '".$this->db->escape($element)."/".$this->db->escape($this->ref)."' AND entity = ".$conf->entity; // No need of getEntity here
9204
9205
			if (!$this->db->query($sql)) {
9206
				$this->error = $this->db->lasterror();
9207
				$this->db->rollback();
9208
				return false;
9209
			}
9210
		}
9211
9212
		// Delete in database with mode 1
9213
		if ($mode == 1) {
9214
			$sql = 'DELETE FROM '.MAIN_DB_PREFIX."ecm_files_extrafields";
9215
			$sql .= " WHERE fk_object IN (SELECT rowid FROM ".MAIN_DB_PREFIX."ecm_files WHERE src_object_type = '".$this->db->escape($this->table_element.(empty($this->module) ? '' : '@'.$this->module))."' AND src_object_id = ".$this->id.")";
9216
			$resql = $this->db->query($sql);
9217
			if (!$resql) {
9218
				$this->error = $this->db->lasterror();
9219
				$this->db->rollback();
9220
				return false;
9221
			}
9222
9223
			$sql = 'DELETE FROM '.MAIN_DB_PREFIX."ecm_files";
9224
			$sql .= " WHERE src_object_type = '".$this->db->escape($this->table_element.(empty($this->module) ? '' : '@'.$this->module))."' AND src_object_id = ".$this->id;
9225
			$resql = $this->db->query($sql);
9226
			if (!$resql) {
9227
				$this->error = $this->db->lasterror();
9228
				$this->db->rollback();
9229
				return false;
9230
			}
9231
		}
9232
9233
		$this->db->commit();
9234
		return true;
9235
	}
9236
}
9237