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

CommonObject::setValueFrom()   F

Complexity

Conditions 28
Paths > 20000

Size

Total Lines 85
Code Lines 55

Duplication

Lines 0
Ratio 0 %

Importance

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

How to fix   Long Method    Complexity    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
/* Copyright (C) 2006-2015 Laurent Destailleur  <[email protected]>
3
 * Copyright (C) 2005-2013 Regis Houssin        <[email protected]>
4
 * Copyright (C) 2010-2020 Juanjo Menent        <[email protected]>
5
 * Copyright (C) 2012-2013 Christophe Battarel  <[email protected]>
6
 * Copyright (C) 2011-2019 Philippe Grand       <[email protected]>
7
 * Copyright (C) 2012-2015 Marcos García        <[email protected]>
8
 * Copyright (C) 2012-2015 Raphaël Doursenaud   <[email protected]>
9
 * Copyright (C) 2012      Cedric Salvador      <[email protected]>
10
 * Copyright (C) 2015-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