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

CommonObject::setPaymentMethods()   C

Complexity

Conditions 11
Paths 193

Size

Total Lines 39
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 27
nc 193
nop 1
dl 0
loc 39
rs 6.5416
c 0
b 0
f 0

How to fix   Complexity   

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:

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