Passed
Branch develop (96ac0e)
by
unknown
23:19
created

CommonObject::isExistingObject()   B

Complexity

Conditions 8
Paths 19

Size

Total Lines 34
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

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

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

2381
								$this->/** @scrutinizer ignore-call */ 
2382
               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...
2382
									$line->id,
2383
									$line->subprice,
2384
									$line->qty,
2385
									$line->remise_percent,
2386
									$line->tva_tx,
2387
									$line->localtax1_tx,
2388
									$line->localtax2_tx,
2389
									($line->description ? $line->description : $line->desc),
2390
									'HT',
2391
									$line->info_bits,
2392
									$line->special_code,
2393
									$line->fk_parent_line,
2394
									$line->skip_update_total,
2395
									$line->fk_fournprice,
2396
									$line->pa_ht,
2397
									$line->label,
2398
									$line->product_type,
2399
									$line->date_start,
2400
									$line->date_end,
2401
									$line->array_options,
2402
									$line->fk_unit,
2403
									$line->multicurrency_subprice
2404
								);
2405
								break;
2406
							case 'commande':
2407
								$this->updateline(
2408
									$line->id,
2409
									($line->description ? $line->description : $line->desc),
2410
									$line->subprice,
2411
									$line->qty,
2412
									$line->remise_percent,
2413
									$line->tva_tx,
2414
									$line->localtax1_tx,
2415
									$line->localtax2_tx,
2416
									'HT',
2417
									$line->info_bits,
2418
									$line->date_start,
2419
									$line->date_end,
2420
									$line->product_type,
2421
									$line->fk_parent_line,
2422
									$line->skip_update_total,
2423
									$line->fk_fournprice,
2424
									$line->pa_ht,
2425
									$line->label,
2426
									$line->special_code,
2427
									$line->array_options,
2428
									$line->fk_unit,
2429
									$line->multicurrency_subprice
2430
								);
2431
								break;
2432
							case 'facture':
2433
								$this->updateline(
2434
									$line->id,
2435
									($line->description ? $line->description : $line->desc),
2436
									$line->subprice,
2437
									$line->qty,
2438
									$line->remise_percent,
2439
									$line->date_start,
2440
									$line->date_end,
2441
									$line->tva_tx,
2442
									$line->localtax1_tx,
2443
									$line->localtax2_tx,
2444
									'HT',
2445
									$line->info_bits,
2446
									$line->product_type,
2447
									$line->fk_parent_line,
2448
									$line->skip_update_total,
2449
									$line->fk_fournprice,
2450
									$line->pa_ht,
2451
									$line->label,
2452
									$line->special_code,
2453
									$line->array_options,
2454
									$line->situation_percent,
2455
									$line->fk_unit,
2456
									$line->multicurrency_subprice
2457
								);
2458
								break;
2459
							case 'supplier_proposal':
2460
								$this->updateline(
2461
									$line->id,
2462
									$line->subprice,
2463
									$line->qty,
2464
									$line->remise_percent,
2465
									$line->tva_tx,
2466
									$line->localtax1_tx,
2467
									$line->localtax2_tx,
2468
									($line->description ? $line->description : $line->desc),
2469
									'HT',
2470
									$line->info_bits,
2471
									$line->special_code,
2472
									$line->fk_parent_line,
2473
									$line->skip_update_total,
2474
									$line->fk_fournprice,
2475
									$line->pa_ht,
2476
									$line->label,
2477
									$line->product_type,
2478
									$line->array_options,
2479
									$line->ref_fourn,
2480
									$line->multicurrency_subprice
2481
								);
2482
								break;
2483
							case 'order_supplier':
2484
								$this->updateline(
2485
									$line->id,
2486
									($line->description ? $line->description : $line->desc),
2487
									$line->subprice,
2488
									$line->qty,
2489
									$line->remise_percent,
2490
									$line->tva_tx,
2491
									$line->localtax1_tx,
2492
									$line->localtax2_tx,
2493
									'HT',
2494
									$line->info_bits,
2495
									$line->product_type,
2496
									false,
2497
									$line->date_start,
2498
									$line->date_end,
2499
									$line->array_options,
2500
									$line->fk_unit,
2501
									$line->multicurrency_subprice,
2502
									$line->ref_supplier
2503
								);
2504
								break;
2505
							case 'invoice_supplier':
2506
								$this->updateline(
2507
									$line->id,
2508
									($line->description ? $line->description : $line->desc),
2509
									$line->subprice,
2510
									$line->tva_tx,
2511
									$line->localtax1_tx,
2512
									$line->localtax2_tx,
2513
									$line->qty,
2514
									0,
2515
									'HT',
2516
									$line->info_bits,
2517
									$line->product_type,
2518
									$line->remise_percent,
2519
									false,
2520
									$line->date_start,
2521
									$line->date_end,
2522
									$line->array_options,
2523
									$line->fk_unit,
2524
									$line->multicurrency_subprice,
2525
									$line->ref_supplier
2526
								);
2527
								break;
2528
							default:
2529
								dol_syslog(get_class($this).'::setMulticurrencyRate no updateline defined', LOG_DEBUG);
2530
								break;
2531
						}
2532
					}
2533
				}
2534
2535
				return 1;
2536
			} else {
2537
				dol_syslog(get_class($this).'::setMulticurrencyRate Error '.$sql.' - '.$this->db->error());
2538
				$this->error = $this->db->error();
2539
				return -1;
2540
			}
2541
		} else {
2542
			dol_syslog(get_class($this).'::setMulticurrencyRate, status of the object is incompatible');
2543
			$this->error = 'Status of the object is incompatible '.$this->statut;
2544
			return -2;
2545
		}
2546
	}
2547
2548
	/**
2549
	 *  Change the payments terms
2550
	 *
2551
	 *  @param		int		$id		Id of new payment terms
2552
	 *  @return		int				>0 if OK, <0 if KO
2553
	 */
2554
	public function setPaymentTerms($id)
2555
	{
2556
		dol_syslog(get_class($this).'::setPaymentTerms('.$id.')');
2557
		if ($this->statut >= 0 || $this->element == 'societe') {
2558
			// TODO uniformize field name
2559
			$fieldname = 'fk_cond_reglement';
2560
			if ($this->element == 'societe') {
2561
				$fieldname = 'cond_reglement';
2562
			}
2563
			if (get_class($this) == 'Fournisseur') {
2564
				$fieldname = 'cond_reglement_supplier';
2565
			}
2566
2567
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2568
			$sql .= " SET ".$fieldname." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
2569
			$sql .= ' WHERE rowid='.((int) $this->id);
2570
2571
			if ($this->db->query($sql)) {
2572
				$this->cond_reglement_id = $id;
2573
				// for supplier
2574
				if (get_class($this) == 'Fournisseur') {
2575
					$this->cond_reglement_supplier_id = $id;
2576
				}
2577
				$this->cond_reglement = $id; // for compatibility
2578
				return 1;
2579
			} else {
2580
				dol_syslog(get_class($this).'::setPaymentTerms Error '.$sql.' - '.$this->db->error());
2581
				$this->error = $this->db->error();
2582
				return -1;
2583
			}
2584
		} else {
2585
			dol_syslog(get_class($this).'::setPaymentTerms, status of the object is incompatible');
2586
			$this->error = 'Status of the object is incompatible '.$this->statut;
2587
			return -2;
2588
		}
2589
	}
2590
2591
	/**
2592
	 *  Change the transport mode methods
2593
	 *
2594
	 *  @param		int		$id		Id of transport mode
2595
	 *  @return		int				>0 if OK, <0 if KO
2596
	 */
2597
	public function setTransportMode($id)
2598
	{
2599
		dol_syslog(get_class($this).'::setTransportMode('.$id.')');
2600
		if ($this->statut >= 0 || $this->element == 'societe') {
2601
			$fieldname = 'fk_transport_mode';
2602
			if ($this->element == 'societe') {
2603
				$fieldname = 'transport_mode';
2604
			}
2605
			if (get_class($this) == 'Fournisseur') {
2606
				$fieldname = 'transport_mode_supplier';
2607
			}
2608
2609
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2610
			$sql .= " SET ".$fieldname." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
2611
			$sql .= ' WHERE rowid='.((int) $this->id);
2612
2613
			if ($this->db->query($sql)) {
2614
				$this->transport_mode_id = $id;
2615
				// for supplier
2616
				if (get_class($this) == 'Fournisseur') {
2617
					$this->transport_mode_supplier_id = $id;
2618
				}
2619
				return 1;
2620
			} else {
2621
				dol_syslog(get_class($this).'::setTransportMode Error '.$sql.' - '.$this->db->error());
2622
				$this->error = $this->db->error();
2623
				return -1;
2624
			}
2625
		} else {
2626
			dol_syslog(get_class($this).'::setTransportMode, status of the object is incompatible');
2627
			$this->error = 'Status of the object is incompatible '.$this->statut;
2628
			return -2;
2629
		}
2630
	}
2631
2632
	/**
2633
	 *  Change the retained warranty payments terms
2634
	 *
2635
	 *  @param		int		$id		Id of new payment terms
2636
	 *  @return		int				>0 if OK, <0 if KO
2637
	 */
2638
	public function setRetainedWarrantyPaymentTerms($id)
2639
	{
2640
		dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms('.$id.')');
2641
		if ($this->statut >= 0 || $this->element == 'societe') {
2642
			$fieldname = 'retained_warranty_fk_cond_reglement';
2643
2644
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2645
			$sql .= " SET ".$fieldname." = ".((int) $id);
2646
			$sql .= ' WHERE rowid='.((int) $this->id);
2647
2648
			if ($this->db->query($sql)) {
2649
				$this->retained_warranty_fk_cond_reglement = $id;
2650
				return 1;
2651
			} else {
2652
				dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms Error '.$sql.' - '.$this->db->error());
2653
				$this->error = $this->db->error();
2654
				return -1;
2655
			}
2656
		} else {
2657
			dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms, status of the object is incompatible');
2658
			$this->error = 'Status of the object is incompatible '.$this->statut;
2659
			return -2;
2660
		}
2661
	}
2662
2663
	/**
2664
	 *	Define delivery address
2665
	 *  @deprecated
2666
	 *
2667
	 *	@param      int		$id		Address id
2668
	 *	@return     int				<0 si ko, >0 si ok
2669
	 */
2670
	public function setDeliveryAddress($id)
2671
	{
2672
		$fieldname = 'fk_delivery_address';
2673
		if ($this->element == 'delivery' || $this->element == 'shipping') {
2674
			$fieldname = 'fk_address';
2675
		}
2676
2677
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET ".$fieldname." = ".((int) $id);
2678
		$sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = 0";
2679
2680
		if ($this->db->query($sql)) {
2681
			$this->fk_delivery_address = $id;
2682
			return 1;
2683
		} else {
2684
			$this->error = $this->db->error();
2685
			dol_syslog(get_class($this).'::setDeliveryAddress Error '.$this->error);
2686
			return -1;
2687
		}
2688
	}
2689
2690
2691
	/**
2692
	 *  Change the shipping method
2693
	 *
2694
	 *  @param      int     $shipping_method_id     Id of shipping method
2695
	 *  @param      bool    $notrigger              false=launch triggers after, true=disable triggers
2696
	 *  @param      User	$userused               Object user
2697
	 *
2698
	 *  @return     int              1 if OK, 0 if KO
2699
	 */
2700
	public function setShippingMethod($shipping_method_id, $notrigger = false, $userused = null)
2701
	{
2702
		global $user;
2703
2704
		if (empty($userused)) {
2705
			$userused = $user;
2706
		}
2707
2708
		$error = 0;
2709
2710
		if (!$this->table_element) {
2711
			dol_syslog(get_class($this)."::setShippingMethod was called on objet with property table_element not defined", LOG_ERR);
2712
			return -1;
2713
		}
2714
2715
		$this->db->begin();
2716
2717
		if ($shipping_method_id < 0) {
2718
			$shipping_method_id = 'NULL';
2719
		}
2720
		dol_syslog(get_class($this).'::setShippingMethod('.$shipping_method_id.')');
2721
2722
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2723
		$sql .= " SET fk_shipping_method = ".((int) $shipping_method_id);
2724
		$sql .= " WHERE rowid=".((int) $this->id);
2725
		$resql = $this->db->query($sql);
2726
		if (!$resql) {
2727
			dol_syslog(get_class($this).'::setShippingMethod Error ', LOG_DEBUG);
2728
			$this->error = $this->db->lasterror();
2729
			$error++;
2730
		} else {
2731
			if (!$notrigger) {
2732
				// Call trigger
2733
				$this->context = array('shippingmethodupdate'=>1);
2734
				$result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $userused);
2735
				if ($result < 0) {
2736
					$error++;
2737
				}
2738
				// End call trigger
2739
			}
2740
		}
2741
		if ($error) {
2742
			$this->db->rollback();
2743
			return -1;
2744
		} else {
2745
			$this->shipping_method_id = ($shipping_method_id == 'NULL') ?null:$shipping_method_id;
2746
			$this->db->commit();
2747
			return 1;
2748
		}
2749
	}
2750
2751
2752
	/**
2753
	 *  Change the warehouse
2754
	 *
2755
	 *  @param      int     $warehouse_id     Id of warehouse
2756
	 *  @return     int              1 if OK, 0 if KO
2757
	 */
2758
	public function setWarehouse($warehouse_id)
2759
	{
2760
		if (!$this->table_element) {
2761
			dol_syslog(get_class($this)."::setWarehouse was called on objet with property table_element not defined", LOG_ERR);
2762
			return -1;
2763
		}
2764
		if ($warehouse_id < 0) {
2765
			$warehouse_id = 'NULL';
2766
		}
2767
		dol_syslog(get_class($this).'::setWarehouse('.$warehouse_id.')');
2768
2769
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2770
		$sql .= " SET fk_warehouse = ".((int) $warehouse_id);
2771
		$sql .= " WHERE rowid=".((int) $this->id);
2772
2773
		if ($this->db->query($sql)) {
2774
			$this->warehouse_id = ($warehouse_id == 'NULL') ?null:$warehouse_id;
2775
			return 1;
2776
		} else {
2777
			dol_syslog(get_class($this).'::setWarehouse Error ', LOG_DEBUG);
2778
			$this->error = $this->db->error();
2779
			return 0;
2780
		}
2781
	}
2782
2783
2784
	/**
2785
	 *		Set last model used by doc generator
2786
	 *
2787
	 *		@param		User	$user		User object that make change
2788
	 *		@param		string	$modelpdf	Modele name
2789
	 *		@return		int					<0 if KO, >0 if OK
2790
	 */
2791
	public function setDocModel($user, $modelpdf)
2792
	{
2793
		if (!$this->table_element) {
2794
			dol_syslog(get_class($this)."::setDocModel was called on objet with property table_element not defined", LOG_ERR);
2795
			return -1;
2796
		}
2797
2798
		$newmodelpdf = dol_trunc($modelpdf, 255);
2799
2800
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2801
		$sql .= " SET model_pdf = '".$this->db->escape($newmodelpdf)."'";
2802
		$sql .= " WHERE rowid = ".((int) $this->id);
2803
2804
		dol_syslog(get_class($this)."::setDocModel", LOG_DEBUG);
2805
		$resql = $this->db->query($sql);
2806
		if ($resql) {
2807
			$this->model_pdf = $modelpdf;
2808
			$this->modelpdf = $modelpdf; // For bakward compatibility
2809
			return 1;
2810
		} else {
2811
			dol_print_error($this->db);
2812
			return 0;
2813
		}
2814
	}
2815
2816
2817
	/**
2818
	 *  Change the bank account
2819
	 *
2820
	 *  @param		int		$fk_account		Id of bank account
2821
	 *  @param      bool    $notrigger      false=launch triggers after, true=disable triggers
2822
	 *  @param      User	$userused		Object user
2823
	 *  @return		int				1 if OK, 0 if KO
2824
	 */
2825
	public function setBankAccount($fk_account, $notrigger = false, $userused = null)
2826
	{
2827
		global $user;
2828
2829
		if (empty($userused)) {
2830
			$userused = $user;
2831
		}
2832
2833
		$error = 0;
2834
2835
		if (!$this->table_element) {
2836
			dol_syslog(get_class($this)."::setBankAccount was called on objet with property table_element not defined", LOG_ERR);
2837
			return -1;
2838
		}
2839
		$this->db->begin();
2840
2841
		if ($fk_account < 0) {
2842
			$fk_account = 'NULL';
2843
		}
2844
		dol_syslog(get_class($this).'::setBankAccount('.$fk_account.')');
2845
2846
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2847
		$sql .= " SET fk_account = ".((int) $fk_account);
2848
		$sql .= " WHERE rowid=".((int) $this->id);
2849
2850
		$resql = $this->db->query($sql);
2851
		if (!$resql) {
2852
			dol_syslog(get_class($this).'::setBankAccount Error '.$sql.' - '.$this->db->error());
2853
			$this->error = $this->db->lasterror();
2854
			$error++;
2855
		} else {
2856
			if (!$notrigger) {
2857
				// Call trigger
2858
				$this->context = array('bankaccountupdate'=>1);
2859
				$result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $userused);
2860
				if ($result < 0) {
2861
					$error++;
2862
				}
2863
				// End call trigger
2864
			}
2865
		}
2866
		if ($error) {
2867
			$this->db->rollback();
2868
			return -1;
2869
		} else {
2870
			$this->fk_account = ($fk_account == 'NULL') ?null:$fk_account;
2871
			$this->db->commit();
2872
			return 1;
2873
		}
2874
	}
2875
2876
2877
	// TODO: Move line related operations to CommonObjectLine?
2878
2879
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2880
	/**
2881
	 *  Save a new position (field rang) for details lines.
2882
	 *  You can choose to set position for lines with already a position or lines without any position defined.
2883
	 *
2884
	 * 	@param		boolean		$renum			   True to renum all already ordered lines, false to renum only not already ordered lines.
2885
	 * 	@param		string		$rowidorder		   ASC or DESC
2886
	 * 	@param		boolean		$fk_parent_line    Table with fk_parent_line field or not
2887
	 * 	@return		int                            <0 if KO, >0 if OK
2888
	 */
2889
	public function line_order($renum = false, $rowidorder = 'ASC', $fk_parent_line = true)
2890
	{
2891
		// phpcs:enable
2892
		if (!$this->table_element_line) {
2893
			dol_syslog(get_class($this)."::line_order was called on objet with property table_element_line not defined", LOG_ERR);
2894
			return -1;
2895
		}
2896
		if (!$this->fk_element) {
2897
			dol_syslog(get_class($this)."::line_order was called on objet with property fk_element not defined", LOG_ERR);
2898
			return -1;
2899
		}
2900
2901
		// Count number of lines to reorder (according to choice $renum)
2902
		$nl = 0;
2903
		$sql = 'SELECT count(rowid) FROM '.MAIN_DB_PREFIX.$this->table_element_line;
2904
		$sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
2905
		if (!$renum) {
2906
			$sql .= ' AND rang = 0';
2907
		}
2908
		if ($renum) {
2909
			$sql .= ' AND rang <> 0';
2910
		}
2911
2912
		dol_syslog(get_class($this)."::line_order", LOG_DEBUG);
2913
		$resql = $this->db->query($sql);
2914
		if ($resql) {
2915
			$row = $this->db->fetch_row($resql);
2916
			$nl = $row[0];
2917
		} else {
2918
			dol_print_error($this->db);
2919
		}
2920
		if ($nl > 0) {
2921
			// The goal of this part is to reorder all lines, with all children lines sharing the same counter that parents.
2922
			$rows = array();
2923
2924
			// We first search all lines that are parent lines (for multilevel details lines)
2925
			$sql = 'SELECT rowid FROM '.MAIN_DB_PREFIX.$this->table_element_line;
2926
			$sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
2927
			if ($fk_parent_line) {
2928
				$sql .= ' AND fk_parent_line IS NULL';
2929
			}
2930
			$sql .= " ORDER BY rang ASC, rowid ".$rowidorder;
2931
2932
			dol_syslog(get_class($this)."::line_order search all parent lines", LOG_DEBUG);
2933
			$resql = $this->db->query($sql);
2934
			if ($resql) {
2935
				$i = 0;
2936
				$num = $this->db->num_rows($resql);
2937
				while ($i < $num) {
2938
					$row = $this->db->fetch_row($resql);
2939
					$rows[] = $row[0]; // Add parent line into array rows
2940
					$childrens = $this->getChildrenOfLine($row[0]);
2941
					if (!empty($childrens)) {
2942
						foreach ($childrens as $child) {
2943
							array_push($rows, $child);
2944
						}
2945
					}
2946
					$i++;
2947
				}
2948
2949
				// Now we set a new number for each lines (parent and children with children included into parent tree)
2950
				if (!empty($rows)) {
2951
					foreach ($rows as $key => $row) {
2952
						$this->updateRangOfLine($row, ($key + 1));
2953
					}
2954
				}
2955
			} else {
2956
				dol_print_error($this->db);
2957
			}
2958
		}
2959
		return 1;
2960
	}
2961
2962
	/**
2963
	 * 	Get children of line
2964
	 *
2965
	 * 	@param	int		$id		            Id of parent line
2966
	 * 	@param	int		$includealltree		0 = 1st level child, 1 = All level child
2967
	 * 	@return	array			            Array with list of children lines id
2968
	 */
2969
	public function getChildrenOfLine($id, $includealltree = 0)
2970
	{
2971
		$rows = array();
2972
2973
		$sql = 'SELECT rowid FROM '.MAIN_DB_PREFIX.$this->table_element_line;
2974
		$sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
2975
		$sql .= ' AND fk_parent_line = '.((int) $id);
2976
		$sql .= ' ORDER BY rang ASC';
2977
2978
		dol_syslog(get_class($this)."::getChildrenOfLine search children lines for line ".$id, LOG_DEBUG);
2979
		$resql = $this->db->query($sql);
2980
		if ($resql) {
2981
			if ($this->db->num_rows($resql) > 0) {
2982
				while ($row = $this->db->fetch_row($resql)) {
2983
					$rows[] = $row[0];
2984
					if (!empty($includealltree)) {
2985
						$rows = array_merge($rows, $this->getChildrenOfLine($row[0]), $includealltree);
2986
					}
2987
				}
2988
			}
2989
		}
2990
		return $rows;
2991
	}
2992
2993
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2994
	/**
2995
	 * 	Update a line to have a lower rank
2996
	 *
2997
	 * 	@param 	int			$rowid				Id of line
2998
	 * 	@param	boolean		$fk_parent_line		Table with fk_parent_line field or not
2999
	 * 	@return	void
3000
	 */
3001
	public function line_up($rowid, $fk_parent_line = true)
3002
	{
3003
		// phpcs:enable
3004
		$this->line_order(false, 'ASC', $fk_parent_line);
3005
3006
		// Get rang of line
3007
		$rang = $this->getRangOfLine($rowid);
3008
3009
		// Update position of line
3010
		$this->updateLineUp($rowid, $rang);
3011
	}
3012
3013
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3014
	/**
3015
	 * 	Update a line to have a higher rank
3016
	 *
3017
	 * 	@param	int			$rowid				Id of line
3018
	 * 	@param	boolean		$fk_parent_line		Table with fk_parent_line field or not
3019
	 * 	@return	void
3020
	 */
3021
	public function line_down($rowid, $fk_parent_line = true)
3022
	{
3023
		// phpcs:enable
3024
		$this->line_order(false, 'ASC', $fk_parent_line);
3025
3026
		// Get rang of line
3027
		$rang = $this->getRangOfLine($rowid);
3028
3029
		// Get max value for rang
3030
		$max = $this->line_max();
3031
3032
		// Update position of line
3033
		$this->updateLineDown($rowid, $rang, $max);
3034
	}
3035
3036
	/**
3037
	 * 	Update position of line (rang)
3038
	 *
3039
	 * 	@param	int		$rowid		Id of line
3040
	 * 	@param	int		$rang		Position
3041
	 * 	@return	void
3042
	 */
3043
	public function updateRangOfLine($rowid, $rang)
3044
	{
3045
		global $hookmanager;
3046
		$fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3047
		if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction'))) {
3048
			$fieldposition = 'position';
3049
		}
3050
3051
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element_line." SET ".$fieldposition." = ".((int) $rang);
3052
		$sql .= ' WHERE rowid = '.((int) $rowid);
3053
3054
		dol_syslog(get_class($this)."::updateRangOfLine", LOG_DEBUG);
3055
		if (!$this->db->query($sql)) {
3056
			dol_print_error($this->db);
3057
		}
3058
		$parameters=array('rowid'=>$rowid, 'rang'=>$rang, 'fieldposition' => $fieldposition);
3059
		$action='';
3060
		$reshook = $hookmanager->executeHooks('afterRankOfLineUpdate', $parameters, $this, $action);
3061
	}
3062
3063
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3064
	/**
3065
	 * 	Update position of line with ajax (rang)
3066
	 *
3067
	 * 	@param	array	$rows	Array of rows
3068
	 * 	@return	void
3069
	 */
3070
	public function line_ajaxorder($rows)
3071
	{
3072
		// phpcs:enable
3073
		$num = count($rows);
3074
		for ($i = 0; $i < $num; $i++) {
3075
			$this->updateRangOfLine($rows[$i], ($i + 1));
3076
		}
3077
	}
3078
3079
	/**
3080
	 * 	Update position of line up (rang)
3081
	 *
3082
	 * 	@param	int		$rowid		Id of line
3083
	 * 	@param	int		$rang		Position
3084
	 * 	@return	void
3085
	 */
3086
	public function updateLineUp($rowid, $rang)
3087
	{
3088
		if ($rang > 1) {
3089
			$fieldposition = 'rang';
3090
			if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction'))) {
3091
				$fieldposition = 'position';
3092
			}
3093
3094
			$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element_line." SET ".$fieldposition." = ".((int) $rang);
3095
			$sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3096
			$sql .= ' AND rang = '.((int) ($rang - 1));
3097
			if ($this->db->query($sql)) {
3098
				$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element_line." SET ".$fieldposition." = ".((int) ($rang - 1));
3099
				$sql .= ' WHERE rowid = '.((int) $rowid);
3100
				if (!$this->db->query($sql)) {
3101
					dol_print_error($this->db);
3102
				}
3103
			} else {
3104
				dol_print_error($this->db);
3105
			}
3106
		}
3107
	}
3108
3109
	/**
3110
	 * 	Update position of line down (rang)
3111
	 *
3112
	 * 	@param	int		$rowid		Id of line
3113
	 * 	@param	int		$rang		Position
3114
	 * 	@param	int		$max		Max
3115
	 * 	@return	void
3116
	 */
3117
	public function updateLineDown($rowid, $rang, $max)
3118
	{
3119
		if ($rang < $max) {
3120
			$fieldposition = 'rang';
3121
			if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction'))) {
3122
				$fieldposition = 'position';
3123
			}
3124
3125
			$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element_line." SET ".$fieldposition." = ".((int) $rang);
3126
			$sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3127
			$sql .= ' AND rang = '.((int) ($rang + 1));
3128
			if ($this->db->query($sql)) {
3129
				$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element_line." SET ".$fieldposition." = ".((int) ($rang + 1));
3130
				$sql .= ' WHERE rowid = '.((int) $rowid);
3131
				if (!$this->db->query($sql)) {
3132
					dol_print_error($this->db);
3133
				}
3134
			} else {
3135
				dol_print_error($this->db);
3136
			}
3137
		}
3138
	}
3139
3140
	/**
3141
	 * 	Get position of line (rang)
3142
	 *
3143
	 * 	@param		int		$rowid		Id of line
3144
	 *  @return		int     			Value of rang in table of lines
3145
	 */
3146
	public function getRangOfLine($rowid)
3147
	{
3148
		$sql = 'SELECT rang FROM '.MAIN_DB_PREFIX.$this->table_element_line;
3149
		$sql .= ' WHERE rowid ='.((int) $rowid);
3150
3151
		dol_syslog(get_class($this)."::getRangOfLine", LOG_DEBUG);
3152
		$resql = $this->db->query($sql);
3153
		if ($resql) {
3154
			$row = $this->db->fetch_row($resql);
3155
			return $row[0];
3156
		}
3157
	}
3158
3159
	/**
3160
	 * 	Get rowid of the line relative to its position
3161
	 *
3162
	 * 	@param		int		$rang		Rang value
3163
	 *  @return     int     			Rowid of the line
3164
	 */
3165
	public function getIdOfLine($rang)
3166
	{
3167
		$sql = 'SELECT rowid FROM '.MAIN_DB_PREFIX.$this->table_element_line;
3168
		$sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3169
		$sql .= ' AND rang = '.((int) $rang);
3170
		$resql = $this->db->query($sql);
3171
		if ($resql) {
3172
			$row = $this->db->fetch_row($resql);
3173
			return $row[0];
3174
		}
3175
	}
3176
3177
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3178
	/**
3179
	 * 	Get max value used for position of line (rang)
3180
	 *
3181
	 * 	@param		int		$fk_parent_line		Parent line id
3182
	 *  @return     int  			   			Max value of rang in table of lines
3183
	 */
3184
	public function line_max($fk_parent_line = 0)
3185
	{
3186
		// phpcs:enable
3187
		$positionfield = 'rang';
3188
		if ($this->table_element == 'bom_bom') {
3189
			$positionfield = 'position';
3190
		}
3191
3192
		// Search the last rang with fk_parent_line
3193
		if ($fk_parent_line) {
3194
			$sql = "SELECT max(".$positionfield.") FROM ".MAIN_DB_PREFIX.$this->table_element_line;
3195
			$sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3196
			$sql .= ' AND fk_parent_line = '.((int) $fk_parent_line);
3197
3198
			dol_syslog(get_class($this)."::line_max", LOG_DEBUG);
3199
			$resql = $this->db->query($sql);
3200
			if ($resql) {
3201
				$row = $this->db->fetch_row($resql);
3202
				if (!empty($row[0])) {
3203
					return $row[0];
3204
				} else {
3205
					return $this->getRangOfLine($fk_parent_line);
3206
				}
3207
			}
3208
		} else {
3209
			// If not, search the last rang of element
3210
			$sql = "SELECT max(".$positionfield.") FROM ".MAIN_DB_PREFIX.$this->table_element_line;
3211
			$sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3212
3213
			dol_syslog(get_class($this)."::line_max", LOG_DEBUG);
3214
			$resql = $this->db->query($sql);
3215
			if ($resql) {
3216
				$row = $this->db->fetch_row($resql);
3217
				return $row[0];
3218
			}
3219
		}
3220
	}
3221
3222
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3223
	/**
3224
	 *  Update external ref of element
3225
	 *
3226
	 *  @param      string		$ref_ext	Update field ref_ext
3227
	 *  @return     int      		   		<0 if KO, >0 if OK
3228
	 */
3229
	public function update_ref_ext($ref_ext)
3230
	{
3231
		// phpcs:enable
3232
		if (!$this->table_element) {
3233
			dol_syslog(get_class($this)."::update_ref_ext was called on objet with property table_element not defined", LOG_ERR);
3234
			return -1;
3235
		}
3236
3237
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
3238
		$sql .= " SET ref_ext = '".$this->db->escape($ref_ext)."'";
3239
		$sql .= " WHERE ".(isset($this->table_rowid) ? $this->table_rowid : 'rowid')." = ".((int) $this->id);
3240
3241
		dol_syslog(get_class($this)."::update_ref_ext", LOG_DEBUG);
3242
		if ($this->db->query($sql)) {
3243
			$this->ref_ext = $ref_ext;
3244
			return 1;
3245
		} else {
3246
			$this->error = $this->db->error();
3247
			return -1;
3248
		}
3249
	}
3250
3251
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3252
	/**
3253
	 *  Update note of element
3254
	 *
3255
	 *  @param      string		$note		New value for note
3256
	 *  @param		string		$suffix		'', '_public' or '_private'
3257
	 *  @return     int      		   		<0 if KO, >0 if OK
3258
	 */
3259
	public function update_note($note, $suffix = '')
3260
	{
3261
		// phpcs:enable
3262
		global $user;
3263
3264
		if (!$this->table_element) {
3265
			$this->error = 'update_note was called on objet with property table_element not defined';
3266
			dol_syslog(get_class($this)."::update_note was called on objet with property table_element not defined", LOG_ERR);
3267
			return -1;
3268
		}
3269
		if (!in_array($suffix, array('', '_public', '_private'))) {
3270
			$this->error = 'update_note Parameter suffix must be empty, \'_private\' or \'_public\'';
3271
			dol_syslog(get_class($this)."::update_note Parameter suffix must be empty, '_private' or '_public'", LOG_ERR);
3272
			return -2;
3273
		}
3274
3275
		$newsuffix = $suffix;
3276
3277
		// Special cas
3278
		if ($this->table_element == 'product' && $newsuffix == '_private') {
3279
			$newsuffix = '';
3280
		}
3281
		if (in_array($this->table_element, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
3282
			$fieldusermod =  "fk_user_mod";
3283
		} elseif ($this->table_element == 'ecm_files') {
3284
			$fieldusermod = "fk_user_m";
3285
		} else {
3286
			$fieldusermod = "fk_user_modif";
3287
		}
3288
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
3289
		$sql .= " SET note".$newsuffix." = ".(!empty($note) ? ("'".$this->db->escape($note)."'") : "NULL");
3290
		$sql .= ", ".$fieldusermod." = ".((int) $user->id);
3291
		$sql .= " WHERE rowid = ".((int) $this->id);
3292
3293
		dol_syslog(get_class($this)."::update_note", LOG_DEBUG);
3294
		if ($this->db->query($sql)) {
3295
			if ($suffix == '_public') {
3296
				$this->note_public = $note;
3297
			} elseif ($suffix == '_private') {
3298
				$this->note_private = $note;
3299
			} else {
3300
				$this->note = $note; // deprecated
3301
				$this->note_private = $note;
3302
			}
3303
			return 1;
3304
		} else {
3305
			$this->error = $this->db->lasterror();
3306
			return -1;
3307
		}
3308
	}
3309
3310
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3311
	/**
3312
	 * 	Update public note (kept for backward compatibility)
3313
	 *
3314
	 * @param      string		$note		New value for note
3315
	 * @return     int      		   		<0 if KO, >0 if OK
3316
	 * @deprecated
3317
	 * @see update_note()
3318
	 */
3319
	public function update_note_public($note)
3320
	{
3321
		// phpcs:enable
3322
		return $this->update_note($note, '_public');
3323
	}
3324
3325
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3326
	/**
3327
	 *	Update total_ht, total_ttc, total_vat, total_localtax1, total_localtax2 for an object (sum of lines).
3328
	 *  Must be called at end of methods addline or updateline.
3329
	 *
3330
	 *	@param	int		$exclspec          	>0 = Exclude special product (product_type=9)
3331
	 *  @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
3332
	 *  @param	int		$nodatabaseupdate	1=Do not update database. Update only properties of object.
3333
	 *  @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.
3334
	 *	@return	int    			           	<0 if KO, >0 if OK
3335
	 */
3336
	public function update_price($exclspec = 0, $roundingadjust = 'none', $nodatabaseupdate = 0, $seller = null)
3337
	{
3338
		// phpcs:enable
3339
		global $conf, $hookmanager, $action;
3340
3341
		$parameters = array('exclspec' => $exclspec, 'roundingadjust' => $roundingadjust, 'nodatabaseupdate' => $nodatabaseupdate, 'seller' => $seller);
3342
		$reshook = $hookmanager->executeHooks('updateTotalPrice', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3343
		if ($reshook > 0) {
3344
			return 1; // replacement code
3345
		} elseif ($reshook < 0) {
3346
			return -1; // failure
3347
		} // reshook = 0 => execute normal code
3348
3349
		// Some external module want no update price after a trigger because they have another method to calculate the total (ex: with an extrafield)
3350
		$MODULE = "";
3351
		if ($this->element == 'propal') {
3352
			$MODULE = "MODULE_DISALLOW_UPDATE_PRICE_PROPOSAL";
3353
		} elseif ($this->element == 'commande' || $this->element == 'order') {
3354
			$MODULE = "MODULE_DISALLOW_UPDATE_PRICE_ORDER";
3355
		} elseif ($this->element == 'facture' || $this->element == 'invoice') {
3356
			$MODULE = "MODULE_DISALLOW_UPDATE_PRICE_INVOICE";
3357
		} elseif ($this->element == 'facture_fourn' || $this->element == 'supplier_invoice' || $this->element == 'invoice_supplier') {
3358
			$MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_INVOICE";
3359
		} elseif ($this->element == 'order_supplier' || $this->element == 'supplier_order') {
3360
			$MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_ORDER";
3361
		} elseif ($this->element == 'supplier_proposal') {
3362
			$MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_PROPOSAL";
3363
		}
3364
3365
		if (!empty($MODULE)) {
3366
			if (!empty($conf->global->$MODULE)) {
3367
				$modsactivated = explode(',', $conf->global->$MODULE);
3368
				foreach ($modsactivated as $mod) {
3369
					if ($conf->$mod->enabled) {
3370
						return 1; // update was disabled by specific setup
3371
					}
3372
				}
3373
			}
3374
		}
3375
3376
		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
3377
3378
		if ($roundingadjust == '-1') {
3379
			$roundingadjust = 'auto'; // For backward compatibility
3380
		}
3381
3382
		$forcedroundingmode = $roundingadjust;
3383
		if ($forcedroundingmode == 'auto' && isset($conf->global->MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND)) {
3384
			$forcedroundingmode = $conf->global->MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND;
3385
		} elseif ($forcedroundingmode == 'auto') {
3386
			$forcedroundingmode = '0';
3387
		}
3388
3389
		$error = 0;
3390
3391
		$multicurrency_tx = !empty($this->multicurrency_tx) ? $this->multicurrency_tx : 1;
3392
3393
		// Define constants to find lines to sum
3394
		$fieldtva = 'total_tva';
3395
		$fieldlocaltax1 = 'total_localtax1';
3396
		$fieldlocaltax2 = 'total_localtax2';
3397
		$fieldup = 'subprice';
3398
		if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
3399
			$fieldtva = 'tva';
3400
			$fieldup = 'pu_ht';
3401
		}
3402
		if ($this->element == 'expensereport') {
3403
			$fieldup = 'value_unit';
3404
		}
3405
3406
		$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,";
3407
		$sql .= ' tva_tx as vatrate, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, info_bits, product_type';
3408
		if ($this->table_element_line == 'facturedet') {
3409
			$sql .= ', situation_percent';
3410
		}
3411
		$sql .= ', multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
3412
		$sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element_line;
3413
		$sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3414
		if ($exclspec) {
3415
			$product_field = 'product_type';
3416
			if ($this->table_element_line == 'contratdet') {
3417
				$product_field = ''; // contratdet table has no product_type field
3418
			}
3419
			if ($product_field) {
3420
				$sql .= " AND ".$product_field." <> 9";
3421
			}
3422
		}
3423
		$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
3424
3425
		dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
3426
		$resql = $this->db->query($sql);
3427
		if ($resql) {
3428
			$this->total_ht  = 0;
3429
			$this->total_tva = 0;
3430
			$this->total_localtax1 = 0;
3431
			$this->total_localtax2 = 0;
3432
			$this->total_ttc = 0;
3433
			$total_ht_by_vats  = array();
3434
			$total_tva_by_vats = array();
3435
			$total_ttc_by_vats = array();
3436
			$this->multicurrency_total_ht = 0;
3437
			$this->multicurrency_total_tva	= 0;
3438
			$this->multicurrency_total_ttc	= 0;
3439
3440
			$num = $this->db->num_rows($resql);
3441
			$i = 0;
3442
			while ($i < $num) {
3443
				$obj = $this->db->fetch_object($resql);
3444
3445
				// Note: There is no check on detail line and no check on total, if $forcedroundingmode = 'none'
3446
				$parameters = array('fk_element' => $obj->rowid);
3447
				$reshook = $hookmanager->executeHooks('changeRoundingMode', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3448
3449
				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'
3450
					// This part of code is to fix data. We should not call it too often.
3451
					$localtax_array = array($obj->localtax1_type, $obj->localtax1_tx, $obj->localtax2_type, $obj->localtax2_tx);
3452
					$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);
3453
3454
					$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.
3455
					$diff_on_current_total = price2num($obj->total_ttc - $obj->total_ht - $obj->total_tva - $obj->total_localtax1 - $obj->total_localtax2, 'MT', 1);
3456
					//var_dump($obj->total_ht.' '.$obj->total_tva.' '.$obj->total_localtax1.' '.$obj->total_localtax2.' =? '.$obj->total_ttc);
3457
					//var_dump($diff_when_using_price_ht.' '.$diff_on_current_total);
3458
3459
					if ($diff_when_using_price_ht && $diff_on_current_total) {
3460
						$sqlfix = "UPDATE ".MAIN_DB_PREFIX.$this->table_element_line." SET ".$fieldtva." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2])." WHERE rowid = ".((int) $obj->rowid);
3461
						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);
3462
						$resqlfix = $this->db->query($sqlfix);
3463
						if (!$resqlfix) {
3464
							dol_print_error($this->db, 'Failed to update line');
3465
						}
3466
						$obj->total_tva = $tmpcal[1];
3467
						$obj->total_ttc = $tmpcal[2];
3468
					}
3469
				}
3470
3471
				$this->total_ht        += $obj->total_ht; // The field visible at end of line detail
3472
				$this->total_tva       += $obj->total_tva;
3473
				$this->total_localtax1 += $obj->total_localtax1;
3474
				$this->total_localtax2 += $obj->total_localtax2;
3475
				$this->total_ttc       += $obj->total_ttc;
3476
				$this->multicurrency_total_ht        += $obj->multicurrency_total_ht; // The field visible at end of line detail
3477
				$this->multicurrency_total_tva       += $obj->multicurrency_total_tva;
3478
				$this->multicurrency_total_ttc       += $obj->multicurrency_total_ttc;
3479
3480
				if (!isset($total_ht_by_vats[$obj->vatrate])) {
3481
					$total_ht_by_vats[$obj->vatrate] = 0;
3482
				}
3483
				if (!isset($total_tva_by_vats[$obj->vatrate])) {
3484
					$total_tva_by_vats[$obj->vatrate] = 0;
3485
				}
3486
				if (!isset($total_ttc_by_vats[$obj->vatrate])) {
3487
					$total_ttc_by_vats[$obj->vatrate] = 0;
3488
				}
3489
				$total_ht_by_vats[$obj->vatrate]  += $obj->total_ht;
3490
				$total_tva_by_vats[$obj->vatrate] += $obj->total_tva;
3491
				$total_ttc_by_vats[$obj->vatrate] += $obj->total_ttc;
3492
3493
				if ($forcedroundingmode == '1') {	// Check if we need adjustement onto line for vat. TODO This works on the company currency but not on multicurrency
3494
					$tmpvat = price2num($total_ht_by_vats[$obj->vatrate] * $obj->vatrate / 100, 'MT', 1);
3495
					$diff = price2num($total_tva_by_vats[$obj->vatrate] - $tmpvat, 'MT', 1);
3496
					//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";
3497
					if ($diff) {
3498
						if (abs($diff) > 0.1) {
3499
							$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.';
3500
							dol_syslog($errmsg, LOG_WARNING);
3501
							dol_print_error('', $errmsg);
3502
							exit;
3503
						}
3504
						$sqlfix = "UPDATE ".MAIN_DB_PREFIX.$this->table_element_line." SET ".$fieldtva." = ".price2num($obj->total_tva - $diff).", total_ttc = ".price2num($obj->total_ttc - $diff)." WHERE rowid = ".((int) $obj->rowid);
3505
						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);
3506
								$resqlfix = $this->db->query($sqlfix);
3507
						if (!$resqlfix) {
3508
							dol_print_error($this->db, 'Failed to update line');
3509
						}
3510
								$this->total_tva -= $diff;
3511
								$this->total_ttc -= $diff;
3512
								$total_tva_by_vats[$obj->vatrate] -= $diff;
3513
								$total_ttc_by_vats[$obj->vatrate] -= $diff;
3514
					}
3515
				}
3516
3517
				$i++;
3518
			}
3519
3520
			// Add revenue stamp to total
3521
			$this->total_ttc += isset($this->revenuestamp) ? $this->revenuestamp : 0;
3522
			$this->multicurrency_total_ttc += isset($this->revenuestamp) ? ($this->revenuestamp * $multicurrency_tx) : 0;
3523
3524
			// Situations totals
3525
			if (!empty($this->situation_cycle_ref) && $this->situation_counter > 1 && method_exists($this, 'get_prev_sits') && $this->type != $this::TYPE_CREDIT_NOTE) {
3526
				$prev_sits = $this->get_prev_sits();
3527
3528
				foreach ($prev_sits as $sit) {				// $sit is an object Facture loaded with a fetch.
3529
					$this->total_ht -= $sit->total_ht;
3530
					$this->total_tva -= $sit->total_tva;
3531
					$this->total_localtax1 -= $sit->total_localtax1;
3532
					$this->total_localtax2 -= $sit->total_localtax2;
3533
					$this->total_ttc -= $sit->total_ttc;
3534
					$this->multicurrency_total_ht -= $sit->multicurrency_total_ht;
3535
					$this->multicurrency_total_tva -= $sit->multicurrency_total_tva;
3536
					$this->multicurrency_total_ttc -= $sit->multicurrency_total_ttc;
3537
				}
3538
			}
3539
3540
			$this->db->free($resql);
3541
3542
			// Now update global field total_ht, total_ttc and tva
3543
			$fieldht = 'total_ht';
3544
			$fieldtva = 'tva';
3545
			$fieldlocaltax1 = 'localtax1';
3546
			$fieldlocaltax2 = 'localtax2';
3547
			$fieldttc = 'total_ttc';
3548
			// Specific code for backward compatibility with old field names
3549
			if ($this->element == 'facture' || $this->element == 'facturerec') {
3550
				$fieldtva = 'total_tva';
3551
			}
3552
			if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
3553
				$fieldtva = 'total_tva';
3554
			}
3555
			if ($this->element == 'propal') {
3556
				$fieldtva = 'total_tva';
3557
			}
3558
			if ($this->element == 'expensereport') {
3559
				$fieldtva = 'total_tva';
3560
			}
3561
			if ($this->element == 'supplier_proposal') {
3562
				$fieldtva = 'total_tva';
3563
			}
3564
			if ($this->element == 'commande') {
3565
				$fieldtva = 'total_tva';
3566
			}
3567
			if ($this->element == 'order_supplier') {
3568
				$fieldtva = 'total_tva';
3569
			}
3570
3571
			if (empty($nodatabaseupdate)) {
3572
				$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element.' SET';
3573
				$sql .= " ".$fieldht." = ".((float) price2num($this->total_ht)).",";
3574
				$sql .= " ".$fieldtva." = ".((float) price2num($this->total_tva)).",";
3575
				$sql .= " ".$fieldlocaltax1." = ".((float) price2num($this->total_localtax1)).",";
3576
				$sql .= " ".$fieldlocaltax2." = ".((float) price2num($this->total_localtax2)).",";
3577
				$sql .= " ".$fieldttc." = ".((float) price2num($this->total_ttc));
3578
				$sql .= ", multicurrency_total_ht = ".((float) price2num($this->multicurrency_total_ht, 'MT', 1));
3579
				$sql .= ", multicurrency_total_tva = ".((float) price2num($this->multicurrency_total_tva, 'MT', 1));
3580
				$sql .= ", multicurrency_total_ttc = ".((float) price2num($this->multicurrency_total_ttc, 'MT', 1));
3581
				$sql .= " WHERE rowid = ".((int) $this->id);
3582
3583
				dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
3584
				$resql = $this->db->query($sql);
3585
				if (!$resql) {
3586
					$error++;
3587
					$this->error = $this->db->lasterror();
3588
					$this->errors[] = $this->db->lasterror();
3589
				}
3590
			}
3591
3592
			if (!$error) {
3593
				return 1;
3594
			} else {
3595
				return -1;
3596
			}
3597
		} else {
3598
			dol_print_error($this->db, 'Bad request in update_price');
3599
			return -1;
3600
		}
3601
	}
3602
3603
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3604
	/**
3605
	 *	Add objects linked in llx_element_element.
3606
	 *
3607
	 *	@param		string	$origin		Linked element type
3608
	 *	@param		int		$origin_id	Linked element id
3609
	 * 	@param		User	$f_user		User that create
3610
	 * 	@param		int		$notrigger	1=Does not execute triggers, 0=execute triggers
3611
	 *	@return		int					<=0 if KO, >0 if OK
3612
	 *	@see		fetchObjectLinked(), updateObjectLinked(), deleteObjectLinked()
3613
	 */
3614
	public function add_object_linked($origin = null, $origin_id = null, $f_user = null, $notrigger = 0)
3615
	{
3616
		// phpcs:enable
3617
		global $user;
3618
		$origin = (!empty($origin) ? $origin : $this->origin);
3619
		$origin_id = (!empty($origin_id) ? $origin_id : $this->origin_id);
3620
		$f_user = isset($f_user) ? $f_user : $user;
3621
3622
		// Special case
3623
		if ($origin == 'order') {
3624
			$origin = 'commande';
3625
		}
3626
		if ($origin == 'invoice') {
3627
			$origin = 'facture';
3628
		}
3629
		if ($origin == 'invoice_template') {
3630
			$origin = 'facturerec';
3631
		}
3632
		if ($origin == 'supplierorder') {
3633
			$origin = 'order_supplier';
3634
		}
3635
		$this->db->begin();
3636
		$error = 0;
3637
3638
		$sql = "INSERT INTO " . MAIN_DB_PREFIX . "element_element (";
3639
		$sql .= "fk_source";
3640
		$sql .= ", sourcetype";
3641
		$sql .= ", fk_target";
3642
		$sql .= ", targettype";
3643
		$sql .= ") VALUES (";
3644
		$sql .= ((int) $origin_id);
3645
		$sql .= ", '" . $this->db->escape($origin) . "'";
3646
		$sql .= ", " . ((int) $this->id);
3647
		$sql .= ", '" . $this->db->escape($this->element) . "'";
3648
		$sql .= ")";
3649
3650
		dol_syslog(get_class($this) . "::add_object_linked", LOG_DEBUG);
3651
		if ($this->db->query($sql)) {
3652
			if (!$notrigger) {
3653
				// Call trigger
3654
				$this->context['link_origin'] = $origin;
3655
				$this->context['link_origin_id'] = $origin_id;
3656
				$result = $this->call_trigger('OBJECT_LINK_INSERT', $f_user);
3657
				if ($result < 0) {
3658
					$error++;
3659
				}
3660
				// End call triggers
3661
			}
3662
		} else {
3663
			$this->error = $this->db->lasterror();
3664
			$error++;
3665
		}
3666
3667
		if (!$error) {
3668
			$this->db->commit();
3669
			return 1;
3670
		} else {
3671
			$this->db->rollback();
3672
			return 0;
3673
		}
3674
	}
3675
3676
	/**
3677
	 *	Fetch array of objects linked to current object (object of enabled modules only). Links are loaded into
3678
	 *		this->linkedObjectsIds array +
3679
	 *		this->linkedObjects array if $loadalsoobjects = 1
3680
	 *  Possible usage for parameters:
3681
	 *  - all parameters empty -> we look all link to current object (current object can be source or target)
3682
	 *  - source id+type -> will get target list linked to source
3683
	 *  - target id+type -> will get source list linked to target
3684
	 *  - source id+type + target type -> will get target list of the type
3685
	 *  - target id+type + target source -> will get source list of the type
3686
	 *
3687
	 *	@param	int		$sourceid			Object source id (if not defined, id of object)
3688
	 *	@param  string	$sourcetype			Object source type (if not defined, element name of object)
3689
	 *	@param  int		$targetid			Object target id (if not defined, id of object)
3690
	 *	@param  string	$targettype			Object target type (if not defined, elemennt name of object)
3691
	 *	@param  string	$clause				'OR' or 'AND' clause used when both source id and target id are provided
3692
	 *  @param  int		$alsosametype		0=Return only links to object that differs from source type. 1=Include also link to objects of same type.
3693
	 *  @param  string	$orderby			SQL 'ORDER BY' clause
3694
	 *  @param	int		$loadalsoobjects	Load also array this->linkedObjects (Use 0 to increase performances)
3695
	 *	@return int							<0 if KO, >0 if OK
3696
	 *  @see	add_object_linked(), updateObjectLinked(), deleteObjectLinked()
3697
	 */
3698
	public function fetchObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $clause = 'OR', $alsosametype = 1, $orderby = 'sourcetype', $loadalsoobjects = 1)
3699
	{
3700
		global $conf;
3701
3702
		$this->linkedObjectsIds = array();
3703
		$this->linkedObjects = array();
3704
3705
		$justsource = false;
3706
		$justtarget = false;
3707
		$withtargettype = false;
3708
		$withsourcetype = false;
3709
3710
		if (!empty($sourceid) && !empty($sourcetype) && empty($targetid)) {
3711
			$justsource = true; // the source (id and type) is a search criteria
3712
			if (!empty($targettype)) {
3713
				$withtargettype = true;
3714
			}
3715
		}
3716
		if (!empty($targetid) && !empty($targettype) && empty($sourceid)) {
3717
			$justtarget = true; // the target (id and type) is a search criteria
3718
			if (!empty($sourcetype)) {
3719
				$withsourcetype = true;
3720
			}
3721
		}
3722
3723
		$sourceid = (!empty($sourceid) ? $sourceid : $this->id);
3724
		$targetid = (!empty($targetid) ? $targetid : $this->id);
3725
		$sourcetype = (!empty($sourcetype) ? $sourcetype : $this->element);
3726
		$targettype = (!empty($targettype) ? $targettype : $this->element);
3727
3728
		/*if (empty($sourceid) && empty($targetid))
3729
		 {
3730
		 dol_syslog('Bad usage of function. No source nor target id defined (nor as parameter nor as object id)', LOG_ERR);
3731
		 return -1;
3732
		 }*/
3733
3734
		// Links between objects are stored in table element_element
3735
		$sql = 'SELECT rowid, fk_source, sourcetype, fk_target, targettype';
3736
		$sql .= ' FROM '.MAIN_DB_PREFIX.'element_element';
3737
		$sql .= " WHERE ";
3738
		if ($justsource || $justtarget) {
3739
			if ($justsource) {
3740
				$sql .= "fk_source = ".((int) $sourceid)." AND sourcetype = '".$this->db->escape($sourcetype)."'";
3741
				if ($withtargettype) {
3742
					$sql .= " AND targettype = '".$this->db->escape($targettype)."'";
3743
				}
3744
			} elseif ($justtarget) {
3745
				$sql .= "fk_target = ".((int) $targetid)." AND targettype = '".$this->db->escape($targettype)."'";
3746
				if ($withsourcetype) {
3747
					$sql .= " AND sourcetype = '".$this->db->escape($sourcetype)."'";
3748
				}
3749
			}
3750
		} else {
3751
			$sql .= "(fk_source = ".((int) $sourceid)." AND sourcetype = '".$this->db->escape($sourcetype)."')";
3752
			$sql .= " ".$clause." (fk_target = ".((int) $targetid)." AND targettype = '".$this->db->escape($targettype)."')";
3753
		}
3754
		$sql .= ' ORDER BY '.$orderby;
3755
3756
		dol_syslog(get_class($this)."::fetchObjectLink", LOG_DEBUG);
3757
		$resql = $this->db->query($sql);
3758
		if ($resql) {
3759
			$num = $this->db->num_rows($resql);
3760
			$i = 0;
3761
			while ($i < $num) {
3762
				$obj = $this->db->fetch_object($resql);
3763
				if ($justsource || $justtarget) {
3764
					if ($justsource) {
3765
						$this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
3766
					} elseif ($justtarget) {
3767
						$this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
3768
					}
3769
				} else {
3770
					if ($obj->fk_source == $sourceid && $obj->sourcetype == $sourcetype) {
3771
						$this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
3772
					}
3773
					if ($obj->fk_target == $targetid && $obj->targettype == $targettype) {
3774
						$this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
3775
					}
3776
				}
3777
				$i++;
3778
			}
3779
3780
			if (!empty($this->linkedObjectsIds)) {
3781
				$tmparray = $this->linkedObjectsIds;
3782
				foreach ($tmparray as $objecttype => $objectids) {       // $objecttype is a module name ('facture', 'mymodule', ...) or a module name with a suffix ('project_task', 'mymodule_myobj', ...)
3783
					// Parse element/subelement (ex: project_task, cabinetmed_consultation, ...)
3784
					$module = $element = $subelement = $objecttype;
3785
					$regs = array();
3786
					if ($objecttype != 'supplier_proposal' && $objecttype != 'order_supplier' && $objecttype != 'invoice_supplier'
3787
						&& preg_match('/^([^_]+)_([^_]+)/i', $objecttype, $regs)) {
3788
						$module = $element = $regs[1];
3789
						$subelement = $regs[2];
3790
					}
3791
3792
					$classpath = $element.'/class';
3793
					// To work with non standard classpath or module name
3794
					if ($objecttype == 'facture') {
3795
						$classpath = 'compta/facture/class';
3796
					} elseif ($objecttype == 'facturerec') {
3797
						$classpath = 'compta/facture/class';
3798
						$module = 'facture';
3799
					} elseif ($objecttype == 'propal') {
3800
						$classpath = 'comm/propal/class';
3801
					} elseif ($objecttype == 'supplier_proposal') {
3802
						$classpath = 'supplier_proposal/class';
3803
					} elseif ($objecttype == 'shipping') {
3804
						$classpath = 'expedition/class';
3805
						$subelement = 'expedition';
3806
						$module = 'expedition_bon';
3807
					} elseif ($objecttype == 'delivery') {
3808
						$classpath = 'delivery/class';
3809
						$subelement = 'delivery';
3810
						$module = 'delivery_note';
3811
					} elseif ($objecttype == 'invoice_supplier' || $objecttype == 'order_supplier') {
3812
						$classpath = 'fourn/class';
3813
						$module = 'fournisseur';
3814
					} elseif ($objecttype == 'fichinter') {
3815
						$classpath = 'fichinter/class';
3816
						$subelement = 'fichinter';
3817
						$module = 'ficheinter';
3818
					} elseif ($objecttype == 'subscription') {
3819
						$classpath = 'adherents/class';
3820
						$module = 'adherent';
3821
					} elseif ($objecttype == 'contact') {
3822
						 $module = 'societe';
3823
					}
3824
					// Set classfile
3825
					$classfile = strtolower($subelement);
3826
					$classname = ucfirst($subelement);
3827
3828
					if ($objecttype == 'order') {
3829
						$classfile = 'commande';
3830
						$classname = 'Commande';
3831
					} elseif ($objecttype == 'invoice_supplier') {
3832
						$classfile = 'fournisseur.facture';
3833
						$classname = 'FactureFournisseur';
3834
					} elseif ($objecttype == 'order_supplier') {
3835
						$classfile = 'fournisseur.commande';
3836
						$classname = 'CommandeFournisseur';
3837
					} elseif ($objecttype == 'supplier_proposal') {
3838
						$classfile = 'supplier_proposal';
3839
						$classname = 'SupplierProposal';
3840
					} elseif ($objecttype == 'facturerec') {
3841
						$classfile = 'facture-rec';
3842
						$classname = 'FactureRec';
3843
					} elseif ($objecttype == 'subscription') {
3844
						$classfile = 'subscription';
3845
						$classname = 'Subscription';
3846
					} elseif ($objecttype == 'project' || $objecttype == 'projet') {
3847
						$classpath = 'projet/class';
3848
						$classfile = 'project';
3849
						$classname = 'Project';
3850
					} elseif ($objecttype == 'conferenceorboothattendee') {
3851
						$classpath = 'eventorganization/class';
3852
						$classfile = 'conferenceorboothattendee';
3853
						$classname = 'ConferenceOrBoothAttendee';
3854
						$module = 'eventorganization';
3855
					} elseif ($objecttype == 'conferenceorbooth') {
3856
						$classpath = 'eventorganization/class';
3857
						$classfile = 'conferenceorbooth';
3858
						$classname = 'ConferenceOrBooth';
3859
						$module = 'eventorganization';
3860
					} elseif ($objecttype == 'mo') {
3861
						$classpath = 'mrp/class';
3862
						$classfile = 'mo';
3863
						$classname = 'Mo';
3864
						$module = 'mrp';
3865
					}
3866
3867
3868
					// Here $module, $classfile and $classname are set
3869
					if ($conf->$module->enabled && (($element != $this->element) || $alsosametype)) {
3870
						if ($loadalsoobjects) {
3871
							dol_include_once('/'.$classpath.'/'.$classfile.'.class.php');
3872
							//print '/'.$classpath.'/'.$classfile.'.class.php '.class_exists($classname);
3873
							if (class_exists($classname)) {
3874
								foreach ($objectids as $i => $objectid) {	// $i is rowid into llx_element_element
3875
									$object = new $classname($this->db);
3876
									$ret = $object->fetch($objectid);
3877
									if ($ret >= 0) {
3878
										$this->linkedObjects[$objecttype][$i] = $object;
3879
									}
3880
								}
3881
							}
3882
						}
3883
					} else {
3884
						unset($this->linkedObjectsIds[$objecttype]);
3885
					}
3886
				}
3887
			}
3888
			return 1;
3889
		} else {
3890
			dol_print_error($this->db);
3891
			return -1;
3892
		}
3893
	}
3894
3895
	/**
3896
	 *	Update object linked of a current object
3897
	 *
3898
	 *	@param	int		$sourceid		Object source id
3899
	 *	@param  string	$sourcetype		Object source type
3900
	 *	@param  int		$targetid		Object target id
3901
	 *	@param  string	$targettype		Object target type
3902
	 * 	@param	User	$f_user			User that create
3903
	 * 	@param	int		$notrigger		1=Does not execute triggers, 0= execute triggers
3904
	 *	@return							int	>0 if OK, <0 if KO
3905
	 *	@see	add_object_linked(), fetObjectLinked(), deleteObjectLinked()
3906
	 */
3907
	public function updateObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $f_user = null, $notrigger = 0)
3908
	{
3909
		global $user;
3910
		$updatesource = false;
3911
		$updatetarget = false;
3912
		$f_user = isset($f_user) ? $f_user : $user;
3913
3914
		if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
3915
			$updatesource = true;
3916
		} elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
3917
			$updatetarget = true;
3918
		}
3919
3920
		$this->db->begin();
3921
		$error = 0;
3922
3923
		$sql = "UPDATE " . MAIN_DB_PREFIX . "element_element SET ";
3924
		if ($updatesource) {
3925
			$sql .= "fk_source = " . ((int) $sourceid);
3926
			$sql .= ", sourcetype = '" . $this->db->escape($sourcetype) . "'";
3927
			$sql .= " WHERE fk_target = " . ((int) $this->id);
3928
			$sql .= " AND targettype = '" . $this->db->escape($this->element) . "'";
3929
		} elseif ($updatetarget) {
3930
			$sql .= "fk_target = " . ((int) $targetid);
3931
			$sql .= ", targettype = '" . $this->db->escape($targettype) . "'";
3932
			$sql .= " WHERE fk_source = " . ((int) $this->id);
3933
			$sql .= " AND sourcetype = '" . $this->db->escape($this->element) . "'";
3934
		}
3935
3936
		dol_syslog(get_class($this) . "::updateObjectLinked", LOG_DEBUG);
3937
		if ($this->db->query($sql)) {
3938
			if (!$notrigger) {
3939
				// Call trigger
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_UPDATE', $f_user);
3945
				if ($result < 0) {
3946
					$error++;
3947
				}
3948
				// End call triggers
3949
			}
3950
		} else {
3951
			$this->error = $this->db->lasterror();
3952
			$error++;
3953
		}
3954
3955
		if (!$error) {
3956
			$this->db->commit();
3957
			return 1;
3958
		} else {
3959
			$this->db->rollback();
3960
			return -1;
3961
		}
3962
	}
3963
3964
	/**
3965
	 *	Delete all links between an object $this
3966
	 *
3967
	 *	@param	int		$sourceid		Object source id
3968
	 *	@param  string	$sourcetype		Object source type
3969
	 *	@param  int		$targetid		Object target id
3970
	 *	@param  string	$targettype		Object target type
3971
	 *  @param	int		$rowid			Row id of line to delete. If defined, other parameters are not used.
3972
	 * 	@param	User	$f_user			User that create
3973
	 * 	@param	int		$notrigger		1=Does not execute triggers, 0= execute triggers
3974
	 *	@return     					int	>0 if OK, <0 if KO
3975
	 *	@see	add_object_linked(), updateObjectLinked(), fetchObjectLinked()
3976
	 */
3977
	public function deleteObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $rowid = '', $f_user = null, $notrigger = 0)
3978
	{
3979
		global $user;
3980
		$deletesource = false;
3981
		$deletetarget = false;
3982
		$f_user = isset($f_user) ? $f_user : $user;
3983
3984
		if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
3985
			$deletesource = true;
3986
		} elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
3987
			$deletetarget = true;
3988
		}
3989
3990
		$sourceid = (!empty($sourceid) ? $sourceid : $this->id);
3991
		$sourcetype = (!empty($sourcetype) ? $sourcetype : $this->element);
3992
		$targetid = (!empty($targetid) ? $targetid : $this->id);
3993
		$targettype = (!empty($targettype) ? $targettype : $this->element);
3994
		$this->db->begin();
3995
		$error = 0;
3996
3997
		if (!$notrigger) {
3998
			// Call trigger
3999
			$this->context['link_id'] = $rowid;
4000
			$this->context['link_source_id'] = $sourceid;
4001
			$this->context['link_source_type'] = $sourcetype;
4002
			$this->context['link_target_id'] = $targetid;
4003
			$this->context['link_target_type'] = $targettype;
4004
			$result = $this->call_trigger('OBJECT_LINK_DELETE', $f_user);
4005
			if ($result < 0) {
4006
				$error++;
4007
			}
4008
			// End call triggers
4009
		}
4010
4011
		if (!$error) {
4012
			$sql = "DELETE FROM " . MAIN_DB_PREFIX . "element_element";
4013
			$sql .= " WHERE";
4014
			if ($rowid > 0) {
4015
				$sql .= " rowid = " . ((int) $rowid);
4016
			} else {
4017
				if ($deletesource) {
4018
					$sql .= " fk_source = " . ((int) $sourceid) . " AND sourcetype = '" . $this->db->escape($sourcetype) . "'";
4019
					$sql .= " AND fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($this->element) . "'";
4020
				} elseif ($deletetarget) {
4021
					$sql .= " fk_target = " . ((int) $targetid) . " AND targettype = '" . $this->db->escape($targettype) . "'";
4022
					$sql .= " AND fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($this->element) . "'";
4023
				} else {
4024
					$sql .= " (fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($this->element) . "')";
4025
					$sql .= " OR";
4026
					$sql .= " (fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($this->element) . "')";
4027
				}
4028
			}
4029
4030
			dol_syslog(get_class($this) . "::deleteObjectLinked", LOG_DEBUG);
4031
			if (!$this->db->query($sql)) {
4032
				$this->error = $this->db->lasterror();
4033
				$this->errors[] = $this->error;
4034
				$error++;
4035
			}
4036
		}
4037
4038
		if (!$error) {
4039
			$this->db->commit();
4040
			return 1;
4041
		} else {
4042
			$this->db->rollback();
4043
			return 0;
4044
		}
4045
	}
4046
4047
	/**
4048
	 * Function used to get an array with all items linked to an object id in association table
4049
	 *
4050
	 * @param	int		$fk_object_where		id of object we need to get linked items
4051
	 * @param	string	$field_select			name of field we need to get a list
4052
	 * @param	string	$field_where			name of field of object we need to get linked items
4053
	 * @param	string	$table_element			name of association table
4054
	 * @return 	array							Array of record
4055
	 */
4056
	public static function getAllItemsLinkedByObjectID($fk_object_where, $field_select, $field_where, $table_element)
4057
	{
4058
		if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4059
			return -1;
4060
		}
4061
4062
		global $db;
4063
4064
		$sql = "SELECT ".$field_select." FROM ".MAIN_DB_PREFIX.$table_element." WHERE ".$field_where." = ".((int) $fk_object_where);
4065
		$resql = $db->query($sql);
4066
4067
		$TRes = array();
4068
		if (!empty($resql)) {
4069
			while ($res = $db->fetch_object($resql)) {
4070
				$TRes[] = $res->{$field_select};
4071
			}
4072
		}
4073
4074
		return $TRes;
4075
	}
4076
4077
	/**
4078
	 * Function used to remove all items linked to an object id in association table
4079
	 *
4080
	 * @param	int		$fk_object_where		id of object we need to remove linked items
4081
	 * @param	string	$field_where			name of field of object we need to delete linked items
4082
	 * @param	string	$table_element			name of association table
4083
	 * @return 	int								<0 if KO, 0 if nothing done, >0 if OK and something done
4084
	 */
4085
	public static function deleteAllItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
4086
	{
4087
		if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4088
			return -1;
4089
		}
4090
4091
		global $db;
4092
4093
		$sql = "DELETE FROM ".MAIN_DB_PREFIX.$table_element." WHERE ".$field_where." = ".((int) $fk_object_where);
4094
		$resql = $db->query($sql);
4095
4096
		if (empty($resql)) {
4097
			return 0;
4098
		}
4099
4100
		return 1;
4101
	}
4102
4103
	/**
4104
	 *      Set status of an object
4105
	 *
4106
	 *      @param	int		$status			Status to set
4107
	 *      @param	int		$elementId		Id of element to force (use this->id by default)
4108
	 *      @param	string	$elementType	Type of element to force (use this->table_element by default)
4109
	 *      @param	string	$trigkey		Trigger key to use for trigger
4110
	 *      @return int						<0 if KO, >0 if OK
4111
	 */
4112
	public function setStatut($status, $elementId = null, $elementType = '', $trigkey = '')
4113
	{
4114
		global $user, $langs, $conf;
4115
4116
		$savElementId = $elementId; // To be used later to know if we were using the method using the id of this or not.
4117
4118
		$elementId = (!empty($elementId) ? $elementId : $this->id);
4119
		$elementTable = (!empty($elementType) ? $elementType : $this->table_element);
4120
4121
		$this->db->begin();
4122
4123
		$fieldstatus = "fk_statut";
4124
		if ($elementTable == 'facture_rec') {
4125
			$fieldstatus = "suspended";
4126
		}
4127
		if ($elementTable == 'mailing') {
4128
			$fieldstatus = "statut";
4129
		}
4130
		if ($elementTable == 'cronjob') {
4131
			$fieldstatus = "status";
4132
		}
4133
		if ($elementTable == 'user') {
4134
			$fieldstatus = "statut";
4135
		}
4136
		if ($elementTable == 'expensereport') {
4137
			$fieldstatus = "fk_statut";
4138
		}
4139
		if ($elementTable == 'commande_fournisseur_dispatch') {
4140
			$fieldstatus = "status";
4141
		}
4142
		if (is_array($this->fields) && array_key_exists('status', $this->fields)) {
4143
			$fieldstatus = 'status';
4144
		}
4145
4146
		$sql = "UPDATE ".MAIN_DB_PREFIX.$elementTable;
4147
		$sql .= " SET ".$fieldstatus." = ".((int) $status);
4148
		// If status = 1 = validated, update also fk_user_valid
4149
		if ($status == 1 && $elementTable == 'expensereport') {
4150
			$sql .= ", fk_user_valid = ".((int) $user->id);
4151
		}
4152
		$sql .= " WHERE rowid=".((int) $elementId);
4153
4154
		dol_syslog(get_class($this)."::setStatut", LOG_DEBUG);
4155
		if ($this->db->query($sql)) {
4156
			$error = 0;
4157
4158
			// Try autoset of trigkey
4159
			if (empty($trigkey)) {
4160
				if ($this->element == 'supplier_proposal' && $status == 2) {
4161
					$trigkey = 'SUPPLIER_PROPOSAL_SIGN'; // 2 = SupplierProposal::STATUS_SIGNED. Can't use constant into this generic class
4162
				}
4163
				if ($this->element == 'supplier_proposal' && $status == 3) {
4164
					$trigkey = 'SUPPLIER_PROPOSAL_REFUSE'; // 3 = SupplierProposal::STATUS_REFUSED. Can't use constant into this generic class
4165
				}
4166
				if ($this->element == 'supplier_proposal' && $status == 4) {
4167
					$trigkey = 'SUPPLIER_PROPOSAL_CLOSE'; // 4 = SupplierProposal::STATUS_CLOSED. Can't use constant into this generic class
4168
				}
4169
				if ($this->element == 'fichinter' && $status == 3) {
4170
					$trigkey = 'FICHINTER_CLASSIFY_DONE';
4171
				}
4172
				if ($this->element == 'fichinter' && $status == 2) {
4173
					$trigkey = 'FICHINTER_CLASSIFY_BILLED';
4174
				}
4175
				if ($this->element == 'fichinter' && $status == 1) {
4176
					$trigkey = 'FICHINTER_CLASSIFY_UNBILLED';
4177
				}
4178
			}
4179
4180
			if ($trigkey) {
4181
				// Call trigger
4182
				$result = $this->call_trigger($trigkey, $user);
4183
				if ($result < 0) {
4184
					$error++;
4185
				}
4186
				// End call triggers
4187
			}
4188
4189
			if (!$error) {
4190
				$this->db->commit();
4191
4192
				if (empty($savElementId)) {    // If the element we update was $this (so $elementId is null)
4193
					$this->statut = $status;
4194
					$this->status = $status;
4195
				}
4196
4197
				return 1;
4198
			} else {
4199
				$this->db->rollback();
4200
				dol_syslog(get_class($this)."::setStatut ".$this->error, LOG_ERR);
4201
				return -1;
4202
			}
4203
		} else {
4204
			$this->error = $this->db->lasterror();
4205
			$this->db->rollback();
4206
			return -1;
4207
		}
4208
	}
4209
4210
4211
	/**
4212
	 *  Load type of canvas of an object if it exists
4213
	 *
4214
	 *  @param      int		$id     Record id
4215
	 *  @param      string	$ref    Record ref
4216
	 *  @return		int				<0 if KO, 0 if nothing done, >0 if OK
4217
	 */
4218
	public function getCanvas($id = 0, $ref = '')
4219
	{
4220
		global $conf;
4221
4222
		if (empty($id) && empty($ref)) {
4223
			return 0;
4224
		}
4225
		if (!empty($conf->global->MAIN_DISABLE_CANVAS)) {
4226
			return 0; // To increase speed. Not enabled by default.
4227
		}
4228
4229
		// Clean parameters
4230
		$ref = trim($ref);
4231
4232
		$sql = "SELECT rowid, canvas";
4233
		$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
4234
		$sql .= " WHERE entity IN (".getEntity($this->element).")";
4235
		if (!empty($id)) {
4236
			$sql .= " AND rowid = ".((int) $id);
4237
		}
4238
		if (!empty($ref)) {
4239
			$sql .= " AND ref = '".$this->db->escape($ref)."'";
4240
		}
4241
4242
		$resql = $this->db->query($sql);
4243
		if ($resql) {
4244
			$obj = $this->db->fetch_object($resql);
4245
			if ($obj) {
4246
				$this->canvas = $obj->canvas;
4247
				return 1;
4248
			} else {
4249
				return 0;
4250
			}
4251
		} else {
4252
			dol_print_error($this->db);
4253
			return -1;
4254
		}
4255
	}
4256
4257
4258
	/**
4259
	 * 	Get special code of a line
4260
	 *
4261
	 * 	@param	int		$lineid		Id of line
4262
	 * 	@return	int					Special code
4263
	 */
4264
	public function getSpecialCode($lineid)
4265
	{
4266
		$sql = 'SELECT special_code FROM '.MAIN_DB_PREFIX.$this->table_element_line;
4267
		$sql .= ' WHERE rowid = '.((int) $lineid);
4268
		$resql = $this->db->query($sql);
4269
		if ($resql) {
4270
			$row = $this->db->fetch_row($resql);
4271
			return $row[0];
4272
		}
4273
	}
4274
4275
	/**
4276
	 *  Function to check if an object is used by others.
4277
	 *  Check is done into this->childtables. There is no check into llx_element_element.
4278
	 *
4279
	 *  @param	int		$id			Force id of object
4280
	 *  @return	int					<0 if KO, 0 if not used, >0 if already used
4281
	 */
4282
	public function isObjectUsed($id = 0)
4283
	{
4284
		global $langs;
4285
4286
		if (empty($id)) {
4287
			$id = $this->id;
4288
		}
4289
4290
		// Check parameters
4291
		if (!isset($this->childtables) || !is_array($this->childtables) || count($this->childtables) == 0) {
4292
			dol_print_error('Called isObjectUsed on a class with property this->childtables not defined');
4293
			return -1;
4294
		}
4295
4296
		$arraytoscan = $this->childtables;
4297
		// For backward compatibility, we check if array is old format array('table1', 'table2', ...)
4298
		$tmparray = array_keys($this->childtables);
4299
		if (is_numeric($tmparray[0])) {
4300
			$arraytoscan = array_flip($this->childtables);
4301
		}
4302
4303
		// Test if child exists
4304
		$haschild = 0;
4305
		foreach ($arraytoscan as $table => $elementname) {
4306
			//print $id.'-'.$table.'-'.$elementname.'<br>';
4307
			// Check if third party can be deleted
4308
			$sql = "SELECT COUNT(*) as nb from ".MAIN_DB_PREFIX.$table;
4309
			$sql .= " WHERE ".$this->fk_element." = ".((int) $id);
4310
			$resql = $this->db->query($sql);
4311
			if ($resql) {
4312
				$obj = $this->db->fetch_object($resql);
4313
				if ($obj->nb > 0) {
4314
					$langs->load("errors");
4315
					//print 'Found into table '.$table.', type '.$langs->transnoentitiesnoconv($elementname).', haschild='.$haschild;
4316
					$haschild += $obj->nb;
4317
					if (is_numeric($elementname)) {	// old usage
4318
						$this->errors[] = $langs->transnoentities("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $table);
4319
					} else // new usage: $elementname=Translation key
4320
					{
4321
						$this->errors[] = $langs->transnoentities("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($elementname));
4322
					}
4323
					break; // We found at least one, we stop here
4324
				}
4325
			} else {
4326
				$this->errors[] = $this->db->lasterror();
4327
				return -1;
4328
			}
4329
		}
4330
		if ($haschild > 0) {
4331
			$this->errors[] = "ErrorRecordHasChildren";
4332
			return $haschild;
4333
		} else {
4334
			return 0;
4335
		}
4336
	}
4337
4338
	/**
4339
	 *  Function to say how many lines object contains
4340
	 *
4341
	 *	@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
4342
	 *  @return	int						<0 if KO, 0 if no predefined products, nb of lines with predefined products if found
4343
	 */
4344
	public function hasProductsOrServices($predefined = -1)
4345
	{
4346
		$nb = 0;
4347
4348
		foreach ($this->lines as $key => $val) {
4349
			$qualified = 0;
4350
			if ($predefined == -1) {
4351
				$qualified = 1;
4352
			}
4353
			if ($predefined == 1 && $val->fk_product > 0) {
4354
				$qualified = 1;
4355
			}
4356
			if ($predefined == 0 && $val->fk_product <= 0) {
4357
				$qualified = 1;
4358
			}
4359
			if ($predefined == 2 && $val->fk_product > 0 && $val->product_type == 0) {
4360
				$qualified = 1;
4361
			}
4362
			if ($predefined == 3 && $val->fk_product > 0 && $val->product_type == 1) {
4363
				$qualified = 1;
4364
			}
4365
			if ($qualified) {
4366
				$nb++;
4367
			}
4368
		}
4369
		dol_syslog(get_class($this).'::hasProductsOrServices we found '.$nb.' qualified lines of products/servcies');
4370
		return $nb;
4371
	}
4372
4373
	/**
4374
	 * Function that returns the total amount HT of discounts applied for all lines.
4375
	 *
4376
	 * @return 	float|string			Total amout of discount
4377
	 */
4378
	public function getTotalDiscount()
4379
	{
4380
		if (!empty($this->table_element_line) ) {
4381
			$total_discount = 0.00;
4382
4383
			$sql = "SELECT subprice as pu_ht, qty, remise_percent, total_ht";
4384
			$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element_line;
4385
			$sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
4386
4387
			dol_syslog(get_class($this).'::getTotalDiscount', LOG_DEBUG);
4388
			$resql = $this->db->query($sql);
4389
			if ($resql) {
4390
				$num = $this->db->num_rows($resql);
4391
				$i = 0;
4392
				while ($i < $num) {
4393
					$obj = $this->db->fetch_object($resql);
4394
4395
					$pu_ht = $obj->pu_ht;
4396
					$qty = $obj->qty;
4397
					$total_ht = $obj->total_ht;
4398
4399
					$total_discount_line = floatval(price2num(($pu_ht * $qty) - $total_ht, 'MT'));
4400
					$total_discount += $total_discount_line;
4401
4402
					$i++;
4403
				}
4404
			}
4405
4406
			//print $total_discount; exit;
4407
			return price2num($total_discount);
4408
		}
4409
4410
		return null;
4411
	}
4412
4413
4414
	/**
4415
	 * Return into unit=0, the calculated total of weight and volume of all lines * qty
4416
	 * Calculate by adding weight and volume of each product line, so properties ->volume/volume_units/weight/weight_units must be loaded on line.
4417
	 *
4418
	 * @return  array                           array('weight'=>...,'volume'=>...)
4419
	 */
4420
	public function getTotalWeightVolume()
4421
	{
4422
		$totalWeight = 0;
4423
		$totalVolume = 0;
4424
		// defined for shipment only
4425
		$totalOrdered = '';
4426
		// defined for shipment only
4427
		$totalToShip = '';
4428
4429
		foreach ($this->lines as $line) {
4430
			if (isset($line->qty_asked)) {
4431
				if (empty($totalOrdered)) {
4432
					$totalOrdered = 0; // Avoid warning because $totalOrdered is ''
4433
				}
4434
				$totalOrdered += $line->qty_asked; // defined for shipment only
4435
			}
4436
			if (isset($line->qty_shipped)) {
4437
				if (empty($totalToShip)) {
4438
					$totalToShip = 0; // Avoid warning because $totalToShip is ''
4439
				}
4440
				$totalToShip += $line->qty_shipped; // defined for shipment only
4441
			} elseif ($line->element == 'commandefournisseurdispatch' && isset($line->qty)) {
4442
				if (empty($totalToShip)) {
4443
					$totalToShip = 0;
4444
				}
4445
				$totalToShip += $line->qty; // defined for reception only
4446
			}
4447
4448
			// Define qty, weight, volume, weight_units, volume_units
4449
			if ($this->element == 'shipping') {
4450
				// for shipments
4451
				$qty = $line->qty_shipped ? $line->qty_shipped : 0;
4452
			} else {
4453
				$qty = $line->qty ? $line->qty : 0;
4454
			}
4455
4456
			$weight = $line->weight ? $line->weight : 0;
4457
			($weight == 0 && !empty($line->product->weight)) ? $weight = $line->product->weight : 0;
4458
			$volume = $line->volume ? $line->volume : 0;
4459
			($volume == 0 && !empty($line->product->volume)) ? $volume = $line->product->volume : 0;
4460
4461
			$weight_units = $line->weight_units;
4462
			($weight_units == 0 && !empty($line->product->weight_units)) ? $weight_units = $line->product->weight_units : 0;
4463
			$volume_units = $line->volume_units;
4464
			($volume_units == 0 && !empty($line->product->volume_units)) ? $volume_units = $line->product->volume_units : 0;
4465
4466
			$weightUnit = 0;
4467
			$volumeUnit = 0;
4468
			if (!empty($weight_units)) {
4469
				$weightUnit = $weight_units;
4470
			}
4471
			if (!empty($volume_units)) {
4472
				$volumeUnit = $volume_units;
4473
			}
4474
4475
			if (empty($totalWeight)) {
4476
				$totalWeight = 0; // Avoid warning because $totalWeight is ''
4477
			}
4478
			if (empty($totalVolume)) {
4479
				$totalVolume = 0; // Avoid warning because $totalVolume is ''
4480
			}
4481
4482
			//var_dump($line->volume_units);
4483
			if ($weight_units < 50) {   // < 50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
4484
				$trueWeightUnit = pow(10, $weightUnit);
4485
				$totalWeight += $weight * $qty * $trueWeightUnit;
4486
			} else {
4487
				if ($weight_units == 99) {
4488
					// conversion 1 Pound = 0.45359237 KG
4489
					$trueWeightUnit = 0.45359237;
4490
					$totalWeight += $weight * $qty * $trueWeightUnit;
4491
				} elseif ($weight_units == 98) {
4492
					// conversion 1 Ounce = 0.0283495 KG
4493
					$trueWeightUnit = 0.0283495;
4494
					$totalWeight += $weight * $qty * $trueWeightUnit;
4495
				} else {
4496
					$totalWeight += $weight * $qty; // This may be wrong if we mix different units
4497
				}
4498
			}
4499
			if ($volume_units < 50) {   // >50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
4500
				//print $line->volume."x".$line->volume_units."x".($line->volume_units < 50)."x".$volumeUnit;
4501
				$trueVolumeUnit = pow(10, $volumeUnit);
4502
				//print $line->volume;
4503
				$totalVolume += $volume * $qty * $trueVolumeUnit;
4504
			} else {
4505
				$totalVolume += $volume * $qty; // This may be wrong if we mix different units
4506
			}
4507
		}
4508
4509
		return array('weight'=>$totalWeight, 'volume'=>$totalVolume, 'ordered'=>$totalOrdered, 'toship'=>$totalToShip);
4510
	}
4511
4512
4513
	/**
4514
	 *	Set extra parameters
4515
	 *
4516
	 *	@return	int      <0 if KO, >0 if OK
4517
	 */
4518
	public function setExtraParameters()
4519
	{
4520
		$this->db->begin();
4521
4522
		$extraparams = (!empty($this->extraparams) ? json_encode($this->extraparams) : null);
4523
4524
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
4525
		$sql .= " SET extraparams = ".(!empty($extraparams) ? "'".$this->db->escape($extraparams)."'" : "null");
4526
		$sql .= " WHERE rowid = ".((int) $this->id);
4527
4528
		dol_syslog(get_class($this)."::setExtraParameters", LOG_DEBUG);
4529
		$resql = $this->db->query($sql);
4530
		if (!$resql) {
4531
			$this->error = $this->db->lasterror();
4532
			$this->db->rollback();
4533
			return -1;
4534
		} else {
4535
			$this->db->commit();
4536
			return 1;
4537
		}
4538
	}
4539
4540
4541
	// --------------------
4542
	// TODO: All functions here must be redesigned and moved as they are not business functions but output functions
4543
	// --------------------
4544
4545
	/* This is to show add lines */
4546
4547
	/**
4548
	 *	Show add free and predefined products/services form
4549
	 *
4550
	 *  @param	int		        $dateSelector       1=Show also date range input fields
4551
	 *  @param	Societe			$seller				Object thirdparty who sell
4552
	 *  @param	Societe			$buyer				Object thirdparty who buy
4553
	 *  @param	string			$defaulttpldir		Directory where to find the template
4554
	 *	@return	void
4555
	 */
4556
	public function formAddObjectLine($dateSelector, $seller, $buyer, $defaulttpldir = '/core/tpl')
4557
	{
4558
		global $conf, $user, $langs, $object, $hookmanager, $extrafields;
4559
		global $form;
4560
4561
		// Line extrafield
4562
		if (!is_object($extrafields)) {
4563
			require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
4564
			$extrafields = new ExtraFields($this->db);
4565
		}
4566
		$extrafields->fetch_name_optionals_label($this->table_element_line);
4567
4568
		// Output template part (modules that overwrite templates must declare this into descriptor)
4569
		// Use global variables + $dateSelector + $seller and $buyer
4570
		// Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook 'formAddObjectLine'.
4571
		$dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
4572
		foreach ($dirtpls as $module => $reldir) {
4573
			if (!empty($module)) {
4574
				$tpl = dol_buildpath($reldir.'/objectline_create.tpl.php');
4575
			} else {
4576
				$tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_create.tpl.php';
4577
			}
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
4591
4592
	/* This is to show array of line of details */
4593
4594
4595
	/**
4596
	 *	Return HTML table for object lines
4597
	 *	TODO Move this into an output class file (htmlline.class.php)
4598
	 *	If lines are into a template, title must also be into a template
4599
	 *	But for the moment we don't know if it's possible as we keep a method available on overloaded objects.
4600
	 *
4601
	 *	@param	string		$action				Action code
4602
	 *	@param  string		$seller            	Object of seller third party
4603
	 *	@param  string  	$buyer             	Object of buyer third party
4604
	 *	@param	int			$selected		   	Object line selected
4605
	 *	@param  int	    	$dateSelector      	1=Show also date range input fields
4606
	 *  @param	string		$defaulttpldir		Directory where to find the template
4607
	 *	@return	void
4608
	 */
4609
	public function printObjectLines($action, $seller, $buyer, $selected = 0, $dateSelector = 0, $defaulttpldir = '/core/tpl')
4610
	{
4611
		global $conf, $hookmanager, $langs, $user, $form, $extrafields, $object;
4612
		// TODO We should not use global var for this
4613
		global $inputalsopricewithtax, $usemargins, $disableedit, $disablemove, $disableremove, $outputalsopricetotalwithtax;
4614
4615
		// Define usemargins
4616
		$usemargins = 0;
4617
		if (!empty($conf->margin->enabled) && !empty($this->element) && in_array($this->element, array('facture', 'facturerec', 'propal', 'commande'))) {
4618
			$usemargins = 1;
4619
		}
4620
4621
		$num = count($this->lines);
4622
4623
		// Line extrafield
4624
		if (!is_object($extrafields)) {
4625
			require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
4626
			$extrafields = new ExtraFields($this->db);
4627
		}
4628
		$extrafields->fetch_name_optionals_label($this->table_element_line);
4629
4630
		$parameters = array('num'=>$num, 'dateSelector'=>$dateSelector, 'seller'=>$seller, 'buyer'=>$buyer, 'selected'=>$selected, 'table_element_line'=>$this->table_element_line);
4631
		$reshook = $hookmanager->executeHooks('printObjectLineTitle', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4632
		if (empty($reshook)) {
4633
			// Output template part (modules that overwrite templates must declare this into descriptor)
4634
			// Use global variables + $dateSelector + $seller and $buyer
4635
			// Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook.
4636
			$dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
4637
			foreach ($dirtpls as $module => $reldir) {
4638
				if (!empty($module)) {
4639
					$tpl = dol_buildpath($reldir.'/objectline_title.tpl.php');
4640
				} else {
4641
					$tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_title.tpl.php';
4642
				}
4643
				if (empty($conf->file->strict_mode)) {
4644
					$res = @include $tpl;
4645
				} else {
4646
					$res = include $tpl; // for debug
4647
				}
4648
				if ($res) {
4649
					break;
4650
				}
4651
			}
4652
		}
4653
4654
		$i = 0;
4655
4656
		print "<!-- begin printObjectLines() --><tbody>\n";
4657
		foreach ($this->lines as $line) {
4658
			//Line extrafield
4659
			$line->fetch_optionals();
4660
4661
			//if (is_object($hookmanager) && (($line->product_type == 9 && ! empty($line->special_code)) || ! empty($line->fk_parent_line)))
4662
			if (is_object($hookmanager)) {   // Old code is commented on preceding line.
4663
				if (empty($line->fk_parent_line)) {
4664
					$parameters = array('line'=>$line, 'num'=>$num, 'i'=>$i, 'dateSelector'=>$dateSelector, 'seller'=>$seller, 'buyer'=>$buyer, 'selected'=>$selected, 'table_element_line'=>$line->table_element);
4665
					$reshook = $hookmanager->executeHooks('printObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4666
				} else {
4667
					$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);
4668
					$reshook = $hookmanager->executeHooks('printObjectSubLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4669
				}
4670
			}
4671
			if (empty($reshook)) {
4672
				$this->printObjectLine($action, $line, '', $num, $i, $dateSelector, $seller, $buyer, $selected, $extrafields, $defaulttpldir);
4673
			}
4674
4675
			$i++;
4676
		}
4677
		print "</tbody><!-- end printObjectLines() -->\n";
4678
	}
4679
4680
	/**
4681
	 *	Return HTML content of a detail line
4682
	 *	TODO Move this into an output class file (htmlline.class.php)
4683
	 *
4684
	 *	@param	string      		$action				GET/POST action
4685
	 *	@param  CommonObjectLine 	$line			    Selected object line to output
4686
	 *	@param  string	    		$var               	Is it a an odd line (true)
4687
	 *	@param  int		    		$num               	Number of line (0)
4688
	 *	@param  int		    		$i					I
4689
	 *	@param  int		    		$dateSelector      	1=Show also date range input fields
4690
	 *	@param  string	    		$seller            	Object of seller third party
4691
	 *	@param  string	    		$buyer             	Object of buyer third party
4692
	 *	@param	int					$selected		   	Object line selected
4693
	 *  @param  Extrafields			$extrafields		Object of extrafields
4694
	 *  @param	string				$defaulttpldir		Directory where to find the template (deprecated)
4695
	 *	@return	void
4696
	 */
4697
	public function printObjectLine($action, $line, $var, $num, $i, $dateSelector, $seller, $buyer, $selected = 0, $extrafields = null, $defaulttpldir = '/core/tpl')
4698
	{
4699
		global $conf, $langs, $user, $object, $hookmanager;
4700
		global $form;
4701
		global $object_rights, $disableedit, $disablemove, $disableremove; // TODO We should not use global var for this !
4702
4703
		$object_rights = $this->getRights();
4704
4705
		$element = $this->element;
4706
4707
		$text = '';
4708
		$description = '';
4709
4710
		// Line in view mode
4711
		if ($action != 'editline' || $selected != $line->id) {
4712
			// Product
4713
			if ($line->fk_product > 0) {
4714
				$product_static = new Product($this->db);
4715
				$product_static->fetch($line->fk_product);
4716
4717
				$product_static->ref = $line->ref; //can change ref in hook
4718
				$product_static->label = $line->label; //can change label in hook
4719
4720
				$text = $product_static->getNomUrl(1);
4721
4722
				// Define output language and label
4723
				if (!empty($conf->global->MAIN_MULTILANGS)) {
4724
					if (property_exists($this, 'socid') && !is_object($this->thirdparty)) {
4725
						dol_print_error('', 'Error: Method printObjectLine was called on an object and object->fetch_thirdparty was not done before');
4726
						return;
4727
					}
4728
4729
					$prod = new Product($this->db);
4730
					$prod->fetch($line->fk_product);
4731
4732
					$outputlangs = $langs;
4733
					$newlang = '';
4734
					if (empty($newlang) && GETPOST('lang_id', 'aZ09')) {
4735
						$newlang = GETPOST('lang_id', 'aZ09');
4736
					}
4737
					if (!empty($conf->global->PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE) && empty($newlang) && is_object($this->thirdparty)) {
4738
						$newlang = $this->thirdparty->default_lang; // To use language of customer
4739
					}
4740
					if (!empty($newlang)) {
4741
						$outputlangs = new Translate("", $conf);
4742
						$outputlangs->setDefaultLang($newlang);
4743
					}
4744
4745
					$label = (!empty($prod->multilangs[$outputlangs->defaultlang]["label"])) ? $prod->multilangs[$outputlangs->defaultlang]["label"] : $line->product_label;
4746
				} else {
4747
					$label = $line->product_label;
4748
				}
4749
4750
				$text .= ' - '.(!empty($line->label) ? $line->label : $label);
4751
				$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.
4752
			}
4753
4754
			$line->pu_ttc = price2num($line->subprice * (1 + ($line->tva_tx / 100)), 'MU');
4755
4756
			// Output template part (modules that overwrite templates must declare this into descriptor)
4757
			// Use global variables + $dateSelector + $seller and $buyer
4758
			// Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
4759
			$dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
4760
			foreach ($dirtpls as $module => $reldir) {
4761
				if (!empty($module)) {
4762
					$tpl = dol_buildpath($reldir.'/objectline_view.tpl.php');
4763
				} else {
4764
					$tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_view.tpl.php';
4765
				}
4766
4767
				if (empty($conf->file->strict_mode)) {
4768
					$res = @include $tpl;
4769
				} else {
4770
					$res = include $tpl; // for debug
4771
				}
4772
				if ($res) {
4773
					break;
4774
				}
4775
			}
4776
		}
4777
4778
		// Line in update mode
4779
		if ($this->statut == 0 && $action == 'editline' && $selected == $line->id) {
4780
			$label = (!empty($line->label) ? $line->label : (($line->fk_product > 0) ? $line->product_label : ''));
4781
4782
			$line->pu_ttc = price2num($line->subprice * (1 + ($line->tva_tx / 100)), 'MU');
4783
4784
			// Output template part (modules that overwrite templates must declare this into descriptor)
4785
			// Use global variables + $dateSelector + $seller and $buyer
4786
			// Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
4787
			$dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
4788
			foreach ($dirtpls as $module => $reldir) {
4789
				if (!empty($module)) {
4790
					$tpl = dol_buildpath($reldir.'/objectline_edit.tpl.php');
4791
				} else {
4792
					$tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_edit.tpl.php';
4793
				}
4794
4795
				if (empty($conf->file->strict_mode)) {
4796
					$res = @include $tpl;
4797
				} else {
4798
					$res = include $tpl; // for debug
4799
				}
4800
				if ($res) {
4801
					break;
4802
				}
4803
			}
4804
		}
4805
	}
4806
4807
4808
	/* This is to show array of line of details of source object */
4809
4810
4811
	/**
4812
	 * 	Return HTML table table of source object lines
4813
	 *  TODO Move this and previous function into output html class file (htmlline.class.php).
4814
	 *  If lines are into a template, title must also be into a template
4815
	 *  But for the moment we don't know if it's possible, so we keep the method available on overloaded objects.
4816
	 *
4817
	 *	@param	string		$restrictlist		''=All lines, 'services'=Restrict to services only
4818
	 *  @param  array       $selectedLines      Array of lines id for selected lines
4819
	 *  @return	void
4820
	 */
4821
	public function printOriginLinesList($restrictlist = '', $selectedLines = array())
4822
	{
4823
		global $langs, $hookmanager, $conf, $form;
4824
4825
		print '<tr class="liste_titre">';
4826
		print '<td>'.$langs->trans('Ref').'</td>';
4827
		print '<td>'.$langs->trans('Description').'</td>';
4828
		print '<td class="right">'.$langs->trans('VATRate').'</td>';
4829
		print '<td class="right">'.$langs->trans('PriceUHT').'</td>';
4830
		if (!empty($conf->multicurrency->enabled)) {
4831
			print '<td class="right">'.$langs->trans('PriceUHTCurrency').'</td>';
4832
		}
4833
		print '<td class="right">'.$langs->trans('Qty').'</td>';
4834
		if (!empty($conf->global->PRODUCT_USE_UNITS)) {
4835
			print '<td class="left">'.$langs->trans('Unit').'</td>';
4836
		}
4837
		print '<td class="right">'.$langs->trans('ReductionShort').'</td>';
4838
		print '<td class="center">'.$form->showCheckAddButtons('checkforselect', 1).'</td>';
4839
		print '</tr>';
4840
		$i = 0;
4841
4842
		if (!empty($this->lines)) {
4843
			foreach ($this->lines as $line) {
4844
				$reshook = 0;
4845
				//if (is_object($hookmanager) && (($line->product_type == 9 && !empty($line->special_code)) || !empty($line->fk_parent_line))) {
4846
				if (is_object($hookmanager)) {   // Old code is commented on preceding line.
4847
					if (empty($line->fk_parent_line)) {
4848
						$parameters = array('line'=>$line, 'i'=>$i, 'restrictlist'=>$restrictlist, 'selectedLines'=> $selectedLines);
4849
						$reshook = $hookmanager->executeHooks('printOriginObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $action seems to be never defined.
Loading history...
4850
					} else {
4851
						$parameters = array('line'=>$line, 'i'=>$i, 'restrictlist'=>$restrictlist, 'selectedLines'=> $selectedLines, 'fk_parent_line'=>$line->fk_parent_line);
4852
						$reshook = $hookmanager->executeHooks('printOriginObjectSubLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4853
					}
4854
				}
4855
				if (empty($reshook)) {
4856
					$this->printOriginLine($line, '', $restrictlist, '/core/tpl', $selectedLines);
4857
				}
4858
4859
				$i++;
4860
			}
4861
		}
4862
	}
4863
4864
	/**
4865
	 * 	Return HTML with a line of table array of source object lines
4866
	 *  TODO Move this and previous function into output html class file (htmlline.class.php).
4867
	 *  If lines are into a template, title must also be into a template
4868
	 *  But for the moment we don't know if it's possible as we keep a method available on overloaded objects.
4869
	 *
4870
	 * 	@param	CommonObjectLine	$line				Line
4871
	 * 	@param	string				$var				Var
4872
	 *	@param	string				$restrictlist		''=All lines, 'services'=Restrict to services only (strike line if not)
4873
	 *  @param	string				$defaulttpldir		Directory where to find the template
4874
	 *  @param  array       		$selectedLines      Array of lines id for selected lines
4875
	 * 	@return	void
4876
	 */
4877
	public function printOriginLine($line, $var, $restrictlist = '', $defaulttpldir = '/core/tpl', $selectedLines = array())
4878
	{
4879
		global $langs, $conf;
4880
4881
		//var_dump($line);
4882
		if (!empty($line->date_start)) {
4883
			$date_start = $line->date_start;
4884
		} else {
4885
			$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...
4886
			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...
4887
				$date_start = $line->date_debut_reel;
4888
			}
4889
		}
4890
		if (!empty($line->date_end)) {
4891
			$date_end = $line->date_end;
4892
		} else {
4893
			$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...
4894
			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...
4895
				$date_end = $line->date_fin_reel;
4896
			}
4897
		}
4898
4899
		$this->tpl['id'] = $line->id;
4900
4901
		$this->tpl['label'] = '';
4902
		if (!empty($line->fk_parent_line)) {
4903
			$this->tpl['label'] .= img_picto('', 'rightarrow');
4904
		}
4905
4906
		if (($line->info_bits & 2) == 2) {  // TODO Not sure this is used for source object
4907
			$discount = new DiscountAbsolute($this->db);
4908
			$discount->fk_soc = $this->socid;
4909
			$this->tpl['label'] .= $discount->getNomUrl(0, 'discount');
4910
		} elseif (!empty($line->fk_product)) {
4911
			$productstatic = new Product($this->db);
4912
			$productstatic->id = $line->fk_product;
4913
			$productstatic->ref = $line->ref;
4914
			$productstatic->type = $line->fk_product_type;
4915
			if (empty($productstatic->ref)) {
4916
				$line->fetch_product();
4917
				$productstatic = $line->product;
4918
			}
4919
4920
			$this->tpl['label'] .= $productstatic->getNomUrl(1);
4921
			$this->tpl['label'] .= ' - '.(!empty($line->label) ? $line->label : $line->product_label);
4922
			// Dates
4923
			if ($line->product_type == 1 && ($date_start || $date_end)) {
4924
				$this->tpl['label'] .= get_date_range($date_start, $date_end);
4925
			}
4926
		} else {
4927
			$this->tpl['label'] .= ($line->product_type == -1 ? '&nbsp;' : ($line->product_type == 1 ? img_object($langs->trans(''), 'service') : img_object($langs->trans(''), 'product')));
4928
			if (!empty($line->desc)) {
4929
				$this->tpl['label'] .= $line->desc;
4930
			} else {
4931
				$this->tpl['label'] .= ($line->label ? '&nbsp;'.$line->label : '');
4932
			}
4933
4934
			// Dates
4935
			if ($line->product_type == 1 && ($date_start || $date_end)) {
4936
				$this->tpl['label'] .= get_date_range($date_start, $date_end);
4937
			}
4938
		}
4939
4940
		if (!empty($line->desc)) {
4941
			if ($line->desc == '(CREDIT_NOTE)') {  // TODO Not sure this is used for source object
4942
				$discount = new DiscountAbsolute($this->db);
4943
				$discount->fetch($line->fk_remise_except);
4944
				$this->tpl['description'] = $langs->transnoentities("DiscountFromCreditNote", $discount->getNomUrl(0));
4945
			} elseif ($line->desc == '(DEPOSIT)') {  // TODO Not sure this is used for source object
4946
				$discount = new DiscountAbsolute($this->db);
4947
				$discount->fetch($line->fk_remise_except);
4948
				$this->tpl['description'] = $langs->transnoentities("DiscountFromDeposit", $discount->getNomUrl(0));
4949
			} elseif ($line->desc == '(EXCESS RECEIVED)') {
4950
				$discount = new DiscountAbsolute($this->db);
4951
				$discount->fetch($line->fk_remise_except);
4952
				$this->tpl['description'] = $langs->transnoentities("DiscountFromExcessReceived", $discount->getNomUrl(0));
4953
			} elseif ($line->desc == '(EXCESS PAID)') {
4954
				$discount = new DiscountAbsolute($this->db);
4955
				$discount->fetch($line->fk_remise_except);
4956
				$this->tpl['description'] = $langs->transnoentities("DiscountFromExcessPaid", $discount->getNomUrl(0));
4957
			} else {
4958
				$this->tpl['description'] = dol_trunc($line->desc, 60);
4959
			}
4960
		} else {
4961
			$this->tpl['description'] = '&nbsp;';
4962
		}
4963
4964
		// VAT Rate
4965
		$this->tpl['vat_rate'] = vatrate($line->tva_tx, true);
4966
		$this->tpl['vat_rate'] .= (($line->info_bits & 1) == 1) ? '*' : '';
4967
		if (!empty($line->vat_src_code) && !preg_match('/\(/', $this->tpl['vat_rate'])) {
4968
			$this->tpl['vat_rate'] .= ' ('.$line->vat_src_code.')';
4969
		}
4970
4971
		$this->tpl['price'] = price($line->subprice);
4972
		$this->tpl['multicurrency_price'] = price($line->multicurrency_subprice);
4973
		$this->tpl['qty'] = (($line->info_bits & 2) != 2) ? $line->qty : '&nbsp;';
4974
		if (!empty($conf->global->PRODUCT_USE_UNITS)) {
4975
			$this->tpl['unit'] = $langs->transnoentities($line->getLabelOfUnit('long'));
4976
		}
4977
		$this->tpl['remise_percent'] = (($line->info_bits & 2) != 2) ? vatrate($line->remise_percent, true) : '&nbsp;';
4978
4979
		// Is the line strike or not
4980
		$this->tpl['strike'] = 0;
4981
		if ($restrictlist == 'services' && $line->product_type != Product::TYPE_SERVICE) {
4982
			$this->tpl['strike'] = 1;
4983
		}
4984
4985
		// Output template part (modules that overwrite templates must declare this into descriptor)
4986
		// Use global variables + $dateSelector + $seller and $buyer
4987
		$dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
4988
		foreach ($dirtpls as $module => $reldir) {
4989
			if (!empty($module)) {
4990
				$tpl = dol_buildpath($reldir.'/originproductline.tpl.php');
4991
			} else {
4992
				$tpl = DOL_DOCUMENT_ROOT.$reldir.'/originproductline.tpl.php';
4993
			}
4994
4995
			if (empty($conf->file->strict_mode)) {
4996
				$res = @include $tpl;
4997
			} else {
4998
				$res = include $tpl; // for debug
4999
			}
5000
			if ($res) {
5001
				break;
5002
			}
5003
		}
5004
	}
5005
5006
5007
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5008
	/**
5009
	 *	Add resources to the current object : add entry into llx_element_resources
5010
	 *	Need $this->element & $this->id
5011
	 *
5012
	 *	@param		int		$resource_id		Resource id
5013
	 *	@param		string	$resource_type		'resource'
5014
	 *	@param		int		$busy				Busy or not
5015
	 *	@param		int		$mandatory			Mandatory or not
5016
	 *	@return		int							<=0 if KO, >0 if OK
5017
	 */
5018
	public function add_element_resource($resource_id, $resource_type, $busy = 0, $mandatory = 0)
5019
	{
5020
		// phpcs:enable
5021
		$this->db->begin();
5022
5023
		$sql = "INSERT INTO ".MAIN_DB_PREFIX."element_resources (";
5024
		$sql .= "resource_id";
5025
		$sql .= ", resource_type";
5026
		$sql .= ", element_id";
5027
		$sql .= ", element_type";
5028
		$sql .= ", busy";
5029
		$sql .= ", mandatory";
5030
		$sql .= ") VALUES (";
5031
		$sql .= $resource_id;
5032
		$sql .= ", '".$this->db->escape($resource_type)."'";
5033
		$sql .= ", '".$this->db->escape($this->id)."'";
5034
		$sql .= ", '".$this->db->escape($this->element)."'";
5035
		$sql .= ", '".$this->db->escape($busy)."'";
5036
		$sql .= ", '".$this->db->escape($mandatory)."'";
5037
		$sql .= ")";
5038
5039
		dol_syslog(get_class($this)."::add_element_resource", LOG_DEBUG);
5040
		if ($this->db->query($sql)) {
5041
			$this->db->commit();
5042
			return 1;
5043
		} else {
5044
			$this->error = $this->db->lasterror();
5045
			$this->db->rollback();
5046
			return  0;
5047
		}
5048
	}
5049
5050
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5051
	/**
5052
	 *    Delete a link to resource line
5053
	 *
5054
	 *    @param	int		$rowid			Id of resource line to delete
5055
	 *    @param	int		$element		element name (for trigger) TODO: use $this->element into commonobject class
5056
	 *    @param	int		$notrigger		Disable all triggers
5057
	 *    @return   int						>0 if OK, <0 if KO
5058
	 */
5059
	public function delete_resource($rowid, $element, $notrigger = 0)
5060
	{
5061
		// phpcs:enable
5062
		global $user;
5063
5064
		$this->db->begin();
5065
5066
		$sql = "DELETE FROM ".MAIN_DB_PREFIX."element_resources";
5067
		$sql .= " WHERE rowid = ".((int) $rowid);
5068
5069
		dol_syslog(get_class($this)."::delete_resource", LOG_DEBUG);
5070
5071
		$resql = $this->db->query($sql);
5072
		if (!$resql) {
5073
			$this->error = $this->db->lasterror();
5074
			$this->db->rollback();
5075
			return -1;
5076
		} else {
5077
			if (!$notrigger) {
5078
				$result = $this->call_trigger(strtoupper($element).'_DELETE_RESOURCE', $user);
5079
				if ($result < 0) {
5080
					$this->db->rollback();
5081
					return -1;
5082
				}
5083
			}
5084
			$this->db->commit();
5085
			return 1;
5086
		}
5087
	}
5088
5089
5090
	/**
5091
	 * Overwrite magic function to solve problem of cloning object that are kept as references
5092
	 *
5093
	 * @return void
5094
	 */
5095
	public function __clone()
5096
	{
5097
		// Force a copy of this->lines, otherwise it will point to same object.
5098
		if (isset($this->lines) && is_array($this->lines)) {
5099
			$nboflines = count($this->lines);
5100
			for ($i = 0; $i < $nboflines; $i++) {
5101
				$this->lines[$i] = clone $this->lines[$i];
5102
			}
5103
		}
5104
	}
5105
5106
	/**
5107
	 * Common function for all objects extending CommonObject for generating documents
5108
	 *
5109
	 * @param 	string 		$modelspath 	Relative folder where generators are placed
5110
	 * @param 	string 		$modele 		Generator to use. Caller must set it to obj->model_pdf or GETPOST('model_pdf','alpha') for example.
5111
	 * @param 	Translate 	$outputlangs 	Output language to use
5112
	 * @param 	int 		$hidedetails 	1 to hide details. 0 by default
5113
	 * @param 	int 		$hidedesc 		1 to hide product description. 0 by default
5114
	 * @param 	int 		$hideref 		1 to hide product reference. 0 by default
5115
	 * @param   null|array  $moreparams     Array to provide more information
5116
	 * @return 	int 						>0 if OK, <0 if KO
5117
	 * @see	addFileIntoDatabaseIndex()
5118
	 */
5119
	protected function commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams = null)
5120
	{
5121
		global $conf, $langs, $user, $hookmanager, $action;
5122
5123
		$srctemplatepath = '';
5124
5125
		$parameters = array('modelspath'=>$modelspath, 'modele'=>$modele, 'outputlangs'=>$outputlangs, 'hidedetails'=>$hidedetails, 'hidedesc'=>$hidedesc, 'hideref'=>$hideref, 'moreparams'=>$moreparams);
5126
		$reshook = $hookmanager->executeHooks('commonGenerateDocument', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5127
5128
		if (empty($reshook)) {
5129
			dol_syslog("commonGenerateDocument modele=".$modele." outputlangs->defaultlang=".(is_object($outputlangs) ? $outputlangs->defaultlang : 'null'));
5130
5131
			if (empty($modele)) {
5132
				$this->error = 'BadValueForParameterModele';
5133
				return -1;
5134
			}
5135
5136
			// Increase limit for PDF build
5137
			$err = error_reporting();
5138
			error_reporting(0);
5139
			@set_time_limit(120);
5140
			error_reporting($err);
5141
5142
			// If selected model is a filename template (then $modele="modelname" or "modelname:filename")
5143
			$tmp = explode(':', $modele, 2);
5144
			if (!empty($tmp[1])) {
5145
				$modele = $tmp[0];
5146
				$srctemplatepath = $tmp[1];
5147
			}
5148
5149
			// Search template files
5150
			$file = '';
5151
			$classname = '';
5152
			$filefound = '';
5153
			$dirmodels = array('/');
5154
			if (is_array($conf->modules_parts['models'])) {
5155
				$dirmodels = array_merge($dirmodels, $conf->modules_parts['models']);
5156
			}
5157
			foreach ($dirmodels as $reldir) {
5158
				foreach (array('doc', 'pdf') as $prefix) {
5159
					if (in_array(get_class($this), array('Adherent'))) {
5160
						// Member module use prefix_modele.class.php
5161
						$file = $prefix."_".$modele.".class.php";
5162
					} else {
5163
						// Other module use prefix_modele.modules.php
5164
						$file = $prefix."_".$modele.".modules.php";
5165
					}
5166
5167
					// On verifie l'emplacement du modele
5168
					$file = dol_buildpath($reldir.$modelspath.$file, 0);
5169
					if (file_exists($file)) {
5170
						$filefound = $file;
5171
						$classname = $prefix.'_'.$modele;
5172
						break;
5173
					}
5174
				}
5175
				if ($filefound) {
5176
					break;
5177
				}
5178
			}
5179
5180
			// If generator was found
5181
			if ($filefound) {
5182
				global $db; // Required to solve a conception default making an include of code using $db instead of $this->db just after.
5183
5184
				require_once $file;
5185
5186
				$obj = new $classname($this->db);
5187
5188
				// If generator is ODT, we must have srctemplatepath defined, if not we set it.
5189
				if ($obj->type == 'odt' && empty($srctemplatepath)) {
5190
					$varfortemplatedir = $obj->scandir;
5191
					if ($varfortemplatedir && !empty($conf->global->$varfortemplatedir)) {
5192
						$dirtoscan = $conf->global->$varfortemplatedir;
5193
5194
						$listoffiles = array();
5195
5196
						// Now we add first model found in directories scanned
5197
						$listofdir = explode(',', $dirtoscan);
5198
						foreach ($listofdir as $key => $tmpdir) {
5199
							$tmpdir = trim($tmpdir);
5200
							$tmpdir = preg_replace('/DOL_DATA_ROOT/', DOL_DATA_ROOT, $tmpdir);
5201
							if (!$tmpdir) {
5202
								unset($listofdir[$key]);
5203
								continue;
5204
							}
5205
							if (is_dir($tmpdir)) {
5206
								$tmpfiles = dol_dir_list($tmpdir, 'files', 0, '\.od(s|t)$', '', 'name', SORT_ASC, 0);
5207
								if (count($tmpfiles)) {
5208
									$listoffiles = array_merge($listoffiles, $tmpfiles);
5209
								}
5210
							}
5211
						}
5212
5213
						if (count($listoffiles)) {
5214
							foreach ($listoffiles as $record) {
5215
								$srctemplatepath = $record['fullname'];
5216
								break;
5217
							}
5218
						}
5219
					}
5220
5221
					if (empty($srctemplatepath)) {
5222
						$this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotDefined';
5223
						return -1;
5224
					}
5225
				}
5226
5227
				if ($obj->type == 'odt' && !empty($srctemplatepath)) {
5228
					if (!dol_is_file($srctemplatepath)) {
5229
						dol_syslog("Failed to locate template file ".$srctemplatepath, LOG_WARNING);
5230
						$this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotFound';
5231
						return -1;
5232
					}
5233
				}
5234
5235
				// We save charset_output to restore it because write_file can change it if needed for
5236
				// output format that does not support UTF8.
5237
				$sav_charset_output = $outputlangs->charset_output;
5238
5239
				if (in_array(get_class($this), array('Adherent'))) {
5240
					$arrayofrecords = array(); // The write_file of templates of adherent class need this var
5241
					$resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, 'member', 1, $moreparams);
5242
				} else {
5243
					 $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, $hidedetails, $hidedesc, $hideref, $moreparams);
5244
				}
5245
				// After call of write_file $obj->result['fullpath'] is set with generated file. It will be used to update the ECM database index.
5246
5247
				if ($resultwritefile > 0) {
5248
					$outputlangs->charset_output = $sav_charset_output;
5249
5250
					// We delete old preview
5251
					require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
5252
					dol_delete_preview($this);
5253
5254
					// Index file in database
5255
					if (!empty($obj->result['fullpath'])) {
5256
						$destfull = $obj->result['fullpath'];
5257
						$upload_dir = dirname($destfull);
5258
						$destfile = basename($destfull);
5259
						$rel_dir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $upload_dir);
5260
5261
						if (!preg_match('/[\\/]temp[\\/]|[\\/]thumbs|\.meta$/', $rel_dir)) {     // If not a tmp dir
5262
							$filename = basename($destfile);
5263
							$rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
5264
							$rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
5265
5266
							include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
5267
							$ecmfile = new EcmFiles($this->db);
5268
							$result = $ecmfile->fetch(0, '', ($rel_dir ? $rel_dir.'/' : '').$filename);
5269
5270
							 // Set the public "share" key
5271
							$setsharekey = false;
5272
							if ($this->element == 'propal') {
5273
								$useonlinesignature = $conf->global->MAIN_FEATURES_LEVEL; // Replace this with 1 when feature to make online signature is ok
5274
								if ($useonlinesignature) {
5275
									$setsharekey = true;
5276
								}
5277
								if (!empty($conf->global->PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD)) {
5278
									$setsharekey = true;
5279
								}
5280
							}
5281
							if ($this->element == 'commande' && !empty($conf->global->ORDER_ALLOW_EXTERNAL_DOWNLOAD)) {
5282
								$setsharekey = true;
5283
							}
5284
							if ($this->element == 'facture' && !empty($conf->global->INVOICE_ALLOW_EXTERNAL_DOWNLOAD)) {
5285
								$setsharekey = true;
5286
							}
5287
							if ($this->element == 'bank_account' && !empty($conf->global->BANK_ACCOUNT_ALLOW_EXTERNAL_DOWNLOAD)) {
5288
								$setsharekey = true;
5289
							}
5290
							if ($this->element == 'contrat' && !empty($conf->global->CONTRACT_ALLOW_EXTERNAL_DOWNLOAD)) {
5291
								$setsharekey = true;
5292
							}
5293
							if ($this->element == 'supplier_proposal' && !empty($conf->global->SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD)) {
5294
								$setsharekey = true;
5295
							}
5296
5297
							if ($setsharekey) {
5298
								if (empty($ecmfile->share)) {	// Because object not found or share not set yet
5299
									require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
5300
									$ecmfile->share = getRandomPassword(true);
5301
								}
5302
							}
5303
5304
							if ($result > 0) {
5305
								$ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
5306
								$ecmfile->fullpath_orig = '';
5307
								$ecmfile->gen_or_uploaded = 'generated';
5308
								$ecmfile->description = ''; // indexed content
5309
								$ecmfile->keywords = ''; // keyword content
5310
								$result = $ecmfile->update($user);
5311
								if ($result < 0) {
5312
									setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
5313
								}
5314
							} else {
5315
								$ecmfile->entity = $conf->entity;
5316
								$ecmfile->filepath = $rel_dir;
5317
								$ecmfile->filename = $filename;
5318
								$ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
5319
								$ecmfile->fullpath_orig = '';
5320
								$ecmfile->gen_or_uploaded = 'generated';
5321
								$ecmfile->description = ''; // indexed content
5322
								$ecmfile->keywords = ''; // keyword content
5323
								$ecmfile->src_object_type = $this->table_element;
5324
								$ecmfile->src_object_id   = $this->id;
5325
5326
								$result = $ecmfile->create($user);
5327
								if ($result < 0) {
5328
									setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
5329
								}
5330
							}
5331
5332
							/*$this->result['fullname']=$destfull;
5333
							$this->result['filepath']=$ecmfile->filepath;
5334
							$this->result['filename']=$ecmfile->filename;*/
5335
							//var_dump($obj->update_main_doc_field);exit;
5336
5337
							// Update the last_main_doc field into main object (if document generator has property ->update_main_doc_field set)
5338
							$update_main_doc_field = 0;
5339
							if (!empty($obj->update_main_doc_field)) {
5340
								$update_main_doc_field = 1;
5341
							}
5342
							if ($update_main_doc_field && !empty($this->table_element)) {
5343
								$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET last_main_doc = '".$this->db->escape($ecmfile->filepath."/".$ecmfile->filename)."'";
5344
								$sql .= " WHERE rowid = ".((int) $this->id);
5345
5346
								$resql = $this->db->query($sql);
5347
								if (!$resql) {
5348
									dol_print_error($this->db);
5349
								} else {
5350
									$this->last_main_doc = $ecmfile->filepath.'/'.$ecmfile->filename;
5351
								}
5352
							}
5353
						}
5354
					} else {
5355
						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);
5356
					}
5357
5358
					// Success in building document. We build meta file.
5359
					dol_meta_create($this);
5360
5361
					return 1;
5362
				} else {
5363
					$outputlangs->charset_output = $sav_charset_output;
5364
					dol_print_error($this->db, "Error generating document for ".__CLASS__.". Error: ".$obj->error, $obj->errors);
5365
					return -1;
5366
				}
5367
			} else {
5368
				if (!$filefound) {
5369
					$this->error = $langs->trans("Error").' Failed to load doc generator with modelpaths='.$modelspath.' - modele='.$modele;
5370
					dol_print_error('', $this->error);
5371
				} else {
5372
					$this->error = $langs->trans("Error")." ".$langs->trans("ErrorFileDoesNotExists", $filefound);
5373
					dol_print_error('', $this->error);
5374
				}
5375
				return -1;
5376
			}
5377
		} else {
5378
			return $reshook;
5379
		}
5380
	}
5381
5382
	/**
5383
	 *  Build thumb
5384
	 *  @todo Move this into files.lib.php
5385
	 *
5386
	 *  @param      string	$file           Path file in UTF8 to original file to create thumbs from.
5387
	 *	@return		void
5388
	 */
5389
	public function addThumbs($file)
5390
	{
5391
		global $maxwidthsmall, $maxheightsmall, $maxwidthmini, $maxheightmini, $quality;
5392
5393
		require_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php'; // This define also $maxwidthsmall, $quality, ...
5394
5395
		$file_osencoded = dol_osencode($file);
5396
		if (file_exists($file_osencoded)) {
5397
			// Create small thumbs for company (Ratio is near 16/9)
5398
			// Used on logon for example
5399
			vignette($file_osencoded, $maxwidthsmall, $maxheightsmall, '_small', $quality);
5400
5401
			// Create mini thumbs for company (Ratio is near 16/9)
5402
			// Used on menu or for setup page for example
5403
			vignette($file_osencoded, $maxwidthmini, $maxheightmini, '_mini', $quality);
5404
		}
5405
	}
5406
5407
5408
	/* Functions common to commonobject and commonobjectline */
5409
5410
	/* For default values */
5411
5412
	/**
5413
	 * Return the default value to use for a field when showing the create form of object.
5414
	 * Return values in this order:
5415
	 * 1) If parameter is available into POST, we return it first.
5416
	 * 2) If not but an alternate value was provided as parameter of function, we return it.
5417
	 * 3) If not but a constant $conf->global->OBJECTELEMENT_FIELDNAME is set, we return it (It is better to use the dedicated table).
5418
	 * 4) Return value found into database (TODO No yet implemented)
5419
	 *
5420
	 * @param   string              $fieldname          Name of field
5421
	 * @param   string              $alternatevalue     Alternate value to use
5422
	 * @return  string|string[]                         Default value (can be an array if the GETPOST return an array)
5423
	 **/
5424
	public function getDefaultCreateValueFor($fieldname, $alternatevalue = null)
5425
	{
5426
		global $conf, $_POST;
5427
5428
		// If param here has been posted, we use this value first.
5429
		if (GETPOSTISSET($fieldname)) {
5430
			return GETPOST($fieldname, 'alphanohtml', 3);
5431
		}
5432
5433
		if (isset($alternatevalue)) {
5434
			return $alternatevalue;
5435
		}
5436
5437
		$newelement = $this->element;
5438
		if ($newelement == 'facture') {
5439
			$newelement = 'invoice';
5440
		}
5441
		if ($newelement == 'commande') {
5442
			$newelement = 'order';
5443
		}
5444
		if (empty($newelement)) {
5445
			dol_syslog("Ask a default value using common method getDefaultCreateValueForField on an object with no property ->element defined. Return empty string.", LOG_WARNING);
5446
			return '';
5447
		}
5448
5449
		$keyforfieldname = strtoupper($newelement.'_DEFAULT_'.$fieldname);
5450
		//var_dump($keyforfieldname);
5451
		if (isset($conf->global->$keyforfieldname)) {
5452
			return $conf->global->$keyforfieldname;
5453
		}
5454
5455
		// TODO Ad here a scan into table llx_overwrite_default with a filter on $this->element and $fieldname
5456
	}
5457
5458
5459
	/* For triggers */
5460
5461
5462
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5463
	/**
5464
	 * Call trigger based on this instance.
5465
	 * Some context information may also be provided into array property this->context.
5466
	 * NB:  Error from trigger are stacked in interface->errors
5467
	 * NB2: If return code of triggers are < 0, action calling trigger should cancel all transaction.
5468
	 *
5469
	 * @param   string    $triggerName   trigger's name to execute
5470
	 * @param   User      $user           Object user
5471
	 * @return  int                       Result of run_triggers
5472
	 */
5473
	public function call_trigger($triggerName, $user)
5474
	{
5475
		// phpcs:enable
5476
		global $langs, $conf;
5477
5478
		if (!is_object($langs)) {	// If lang was not defined, we set it. It is required by run_triggers.
5479
			include_once DOL_DOCUMENT_ROOT.'/core/class/translate.class.php';
5480
			$langs = new Translate('', $conf);
5481
		}
5482
5483
		include_once DOL_DOCUMENT_ROOT.'/core/class/interfaces.class.php';
5484
		$interface = new Interfaces($this->db);
5485
		$result = $interface->run_triggers($triggerName, $this, $user, $langs, $conf);
5486
5487
		if ($result < 0) {
5488
			if (!empty($this->errors)) {
5489
				$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.
5490
			} else {
5491
				$this->errors = $interface->errors;
5492
			}
5493
		}
5494
		return $result;
5495
	}
5496
5497
5498
	/* Functions for data in other language */
5499
5500
5501
	/**
5502
	 *  Function to get alternative languages of a data into $this->array_languages
5503
	 *  This method is NOT called by method fetch of objects but must be called separately.
5504
	 *
5505
	 *  @return	int						<0 if error, 0 if no values of alternative languages to find nor found, 1 if a value was found and loaded
5506
	 *  @see fetch_optionnals()
5507
	 */
5508
	public function fetchValuesForExtraLanguages()
5509
	{
5510
		// To avoid SQL errors. Probably not the better solution though
5511
		if (!$this->element) {
5512
			return 0;
5513
		}
5514
		if (!($this->id > 0)) {
5515
			return 0;
5516
		}
5517
		if (is_array($this->array_languages)) {
5518
			return 1;
5519
		}
5520
5521
		$this->array_languages = array();
5522
5523
		$element = $this->element;
5524
		if ($element == 'categorie') {
5525
			$element = 'categories'; // For compatibility
5526
		}
5527
5528
		// Request to get translation values for object
5529
		$sql = "SELECT rowid, property, lang , value";
5530
		$sql .= " FROM ".MAIN_DB_PREFIX."object_lang";
5531
		$sql .= " WHERE type_object = '".$this->db->escape($element)."'";
5532
		$sql .= " AND fk_object = ".((int) $this->id);
5533
5534
		//dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG);		// Too verbose
5535
		$resql = $this->db->query($sql);
5536
		if ($resql) {
5537
			$numrows = $this->db->num_rows($resql);
5538
			if ($numrows) {
5539
				$i = 0;
5540
				while ($i < $numrows) {
5541
					$obj = $this->db->fetch_object($resql);
5542
					$key = $obj->property;
5543
					$value = $obj->value;
5544
					$codelang = $obj->lang;
5545
					$type = $this->fields[$key]['type'];
5546
5547
					// we can add this attribute to object
5548
					if (preg_match('/date/', $type)) {
5549
						$this->array_languages[$key][$codelang] = $this->db->jdate($value);
5550
					} else {
5551
						$this->array_languages[$key][$codelang] = $value;
5552
					}
5553
5554
					$i++;
5555
				}
5556
			}
5557
5558
			$this->db->free($resql);
5559
5560
			if ($numrows) {
5561
				return $numrows;
5562
			} else {
5563
				return 0;
5564
			}
5565
		} else {
5566
			dol_print_error($this->db);
5567
			return -1;
5568
		}
5569
	}
5570
5571
	/**
5572
	 * Fill array_options property of object by extrafields value (using for data sent by forms)
5573
	 *
5574
	 * @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.
5575
	 * @return	int						1 if array_options set, 0 if no value, -1 if error (field required missing for example)
5576
	 */
5577
	public function setValuesForExtraLanguages($onlykey = '')
5578
	{
5579
		global $_POST, $langs;
5580
5581
		// Get extra fields
5582
		foreach ($_POST as $postfieldkey => $postfieldvalue) {
5583
			$tmparray = explode('-', $postfieldkey);
5584
			if ($tmparray[0] != 'field') {
5585
				continue;
5586
			}
5587
5588
			$element = $tmparray[1];
5589
			$key = $tmparray[2];
5590
			$codelang = $tmparray[3];
5591
			//var_dump("postfieldkey=".$postfieldkey." element=".$element." key=".$key." codelang=".$codelang);
5592
5593
			if (!empty($onlykey) && $key != $onlykey) {
5594
				continue;
5595
			}
5596
			if ($element != $this->element) {
5597
				continue;
5598
			}
5599
5600
			$key_type = $this->fields[$key]['type'];
5601
5602
			$enabled = 1;
5603
			if (isset($this->fields[$key]['enabled'])) {
5604
				$enabled = dol_eval($this->fields[$key]['enabled'], 1);
5605
			}
5606
			/*$perms = 1;
5607
			if (isset($this->fields[$key]['perms']))
5608
			{
5609
				$perms = dol_eval($this->fields[$key]['perms'], 1);
5610
			}*/
5611
			if (empty($enabled)) {
5612
				continue;
5613
			}
5614
			//if (empty($perms)) continue;
5615
5616
			if (in_array($key_type, array('date'))) {
5617
				// Clean parameters
5618
				// TODO GMT date in memory must be GMT so we should add gm=true in parameters
5619
				$value_key = dol_mktime(0, 0, 0, $_POST[$postfieldkey."month"], $_POST[$postfieldkey."day"], $_POST[$postfieldkey."year"]);
5620
			} elseif (in_array($key_type, array('datetime'))) {
5621
				// Clean parameters
5622
				// TODO GMT date in memory must be GMT so we should add gm=true in parameters
5623
				$value_key = dol_mktime($_POST[$postfieldkey."hour"], $_POST[$postfieldkey."min"], 0, $_POST[$postfieldkey."month"], $_POST[$postfieldkey."day"], $_POST[$postfieldkey."year"]);
5624
			} elseif (in_array($key_type, array('checkbox', 'chkbxlst'))) {
5625
				$value_arr = GETPOST($postfieldkey, 'array'); // check if an array
5626
				if (!empty($value_arr)) {
5627
					$value_key = implode(',', $value_arr);
5628
				} else {
5629
					$value_key = '';
5630
				}
5631
			} elseif (in_array($key_type, array('price', 'double'))) {
5632
				$value_arr = GETPOST($postfieldkey, 'alpha');
5633
				$value_key = price2num($value_arr);
5634
			} else {
5635
				$value_key = GETPOST($postfieldkey);
5636
				if (in_array($key_type, array('link')) && $value_key == '-1') {
5637
					$value_key = '';
5638
				}
5639
			}
5640
5641
			$this->array_languages[$key][$codelang] = $value_key;
5642
5643
			/*if ($nofillrequired) {
5644
				$langs->load('errors');
5645
				setEventMessages($langs->trans('ErrorFieldsRequired').' : '.implode(', ', $error_field_required), null, 'errors');
5646
				return -1;
5647
			}*/
5648
		}
5649
5650
		return 1;
5651
	}
5652
5653
5654
	/* Functions for extrafields */
5655
5656
	/**
5657
	 * Function to make a fetch but set environment to avoid to load computed values before.
5658
	 *
5659
	 * @param	int		$id			ID of object
5660
	 * @return	int					>0 if OK, 0 if not found, <0 if KO
5661
	 */
5662
	public function fetchNoCompute($id)
5663
	{
5664
		global $conf;
5665
5666
		$savDisableCompute = $conf->disable_compute;
5667
		$conf->disable_compute = 1;
5668
5669
		$ret = $this->fetch($id);
5670
5671
		$conf->disable_compute = $savDisableCompute;
5672
5673
		return $ret;
5674
	}
5675
5676
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5677
	/**
5678
	 *  Function to get extra fields of an object into $this->array_options
5679
	 *  This method is in most cases called by method fetch of objects but you can call it separately.
5680
	 *
5681
	 *  @param	int		$rowid			Id of line. Use the id of object if not defined. Deprecated. Function must be called without parameters.
5682
	 *  @param  array	$optionsArray   Array resulting of call of extrafields->fetch_name_optionals_label(). Deprecated. Function must be called without parameters.
5683
	 *  @return	int						<0 if error, 0 if no values of extrafield to find nor found, 1 if an attribute is found and value loaded
5684
	 *  @see fetchValuesForExtraLanguages()
5685
	 */
5686
	public function fetch_optionals($rowid = null, $optionsArray = null)
5687
	{
5688
		// phpcs:enable
5689
		global $conf, $extrafields;
5690
5691
		if (empty($rowid)) {
5692
			$rowid = $this->id;
5693
		}
5694
		if (empty($rowid) && isset($this->rowid)) {
5695
			$rowid = $this->rowid; // deprecated
5696
		}
5697
5698
		// To avoid SQL errors. Probably not the better solution though
5699
		if (!$this->table_element) {
5700
			return 0;
5701
		}
5702
5703
		$this->array_options = array();
5704
5705
		if (!is_array($optionsArray)) {
5706
			// If $extrafields is not a known object, we initialize it. Best practice is to have $extrafields defined into card.php or list.php page.
5707
			if (!isset($extrafields) || !is_object($extrafields)) {
5708
				require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5709
				$extrafields = new ExtraFields($this->db);
5710
			}
5711
5712
			// Load array of extrafields for elementype = $this->table_element
5713
			if (empty($extrafields->attributes[$this->table_element]['loaded'])) {
5714
				$extrafields->fetch_name_optionals_label($this->table_element);
5715
			}
5716
			$optionsArray = (!empty($extrafields->attributes[$this->table_element]['label']) ? $extrafields->attributes[$this->table_element]['label'] : null);
5717
		} else {
5718
			global $extrafields;
5719
			dol_syslog("Warning: fetch_optionals was called with param optionsArray defined when you should pass null now", LOG_WARNING);
5720
		}
5721
5722
		$table_element = $this->table_element;
5723
		if ($table_element == 'categorie') {
5724
			$table_element = 'categories'; // For compatibility
5725
		}
5726
5727
		// Request to get complementary values
5728
		if (is_array($optionsArray) && count($optionsArray) > 0) {
5729
			$sql = "SELECT rowid";
5730
			foreach ($optionsArray as $name => $label) {
5731
				if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || $extrafields->attributes[$this->table_element]['type'][$name] != 'separate') {
5732
					$sql .= ", ".$name;
5733
				}
5734
			}
5735
			$sql .= " FROM ".MAIN_DB_PREFIX.$table_element."_extrafields";
5736
			$sql .= " WHERE fk_object = ".((int) $rowid);
5737
5738
			//dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG);		// Too verbose
5739
			$resql = $this->db->query($sql);
5740
			if ($resql) {
5741
				$numrows = $this->db->num_rows($resql);
5742
				if ($numrows) {
5743
					$tab = $this->db->fetch_array($resql);
5744
5745
					foreach ($tab as $key => $value) {
5746
						// 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)
5747
						if ($key != 'rowid' && $key != 'tms' && $key != 'fk_member' && !is_int($key)) {
5748
							// we can add this attribute to object
5749
							if (!empty($extrafields) && in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date', 'datetime'))) {
5750
								//var_dump($extrafields->attributes[$this->table_element]['type'][$key]);
5751
								$this->array_options["options_".$key] = $this->db->jdate($value);
5752
							} else {
5753
								$this->array_options["options_".$key] = $value;
5754
							}
5755
5756
							//var_dump('key '.$key.' '.$value.' type='.$extrafields->attributes[$this->table_element]['type'][$key].' '.$this->array_options["options_".$key]);
5757
						}
5758
					}
5759
5760
					// If field is a computed field, value must become result of compute
5761
					foreach ($tab as $key => $value) {
5762
						if (!empty($extrafields) && !empty($extrafields->attributes[$this->table_element]['computed'][$key])) {
5763
							//var_dump($conf->disable_compute);
5764
							if (empty($conf->disable_compute)) {
5765
								$this->array_options["options_".$key] = dol_eval($extrafields->attributes[$this->table_element]['computed'][$key], 1, 0);
5766
							}
5767
						}
5768
					}
5769
				}
5770
5771
				$this->db->free($resql);
5772
5773
				if ($numrows) {
5774
					return $numrows;
5775
				} else {
5776
					return 0;
5777
				}
5778
			} else {
5779
				$this->errors[]=$this->db->lasterror;
5780
				return -1;
5781
			}
5782
		}
5783
		return 0;
5784
	}
5785
5786
	/**
5787
	 *	Delete all extra fields values for the current object.
5788
	 *
5789
	 *  @return	int		<0 if KO, >0 if OK
5790
	 *  @see deleteExtraLanguages(), insertExtraField(), updateExtraField(), setValueFrom()
5791
	 */
5792
	public function deleteExtraFields()
5793
	{
5794
		global $conf;
5795
5796
		if (!empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) {
5797
			return 0;
5798
		}
5799
5800
		$this->db->begin();
5801
5802
		$table_element = $this->table_element;
5803
		if ($table_element == 'categorie') {
5804
			$table_element = 'categories'; // For compatibility
5805
		}
5806
5807
		dol_syslog(get_class($this)."::deleteExtraFields delete", LOG_DEBUG);
5808
5809
		$sql_del = "DELETE FROM ".MAIN_DB_PREFIX.$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
5810
5811
		$resql = $this->db->query($sql_del);
5812
		if (!$resql) {
5813
			$this->error = $this->db->lasterror();
5814
			$this->db->rollback();
5815
			return -1;
5816
		} else {
5817
			$this->db->commit();
5818
			return 1;
5819
		}
5820
	}
5821
5822
	/**
5823
	 *	Add/Update all extra fields values for the current object.
5824
	 *  Data to describe values to insert/update are stored into $this->array_options=array('options_codeforfield1'=>'valueforfield1', 'options_codeforfield2'=>'valueforfield2', ...)
5825
	 *  This function delete record with all extrafields and insert them again from the array $this->array_options.
5826
	 *
5827
	 *  @param	string		$trigger		If defined, call also the trigger (for example COMPANY_MODIFY)
5828
	 *  @param	User		$userused		Object user
5829
	 *  @return int 						-1=error, O=did nothing, 1=OK
5830
	 *  @see insertExtraLanguages(), updateExtraField(), deleteExtraField(), setValueFrom()
5831
	 */
5832
	public function insertExtraFields($trigger = '', $userused = null)
5833
	{
5834
		global $conf, $langs, $user;
5835
5836
		if (!empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) {
5837
			return 0;
5838
		}
5839
5840
		if (empty($userused)) {
5841
			$userused = $user;
5842
		}
5843
5844
		$error = 0;
5845
5846
		if (!empty($this->array_options)) {
5847
			// Check parameters
5848
			$langs->load('admin');
5849
			require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5850
			$extrafields = new ExtraFields($this->db);
5851
			$target_extrafields = $extrafields->fetch_name_optionals_label($this->table_element);
5852
5853
			// Eliminate copied source object extra fields that do not exist in target object
5854
			$new_array_options = array();
5855
			foreach ($this->array_options as $key => $value) {
5856
				if (in_array(substr($key, 8), array_keys($target_extrafields))) {	// We remove the 'options_' from $key for test
5857
					$new_array_options[$key] = $value;
5858
				} elseif (in_array($key, array_keys($target_extrafields))) {		// We test on $key that does not contains the 'options_' prefix
5859
					$new_array_options['options_'.$key] = $value;
5860
				}
5861
			}
5862
5863
			foreach ($new_array_options as $key => $value) {
5864
				$attributeKey      = substr($key, 8); // Remove 'options_' prefix
5865
				$attributeType     = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
5866
				$attributeLabel    = $extrafields->attributes[$this->table_element]['label'][$attributeKey];
5867
				$attributeParam    = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
5868
				$attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
5869
				$attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$attributeKey];
5870
5871
				// Similar code than into insertExtraFields
5872
				if ($attributeRequired) {
5873
					$mandatorypb = false;
5874
					if ($attributeType == 'link' && $this->array_options[$key] == '-1') {
5875
						$mandatorypb = true;
5876
					}
5877
					if ($this->array_options[$key] === '') {
5878
						$mandatorypb = true;
5879
					}
5880
					if ($attributeType == 'sellist' && $this->array_options[$key] == '0') {
5881
						$mandatorypb = true;
5882
					}
5883
					if ($mandatorypb) {
5884
						$langs->load("errors");
5885
						dol_syslog("Mandatory field '".$key."' is empty during create and set to required into definition of extrafields");
5886
						$this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
5887
						return -1;
5888
					}
5889
				}
5890
5891
				//dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
5892
				//dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
5893
5894
				if (!empty($attrfieldcomputed)) {
5895
					if (!empty($conf->global->MAIN_STORE_COMPUTED_EXTRAFIELDS)) {
5896
						$value = dol_eval($attrfieldcomputed, 1, 0);
5897
						dol_syslog($langs->trans("Extrafieldcomputed")." sur ".$attributeLabel."(".$value.")", LOG_DEBUG);
5898
						$new_array_options[$key] = $value;
5899
					} else {
5900
						$new_array_options[$key] = null;
5901
					}
5902
				}
5903
5904
				switch ($attributeType) {
5905
					case 'int':
5906
						if (!is_numeric($value) && $value != '') {
5907
							$this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
5908
							return -1;
5909
						} elseif ($value == '') {
5910
							$new_array_options[$key] = null;
5911
						}
5912
						break;
5913
					case 'price':
5914
					case 'double':
5915
						$value = price2num($value);
5916
						if (!is_numeric($value) && $value != '') {
5917
							dol_syslog($langs->trans("ExtraFieldHasWrongValue")." for ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
5918
							$this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
5919
							return -1;
5920
						} elseif ($value == '') {
5921
							$new_array_options[$key] = null;
5922
						}
5923
						//dol_syslog("double value"." sur ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
5924
						$new_array_options[$key] = $value;
5925
						break;
5926
					/*case 'select':	// Not required, we chosed value='0' for undefined values
5927
						 if ($value=='-1')
5928
						 {
5929
							 $this->array_options[$key] = null;
5930
						 }
5931
						 break;*/
5932
					case 'password':
5933
						$algo = '';
5934
						if ($this->array_options[$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
5935
							// If there is an encryption choice, we use it to crypt data before insert
5936
							$tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
5937
							$algo = reset($tmparrays);
5938
							if ($algo != '') {
5939
								//global $action;		// $action may be 'create', 'update', 'update_extras'...
5940
								//var_dump($action);
5941
								//var_dump($this->oldcopy);exit;
5942
								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
5943
									//var_dump($this->oldcopy->array_options[$key]); var_dump($this->array_options[$key]);
5944
									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.
5945
										$new_array_options[$key] = $this->array_options[$key]; // Value is kept
5946
									} else {
5947
										// var_dump($algo);
5948
										$newvalue = dol_hash($this->array_options[$key], $algo);
5949
										$new_array_options[$key] = $newvalue;
5950
									}
5951
								} else {
5952
									$new_array_options[$key] = $this->array_options[$key]; // Value is kept
5953
								}
5954
							}
5955
						} else // Common usage
5956
						{
5957
							$new_array_options[$key] = $this->array_options[$key];
5958
						}
5959
						break;
5960
					case 'date':
5961
					case 'datetime':
5962
						// If data is a string instead of a timestamp, we convert it
5963
						if (!is_int($this->array_options[$key])) {
5964
							$this->array_options[$key] = strtotime($this->array_options[$key]);
5965
						}
5966
						$new_array_options[$key] = $this->db->idate($this->array_options[$key]);
5967
						break;
5968
					case 'link':
5969
						$param_list = array_keys($attributeParam['options']);
5970
						// 0 : ObjectName
5971
						// 1 : classPath
5972
						$InfoFieldList = explode(":", $param_list[0]);
5973
						dol_include_once($InfoFieldList[1]);
5974
						if ($InfoFieldList[0] && class_exists($InfoFieldList[0])) {
5975
							if ($value == '-1') {	// -1 is key for no defined in combo list of objects
5976
								$new_array_options[$key] = '';
5977
							} elseif ($value) {
5978
								$object = new $InfoFieldList[0]($this->db);
5979
								if (is_numeric($value)) {
5980
									$res = $object->fetch($value); // Common case
5981
								} else {
5982
									$res = $object->fetch('', $value); // For compatibility
5983
								}
5984
5985
								if ($res > 0) {
5986
									$new_array_options[$key] = $object->id;
5987
								} else {
5988
									$this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
5989
									$this->db->rollback();
5990
									return -1;
5991
								}
5992
							}
5993
						} else {
5994
							dol_syslog('Error bad setup of extrafield', LOG_WARNING);
5995
						}
5996
						break;
5997
				}
5998
			}
5999
6000
			$this->db->begin();
6001
6002
			$table_element = $this->table_element;
6003
			if ($table_element == 'categorie') {
6004
				$table_element = 'categories'; // For compatibility
6005
			}
6006
6007
			dol_syslog(get_class($this)."::insertExtraFields delete then insert", LOG_DEBUG);
6008
6009
			$sql_del = "DELETE FROM ".MAIN_DB_PREFIX.$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
6010
			$this->db->query($sql_del);
6011
6012
			$sql = "INSERT INTO ".MAIN_DB_PREFIX.$table_element."_extrafields (fk_object";
6013
			foreach ($new_array_options as $key => $value) {
6014
				$attributeKey = substr($key, 8); // Remove 'options_' prefix
6015
				// Add field of attribut
6016
				if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] != 'separate') { // Only for other type than separator
6017
					$sql .= ",".$attributeKey;
6018
				}
6019
			}
6020
			// We must insert a default value for fields for other entities that are mandatory to avoid not null error
6021
			if (!empty($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities']) && is_array($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'])) {
6022
				foreach ($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'] as $tmpkey => $tmpval) {
6023
					if (!isset($extrafields->attributes[$this->table_element]['type'][$tmpkey])) {    // If field not already added previously
6024
						$sql .= ",".$tmpkey;
6025
					}
6026
				}
6027
			}
6028
			$sql .= ") VALUES (".$this->id;
6029
6030
			foreach ($new_array_options as $key => $value) {
6031
				$attributeKey = substr($key, 8); // Remove 'options_' prefix
6032
				// Add field of attribute
6033
				if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] != 'separate') { // Only for other type than separator)
6034
					if ($new_array_options[$key] != '' || $new_array_options[$key] == '0') {
6035
						$sql .= ",'".$this->db->escape($new_array_options[$key])."'";
6036
					} else {
6037
						$sql .= ",null";
6038
					}
6039
				}
6040
			}
6041
			// We must insert a default value for fields for other entities that are mandatory to avoid not null error
6042
			if (!empty($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities']) && is_array($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'])) {
6043
				foreach ($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'] as $tmpkey => $tmpval) {
6044
					if (!isset($extrafields->attributes[$this->table_element]['type'][$tmpkey])) {   // If field not already added previously
6045
						if (in_array($tmpval, array('int', 'double', 'price'))) {
6046
							$sql .= ", 0";
6047
						} else {
6048
							$sql .= ", ''";
6049
						}
6050
					}
6051
				}
6052
			}
6053
6054
			$sql .= ")";
6055
6056
			$resql = $this->db->query($sql);
6057
			if (!$resql) {
6058
				$this->error = $this->db->lasterror();
6059
				$error++;
6060
			}
6061
6062
			if (!$error && $trigger) {
6063
				// Call trigger
6064
				$this->context = array('extrafieldaddupdate'=>1);
6065
				$result = $this->call_trigger($trigger, $userused);
6066
				if ($result < 0) {
6067
					$error++;
6068
				}
6069
				// End call trigger
6070
			}
6071
6072
			if ($error) {
6073
				$this->db->rollback();
6074
				return -1;
6075
			} else {
6076
				$this->db->commit();
6077
				return 1;
6078
			}
6079
		} else {
6080
			return 0;
6081
		}
6082
	}
6083
6084
	/**
6085
	 *	Add/Update all extra fields values for the current object.
6086
	 *  Data to describe values to insert/update are stored into $this->array_options=array('options_codeforfield1'=>'valueforfield1', 'options_codeforfield2'=>'valueforfield2', ...)
6087
	 *  This function delete record with all extrafields and insert them again from the array $this->array_options.
6088
	 *
6089
	 *  @param	string		$trigger		If defined, call also the trigger (for example COMPANY_MODIFY)
6090
	 *  @param	User		$userused		Object user
6091
	 *  @return int 						-1=error, O=did nothing, 1=OK
6092
	 *  @see insertExtraFields(), updateExtraField(), setValueFrom()
6093
	 */
6094
	public function insertExtraLanguages($trigger = '', $userused = null)
6095
	{
6096
		global $conf, $langs, $user;
6097
6098
		if (empty($userused)) {
6099
			$userused = $user;
6100
		}
6101
6102
		$error = 0;
6103
6104
		if (!empty($conf->global->MAIN_EXTRALANGUAGES_DISABLED)) {
6105
			return 0; // For avoid conflicts if trigger used
6106
		}
6107
6108
		if (is_array($this->array_languages)) {
6109
			$new_array_languages = $this->array_languages;
6110
6111
			foreach ($new_array_languages as $key => $value) {
6112
				$attributeKey      = $key;
6113
				$attributeType     = $this->fields[$attributeKey]['type'];
6114
				$attributeLabel    = $this->fields[$attributeKey]['label'];
6115
6116
				//dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
6117
				//dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
6118
6119
				switch ($attributeType) {
6120
					case 'int':
6121
						if (!is_numeric($value) && $value != '') {
6122
							$this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
6123
							return -1;
6124
						} elseif ($value == '') {
6125
							$new_array_languages[$key] = null;
6126
						}
6127
						break;
6128
					case 'double':
6129
						$value = price2num($value);
6130
						if (!is_numeric($value) && $value != '') {
6131
							dol_syslog($langs->trans("ExtraLanguageHasWrongValue")." sur ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
6132
							$this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
6133
							return -1;
6134
						} elseif ($value == '') {
6135
							$new_array_languages[$key] = null;
6136
						}
6137
						//dol_syslog("double value"." sur ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
6138
						$new_array_languages[$key] = $value;
6139
						break;
6140
						/*case 'select':	// Not required, we chosed value='0' for undefined values
6141
						 if ($value=='-1')
6142
						 {
6143
						 $this->array_options[$key] = null;
6144
						 }
6145
						 break;*/
6146
				}
6147
			}
6148
6149
			$this->db->begin();
6150
6151
			$table_element = $this->table_element;
6152
			if ($table_element == 'categorie') {
6153
				$table_element = 'categories'; // For compatibility
6154
			}
6155
6156
			dol_syslog(get_class($this)."::insertExtraLanguages delete then insert", LOG_DEBUG);
6157
6158
			foreach ($new_array_languages as $key => $langcodearray) {	// $key = 'name', 'town', ...
6159
				foreach ($langcodearray as $langcode => $value) {
6160
					$sql_del = "DELETE FROM ".MAIN_DB_PREFIX."object_lang";
6161
					$sql_del .= " WHERE fk_object = ".((int) $this->id)." AND property = '".$this->db->escape($key)."' AND type_object = '".$this->db->escape($table_element)."'";
6162
					$sql_del .= " AND lang = '".$this->db->escape($langcode)."'";
6163
					$this->db->query($sql_del);
6164
6165
					if ($value !== '') {
6166
						$sql = "INSERT INTO ".MAIN_DB_PREFIX."object_lang (fk_object, property, type_object, lang, value";
6167
						$sql .= ") VALUES (".$this->id.", '".$this->db->escape($key)."', '".$this->db->escape($table_element)."', '".$this->db->escape($langcode)."', '".$this->db->escape($value)."'";
6168
						$sql .= ")";
6169
6170
						$resql = $this->db->query($sql);
6171
						if (!$resql) {
6172
							$this->error = $this->db->lasterror();
6173
							$error++;
6174
							break;
6175
						}
6176
					}
6177
				}
6178
			}
6179
6180
			if (!$error && $trigger) {
6181
				// Call trigger
6182
				$this->context = array('extralanguagesaddupdate'=>1);
6183
				$result = $this->call_trigger($trigger, $userused);
6184
				if ($result < 0) {
6185
					$error++;
6186
				}
6187
				// End call trigger
6188
			}
6189
6190
			if ($error) {
6191
				$this->db->rollback();
6192
				return -1;
6193
			} else {
6194
				$this->db->commit();
6195
				return 1;
6196
			}
6197
		} else {
6198
			return 0;
6199
		}
6200
	}
6201
6202
	/**
6203
	 *	Update 1 extra field value for the current object. Keep other fields unchanged.
6204
	 *  Data to describe values to update are stored into $this->array_options=array('options_codeforfield1'=>'valueforfield1', 'options_codeforfield2'=>'valueforfield2', ...)
6205
	 *
6206
	 *  @param  string      $key    		Key of the extrafield to update (without starting 'options_')
6207
	 *  @param	string		$trigger		If defined, call also the trigger (for example COMPANY_MODIFY)
6208
	 *  @param	User		$userused		Object user
6209
	 *  @return int                 		-1=error, O=did nothing, 1=OK
6210
	 *  @see updateExtraLanguages(), insertExtraFields(), deleteExtraFields(), setValueFrom()
6211
	 */
6212
	public function updateExtraField($key, $trigger = null, $userused = null)
6213
	{
6214
		global $conf, $langs, $user;
6215
6216
		if (!empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) {
6217
			return 0;
6218
		}
6219
6220
		if (empty($userused)) {
6221
			$userused = $user;
6222
		}
6223
6224
		$error = 0;
6225
6226
		if (!empty($this->array_options) && isset($this->array_options["options_".$key])) {
6227
			// Check parameters
6228
			$langs->load('admin');
6229
			require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6230
			$extrafields = new ExtraFields($this->db);
6231
			$extrafields->fetch_name_optionals_label($this->table_element);
6232
6233
			$value = $this->array_options["options_".$key];
6234
6235
			$attributeType     = $extrafields->attributes[$this->table_element]['type'][$key];
6236
			$attributeLabel    = $extrafields->attributes[$this->table_element]['label'][$key];
6237
			$attributeParam    = $extrafields->attributes[$this->table_element]['param'][$key];
6238
			$attributeRequired = $extrafields->attributes[$this->table_element]['required'][$key];
6239
			$attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$key];
6240
6241
			// Similar code than into insertExtraFields
6242
			if ($attributeRequired) {
6243
				$mandatorypb = false;
6244
				if ($attributeType == 'link' && $this->array_options["options_".$key] == '-1') {
6245
					$mandatorypb = true;
6246
				}
6247
				if ($this->array_options["options_".$key] === '') {
6248
					$mandatorypb = true;
6249
				}
6250
				if ($mandatorypb) {
6251
					$langs->load("errors");
6252
					dol_syslog("Mandatory field 'options_".$key."' is empty during update and set to required into definition of extrafields");
6253
					$this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
6254
					return -1;
6255
				}
6256
			}
6257
6258
			//dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
6259
			//dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
6260
6261
			if (!empty($attrfieldcomputed)) {
6262
				if (!empty($conf->global->MAIN_STORE_COMPUTED_EXTRAFIELDS)) {
6263
					$value = dol_eval($attrfieldcomputed, 1, 0);
6264
					dol_syslog($langs->trans("Extrafieldcomputed")." sur ".$attributeLabel."(".$value.")", LOG_DEBUG);
6265
					$this->array_options["options_".$key] = $value;
6266
				} else {
6267
					$this->array_options["options_".$key] = null;
6268
				}
6269
			}
6270
6271
			switch ($attributeType) {
6272
				case 'int':
6273
					if (!is_numeric($value) && $value != '') {
6274
						$this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
6275
						return -1;
6276
					} elseif ($value === '') {
6277
						$this->array_options["options_".$key] = null;
6278
					}
6279
					break;
6280
				case 'double':
6281
					$value = price2num($value);
6282
					if (!is_numeric($value) && $value != '') {
6283
						dol_syslog($langs->trans("ExtraFieldHasWrongValue")." sur ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
6284
						$this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
6285
						return -1;
6286
					} elseif ($value === '') {
6287
						$this->array_options["options_".$key] = null;
6288
					}
6289
					//dol_syslog("double value"." sur ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
6290
					$this->array_options["options_".$key] = $value;
6291
					break;
6292
				/*case 'select':	// Not required, we chosed value='0' for undefined values
6293
					 if ($value=='-1')
6294
					 {
6295
						 $this->array_options[$key] = null;
6296
					 }
6297
					 break;*/
6298
				case 'price':
6299
					$this->array_options["options_".$key] = price2num($this->array_options["options_".$key]);
6300
					break;
6301
				case 'date':
6302
				case 'datetime':
6303
					if (empty($this->array_options["options_".$key])) {
6304
						$this->array_options["options_".$key] = null;
6305
					} else {
6306
						$this->array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key]);
6307
					}
6308
					break;
6309
				/*
6310
				case 'link':
6311
					$param_list = array_keys($attributeParam['options']);
6312
					// 0 : ObjectName
6313
					// 1 : classPath
6314
					$InfoFieldList = explode(":", $param_list[0]);
6315
					dol_include_once($InfoFieldList[1]);
6316
					if ($InfoFieldList[0] && class_exists($InfoFieldList[0]))
6317
					{
6318
						if ($value == '-1')	// -1 is key for no defined in combo list of objects
6319
						{
6320
							$new_array_options[$key] = '';
6321
						} elseif ($value) {
6322
							$object = new $InfoFieldList[0]($this->db);
6323
							if (is_numeric($value)) $res = $object->fetch($value);	// Common case
6324
							else $res = $object->fetch('', $value);					// For compatibility
6325
6326
							if ($res > 0) $new_array_options[$key] = $object->id;
6327
							else {
6328
								$this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
6329
								$this->db->rollback();
6330
								return -1;
6331
							}
6332
						}
6333
					} else {
6334
						dol_syslog('Error bad setup of extrafield', LOG_WARNING);
6335
					}
6336
					break;
6337
				*/
6338
			}
6339
6340
			$this->db->begin();
6341
6342
			$linealreadyfound = 0;
6343
6344
			// 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)
6345
			$sql = "SELECT COUNT(rowid) as nb FROM ".MAIN_DB_PREFIX.$this->table_element."_extrafields WHERE fk_object = ".((int) $this->id);
6346
			$resql = $this->db->query($sql);
6347
			if ($resql) {
6348
				$tmpobj = $this->db->fetch_object($resql);
6349
				if ($tmpobj) {
6350
					$linealreadyfound = $tmpobj->nb;
6351
				}
6352
			}
6353
6354
			if ($linealreadyfound) {
6355
				if ($this->array_options["options_".$key] === null) {
6356
					$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element."_extrafields SET ".$key." = null";
6357
				} else {
6358
					$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element."_extrafields SET ".$key." = '".$this->db->escape($this->array_options["options_".$key])."'";
6359
				}
6360
				$sql .= " WHERE fk_object = ".((int) $this->id);
6361
			} else {
6362
				$result = $this->insertExtraFields('', $user);
6363
				if ($result < 0) {
6364
					$error++;
6365
				}
6366
			}
6367
6368
			$resql = $this->db->query($sql);
6369
			if (!$resql) {
6370
				$error++;
6371
				$this->error = $this->db->lasterror();
6372
			}
6373
			if (!$error && $trigger) {
6374
				// Call trigger
6375
				$this->context = array('extrafieldupdate'=>1);
6376
				$result = $this->call_trigger($trigger, $userused);
6377
				if ($result < 0) {
6378
					$error++;
6379
				}
6380
				// End call trigger
6381
			}
6382
6383
			if ($error) {
6384
				dol_syslog(__METHOD__.$this->error, LOG_ERR);
6385
				$this->db->rollback();
6386
				return -1;
6387
			} else {
6388
				$this->db->commit();
6389
				return 1;
6390
			}
6391
		} else {
6392
			return 0;
6393
		}
6394
	}
6395
6396
	/**
6397
	 *	Update an extra language value for the current object.
6398
	 *  Data to describe values to update are stored into $this->array_options=array('options_codeforfield1'=>'valueforfield1', 'options_codeforfield2'=>'valueforfield2', ...)
6399
	 *
6400
	 *  @param  string      $key    		Key of the extrafield (without starting 'options_')
6401
	 *  @param	string		$trigger		If defined, call also the trigger (for example COMPANY_MODIFY)
6402
	 *  @param	User		$userused		Object user
6403
	 *  @return int                 		-1=error, O=did nothing, 1=OK
6404
	 *  @see updateExtraFields(), insertExtraLanguages()
6405
	 */
6406
	public function updateExtraLanguages($key, $trigger = null, $userused = null)
6407
	{
6408
		global $conf, $langs, $user;
6409
6410
		if (empty($userused)) {
6411
			$userused = $user;
6412
		}
6413
6414
		$error = 0;
6415
6416
		if (!empty($conf->global->MAIN_EXTRALANGUAGES_DISABLED)) {
6417
			return 0; // For avoid conflicts if trigger used
6418
		}
6419
6420
		return 0;
6421
	}
6422
6423
6424
	/**
6425
	 * Return HTML string to put an input field into a page
6426
	 * Code very similar with showInputField of extra fields
6427
	 *
6428
	 * @param  array   		$val	       Array of properties for field to show (used only if ->fields not defined)
6429
	 * @param  string  		$key           Key of attribute
6430
	 * @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)
6431
	 * @param  string  		$moreparam     To add more parameters on html input tag
6432
	 * @param  string  		$keysuffix     Prefix string to add into name and id of field (can be used to avoid duplicate names)
6433
	 * @param  string  		$keyprefix     Suffix string to add into name and id of field (can be used to avoid duplicate names)
6434
	 * @param  string|int	$morecss       Value for css to define style/length of field. May also be a numeric.
6435
	 * @param  int			$nonewbutton   Force to not show the new button on field that are links to object
6436
	 * @return string
6437
	 */
6438
	public function showInputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = 0, $nonewbutton = 0)
6439
	{
6440
		global $conf, $langs, $form;
6441
6442
		if (!is_object($form)) {
6443
			require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
6444
			$form = new Form($this->db);
6445
		}
6446
6447
		if (!empty($this->fields)) {
6448
			$val = $this->fields[$key];
6449
		}
6450
6451
		// Validation tests and output
6452
		$fieldValidationErrorMsg = '';
6453
		$validationClass = '';
6454
		$fieldValidationErrorMsg = $this->getFieldError($key);
6455
		if (!empty($fieldValidationErrorMsg)) {
6456
			$validationClass = ' --error'; // the -- is use as class state in css :  .--error can't be be defined alone it must be define with another class like .my-class.--error or input.--error
6457
		} else {
6458
			$validationClass = ' --success'; // the -- is use as class state in css :  .--success can't be be defined alone it must be define with another class like .my-class.--success or input.--success
6459
		}
6460
6461
		$out = '';
6462
		$type = '';
6463
		$isDependList=0;
6464
		$param = array();
6465
		$param['options'] = array();
6466
		$reg = array();
6467
		$size = !empty($this->fields[$key]['size']) ? $this->fields[$key]['size'] : 0;
6468
		// Because we work on extrafields
6469
		if (preg_match('/^(integer|link):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
6470
			$param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
6471
			$type = 'link';
6472
		} elseif (preg_match('/^(integer|link):(.*):(.*):(.*)/i', $val['type'], $reg)) {
6473
			$param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
6474
			$type = 'link';
6475
		} elseif (preg_match('/^(integer|link):(.*):(.*)/i', $val['type'], $reg)) {
6476
			$param['options'] = array($reg[2].':'.$reg[3] => 'N');
6477
			$type = 'link';
6478
		} elseif (preg_match('/^(sellist):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
6479
			$param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
6480
			$type = 'sellist';
6481
		} elseif (preg_match('/^(sellist):(.*):(.*):(.*)/i', $val['type'], $reg)) {
6482
			$param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
6483
			$type = 'sellist';
6484
		} elseif (preg_match('/^(sellist):(.*):(.*)/i', $val['type'], $reg)) {
6485
			$param['options'] = array($reg[2].':'.$reg[3] => 'N');
6486
			$type = 'sellist';
6487
		} elseif (preg_match('/varchar\((\d+)\)/', $val['type'], $reg)) {
6488
			$param['options'] = array();
6489
			$type = 'varchar';
6490
			$size = $reg[1];
6491
		} elseif (preg_match('/varchar/', $val['type'])) {
6492
			$param['options'] = array();
6493
			$type = 'varchar';
6494
		} else {
6495
			$param['options'] = array();
6496
			$type = $this->fields[$key]['type'];
6497
		}
6498
6499
		// Special case that force options and type ($type can be integer, varchar, ...)
6500
		if (!empty($this->fields[$key]['arrayofkeyval']) && is_array($this->fields[$key]['arrayofkeyval'])) {
6501
			$param['options'] = $this->fields[$key]['arrayofkeyval'];
6502
			$type = 'select';
6503
		}
6504
6505
		$label = $this->fields[$key]['label'];
6506
		//$elementtype=$this->fields[$key]['elementtype'];	// Seems not used
6507
		$default = (!empty($this->fields[$key]['default']) ? $this->fields[$key]['default'] : '');
6508
		$computed = (!empty($this->fields[$key]['computed']) ? $this->fields[$key]['computed'] : '');
6509
		$unique = (!empty($this->fields[$key]['unique']) ? $this->fields[$key]['unique'] : 0);
6510
		$required = (!empty($this->fields[$key]['required']) ? $this->fields[$key]['required'] : 0);
6511
		$autofocusoncreate = (!empty($this->fields[$key]['autofocusoncreate']) ? $this->fields[$key]['autofocusoncreate'] : 0);
6512
6513
		$langfile = (!empty($this->fields[$key]['langfile']) ? $this->fields[$key]['langfile'] : '');
6514
		$list = (!empty($this->fields[$key]['list']) ? $this->fields[$key]['list'] : 0);
6515
		$hidden = (in_array(abs($this->fields[$key]['visible']), array(0, 2)) ? 1 : 0);
6516
6517
		$objectid = $this->id;
6518
6519
		if ($computed) {
6520
			if (!preg_match('/^search_/', $keyprefix)) {
6521
				return '<span class="opacitymedium">'.$langs->trans("AutomaticallyCalculated").'</span>';
6522
			} else {
6523
				return '';
6524
			}
6525
		}
6526
6527
		// Set value of $morecss. For this, we use in priority showsize from parameters, then $val['css'] then autodefine
6528
		if (empty($morecss) && !empty($val['css'])) {
6529
			$morecss = $val['css'];
6530
		} elseif (empty($morecss)) {
6531
			if ($type == 'date') {
6532
				$morecss = 'minwidth100imp';
6533
			} elseif ($type == 'datetime' || $type == 'link') {	// link means an foreign key to another primary id
6534
				$morecss = 'minwidth200imp';
6535
			} elseif (in_array($type, array('int', 'integer', 'price')) || preg_match('/^double(\([0-9],[0-9]\)){0,1}/', $type)) {
6536
				$morecss = 'maxwidth75';
6537
			} elseif ($type == 'url') {
6538
				$morecss = 'minwidth400';
6539
			} elseif ($type == 'boolean') {
6540
				$morecss = '';
6541
			} else {
6542
				if (round($size) < 12) {
6543
					$morecss = 'minwidth100';
6544
				} elseif (round($size) <= 48) {
6545
					$morecss = 'minwidth200';
6546
				} else {
6547
					$morecss = 'minwidth400';
6548
				}
6549
			}
6550
		}
6551
6552
		// Add validation state class
6553
		if (!empty($validationClass)) {
6554
			$morecss.= ' '.$validationClass;
6555
		}
6556
6557
		if (in_array($type, array('date'))) {
6558
			$tmp = explode(',', $size);
6559
			$newsize = $tmp[0];
6560
			$showtime = 0;
6561
6562
			// Do not show current date when field not required (see selectDate() method)
6563
			if (!$required && $value == '') {
6564
				$value = '-1';
6565
			}
6566
6567
			// TODO Must also support $moreparam
6568
			$out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1);
6569
		} elseif (in_array($type, array('datetime'))) {
6570
			$tmp = explode(',', $size);
6571
			$newsize = $tmp[0];
6572
			$showtime = 1;
6573
6574
			// Do not show current date when field not required (see selectDate() method)
6575
			if (!$required && $value == '') $value = '-1';
6576
6577
			// TODO Must also support $moreparam
6578
			$out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1, '', '', '', 1, '', '', 'tzuserrel');
6579
		} elseif (in_array($type, array('duration'))) {
6580
			$out = $form->select_duration($keyprefix.$key.$keysuffix, $value, 0, 'text', 0, 1);
6581
		} elseif (in_array($type, array('int', 'integer'))) {
6582
			$tmp = explode(',', $size);
6583
			$newsize = $tmp[0];
6584
			$out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'"'.($newsize > 0 ? ' maxlength="'.$newsize.'"' : '').' value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
6585
		} elseif (in_array($type, array('real'))) {
6586
			$out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
6587
		} elseif (preg_match('/varchar/', $type)) {
6588
			$out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'"'.($size > 0 ? ' maxlength="'.$size.'"' : '').' value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
6589
		} elseif (in_array($type, array('mail', 'phone', 'url'))) {
6590
			$out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
6591
		} elseif (preg_match('/^text/', $type)) {
6592
			if (!preg_match('/search_/', $keyprefix)) {		// If keyprefix is search_ or search_options_, we must just use a simple text field
6593
				require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
6594
				$doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, false, ROWS_5, '90%');
6595
				$out = $doleditor->Create(1);
6596
			} else {
6597
				$out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
6598
			}
6599
		} elseif (preg_match('/^html/', $type)) {
6600
			if (!preg_match('/search_/', $keyprefix)) {		// If keyprefix is search_ or search_options_, we must just use a simple text field
6601
				require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
6602
				$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%');
6603
				$out = $doleditor->Create(1);
6604
			} else {
6605
				$out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
6606
			}
6607
		} elseif ($type == 'boolean') {
6608
			$checked = '';
6609
			if (!empty($value)) {
6610
				$checked = ' checked value="1" ';
6611
			} else {
6612
				$checked = ' value="1" ';
6613
			}
6614
			$out = '<input type="checkbox" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.$checked.' '.($moreparam ? $moreparam : '').'>';
6615
		} elseif ($type == 'price') {
6616
			if (!empty($value)) {		// $value in memory is a php numeric, we format it into user number format.
6617
				$value = price($value);
6618
			}
6619
			$out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'> '.$langs->getCurrencySymbol($conf->currency);
6620
		} elseif (preg_match('/^double(\([0-9],[0-9]\)){0,1}/', $type)) {
6621
			if (!empty($value)) {		// $value in memory is a php numeric, we format it into user number format.
6622
				$value = price($value);
6623
			}
6624
			$out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'> ';
6625
		} elseif ($type == 'select') {
6626
			$out = '';
6627
			if (!empty($conf->use_javascript_ajax) && empty($conf->global->MAIN_EXTRAFIELDS_DISABLE_SELECT2)) {
6628
				include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
6629
				$out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
6630
			}
6631
6632
			$out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
6633
			if ((!isset($this->fields[$key]['default'])) || ($this->fields[$key]['notnull'] != 1)) {
6634
				$out .= '<option value="0">&nbsp;</option>';
6635
			}
6636
			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...
6637
				if ((string) $key == '') {
6638
					continue;
6639
				}
6640
				list($val, $parent) = explode('|', $val);
6641
				$out .= '<option value="'.$key.'"';
6642
				$out .= (((string) $value == (string) $key) ? ' selected' : '');
6643
				$out .= (!empty($parent) ? ' parent="'.$parent.'"' : '');
6644
				$out .= '>'.$val.'</option>';
6645
			}
6646
			$out .= '</select>';
6647
		} elseif ($type == 'sellist') {
6648
			$out = '';
6649
			if (!empty($conf->use_javascript_ajax) && empty($conf->global->MAIN_EXTRAFIELDS_DISABLE_SELECT2)) {
6650
				include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
6651
				$out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
6652
			}
6653
6654
			$out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
6655
			if (is_array($param['options'])) {
6656
				$param_list = array_keys($param['options']);
6657
				$InfoFieldList = explode(":", $param_list[0]);
6658
				$parentName = '';
6659
				$parentField = '';
6660
				// 0 : tableName
6661
				// 1 : label field name
6662
				// 2 : key fields name (if differ of rowid)
6663
				// 3 : key field parent (for dependent lists)
6664
				// 4 : where clause filter on column or table extrafield, syntax field='value' or extra.field=value
6665
				$keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
6666
6667
				if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
6668
					if (strpos($InfoFieldList[4], 'extra.') !== false) {
6669
						$keyList = 'main.'.$InfoFieldList[2].' as rowid';
6670
					} else {
6671
						$keyList = $InfoFieldList[2].' as rowid';
6672
					}
6673
				}
6674
				if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
6675
					list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
6676
					$keyList .= ', '.$parentField;
6677
				}
6678
6679
				$fields_label = explode('|', $InfoFieldList[1]);
6680
				if (is_array($fields_label)) {
6681
					$keyList .= ', ';
6682
					$keyList .= implode(', ', $fields_label);
6683
				}
6684
6685
				$sqlwhere = '';
6686
				$sql = 'SELECT '.$keyList;
6687
				$sql .= ' FROM '.MAIN_DB_PREFIX.$InfoFieldList[0];
6688
				if (!empty($InfoFieldList[4])) {
6689
					// can use SELECT request
6690
					if (strpos($InfoFieldList[4], '$SEL$') !== false) {
6691
						$InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
6692
					}
6693
6694
					// current object id can be use into filter
6695
					if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
6696
						$InfoFieldList[4] = str_replace('$ID$', $objectid, $InfoFieldList[4]);
6697
					} else {
6698
						$InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
6699
					}
6700
6701
					//We have to join on extrafield table
6702
					if (strpos($InfoFieldList[4], 'extra') !== false) {
6703
						$sql .= " as main, ".MAIN_DB_PREFIX.$InfoFieldList[0]."_extrafields as extra";
6704
						$sqlwhere .= " WHERE extra.fk_object=main.".$InfoFieldList[2]." AND ".$InfoFieldList[4];
6705
					} else {
6706
						$sqlwhere .= " WHERE ".$InfoFieldList[4];
6707
					}
6708
				} else {
6709
					$sqlwhere .= ' WHERE 1=1';
6710
				}
6711
				// Some tables may have field, some other not. For the moment we disable it.
6712
				if (in_array($InfoFieldList[0], array('tablewithentity'))) {
6713
					$sqlwhere .= " AND entity = ".((int) $conf->entity);
6714
				}
6715
				$sql .= $sqlwhere;
6716
				//print $sql;
6717
6718
				$sql .= ' ORDER BY '.implode(', ', $fields_label);
6719
6720
				dol_syslog(get_class($this).'::showInputField type=sellist', LOG_DEBUG);
6721
				$resql = $this->db->query($sql);
6722
				if ($resql) {
6723
					$out .= '<option value="0">&nbsp;</option>';
6724
					$num = $this->db->num_rows($resql);
6725
					$i = 0;
6726
					while ($i < $num) {
6727
						$labeltoshow = '';
6728
						$obj = $this->db->fetch_object($resql);
6729
6730
						// Several field into label (eq table:code|libelle:rowid)
6731
						$notrans = false;
6732
						$fields_label = explode('|', $InfoFieldList[1]);
6733
						if (count($fields_label) > 1) {
6734
							$notrans = true;
6735
							foreach ($fields_label as $field_toshow) {
6736
								$labeltoshow .= $obj->$field_toshow.' ';
6737
							}
6738
						} else {
6739
							$labeltoshow = $obj->{$InfoFieldList[1]};
6740
						}
6741
						$labeltoshow = dol_trunc($labeltoshow, 45);
6742
6743
						if ($value == $obj->rowid) {
6744
							foreach ($fields_label as $field_toshow) {
6745
								$translabel = $langs->trans($obj->$field_toshow);
6746
								if ($translabel != $obj->$field_toshow) {
6747
									$labeltoshow = dol_trunc($translabel).' ';
6748
								} else {
6749
									$labeltoshow = dol_trunc($obj->$field_toshow).' ';
6750
								}
6751
							}
6752
							$out .= '<option value="'.$obj->rowid.'" selected>'.$labeltoshow.'</option>';
6753
						} else {
6754
							if (!$notrans) {
6755
								$translabel = $langs->trans($obj->{$InfoFieldList[1]});
6756
								if ($translabel != $obj->{$InfoFieldList[1]}) {
6757
									$labeltoshow = dol_trunc($translabel, 18);
6758
								} else {
6759
									$labeltoshow = dol_trunc($obj->{$InfoFieldList[1]});
6760
								}
6761
							}
6762
							if (empty($labeltoshow)) {
6763
								$labeltoshow = '(not defined)';
6764
							}
6765
							if ($value == $obj->rowid) {
6766
								$out .= '<option value="'.$obj->rowid.'" selected>'.$labeltoshow.'</option>';
6767
							}
6768
6769
							if (!empty($InfoFieldList[3]) && $parentField) {
6770
								$parent = $parentName.':'.$obj->{$parentField};
6771
								$isDependList=1;
6772
							}
6773
6774
							$out .= '<option value="'.$obj->rowid.'"';
6775
							$out .= ($value == $obj->rowid ? ' selected' : '');
6776
							$out .= (!empty($parent) ? ' parent="'.$parent.'"' : '');
6777
							$out .= '>'.$labeltoshow.'</option>';
6778
						}
6779
6780
						$i++;
6781
					}
6782
					$this->db->free($resql);
6783
				} else {
6784
					print 'Error in request '.$sql.' '.$this->db->lasterror().'. Check setup of extra parameters.<br>';
6785
				}
6786
			}
6787
			$out .= '</select>';
6788
		} elseif ($type == 'checkbox') {
6789
			$value_arr = explode(',', $value);
6790
			$out = $form->multiselectarray($keyprefix.$key.$keysuffix, (empty($param['options']) ?null:$param['options']), $value_arr, '', 0, '', 0, '100%');
6791
		} elseif ($type == 'radio') {
6792
			$out = '';
6793
			foreach ($param['options'] as $keyopt => $val) {
0 ignored issues
show
introduced by
$val is overwriting one of the parameters of this function.
Loading history...
6794
				$out .= '<input class="flat '.$morecss.'" type="radio" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '');
6795
				$out .= ' value="'.$keyopt.'"';
6796
				$out .= ' id="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'"';
6797
				$out .= ($value == $keyopt ? 'checked' : '');
6798
				$out .= '/><label for="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'">'.$val.'</label><br>';
6799
			}
6800
		} elseif ($type == 'chkbxlst') {
6801
			if (is_array($value)) {
6802
				$value_arr = $value;
6803
			} else {
6804
				$value_arr = explode(',', $value);
6805
			}
6806
6807
			if (is_array($param['options'])) {
6808
				$param_list = array_keys($param['options']);
6809
				$InfoFieldList = explode(":", $param_list[0]);
6810
				$parentName = '';
6811
				$parentField = '';
6812
				// 0 : tableName
6813
				// 1 : label field name
6814
				// 2 : key fields name (if differ of rowid)
6815
				// 3 : key field parent (for dependent lists)
6816
				// 4 : where clause filter on column or table extrafield, syntax field='value' or extra.field=value
6817
				$keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
6818
6819
				if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
6820
					list ($parentName, $parentField) = explode('|', $InfoFieldList[3]);
6821
					$keyList .= ', '.$parentField;
6822
				}
6823
				if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
6824
					if (strpos($InfoFieldList[4], 'extra.') !== false) {
6825
						$keyList = 'main.'.$InfoFieldList[2].' as rowid';
6826
					} else {
6827
						$keyList = $InfoFieldList[2].' as rowid';
6828
					}
6829
				}
6830
6831
				$fields_label = explode('|', $InfoFieldList[1]);
6832
				if (is_array($fields_label)) {
6833
					$keyList .= ', ';
6834
					$keyList .= implode(', ', $fields_label);
6835
				}
6836
6837
				$sqlwhere = '';
6838
				$sql = 'SELECT '.$keyList;
6839
				$sql .= ' FROM '.MAIN_DB_PREFIX.$InfoFieldList[0];
6840
				if (!empty($InfoFieldList[4])) {
6841
					// can use SELECT request
6842
					if (strpos($InfoFieldList[4], '$SEL$') !== false) {
6843
						$InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
6844
					}
6845
6846
					// current object id can be use into filter
6847
					if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
6848
						$InfoFieldList[4] = str_replace('$ID$', $objectid, $InfoFieldList[4]);
6849
					} else {
6850
						$InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
6851
					}
6852
6853
					// We have to join on extrafield table
6854
					if (strpos($InfoFieldList[4], 'extra') !== false) {
6855
						$sql .= ' as main, '.MAIN_DB_PREFIX.$InfoFieldList[0].'_extrafields as extra';
6856
						$sqlwhere .= " WHERE extra.fk_object=main.".$InfoFieldList[2]." AND ".$InfoFieldList[4];
6857
					} else {
6858
						$sqlwhere .= " WHERE ".$InfoFieldList[4];
6859
					}
6860
				} else {
6861
					$sqlwhere .= ' WHERE 1=1';
6862
				}
6863
				// Some tables may have field, some other not. For the moment we disable it.
6864
				if (in_array($InfoFieldList[0], array('tablewithentity'))) {
6865
					$sqlwhere .= " AND entity = ".((int) $conf->entity);
6866
				}
6867
				// $sql.=preg_replace('/^ AND /','',$sqlwhere);
6868
				// print $sql;
6869
6870
				$sql .= $sqlwhere;
6871
				dol_syslog(get_class($this).'::showInputField type=chkbxlst', LOG_DEBUG);
6872
				$resql = $this->db->query($sql);
6873
				if ($resql) {
6874
					$num = $this->db->num_rows($resql);
6875
					$i = 0;
6876
6877
					$data = array();
6878
6879
					while ($i < $num) {
6880
						$labeltoshow = '';
6881
						$obj = $this->db->fetch_object($resql);
6882
6883
						$notrans = false;
6884
						// Several field into label (eq table:code|libelle:rowid)
6885
						$fields_label = explode('|', $InfoFieldList[1]);
6886
						if (count($fields_label) > 1) {
6887
							$notrans = true;
6888
							foreach ($fields_label as $field_toshow) {
6889
								$labeltoshow .= $obj->$field_toshow.' ';
6890
							}
6891
						} else {
6892
							$labeltoshow = $obj->{$InfoFieldList[1]};
6893
						}
6894
						$labeltoshow = dol_trunc($labeltoshow, 45);
6895
6896
						if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
6897
							foreach ($fields_label as $field_toshow) {
6898
								$translabel = $langs->trans($obj->$field_toshow);
6899
								if ($translabel != $obj->$field_toshow) {
6900
									$labeltoshow = dol_trunc($translabel, 18).' ';
6901
								} else {
6902
									$labeltoshow = dol_trunc($obj->$field_toshow, 18).' ';
6903
								}
6904
							}
6905
6906
							$data[$obj->rowid] = $labeltoshow;
6907
						} else {
6908
							if (!$notrans) {
6909
								$translabel = $langs->trans($obj->{$InfoFieldList[1]});
6910
								if ($translabel != $obj->{$InfoFieldList[1]}) {
6911
									$labeltoshow = dol_trunc($translabel, 18);
6912
								} else {
6913
									$labeltoshow = dol_trunc($obj->{$InfoFieldList[1]}, 18);
6914
								}
6915
							}
6916
							if (empty($labeltoshow)) {
6917
								$labeltoshow = '(not defined)';
6918
							}
6919
6920
							if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
6921
								$data[$obj->rowid] = $labeltoshow;
6922
							}
6923
6924
							if (!empty($InfoFieldList[3]) && $parentField) {
6925
								$parent = $parentName.':'.$obj->{$parentField};
6926
								$isDependList=1;
6927
							}
6928
6929
							$data[$obj->rowid] = $labeltoshow;
6930
						}
6931
6932
						$i++;
6933
					}
6934
					$this->db->free($resql);
6935
6936
					$out = $form->multiselectarray($keyprefix.$key.$keysuffix, $data, $value_arr, '', 0, '', 0, '100%');
6937
				} else {
6938
					print 'Error in request '.$sql.' '.$this->db->lasterror().'. Check setup of extra parameters.<br>';
6939
				}
6940
			}
6941
		} elseif ($type == 'link') {
6942
			$param_list = array_keys($param['options']); // $param_list='ObjectName:classPath[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
6943
			$param_list_array = explode(':', $param_list[0]);
6944
			$showempty = (($required && $default != '') ? 0 : 1);
6945
6946
			if (!preg_match('/search_/', $keyprefix)) {
6947
				if (!empty($param_list_array[2])) {		// If the entry into $fields is set to add a create button
6948
					if (!empty($this->fields[$key]['picto'])) {
6949
						$morecss .= ' widthcentpercentminusxx';
6950
					} else {
6951
						$morecss .= ' widthcentpercentminusx';
6952
					}
6953
				} else {
6954
					if (!empty($this->fields[$key]['picto'])) {
6955
						$morecss .= ' widthcentpercentminusx';
6956
					}
6957
				}
6958
			}
6959
6960
			$out = $form->selectForForms($param_list[0], $keyprefix.$key.$keysuffix, $value, $showempty, '', '', $morecss, $moreparam, 0, empty($val['disabled']) ? 0 : 1);
6961
6962
			if (!empty($param_list_array[2])) {		// If the entry into $fields is set to add a create button
6963
				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".
6964
					list($class, $classfile) = explode(':', $param_list[0]);
6965
					if (file_exists(dol_buildpath(dirname(dirname($classfile)).'/card.php'))) {
6966
						$url_path = dol_buildpath(dirname(dirname($classfile)).'/card.php', 1);
6967
					} else {
6968
						$url_path = dol_buildpath(dirname(dirname($classfile)).'/'.strtolower($class).'_card.php', 1);
6969
					}
6970
					$paramforthenewlink = '';
6971
					$paramforthenewlink .= (GETPOSTISSET('action') ? '&action='.GETPOST('action', 'aZ09') : '');
6972
					$paramforthenewlink .= (GETPOSTISSET('id') ? '&id='.GETPOST('id', 'int') : '');
6973
					$paramforthenewlink .= '&fk_'.strtolower($class).'=--IDFORBACKTOPAGE--';
6974
					// TODO Add Javascript code to add input fields already filled into $paramforthenewlink so we won't loose them when going back to main page
6975
					$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>';
6976
				}
6977
			}
6978
		} elseif ($type == 'password') {
6979
			// If prefix is 'search_', field is used as a filter, we use a common text field.
6980
			$out = '<input type="'.($keyprefix == 'search_' ? 'text' : 'password').'" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'>';
6981
		} elseif ($type == 'array') {
6982
			$newval = $val;
6983
			$newval['type'] = 'varchar(256)';
6984
6985
			$out = '';
6986
			if (!empty($value)) {
6987
				foreach ($value as $option) {
6988
					$out .= '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
6989
					$out .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', $option, $moreparam, '', '', $morecss).'<br></span>';
6990
				}
6991
			}
6992
			$out .= '<a id="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_add" href="javascript:;"><span class="fa fa-plus-circle valignmiddle"></span></a>';
6993
6994
			$newInput = '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
6995
			$newInput .= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', '', $moreparam, '', '', $morecss).'<br></span>';
6996
6997
			if (!empty($conf->use_javascript_ajax)) {
6998
				$out .= '
6999
					<script>
7000
					$(document).ready(function() {
7001
						$("a#'.dol_escape_js($keyprefix.$key.$keysuffix).'_add").click(function() {
7002
							$("'.dol_escape_js($newInput).'").insertBefore(this);
7003
						});
7004
7005
						$(document).on("click", "a.'.dol_escape_js($keyprefix.$key.$keysuffix).'_del", function() {
7006
							$(this).parent().remove();
7007
						});
7008
					});
7009
					</script>';
7010
			}
7011
		}
7012
		if (!empty($hidden)) {
7013
			$out = '<input type="hidden" value="'.$value.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'"/>';
7014
		}
7015
7016
		if ($isDependList==1) {
0 ignored issues
show
introduced by
The condition $isDependList == 1 is always false.
Loading history...
7017
			$out .= $this->getJSListDependancies('_common');
7018
		}
7019
		/* Add comments
7020
		 if ($type == 'date') $out.=' (YYYY-MM-DD)';
7021
		 elseif ($type == 'datetime') $out.=' (YYYY-MM-DD HH:MM:SS)';
7022
		 */
7023
7024
		// Display error message for field
7025
		if (!empty($fieldValidationErrorMsg) && function_exists('getFieldErrorIcon')) {
7026
			$out .= ' '.getFieldErrorIcon($fieldValidationErrorMsg);
7027
		}
7028
7029
		return $out;
7030
	}
7031
7032
	/**
7033
	 * Return HTML string to show a field into a page
7034
	 * Code very similar with showOutputField of extra fields
7035
	 *
7036
	 * @param  array   $val		       Array of properties of field to show
7037
	 * @param  string  $key            Key of attribute
7038
	 * @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)
7039
	 * @param  string  $moreparam      To add more parametes on html input tag
7040
	 * @param  string  $keysuffix      Prefix string to add into name and id of field (can be used to avoid duplicate names)
7041
	 * @param  string  $keyprefix      Suffix string to add into name and id of field (can be used to avoid duplicate names)
7042
	 * @param  mixed   $morecss        Value for css to define size. May also be a numeric.
7043
	 * @return string
7044
	 */
7045
	public function showOutputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = '')
7046
	{
7047
		global $conf, $langs, $form;
7048
7049
		if (!is_object($form)) {
7050
			require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
7051
			$form = new Form($this->db);
7052
		}
7053
7054
		$objectid = $this->id;	// Not used ???
7055
7056
		$label = empty($val['label']) ? '' : $val['label'];
7057
		$type  = empty($val['type']) ? '' : $val['type'];
7058
		$size  = empty($val['css']) ? '' : $val['css'];
7059
		$reg = array();
7060
7061
		// Convert var to be able to share same code than showOutputField of extrafields
7062
		if (preg_match('/varchar\((\d+)\)/', $type, $reg)) {
7063
			$type = 'varchar'; // convert varchar(xx) int varchar
7064
			$size = $reg[1];
7065
		} elseif (preg_match('/varchar/', $type)) {
7066
			$type = 'varchar'; // convert varchar(xx) int varchar
7067
		}
7068
		if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
7069
			$type = 'select';
7070
		}
7071
		if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
7072
			$type = 'link';
7073
		}
7074
7075
		$default = empty($val['default']) ? '' : $val['default'];
7076
		$computed = empty($val['computed']) ? '' : $val['computed'];
7077
		$unique = empty($val['unique']) ? '' : $val['unique'];
7078
		$required = empty($val['required']) ? '' : $val['required'];
7079
		$param = array();
7080
		$param['options'] = array();
7081
7082
		if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
7083
			$param['options'] = $val['arrayofkeyval'];
7084
		}
7085
		if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
7086
			$type = 'link';
7087
			$param['options'] = array($reg[1].':'.$reg[2]=>$reg[1].':'.$reg[2]);
7088
		} elseif (preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7089
			$param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7090
			$type = 'sellist';
7091
		} elseif (preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
7092
			$param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
7093
			$type = 'sellist';
7094
		} elseif (preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
7095
			$param['options'] = array($reg[1].':'.$reg[2] => 'N');
7096
			$type = 'sellist';
7097
		}
7098
7099
		$langfile = empty($val['langfile']) ? '' : $val['langfile'];
7100
		$list = (empty($val['list']) ? '' : $val['list']);
7101
		$help = (empty($val['help']) ? '' : $val['help']);
7102
		$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)
7103
7104
		if ($hidden) {
7105
			return '';
7106
		}
7107
7108
		// If field is a computed field, value must become result of compute
7109
		if ($computed) {
7110
			// Make the eval of compute string
7111
			//var_dump($computed);
7112
			$value = dol_eval($computed, 1, 0);
7113
		}
7114
7115
		if (empty($morecss)) {
7116
			if ($type == 'date') {
7117
				$morecss = 'minwidth100imp';
7118
			} elseif ($type == 'datetime' || $type == 'timestamp') {
7119
				$morecss = 'minwidth200imp';
7120
			} elseif (in_array($type, array('int', 'double', 'price'))) {
7121
				$morecss = 'maxwidth75';
7122
			} elseif ($type == 'url') {
7123
				$morecss = 'minwidth400';
7124
			} elseif ($type == 'boolean') {
7125
				$morecss = '';
7126
			} else {
7127
				if (is_numeric($size) && round($size) < 12) {
7128
					$morecss = 'minwidth100';
7129
				} elseif (is_numeric($size) && round($size) <= 48) {
7130
					$morecss = 'minwidth200';
7131
				} else {
7132
					$morecss = 'minwidth400';
7133
				}
7134
			}
7135
		}
7136
7137
		// Format output value differently according to properties of field
7138
		if ($key == 'ref' && method_exists($this, 'getNomUrl')) {
7139
			$value = $this->getNomUrl(1, '', 0, '', 1);
7140
		} elseif ($key == 'status' && method_exists($this, 'getLibStatut')) {
7141
			$value = $this->getLibStatut(3);
7142
		} elseif ($type == 'date') {
7143
			if (!empty($value)) {
7144
				$value = dol_print_date($value, 'day');	// We suppose dates without time are always gmt (storage of course + output)
7145
			} else {
7146
				$value = '';
7147
			}
7148
		} elseif ($type == 'datetime' || $type == 'timestamp') {
7149
			if (!empty($value)) {
7150
				$value = dol_print_date($value, 'dayhour', 'tzuserrel');
7151
			} else {
7152
				$value = '';
7153
			}
7154
		} elseif ($type == 'duration') {
7155
			include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
7156
			if (!is_null($value) && $value !== '') {
7157
				$value = convertSecondToTime($value, 'allhourmin');
7158
			}
7159
		} elseif ($type == 'double' || $type == 'real') {
7160
			if (!is_null($value) && $value !== '') {
7161
				$value = price($value);
7162
			}
7163
		} elseif ($type == 'boolean') {
7164
			$checked = '';
7165
			if (!empty($value)) {
7166
				$checked = ' checked ';
7167
			}
7168
			$value = '<input type="checkbox" '.$checked.' '.($moreparam ? $moreparam : '').' readonly disabled>';
7169
		} elseif ($type == 'mail') {
7170
			$value = dol_print_email($value, 0, 0, 0, 64, 1, 1);
7171
		} elseif ($type == 'url') {
7172
			$value = dol_print_url($value, '_blank', 32, 1);
7173
		} elseif ($type == 'phone') {
7174
			$value = dol_print_phone($value, '', 0, 0, '', '&nbsp;', 1);
7175
		} elseif ($type == 'price') {
7176
			if (!is_null($value) && $value !== '') {
7177
				$value = price($value, 0, $langs, 0, 0, -1, $conf->currency);
7178
			}
7179
		} elseif ($type == 'select') {
7180
			$value = $param['options'][$value];
7181
		} elseif ($type == 'sellist') {
7182
			$param_list = array_keys($param['options']);
7183
			$InfoFieldList = explode(":", $param_list[0]);
7184
7185
			$selectkey = "rowid";
7186
			$keyList = 'rowid';
7187
7188
			if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
7189
				$selectkey = $InfoFieldList[2];
7190
				$keyList = $InfoFieldList[2].' as rowid';
7191
			}
7192
7193
			$fields_label = explode('|', $InfoFieldList[1]);
7194
			if (is_array($fields_label)) {
7195
				$keyList .= ', ';
7196
				$keyList .= implode(', ', $fields_label);
7197
			}
7198
7199
			$sql = 'SELECT '.$keyList;
7200
			$sql .= ' FROM '.MAIN_DB_PREFIX.$InfoFieldList[0];
7201
			if (strpos($InfoFieldList[4], 'extra') !== false) {
7202
				$sql .= ' as main';
7203
			}
7204
			if ($selectkey == 'rowid' && empty($value)) {
7205
				$sql .= " WHERE ".$selectkey." = 0";
7206
			} elseif ($selectkey == 'rowid') {
7207
				$sql .= " WHERE ".$selectkey." = ".((int) $value);
7208
			} else {
7209
				$sql .= " WHERE ".$selectkey." = '".$this->db->escape($value)."'";
7210
			}
7211
7212
			//$sql.= ' AND entity = '.$conf->entity;
7213
7214
			dol_syslog(get_class($this).':showOutputField:$type=sellist', LOG_DEBUG);
7215
			$resql = $this->db->query($sql);
7216
			if ($resql) {
7217
				$value = ''; // value was used, so now we reste it to use it to build final output
7218
7219
				$obj = $this->db->fetch_object($resql);
7220
7221
				// Several field into label (eq table:code|libelle:rowid)
7222
				$fields_label = explode('|', $InfoFieldList[1]);
7223
7224
				if (is_array($fields_label) && count($fields_label) > 1) {
7225
					foreach ($fields_label as $field_toshow) {
7226
						$translabel = '';
7227
						if (!empty($obj->$field_toshow)) {
7228
							$translabel = $langs->trans($obj->$field_toshow);
7229
						}
7230
						if ($translabel != $field_toshow) {
7231
							$value .= dol_trunc($translabel, 18).' ';
7232
						} else {
7233
							$value .= $obj->$field_toshow.' ';
7234
						}
7235
					}
7236
				} else {
7237
					$translabel = '';
7238
					if (!empty($obj->{$InfoFieldList[1]})) {
7239
						$translabel = $langs->trans($obj->{$InfoFieldList[1]});
7240
					}
7241
					if ($translabel != $obj->{$InfoFieldList[1]}) {
7242
						$value = dol_trunc($translabel, 18);
7243
					} else {
7244
						$value = $obj->{$InfoFieldList[1]};
7245
					}
7246
				}
7247
			} else {
7248
				dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
7249
			}
7250
		} elseif ($type == 'radio') {
7251
			$value = $param['options'][$value];
7252
		} elseif ($type == 'checkbox') {
7253
			$value_arr = explode(',', $value);
7254
			$value = '';
7255
			if (is_array($value_arr) && count($value_arr) > 0) {
7256
				$toprint = array();
7257
				foreach ($value_arr as $keyval => $valueval) {
7258
					$toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">'.$param['options'][$valueval].'</li>';
7259
				}
7260
				$value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
7261
			}
7262
		} elseif ($type == 'chkbxlst') {
7263
			$value_arr = explode(',', $value);
7264
7265
			$param_list = array_keys($param['options']);
7266
			$InfoFieldList = explode(":", $param_list[0]);
7267
7268
			$selectkey = "rowid";
7269
			$keyList = 'rowid';
7270
7271
			if (count($InfoFieldList) >= 3) {
7272
				$selectkey = $InfoFieldList[2];
7273
				$keyList = $InfoFieldList[2].' as rowid';
7274
			}
7275
7276
			$fields_label = explode('|', $InfoFieldList[1]);
7277
			if (is_array($fields_label)) {
7278
				$keyList .= ', ';
7279
				$keyList .= implode(', ', $fields_label);
7280
			}
7281
7282
			$sql = 'SELECT '.$keyList;
7283
			$sql .= ' FROM '.MAIN_DB_PREFIX.$InfoFieldList[0];
7284
			if (strpos($InfoFieldList[4], 'extra') !== false) {
7285
				$sql .= ' as main';
7286
			}
7287
			// $sql.= " WHERE ".$selectkey."='".$this->db->escape($value)."'";
7288
			// $sql.= ' AND entity = '.$conf->entity;
7289
7290
			dol_syslog(get_class($this).':showOutputField:$type=chkbxlst', LOG_DEBUG);
7291
			$resql = $this->db->query($sql);
7292
			if ($resql) {
7293
				$value = ''; // value was used, so now we reste it to use it to build final output
7294
				$toprint = array();
7295
				while ($obj = $this->db->fetch_object($resql)) {
7296
					// Several field into label (eq table:code|libelle:rowid)
7297
					$fields_label = explode('|', $InfoFieldList[1]);
7298
					if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
7299
						if (is_array($fields_label) && count($fields_label) > 1) {
7300
							foreach ($fields_label as $field_toshow) {
7301
								$translabel = '';
7302
								if (!empty($obj->$field_toshow)) {
7303
									$translabel = $langs->trans($obj->$field_toshow);
7304
								}
7305
								if ($translabel != $field_toshow) {
7306
									$toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">'.dol_trunc($translabel, 18).'</li>';
7307
								} else {
7308
									$toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">'.$obj->$field_toshow.'</li>';
7309
								}
7310
							}
7311
						} else {
7312
							$translabel = '';
7313
							if (!empty($obj->{$InfoFieldList[1]})) {
7314
								$translabel = $langs->trans($obj->{$InfoFieldList[1]});
7315
							}
7316
							if ($translabel != $obj->{$InfoFieldList[1]}) {
7317
								$toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">'.dol_trunc($translabel, 18).'</li>';
7318
							} else {
7319
								$toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #bbb">'.$obj->{$InfoFieldList[1]}.'</li>';
7320
							}
7321
						}
7322
					}
7323
				}
7324
				$value = '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
7325
			} else {
7326
				dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
7327
			}
7328
		} elseif ($type == 'link') {
7329
			$out = '';
7330
7331
			// only if something to display (perf)
7332
			if ($value) {
7333
				$param_list = array_keys($param['options']); // $param_list='ObjectName:classPath'
7334
7335
				$InfoFieldList = explode(":", $param_list[0]);
7336
				$classname = $InfoFieldList[0];
7337
				$classpath = $InfoFieldList[1];
7338
				$getnomurlparam = (empty($InfoFieldList[2]) ? 3 : $InfoFieldList[2]);
7339
				$getnomurlparam2 = (empty($InfoFieldList[4]) ? '' : $InfoFieldList[4]);
7340
				if (!empty($classpath)) {
7341
					dol_include_once($InfoFieldList[1]);
7342
					if ($classname && class_exists($classname)) {
7343
						$object = new $classname($this->db);
7344
						if ($object->element === 'product') {	// Special cas for product because default valut of fetch are wrong
7345
							$object->fetch($value, '', '', '', 0, 1, 1);
7346
						} else {
7347
							$object->fetch($value);
7348
						}
7349
						$value = $object->getNomUrl($getnomurlparam, $getnomurlparam2);
7350
					}
7351
				} else {
7352
					dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7353
					return 'Error bad setup of extrafield';
7354
				}
7355
			} else {
7356
				$value = '';
7357
			}
7358
		} elseif (preg_match('/^(text|html)/', $type)) {
7359
			$value = dol_htmlentitiesbr($value);
7360
		} elseif ($type == 'password') {
7361
			$value = preg_replace('/./i', '*', $value);
7362
		} elseif ($type == 'array') {
7363
			$value = implode('<br>', $value);
7364
		}
7365
7366
		//print $type.'-'.$size.'-'.$value;
7367
		$out = $value;
7368
7369
		return $out;
7370
	}
7371
7372
	/**
7373
	 * clear validation message result for a field
7374
	 *
7375
	 * @param string $fieldKey Key of attribute to clear
7376
	 * @return null
7377
	 */
7378
	public function clearFieldError($fieldKey)
7379
	{
7380
		$this->error = '';
7381
		unset($this->validateFieldsErrors[$fieldKey]);
7382
	}
7383
7384
	/**
7385
	 * set validation error message a field
7386
	 *
7387
	 * @param string $fieldKey Key of attribute
7388
	 * @param string $msg the field error message
7389
	 * @return null
7390
	 */
7391
	public function setFieldError($fieldKey, $msg = '')
7392
	{
7393
		global $langs;
7394
		if (empty($msg)) { $msg = $langs->trans("UnknowError"); }
7395
7396
		$this->error = $this->validateFieldsErrors[$fieldKey] = $msg;
7397
	}
7398
7399
	/**
7400
	 * get field error message
7401
	 *
7402
	 * @param  string  $fieldKey            Key of attribute
7403
	 * @return string
7404
	 */
7405
	public function getFieldError($fieldKey)
7406
	{
7407
		if (!empty($this->validateFieldsErrors[$fieldKey])) {
7408
			return $this->validateFieldsErrors[$fieldKey];
7409
		}
7410
		return '';
7411
	}
7412
7413
	/**
7414
	 * Return validation test result for a field
7415
	 *
7416
	 * @param  array   $val		       		Array of properties of field to show
7417
	 * @param  string  $fieldKey            Key of attribute
7418
	 * @param  string  $fieldValue          value of attribute
7419
	 * @return bool return false if fail true on success, see $this->error for error message
7420
	 */
7421
	public function validateField($val, $fieldKey, $fieldValue)
7422
	{
7423
		global $langs;
7424
7425
		if (!class_exists('Validate')) { require_once DOL_DOCUMENT_ROOT . '/core/class/validate.class.php'; }
7426
7427
		$this->clearFieldError($fieldKey);
7428
7429
		if (!isset($val[$fieldKey])) {
7430
			$this->setFieldError($fieldKey, $langs->trans('FieldNotFoundInObject'));
7431
			return false;
7432
		}
7433
7434
		$param = array();
7435
		$param['options'] = array();
7436
		$type  = $val[$fieldKey]['type'];
7437
7438
		$required = false;
7439
		if (isset($val[$fieldKey]['notnull']) && $val[$fieldKey]['notnull'] === 1) {
7440
			// 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
7441
			$required = true;
7442
		}
7443
7444
		$maxSize = 0;
7445
		$minSize = 0;
7446
7447
		//
7448
		// PREPARE Elements
7449
		//
7450
7451
		// Convert var to be able to share same code than showOutputField of extrafields
7452
		if (preg_match('/varchar\((\d+)\)/', $type, $reg)) {
7453
			$type = 'varchar'; // convert varchar(xx) int varchar
7454
			$maxSize = $reg[1];
7455
		} elseif (preg_match('/varchar/', $type)) {
7456
			$type = 'varchar'; // convert varchar(xx) int varchar
7457
		}
7458
7459
		if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
7460
			$type = 'select';
7461
		}
7462
7463
		if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
7464
			$type = 'link';
7465
		}
7466
7467
		if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
7468
			$param['options'] = $val['arrayofkeyval'];
7469
		}
7470
7471
		if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) {
7472
			$type = 'link';
7473
			$param['options'] = array($reg[1].':'.$reg[2]=>$reg[1].':'.$reg[2]);
7474
		} elseif (preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7475
			$param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7476
			$type = 'sellist';
7477
		} elseif (preg_match('/^sellist:(.*):(.*):(.*)/i', $val['type'], $reg)) {
7478
			$param['options'] = array($reg[1].':'.$reg[2].':'.$reg[3] => 'N');
7479
			$type = 'sellist';
7480
		} elseif (preg_match('/^sellist:(.*):(.*)/i', $val['type'], $reg)) {
7481
			$param['options'] = array($reg[1].':'.$reg[2] => 'N');
7482
			$type = 'sellist';
7483
		}
7484
7485
		//
7486
		// TEST Value
7487
		//
7488
7489
		// Use Validate class to allow external Modules to use data validation part instead of concentrate all test here (factoring) or just for reuse
7490
		$validate = new Validate($this->db, $langs);
7491
7492
7493
		// little trick : to perform tests with good performances sort tests by quick to low
7494
7495
		//
7496
		// COMMON TESTS
7497
		//
7498
7499
		// Required test and empty value
7500
		if ($required && !$validate->isNotEmptyString($fieldValue)) {
7501
			$this->setFieldError($fieldKey, $validate->error);
7502
			return false;
7503
		} elseif (!$required && !$validate->isNotEmptyString($fieldValue)) {
7504
			// if no value sent and the field is not mandatory, no need to perform tests
7505
			return true;
7506
		}
7507
7508
		// MAX Size test
7509
		if (!empty($maxSize) && !$validate->isMaxLength($fieldValue, $maxSize)) {
7510
			$this->setFieldError($fieldKey, $validate->error);
7511
			return false;
7512
		}
7513
7514
		// MIN Size test
7515
		if (!empty($minSize) && !$validate->isMinLength($fieldValue, $minSize)) {
7516
			$this->setFieldError($fieldKey, $validate->error);
7517
			return false;
7518
		}
7519
7520
		//
7521
		// TESTS for TYPE
7522
		//
7523
7524
		if (in_array($type, array('date', 'datetime', 'timestamp'))) {
7525
			if (!$validate->isTimestamp($fieldValue)) {
7526
				$this->setFieldError($fieldKey, $validate->error);
7527
				return false;
7528
			} else { return true; }
7529
		} elseif ($type == 'duration') {
7530
			if (!$validate->isDuration($fieldValue)) {
7531
				$this->setFieldError($fieldKey, $validate->error);
7532
				return false;
7533
			} else { return true; }
7534
		} elseif (in_array($type, array('double', 'real', 'price'))) {
7535
			// is numeric
7536
			if (!$validate->isDuration($fieldValue)) {
7537
				$this->setFieldError($fieldKey, $validate->error);
7538
				return false;
7539
			} else { return true; }
7540
		} elseif ($type == 'boolean') {
7541
			if (!$validate->isBool($fieldValue)) {
7542
				$this->setFieldError($fieldKey, $validate->error);
7543
				return false;
7544
			} else { return true; }
7545
		} elseif ($type == 'mail') {
7546
			if (!$validate->isEmail($fieldValue)) {
7547
				$this->setFieldError($fieldKey, $validate->error);
7548
				return false;
7549
			}
7550
		} elseif ($type == 'url') {
7551
			if (!$validate->isUrl($fieldValue)) {
7552
				$this->setFieldError($fieldKey, $validate->error);
7553
				return false;
7554
			} else { return true; }
7555
		} elseif ($type == 'phone') {
7556
			if (!$validate->isPhone($fieldValue)) {
7557
				$this->setFieldError($fieldKey, $validate->error);
7558
				return false;
7559
			} else { return true; }
7560
		} elseif ($type == 'select' || $type == 'radio') {
7561
			if (!isset($param['options'][$fieldValue])) {
7562
				$this->error = $langs->trans('RequireValidValue');
7563
				return false;
7564
			} else { return true; }
7565
		} elseif ($type == 'sellist' || $type == 'chkbxlst') {
7566
			$param_list = array_keys($param['options']);
7567
			$InfoFieldList = explode(":", $param_list[0]);
7568
			$value_arr = explode(',', $fieldValue);
7569
			$value_arr = array_map(array($this->db, 'escape'), $value_arr);
7570
7571
			$selectkey = "rowid";
7572
			if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
7573
				$selectkey = $InfoFieldList[2];
7574
			}
7575
7576
			if (!isInDb($value_arr, $InfoFieldList[0], $selectkey)) {
0 ignored issues
show
Bug introduced by
The function isInDb was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

7576
			if (!/** @scrutinizer ignore-call */ isInDb($value_arr, $InfoFieldList[0], $selectkey)) {
Loading history...
7577
				$this->setFieldError($fieldKey, $validate->error);
7578
				return false;
7579
			} else { return true; }
7580
		} elseif ($type == 'link') {
7581
			$param_list = array_keys($param['options']); // $param_list='ObjectName:classPath'
7582
			$InfoFieldList = explode(":", $param_list[0]);
7583
			$classname = $InfoFieldList[0];
7584
			$classpath = $InfoFieldList[1];
7585
			if (!$validate->isFetchable($fieldValue, $classname, $classpath)) {
7586
				$this->setFieldError($fieldKey, $validate->error);
7587
				return false;
7588
			} else { return true; }
7589
		}
7590
7591
		// if no test failled all is ok
7592
		return true;
7593
	}
7594
7595
	/**
7596
	 * Function to show lines of extrafields with output datas.
7597
	 * 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
7598
	 *
7599
	 * @param 	Extrafields $extrafields    Extrafield Object
7600
	 * @param 	string      $mode           Show output ('view') or input ('create' or 'edit') for extrafield
7601
	 * @param 	array       $params         Optional parameters. Example: array('style'=>'class="oddeven"', 'colspan'=>$colspan)
7602
	 * @param 	string      $keysuffix      Suffix string to add after name and id of field (can be used to avoid duplicate names)
7603
	 * @param 	string      $keyprefix      Prefix string to add before name and id of field (can be used to avoid duplicate names)
7604
	 * @param	string		$onetrtd		All fields in same tr td. Used by objectline_create.tpl.php for example.
7605
	 * @param	string		$display_type	"card" for form display, "line" for document line display (extrafields on propal line, order line, etc...)
7606
	 * @return 	string
7607
	 */
7608
	public function showOptionals($extrafields, $mode = 'view', $params = null, $keysuffix = '', $keyprefix = '', $onetrtd = 0, $display_type = 'card')
7609
	{
7610
		global $db, $conf, $langs, $action, $form, $hookmanager;
7611
7612
		if (!is_object($form)) {
7613
			$form = new Form($db);
7614
		}
7615
7616
		$out = '';
7617
7618
		$parameters = array();
7619
		$reshook = $hookmanager->executeHooks('showOptionals', $parameters, $this, $action); // Note that $action and $object may have been modified by hook
7620
		if (empty($reshook)) {
7621
			if (key_exists('label', $extrafields->attributes[$this->table_element]) && is_array($extrafields->attributes[$this->table_element]['label']) && count($extrafields->attributes[$this->table_element]['label']) > 0) {
7622
				$out .= "\n";
7623
				$out .= '<!-- showOptionals --> ';
7624
				$out .= "\n";
7625
7626
				$extrafields_collapse_num = '';
7627
				$e = 0;
7628
				foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $label) {
7629
					// Show only the key field in params
7630
					if (is_array($params) && array_key_exists('onlykey', $params) && $key != $params['onlykey']) {
7631
						continue;
7632
					}
7633
7634
					// Test on 'enabled' ('enabled' is different than 'list' = 'visibility')
7635
					$enabled = 1;
7636
					if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
7637
						$enabled = dol_eval($extrafields->attributes[$this->table_element]['enabled'][$key], 1);
7638
					}
7639
					if (empty($enabled)) {
7640
						continue;
7641
					}
7642
7643
					$visibility = 1;
7644
					if ($visibility && isset($extrafields->attributes[$this->table_element]['list'][$key])) {
7645
						$visibility = dol_eval($extrafields->attributes[$this->table_element]['list'][$key], 1);
7646
					}
7647
7648
					$perms = 1;
7649
					if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
7650
						$perms = dol_eval($extrafields->attributes[$this->table_element]['perms'][$key], 1);
7651
					}
7652
7653
					if (($mode == 'create') && abs($visibility) != 1 && abs($visibility) != 3) {
7654
						continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list
7655
					} elseif (($mode == 'edit') && abs($visibility) != 1 && abs($visibility) != 3 && abs($visibility) != 4) {
7656
						continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list and <> 4 = not visible at the creation
7657
					} elseif ($mode == 'view' && empty($visibility)) {
7658
						continue;
7659
					}
7660
					if (empty($perms)) {
7661
						continue;
7662
					}
7663
					// Load language if required
7664
					if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
7665
						$langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
7666
					}
7667
7668
					$colspan = '';
7669
					if (is_array($params) && count($params) > 0 && $display_type=='card') {
7670
						if (array_key_exists('cols', $params)) {
7671
							$colspan = $params['cols'];
7672
						} elseif (array_key_exists('colspan', $params)) {	// For backward compatibility. Use cols instead now.
7673
							$reg = array();
7674
							if (preg_match('/colspan="(\d+)"/', $params['colspan'], $reg)) {
7675
								$colspan = $reg[1];
7676
							} else {
7677
								$colspan = $params['colspan'];
7678
							}
7679
						}
7680
					}
7681
7682
					switch ($mode) {
7683
						case "view":
7684
							$value = $this->array_options["options_".$key.$keysuffix]; // Value may be clean or formated later
7685
							break;
7686
						case "create":
7687
						case "edit":
7688
							// We get the value of property found with GETPOST so it takes into account:
7689
							// default values overwrite, restore back to list link, ... (but not 'default value in database' of field)
7690
							$check = 'alphanohtml';
7691
							if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text'))) {
7692
								$check = 'restricthtml';
7693
							}
7694
							$getposttemp = GETPOST($keyprefix.'options_'.$key.$keysuffix, $check, 3); // GETPOST can get value from GET, POST or setup of default values overwrite.
7695
							// GETPOST("options_" . $key) can be 'abc' or array(0=>'abc')
7696
							if (is_array($getposttemp) || $getposttemp != '' || GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix)) {
7697
								if (is_array($getposttemp)) {
7698
									// $getposttemp is an array but following code expects a comma separated string
7699
									$value = implode(",", $getposttemp);
7700
								} else {
7701
									$value = $getposttemp;
7702
								}
7703
							} else {
7704
								$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.
7705
							}
7706
							//var_dump($keyprefix.' - '.$key.' - '.$keysuffix.' - '.$keyprefix.'options_'.$key.$keysuffix.' - '.$this->array_options["options_".$key.$keysuffix].' - '.$getposttemp.' - '.$value);
7707
							break;
7708
					}
7709
7710
					// Output value of the current field
7711
					if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
7712
						$extrafields_collapse_num = '';
7713
						$extrafield_param = $extrafields->attributes[$this->table_element]['param'][$key];
7714
						if (!empty($extrafield_param) && is_array($extrafield_param)) {
7715
							$extrafield_param_list = array_keys($extrafield_param['options']);
7716
7717
							if (count($extrafield_param_list) > 0) {
7718
								$extrafield_collapse_display_value = intval($extrafield_param_list[0]);
7719
7720
								if ($extrafield_collapse_display_value == 1 || $extrafield_collapse_display_value == 2) {
7721
									$extrafields_collapse_num = $extrafields->attributes[$this->table_element]['pos'][$key];
7722
								}
7723
							}
7724
						}
7725
7726
						$out .= $extrafields->showSeparator($key, $this, ($colspan + 1), $display_type);
7727
					} else {
7728
						$class = (!empty($extrafields->attributes[$this->table_element]['hidden'][$key]) ? 'hideobject ' : '');
7729
						$csstyle = '';
7730
						if (is_array($params) && count($params) > 0) {
7731
							if (array_key_exists('class', $params)) {
7732
								$class .= $params['class'].' ';
7733
							}
7734
							if (array_key_exists('style', $params)) {
7735
								$csstyle = $params['style'];
7736
							}
7737
						}
7738
7739
						// add html5 elements
7740
						$domData  = ' data-element="extrafield"';
7741
						$domData .= ' data-targetelement="'.$this->element.'"';
7742
						$domData .= ' data-targetid="'.$this->id.'"';
7743
7744
						$html_id = (empty($this->id) ? '' : 'extrarow-'.$this->element.'_'.$key.'_'.$this->id);
7745
						if ($display_type=='card') {
7746
							if (!empty($conf->global->MAIN_EXTRAFIELDS_USE_TWO_COLUMS) && ($e % 2) == 0) {
7747
								$colspan = '0';
7748
							}
7749
7750
							if ($action == 'selectlines') {
7751
								$colspan++;
7752
							}
7753
						}
7754
7755
						// Convert date into timestamp format (value in memory must be a timestamp)
7756
						if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date'))) {
7757
							$datenotinstring = $this->array_options['options_'.$key];
7758
							if (!is_numeric($this->array_options['options_'.$key])) {	// For backward compatibility
7759
								$datenotinstring = $this->db->jdate($datenotinstring);
7760
							}
7761
							$value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix)) ? dol_mktime(12, 0, 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;
7762
						}
7763
						if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('datetime'))) {
7764
							$datenotinstring = $this->array_options['options_'.$key];
7765
							if (!is_numeric($this->array_options['options_'.$key])) {	// For backward compatibility
7766
								$datenotinstring = $this->db->jdate($datenotinstring);
7767
							}
7768
							$value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix)) ? dol_mktime(GETPOST($keyprefix.'options_'.$key.$keysuffix."hour", 'int', 3), GETPOST($keyprefix.'options_'.$key.$keysuffix."min", 'int', 3), GETPOST($keyprefix.'options_'.$key.$keysuffix."sec", 'int', 3), GETPOST($keyprefix.'options_'.$key.$keysuffix."month", 'int', 3), GETPOST($keyprefix.'options_'.$key.$keysuffix."day", 'int', 3), GETPOST($keyprefix.'options_'.$key.$keysuffix."year", 'int', 3), 'tzuserrel') : $datenotinstring;
7769
						}
7770
						// Convert float submited string into real php numeric (value in memory must be a php numeric)
7771
						if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('price', 'double'))) {
7772
							$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...
7773
						}
7774
7775
						// HTML, text, select, integer and varchar: take into account default value in database if in create mode
7776
						if (in_array($extrafields->attributes[$this->table_element]['type'][$key], array('html', 'text', 'varchar', 'select', 'int'))) {
7777
							if ($action == 'create') {
7778
								$value = (GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix) || $value) ? $value : $extrafields->attributes[$this->table_element]['default'][$key];
7779
							}
7780
						}
7781
7782
						$labeltoshow = $langs->trans($label);
7783
						$helptoshow = $langs->trans($extrafields->attributes[$this->table_element]['help'][$key]);
7784
7785
						if ($display_type == 'card') {
7786
							$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.' >';
7787
							if (!empty($conf->global->MAIN_VIEW_LINE_NUMBER) && ($action == 'view' || $action == 'editline')) {
7788
								$out .= '<td></td>';
7789
							}
7790
							$out .= '<td class="wordbreak';
7791
						} elseif ($display_type == 'line') {
7792
							$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.' >';
7793
							$out .= '<div style="display: inline-block; padding-right:4px" class="wordbreak';
7794
						}
7795
						//$out .= "titlefield";
7796
						//if (GETPOST('action', 'restricthtml') == 'create') $out.='create';
7797
						// BUG #11554 : For public page, use red dot for required fields, instead of bold label
7798
						$tpl_context = isset($params["tpl_context"]) ? $params["tpl_context"] : "none";
7799
						if ($tpl_context == "public") {	// Public page : red dot instead of fieldrequired characters
7800
							$out .= '">';
7801
							if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
7802
								$out .= $form->textwithpicto($labeltoshow, $helptoshow);
7803
							} else {
7804
								$out .= $labeltoshow;
7805
							}
7806
							if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
7807
								$out .= '&nbsp;<font color="red">*</font>';
7808
							}
7809
						} else {
7810
							if ($mode != 'view' && !empty($extrafields->attributes[$this->table_element]['required'][$key])) {
7811
								$out .= ' fieldrequired';
7812
							}
7813
							$out .= '">';
7814
							if (!empty($extrafields->attributes[$this->table_element]['help'][$key])) {
7815
								$out .= $form->textwithpicto($labeltoshow, $helptoshow);
7816
							} else {
7817
								$out .= $labeltoshow;
7818
							}
7819
						}
7820
7821
						$out .= ($display_type == 'card' ? '</td>' : '</div>');
7822
7823
						$html_id = !empty($this->id) ? $this->element.'_extras_'.$key.'_'.$this->id : '';
7824
						if ($display_type == 'card') {
7825
							$out .= '<td '.($html_id ? 'id="'.$html_id.'" ' : '').' class="'.$this->element.'_extras_'.$key.'" '.($colspan ? ' colspan="'.$colspan.'"' : '').'>';
7826
						} elseif ($display_type == 'line') {
7827
							$out .= '<div '.($html_id ? 'id="'.$html_id.'" ' : '').' style="display: inline-block" class="'.$this->element.'_extras_'.$key.'">';
7828
						}
7829
7830
						switch ($mode) {
7831
							case "view":
7832
								$out .= $extrafields->showOutputField($key, $value);
7833
								break;
7834
							case "create":
7835
								$out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', 0, $this->id, $this->table_element);
7836
								break;
7837
							case "edit":
7838
								$out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', 0, $this->id, $this->table_element);
7839
								break;
7840
						}
7841
7842
						$out .= ($display_type=='card' ? '</td>' : '</div>');
7843
7844
						/*for($ii = 0; $ii < ($colspan - 1); $ii++)
7845
						{
7846
							$out .='<td class="'.$this->element.'_extras_'.$key.'"></td>';
7847
						}*/
7848
7849
						if (!empty($conf->global->MAIN_EXTRAFIELDS_USE_TWO_COLUMS) && (($e % 2) == 1)) {
7850
							$out .= ($display_type=='card' ? '</tr>' : '</div>');
7851
						} else {
7852
							$out .= ($display_type=='card' ? '</tr>' : '</div>');
7853
						}
7854
						$e++;
7855
					}
7856
				}
7857
				$out .= "\n";
7858
				// Add code to manage list depending on others
7859
				if (!empty($conf->use_javascript_ajax)) {
7860
					$out .= $this->getJSListDependancies();
7861
				}
7862
7863
				$out .= '<!-- /showOptionals --> '."\n";
7864
			}
7865
		}
7866
7867
		$out .= $hookmanager->resPrint;
7868
7869
		return $out;
7870
	}
7871
7872
	/**
7873
	 * @param 	string 	$type	Type for prefix
7874
	 * @return 	string			Javacript code to manage dependency
7875
	 */
7876
	public function getJSListDependancies($type = '_extra')
7877
	{
7878
		$out = '
7879
					<script>
7880
					jQuery(document).ready(function() {
7881
						function showOptions'.$type.'(child_list, parent_list, orig_select)
7882
						{
7883
							var val = $("select[name=\""+parent_list+"\"]").val();
7884
							var parentVal = parent_list + ":" + val;
7885
							if(typeof val == "string"){
7886
				    		    if(val != "") {
7887
					    			var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
7888
									$("select[name=\""+child_list+"\"] option[parent]").remove();
7889
									$("select[name=\""+child_list+"\"]").append(options);
7890
								} else {
7891
									var options = orig_select.find("option[parent]").clone();
7892
									$("select[name=\""+child_list+"\"] option[parent]").remove();
7893
									$("select[name=\""+child_list+"\"]").append(options);
7894
								}
7895
				    		} else if(val > 0) {
7896
								var options = orig_select.find("option[parent=\""+parentVal+"\"]").clone();
7897
								$("select[name=\""+child_list+"\"] option[parent]").remove();
7898
								$("select[name=\""+child_list+"\"]").append(options);
7899
							} else {
7900
								var options = orig_select.find("option[parent]").clone();
7901
								$("select[name=\""+child_list+"\"] option[parent]").remove();
7902
								$("select[name=\""+child_list+"\"]").append(options);
7903
							}
7904
						}
7905
						function setListDependencies'.$type.'() {
7906
							jQuery("select option[parent]").parent().each(function() {
7907
								var orig_select = {};
7908
								var child_list = $(this).attr("name");
7909
								orig_select[child_list] = $(this).clone();
7910
								var parent = $(this).find("option[parent]:first").attr("parent");
7911
								var infos = parent.split(":");
7912
								var parent_list = infos[0];
7913
7914
								//Hide daughters lists
7915
								if ($("#"+child_list).val() == 0 && $("#"+parent_list).val() == 0){
7916
								    $("#"+child_list).hide();
7917
								//Show mother lists
7918
								} else if ($("#"+parent_list).val() != 0){
7919
								    $("#"+parent_list).show();
7920
								}
7921
								//Show the child list if the parent list value is selected
7922
								$("select[name=\""+parent_list+"\"]").click(function() {
7923
								    if ($(this).val() != 0){
7924
								        $("#"+child_list).show()
7925
									}
7926
								});
7927
7928
								//When we change parent list
7929
								$("select[name=\""+parent_list+"\"]").change(function() {
7930
									showOptions'.$type.'(child_list, parent_list, orig_select[child_list]);
7931
									//Select the value 0 on child list after a change on the parent list
7932
									$("#"+child_list).val(0).trigger("change");
7933
									//Hide child lists if the parent value is set to 0
7934
									if ($(this).val() == 0){
7935
								   		$("#"+child_list).hide();
7936
									}
7937
								});
7938
							});
7939
						}
7940
7941
						setListDependencies'.$type.'();
7942
					});
7943
					</script>'."\n";
7944
		return $out;
7945
	}
7946
7947
	/**
7948
	 * Returns the rights used for this class
7949
	 * @return stdClass
7950
	 */
7951
	public function getRights()
7952
	{
7953
		global $user;
7954
7955
		$element = $this->element;
7956
		if ($element == 'facturerec') {
7957
			$element = 'facture';
7958
		}
7959
7960
		return $user->rights->{$element};
7961
	}
7962
7963
	/**
7964
	 * Function used to replace a thirdparty id with another one.
7965
	 * This function is meant to be called from replaceThirdparty with the appropiate tables
7966
	 * Column name fk_soc MUST be used to identify thirdparties
7967
	 *
7968
	 * @param  DoliDB 	   $db 			  Database handler
7969
	 * @param  int 		   $origin_id     Old thirdparty id (the thirdparty to delete)
7970
	 * @param  int 		   $dest_id       New thirdparty id (the thirdparty that will received element of the other)
7971
	 * @param  string[]    $tables        Tables that need to be changed
7972
	 * @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)
7973
	 * @return bool						  True if success, False if error
7974
	 */
7975
	public static function commonReplaceThirdparty(DoliDB $db, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
7976
	{
7977
		foreach ($tables as $table) {
7978
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$table.' SET fk_soc = '.((int) $dest_id).' WHERE fk_soc = '.((int) $origin_id);
7979
7980
			if (!$db->query($sql)) {
7981
				if ($ignoreerrors) {
7982
					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.
7983
				}
7984
				//$this->errors = $db->lasterror();
7985
				return false;
7986
			}
7987
		}
7988
7989
		return true;
7990
	}
7991
7992
	/**
7993
	 * Get buy price to use for margin calculation. This function is called when buy price is unknown.
7994
	 *	 Set buy price = sell price if ForceBuyingPriceIfNull configured,
7995
	 *   else if calculation MARGIN_TYPE = 'costprice' and costprice is defined, use costprice as buyprice
7996
	 *	 else if calculation MARGIN_TYPE = 'pmp' and pmp is calculated, use pmp as buyprice
7997
	 *	 else set min buy price as buy price
7998
	 *
7999
	 * @param float		$unitPrice		 Product unit price
8000
	 * @param float		$discountPercent Line discount percent
8001
	 * @param int		$fk_product		 Product id
8002
	 * @return	float                    <0 if KO, buyprice if OK
8003
	 */
8004
	public function defineBuyPrice($unitPrice = 0.0, $discountPercent = 0.0, $fk_product = 0)
8005
	{
8006
		global $conf;
8007
8008
		$buyPrice = 0;
8009
8010
		if (($unitPrice > 0) && (isset($conf->global->ForceBuyingPriceIfNull) && $conf->global->ForceBuyingPriceIfNull > 0)) {
8011
			 // When ForceBuyingPriceIfNull is set
8012
			$buyPrice = $unitPrice * (1 - $discountPercent / 100);
8013
		} else {
8014
			// Get cost price for margin calculation
8015
			if (!empty($fk_product) && $fk_product > 0) {
8016
				if (isset($conf->global->MARGIN_TYPE) && $conf->global->MARGIN_TYPE == 'costprice') {
8017
					require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
8018
					$product = new Product($this->db);
8019
					$result = $product->fetch($fk_product);
8020
					if ($result <= 0) {
8021
						$this->errors[] = 'ErrorProductIdDoesNotExists';
8022
						return -1;
8023
					}
8024
					if ($product->cost_price > 0) {
8025
						$buyPrice = $product->cost_price;
8026
					} elseif ($product->pmp > 0) {
8027
						$buyPrice = $product->pmp;
8028
					}
8029
				} elseif (isset($conf->global->MARGIN_TYPE) && $conf->global->MARGIN_TYPE == 'pmp') {
8030
					require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
8031
					$product = new Product($this->db);
8032
					$result = $product->fetch($fk_product);
8033
					if ($result <= 0) {
8034
						$this->errors[] = 'ErrorProductIdDoesNotExists';
8035
						return -1;
8036
					}
8037
					if ($product->pmp > 0) {
8038
						$buyPrice = $product->pmp;
8039
					}
8040
				}
8041
8042
				if (empty($buyPrice) && isset($conf->global->MARGIN_TYPE) && in_array($conf->global->MARGIN_TYPE, array('1', 'pmp', 'costprice'))) {
8043
					require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
8044
					$productFournisseur = new ProductFournisseur($this->db);
8045
					if (($result = $productFournisseur->find_min_price_product_fournisseur($fk_product)) > 0) {
8046
						$buyPrice = $productFournisseur->fourn_unitprice;
8047
					} elseif ($result < 0) {
8048
						$this->errors[] = $productFournisseur->error;
8049
						return -2;
8050
					}
8051
				}
8052
			}
8053
		}
8054
		return $buyPrice;
8055
	}
8056
8057
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
8058
	/**
8059
	 *  Show photos of an object (nbmax maximum), into several columns
8060
	 *
8061
	 *  @param		string	$modulepart		'product', 'ticket', ...
8062
	 *  @param      string	$sdir        	Directory to scan (full absolute path)
8063
	 *  @param      int		$size        	0=original size, 1='small' use thumbnail if possible
8064
	 *  @param      int		$nbmax       	Nombre maximum de photos (0=pas de max)
8065
	 *  @param      int		$nbbyrow     	Number of image per line or -1 to use div. Used only if size=1.
8066
	 * 	@param		int		$showfilename	1=Show filename
8067
	 * 	@param		int		$showaction		1=Show icon with action links (resize, delete)
8068
	 * 	@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.
8069
	 * 	@param		int		$maxWidth		Max width of original image when size='small'
8070
	 *  @param      int     $nolink         Do not add a href link to view enlarged imaged into a new tab
8071
	 *  @param      int     $notitle        Do not add title tag on image
8072
	 *  @param		int		$usesharelink	Use the public shared link of image (if not available, the 'nophoto' image will be shown instead)
8073
	 *  @return     string					Html code to show photo. Number of photos shown is saved in this->nbphoto
8074
	 */
8075
	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)
8076
	{
8077
		// phpcs:enable
8078
		global $conf, $user, $langs;
8079
8080
		include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
8081
		include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
8082
8083
		$sortfield = 'position_name';
8084
		$sortorder = 'asc';
8085
8086
		$dir = $sdir.'/';
8087
		$pdir = '/';
8088
8089
		$dir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
8090
		$pdir .= get_exdir(0, 0, 0, 0, $this, $modulepart);
8091
8092
		// For backward compatibility
8093
		if ($modulepart == 'product') {
8094
			if (!empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO)) {
8095
				$dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
8096
				$pdir = '/'.get_exdir($this->id, 2, 0, 0, $this, $modulepart).$this->id."/photos/";
8097
			}
8098
		}
8099
8100
		// Defined relative dir to DOL_DATA_ROOT
8101
		$relativedir = '';
8102
		if ($dir) {
8103
			$relativedir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $dir);
8104
			$relativedir = preg_replace('/^[\\/]/', '', $relativedir);
8105
			$relativedir = preg_replace('/[\\/]$/', '', $relativedir);
8106
		}
8107
8108
		$dirthumb = $dir.'thumbs/';
8109
		$pdirthumb = $pdir.'thumbs/';
8110
8111
		$return = '<!-- Photo -->'."\n";
8112
		$nbphoto = 0;
8113
8114
		$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...
8115
8116
		/*if (! empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO))    // For backward compatiblity, we scan also old dirs
8117
		 {
8118
		 $filearrayold=dol_dir_list($dirold,"files",0,'','(\.meta|_preview.*\.png)$',$sortfield,(strtolower($sortorder)=='desc'?SORT_DESC:SORT_ASC),1);
8119
		 $filearray=array_merge($filearray, $filearrayold);
8120
		 }*/
8121
8122
		completeFileArrayWithDatabaseInfo($filearray, $relativedir);
8123
8124
		if (count($filearray)) {
8125
			if ($sortfield && $sortorder) {
8126
				$filearray = dol_sort_array($filearray, $sortfield, $sortorder);
8127
			}
8128
8129
			foreach ($filearray as $key => $val) {
8130
				$photo = '';
8131
				$file = $val['name'];
8132
8133
				//if (! utf8_check($file)) $file=utf8_encode($file);	// To be sure file is stored in UTF8 in memory
8134
8135
				//if (dol_is_file($dir.$file) && image_format_supported($file) >= 0)
8136
				if (image_format_supported($file) >= 0) {
8137
					$nbphoto++;
8138
					$photo = $file;
8139
					$viewfilename = $file;
8140
8141
					if ($size == 1 || $size == 'small') {   // Format vignette
8142
						// Find name of thumb file
8143
						$photo_vignette = basename(getImageFileNameForSize($dir.$file, '_small'));
8144
						if (!dol_is_file($dirthumb.$photo_vignette)) {
8145
							$photo_vignette = '';
8146
						}
8147
8148
						// Get filesize of original file
8149
						$imgarray = dol_getImageSize($dir.$photo);
8150
8151
						if ($nbbyrow > 0) {
8152
							if ($nbphoto == 1) {
8153
								$return .= '<table class="valigntop center centpercent" style="border: 0; padding: 2px; border-spacing: 2px; border-collapse: separate;">';
8154
							}
8155
8156
							if ($nbphoto % $nbbyrow == 1) {
8157
								$return .= '<tr class="center valignmiddle" style="border: 1px">';
8158
							}
8159
							$return .= '<td style="width: '.ceil(100 / $nbbyrow).'%" class="photo">';
8160
						} elseif ($nbbyrow < 0) {
8161
							$return .= '<div class="inline-block">';
8162
						}
8163
8164
						$return .= "\n";
8165
8166
						$relativefile = preg_replace('/^\//', '', $pdir.$photo);
8167
						if (empty($nolink)) {
8168
							$urladvanced = getAdvancedPreviewUrl($modulepart, $relativefile, 0, 'entity='.$this->entity);
8169
							if ($urladvanced) {
8170
								$return .= '<a href="'.$urladvanced.'">';
8171
							} else {
8172
								$return .= '<a href="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'" class="aphoto" target="_blank">';
8173
							}
8174
						}
8175
8176
						// Show image (width height=$maxHeight)
8177
						// Si fichier vignette disponible et image source trop grande, on utilise la vignette, sinon on utilise photo origine
8178
						$alt = $langs->transnoentitiesnoconv('File').': '.$relativefile;
8179
						$alt .= ' - '.$langs->transnoentitiesnoconv('Size').': '.$imgarray['width'].'x'.$imgarray['height'];
8180
						if ($notitle) {
8181
							$alt = '';
8182
						}
8183
8184
						if ($usesharelink) {
8185
							if ($val['share']) {
8186
								if (empty($maxHeight) || $photo_vignette && $imgarray['height'] > $maxHeight) {
8187
									$return .= '<!-- Show original file (thumb not yet available with shared links) -->';
8188
									$return .= '<img class="photo photowithmargin" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/viewimage.php?hashp='.urlencode($val['share']).'" title="'.dol_escape_htmltag($alt).'">';
8189
								} else {
8190
									$return .= '<!-- Show original file -->';
8191
									$return .= '<img class="photo photowithmargin" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/viewimage.php?hashp='.urlencode($val['share']).'" title="'.dol_escape_htmltag($alt).'">';
8192
								}
8193
							} else {
8194
								$return .= '<!-- Show nophoto file (because file is not shared) -->';
8195
								$return .= '<img class="photo photowithmargin" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/public/theme/common/nophoto.png" title="'.dol_escape_htmltag($alt).'">';
8196
							}
8197
						} else {
8198
							if (empty($maxHeight) || $photo_vignette && $imgarray['height'] > $maxHeight) {
8199
								$return .= '<!-- Show thumb -->';
8200
								$return .= '<img class="photo photowithmargin maxwidth150onsmartphone maxwidth200" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdirthumb.$photo_vignette).'" title="'.dol_escape_htmltag($alt).'">';
8201
							} else {
8202
								$return .= '<!-- Show original file -->';
8203
								$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).'">';
8204
							}
8205
						}
8206
8207
						if (empty($nolink)) {
8208
							$return .= '</a>';
8209
						}
8210
						$return .= "\n";
8211
8212
						if ($showfilename) {
8213
							$return .= '<br>'.$viewfilename;
8214
						}
8215
						if ($showaction) {
8216
							$return .= '<br>';
8217
							// On propose la generation de la vignette si elle n'existe pas et si la taille est superieure aux limites
8218
							if ($photo_vignette && (image_format_supported($photo) > 0) && ($this->imgWidth > $maxWidth || $this->imgHeight > $maxHeight)) {
8219
								$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>';
8220
							}
8221
							// Special cas for product
8222
							if ($modulepart == 'product' && ($user->rights->produit->creer || $user->rights->service->creer)) {
8223
								// Link to resize
8224
								$return .= '<a href="'.DOL_URL_ROOT.'/core/photos_resize.php?modulepart='.urlencode('produit|service').'&id='.$this->id.'&file='.urlencode($pdir.$viewfilename).'" title="'.dol_escape_htmltag($langs->trans("Resize")).'">'.img_picto($langs->trans("Resize"), 'resize', '').'</a> &nbsp; ';
8225
8226
								// Link to delete
8227
								$return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=delete&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">';
8228
								$return .= img_delete().'</a>';
8229
							}
8230
						}
8231
						$return .= "\n";
8232
8233
						if ($nbbyrow > 0) {
8234
							$return .= '</td>';
8235
							if (($nbphoto % $nbbyrow) == 0) {
8236
								$return .= '</tr>';
8237
							}
8238
						} elseif ($nbbyrow < 0) {
8239
							$return .= '</div>';
8240
						}
8241
					}
8242
8243
					if (empty($size)) {     // Format origine
8244
						$return .= '<img class="photo photowithmargin" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'">';
8245
8246
						if ($showfilename) {
8247
							$return .= '<br>'.$viewfilename;
8248
						}
8249
						if ($showaction) {
8250
							// Special case for product
8251
							if ($modulepart == 'product' && ($user->rights->produit->creer || $user->rights->service->creer)) {
8252
								// Link to resize
8253
								$return .= '<a href="'.DOL_URL_ROOT.'/core/photos_resize.php?modulepart='.urlencode('produit|service').'&id='.$this->id.'&file='.urlencode($pdir.$viewfilename).'" title="'.dol_escape_htmltag($langs->trans("Resize")).'">'.img_picto($langs->trans("Resize"), 'resize', '').'</a> &nbsp; ';
8254
8255
								// Link to delete
8256
								$return .= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&action=delete&token='.newToken().'&file='.urlencode($pdir.$viewfilename).'">';
8257
								$return .= img_delete().'</a>';
8258
							}
8259
						}
8260
					}
8261
8262
					// On continue ou on arrete de boucler ?
8263
					if ($nbmax && $nbphoto >= $nbmax) {
8264
						break;
8265
					}
8266
				}
8267
			}
8268
8269
			if ($size == 1 || $size == 'small') {
8270
				if ($nbbyrow > 0) {
8271
					// Ferme tableau
8272
					while ($nbphoto % $nbbyrow) {
8273
						$return .= '<td style="width: '.ceil(100 / $nbbyrow).'%">&nbsp;</td>';
8274
						$nbphoto++;
8275
					}
8276
8277
					if ($nbphoto) {
8278
						$return .= '</table>';
8279
					}
8280
				}
8281
			}
8282
		}
8283
8284
		$this->nbphoto = $nbphoto;
8285
8286
		return $return;
8287
	}
8288
8289
8290
	/**
8291
	 * Function test if type is array
8292
	 *
8293
	 * @param   array   $info   content informations of field
8294
	 * @return  bool			true if array
8295
	 */
8296
	protected function isArray($info)
8297
	{
8298
		if (is_array($info)) {
8299
			if (isset($info['type']) && $info['type'] == 'array') {
8300
				return true;
8301
			} else {
8302
				return false;
8303
			}
8304
		}
8305
		return false;
8306
	}
8307
8308
	/**
8309
	 * Function test if type is date
8310
	 *
8311
	 * @param   array   $info   content informations of field
8312
	 * @return  bool			true if date
8313
	 */
8314
	public function isDate($info)
8315
	{
8316
		if (isset($info['type']) && ($info['type'] == 'date' || $info['type'] == 'datetime' || $info['type'] == 'timestamp')) {
8317
			return true;
8318
		}
8319
		return false;
8320
	}
8321
8322
	/**
8323
	 * Function test if type is duration
8324
	 *
8325
	 * @param   array   $info   content informations of field
8326
	 * @return  bool			true if field of type duration
8327
	 */
8328
	public function isDuration($info)
8329
	{
8330
		if (is_array($info)) {
8331
			if (isset($info['type']) && ($info['type'] == 'duration')) {
8332
				return true;
8333
			} else {
8334
				return false;
8335
			}
8336
		} else {
8337
			return false;
8338
		}
8339
	}
8340
8341
	/**
8342
	 * Function test if type is integer
8343
	 *
8344
	 * @param   array   $info   content informations of field
8345
	 * @return  bool			true if integer
8346
	 */
8347
	public function isInt($info)
8348
	{
8349
		if (is_array($info)) {
8350
			if (isset($info['type']) && ($info['type'] == 'int' || preg_match('/^integer/i', $info['type']))) {
8351
				return true;
8352
			} else {
8353
				return false;
8354
			}
8355
		} else {
8356
			return false;
8357
		}
8358
	}
8359
8360
	/**
8361
	 * Function test if type is float
8362
	 *
8363
	 * @param   array   $info   content informations of field
8364
	 * @return  bool			true if float
8365
	 */
8366
	public function isFloat($info)
8367
	{
8368
		if (is_array($info)) {
8369
			if (isset($info['type']) && (preg_match('/^(double|real|price)/i', $info['type']))) {
8370
				return true;
8371
			} else {
8372
				return false;
8373
			}
8374
		}
8375
		return false;
8376
	}
8377
8378
	/**
8379
	 * Function test if type is text
8380
	 *
8381
	 * @param   array   $info   content informations of field
8382
	 * @return  bool			true if type text
8383
	 */
8384
	public function isText($info)
8385
	{
8386
		if (is_array($info)) {
8387
			if (isset($info['type']) && $info['type'] == 'text') {
8388
				return true;
8389
			} else {
8390
				return false;
8391
			}
8392
		}
8393
		return false;
8394
	}
8395
8396
	/**
8397
	 * Function test if field can be null
8398
	 *
8399
	 * @param   array   $info   content informations of field
8400
	 * @return  bool			true if it can be null
8401
	 */
8402
	protected function canBeNull($info)
8403
	{
8404
		if (is_array($info)) {
8405
			if (isset($info['notnull']) && $info['notnull'] != '1') {
8406
				return true;
8407
			} else {
8408
				return false;
8409
			}
8410
		}
8411
		return true;
8412
	}
8413
8414
	/**
8415
	 * Function test if field is forced to null if zero or empty
8416
	 *
8417
	 * @param   array   $info   content informations of field
8418
	 * @return  bool			true if forced to null
8419
	 */
8420
	protected function isForcedToNullIfZero($info)
8421
	{
8422
		if (is_array($info)) {
8423
			if (isset($info['notnull']) && $info['notnull'] == '-1') {
8424
				return true;
8425
			} else {
8426
				return false;
8427
			}
8428
		}
8429
		return false;
8430
	}
8431
8432
	/**
8433
	 * Function test if is indexed
8434
	 *
8435
	 * @param   array   $info   content informations of field
8436
	 * @return                  bool
8437
	 */
8438
	protected function isIndex($info)
8439
	{
8440
		if (is_array($info)) {
8441
			if (isset($info['index']) && $info['index'] == true) {
8442
				return true;
8443
			} else {
8444
				return false;
8445
			}
8446
		}
8447
		return false;
8448
	}
8449
8450
8451
	/**
8452
	 * Function to prepare a part of the query for insert.
8453
	 * Note $this->${field} are set by the page that make the createCommon or the updateCommon.
8454
	 * $this->${field} should be a clean value. The page can run
8455
	 *
8456
	 * @return array
8457
	 */
8458
	protected function setSaveQuery()
8459
	{
8460
		global $conf;
8461
8462
		$queryarray = array();
8463
		foreach ($this->fields as $field => $info) {	// Loop on definition of fields
8464
			// Depending on field type ('datetime', ...)
8465
			if ($this->isDate($info)) {
8466
				if (empty($this->{$field})) {
8467
					$queryarray[$field] = null;
8468
				} else {
8469
					$queryarray[$field] = $this->db->idate($this->{$field});
8470
				}
8471
			} elseif ($this->isDuration($info)) {
8472
				// $this->{$field} may be null, '', 0, '0', 123, '123'
8473
				if ((isset($this->{$field}) && $this->{$field} != '') || !empty($info['notnull'])) {
8474
					if (!isset($this->{$field})) {
8475
						$queryarray[$field] = 0;
8476
					} else {
8477
						$queryarray[$field] = (int) $this->{$field};		// If '0', it may be set to null later if $info['notnull'] == -1
8478
					}
8479
				} else {
8480
					$queryarray[$field] = null;
8481
				}
8482
			} elseif ($this->isInt($info) || $this->isFloat($info)) {
8483
				if ($field == 'entity' && is_null($this->{$field})) {
8484
					$queryarray[$field] = $conf->entity;
8485
				} else {
8486
					// $this->{$field} may be null, '', 0, '0', 123, '123'
8487
					if ((isset($this->{$field}) && $this->{$field} != '') || !empty($info['notnull'])) {
8488
						if (!isset($this->{$field})) {
8489
							$queryarray[$field] = 0;
8490
						} elseif ($this->isInt($info)) {
8491
							$queryarray[$field] = (int) $this->{$field};	// If '0', it may be set to null later if $info['notnull'] == -1
8492
						} elseif ($this->isFloat($info)) {
8493
							$queryarray[$field] = (double) $this->{$field};	// If '0', it may be set to null later if $info['notnull'] == -1
8494
						}
8495
					} else {
8496
						$queryarray[$field] = null;
8497
					}
8498
				}
8499
			} else {
8500
				$queryarray[$field] = $this->{$field};
8501
			}
8502
8503
			if ($info['type'] == 'timestamp' && empty($queryarray[$field])) {
8504
				unset($queryarray[$field]);
8505
			}
8506
			if (!empty($info['notnull']) && $info['notnull'] == -1 && empty($queryarray[$field])) {
8507
				$queryarray[$field] = null; // May force 0 to null
8508
			}
8509
		}
8510
8511
		return $queryarray;
8512
	}
8513
8514
	/**
8515
	 * Function to load data from a SQL pointer into properties of current object $this
8516
	 *
8517
	 * @param   stdClass    $obj    Contain data of object from database
8518
	 * @return void
8519
	 */
8520
	public function setVarsFromFetchObj(&$obj)
8521
	{
8522
		global $db;
8523
8524
		foreach ($this->fields as $field => $info) {
8525
			if ($this->isDate($info)) {
8526
				if (is_null($obj->{$field}) || $obj->{$field} === '' || $obj->{$field} === '0000-00-00 00:00:00' || $obj->{$field} === '1000-01-01 00:00:00') {
8527
					$this->{$field} = '';
8528
				} else {
8529
					$this->{$field} = $db->jdate($obj->{$field});
8530
				}
8531
			} elseif ($this->isInt($info)) {
8532
				if ($field == 'rowid') {
8533
					$this->id = (int) $obj->{$field};
8534
				} else {
8535
					if ($this->isForcedToNullIfZero($info)) {
8536
						if (empty($obj->{$field})) {
8537
							$this->{$field} = null;
8538
						} else {
8539
							$this->{$field} = (double) $obj->{$field};
8540
						}
8541
					} else {
8542
						if (!is_null($obj->{$field}) || (isset($info['notnull']) && $info['notnull'] == 1)) {
8543
							$this->{$field} = (int) $obj->{$field};
8544
						} else {
8545
							$this->{$field} = null;
8546
						}
8547
					}
8548
				}
8549
			} elseif ($this->isFloat($info)) {
8550
				if ($this->isForcedToNullIfZero($info)) {
8551
					if (empty($obj->{$field})) {
8552
						$this->{$field} = null;
8553
					} else {
8554
						$this->{$field} = (double) $obj->{$field};
8555
					}
8556
				} else {
8557
					if (!is_null($obj->{$field}) || (isset($info['notnull']) && $info['notnull'] == 1)) {
8558
						$this->{$field} = (double) $obj->{$field};
8559
					} else {
8560
						$this->{$field} = null;
8561
					}
8562
				}
8563
			} else {
8564
				$this->{$field} = $obj->{$field};
8565
			}
8566
		}
8567
8568
		// If there is no 'ref' field, we force property ->ref to ->id for a better compatibility with common functions.
8569
		if (!isset($this->fields['ref']) && isset($this->id)) {
8570
			$this->ref = $this->id;
8571
		}
8572
	}
8573
8574
	/**
8575
	 * Function to concat keys of fields
8576
	 *
8577
	 * @param   string   $alias   	String of alias of table for fields. For example 't'.
8578
	 * @return  string				list of alias fields
8579
	 */
8580
	public function getFieldList($alias = '')
8581
	{
8582
		$keys = array_keys($this->fields);
8583
		if (!empty($alias)) {
8584
			$keys_with_alias = array();
8585
			foreach ($keys as $fieldname) {
8586
				$keys_with_alias[] = $alias . '.' . $fieldname;
8587
			}
8588
			return implode(',', $keys_with_alias);
8589
		} else {
8590
			return implode(',', $keys);
8591
		}
8592
	}
8593
8594
	/**
8595
	 * Add quote to field value if necessary
8596
	 *
8597
	 * @param 	string|int	$value			Value to protect
8598
	 * @param	array		$fieldsentry	Properties of field
8599
	 * @return 	string
8600
	 */
8601
	protected function quote($value, $fieldsentry)
8602
	{
8603
		if (is_null($value)) {
1 ignored issue
show
introduced by
The condition is_null($value) is always false.
Loading history...
8604
			return 'NULL';
8605
		} elseif (preg_match('/^(int|double|real|price)/i', $fieldsentry['type'])) {
8606
			return price2num("$value");
8607
		} elseif ($fieldsentry['type'] == 'boolean') {
8608
			if ($value) {
8609
				return 'true';
8610
			} else {
8611
				return 'false';
8612
			}
8613
		} else {
8614
			return "'".$this->db->escape($value)."'";
8615
		}
8616
	}
8617
8618
8619
	/**
8620
	 * Create object into database
8621
	 *
8622
	 * @param  User $user      User that creates
8623
	 * @param  bool $notrigger false=launch triggers after, true=disable triggers
8624
	 * @return int             <0 if KO, Id of created object if OK
8625
	 */
8626
	public function createCommon(User $user, $notrigger = false)
8627
	{
8628
		global $langs;
8629
		dol_syslog(get_class($this)."::createCommon create", LOG_DEBUG);
8630
8631
		$error = 0;
8632
8633
		$now = dol_now();
8634
8635
		$fieldvalues = $this->setSaveQuery();
8636
8637
		if (array_key_exists('date_creation', $fieldvalues) && empty($fieldvalues['date_creation'])) {
8638
			$fieldvalues['date_creation'] = $this->db->idate($now);
8639
		}
8640
		if (array_key_exists('fk_user_creat', $fieldvalues) && !($fieldvalues['fk_user_creat'] > 0)) {
8641
			$fieldvalues['fk_user_creat'] = $user->id;
8642
		}
8643
		unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into insert.
8644
		if (array_key_exists('ref', $fieldvalues)) {
8645
			$fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
8646
		}
8647
8648
		$keys = array();
8649
		$values = array(); // Array to store string forged for SQL syntax
8650
		foreach ($fieldvalues as $k => $v) {
8651
			$keys[$k] = $k;
8652
			$value = $this->fields[$k];
8653
			$values[$k] = $this->quote($v, $value); // May return string 'NULL' if $value is null
8654
		}
8655
8656
		// Clean and check mandatory
8657
		foreach ($keys as $key) {
8658
			// If field is an implicit foreign key field
8659
			if (preg_match('/^integer:/i', $this->fields[$key]['type']) && $values[$key] == '-1') {
8660
				$values[$key] = '';
8661
			}
8662
			if (!empty($this->fields[$key]['foreignkey']) && $values[$key] == '-1') {
8663
				$values[$key] = '';
8664
			}
8665
8666
			if (isset($this->fields[$key]['notnull']) && $this->fields[$key]['notnull'] == 1 && (!isset($values[$key]) || $values[$key] === 'NULL') && is_null($this->fields[$key]['default'])) {
8667
				$error++;
8668
				$langs->load("errors");
8669
				dol_syslog("Mandatory field '".$key."' is empty and required into ->fields definition of class");
8670
				$this->errors[] = $langs->trans("ErrorFieldRequired", $this->fields[$key]['label']);
8671
			}
8672
8673
			// If value is null and there is a default value for field
8674
			if (isset($this->fields[$key]['notnull']) && $this->fields[$key]['notnull'] == 1 && (!isset($values[$key]) || $values[$key] === 'NULL') && !is_null($this->fields[$key]['default'])) {
8675
				$values[$key] = $this->quote($this->fields[$key]['default'], $this->fields[$key]);
8676
			}
8677
8678
			// If field is an implicit foreign key field
8679
			if (preg_match('/^integer:/i', $this->fields[$key]['type']) && empty($values[$key])) {
8680
				if (isset($this->fields[$key]['default'])) {
8681
					$values[$key] = $this->fields[$key]['default'];
8682
				} else {
8683
					$values[$key] = 'null';
8684
				}
8685
			}
8686
			if (!empty($this->fields[$key]['foreignkey']) && empty($values[$key])) {
8687
				$values[$key] = 'null';
8688
			}
8689
		}
8690
8691
		if ($error) {
8692
			return -1;
8693
		}
8694
8695
		$this->db->begin();
8696
8697
		if (!$error) {
8698
			$sql = 'INSERT INTO '.MAIN_DB_PREFIX.$this->table_element;
8699
			$sql .= ' ('.implode(", ", $keys).')';
8700
			$sql .= ' VALUES ('.implode(", ", $values).')';
8701
8702
			$res = $this->db->query($sql);
8703
			if ($res === false) {
0 ignored issues
show
introduced by
The condition $res === false is always false.
Loading history...
8704
				$error++;
8705
				$this->errors[] = $this->db->lasterror();
8706
			}
8707
		}
8708
8709
		if (!$error) {
8710
			$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
8711
		}
8712
8713
		// If we have a field ref with a default value of (PROV)
8714
		if (!$error) {
8715
			if (key_exists('ref', $this->fields) && $this->fields['ref']['notnull'] > 0 && key_exists('default', $this->fields['ref']) && $this->fields['ref']['default'] == '(PROV)') {
8716
				$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET ref = '(PROV".$this->id.")' WHERE (ref = '(PROV)' OR ref = '') AND rowid = ".((int) $this->id);
8717
				$resqlupdate = $this->db->query($sql);
8718
8719
				if ($resqlupdate === false) {
0 ignored issues
show
introduced by
The condition $resqlupdate === false is always false.
Loading history...
8720
					$error++;
8721
					$this->errors[] = $this->db->lasterror();
8722
				} else {
8723
					$this->ref = '(PROV'.$this->id.')';
8724
				}
8725
			}
8726
		}
8727
8728
		// Create extrafields
8729
		if (!$error) {
8730
			$result = $this->insertExtraFields();
8731
			if ($result < 0) {
8732
				$error++;
8733
			}
8734
		}
8735
8736
		// Create lines
8737
		if (!empty($this->table_element_line) && !empty($this->fk_element)) {
8738
			$num = (is_array($this->lines) ? count($this->lines) : 0);
8739
			for ($i = 0; $i < $num; $i++) {
8740
				$line = $this->lines[$i];
8741
8742
				$keyforparent = $this->fk_element;
8743
				$line->$keyforparent = $this->id;
8744
8745
				// Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
8746
				//if (! is_object($line)) $line=json_decode(json_encode($line), false);  // convert recursively array into object.
8747
				if (!is_object($line)) {
8748
					$line = (object) $line;
8749
				}
8750
8751
				$result = $line->create($user, 1);
8752
				if ($result < 0) {
8753
					$this->error = $line->error;
8754
					$this->db->rollback();
8755
					return -1;
8756
				}
8757
			}
8758
		}
8759
8760
		// Triggers
8761
		if (!$error && !$notrigger) {
8762
			// Call triggers
8763
			$result = $this->call_trigger(strtoupper(get_class($this)).'_CREATE', $user);
8764
			if ($result < 0) {
8765
				$error++;
8766
			}
8767
			// End call triggers
8768
		}
8769
8770
		// Commit or rollback
8771
		if ($error) {
8772
			$this->db->rollback();
8773
			return -1;
8774
		} else {
8775
			$this->db->commit();
8776
			return $this->id;
8777
		}
8778
	}
8779
8780
8781
	/**
8782
	 * Load object in memory from the database
8783
	 *
8784
	 * @param	int    	$id				Id object
8785
	 * @param	string 	$ref			Ref
8786
	 * @param	string	$morewhere		More SQL filters (' AND ...')
8787
	 * @return 	int         			<0 if KO, 0 if not found, >0 if OK
8788
	 */
8789
	public function fetchCommon($id, $ref = null, $morewhere = '')
8790
	{
8791
		if (empty($id) && empty($ref) && empty($morewhere)) {
8792
			return -1;
8793
		}
8794
8795
		$fieldlist = $this->getFieldList('t');
8796
		if (empty($fieldlist)) {
8797
			return 0;
8798
		}
8799
8800
		$sql = "SELECT ".$fieldlist;
8801
		$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element.' as t';
8802
8803
		if (!empty($id)) {
8804
			$sql .= ' WHERE t.rowid = '.((int) $id);
8805
		} elseif (!empty($ref)) {
8806
			$sql .= " WHERE t.ref = '".$this->db->escape($ref)."'";
8807
		} else {
8808
			$sql .= ' WHERE 1 = 1'; // usage with empty id and empty ref is very rare
8809
		}
8810
		if (empty($id) && isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
8811
			$sql .= ' AND t.entity IN ('.getEntity($this->table_element).')';
8812
		}
8813
		if ($morewhere) {
8814
			$sql .= $morewhere;
8815
		}
8816
		$sql .= ' LIMIT 1'; // This is a fetch, to be sure to get only one record
8817
8818
		$res = $this->db->query($sql);
8819
		if ($res) {
8820
			$obj = $this->db->fetch_object($res);
8821
			if ($obj) {
8822
				$this->setVarsFromFetchObj($obj);
8823
8824
				// Retrieve all extrafield
8825
				// fetch optionals attributes and labels
8826
				$this->fetch_optionals();
8827
8828
				return $this->id;
8829
			} else {
8830
				return 0;
8831
			}
8832
		} else {
8833
			$this->error = $this->db->lasterror();
8834
			$this->errors[] = $this->error;
8835
			return -1;
8836
		}
8837
	}
8838
8839
	/**
8840
	 * Load object in memory from the database
8841
	 *
8842
	 * @param	string	$morewhere		More SQL filters (' AND ...')
8843
	 * @return 	int         			<0 if KO, 0 if not found, >0 if OK
8844
	 */
8845
	public function fetchLinesCommon($morewhere = '')
8846
	{
8847
		$objectlineclassname = get_class($this).'Line';
8848
		if (!class_exists($objectlineclassname)) {
8849
			$this->error = 'Error, class '.$objectlineclassname.' not found during call of fetchLinesCommon';
8850
			return -1;
8851
		}
8852
8853
		$objectline = new $objectlineclassname($this->db);
8854
8855
		$sql = "SELECT ".$objectline->getFieldList('l');
8856
		$sql .= " FROM ".MAIN_DB_PREFIX.$objectline->table_element." as l";
8857
		$sql .= " WHERE l.fk_".$this->element." = ".((int) $this->id);
8858
		if ($morewhere) {
8859
			$sql .= $morewhere;
8860
		}
8861
		if (isset($objectline->fields['position'])) {
8862
			$sql .= $this->db->order('position', 'ASC');
8863
		}
8864
8865
		$resql = $this->db->query($sql);
8866
		if ($resql) {
8867
			$num_rows = $this->db->num_rows($resql);
8868
			$i = 0;
8869
			while ($i < $num_rows) {
8870
				$obj = $this->db->fetch_object($resql);
8871
				if ($obj) {
8872
					$newline = new $objectlineclassname($this->db);
8873
					$newline->setVarsFromFetchObj($obj);
8874
8875
					$this->lines[] = $newline;
8876
				}
8877
				$i++;
8878
			}
8879
8880
			return 1;
8881
		} else {
8882
			$this->error = $this->db->lasterror();
8883
			$this->errors[] = $this->error;
8884
			return -1;
8885
		}
8886
	}
8887
8888
	/**
8889
	 * Update object into database
8890
	 *
8891
	 * @param  User $user      	User that modifies
8892
	 * @param  bool $notrigger 	false=launch triggers after, true=disable triggers
8893
	 * @return int             	<0 if KO, >0 if OK
8894
	 */
8895
	public function updateCommon(User $user, $notrigger = false)
8896
	{
8897
		global $conf, $langs;
8898
		dol_syslog(get_class($this)."::updateCommon update", LOG_DEBUG);
8899
8900
		$error = 0;
8901
8902
		$now = dol_now();
8903
8904
		$fieldvalues = $this->setSaveQuery();
8905
8906
		if (array_key_exists('date_modification', $fieldvalues) && empty($fieldvalues['date_modification'])) {
8907
			$fieldvalues['date_modification'] = $this->db->idate($now);
8908
		}
8909
		if (array_key_exists('fk_user_modif', $fieldvalues) && !($fieldvalues['fk_user_modif'] > 0)) {
8910
			$fieldvalues['fk_user_modif'] = $user->id;
8911
		}
8912
		unset($fieldvalues['rowid']); // The field 'rowid' is reserved field name for autoincrement field so we don't need it into update.
8913
		if (array_key_exists('ref', $fieldvalues)) {
8914
			$fieldvalues['ref'] = dol_string_nospecial($fieldvalues['ref']); // If field is a ref, we sanitize data
8915
		}
8916
8917
		// Add quotes and escape on fields with type string
8918
		$keys = array();
8919
		$values = array();
8920
		$tmp = array();
8921
		foreach ($fieldvalues as $k => $v) {
8922
			$keys[$k] = $k;
8923
			$value = $this->fields[$k];
8924
			$values[$k] = $this->quote($v, $value);
8925
			$tmp[] = $k.'='.$this->quote($v, $this->fields[$k]);
8926
		}
8927
8928
		// Clean and check mandatory fields
8929
		foreach ($keys as $key) {
8930
			if (preg_match('/^integer:/i', $this->fields[$key]['type']) && $values[$key] == '-1') {
8931
				$values[$key] = ''; // This is an implicit foreign key field
8932
			}
8933
			if (!empty($this->fields[$key]['foreignkey']) && $values[$key] == '-1') {
8934
				$values[$key] = ''; // This is an explicit foreign key field
8935
			}
8936
8937
			//var_dump($key.'-'.$values[$key].'-'.($this->fields[$key]['notnull'] == 1));
8938
			/*
8939
			if ($this->fields[$key]['notnull'] == 1 && empty($values[$key]))
8940
			{
8941
				$error++;
8942
				$this->errors[]=$langs->trans("ErrorFieldRequired", $this->fields[$key]['label']);
8943
			}*/
8944
		}
8945
8946
		$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element.' SET '.implode(', ', $tmp).' WHERE rowid='.((int) $this->id);
8947
8948
		$this->db->begin();
8949
		if (!$error) {
8950
			$res = $this->db->query($sql);
8951
			if ($res === false) {
0 ignored issues
show
introduced by
The condition $res === false is always false.
Loading history...
8952
				$error++;
8953
				$this->errors[] = $this->db->lasterror();
8954
			}
8955
		}
8956
8957
		// Update extrafield
8958
		if (!$error) {
8959
			$result = $this->insertExtraFields();
8960
			if ($result < 0) {
8961
				$error++;
8962
			}
8963
		}
8964
8965
		// Triggers
8966
		if (!$error && !$notrigger) {
8967
			// Call triggers
8968
			$result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $user);
8969
			if ($result < 0) {
8970
				$error++;
8971
			} //Do also here what you must do to rollback action if trigger fail
8972
			// End call triggers
8973
		}
8974
8975
		// Commit or rollback
8976
		if ($error) {
8977
			$this->db->rollback();
8978
			return -1;
8979
		} else {
8980
			$this->db->commit();
8981
			return $this->id;
8982
		}
8983
	}
8984
8985
	/**
8986
	 * Delete object in database
8987
	 *
8988
	 * @param 	User 	$user       			User that deletes
8989
	 * @param 	bool 	$notrigger  			false=launch triggers after, true=disable triggers
8990
	 * @param	int		$forcechilddeletion		0=no, 1=Force deletion of children
8991
	 * @return 	int             				<=0 if KO, 0=Nothing done because object has child, >0 if OK
8992
	 */
8993
	public function deleteCommon(User $user, $notrigger = false, $forcechilddeletion = 0)
8994
	{
8995
		dol_syslog(get_class($this)."::deleteCommon delete", LOG_DEBUG);
8996
8997
		$error = 0;
8998
8999
		$this->db->begin();
9000
9001
		if ($forcechilddeletion) {	// Force also delete of childtables that should lock deletion in standard case when option force is off
9002
			foreach ($this->childtables as $table) {
9003
				$sql = "DELETE FROM ".MAIN_DB_PREFIX.$table." WHERE ".$this->fk_element." = ".((int) $this->id);
9004
				$resql = $this->db->query($sql);
9005
				if (!$resql) {
9006
					$this->error = $this->db->lasterror();
9007
					$this->errors[] = $this->error;
9008
					$this->db->rollback();
9009
					return -1;
9010
				}
9011
			}
9012
		} elseif (!empty($this->fk_element) && !empty($this->childtables)) {	// If object has childs linked with a foreign key field, we check all child tables.
9013
			$objectisused = $this->isObjectUsed($this->id);
9014
			if (!empty($objectisused)) {
9015
				dol_syslog(get_class($this)."::deleteCommon Can't delete record as it has some child", LOG_WARNING);
9016
				$this->error = 'ErrorRecordHasChildren';
9017
				$this->errors[] = $this->error;
9018
				$this->db->rollback();
9019
				return 0;
9020
			}
9021
		}
9022
9023
		// Delete cascade first
9024
		if (is_array($this->childtablesoncascade) && !empty($this->childtablesoncascade)) {
9025
			foreach ($this->childtablesoncascade as $table) {
9026
				$deleteFromObject = explode(':', $table);
9027
				if (count($deleteFromObject) >= 2) {
9028
					$className = str_replace('@', '', $deleteFromObject[0]);
9029
					$filePath = $deleteFromObject[1];
9030
					$columnName = $deleteFromObject[2];
9031
					if (dol_include_once($filePath)) {
9032
						$childObject = new $className($this->db);
9033
						if (method_exists($childObject, 'deleteByParentField')) {
9034
							$result = $childObject->deleteByParentField($this->id, $columnName);
9035
							if ($result < 0) {
9036
								$error++;
9037
								$this->errors[] = $childObject->error;
9038
								break;
9039
							}
9040
						} else {
9041
							$error++;
9042
							$this->errors[] = "You defined a cascade delete on an object $childObject but there is no method deleteByParentField for it";
9043
							break;
9044
						}
9045
					} else {
9046
						$error++;
9047
						$this->errors[] = 'Cannot include child class file '.$filePath;
9048
						break;
9049
					}
9050
				} else {
9051
					// Delete record in child table
9052
					$sql = "DELETE FROM ".MAIN_DB_PREFIX.$table." WHERE ".$this->fk_element." = ".((int) $this->id);
9053
9054
					$resql = $this->db->query($sql);
9055
					if (!$resql) {
9056
						$error++;
9057
						$this->error = $this->db->lasterror();
9058
						$this->errors[] = $this->error;
9059
						break;
9060
					}
9061
				}
9062
			}
9063
		}
9064
9065
		if (!$error) {
9066
			if (!$notrigger) {
9067
				// Call triggers
9068
				$result = $this->call_trigger(strtoupper(get_class($this)).'_DELETE', $user);
9069
				if ($result < 0) {
9070
					$error++;
9071
				} // Do also here what you must do to rollback action if trigger fail
9072
				// End call triggers
9073
			}
9074
		}
9075
9076
		// Delete llx_ecm_files
9077
		if (!$error) {
9078
			$res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
9079
			if (!$res) {
0 ignored issues
show
introduced by
The condition $res is always false.
Loading history...
9080
				$error++;
9081
			}
9082
		}
9083
9084
		if (!$error && !empty($this->isextrafieldmanaged)) {
9085
			$result = $this->deleteExtraFields();
9086
			if ($result < 0) {
9087
				$error++;
9088
			}
9089
		}
9090
9091
		if (!$error) {
9092
			$sql = 'DELETE FROM '.MAIN_DB_PREFIX.$this->table_element.' WHERE rowid='.((int) $this->id);
9093
9094
			$resql = $this->db->query($sql);
9095
			if (!$resql) {
9096
				$error++;
9097
				$this->errors[] = $this->db->lasterror();
9098
			}
9099
		}
9100
9101
		// Commit or rollback
9102
		if ($error) {
9103
			$this->db->rollback();
9104
			return -1;
9105
		} else {
9106
			$this->db->commit();
9107
			return 1;
9108
		}
9109
	}
9110
9111
	/**
9112
	 * Delete all child object from a parent ID
9113
	 *
9114
	 * @param		int		$parentId      Parent Id
9115
	 * @param		string	$parentField   Name of Foreign key parent column
9116
	 * @return		int						<0 if KO, >0 if OK
9117
	 * @throws Exception
9118
	 */
9119
	public function deleteByParentField($parentId = 0, $parentField = '')
9120
	{
9121
		global $user;
9122
9123
		$error = 0;
9124
		$deleted = 0;
9125
9126
		if (!empty($parentId) && !empty($parentField)) {
9127
			$this->db->begin();
9128
9129
			$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX.$this->table_element;
9130
			$sql .= " WHERE ".$parentField." = ".(int) $parentId;
9131
9132
			$resql = $this->db->query($sql);
9133
			if (!$resql) {
9134
				$this->errors[] = $this->db->lasterror();
9135
				$error++;
9136
			} else {
9137
				while ($obj = $this->db->fetch_object($resql)) {
9138
					$result = $this->fetch($obj->rowid);
9139
					if ($result < 0) {
9140
						$error++;
9141
						$this->errors[] = $this->error;
9142
					} else {
9143
						if (get_class($this) == 'Contact') { // TODO special code because delete() for contact has not been standardized like other delete.
9144
							$result = $this->delete();
9145
						} else {
9146
							$result = $this->delete($user);
9147
						}
9148
						if ($result < 0) {
9149
							$error++;
9150
							$this->errors[] = $this->error;
9151
						} else {
9152
							$deleted++;
9153
						}
9154
					}
9155
				}
9156
			}
9157
9158
			if (empty($error)) {
9159
				$this->db->commit();
9160
				return $deleted;
9161
			} else {
9162
				$this->error = implode(', ', $this->errors);
9163
				$this->db->rollback();
9164
				return $error * -1;
9165
			}
9166
		}
9167
9168
		return $deleted;
9169
	}
9170
9171
	/**
9172
	 *  Delete a line of object in database
9173
	 *
9174
	 *	@param  User	$user       User that delete
9175
	 *  @param	int		$idline		Id of line to delete
9176
	 *  @param 	bool 	$notrigger  false=launch triggers after, true=disable triggers
9177
	 *  @return int         		>0 if OK, <0 if KO
9178
	 */
9179
	public function deleteLineCommon(User $user, $idline, $notrigger = false)
9180
	{
9181
		global $conf;
9182
9183
		$error = 0;
9184
9185
		$tmpforobjectclass = get_class($this);
9186
		$tmpforobjectlineclass = ucfirst($tmpforobjectclass).'Line';
9187
9188
		// Call trigger
9189
		$result = $this->call_trigger('LINE'.strtoupper($tmpforobjectclass).'_DELETE', $user);
9190
		if ($result < 0) {
9191
			return -1;
9192
		}
9193
		// End call triggers
9194
9195
		$this->db->begin();
9196
9197
		$sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element_line;
9198
		$sql .= " WHERE rowid = ".((int) $idline);
9199
9200
		dol_syslog(get_class($this)."::deleteLineCommon", LOG_DEBUG);
9201
		$resql = $this->db->query($sql);
9202
		if (!$resql) {
9203
			$this->error = "Error ".$this->db->lasterror();
9204
			$error++;
9205
		}
9206
9207
		if (empty($error)) {
9208
			// Remove extrafields
9209
			$tmpobjectline = new $tmpforobjectlineclass($this->db);
9210
			if (!isset($tmpobjectline->isextrafieldmanaged) || !empty($tmpobjectline->isextrafieldmanaged)) {
9211
				$tmpobjectline->id = $idline;
9212
				$result = $tmpobjectline->deleteExtraFields();
9213
				if ($result < 0) {
9214
					$error++;
9215
					$this->error = "Error ".get_class($this)."::deleteLineCommon deleteExtraFields error -4 ".$tmpobjectline->error;
9216
				}
9217
			}
9218
		}
9219
9220
		if (empty($error)) {
9221
			$this->db->commit();
9222
			return 1;
9223
		} else {
9224
			dol_syslog(get_class($this)."::deleteLineCommon ERROR:".$this->error, LOG_ERR);
9225
			$this->db->rollback();
9226
			return -1;
9227
		}
9228
	}
9229
9230
9231
	/**
9232
	 *	Set to a status
9233
	 *
9234
	 *	@param	User	$user			Object user that modify
9235
	 *  @param	int		$status			New status to set (often a constant like self::STATUS_XXX)
9236
	 *  @param	int		$notrigger		1=Does not execute triggers, 0=Execute triggers
9237
	 *  @param  string  $triggercode    Trigger code to use
9238
	 *	@return	int						<0 if KO, >0 if OK
9239
	 */
9240
	public function setStatusCommon($user, $status, $notrigger = 0, $triggercode = '')
9241
	{
9242
		$error = 0;
9243
9244
		$this->db->begin();
9245
9246
		$statusfield = 'status';
9247
		if ($this->element == 'don' || $this->element == 'donation') {
9248
			$statusfield = 'fk_statut';
9249
		}
9250
9251
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
9252
		$sql .= " SET ".$statusfield." = ".((int) $status);
9253
		$sql .= " WHERE rowid = ".((int) $this->id);
9254
9255
		if ($this->db->query($sql)) {
9256
			if (!$error) {
9257
				$this->oldcopy = clone $this;
9258
			}
9259
9260
			if (!$error && !$notrigger) {
9261
				// Call trigger
9262
				$result = $this->call_trigger($triggercode, $user);
9263
				if ($result < 0) {
9264
					$error++;
9265
				}
9266
			}
9267
9268
			if (!$error) {
9269
				$this->status = $status;
9270
				$this->db->commit();
9271
				return 1;
9272
			} else {
9273
				$this->db->rollback();
9274
				return -1;
9275
			}
9276
		} else {
9277
			$this->error = $this->db->error();
9278
			$this->db->rollback();
9279
			return -1;
9280
		}
9281
	}
9282
9283
9284
	/**
9285
	 * Initialise object with example values
9286
	 * Id must be 0 if object instance is a specimen
9287
	 *
9288
	 * @return int
9289
	 */
9290
	public function initAsSpecimenCommon()
9291
	{
9292
		global $user;
9293
9294
		$this->id = 0;
9295
		$this->specimen = 1;
9296
		$fields = array(
9297
			'label' => 'This is label',
9298
			'ref' => 'ABCD1234',
9299
			'description' => 'This is a description',
9300
			'qty' => 123.12,
9301
			'note_public' => 'Public note',
9302
			'note_private' => 'Private note',
9303
			'date_creation' => (dol_now() - 3600 * 48),
9304
			'date_modification' => (dol_now() - 3600 * 24),
9305
			'fk_user_creat' => $user->id,
9306
			'fk_user_modif' => $user->id,
9307
			'date' => dol_now(),
9308
		);
9309
		foreach ($fields as $key => $value) {
9310
			if (array_key_exists($key, $this->fields)) {
9311
				$this->{$key} = $value;
9312
			}
9313
		}
9314
		return 1;
9315
	}
9316
9317
9318
	/* Part for comments */
9319
9320
	/**
9321
	 * Load comments linked with current task
9322
	 *	@return boolean	1 if ok
9323
	 */
9324
	public function fetchComments()
9325
	{
9326
		require_once DOL_DOCUMENT_ROOT.'/core/class/comment.class.php';
9327
9328
		$comment = new Comment($this->db);
9329
		$result = $comment->fetchAllFor($this->element, $this->id);
9330
		if ($result < 0) {
9331
			$this->errors = array_merge($this->errors, $comment->errors);
9332
			return -1;
9333
		} else {
9334
			$this->comments = $comment->comments;
9335
		}
9336
		return count($this->comments);
9337
	}
9338
9339
	/**
9340
	 * Return nb comments already posted
9341
	 *
9342
	 * @return int
9343
	 */
9344
	public function getNbComments()
9345
	{
9346
		return count($this->comments);
9347
	}
9348
9349
	/**
9350
	 * Trim object parameters
9351
	 *
9352
	 * @param string[] $parameters array of parameters to trim
9353
	 * @return void
9354
	 */
9355
	public function trimParameters($parameters)
9356
	{
9357
		if (!is_array($parameters)) {
9358
			return;
9359
		}
9360
		foreach ($parameters as $parameter) {
9361
			if (isset($this->$parameter)) {
9362
				$this->$parameter = trim($this->$parameter);
9363
			}
9364
		}
9365
	}
9366
9367
	/* Part for categories/tags */
9368
9369
	/**
9370
	 * Sets object to given categories.
9371
	 *
9372
	 * Deletes object from existing categories not supplied.
9373
	 * Adds it to non existing supplied categories.
9374
	 * Existing categories are left untouch.
9375
	 *
9376
	 * @param 	string 		$type_categ 	Category type ('customer', 'supplier', 'website_page', ...)
9377
	 * @return	int							Array of category objects or < 0 if KO
9378
	 */
9379
	public function getCategoriesCommon($type_categ)
9380
	{
9381
		require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
9382
9383
		// Get current categories
9384
		$c = new Categorie($this->db);
9385
		$existing = $c->containing($this->id, $type_categ, 'id');
9386
9387
		return $existing;
9388
	}
9389
9390
	/**
9391
	 * Sets object to given categories.
9392
	 *
9393
	 * Adds it to non existing supplied categories.
9394
	 * Deletes object from existing categories not supplied (if remove_existing==true).
9395
	 * Existing categories are left untouch.
9396
	 *
9397
	 * @param 	int[]|int 	$categories 		Category ID or array of Categories IDs
9398
	 * @param 	string 		$type_categ 		Category type ('customer', 'supplier', 'website_page', ...) definied into const class Categorie type
9399
	 * @param 	boolean		$remove_existing 	True: Remove existings categories from Object if not supplies by $categories, False: let them
9400
	 * @return	int							<0 if KO, >0 if OK
9401
	 */
9402
	public function setCategoriesCommon($categories, $type_categ = '', $remove_existing = true)
9403
	{
9404
		dol_syslog(get_class($this)."::setCategoriesCommon Oject Id:".$this->id.' type_categ:'.$type_categ.' nb tag add:'.count($categories), LOG_DEBUG);
9405
9406
		require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
9407
9408
		if (empty($type_categ)) {
9409
			dol_syslog(__METHOD__.': Type '.$type_categ.'is an unknown category type. Done nothing.', LOG_ERR);
9410
			return -1;
9411
		}
9412
9413
		// Handle single category
9414
		if (!is_array($categories)) {
9415
			$categories = array($categories);
9416
		}
9417
9418
		// Get current categories
9419
		$c = new Categorie($this->db);
9420
		$existing = $c->containing($this->id, $type_categ, 'id');
9421
		if ($remove_existing) {
9422
			// Diff
9423
			if (is_array($existing)) {
0 ignored issues
show
introduced by
The condition is_array($existing) is always false.
Loading history...
9424
				$to_del = array_diff($existing, $categories);
9425
				$to_add = array_diff($categories, $existing);
9426
			} else {
9427
				$to_del = array(); // Nothing to delete
9428
				$to_add = $categories;
9429
			}
9430
		} else {
9431
			$to_del = array(); // Nothing to delete
9432
			$to_add = array_diff($categories, $existing);
9433
		}
9434
9435
		$error = 0;
9436
		$ok=0;
9437
9438
		// Process
9439
		foreach ($to_del as $del) {
9440
			if ($c->fetch($del) > 0) {
9441
				$result=$c->del_type($this, $type_categ);
9442
				if ($result < 0) {
9443
					$error++;
9444
					$this->error = $c->error;
9445
					$this->errors = $c->errors;
9446
					break;
9447
				} else {
9448
					$ok+=$result;
9449
				}
9450
			}
9451
		}
9452
		foreach ($to_add as $add) {
9453
			if ($c->fetch($add) > 0) {
9454
				$result = $c->add_type($this, $type_categ);
9455
				if ($result < 0) {
9456
					$error++;
9457
					$this->error = $c->error;
9458
					$this->errors = $c->errors;
9459
					break;
9460
				} else {
9461
					$ok+=$result;
9462
				}
9463
			}
9464
		}
9465
9466
		return $error ? -1 * $error : $ok;
9467
	}
9468
9469
	/**
9470
	 * Copy related categories to another object
9471
	 *
9472
	 * @param  int		$fromId	Id object source
9473
	 * @param  int		$toId	Id object cible
9474
	 * @param  string	$type	Type of category ('product', ...)
9475
	 * @return int      < 0 if error, > 0 if ok
9476
	 */
9477
	public function cloneCategories($fromId, $toId, $type = '')
9478
	{
9479
		$this->db->begin();
9480
9481
		if (empty($type)) {
9482
			$type = $this->table_element;
9483
		}
9484
9485
		require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
9486
		$categorystatic = new Categorie($this->db);
9487
9488
		$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...
9489
		$sql .= " SELECT fk_categorie, $toId FROM ".MAIN_DB_PREFIX."categorie_".(empty($categorystatic->MAP_CAT_TABLE[$type]) ? $type : $categorystatic->MAP_CAT_TABLE[$type]);
9490
		$sql .= " WHERE fk_product = ".((int) $fromId);
9491
9492
		if (!$this->db->query($sql)) {
9493
			$this->error = $this->db->lasterror();
9494
			$this->db->rollback();
9495
			return -1;
9496
		}
9497
9498
		$this->db->commit();
9499
		return 1;
9500
	}
9501
9502
	/**
9503
	 * Delete related files of object in database
9504
	 *
9505
	 * @param	integer		$mode		0=Use path to find record, 1=Use src_object_xxx fields (Mode 1 is recommanded for new objects)
9506
	 * @return 	bool					True if OK, False if KO
9507
	 */
9508
	public function deleteEcmFiles($mode = 0)
9509
	{
9510
		global $conf;
9511
9512
		$this->db->begin();
9513
9514
		// Delete in database with mode 0
9515
		if ($mode == 0) {
9516
			switch ($this->element) {
9517
				case 'propal':
9518
					$element = 'propale';
9519
					break;
9520
				case 'product':
9521
					$element = 'produit';
9522
					break;
9523
				case 'order_supplier':
9524
					$element = 'fournisseur/commande';
9525
					break;
9526
				case 'invoice_supplier':
9527
					$element = 'fournisseur/facture/'.get_exdir($this->id, 2, 0, 1, $this, 'invoice_supplier');
9528
					break;
9529
				case 'shipping':
9530
					$element = 'expedition/sending';
9531
					break;
9532
				default:
9533
					$element = $this->element;
9534
			}
9535
9536
			// Delete ecm_files extrafields
9537
			$sql = "DELETE FROM ".MAIN_DB_PREFIX."ecm_files_extrafields WHERE fk_object IN (";
9538
			$sql .= " SELECT rowid FROM ".MAIN_DB_PREFIX."ecm_files WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
9539
			$sql .= " AND filepath = '".$this->db->escape($element)."/".$this->db->escape($this->ref)."' AND entity = ".((int) $conf->entity); // No need of getEntity here
9540
			$sql .= ")";
9541
9542
			if (!$this->db->query($sql)) {
9543
				$this->error = $this->db->lasterror();
9544
				$this->db->rollback();
9545
				return false;
9546
			}
9547
9548
			// Delete ecm_files
9549
			$sql = "DELETE FROM ".MAIN_DB_PREFIX."ecm_files";
9550
			$sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%'";
9551
			$sql .= " AND filepath = '".$this->db->escape($element)."/".$this->db->escape($this->ref)."' AND entity = ".((int) $conf->entity); // No need of getEntity here
9552
9553
			if (!$this->db->query($sql)) {
9554
				$this->error = $this->db->lasterror();
9555
				$this->db->rollback();
9556
				return false;
9557
			}
9558
		}
9559
9560
		// Delete in database with mode 1
9561
		if ($mode == 1) {
9562
			$sql = 'DELETE FROM '.MAIN_DB_PREFIX."ecm_files_extrafields";
9563
			$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 = ".((int) $this->id).")";
9564
			$resql = $this->db->query($sql);
9565
			if (!$resql) {
9566
				$this->error = $this->db->lasterror();
9567
				$this->db->rollback();
9568
				return false;
9569
			}
9570
9571
			$sql = 'DELETE FROM '.MAIN_DB_PREFIX."ecm_files";
9572
			$sql .= " WHERE src_object_type = '".$this->db->escape($this->table_element.(empty($this->module) ? "" : "@".$this->module))."' AND src_object_id = ".((int) $this->id);
9573
			$resql = $this->db->query($sql);
9574
			if (!$resql) {
9575
				$this->error = $this->db->lasterror();
9576
				$this->db->rollback();
9577
				return false;
9578
			}
9579
		}
9580
9581
		$this->db->commit();
9582
		return true;
9583
	}
9584
}
9585