Completed
Branch develop (200de2)
by
unknown
32:53
created

CommonObject::fetch_product()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
1814
		if ($this->statut >= 0 || $this->element == 'societe')
1815
		{
1816
			$fieldname = 'multicurrency_code';
1817
1818
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1819
			$sql .= ' SET '.$fieldname." = '".$this->db->escape($code)."'";
1820
			$sql .= ' WHERE rowid='.$this->id;
1821
1822
			if ($this->db->query($sql))
1823
			{
1824
				$this->multicurrency_code = $code;
1825
1826
				list($fk_multicurrency, $rate) = MultiCurrency::getIdAndTxFromCode($this->db, $code);
1827
				if ($rate) $this->setMulticurrencyRate($rate,2);
1828
1829
				return 1;
1830
			}
1831
			else
1832
			{
1833
				dol_syslog(get_class($this).'::setMulticurrencyCode Erreur '.$sql.' - '.$this->db->error());
1834
				$this->error=$this->db->error();
1835
				return -1;
1836
			}
1837
		}
1838
		else
1839
		{
1840
			dol_syslog(get_class($this).'::setMulticurrencyCode, status of the object is incompatible');
1841
			$this->error='Status of the object is incompatible '.$this->statut;
1842
			return -2;
1843
		}
1844
	}
1845
1846
	/**
1847
	 *  Change the multicurrency rate
1848
	 *
1849
	 *  @param		double	$rate	multicurrency rate
1850
	 *  @param		int		$mode	mode 1 : amounts in company currency will be recalculated, mode 2 : amounts in foreign currency
1851
	 *  @return		int				>0 if OK, <0 if KO
1852
	 */
1853
	function setMulticurrencyRate($rate, $mode=1)
1854
	{
1855
		dol_syslog(get_class($this).'::setMulticurrencyRate('.$id.')');
0 ignored issues
show
Bug introduced by
The variable $id does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
1856
		if ($this->statut >= 0 || $this->element == 'societe')
1857
		{
1858
			$fieldname = 'multicurrency_tx';
1859
1860
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1861
			$sql .= ' SET '.$fieldname.' = '.$rate;
1862
			$sql .= ' WHERE rowid='.$this->id;
1863
1864
			if ($this->db->query($sql))
1865
			{
1866
				$this->multicurrency_tx = $rate;
1867
1868
				// Update line price
1869
				if (!empty($this->lines))
1870
				{
1871
					foreach ($this->lines as &$line)
1872
					{
1873
						if($mode == 1) {
1874
							$line->subprice = 0;
1875
						}
1876
1877
						switch ($this->element) {
1878
							case 'propal':
1879
								$this->updateline(
1880
									$line->id, $line->subprice, $line->qty, $line->remise_percent, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx,
1881
									($line->description?$line->description:$line->desc), 'HT', $line->info_bits, $line->special_code, $line->fk_parent_line,
1882
									$line->skip_update_total, $line->fk_fournprice, $line->pa_ht, $line->label, $line->product_type, $line->date_start,
1883
									$line->date_end, $line->array_options, $line->fk_unit, $line->multicurrency_subprice
1884
								);
1885
								break;
1886
							case 'commande':
1887
								$this->updateline(
1888
									$line->id, ($line->description?$line->description:$line->desc), $line->subprice, $line->qty, $line->remise_percent,
1889
									$line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, 'HT', $line->info_bits, $line->date_start, $line->date_end,
1890
									$line->product_type, $line->fk_parent_line, $line->skip_update_total, $line->fk_fournprice, $line->pa_ht, $line->label,
1891
									$line->special_code, $line->array_options, $line->fk_unit, $line->multicurrency_subprice
1892
								);
1893
								break;
1894
							case 'facture':
1895
								$this->updateline(
1896
									$line->id, ($line->description?$line->description:$line->desc), $line->subprice, $line->qty, $line->remise_percent,
1897
									$line->date_start, $line->date_end, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, 'HT', $line->info_bits,
1898
									$line->product_type, $line->fk_parent_line, $line->skip_update_total, $line->fk_fournprice, $line->pa_ht, $line->label,
1899
									$line->special_code, $line->array_options, $line->situation_percent, $line->fk_unit, $line->multicurrency_subprice
1900
								);
1901
								break;
1902
							case 'supplier_proposal':
1903
								$this->updateline(
1904
									$line->id, $line->subprice, $line->qty, $line->remise_percent, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx,
1905
									($line->description?$line->description:$line->desc), 'HT', $line->info_bits, $line->special_code, $line->fk_parent_line,
1906
									$line->skip_update_total, $line->fk_fournprice, $line->pa_ht, $line->label, $line->product_type, $line->array_options,
1907
									$line->ref_fourn, $line->multicurrency_subprice
1908
								);
1909
								break;
1910
							case 'order_supplier':
1911
								$this->updateline(
1912
									$line->id, ($line->description?$line->description:$line->desc), $line->subprice, $line->qty, $line->remise_percent,
1913
									$line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, 'HT', $line->info_bits, $line->product_type, false,
1914
									$line->date_start, $line->date_end, $line->array_options, $line->fk_unit, $line->multicurrency_subprice
1915
								);
1916
								break;
1917
							case 'invoice_supplier':
1918
								$this->updateline(
1919
									$line->id, ($line->description?$line->description:$line->desc), $line->subprice, $line->tva_tx, $line->localtax1_tx,
1920
									$line->localtax2_tx, $line->qty, 0, 'HT', $line->info_bits, $line->product_type, $line->remise_percent, false,
1921
									$line->date_start, $line->date_end, $line->array_options, $line->fk_unit, $line->multicurrency_subprice
1922
								);
1923
								break;
1924
							default:
1925
								dol_syslog(get_class($this).'::setMulticurrencyRate no updateline defined', LOG_DEBUG);
1926
								break;
1927
						}
1928
					}
1929
				}
1930
1931
				return 1;
1932
			}
1933
			else
1934
			{
1935
				dol_syslog(get_class($this).'::setMulticurrencyRate Erreur '.$sql.' - '.$this->db->error());
1936
				$this->error=$this->db->error();
1937
				return -1;
1938
			}
1939
		}
1940
		else
1941
		{
1942
			dol_syslog(get_class($this).'::setMulticurrencyRate, status of the object is incompatible');
1943
			$this->error='Status of the object is incompatible '.$this->statut;
1944
			return -2;
1945
		}
1946
	}
1947
1948
	/**
1949
	 *  Change the payments terms
1950
	 *
1951
	 *  @param		int		$id		Id of new payment terms
1952
	 *  @return		int				>0 if OK, <0 if KO
1953
	 */
1954
	function setPaymentTerms($id)
1955
	{
1956
		dol_syslog(get_class($this).'::setPaymentTerms('.$id.')');
1957
		if ($this->statut >= 0 || $this->element == 'societe')
1958
		{
1959
			// TODO uniformize field name
1960
			$fieldname = 'fk_cond_reglement';
1961
			if ($this->element == 'societe') $fieldname = 'cond_reglement';
1962
			if (get_class($this) == 'Fournisseur') $fieldname = 'cond_reglement_supplier';
1963
1964
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1965
			$sql .= ' SET '.$fieldname.' = '.$id;
1966
			$sql .= ' WHERE rowid='.$this->id;
1967
1968
			if ($this->db->query($sql))
1969
			{
1970
				$this->cond_reglement_id = $id;
1971
				// for supplier
1972
				if (get_class($this) == 'Fournisseur') $this->cond_reglement_supplier_id = $id;
1973
				$this->cond_reglement = $id;	// for compatibility
1974
				return 1;
1975
			}
1976
			else
1977
			{
1978
				dol_syslog(get_class($this).'::setPaymentTerms Erreur '.$sql.' - '.$this->db->error());
1979
				$this->error=$this->db->error();
1980
				return -1;
1981
			}
1982
		}
1983
		else
1984
		{
1985
			dol_syslog(get_class($this).'::setPaymentTerms, status of the object is incompatible');
1986
			$this->error='Status of the object is incompatible '.$this->statut;
1987
			return -2;
1988
		}
1989
	}
1990
1991
	/**
1992
	 *	Define delivery address
1993
	 *  @deprecated
1994
	 *
1995
	 *	@param      int		$id		Address id
1996
	 *	@return     int				<0 si ko, >0 si ok
1997
	 */
1998
	function setDeliveryAddress($id)
1999
	{
2000
		$fieldname = 'fk_delivery_address';
2001
		if ($this->element == 'delivery' || $this->element == 'shipping') $fieldname = 'fk_address';
2002
2003
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET ".$fieldname." = ".$id;
2004
		$sql.= " WHERE rowid = ".$this->id." AND fk_statut = 0";
2005
2006
		if ($this->db->query($sql))
2007
		{
2008
			$this->fk_delivery_address = $id;
2009
			return 1;
2010
		}
2011
		else
2012
		{
2013
			$this->error=$this->db->error();
2014
			dol_syslog(get_class($this).'::setDeliveryAddress Erreur '.$sql.' - '.$this->error);
2015
			return -1;
2016
		}
2017
	}
2018
2019
2020
	/**
2021
	 *  Change the shipping method
2022
	 *
2023
	 *  @param      int     $shipping_method_id     Id of shipping method
2024
     *  @param      bool    $notrigger              false=launch triggers after, true=disable triggers
2025
     *  @param      User	$userused               Object user
2026
	 *
2027
	 *  @return     int              1 if OK, 0 if KO
2028
	 */
2029
	function setShippingMethod($shipping_method_id, $notrigger=false, $userused=null)
2030
	{
2031
        global $user;
2032
2033
        if (empty($userused)) $userused=$user;
2034
2035
        $error = 0;
2036
2037
		if (! $this->table_element) {
2038
			dol_syslog(get_class($this)."::setShippingMethod was called on objet with property table_element not defined",LOG_ERR);
2039
			return -1;
2040
		}
2041
2042
        $this->db->begin();
2043
2044
		if ($shipping_method_id<0) $shipping_method_id='NULL';
2045
		dol_syslog(get_class($this).'::setShippingMethod('.$shipping_method_id.')');
2046
2047
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2048
		$sql.= " SET fk_shipping_method = ".$shipping_method_id;
2049
		$sql.= " WHERE rowid=".$this->id;
2050
        $resql = $this->db->query($sql);
2051
		if (! $resql) {
2052
			dol_syslog(get_class($this).'::setShippingMethod Error ', LOG_DEBUG);
2053
			$this->error = $this->db->lasterror();
2054
			$error++;
2055
        } else {
2056
            if (!$notrigger)
2057
            {
2058
                // Call trigger
2059
                $this->context=array('shippingmethodupdate'=>1);
2060
                $result = $this->call_trigger(strtoupper(get_class($this)) . '_MODIFY', $userused);
2061
                if ($result < 0) $error++;
2062
                // End call trigger
2063
            }
2064
        }
2065
        if ($error)
2066
        {
2067
            $this->db->rollback();
2068
            return -1;
2069
        } else {
2070
            $this->shipping_method_id = ($shipping_method_id=='NULL')?null:$shipping_method_id;
0 ignored issues
show
Documentation Bug introduced by
It seems like $shipping_method_id == '...l : $shipping_method_id can also be of type string. However, the property $shipping_method_id is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
2071
            $this->db->commit();
2072
            return 1;
2073
        }
2074
	}
2075
2076
2077
	/**
2078
	 *  Change the warehouse
2079
	 *
2080
	 *  @param      int     $warehouse_id     Id of warehouse
2081
	 *  @return     int              1 if OK, 0 if KO
2082
	 */
2083
	function setWarehouse($warehouse_id)
2084
	{
2085
		if (! $this->table_element) {
2086
			dol_syslog(get_class($this)."::setWarehouse was called on objet with property table_element not defined",LOG_ERR);
2087
			return -1;
2088
		}
2089
		if ($warehouse_id<0) $warehouse_id='NULL';
2090
		dol_syslog(get_class($this).'::setWarehouse('.$warehouse_id.')');
2091
2092
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2093
		$sql.= " SET fk_warehouse = ".$warehouse_id;
2094
		$sql.= " WHERE rowid=".$this->id;
2095
2096
		if ($this->db->query($sql)) {
2097
			$this->warehouse_id = ($warehouse_id=='NULL')?null:$warehouse_id;
2098
			return 1;
2099
		} else {
2100
			dol_syslog(get_class($this).'::setWarehouse Error ', LOG_DEBUG);
2101
			$this->error=$this->db->error();
2102
			return 0;
2103
		}
2104
	}
2105
2106
2107
	/**
2108
	 *		Set last model used by doc generator
2109
	 *
2110
	 *		@param		User	$user		User object that make change
2111
	 *		@param		string	$modelpdf	Modele name
2112
	 *		@return		int					<0 if KO, >0 if OK
2113
	 */
2114
	function setDocModel($user, $modelpdf)
2115
	{
2116
		if (! $this->table_element)
2117
		{
2118
			dol_syslog(get_class($this)."::setDocModel was called on objet with property table_element not defined",LOG_ERR);
2119
			return -1;
2120
		}
2121
2122
		$newmodelpdf=dol_trunc($modelpdf,255);
2123
2124
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2125
		$sql.= " SET model_pdf = '".$this->db->escape($newmodelpdf)."'";
2126
		$sql.= " WHERE rowid = ".$this->id;
2127
		// if ($this->element == 'facture') $sql.= " AND fk_statut < 2";
2128
		// if ($this->element == 'propal')  $sql.= " AND fk_statut = 0";
2129
2130
		dol_syslog(get_class($this)."::setDocModel", LOG_DEBUG);
2131
		$resql=$this->db->query($sql);
2132
		if ($resql)
2133
		{
2134
			$this->modelpdf=$modelpdf;
2135
			return 1;
2136
		}
2137
		else
2138
		{
2139
			dol_print_error($this->db);
2140
			return 0;
2141
		}
2142
	}
2143
2144
2145
	/**
2146
	 *  Change the bank account
2147
	 *
2148
	 *  @param		int		$fk_account		Id of bank account
2149
	 *  @param      bool    $notrigger      false=launch triggers after, true=disable triggers
2150
	 *  @param      User	$userused		Object user
2151
	 *  @return		int				1 if OK, 0 if KO
2152
	 */
2153
	function setBankAccount($fk_account, $notrigger=false, $userused=null)
2154
	{
2155
        global $user;
2156
2157
        if (empty($userused)) $userused=$user;
2158
2159
        $error = 0;
2160
2161
		if (! $this->table_element) {
2162
			dol_syslog(get_class($this)."::setBankAccount was called on objet with property table_element not defined",LOG_ERR);
2163
			return -1;
2164
		}
2165
        $this->db->begin();
2166
2167
		if ($fk_account<0) $fk_account='NULL';
2168
		dol_syslog(get_class($this).'::setBankAccount('.$fk_account.')');
2169
2170
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2171
		$sql.= " SET fk_account = ".$fk_account;
2172
		$sql.= " WHERE rowid=".$this->id;
2173
2174
        $resql = $this->db->query($sql);
2175
        if (! $resql)
2176
        {
2177
            dol_syslog(get_class($this).'::setBankAccount Error '.$sql.' - '.$this->db->error());
2178
            $this->error = $this->db->lasterror();
2179
            $error++;
2180
        }
2181
        else
2182
        {
2183
            if (!$notrigger)
2184
            {
2185
                // Call trigger
2186
                $this->context=array('bankaccountupdate'=>1);
2187
                $result = $this->call_trigger(strtoupper(get_class($this)) . '_MODIFY', $userused);
2188
                if ($result < 0) $error++;
2189
                // End call trigger
2190
            }
2191
        }
2192
        if ($error)
2193
        {
2194
            $this->db->rollback();
2195
            return -1;
2196
        }
2197
        else
2198
        {
2199
            $this->fk_account = ($fk_account=='NULL')?null:$fk_account;
0 ignored issues
show
Documentation Bug introduced by
It seems like $fk_account == 'NULL' ? null : $fk_account can also be of type string. However, the property $fk_account is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
2200
            $this->db->commit();
2201
            return 1;
2202
        }
2203
    }
2204
2205
2206
	// TODO: Move line related operations to CommonObjectLine?
2207
2208
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2209
	/**
2210
	 *  Save a new position (field rang) for details lines.
2211
	 *  You can choose to set position for lines with already a position or lines without any position defined.
2212
	 *
2213
	 * 	@param		boolean		$renum			   True to renum all already ordered lines, false to renum only not already ordered lines.
2214
	 * 	@param		string		$rowidorder		   ASC or DESC
2215
	 * 	@param		boolean		$fk_parent_line    Table with fk_parent_line field or not
2216
	 * 	@return		int                            <0 if KO, >0 if OK
2217
	 */
2218
	function line_order($renum=false, $rowidorder='ASC', $fk_parent_line=true)
2219
	{
2220
        // phpcs:enable
2221
		if (! $this->table_element_line)
2222
		{
2223
			dol_syslog(get_class($this)."::line_order was called on objet with property table_element_line not defined",LOG_ERR);
2224
			return -1;
2225
		}
2226
		if (! $this->fk_element)
2227
		{
2228
			dol_syslog(get_class($this)."::line_order was called on objet with property fk_element not defined",LOG_ERR);
2229
			return -1;
2230
		}
2231
2232
		// Count number of lines to reorder (according to choice $renum)
2233
		$nl=0;
2234
		$sql = 'SELECT count(rowid) FROM '.MAIN_DB_PREFIX.$this->table_element_line;
2235
		$sql.= ' WHERE '.$this->fk_element.'='.$this->id;
2236
		if (! $renum) $sql.= ' AND rang = 0';
2237
		if ($renum) $sql.= ' AND rang <> 0';
2238
2239
		dol_syslog(get_class($this)."::line_order", LOG_DEBUG);
2240
		$resql = $this->db->query($sql);
2241
		if ($resql)
2242
		{
2243
			$row = $this->db->fetch_row($resql);
2244
			$nl = $row[0];
2245
		}
2246
		else dol_print_error($this->db);
2247
		if ($nl > 0)
2248
		{
2249
			// The goal of this part is to reorder all lines, with all children lines sharing the same
2250
			// counter that parents.
2251
			$rows=array();
2252
2253
			// We first search all lines that are parent lines (for multilevel details lines)
2254
			$sql = 'SELECT rowid FROM '.MAIN_DB_PREFIX.$this->table_element_line;
2255
			$sql.= ' WHERE '.$this->fk_element.' = '.$this->id;
2256
			if ($fk_parent_line) $sql.= ' AND fk_parent_line IS NULL';
2257
			$sql.= ' ORDER BY rang ASC, rowid '.$rowidorder;
2258
2259
			dol_syslog(get_class($this)."::line_order search all parent lines", LOG_DEBUG);
2260
			$resql = $this->db->query($sql);
2261
			if ($resql)
2262
			{
2263
				$i=0;
2264
				$num = $this->db->num_rows($resql);
2265
				while ($i < $num)
2266
				{
2267
					$row = $this->db->fetch_row($resql);
2268
					$rows[] = $row[0];	// Add parent line into array rows
2269
					$childrens = $this->getChildrenOfLine($row[0]);
2270
					if (! empty($childrens))
2271
					{
2272
						foreach($childrens as $child)
2273
						{
2274
							array_push($rows, $child);
2275
						}
2276
					}
2277
					$i++;
2278
				}
2279
2280
				// Now we set a new number for each lines (parent and children with children included into parent tree)
2281
				if (! empty($rows))
2282
				{
2283
					foreach($rows as $key => $row)
2284
					{
2285
						$this->updateRangOfLine($row, ($key+1));
2286
					}
2287
				}
2288
			}
2289
			else
2290
			{
2291
				dol_print_error($this->db);
2292
			}
2293
		}
2294
		return 1;
2295
	}
2296
2297
	/**
2298
	 * 	Get children of line
2299
	 *
2300
	 * 	@param	int		$id		Id of parent line
2301
	 * 	@return	array			Array with list of children lines id
2302
	 */
2303
	function getChildrenOfLine($id)
2304
	{
2305
		$rows=array();
2306
2307
		$sql = 'SELECT rowid FROM '.MAIN_DB_PREFIX.$this->table_element_line;
2308
		$sql.= ' WHERE '.$this->fk_element.' = '.$this->id;
2309
		$sql.= ' AND fk_parent_line = '.$id;
2310
		$sql.= ' ORDER BY rang ASC';
2311
2312
		dol_syslog(get_class($this)."::getChildrenOfLine search children lines for line ".$id."", LOG_DEBUG);
2313
		$resql = $this->db->query($sql);
2314
		if ($resql)
2315
		{
2316
			$i=0;
2317
			$num = $this->db->num_rows($resql);
2318
			while ($i < $num)
2319
			{
2320
				$row = $this->db->fetch_row($resql);
2321
				$rows[$i] = $row[0];
2322
				$i++;
2323
			}
2324
		}
2325
2326
		return $rows;
2327
	}
2328
2329
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2330
	/**
2331
	 * 	Update a line to have a lower rank
2332
	 *
2333
	 * 	@param 	int			$rowid				Id of line
2334
	 * 	@param	boolean		$fk_parent_line		Table with fk_parent_line field or not
2335
	 * 	@return	void
2336
	 */
2337
	function line_up($rowid, $fk_parent_line=true)
2338
	{
2339
        // phpcs:enable
2340
		$this->line_order(false, 'ASC', $fk_parent_line);
2341
2342
		// Get rang of line
2343
		$rang = $this->getRangOfLine($rowid);
2344
2345
		// Update position of line
2346
		$this->updateLineUp($rowid, $rang);
2347
	}
2348
2349
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2350
	/**
2351
	 * 	Update a line to have a higher rank
2352
	 *
2353
	 * 	@param	int			$rowid				Id of line
2354
	 * 	@param	boolean		$fk_parent_line		Table with fk_parent_line field or not
2355
	 * 	@return	void
2356
	 */
2357
	function line_down($rowid, $fk_parent_line=true)
2358
	{
2359
        // phpcs:enable
2360
		$this->line_order(false, 'ASC', $fk_parent_line);
2361
2362
		// Get rang of line
2363
		$rang = $this->getRangOfLine($rowid);
2364
2365
		// Get max value for rang
2366
		$max = $this->line_max();
2367
2368
		// Update position of line
2369
		$this->updateLineDown($rowid, $rang, $max);
2370
	}
2371
2372
	/**
2373
	 * 	Update position of line (rang)
2374
	 *
2375
	 * 	@param	int		$rowid		Id of line
2376
	 * 	@param	int		$rang		Position
2377
	 * 	@return	void
2378
	 */
2379
	function updateRangOfLine($rowid,$rang)
2380
	{
2381
		$fieldposition = 'rang';
2382
		if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction'))) $fieldposition = 'position';
2383
2384
		$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element_line.' SET '.$fieldposition.' = '.$rang;
2385
		$sql.= ' WHERE rowid = '.$rowid;
2386
2387
		dol_syslog(get_class($this)."::updateRangOfLine", LOG_DEBUG);
2388
		if (! $this->db->query($sql))
2389
		{
2390
			dol_print_error($this->db);
2391
		}
2392
	}
2393
2394
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2395
	/**
2396
	 * 	Update position of line with ajax (rang)
2397
	 *
2398
	 * 	@param	array	$rows	Array of rows
2399
	 * 	@return	void
2400
	 */
2401
	function line_ajaxorder($rows)
2402
	{
2403
        // phpcs:enable
2404
		$num = count($rows);
2405
		for ($i = 0 ; $i < $num ; $i++)
2406
		{
2407
			$this->updateRangOfLine($rows[$i], ($i+1));
2408
		}
2409
	}
2410
2411
	/**
2412
	 * 	Update position of line up (rang)
2413
	 *
2414
	 * 	@param	int		$rowid		Id of line
2415
	 * 	@param	int		$rang		Position
2416
	 * 	@return	void
2417
	 */
2418
	function updateLineUp($rowid,$rang)
2419
	{
2420
		if ($rang > 1)
2421
		{
2422
			$fieldposition = 'rang';
2423
			if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction'))) $fieldposition = 'position';
2424
2425
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element_line.' SET '.$fieldposition.' = '.$rang ;
2426
			$sql.= ' WHERE '.$this->fk_element.' = '.$this->id;
2427
			$sql.= ' AND rang = '.($rang - 1);
2428
			if ($this->db->query($sql) )
2429
			{
2430
				$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element_line.' SET '.$fieldposition.' = '.($rang - 1);
2431
				$sql.= ' WHERE rowid = '.$rowid;
2432
				if (! $this->db->query($sql) )
2433
				{
2434
					dol_print_error($this->db);
2435
				}
2436
			}
2437
			else
2438
			{
2439
				dol_print_error($this->db);
2440
			}
2441
		}
2442
	}
2443
2444
	/**
2445
	 * 	Update position of line down (rang)
2446
	 *
2447
	 * 	@param	int		$rowid		Id of line
2448
	 * 	@param	int		$rang		Position
2449
	 * 	@param	int		$max		Max
2450
	 * 	@return	void
2451
	 */
2452
	function updateLineDown($rowid,$rang,$max)
2453
	{
2454
		if ($rang < $max)
2455
		{
2456
			$fieldposition = 'rang';
2457
			if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction'))) $fieldposition = 'position';
2458
2459
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element_line.' SET '.$fieldposition.' = '.$rang;
2460
			$sql.= ' WHERE '.$this->fk_element.' = '.$this->id;
2461
			$sql.= ' AND rang = '.($rang+1);
2462
			if ($this->db->query($sql) )
2463
			{
2464
				$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element_line.' SET '.$fieldposition.' = '.($rang+1);
2465
				$sql.= ' WHERE rowid = '.$rowid;
2466
				if (! $this->db->query($sql) )
2467
				{
2468
					dol_print_error($this->db);
2469
				}
2470
			}
2471
			else
2472
			{
2473
				dol_print_error($this->db);
2474
			}
2475
		}
2476
	}
2477
2478
	/**
2479
	 * 	Get position of line (rang)
2480
	 *
2481
	 * 	@param		int		$rowid		Id of line
2482
	 *  @return		int     			Value of rang in table of lines
2483
	 */
2484
	function getRangOfLine($rowid)
2485
	{
2486
		$sql = 'SELECT rang FROM '.MAIN_DB_PREFIX.$this->table_element_line;
2487
		$sql.= ' WHERE rowid ='.$rowid;
2488
2489
		dol_syslog(get_class($this)."::getRangOfLine", LOG_DEBUG);
2490
		$resql = $this->db->query($sql);
2491
		if ($resql)
2492
		{
2493
			$row = $this->db->fetch_row($resql);
2494
			return $row[0];
2495
		}
2496
	}
2497
2498
	/**
2499
	 * 	Get rowid of the line relative to its position
2500
	 *
2501
	 * 	@param		int		$rang		Rang value
2502
	 *  @return     int     			Rowid of the line
2503
	 */
2504
	function getIdOfLine($rang)
2505
	{
2506
		$sql = 'SELECT rowid FROM '.MAIN_DB_PREFIX.$this->table_element_line;
2507
		$sql.= ' WHERE '.$this->fk_element.' = '.$this->id;
2508
		$sql.= ' AND rang = '.$rang;
2509
		$resql = $this->db->query($sql);
2510
		if ($resql)
2511
		{
2512
			$row = $this->db->fetch_row($resql);
2513
			return $row[0];
2514
		}
2515
	}
2516
2517
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2518
	/**
2519
	 * 	Get max value used for position of line (rang)
2520
	 *
2521
	 * 	@param		int		$fk_parent_line		Parent line id
2522
	 *  @return     int  			   			Max value of rang in table of lines
2523
	 */
2524
	function line_max($fk_parent_line=0)
2525
	{
2526
        // phpcs:enable
2527
		// Search the last rang with fk_parent_line
2528
		if ($fk_parent_line)
2529
		{
2530
			$sql = 'SELECT max(rang) FROM '.MAIN_DB_PREFIX.$this->table_element_line;
2531
			$sql.= ' WHERE '.$this->fk_element.' = '.$this->id;
2532
			$sql.= ' AND fk_parent_line = '.$fk_parent_line;
2533
2534
			dol_syslog(get_class($this)."::line_max", LOG_DEBUG);
2535
			$resql = $this->db->query($sql);
2536
			if ($resql)
2537
			{
2538
				$row = $this->db->fetch_row($resql);
2539
				if (! empty($row[0]))
2540
				{
2541
					return $row[0];
2542
				}
2543
				else
2544
				{
2545
					return $this->getRangOfLine($fk_parent_line);
2546
				}
2547
			}
2548
		}
2549
		// If not, search the last rang of element
2550
		else
2551
		{
2552
			$sql = 'SELECT max(rang) FROM '.MAIN_DB_PREFIX.$this->table_element_line;
2553
			$sql.= ' WHERE '.$this->fk_element.' = '.$this->id;
2554
2555
			dol_syslog(get_class($this)."::line_max", LOG_DEBUG);
2556
			$resql = $this->db->query($sql);
2557
			if ($resql)
2558
			{
2559
				$row = $this->db->fetch_row($resql);
2560
				return $row[0];
2561
			}
2562
		}
2563
	}
2564
2565
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2566
	/**
2567
	 *  Update external ref of element
2568
	 *
2569
	 *  @param      string		$ref_ext	Update field ref_ext
2570
	 *  @return     int      		   		<0 if KO, >0 if OK
2571
	 */
2572
	function update_ref_ext($ref_ext)
2573
	{
2574
        // phpcs:enable
2575
		if (! $this->table_element)
2576
		{
2577
			dol_syslog(get_class($this)."::update_ref_ext was called on objet with property table_element not defined", LOG_ERR);
2578
			return -1;
2579
		}
2580
2581
		$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2582
		$sql.= " SET ref_ext = '".$this->db->escape($ref_ext)."'";
2583
		$sql.= " WHERE ".(isset($this->table_rowid)?$this->table_rowid:'rowid')." = ". $this->id;
2584
2585
		dol_syslog(get_class($this)."::update_ref_ext", LOG_DEBUG);
2586
		if ($this->db->query($sql))
2587
		{
2588
			$this->ref_ext = $ref_ext;
2589
			return 1;
2590
		}
2591
		else
2592
		{
2593
			$this->error=$this->db->error();
2594
			return -1;
2595
		}
2596
	}
2597
2598
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2599
	/**
2600
	 *  Update note of element
2601
	 *
2602
	 *  @param      string		$note		New value for note
2603
	 *  @param		string		$suffix		'', '_public' or '_private'
2604
	 *  @return     int      		   		<0 if KO, >0 if OK
2605
	 */
2606
	function update_note($note, $suffix='')
2607
	{
2608
        // phpcs:enable
2609
		global $user;
2610
2611
		if (! $this->table_element)
2612
		{
2613
			$this->error='update_note was called on objet with property table_element not defined';
2614
			dol_syslog(get_class($this)."::update_note was called on objet with property table_element not defined", LOG_ERR);
2615
			return -1;
2616
		}
2617
		if (! in_array($suffix,array('','_public','_private')))
2618
		{
2619
			$this->error='update_note Parameter suffix must be empty, \'_private\' or \'_public\'';
2620
			dol_syslog(get_class($this)."::update_note Parameter suffix must be empty, '_private' or '_public'", LOG_ERR);
2621
			return -2;
2622
		}
2623
		// Special cas
2624
		//var_dump($this->table_element);exit;
2625
		if ($this->table_element == 'product') $suffix='';
2626
2627
		$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2628
		$sql.= " SET note".$suffix." = ".(!empty($note)?("'".$this->db->escape($note)."'"):"NULL");
2629
		$sql.= " ,".(in_array($this->table_element, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))?"fk_user_mod":"fk_user_modif")." = ".$user->id;
2630
		$sql.= " WHERE rowid =". $this->id;
2631
2632
		dol_syslog(get_class($this)."::update_note", LOG_DEBUG);
2633
		if ($this->db->query($sql))
2634
		{
2635
			if ($suffix == '_public') $this->note_public = $note;
2636
			else if ($suffix == '_private') $this->note_private = $note;
2637
			else
2638
			{
2639
				$this->note = $note;      // deprecated
2640
				$this->note_private = $note;
2641
			}
2642
			return 1;
2643
		}
2644
		else
2645
		{
2646
			$this->error=$this->db->lasterror();
2647
			return -1;
2648
		}
2649
	}
2650
2651
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2652
	/**
2653
	 * 	Update public note (kept for backward compatibility)
2654
	 *
2655
	 * @param      string		$note		New value for note
2656
	 * @return     int      		   		<0 if KO, >0 if OK
2657
	 * @deprecated
2658
	 * @see update_note()
2659
	 */
2660
	function update_note_public($note)
2661
	{
2662
        // phpcs:enable
2663
		return $this->update_note($note,'_public');
2664
	}
2665
2666
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2667
	/**
2668
	 *	Update total_ht, total_ttc, total_vat, total_localtax1, total_localtax2 for an object (sum of lines).
2669
	 *  Must be called at end of methods addline or updateline.
2670
	 *
2671
	 *	@param	int		$exclspec          	>0 = Exclude special product (product_type=9)
2672
	 *  @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
2673
	 *  @param	int		$nodatabaseupdate	1=Do not update database. Update only properties of object.
2674
	 *  @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.
2675
	 *	@return	int    			           	<0 if KO, >0 if OK
2676
	 */
2677
	function update_price($exclspec=0,$roundingadjust='none',$nodatabaseupdate=0,$seller=null)
2678
	{
2679
        // phpcs:enable
2680
		global $conf, $hookmanager, $action;
2681
2682
		// Some external module want no update price after a trigger because they have another method to calculate the total (ex: with an extrafield)
2683
		$MODULE = "";
2684
		if ($this->element == 'propal')
2685
			$MODULE = "MODULE_DISALLOW_UPDATE_PRICE_PROPOSAL";
2686
		elseif ($this->element == 'order')
2687
			$MODULE = "MODULE_DISALLOW_UPDATE_PRICE_ORDER";
2688
		elseif ($this->element == 'facture')
2689
			$MODULE = "MODULE_DISALLOW_UPDATE_PRICE_INVOICE";
2690
		elseif ($this->element == 'facture_fourn')
2691
			$MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_INVOICE";
2692
		elseif ($this->element == 'order_supplier')
2693
			$MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_ORDER";
2694
		elseif ($this->element == 'supplier_proposal')
2695
			$MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_PROPOSAL";
2696
2697
		if (! empty($MODULE)) {
2698
			if (! empty($conf->global->$MODULE)) {
2699
				$modsactivated = explode(',', $conf->global->$MODULE);
2700
				foreach ($modsactivated as $mod) {
2701
					if ($conf->$mod->enabled)
2702
						return 1; // update was disabled by specific setup
2703
				}
2704
			}
2705
		}
2706
2707
		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2708
2709
		if ($roundingadjust == '-1') $roundingadjust='auto';	// For backward compatibility
2710
2711
		$forcedroundingmode=$roundingadjust;
2712
		if ($forcedroundingmode == 'auto' && isset($conf->global->MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND)) $forcedroundingmode=$conf->global->MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND;
2713
		elseif ($forcedroundingmode == 'auto') $forcedroundingmode='0';
2714
2715
		$error=0;
2716
2717
		$multicurrency_tx = !empty($this->multicurrency_tx) ? $this->multicurrency_tx : 1;
2718
2719
		// Define constants to find lines to sum
2720
		$fieldtva='total_tva';
2721
		$fieldlocaltax1='total_localtax1';
2722
		$fieldlocaltax2='total_localtax2';
2723
		$fieldup='subprice';
2724
		if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier')
2725
		{
2726
			$fieldtva='tva';
2727
			$fieldup='pu_ht';
2728
		}
2729
		if ($this->element == 'expensereport')
2730
		{
2731
			$fieldup='value_unit';
2732
		}
2733
2734
		$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,';
2735
		$sql.= ' tva_tx as vatrate, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, info_bits, product_type';
2736
			if ($this->table_element_line == 'facturedet') $sql.= ', situation_percent';
2737
			$sql.= ', multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
2738
		$sql.= ' FROM '.MAIN_DB_PREFIX.$this->table_element_line;
2739
		$sql.= ' WHERE '.$this->fk_element.' = '.$this->id;
2740
		if ($exclspec)
2741
		{
2742
			$product_field='product_type';
2743
			if ($this->table_element_line == 'contratdet') $product_field='';    // contratdet table has no product_type field
2744
			if ($product_field) $sql.= ' AND '.$product_field.' <> 9';
2745
		}
2746
		$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
2747
2748
		dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
2749
		$resql = $this->db->query($sql);
2750
		if ($resql)
2751
		{
2752
			$this->total_ht  = 0;
2753
			$this->total_tva = 0;
2754
			$this->total_localtax1 = 0;
2755
			$this->total_localtax2 = 0;
2756
			$this->total_ttc = 0;
2757
			$total_ht_by_vats  = array();
2758
			$total_tva_by_vats = array();
2759
			$total_ttc_by_vats = array();
2760
			$this->multicurrency_total_ht	= 0;
2761
			$this->multicurrency_total_tva	= 0;
2762
			$this->multicurrency_total_ttc	= 0;
2763
2764
			$num = $this->db->num_rows($resql);
2765
			$i = 0;
2766
			while ($i < $num)
2767
			{
2768
				$obj = $this->db->fetch_object($resql);
2769
2770
				// Note: There is no check on detail line and no check on total, if $forcedroundingmode = 'none'
2771
				$parameters=array('fk_element' => $obj->rowid);
2772
				$reshook = $hookmanager->executeHooks('changeRoundingMode', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2773
2774
				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'
2775
				{
2776
					$localtax_array=array($obj->localtax1_type,$obj->localtax1_tx,$obj->localtax2_type,$obj->localtax2_tx);
2777
					$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);
0 ignored issues
show
Bug introduced by
It seems like $seller defined by parameter $seller on line 2677 can be null; however, calcul_price_total() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
2778
					$diff=price2num($tmpcal[1] - $obj->total_tva, 'MT', 1);
2779
					if ($diff)
2780
					{
2781
						$sqlfix="UPDATE ".MAIN_DB_PREFIX.$this->table_element_line." SET ".$fieldtva." = ".$tmpcal[1].", total_ttc = ".$tmpcal[2]." WHERE rowid = ".$obj->rowid;
2782
						dol_syslog('We found unconsistent data into detailed line (difference of '.$diff.') for line rowid = '.$obj->rowid." (total vat of line calculated=".$tmpcal[1].", database=".$obj->total_tva."). We fix the total_vat and total_ttc of line by running sqlfix = ".$sqlfix);
2783
								$resqlfix=$this->db->query($sqlfix);
2784
								if (! $resqlfix) dol_print_error($this->db,'Failed to update line');
2785
								$obj->total_tva = $tmpcal[1];
2786
								$obj->total_ttc = $tmpcal[2];
2787
						//
2788
					}
2789
				}
2790
2791
				$this->total_ht        += $obj->total_ht;		// The field visible at end of line detail
2792
				$this->total_tva       += $obj->total_tva;
2793
				$this->total_localtax1 += $obj->total_localtax1;
2794
				$this->total_localtax2 += $obj->total_localtax2;
2795
				$this->total_ttc       += $obj->total_ttc;
2796
				$this->multicurrency_total_ht        += $obj->multicurrency_total_ht;		// The field visible at end of line detail
2797
				$this->multicurrency_total_tva       += $obj->multicurrency_total_tva;
2798
				$this->multicurrency_total_ttc       += $obj->multicurrency_total_ttc;
2799
2800
				if (! isset($total_ht_by_vats[$obj->vatrate]))  $total_ht_by_vats[$obj->vatrate]=0;
2801
				if (! isset($total_tva_by_vats[$obj->vatrate])) $total_tva_by_vats[$obj->vatrate]=0;
2802
				if (! isset($total_ttc_by_vats[$obj->vatrate])) $total_ttc_by_vats[$obj->vatrate]=0;
2803
				$total_ht_by_vats[$obj->vatrate]  += $obj->total_ht;
2804
				$total_tva_by_vats[$obj->vatrate] += $obj->total_tva;
2805
				$total_ttc_by_vats[$obj->vatrate] += $obj->total_ttc;
2806
2807
				if ($forcedroundingmode == '1')	// Check if we need adjustement onto line for vat. TODO This works on the company currency but not on multicurrency
2808
				{
2809
					$tmpvat=price2num($total_ht_by_vats[$obj->vatrate] * $obj->vatrate / 100, 'MT', 1);
2810
					$diff=price2num($total_tva_by_vats[$obj->vatrate]-$tmpvat, 'MT', 1);
2811
					//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";
2812
					if ($diff)
2813
					{
2814
						if (abs($diff) > 0.1) { dol_syslog('A rounding difference was detected into TOTAL but is too high to be corrected', LOG_WARNING); exit; }
2815
						$sqlfix="UPDATE ".MAIN_DB_PREFIX.$this->table_element_line." SET ".$fieldtva." = ".($obj->total_tva - $diff).", total_ttc = ".($obj->total_ttc - $diff)." WHERE rowid = ".$obj->rowid;
2816
						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);
2817
								$resqlfix=$this->db->query($sqlfix);
2818
								if (! $resqlfix) dol_print_error($this->db,'Failed to update line');
2819
								$this->total_tva -= $diff;
2820
								$this->total_ttc -= $diff;
2821
								$total_tva_by_vats[$obj->vatrate] -= $diff;
2822
								$total_ttc_by_vats[$obj->vatrate] -= $diff;
2823
					}
2824
				}
2825
2826
				$i++;
2827
			}
2828
2829
			// Add revenue stamp to total
2830
			$this->total_ttc       			+= isset($this->revenuestamp)?$this->revenuestamp:0;
2831
			$this->multicurrency_total_ttc  += isset($this->revenuestamp)?($this->revenuestamp * $multicurrency_tx):0;
2832
2833
			// Situations totals
2834
			if ($this->situation_cycle_ref && $this->situation_counter > 1 && method_exists($this, 'get_prev_sits') && $this->type != $this::TYPE_CREDIT_NOTE )
2835
			{
2836
				$prev_sits = $this->get_prev_sits();
2837
2838
				foreach ($prev_sits as $sit) {				// $sit is an object Facture loaded with a fetch.
2839
					$this->total_ht -= $sit->total_ht;
2840
					$this->total_tva -= $sit->total_tva;
2841
					$this->total_localtax1 -= $sit->total_localtax1;
2842
					$this->total_localtax2 -= $sit->total_localtax2;
2843
					$this->total_ttc -= $sit->total_ttc;
2844
					$this->multicurrency_total_ht -= $sit->multicurrency_total_ht;
2845
					$this->multicurrency_total_tva -= $sit->multicurrency_total_tva;
2846
					$this->multicurrency_total_ttc -= $sit->multicurrency_total_ttc;
2847
				}
2848
			}
2849
2850
			$this->db->free($resql);
2851
2852
			// Now update global field total_ht, total_ttc and tva
2853
			$fieldht='total_ht';
2854
			$fieldtva='tva';
2855
			$fieldlocaltax1='localtax1';
2856
			$fieldlocaltax2='localtax2';
2857
			$fieldttc='total_ttc';
2858
			// Specific code for backward compatibility with old field names
2859
			if ($this->element == 'facture' || $this->element == 'facturerec')             $fieldht='total';
2860
			if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') $fieldtva='total_tva';
2861
			if ($this->element == 'propal')                                                $fieldttc='total';
2862
			if ($this->element == 'expensereport')                                         $fieldtva='total_tva';
2863
			if ($this->element == 'supplier_proposal')                                     $fieldttc='total';
2864
2865
			if (empty($nodatabaseupdate))
2866
			{
2867
				$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element.' SET';
2868
				$sql .= " ".$fieldht."='".price2num($this->total_ht)."',";
2869
				$sql .= " ".$fieldtva."='".price2num($this->total_tva)."',";
2870
				$sql .= " ".$fieldlocaltax1."='".price2num($this->total_localtax1)."',";
2871
				$sql .= " ".$fieldlocaltax2."='".price2num($this->total_localtax2)."',";
2872
				$sql .= " ".$fieldttc."='".price2num($this->total_ttc)."'";
2873
						$sql .= ", multicurrency_total_ht='".price2num($this->multicurrency_total_ht, 'MT', 1)."'";
2874
						$sql .= ", multicurrency_total_tva='".price2num($this->multicurrency_total_tva, 'MT', 1)."'";
2875
						$sql .= ", multicurrency_total_ttc='".price2num($this->multicurrency_total_ttc, 'MT', 1)."'";
2876
				$sql .= ' WHERE rowid = '.$this->id;
2877
2878
2879
				dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
2880
				$resql=$this->db->query($sql);
2881
				if (! $resql)
2882
				{
2883
					$error++;
2884
					$this->error=$this->db->lasterror();
2885
					$this->errors[]=$this->db->lasterror();
2886
				}
2887
			}
2888
2889
			if (! $error)
2890
			{
2891
				return 1;
2892
			}
2893
			else
2894
			{
2895
				return -1;
2896
			}
2897
		}
2898
		else
2899
		{
2900
			dol_print_error($this->db,'Bad request in update_price');
2901
			return -1;
2902
		}
2903
	}
2904
2905
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2906
	/**
2907
	 *	Add objects linked in llx_element_element.
2908
	 *
2909
	 *	@param		string	$origin		Linked element type
2910
	 *	@param		int		$origin_id	Linked element id
2911
	 *	@return		int					<=0 if KO, >0 if OK
2912
	 *	@see		fetchObjectLinked, updateObjectLinked, deleteObjectLinked
2913
	 */
2914
	function add_object_linked($origin=null, $origin_id=null)
2915
	{
2916
        // phpcs:enable
2917
		$origin = (! empty($origin) ? $origin : $this->origin);
2918
		$origin_id = (! empty($origin_id) ? $origin_id : $this->origin_id);
2919
2920
		// Special case
2921
		if ($origin == 'order') $origin='commande';
2922
		if ($origin == 'invoice') $origin='facture';
2923
		if ($origin == 'invoice_template') $origin='facturerec';
2924
2925
		$this->db->begin();
2926
2927
		$sql = "INSERT INTO ".MAIN_DB_PREFIX."element_element (";
2928
		$sql.= "fk_source";
2929
		$sql.= ", sourcetype";
2930
		$sql.= ", fk_target";
2931
		$sql.= ", targettype";
2932
		$sql.= ") VALUES (";
2933
		$sql.= $origin_id;
2934
		$sql.= ", '".$this->db->escape($origin)."'";
2935
		$sql.= ", ".$this->id;
2936
		$sql.= ", '".$this->db->escape($this->element)."'";
2937
		$sql.= ")";
2938
2939
		dol_syslog(get_class($this)."::add_object_linked", LOG_DEBUG);
2940
		if ($this->db->query($sql))
2941
	  	{
2942
	  		$this->db->commit();
2943
	  		return 1;
2944
	  	}
2945
	  	else
2946
	  	{
2947
	  		$this->error=$this->db->lasterror();
2948
	  		$this->db->rollback();
2949
	  		return 0;
2950
	  	}
2951
	}
2952
2953
	/**
2954
	 *	Fetch array of objects linked to current object (object of enabled modules only). Links are loaded into
2955
	 *		this->linkedObjectsIds array and
2956
	 *		this->linkedObjects array if $loadalsoobjects = 1
2957
	 *  Possible usage for parameters:
2958
	 *  - all parameters empty -> we look all link to current object (current object can be source or target)
2959
	 *  - source id+type -> will get target list linked to source
2960
	 *  - target id+type -> will get source list linked to target
2961
	 *  - source id+type + target type -> will get target list of the type
2962
	 *  - target id+type + target source -> will get source list of the type
2963
	 *
2964
	 *	@param	int		$sourceid			Object source id (if not defined, id of object)
2965
	 *	@param  string	$sourcetype			Object source type (if not defined, element name of object)
2966
	 *	@param  int		$targetid			Object target id (if not defined, id of object)
2967
	 *	@param  string	$targettype			Object target type (if not defined, elemennt name of object)
2968
	 *	@param  string	$clause				'OR' or 'AND' clause used when both source id and target id are provided
2969
	 *  @param  int		$alsosametype		0=Return only links to object that differs from source type. 1=Include also link to objects of same type.
2970
	 *  @param  string	$orderby			SQL 'ORDER BY' clause
2971
	 *  @param	int		$loadalsoobjects	Load also array this->linkedObjects (Use 0 to increase performances)
2972
	 *	@return int							<0 if KO, >0 if OK
2973
	 *  @see	add_object_linked, updateObjectLinked, deleteObjectLinked
2974
	 */
2975
	function fetchObjectLinked($sourceid=null,$sourcetype='',$targetid=null,$targettype='',$clause='OR',$alsosametype=1,$orderby='sourcetype',$loadalsoobjects=1)
2976
	{
2977
		global $conf;
2978
2979
		$this->linkedObjectsIds=array();
2980
		$this->linkedObjects=array();
2981
2982
		$justsource=false;
2983
		$justtarget=false;
2984
		$withtargettype=false;
2985
		$withsourcetype=false;
2986
2987
		if (! empty($sourceid) && ! empty($sourcetype) && empty($targetid))
2988
		{
2989
			$justsource=true;  // the source (id and type) is a search criteria
2990
			if (! empty($targettype)) $withtargettype=true;
2991
		}
2992
		if (! empty($targetid) && ! empty($targettype) && empty($sourceid))
2993
		{
2994
			$justtarget=true;  // the target (id and type) is a search criteria
2995
			if (! empty($sourcetype)) $withsourcetype=true;
2996
		}
2997
2998
		$sourceid = (! empty($sourceid) ? $sourceid : $this->id);
2999
		$targetid = (! empty($targetid) ? $targetid : $this->id);
3000
		$sourcetype = (! empty($sourcetype) ? $sourcetype : $this->element);
3001
		$targettype = (! empty($targettype) ? $targettype : $this->element);
3002
3003
		/*if (empty($sourceid) && empty($targetid))
3004
		 {
3005
		 dol_syslog('Bad usage of function. No source nor target id defined (nor as parameter nor as object id)', LOG_ERR);
3006
		 return -1;
3007
		 }*/
3008
3009
		// Links between objects are stored in table element_element
3010
		$sql = 'SELECT rowid, fk_source, sourcetype, fk_target, targettype';
3011
		$sql.= ' FROM '.MAIN_DB_PREFIX.'element_element';
3012
		$sql.= " WHERE ";
3013
		if ($justsource || $justtarget)
3014
		{
3015
			if ($justsource)
3016
			{
3017
				$sql.= "fk_source = ".$sourceid." AND sourcetype = '".$sourcetype."'";
3018
				if ($withtargettype) $sql.= " AND targettype = '".$targettype."'";
3019
			}
3020
			else if ($justtarget)
3021
			{
3022
				$sql.= "fk_target = ".$targetid." AND targettype = '".$targettype."'";
3023
				if ($withsourcetype) $sql.= " AND sourcetype = '".$sourcetype."'";
3024
			}
3025
		}
3026
		else
3027
		{
3028
			$sql.= "(fk_source = ".$sourceid." AND sourcetype = '".$sourcetype."')";
3029
			$sql.= " ".$clause." (fk_target = ".$targetid." AND targettype = '".$targettype."')";
3030
		}
3031
		$sql .= ' ORDER BY '.$orderby;
3032
3033
		dol_syslog(get_class($this)."::fetchObjectLink", LOG_DEBUG);
3034
		$resql = $this->db->query($sql);
3035
		if ($resql)
3036
		{
3037
			$num = $this->db->num_rows($resql);
3038
			$i = 0;
3039
			while ($i < $num)
3040
			{
3041
				$obj = $this->db->fetch_object($resql);
3042
				if ($justsource || $justtarget)
3043
				{
3044
					if ($justsource)
3045
					{
3046
						$this->linkedObjectsIds[$obj->targettype][$obj->rowid]=$obj->fk_target;
3047
					}
3048
					else if ($justtarget)
3049
					{
3050
						$this->linkedObjectsIds[$obj->sourcetype][$obj->rowid]=$obj->fk_source;
3051
					}
3052
				}
3053
				else
3054
				{
3055
					if ($obj->fk_source == $sourceid && $obj->sourcetype == $sourcetype)
3056
					{
3057
						$this->linkedObjectsIds[$obj->targettype][$obj->rowid]=$obj->fk_target;
3058
					}
3059
					if ($obj->fk_target == $targetid && $obj->targettype == $targettype)
3060
					{
3061
						$this->linkedObjectsIds[$obj->sourcetype][$obj->rowid]=$obj->fk_source;
3062
					}
3063
				}
3064
				$i++;
3065
			}
3066
3067
			if (! empty($this->linkedObjectsIds))
3068
			{
3069
				$tmparray = $this->linkedObjectsIds;
3070
				foreach($tmparray as $objecttype => $objectids)       // $objecttype is a module name ('facture', 'mymodule', ...) or a module name with a suffix ('project_task', 'mymodule_myobj', ...)
3071
				{
3072
					// Parse element/subelement (ex: project_task, cabinetmed_consultation, ...)
3073
					$module = $element = $subelement = $objecttype;
3074
					if ($objecttype != 'supplier_proposal' && $objecttype != 'order_supplier' && $objecttype != 'invoice_supplier'
3075
						&& preg_match('/^([^_]+)_([^_]+)/i',$objecttype,$regs))
3076
					{
3077
						$module = $element = $regs[1];
3078
						$subelement = $regs[2];
3079
					}
3080
3081
					$classpath = $element.'/class';
3082
					// To work with non standard classpath or module name
3083
					if ($objecttype == 'facture')			{
3084
						$classpath = 'compta/facture/class';
3085
					}
3086
					else if ($objecttype == 'facturerec')			{
3087
						$classpath = 'compta/facture/class'; $module = 'facture';
3088
					}
3089
					else if ($objecttype == 'propal')			{
3090
						$classpath = 'comm/propal/class';
3091
					}
3092
					else if ($objecttype == 'supplier_proposal')			{
3093
						$classpath = 'supplier_proposal/class';
3094
					}
3095
					else if ($objecttype == 'shipping')			{
3096
						$classpath = 'expedition/class'; $subelement = 'expedition'; $module = 'expedition_bon';
3097
					}
3098
					else if ($objecttype == 'delivery')			{
3099
						$classpath = 'livraison/class'; $subelement = 'livraison'; $module = 'livraison_bon';
3100
					}
3101
					else if ($objecttype == 'invoice_supplier' || $objecttype == 'order_supplier')	{
3102
						$classpath = 'fourn/class'; $module = 'fournisseur';
3103
					}
3104
					else if ($objecttype == 'fichinter')			{
3105
						$classpath = 'fichinter/class'; $subelement = 'fichinter'; $module = 'ficheinter';
3106
					}
3107
					else if ($objecttype == 'subscription')			{
3108
						$classpath = 'adherents/class'; $module = 'adherent';
3109
					}
3110
3111
					// Set classfile
3112
					$classfile = strtolower($subelement); $classname = ucfirst($subelement);
3113
3114
					if ($objecttype == 'order') {
3115
						$classfile = 'commande'; $classname = 'Commande';
3116
					}
3117
					else if ($objecttype == 'invoice_supplier') {
3118
						$classfile = 'fournisseur.facture'; $classname = 'FactureFournisseur';
3119
					}
3120
					else if ($objecttype == 'order_supplier')   {
3121
						$classfile = 'fournisseur.commande'; $classname = 'CommandeFournisseur';
3122
					}
3123
					else if ($objecttype == 'supplier_proposal')   {
3124
						$classfile = 'supplier_proposal'; $classname = 'SupplierProposal';
3125
					}
3126
					else if ($objecttype == 'facturerec')   {
3127
						$classfile = 'facture-rec'; $classname = 'FactureRec';
3128
					}
3129
					else if ($objecttype == 'subscription')   {
3130
						$classfile = 'subscription'; $classname = 'Subscription';
3131
					}
3132
3133
					// Here $module, $classfile and $classname are set
3134
					if ($conf->$module->enabled && (($element != $this->element) || $alsosametype))
3135
					{
3136
						if ($loadalsoobjects)
3137
						{
3138
							dol_include_once('/'.$classpath.'/'.$classfile.'.class.php');
3139
							//print '/'.$classpath.'/'.$classfile.'.class.php '.class_exists($classname);
3140
							if (class_exists($classname))
3141
							{
3142
								foreach($objectids as $i => $objectid)	// $i is rowid into llx_element_element
3143
								{
3144
									$object = new $classname($this->db);
3145
									$ret = $object->fetch($objectid);
3146
									if ($ret >= 0)
3147
									{
3148
										$this->linkedObjects[$objecttype][$i] = $object;
3149
									}
3150
								}
3151
							}
3152
						}
3153
					}
3154
					else
3155
					{
3156
						unset($this->linkedObjectsIds[$objecttype]);
3157
					}
3158
				}
3159
			}
3160
			return 1;
3161
		}
3162
		else
3163
		{
3164
			dol_print_error($this->db);
3165
			return -1;
3166
		}
3167
	}
3168
3169
	/**
3170
	 *	Update object linked of a current object
3171
	 *
3172
	 *	@param	int		$sourceid		Object source id
3173
	 *	@param  string	$sourcetype		Object source type
3174
	 *	@param  int		$targetid		Object target id
3175
	 *	@param  string	$targettype		Object target type
3176
	 *	@return							int	>0 if OK, <0 if KO
3177
	 *	@see	add_object_linked, fetObjectLinked, deleteObjectLinked
3178
	 */
3179
	function updateObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='')
3180
	{
3181
		$updatesource=false;
3182
		$updatetarget=false;
3183
3184
		if (! empty($sourceid) && ! empty($sourcetype) && empty($targetid) && empty($targettype)) $updatesource=true;
3185
		else if (empty($sourceid) && empty($sourcetype) && ! empty($targetid) && ! empty($targettype)) $updatetarget=true;
3186
3187
		$sql = "UPDATE ".MAIN_DB_PREFIX."element_element SET ";
3188
		if ($updatesource)
3189
		{
3190
			$sql.= "fk_source = ".$sourceid;
3191
			$sql.= ", sourcetype = '".$this->db->escape($sourcetype)."'";
3192
			$sql.= " WHERE fk_target = ".$this->id;
3193
			$sql.= " AND targettype = '".$this->db->escape($this->element)."'";
3194
		}
3195
		else if ($updatetarget)
3196
		{
3197
			$sql.= "fk_target = ".$targetid;
3198
			$sql.= ", targettype = '".$this->db->escape($targettype)."'";
3199
			$sql.= " WHERE fk_source = ".$this->id;
3200
			$sql.= " AND sourcetype = '".$this->db->escape($this->element)."'";
3201
		}
3202
3203
		dol_syslog(get_class($this)."::updateObjectLinked", LOG_DEBUG);
3204
		if ($this->db->query($sql))
3205
		{
3206
			return 1;
3207
		}
3208
		else
3209
		{
3210
			$this->error=$this->db->lasterror();
3211
			return -1;
3212
		}
3213
	}
3214
3215
	/**
3216
	 *	Delete all links between an object $this
3217
	 *
3218
	 *	@param	int		$sourceid		Object source id
3219
	 *	@param  string	$sourcetype		Object source type
3220
	 *	@param  int		$targetid		Object target id
3221
	 *	@param  string	$targettype		Object target type
3222
	 *  @param	int		$rowid			Row id of line to delete. If defined, other parameters are not used.
3223
	 *	@return     					int	>0 if OK, <0 if KO
3224
	 *	@see	add_object_linked, updateObjectLinked, fetchObjectLinked
3225
	 */
3226
	function deleteObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $rowid='')
3227
	{
3228
		$deletesource=false;
3229
		$deletetarget=false;
3230
3231
		if (! empty($sourceid) && ! empty($sourcetype) && empty($targetid) && empty($targettype)) $deletesource=true;
3232
		else if (empty($sourceid) && empty($sourcetype) && ! empty($targetid) && ! empty($targettype)) $deletetarget=true;
3233
3234
		$sourceid = (! empty($sourceid) ? $sourceid : $this->id);
3235
		$sourcetype = (! empty($sourcetype) ? $sourcetype : $this->element);
3236
		$targetid = (! empty($targetid) ? $targetid : $this->id);
3237
		$targettype = (! empty($targettype) ? $targettype : $this->element);
3238
3239
		$sql = "DELETE FROM ".MAIN_DB_PREFIX."element_element";
3240
		$sql.= " WHERE";
3241
		if ($rowid > 0)
3242
		{
3243
			$sql.=" rowid = ".$rowid;
3244
		}
3245
		else
3246
		{
3247
			if ($deletesource)
3248
			{
3249
				$sql.= " fk_source = ".$sourceid." AND sourcetype = '".$this->db->escape($sourcetype)."'";
3250
				$sql.= " AND fk_target = ".$this->id." AND targettype = '".$this->db->escape($this->element)."'";
3251
			}
3252
			else if ($deletetarget)
3253
			{
3254
				$sql.= " fk_target = ".$targetid." AND targettype = '".$this->db->escape($targettype)."'";
3255
				$sql.= " AND fk_source = ".$this->id." AND sourcetype = '".$this->db->escape($this->element)."'";
3256
			}
3257
			else
3258
			{
3259
				$sql.= " (fk_source = ".$this->id." AND sourcetype = '".$this->db->escape($this->element)."')";
3260
				$sql.= " OR";
3261
				$sql.= " (fk_target = ".$this->id." AND targettype = '".$this->db->escape($this->element)."')";
3262
			}
3263
		}
3264
3265
		dol_syslog(get_class($this)."::deleteObjectLinked", LOG_DEBUG);
3266
		if ($this->db->query($sql))
3267
		{
3268
			return 1;
3269
		}
3270
		else
3271
		{
3272
			$this->error=$this->db->lasterror();
3273
			$this->errors[]=$this->error;
3274
			return -1;
3275
		}
3276
	}
3277
3278
	/**
3279
	 *      Set status of an object
3280
	 *
3281
	 *      @param	int		$status			Status to set
3282
	 *      @param	int		$elementId		Id of element to force (use this->id by default)
3283
	 *      @param	string	$elementType	Type of element to force (use this->table_element by default)
3284
	 *      @param	string	$trigkey		Trigger key to use for trigger
3285
	 *      @return int						<0 if KO, >0 if OK
3286
	 */
3287
	function setStatut($status, $elementId=null, $elementType='', $trigkey='')
3288
	{
3289
		global $user,$langs,$conf;
3290
3291
		$savElementId=$elementId;  // To be used later to know if we were using the method using the id of this or not.
3292
3293
		$elementId = (!empty($elementId)?$elementId:$this->id);
3294
		$elementTable = (!empty($elementType)?$elementType:$this->table_element);
3295
3296
		$this->db->begin();
3297
3298
		$fieldstatus="fk_statut";
3299
		if ($elementTable == 'facture_rec') $fieldstatus="suspended";
3300
		if ($elementTable == 'mailing') $fieldstatus="statut";
3301
		if ($elementTable == 'cronjob') $fieldstatus="status";
3302
		if ($elementTable == 'user') $fieldstatus="statut";
3303
		if ($elementTable == 'expensereport') $fieldstatus="fk_statut";
3304
		if ($elementTable == 'commande_fournisseur_dispatch') $fieldstatus="status";
3305
3306
		$sql = "UPDATE ".MAIN_DB_PREFIX.$elementTable;
3307
		$sql.= " SET ".$fieldstatus." = ".$status;
3308
		// If status = 1 = validated, update also fk_user_valid
3309
		if ($status == 1 && $elementTable == 'expensereport') $sql.=", fk_user_valid = ".$user->id;
3310
		$sql.= " WHERE rowid=".$elementId;
3311
3312
		dol_syslog(get_class($this)."::setStatut", LOG_DEBUG);
3313
		if ($this->db->query($sql))
3314
		{
3315
			$error = 0;
3316
3317
			// Try autoset of trigkey
3318
			if (empty($trigkey))
3319
			{
3320
				if ($this->element == 'supplier_proposal' && $status == 2) $trigkey='SUPPLIER_PROPOSAL_SIGN';   // 2 = SupplierProposal::STATUS_SIGNED. Can't use constant into this generic class
3321
				if ($this->element == 'supplier_proposal' && $status == 3) $trigkey='SUPPLIER_PROPOSAL_REFUSE'; // 3 = SupplierProposal::STATUS_REFUSED. Can't use constant into this generic class
3322
				if ($this->element == 'supplier_proposal' && $status == 4) $trigkey='SUPPLIER_PROPOSAL_CLOSE';  // 4 = SupplierProposal::STATUS_CLOSED. Can't use constant into this generic class
3323
				if ($this->element == 'fichinter' && $status == 3) $trigkey='FICHINTER_CLASSIFY_DONE';
3324
				if ($this->element == 'fichinter' && $status == 2) $trigkey='FICHINTER_CLASSIFY_BILLED';
3325
				if ($this->element == 'fichinter' && $status == 1) $trigkey='FICHINTER_CLASSIFY_UNBILLED';
3326
			}
3327
3328
			if ($trigkey)
3329
			{
3330
				// Appel des triggers
3331
				include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
3332
				$interface=new Interfaces($this->db);
3333
				$result=$interface->run_triggers($trigkey,$this,$user,$langs,$conf);
3334
				if ($result < 0) {
3335
					$error++; $this->errors=$interface->errors;
3336
				}
3337
				// Fin appel triggers
3338
			}
3339
3340
			if (! $error)
3341
			{
3342
				$this->db->commit();
3343
3344
				if (empty($savElementId))    // If the element we update was $this (so $elementId is null)
3345
				{
3346
					$this->statut = $status;
3347
					$this->status = $status;
3348
				}
3349
3350
				return 1;
3351
			}
3352
			else
3353
			{
3354
				$this->db->rollback();
3355
				dol_syslog(get_class($this)."::setStatus ".$this->error,LOG_ERR);
3356
				return -1;
3357
			}
3358
		}
3359
		else
3360
		{
3361
			$this->error=$this->db->lasterror();
3362
			$this->db->rollback();
3363
			return -1;
3364
		}
3365
	}
3366
3367
3368
	/**
3369
	 *  Load type of canvas of an object if it exists
3370
	 *
3371
	 *  @param      int		$id     Record id
3372
	 *  @param      string	$ref    Record ref
3373
	 *  @return		int				<0 if KO, 0 if nothing done, >0 if OK
3374
	 */
3375
	function getCanvas($id=0,$ref='')
3376
	{
3377
		global $conf;
3378
3379
		if (empty($id) && empty($ref)) return 0;
3380
		if (! empty($conf->global->MAIN_DISABLE_CANVAS)) return 0;    // To increase speed. Not enabled by default.
3381
3382
		// Clean parameters
3383
		$ref = trim($ref);
3384
3385
		$sql = "SELECT rowid, canvas";
3386
		$sql.= " FROM ".MAIN_DB_PREFIX.$this->table_element;
3387
		$sql.= " WHERE entity IN (".getEntity($this->element).")";
3388
		if (! empty($id))  $sql.= " AND rowid = ".$id;
3389
		if (! empty($ref)) $sql.= " AND ref = '".$this->db->escape($ref)."'";
3390
3391
		$resql = $this->db->query($sql);
3392
		if ($resql)
3393
		{
3394
			$obj = $this->db->fetch_object($resql);
3395
			if ($obj)
3396
			{
3397
				$this->canvas   = $obj->canvas;
3398
				return 1;
3399
			}
3400
			else return 0;
3401
		}
3402
		else
3403
		{
3404
			dol_print_error($this->db);
3405
			return -1;
3406
		}
3407
	}
3408
3409
3410
	/**
3411
	 * 	Get special code of a line
3412
	 *
3413
	 * 	@param	int		$lineid		Id of line
3414
	 * 	@return	int					Special code
3415
	 */
3416
	function getSpecialCode($lineid)
3417
	{
3418
		$sql = 'SELECT special_code FROM '.MAIN_DB_PREFIX.$this->table_element_line;
3419
		$sql.= ' WHERE rowid = '.$lineid;
3420
		$resql = $this->db->query($sql);
3421
		if ($resql)
3422
		{
3423
			$row = $this->db->fetch_row($resql);
3424
			return $row[0];
3425
		}
3426
	}
3427
3428
	/**
3429
	 *  Function to check if an object is used by others.
3430
	 *  Check is done into this->childtables. There is no check into llx_element_element.
3431
	 *
3432
	 *  @param	int		$id			Force id of object
3433
	 *  @return	int					<0 if KO, 0 if not used, >0 if already used
3434
	 */
3435
	function isObjectUsed($id=0)
3436
	{
3437
		global $langs;
3438
3439
		if (empty($id)) $id=$this->id;
3440
3441
		// Check parameters
3442
		if (! isset($this->childtables) || ! is_array($this->childtables) || count($this->childtables) == 0)
3443
		{
3444
			dol_print_error('Called isObjectUsed on a class with property this->childtables not defined');
3445
			return -1;
3446
		}
3447
3448
		$arraytoscan = $this->childtables;
3449
		// For backward compatibility, we check if array is old format array('table1', 'table2', ...)
3450
		$tmparray=array_keys($this->childtables);
3451
		if (is_numeric($tmparray[0]))
3452
		{
3453
			$arraytoscan = array_flip($this->childtables);
3454
		}
3455
3456
		// Test if child exists
3457
		$haschild=0;
3458
		foreach($arraytoscan as $table => $elementname)
3459
		{
3460
			//print $id.'-'.$table.'-'.$elementname.'<br>';
3461
			// Check if third party can be deleted
3462
			$sql = "SELECT COUNT(*) as nb from ".MAIN_DB_PREFIX.$table;
3463
			$sql.= " WHERE ".$this->fk_element." = ".$id;
3464
			$resql=$this->db->query($sql);
3465
			if ($resql)
3466
			{
3467
				$obj=$this->db->fetch_object($resql);
3468
				if ($obj->nb > 0)
3469
				{
3470
					$langs->load("errors");
3471
					//print 'Found into table '.$table.', type '.$langs->transnoentitiesnoconv($elementname).', haschild='.$haschild;
3472
					$haschild += $obj->nb;
3473
					if (is_numeric($elementname))	// old usage
3474
					{
3475
						$this->errors[]=$langs->trans("ErrorRecordHasAtLeastOneChildOfType", $table);
3476
					}
3477
					else	// new usage: $elementname=Translation key
3478
					{
3479
						$this->errors[]=$langs->trans("ErrorRecordHasAtLeastOneChildOfType", $langs->transnoentitiesnoconv($elementname));
3480
					}
3481
					break;    // We found at least one, we stop here
3482
				}
3483
			}
3484
			else
3485
			{
3486
				$this->errors[]=$this->db->lasterror();
3487
				return -1;
3488
			}
3489
		}
3490
		if ($haschild > 0)
3491
		{
3492
			$this->errors[]="ErrorRecordHasChildren";
3493
			return $haschild;
3494
		}
3495
		else return 0;
3496
	}
3497
3498
	/**
3499
	 *  Function to say how many lines object contains
3500
	 *
3501
	 *	@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
3502
	 *  @return	int						<0 if KO, 0 if no predefined products, nb of lines with predefined products if found
3503
	 */
3504
	function hasProductsOrServices($predefined=-1)
3505
	{
3506
		$nb=0;
3507
3508
		foreach($this->lines as $key => $val)
3509
		{
3510
			$qualified=0;
3511
			if ($predefined == -1) $qualified=1;
3512
			if ($predefined == 1 && $val->fk_product > 0) $qualified=1;
3513
			if ($predefined == 0 && $val->fk_product <= 0) $qualified=1;
3514
			if ($predefined == 2 && $val->fk_product > 0 && $val->product_type==0) $qualified=1;
3515
			if ($predefined == 3 && $val->fk_product > 0 && $val->product_type==1) $qualified=1;
3516
			if ($qualified) $nb++;
3517
		}
3518
		dol_syslog(get_class($this).'::hasProductsOrServices we found '.$nb.' qualified lines of products/servcies');
3519
		return $nb;
3520
	}
3521
3522
	/**
3523
	 * Function that returns the total amount HT of discounts applied for all lines.
3524
	 *
3525
	 * @return 	float
3526
	 */
3527
	function getTotalDiscount()
3528
	{
3529
		$total_discount=0.00;
3530
3531
		$sql = "SELECT subprice as pu_ht, qty, remise_percent, total_ht";
3532
		$sql.= " FROM ".MAIN_DB_PREFIX.$this->table_element."det";
3533
		$sql.= " WHERE ".$this->fk_element." = ".$this->id;
3534
3535
		dol_syslog(get_class($this).'::getTotalDiscount', LOG_DEBUG);
3536
		$resql = $this->db->query($sql);
3537
		if ($resql)
3538
		{
3539
			$num=$this->db->num_rows($resql);
3540
			$i=0;
3541
			while ($i < $num)
3542
			{
3543
				$obj = $this->db->fetch_object($resql);
3544
3545
				$pu_ht = $obj->pu_ht;
3546
				$qty= $obj->qty;
3547
				$total_ht = $obj->total_ht;
3548
3549
				$total_discount_line = floatval(price2num(($pu_ht * $qty) - $total_ht, 'MT'));
3550
				$total_discount += $total_discount_line;
3551
3552
				$i++;
3553
			}
3554
		}
3555
3556
		//print $total_discount; exit;
3557
		return price2num($total_discount);
3558
	}
3559
3560
3561
	/**
3562
	 * Return into unit=0, the calculated total of weight and volume of all lines * qty
3563
	 * Calculate by adding weight and volume of each product line, so properties ->volume/volume_units/weight/weight_units must be loaded on line.
3564
	 *
3565
	 * @return  array                           array('weight'=>...,'volume'=>...)
3566
	 */
3567
	function getTotalWeightVolume()
3568
	{
3569
		$totalWeight = 0;
3570
		$totalVolume = 0;
3571
		// defined for shipment only
3572
		$totalOrdered = '';
3573
		// defined for shipment only
3574
		$totalToShip = '';
3575
3576
		foreach ($this->lines as $line)
3577
		{
3578
			if (isset($line->qty_asked))
3579
			{
3580
				if (empty($totalOrdered)) $totalOrdered=0;  // Avoid warning because $totalOrdered is ''
3581
				$totalOrdered+=$line->qty_asked;    // defined for shipment only
3582
			}
3583
			if (isset($line->qty_shipped))
3584
			{
3585
				if (empty($totalToShip)) $totalToShip=0;    // Avoid warning because $totalToShip is ''
3586
				$totalToShip+=$line->qty_shipped;   // defined for shipment only
3587
			}
3588
3589
			// Define qty, weight, volume, weight_units, volume_units
3590
			if ($this->element == 'shipping') {
3591
				// for shipments
3592
				$qty = $line->qty_shipped ? $line->qty_shipped : 0;
3593
			}
3594
			else {
3595
				$qty = $line->qty ? $line->qty : 0;
3596
			}
3597
3598
			$weight = $line->weight ? $line->weight : 0;
3599
			$volume = $line->volume ? $line->volume : 0;
3600
3601
			$weight_units=$line->weight_units;
3602
			$volume_units=$line->volume_units;
3603
3604
			$weightUnit=0;
3605
			$volumeUnit=0;
3606
			if (! empty($weight_units)) $weightUnit = $weight_units;
3607
			if (! empty($volume_units)) $volumeUnit = $volume_units;
3608
3609
			if (empty($totalWeight)) $totalWeight=0;  // Avoid warning because $totalWeight is ''
3610
			if (empty($totalVolume)) $totalVolume=0;  // Avoid warning because $totalVolume is ''
3611
3612
			//var_dump($line->volume_units);
3613
			if ($weight_units < 50)   // >50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
3614
			{
3615
				$trueWeightUnit=pow(10, $weightUnit);
3616
				$totalWeight += $weight * $qty * $trueWeightUnit;
3617
			}
3618
			else {
3619
		if ($weight_units == 99) {
3620
			// conversion 1 Pound = 0.45359237 KG
3621
			$trueWeightUnit = 0.45359237;
3622
			$totalWeight += $weight * $qty * $trueWeightUnit;
3623
		} elseif ($weight_units == 98) {
3624
			// conversion 1 Ounce = 0.0283495 KG
3625
			$trueWeightUnit = 0.0283495;
3626
			$totalWeight += $weight * $qty * $trueWeightUnit;
3627
		}
3628
		else
3629
					$totalWeight += $weight * $qty;   // This may be wrong if we mix different units
3630
			}
3631
			if ($volume_units < 50)   // >50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
3632
			{
3633
				//print $line->volume."x".$line->volume_units."x".($line->volume_units < 50)."x".$volumeUnit;
3634
				$trueVolumeUnit=pow(10, $volumeUnit);
3635
				//print $line->volume;
3636
				$totalVolume += $volume * $qty * $trueVolumeUnit;
3637
			}
3638
			else
3639
			{
3640
				$totalVolume += $volume * $qty;   // This may be wrong if we mix different units
3641
			}
3642
		}
3643
3644
		return array('weight'=>$totalWeight, 'volume'=>$totalVolume, 'ordered'=>$totalOrdered, 'toship'=>$totalToShip);
3645
	}
3646
3647
3648
	/**
3649
	 *	Set extra parameters
3650
	 *
3651
	 *	@return	int      <0 if KO, >0 if OK
3652
	 */
3653
	function setExtraParameters()
3654
	{
3655
		$this->db->begin();
3656
3657
		$extraparams = (! empty($this->extraparams) ? json_encode($this->extraparams) : null);
3658
3659
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
3660
		$sql.= " SET extraparams = ".(! empty($extraparams) ? "'".$this->db->escape($extraparams)."'" : "null");
3661
		$sql.= " WHERE rowid = ".$this->id;
3662
3663
		dol_syslog(get_class($this)."::setExtraParameters", LOG_DEBUG);
3664
		$resql = $this->db->query($sql);
3665
		if (! $resql)
3666
		{
3667
			$this->error=$this->db->lasterror();
3668
			$this->db->rollback();
3669
			return -1;
3670
		}
3671
		else
3672
		{
3673
			$this->db->commit();
3674
			return 1;
3675
		}
3676
	}
3677
3678
3679
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3680
	/**
3681
	 *    Return incoterms informations
3682
	 *    TODO Use a cache for label get
3683
	 *
3684
	 *    @return	string	incoterms info
3685
	 */
3686
	function display_incoterms()
3687
	{
3688
        // phpcs:enable
3689
		$out = '';
3690
		$this->libelle_incoterms = '';
3691
		if (!empty($this->fk_incoterms))
3692
		{
3693
			$sql = 'SELECT code FROM '.MAIN_DB_PREFIX.'c_incoterms WHERE rowid = '.(int) $this->fk_incoterms;
3694
			$result = $this->db->query($sql);
3695
			if ($result)
3696
			{
3697
				$res = $this->db->fetch_object($result);
3698
				$out .= $res->code;
3699
			}
3700
		}
3701
3702
		$out .= (($res->code && $this->location_incoterms)?' - ':'').$this->location_incoterms;
0 ignored issues
show
Bug introduced by
The variable $res does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
3703
3704
		return $out;
3705
	}
3706
3707
	/**
3708
	 *    Return incoterms informations for pdf display
3709
	 *
3710
	 *    @return	string		incoterms info
3711
	 */
3712
	function getIncotermsForPDF()
3713
	{
3714
		$sql = 'SELECT code FROM '.MAIN_DB_PREFIX.'c_incoterms WHERE rowid = '.(int) $this->fk_incoterms;
3715
		$resql = $this->db->query($sql);
3716
		if ($resql)
3717
		{
3718
			$num = $this->db->num_rows($resql);
3719
			if ($num > 0)
3720
			{
3721
				$res = $this->db->fetch_object($resql);
3722
				return 'Incoterm : '.$res->code.' - '.$this->location_incoterms;
3723
			}
3724
			else
3725
			{
3726
				return '';
3727
			}
3728
		}
3729
		else
3730
		{
3731
			$this->errors[] = $this->db->lasterror();
3732
			return false;
3733
		}
3734
	}
3735
3736
	/**
3737
	 *    Define incoterms values of current object
3738
	 *
3739
	 *    @param	int		$id_incoterm     Id of incoterm to set or '' to remove
3740
	 * 	  @param 	string  $location		 location of incoterm
3741
	 *    @return	int     		<0 if KO, >0 if OK
3742
	 */
3743
	function setIncoterms($id_incoterm, $location)
3744
	{
3745
		if ($this->id && $this->table_element)
3746
		{
3747
			$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
3748
			$sql.= " SET fk_incoterms = ".($id_incoterm > 0 ? $id_incoterm : "null");
3749
			$sql.= ", location_incoterms = ".($id_incoterm > 0 ? "'".$this->db->escape($location)."'" : "null");
3750
			$sql.= " WHERE rowid = " . $this->id;
3751
			dol_syslog(get_class($this).'::setIncoterms', LOG_DEBUG);
3752
			$resql=$this->db->query($sql);
3753
			if ($resql)
3754
			{
3755
				$this->fk_incoterms = $id_incoterm;
3756
				$this->location_incoterms = $location;
3757
3758
				$sql = 'SELECT libelle FROM '.MAIN_DB_PREFIX.'c_incoterms WHERE rowid = '.(int) $this->fk_incoterms;
3759
				$res = $this->db->query($sql);
3760
				if ($res)
3761
				{
3762
					$obj = $this->db->fetch_object($res);
3763
					$this->libelle_incoterms = $obj->libelle;
3764
				}
3765
				return 1;
3766
			}
3767
			else
3768
			{
3769
				$this->errors[] = $this->db->lasterror();
3770
				return -1;
3771
			}
3772
		}
3773
		else return -1;
3774
	}
3775
3776
3777
	// --------------------
3778
	// TODO: All functions here must be redesigned and moved as they are not business functions but output functions
3779
	// --------------------
3780
3781
	/* This is to show add lines */
3782
3783
	/**
3784
	 *	Show add free and predefined products/services form
3785
	 *
3786
	 *  @param	int		        $dateSelector       1=Show also date range input fields
3787
	 *  @param	Societe			$seller				Object thirdparty who sell
3788
	 *  @param	Societe			$buyer				Object thirdparty who buy
3789
	 *	@return	void
3790
	 */
3791
	function formAddObjectLine($dateSelector, $seller, $buyer)
3792
	{
3793
		global $conf,$user,$langs,$object,$hookmanager;
3794
		global $form,$bcnd,$var;
3795
3796
		// Line extrafield
3797
		require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
3798
		$extrafieldsline = new ExtraFields($this->db);
3799
		$extralabelslines=$extrafieldsline->fetch_name_optionals_label($this->table_element_line);
3800
3801
		// Output template part (modules that overwrite templates must declare this into descriptor)
3802
		// Use global variables + $dateSelector + $seller and $buyer
3803
		$dirtpls=array_merge($conf->modules_parts['tpl'],array('/core/tpl'));
3804
		foreach($dirtpls as $reldir)
3805
		{
3806
			$tpl = dol_buildpath($reldir.'/objectline_create.tpl.php');
3807
			if (empty($conf->file->strict_mode)) {
3808
				$res=@include $tpl;
3809
			} else {
3810
				$res=include $tpl; // for debug
3811
			}
3812
			if ($res) break;
3813
		}
3814
	}
3815
3816
3817
3818
	/* This is to show array of line of details */
3819
3820
3821
	/**
3822
	 *	Return HTML table for object lines
3823
	 *	TODO Move this into an output class file (htmlline.class.php)
3824
	 *	If lines are into a template, title must also be into a template
3825
	 *	But for the moment we don't know if it's possible as we keep a method available on overloaded objects.
3826
	 *
3827
	 *	@param	string		$action				Action code
3828
	 *	@param  string		$seller            	Object of seller third party
3829
	 *	@param  string  	$buyer             	Object of buyer third party
3830
	 *	@param	int			$selected		   	Object line selected
3831
	 *	@param  int	    	$dateSelector      	1=Show also date range input fields
3832
	 *	@return	void
3833
	 */
3834
	function printObjectLines($action, $seller, $buyer, $selected=0, $dateSelector=0)
3835
	{
3836
		global $conf, $hookmanager, $langs, $user;
3837
		// TODO We should not use global var for this !
3838
		global $inputalsopricewithtax, $usemargins, $disableedit, $disablemove, $disableremove, $outputalsopricetotalwithtax;
3839
3840
		// Define usemargins
3841
		$usemargins=0;
3842
		if (! empty($conf->margin->enabled) && ! empty($this->element) && in_array($this->element,array('facture','propal','commande'))) $usemargins=1;
3843
3844
		$num = count($this->lines);
3845
3846
		// Line extrafield
3847
		require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
3848
		$extrafieldsline = new ExtraFields($this->db);
3849
		$extralabelslines=$extrafieldsline->fetch_name_optionals_label($this->table_element_line);
3850
3851
		$parameters = array('num'=>$num,'i'=>$i,'dateSelector'=>$dateSelector,'seller'=>$seller,'buyer'=>$buyer,'selected'=>$selected, 'extrafieldsline'=>$extrafieldsline);
0 ignored issues
show
Bug introduced by
The variable $i seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
3852
		$reshook = $hookmanager->executeHooks('printObjectLineTitle', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3853
		if (empty($reshook))
3854
		{
3855
			// Title line
3856
		    print "<thead>\n";
3857
3858
			print '<tr class="liste_titre nodrag nodrop">';
3859
3860
			// Adds a line numbering column
3861
			if (! empty($conf->global->MAIN_VIEW_LINE_NUMBER)) print '<td class="linecolnum" align="center" width="5">&nbsp;</td>';
3862
3863
			// Description
3864
			print '<td class="linecoldescription">'.$langs->trans('Description').'</td>';
3865
3866
			if ($this->element == 'supplier_proposal' || $this->element == 'order_supplier' || $this->element == 'invoice_supplier')
3867
			{
3868
				print '<td class="linerefsupplier"><span id="title_fourn_ref">'.$langs->trans("SupplierRef").'</span></td>';
3869
			}
3870
3871
			// VAT
3872
			print '<td class="linecolvat" align="right" width="80">'.$langs->trans('VAT').'</td>';
3873
3874
			// Price HT
3875
			print '<td class="linecoluht" align="right" width="80">'.$langs->trans('PriceUHT').'</td>';
3876
3877
			// Multicurrency
3878
			if (!empty($conf->multicurrency->enabled) && $this->multicurrency_code != $conf->currency) print '<td class="linecoluht_currency" align="right" width="80">'.$langs->trans('PriceUHTCurrency', $this->multicurrency_code).'</td>';
3879
3880
			if ($inputalsopricewithtax) print '<td align="right" width="80">'.$langs->trans('PriceUTTC').'</td>';
3881
3882
			// Qty
3883
			print '<td class="linecolqty" align="right">'.$langs->trans('Qty').'</td>';
3884
3885
			if($conf->global->PRODUCT_USE_UNITS)
3886
			{
3887
				print '<td class="linecoluseunit" align="left">'.$langs->trans('Unit').'</td>';
3888
			}
3889
3890
			// Reduction short
3891
			print '<td class="linecoldiscount" align="right">'.$langs->trans('ReductionShort').'</td>';
3892
3893
			if ($this->situation_cycle_ref) {
3894
				print '<td class="linecolcycleref" align="right">' . $langs->trans('Progress') . '</td>';
3895
			}
3896
3897
			if ($usemargins && ! empty($conf->margin->enabled) && empty($user->societe_id))
3898
			{
3899
				if (!empty($user->rights->margins->creer))
3900
				{
3901
					if ($conf->global->MARGIN_TYPE == "1")
3902
						print '<td class="linecolmargin1 margininfos" align="right" width="80">'.$langs->trans('BuyingPrice').'</td>';
3903
					else
3904
						print '<td class="linecolmargin1 margininfos" align="right" width="80">'.$langs->trans('CostPrice').'</td>';
3905
				}
3906
3907
				if (! empty($conf->global->DISPLAY_MARGIN_RATES) && $user->rights->margins->liretous)
3908
					print '<td class="linecolmargin2 margininfos" align="right" width="50">'.$langs->trans('MarginRate').'</td>';
3909
				if (! empty($conf->global->DISPLAY_MARK_RATES) && $user->rights->margins->liretous)
3910
					print '<td class="linecolmargin2 margininfos" align="right" width="50">'.$langs->trans('MarkRate').'</td>';
3911
			}
3912
3913
			// Total HT
3914
			print '<td class="linecolht" align="right">'.$langs->trans('TotalHTShort').'</td>';
3915
3916
			// Multicurrency
3917
			if (!empty($conf->multicurrency->enabled) && $this->multicurrency_code != $conf->currency) print '<td class="linecoltotalht_currency" align="right">'.$langs->trans('TotalHTShortCurrency', $this->multicurrency_code).'</td>';
3918
3919
			if ($outputalsopricetotalwithtax) print '<td align="right" width="80">'.$langs->trans('TotalTTCShort').'</td>';
3920
3921
			print '<td class="linecoledit"></td>';  // No width to allow autodim
3922
3923
			print '<td class="linecoldelete" width="10"></td>';
3924
3925
			print '<td class="linecolmove" width="10"></td>';
3926
3927
			if($action == 'selectlines')
3928
			{
3929
			    print '<td class="linecolcheckall" align="center">';
3930
			    print '<input type="checkbox" class="linecheckboxtoggle" />';
3931
			    print '<script type="text/javascript">$(document).ready(function() {$(".linecheckboxtoggle").click(function() {var checkBoxes = $(".linecheckbox");checkBoxes.prop("checked", this.checked);})});</script>';
3932
			    print '</td>';
3933
			}
3934
3935
			print "</tr>\n";
3936
			print "</thead>\n";
3937
		}
3938
3939
		$var = true;
3940
		$i	 = 0;
3941
3942
		print "<tbody>\n";
3943
		foreach ($this->lines as $line)
3944
		{
3945
			//Line extrafield
3946
			$line->fetch_optionals();
3947
3948
			//if (is_object($hookmanager) && (($line->product_type == 9 && ! empty($line->special_code)) || ! empty($line->fk_parent_line)))
3949
			if (is_object($hookmanager))   // Old code is commented on preceding line.
3950
			{
3951
				if (empty($line->fk_parent_line))
3952
				{
3953
					$parameters = array('line'=>$line,'var'=>$var,'num'=>$num,'i'=>$i,'dateSelector'=>$dateSelector,'seller'=>$seller,'buyer'=>$buyer,'selected'=>$selected, 'extrafieldsline'=>$extrafieldsline);
3954
					$reshook = $hookmanager->executeHooks('printObjectLine', $parameters, $this, $action);    // Note that $action and $object may have been modified by some hooks
3955
				}
3956
				else
3957
				{
3958
					$parameters = array('line'=>$line,'var'=>$var,'num'=>$num,'i'=>$i,'dateSelector'=>$dateSelector,'seller'=>$seller,'buyer'=>$buyer,'selected'=>$selected, 'extrafieldsline'=>$extrafieldsline, 'fk_parent_line'=>$line->fk_parent_line);
3959
					$reshook = $hookmanager->executeHooks('printObjectSubLine', $parameters, $this, $action);    // Note that $action and $object may have been modified by some hooks
3960
				}
3961
			}
3962
			if (empty($reshook))
3963
			{
3964
				$this->printObjectLine($action,$line,$var,$num,$i,$dateSelector,$seller,$buyer,$selected,$extrafieldsline);
3965
			}
3966
3967
			$i++;
3968
		}
3969
		print "</tbody>\n";
3970
	}
3971
3972
	/**
3973
	 *	Return HTML content of a detail line
3974
	 *	TODO Move this into an output class file (htmlline.class.php)
3975
	 *
3976
	 *	@param	string		$action				GET/POST action
3977
	 *	@param CommonObjectLine $line		       	Selected object line to output
3978
	 *	@param  string	    $var               	Is it a an odd line (true)
3979
	 *	@param  int		    $num               	Number of line (0)
3980
	 *	@param  int		    $i					I
3981
	 *	@param  int		    $dateSelector      	1=Show also date range input fields
3982
	 *	@param  string	    $seller            	Object of seller third party
3983
	 *	@param  string	    $buyer             	Object of buyer third party
3984
	 *	@param	int			$selected		   	Object line selected
3985
	 *  @param  int			$extrafieldsline	Object of extrafield line attribute
3986
	 *	@return	void
3987
	 */
3988
	function printObjectLine($action,$line,$var,$num,$i,$dateSelector,$seller,$buyer,$selected=0,$extrafieldsline=0)
3989
	{
3990
		global $conf,$langs,$user,$object,$hookmanager;
3991
		global $form,$bc,$bcdd;
3992
		global $object_rights, $disableedit, $disablemove, $disableremove;   // TODO We should not use global var for this !
3993
3994
		$object_rights = $this->getRights();
3995
3996
		$element=$this->element;
3997
3998
		$text=''; $description=''; $type=0;
3999
4000
		// Show product and description
4001
		$type=(! empty($line->product_type)?$line->product_type:$line->fk_product_type);
4002
		// Try to enhance type detection using date_start and date_end for free lines where type was not saved.
4003
		if (! empty($line->date_start)) $type=1; // deprecated
4004
		if (! empty($line->date_end)) $type=1; // deprecated
4005
4006
		// Ligne en mode visu
4007
		if ($action != 'editline' || $selected != $line->id)
4008
		{
4009
			// Product
4010
			if ($line->fk_product > 0)
4011
			{
4012
				$product_static = new Product($this->db);
4013
				$product_static->fetch($line->fk_product);
4014
4015
				$product_static->ref = $line->ref; //can change ref in hook
4016
				$product_static->label = $line->label; //can change label in hook
4017
				$text=$product_static->getNomUrl(1);
4018
4019
				// Define output language and label
4020
				if (! empty($conf->global->MAIN_MULTILANGS))
4021
				{
4022
					if (! is_object($this->thirdparty))
4023
					{
4024
						dol_print_error('','Error: Method printObjectLine was called on an object and object->fetch_thirdparty was not done before');
4025
						return;
4026
					}
4027
4028
					$prod = new Product($this->db);
4029
					$prod->fetch($line->fk_product);
4030
4031
					$outputlangs = $langs;
4032
					$newlang='';
4033
					if (empty($newlang) && GETPOST('lang_id','aZ09')) $newlang=GETPOST('lang_id','aZ09');
4034
					if (! empty($conf->global->PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE) && empty($newlang)) $newlang=$this->thirdparty->default_lang;		// For language to language of customer
4035
					if (! empty($newlang))
4036
					{
4037
						$outputlangs = new Translate("",$conf);
4038
						$outputlangs->setDefaultLang($newlang);
4039
					}
4040
4041
					$label = (! empty($prod->multilangs[$outputlangs->defaultlang]["label"])) ? $prod->multilangs[$outputlangs->defaultlang]["label"] : $line->product_label;
4042
				}
4043
				else
4044
				{
4045
					$label = $line->product_label;
4046
				}
4047
4048
				$text.= ' - '.(! empty($line->label)?$line->label:$label);
4049
				$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.
4050
			}
4051
4052
			$line->pu_ttc = price2num($line->subprice * (1 + ($line->tva_tx/100)), 'MU');
4053
4054
			// Output template part (modules that overwrite templates must declare this into descriptor)
4055
			// Use global variables + $dateSelector + $seller and $buyer
4056
			$dirtpls=array_merge($conf->modules_parts['tpl'],array('/core/tpl'));
4057
			foreach($dirtpls as $reldir)
4058
			{
4059
				$tpl = dol_buildpath($reldir.'/objectline_view.tpl.php');
4060
				if (empty($conf->file->strict_mode)) {
4061
					$res=@include $tpl;
4062
				} else {
4063
					$res=include $tpl; // for debug
4064
				}
4065
				if ($res) break;
4066
			}
4067
		}
4068
4069
		// Ligne en mode update
4070
		if ($this->statut == 0 && $action == 'editline' && $selected == $line->id)
4071
		{
4072
			$label = (! empty($line->label) ? $line->label : (($line->fk_product > 0) ? $line->product_label : ''));
4073
			$placeholder=' placeholder="'.$langs->trans("Label").'"';
4074
4075
			$line->pu_ttc = price2num($line->subprice * (1 + ($line->tva_tx/100)), 'MU');
4076
4077
			// Output template part (modules that overwrite templates must declare this into descriptor)
4078
			// Use global variables + $dateSelector + $seller and $buyer
4079
			$dirtpls=array_merge($conf->modules_parts['tpl'],array('/core/tpl'));
4080
			foreach($dirtpls as $reldir)
4081
			{
4082
				$tpl = dol_buildpath($reldir.'/objectline_edit.tpl.php');
4083
				if (empty($conf->file->strict_mode)) {
4084
					$res=@include $tpl;
4085
				} else {
4086
					$res=include $tpl; // for debug
4087
				}
4088
				if ($res) break;
4089
			}
4090
		}
4091
	}
4092
4093
4094
	/* This is to show array of line of details of source object */
4095
4096
4097
	/**
4098
	 * 	Return HTML table table of source object lines
4099
	 *  TODO Move this and previous function into output html class file (htmlline.class.php).
4100
	 *  If lines are into a template, title must also be into a template
4101
	 *  But for the moment we don't know if it's possible, so we keep the method available on overloaded objects.
4102
	 *
4103
	 *	@param	string		$restrictlist		''=All lines, 'services'=Restrict to services only
4104
	 *  @return	void
4105
	 */
4106
	function printOriginLinesList($restrictlist='')
4107
	{
4108
		global $langs, $hookmanager, $conf;
4109
4110
		print '<tr class="liste_titre">';
4111
		print '<td>'.$langs->trans('Ref').'</td>';
4112
		print '<td>'.$langs->trans('Description').'</td>';
4113
		print '<td align="right">'.$langs->trans('VATRate').'</td>';
4114
		print '<td align="right">'.$langs->trans('PriceUHT').'</td>';
4115
		if (!empty($conf->multicurrency->enabled)) print '<td align="right">'.$langs->trans('PriceUHTCurrency').'</td>';
4116
		print '<td align="right">'.$langs->trans('Qty').'</td>';
4117
		if($conf->global->PRODUCT_USE_UNITS)
4118
		{
4119
			print '<td align="left">'.$langs->trans('Unit').'</td>';
4120
		}
4121
		print '<td align="right">'.$langs->trans('ReductionShort').'</td></tr>';
4122
4123
		$var = true;
4124
		$i	 = 0;
4125
4126
		if (! empty($this->lines))
4127
		{
4128
			foreach ($this->lines as $line)
4129
			{
4130
				if (is_object($hookmanager) && (($line->product_type == 9 && ! empty($line->special_code)) || ! empty($line->fk_parent_line)))
4131
				{
4132
					if (empty($line->fk_parent_line))
4133
					{
4134
						$parameters=array('line'=>$line,'var'=>$var,'i'=>$i);
4135
						$action='';
4136
						$hookmanager->executeHooks('printOriginObjectLine',$parameters,$this,$action);    // Note that $action and $object may have been modified by some hooks
4137
					}
4138
				}
4139
				else
4140
				{
4141
					$this->printOriginLine($line, $var, $restrictlist);
4142
				}
4143
4144
				$i++;
4145
			}
4146
		}
4147
	}
4148
4149
	/**
4150
	 * 	Return HTML with a line of table array of source object lines
4151
	 *  TODO Move this and previous function into output html class file (htmlline.class.php).
4152
	 *  If lines are into a template, title must also be into a template
4153
	 *  But for the moment we don't know if it's possible as we keep a method available on overloaded objects.
4154
	 *
4155
	 * 	@param	CommonObjectLine	$line				Line
4156
	 * 	@param	string				$var				Var
4157
	 *	@param	string				$restrictlist		''=All lines, 'services'=Restrict to services only (strike line if not)
4158
	 * 	@return	void
4159
	 */
4160
	function printOriginLine($line, $var, $restrictlist='')
4161
	{
4162
		global $langs, $conf;
4163
4164
		//var_dump($line);
4165
		if (!empty($line->date_start))
4166
		{
4167
			$date_start=$line->date_start;
4168
		}
4169
		else
4170
		{
4171
			$date_start=$line->date_debut_prevue;
4172
			if ($line->date_debut_reel) $date_start=$line->date_debut_reel;
4173
		}
4174
		if (!empty($line->date_end))
4175
		{
4176
			$date_end=$line->date_end;
4177
		}
4178
		else
4179
		{
4180
			$date_end=$line->date_fin_prevue;
4181
			if ($line->date_fin_reel) $date_end=$line->date_fin_reel;
4182
		}
4183
4184
		$this->tpl['label'] = '';
4185
		if (! empty($line->fk_parent_line)) $this->tpl['label'].= img_picto('', 'rightarrow');
4186
4187
		if (($line->info_bits & 2) == 2)  // TODO Not sure this is used for source object
4188
		{
4189
			$discount=new DiscountAbsolute($this->db);
4190
			$discount->fk_soc = $this->socid;
4191
			$this->tpl['label'].= $discount->getNomUrl(0,'discount');
4192
		}
4193
		else if (! empty($line->fk_product))
4194
		{
4195
			$productstatic = new Product($this->db);
4196
			$productstatic->id = $line->fk_product;
4197
			$productstatic->ref = $line->ref;
4198
			$productstatic->type = $line->fk_product_type;
4199
			$this->tpl['label'].= $productstatic->getNomUrl(1);
4200
			$this->tpl['label'].= ' - '.(! empty($line->label)?$line->label:$line->product_label);
4201
			// Dates
4202
			if ($line->product_type == 1 && ($date_start || $date_end))
4203
			{
4204
				$this->tpl['label'].= get_date_range($date_start,$date_end);
4205
			}
4206
		}
4207
		else
4208
		{
4209
			$this->tpl['label'].= ($line->product_type == -1 ? '&nbsp;' : ($line->product_type == 1 ? img_object($langs->trans(''),'service') : img_object($langs->trans(''),'product')));
4210
			if (!empty($line->desc)) {
4211
				$this->tpl['label'].=$line->desc;
4212
			}else {
4213
				$this->tpl['label'].= ($line->label ? '&nbsp;'.$line->label : '');
4214
			}
4215
			// Dates
4216
			if ($line->product_type == 1 && ($date_start || $date_end))
4217
			{
4218
				$this->tpl['label'].= get_date_range($date_start,$date_end);
4219
			}
4220
		}
4221
4222
		if (! empty($line->desc))
4223
		{
4224
			if ($line->desc == '(CREDIT_NOTE)')  // TODO Not sure this is used for source object
4225
			{
4226
				$discount=new DiscountAbsolute($this->db);
4227
				$discount->fetch($line->fk_remise_except);
4228
				$this->tpl['description'] = $langs->transnoentities("DiscountFromCreditNote",$discount->getNomUrl(0));
4229
			}
4230
			elseif ($line->desc == '(DEPOSIT)')  // TODO Not sure this is used for source object
4231
			{
4232
				$discount=new DiscountAbsolute($this->db);
4233
				$discount->fetch($line->fk_remise_except);
4234
				$this->tpl['description'] = $langs->transnoentities("DiscountFromDeposit",$discount->getNomUrl(0));
4235
			}
4236
			elseif ($line->desc == '(EXCESS RECEIVED)')
4237
			{
4238
				$discount=new DiscountAbsolute($this->db);
4239
				$discount->fetch($line->fk_remise_except);
4240
				$this->tpl['description'] = $langs->transnoentities("DiscountFromExcessReceived",$discount->getNomUrl(0));
4241
			}
4242
			elseif ($line->desc == '(EXCESS PAID)')
4243
			{
4244
				$discount=new DiscountAbsolute($this->db);
4245
				$discount->fetch($line->fk_remise_except);
4246
				$this->tpl['description'] = $langs->transnoentities("DiscountFromExcessPaid",$discount->getNomUrl(0));
4247
			}
4248
			else
4249
			{
4250
				$this->tpl['description'] = dol_trunc($line->desc,60);
4251
			}
4252
		}
4253
		else
4254
		{
4255
			$this->tpl['description'] = '&nbsp;';
4256
		}
4257
4258
        // VAT Rate
4259
        $this->tpl['vat_rate'] = vatrate($line->tva_tx, true);
4260
        $this->tpl['vat_rate'] .= (($line->info_bits & 1) == 1) ? '*' : '';
4261
        if (! empty($line->vat_src_code) && ! preg_match('/\(/', $this->tpl['vat_rate'])) $this->tpl['vat_rate'].=' ('.$line->vat_src_code.')';
4262
4263
		$this->tpl['price'] = price($line->subprice);
4264
		$this->tpl['multicurrency_price'] = price($line->multicurrency_subprice);
4265
		$this->tpl['qty'] = (($line->info_bits & 2) != 2) ? $line->qty : '&nbsp;';
4266
		if ($conf->global->PRODUCT_USE_UNITS) $this->tpl['unit'] = $langs->transnoentities($line->getLabelOfUnit('long'));
4267
		$this->tpl['remise_percent'] = (($line->info_bits & 2) != 2) ? vatrate($line->remise_percent, true) : '&nbsp;';
4268
4269
		// Is the line strike or not
4270
		$this->tpl['strike']=0;
4271
		if ($restrictlist == 'services' && $line->product_type != Product::TYPE_SERVICE) $this->tpl['strike']=1;
4272
4273
		// Output template part (modules that overwrite templates must declare this into descriptor)
4274
		// Use global variables + $dateSelector + $seller and $buyer
4275
		$dirtpls=array_merge($conf->modules_parts['tpl'],array('/core/tpl'));
4276
		foreach($dirtpls as $reldir)
4277
		{
4278
			$tpl = dol_buildpath($reldir.'/originproductline.tpl.php');
4279
			if (empty($conf->file->strict_mode)) {
4280
				$res=@include $tpl;
4281
			} else {
4282
				$res=include $tpl; // for debug
4283
			}
4284
			if ($res) break;
4285
		}
4286
	}
4287
4288
4289
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4290
	/**
4291
	 *	Add resources to the current object : add entry into llx_element_resources
4292
	 *	Need $this->element & $this->id
4293
	 *
4294
	 *	@param		int		$resource_id		Resource id
4295
	 *	@param		string	$resource_type		'resource'
4296
	 *	@param		int		$busy				Busy or not
4297
	 *	@param		int		$mandatory			Mandatory or not
4298
	 *	@return		int							<=0 if KO, >0 if OK
4299
	 */
4300
	function add_element_resource($resource_id, $resource_type, $busy=0, $mandatory=0)
4301
	{
4302
        // phpcs:enable
4303
		$this->db->begin();
4304
4305
		$sql = "INSERT INTO ".MAIN_DB_PREFIX."element_resources (";
4306
		$sql.= "resource_id";
4307
		$sql.= ", resource_type";
4308
		$sql.= ", element_id";
4309
		$sql.= ", element_type";
4310
		$sql.= ", busy";
4311
		$sql.= ", mandatory";
4312
		$sql.= ") VALUES (";
4313
		$sql.= $resource_id;
4314
		$sql.= ", '".$this->db->escape($resource_type)."'";
4315
		$sql.= ", '".$this->db->escape($this->id)."'";
4316
		$sql.= ", '".$this->db->escape($this->element)."'";
4317
		$sql.= ", '".$this->db->escape($busy)."'";
4318
		$sql.= ", '".$this->db->escape($mandatory)."'";
4319
		$sql.= ")";
4320
4321
		dol_syslog(get_class($this)."::add_element_resource", LOG_DEBUG);
4322
		if ($this->db->query($sql))
4323
		{
4324
			$this->db->commit();
4325
			return 1;
4326
		}
4327
		else
4328
		{
4329
			$this->error=$this->db->lasterror();
4330
			$this->db->rollback();
4331
			return  0;
4332
		}
4333
	}
4334
4335
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4336
	/**
4337
	 *    Delete a link to resource line
4338
	 *
4339
	 *    @param	int		$rowid			Id of resource line to delete
4340
	 *    @param	int		$element		element name (for trigger) TODO: use $this->element into commonobject class
4341
	 *    @param	int		$notrigger		Disable all triggers
4342
	 *    @return   int						>0 if OK, <0 if KO
4343
	 */
4344
	function delete_resource($rowid, $element, $notrigger=0)
4345
	{
4346
        // phpcs:enable
4347
		global $user;
4348
4349
		$this->db->begin();
4350
4351
		$sql = "DELETE FROM ".MAIN_DB_PREFIX."element_resources";
4352
		$sql.= " WHERE rowid=".$rowid;
4353
4354
		dol_syslog(get_class($this)."::delete_resource", LOG_DEBUG);
4355
4356
		$resql=$this->db->query($sql);
4357
		if (! $resql)
4358
		{
4359
			$this->error=$this->db->lasterror();
4360
			$this->db->rollback();
4361
			return -1;
4362
		}
4363
		else
4364
		{
4365
			if (! $notrigger)
4366
			{
4367
				$result=$this->call_trigger(strtoupper($element).'_DELETE_RESOURCE', $user);
4368
				if ($result < 0) { $this->db->rollback(); return -1; }
4369
			}
4370
			$this->db->commit();
4371
			return 1;
4372
		}
4373
	}
4374
4375
4376
	/**
4377
	 * Overwrite magic function to solve problem of cloning object that are kept as references
4378
	 *
4379
	 * @return void
4380
	 */
4381
	function __clone()
4382
	{
4383
		// Force a copy of this->lines, otherwise it will point to same object.
4384
		if (isset($this->lines) && is_array($this->lines))
4385
		{
4386
			$nboflines=count($this->lines);
4387
			for($i=0; $i < $nboflines; $i++)
4388
			{
4389
				$this->lines[$i] = clone $this->lines[$i];
4390
			}
4391
		}
4392
	}
4393
4394
	/**
4395
	 * Common function for all objects extending CommonObject for generating documents
4396
	 *
4397
	 * @param 	string 		$modelspath 	Relative folder where generators are placed
4398
	 * @param 	string 		$modele 		Generator to use. Caller must set it to obj->modelpdf or GETPOST('modelpdf') for example.
4399
	 * @param 	Translate 	$outputlangs 	Output language to use
4400
	 * @param 	int 		$hidedetails 	1 to hide details. 0 by default
4401
	 * @param 	int 		$hidedesc 		1 to hide product description. 0 by default
4402
	 * @param 	int 		$hideref 		1 to hide product reference. 0 by default
4403
	 * @param   null|array  $moreparams     Array to provide more information
4404
	 * @return 	int 						>0 if OK, <0 if KO
4405
	 * @see	addFileIntoDatabaseIndex
4406
	 */
4407
	protected function commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
4408
	{
4409
		global $conf, $langs, $user;
4410
4411
		$srctemplatepath='';
4412
4413
		// Increase limit for PDF build
4414
		$err=error_reporting();
4415
		error_reporting(0);
4416
		@set_time_limit(120);
4417
		error_reporting($err);
4418
4419
		// If selected model is a filename template (then $modele="modelname" or "modelname:filename")
4420
		$tmp=explode(':',$modele,2);
4421
		if (! empty($tmp[1]))
4422
		{
4423
			$modele=$tmp[0];
4424
			$srctemplatepath=$tmp[1];
4425
		}
4426
4427
		// Search template files
4428
		$file=''; $classname=''; $filefound=0;
4429
		$dirmodels=array('/');
4430
		if (is_array($conf->modules_parts['models'])) $dirmodels=array_merge($dirmodels,$conf->modules_parts['models']);
4431
		foreach($dirmodels as $reldir)
4432
		{
4433
			foreach(array('doc','pdf') as $prefix)
4434
			{
4435
				if (in_array(get_class($this), array('Adherent'))) $file = $prefix."_".$modele.".class.php";     // Member module use prefix_module.class.php
4436
				else $file = $prefix."_".$modele.".modules.php";
4437
4438
				// On verifie l'emplacement du modele
4439
				$file=dol_buildpath($reldir.$modelspath.$file,0);
4440
				if (file_exists($file))
4441
				{
4442
					$filefound=1;
4443
					$classname=$prefix.'_'.$modele;
4444
					break;
4445
				}
4446
			}
4447
			if ($filefound) break;
4448
		}
4449
4450
		// If generator was found
4451
		if ($filefound)
4452
		{
4453
			global $db;  // Required to solve a conception default in commonstickergenerator.class.php making an include of code using $db
4454
4455
			require_once $file;
4456
4457
			$obj = new $classname($this->db);
4458
4459
			// If generator is ODT, we must have srctemplatepath defined, if not we set it.
4460
			if ($obj->type == 'odt' && empty($srctemplatepath))
4461
			{
4462
				$varfortemplatedir=$obj->scandir;
4463
				if ($varfortemplatedir && ! empty($conf->global->$varfortemplatedir))
4464
				{
4465
					$dirtoscan=$conf->global->$varfortemplatedir;
4466
4467
					$listoffiles=array();
4468
4469
					// Now we add first model found in directories scanned
4470
					$listofdir=explode(',',$dirtoscan);
4471
					foreach($listofdir as $key => $tmpdir)
4472
					{
4473
						$tmpdir=trim($tmpdir);
4474
						$tmpdir=preg_replace('/DOL_DATA_ROOT/',DOL_DATA_ROOT,$tmpdir);
4475
						if (! $tmpdir) { unset($listofdir[$key]); continue; }
4476
						if (is_dir($tmpdir))
4477
						{
4478
							$tmpfiles=dol_dir_list($tmpdir,'files',0,'\.od(s|t)$','','name',SORT_ASC,0);
4479
							if (count($tmpfiles)) $listoffiles=array_merge($listoffiles,$tmpfiles);
4480
						}
4481
					}
4482
4483
					if (count($listoffiles))
4484
					{
4485
						foreach($listoffiles as $record)
4486
						{
4487
							$srctemplatepath=$record['fullname'];
4488
							break;
4489
						}
4490
					}
4491
				}
4492
4493
				if (empty($srctemplatepath))
4494
				{
4495
					$this->error='ErrorGenerationAskedForOdtTemplateWithSrcFileNotDefined';
4496
					return -1;
4497
				}
4498
			}
4499
4500
			if ($obj->type == 'odt' && ! empty($srctemplatepath))
4501
			{
4502
				if (! dol_is_file($srctemplatepath))
4503
				{
4504
					$this->error='ErrorGenerationAskedForOdtTemplateWithSrcFileNotFound';
4505
					return -1;
4506
				}
4507
			}
4508
4509
			// We save charset_output to restore it because write_file can change it if needed for
4510
			// output format that does not support UTF8.
4511
			$sav_charset_output=$outputlangs->charset_output;
4512
4513
			if (in_array(get_class($this), array('Adherent')))
4514
			{
4515
				$arrayofrecords = array();   // The write_file of templates of adherent class need this var
4516
				$resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, 'member', 1, $moreparams);
4517
			}
4518
			else
4519
			{
4520
				$resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, $hidedetails, $hidedesc, $hideref, $moreparams);
4521
			}
4522
			// After call of write_file $obj->result['fullpath'] is set with generated file. It will be used to update the ECM database index.
4523
4524
			if ($resultwritefile > 0)
4525
			{
4526
				$outputlangs->charset_output=$sav_charset_output;
4527
4528
				// We delete old preview
4529
				require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
4530
				dol_delete_preview($this);
4531
4532
				// Index file in database
4533
				if (! empty($obj->result['fullpath']))
4534
				{
4535
					$destfull = $obj->result['fullpath'];
4536
					$upload_dir = dirname($destfull);
4537
					$destfile = basename($destfull);
4538
					$rel_dir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT,'/').'/', '', $upload_dir);
4539
4540
					if (! preg_match('/[\\/]temp[\\/]|[\\/]thumbs|\.meta$/', $rel_dir))     // If not a tmp dir
4541
					{
4542
						$filename = basename($destfile);
4543
						$rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
4544
						$rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
4545
4546
						include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
4547
						$ecmfile=new EcmFiles($this->db);
4548
						$result = $ecmfile->fetch(0, '', ($rel_dir?$rel_dir.'/':'').$filename);
4549
4550
						// Set the public "share" key
4551
						$setsharekey = false;
4552
						if ($this->element == 'propal')
4553
						{
4554
							$useonlinesignature = $conf->global->MAIN_FEATURES_LEVEL;	// Replace this with 1 when feature to make online signature is ok
4555
							if ($useonlinesignature) $setsharekey=true;
4556
							if (! empty($conf->global->PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD)) $setsharekey=true;
4557
						}
4558
						if ($this->element == 'commande'     && ! empty($conf->global->ORDER_ALLOW_EXTERNAL_DOWNLOAD))        $setsharekey=true;
4559
						if ($this->element == 'facture'      && ! empty($conf->global->INVOICE_ALLOW_EXTERNAL_DOWNLOAD))      $setsharekey=true;
4560
						if ($this->element == 'bank_account' && ! empty($conf->global->BANK_ACCOUNT_ALLOW_EXTERNAL_DOWNLOAD)) $setsharekey=true;
4561
4562
						if ($setsharekey)
4563
						{
4564
							if (empty($ecmfile->share))	// Because object not found or share not set yet
4565
							{
4566
								require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
4567
								$ecmfile->share = getRandomPassword(true);
4568
							}
4569
						}
4570
4571
						if ($result > 0)
4572
						{
4573
							$ecmfile->label = md5_file(dol_osencode($destfull));	// hash of file content
4574
							$ecmfile->fullpath_orig = '';
4575
							$ecmfile->gen_or_uploaded = 'generated';
4576
							$ecmfile->description = '';    // indexed content
4577
							$ecmfile->keyword = '';        // keyword content
4578
							$result = $ecmfile->update($user);
4579
							if ($result < 0)
4580
							{
4581
								setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
4582
							}
4583
						}
4584
						else
4585
						{
4586
							$ecmfile->entity = $conf->entity;
4587
							$ecmfile->filepath = $rel_dir;
4588
							$ecmfile->filename = $filename;
4589
							$ecmfile->label = md5_file(dol_osencode($destfull));	// hash of file content
4590
							$ecmfile->fullpath_orig = '';
4591
							$ecmfile->gen_or_uploaded = 'generated';
4592
							$ecmfile->description = '';    // indexed content
4593
							$ecmfile->keyword = '';        // keyword content
4594
							$ecmfile->src_object_type = $this->table_element;
4595
							$ecmfile->src_object_id   = $this->id;
4596
4597
							$result = $ecmfile->create($user);
4598
							if ($result < 0)
4599
							{
4600
								setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
4601
							}
4602
						}
4603
4604
						/*$this->result['fullname']=$destfull;
4605
						$this->result['filepath']=$ecmfile->filepath;
4606
						$this->result['filename']=$ecmfile->filename;*/
4607
						//var_dump($obj->update_main_doc_field);exit;
4608
4609
						// Update the last_main_doc field into main object (if documenent generator has property ->update_main_doc_field set)
4610
						$update_main_doc_field=0;
4611
						if (! empty($obj->update_main_doc_field)) $update_main_doc_field=1;
4612
						if ($update_main_doc_field && ! empty($this->table_element))
4613
						{
4614
							$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element." SET last_main_doc = '".($ecmfile->filepath.'/'.$ecmfile->filename)."'";
4615
							$sql.= ' WHERE rowid = '.$this->id;
4616
							$resql = $this->db->query($sql);
4617
							if (! $resql) dol_print_error($this->db);
4618
						}
4619
					}
4620
				}
4621
				else
4622
				{
4623
					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);
4624
				}
4625
4626
				// Success in building document. We build meta file.
4627
				dol_meta_create($this);
4628
4629
				return 1;
4630
			}
4631
			else
4632
			{
4633
				$outputlangs->charset_output=$sav_charset_output;
4634
				dol_print_error($this->db, "Error generating document for ".__CLASS__.". Error: ".$obj->error, $obj->errors);
4635
				return -1;
4636
			}
4637
		}
4638
		else
4639
		{
4640
			$this->error=$langs->trans("Error")." ".$langs->trans("ErrorFileDoesNotExists",$file);
4641
			dol_print_error('',$this->error);
4642
			return -1;
4643
		}
4644
	}
4645
4646
	/**
4647
	 *  Build thumb
4648
	 *  @TODO Move this into files.lib.php
4649
	 *
4650
	 *  @param      string	$file           Path file in UTF8 to original file to create thumbs from.
4651
	 *	@return		void
4652
	 */
4653
	function addThumbs($file)
4654
	{
4655
		global $maxwidthsmall, $maxheightsmall, $maxwidthmini, $maxheightmini, $quality;
4656
4657
		require_once DOL_DOCUMENT_ROOT .'/core/lib/images.lib.php';		// This define also $maxwidthsmall, $quality, ...
4658
4659
		$file_osencoded=dol_osencode($file);
4660
		if (file_exists($file_osencoded))
4661
		{
4662
			// Create small thumbs for company (Ratio is near 16/9)
4663
			// Used on logon for example
4664
			vignette($file_osencoded, $maxwidthsmall, $maxheightsmall, '_small', $quality);
4665
4666
			// Create mini thumbs for company (Ratio is near 16/9)
4667
			// Used on menu or for setup page for example
4668
			vignette($file_osencoded, $maxwidthmini, $maxheightmini, '_mini', $quality);
4669
		}
4670
	}
4671
4672
4673
	/* Functions common to commonobject and commonobjectline */
4674
4675
	/* For default values */
4676
4677
	/**
4678
	 * Return the default value to use for a field when showing the create form of object.
4679
	 * Return values in this order:
4680
	 * 1) If parameter is available into POST, we return it first.
4681
	 * 2) If not but an alternate value was provided as parameter of function, we return it.
4682
	 * 3) If not but a constant $conf->global->OBJECTELEMENT_FIELDNAME is set, we return it (It is better to use the dedicated table).
4683
	 * 4) Return value found into database (TODO No yet implemented)
4684
	 *
4685
	 * @param   string              $fieldname          Name of field
4686
	 * @param   string              $alternatevalue     Alternate value to use
4687
	 * @return  string|string[]                         Default value (can be an array if the GETPOST return an array)
4688
	 **/
4689
	function getDefaultCreateValueFor($fieldname, $alternatevalue=null)
4690
	{
4691
		global $conf, $_POST;
4692
4693
		// If param here has been posted, we use this value first.
4694
		if (isset($_POST[$fieldname])) return GETPOST($fieldname, 2);
4695
4696
		if (isset($alternatevalue)) return $alternatevalue;
4697
4698
		$newelement=$this->element;
4699
		if ($newelement == 'facture') $newelement='invoice';
4700
		if ($newelement == 'commande') $newelement='order';
4701
		if (empty($newelement))
4702
		{
4703
			dol_syslog("Ask a default value using common method getDefaultCreateValueForField on an object with no property ->element defined. Return empty string.", LOG_WARNING);
4704
			return '';
4705
		}
4706
4707
		$keyforfieldname=strtoupper($newelement.'_DEFAULT_'.$fieldname);
4708
		//var_dump($keyforfieldname);
4709
		if (isset($conf->global->$keyforfieldname)) return $conf->global->$keyforfieldname;
4710
4711
		// TODO Ad here a scan into table llx_overwrite_default with a filter on $this->element and $fieldname
4712
	}
4713
4714
4715
	/* For triggers */
4716
4717
4718
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4719
	/**
4720
	 * Call trigger based on this instance.
4721
	 * Some context information may also be provided into array property this->context.
4722
	 * NB:  Error from trigger are stacked in interface->errors
4723
	 * NB2: If return code of triggers are < 0, action calling trigger should cancel all transaction.
4724
	 *
4725
	 * @param   string    $trigger_name   trigger's name to execute
4726
	 * @param   User      $user           Object user
4727
	 * @return  int                       Result of run_triggers
4728
	 */
4729
	function call_trigger($trigger_name, $user)
4730
	{
4731
        // phpcs:enable
4732
		global $langs,$conf;
4733
4734
		include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
4735
		$interface=new Interfaces($this->db);
4736
		$result=$interface->run_triggers($trigger_name,$this,$user,$langs,$conf);
4737
4738
		if ($result < 0)
4739
		{
4740
			if (!empty($this->errors))
4741
			{
4742
				$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.
4743
			}
4744
			else
4745
			{
4746
				$this->errors=$interface->errors;
4747
			}
4748
		}
4749
		return $result;
4750
	}
4751
4752
4753
	/* Functions for extrafields */
4754
4755
4756
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4757
	/**
4758
	 *  Function to get extra fields of an object into $this->array_options
4759
	 *  This method is in most cases called by method fetch of objects but you can call it separately.
4760
	 *
4761
	 *  @param	int		$rowid			Id of line. Use the id of object if not defined. Deprecated. Function must be called without parameters.
4762
	 *  @param  array	$optionsArray   Array resulting of call of extrafields->fetch_name_optionals_label(). Deprecated. Function must be called without parameters.
4763
	 *  @return	int						<0 if error, 0 if no values of extrafield to find nor found, 1 if an attribute is found and value loaded
4764
	 */
4765
	function fetch_optionals($rowid=null, $optionsArray=null)
4766
	{
4767
        // phpcs:enable
4768
		if (empty($rowid)) $rowid=$this->id;
4769
4770
		// To avoid SQL errors. Probably not the better solution though
4771
		if (!$this->table_element) {
4772
			return 0;
4773
		}
4774
4775
		$this->array_options=array();
4776
4777
		if (! is_array($optionsArray))
4778
		{
4779
			// If $extrafields is not a known object, we initialize it. Best practice is to have $extrafields defined into card.php or list.php page.
4780
			// TODO Use of existing $extrafield is not yet ready (must mutualize code that use extrafields in form first)
4781
			// global $extrafields;
4782
			//if (! is_object($extrafields))
4783
			//{
4784
				require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
4785
				$extrafields = new ExtraFields($this->db);
4786
			//}
4787
4788
			// Load array of extrafields for elementype = $this->table_element
4789
			if (empty($extrafields->attributes[$this->table_element]['loaded']))
4790
			{
4791
				$extrafields->fetch_name_optionals_label($this->table_element);
4792
			}
4793
			$optionsArray = (! empty($extrafields->attributes[$this->table_element]['label'])?$extrafields->attributes[$this->table_element]['label']:null);
4794
		}
4795
		else
4796
		{
4797
			global $extrafields;
4798
			dol_syslog("Warning: fetch_optionals was called with param optionsArray defined when you should pass null now", LOG_WARNING);
4799
		}
4800
4801
		$table_element = $this->table_element;
4802
		if ($table_element == 'categorie') $table_element = 'categories'; // For compatibility
4803
4804
		// Request to get complementary values
4805
		if (is_array($optionsArray) && count($optionsArray) > 0)
4806
		{
4807
			$sql = "SELECT rowid";
4808
			foreach ($optionsArray as $name => $label)
4809
			{
4810
				if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || $extrafields->attributes[$this->table_element]['type'][$name] != 'separate')
4811
				{
4812
					$sql.= ", ".$name;
4813
				}
4814
			}
4815
			$sql.= " FROM ".MAIN_DB_PREFIX.$table_element."_extrafields";
4816
			$sql.= " WHERE fk_object = ".$rowid;
4817
4818
			//dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG);		// Too verbose
4819
			$resql=$this->db->query($sql);
4820
			if ($resql)
4821
			{
4822
				$this->array_options = array();
4823
				$numrows=$this->db->num_rows($resql);
4824
				if ($numrows)
4825
				{
4826
					$tab = $this->db->fetch_array($resql);
4827
4828
					foreach ($tab as $key => $value)
4829
					{
4830
						// 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)
4831
						if ($key != 'rowid' && $key != 'tms' && $key != 'fk_member' && ! is_int($key))
4832
						{
4833
							// we can add this attribute to object
4834
							if (! empty($extrafields) && in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date','datetime')))
4835
							{
4836
								//var_dump($extrafields->attributes[$this->table_element]['type'][$key]);
4837
								$this->array_options["options_".$key]=$this->db->jdate($value);
4838
							}
4839
							else
4840
							{
4841
								$this->array_options["options_".$key]=$value;
4842
							}
4843
4844
							//var_dump('key '.$key.' '.$value.' type='.$extrafields->attributes[$this->table_element]['type'][$key].' '.$this->array_options["options_".$key]);
4845
						}
4846
					}
4847
				}
4848
4849
				$this->db->free($resql);
4850
4851
				if ($numrows) return $numrows;
4852
				else return 0;
4853
			}
4854
			else
4855
			{
4856
				dol_print_error($this->db);
4857
				return -1;
4858
			}
4859
		}
4860
		return 0;
4861
	}
4862
4863
	/**
4864
	 *	Delete all extra fields values for the current object.
4865
	 *
4866
	 *  @return	int		<0 if KO, >0 if OK
4867
	 */
4868
	function deleteExtraFields()
4869
	{
4870
		$this->db->begin();
4871
4872
		$table_element = $this->table_element;
4873
		if ($table_element == 'categorie') $table_element = 'categories'; // For compatibility
4874
4875
		$sql_del = "DELETE FROM ".MAIN_DB_PREFIX.$table_element."_extrafields WHERE fk_object = ".$this->id;
4876
		dol_syslog(get_class($this)."::deleteExtraFields delete", LOG_DEBUG);
4877
		$resql=$this->db->query($sql_del);
4878
		if (! $resql)
4879
		{
4880
			$this->error=$this->db->lasterror();
4881
			$this->db->rollback();
4882
			return -1;
4883
		}
4884
		else
4885
		{
4886
			$this->db->commit();
4887
			return 1;
4888
		}
4889
	}
4890
4891
	/**
4892
	 *	Add/Update all extra fields values for the current object.
4893
	 *  Data to describe values to insert/update are stored into $this->array_options=array('options_codeforfield1'=>'valueforfield1', 'options_codeforfield2'=>'valueforfield2', ...)
4894
	 *  This function delete record with all extrafields and insert them again from the array $this->array_options.
4895
	 *
4896
	 *  @param	string		$trigger		If defined, call also the trigger (for example COMPANY_MODIFY)
4897
	 *  @param	User		$userused		Object user
4898
	 *  @return int 						-1=error, O=did nothing, 1=OK
4899
	 *  @see updateExtraField, setValueFrom
4900
	 */
4901
	function insertExtraFields($trigger='', $userused=null)
4902
	{
4903
		global $conf,$langs,$user;
4904
4905
		if (empty($userused)) $userused=$user;
4906
4907
		$error=0;
4908
4909
		if (! empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) return 0;	// For avoid conflicts if trigger used
4910
4911
		if (! empty($this->array_options))
4912
		{
4913
			// Check parameters
4914
			$langs->load('admin');
4915
			require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
4916
			$extrafields = new ExtraFields($this->db);
4917
			$target_extrafields=$extrafields->fetch_name_optionals_label($this->table_element);
4918
4919
			//Eliminate copied source object extra_fields that do not exist in target object
4920
			$new_array_options=array();
4921
			foreach ($this->array_options as $key => $value) {
4922
				if (in_array(substr($key,8), array_keys($target_extrafields)))	// We remove the 'options_' from $key for test
4923
					$new_array_options[$key] = $value;
4924
				elseif (in_array($key, array_keys($target_extrafields)))		// We test on $key that does not contains the 'options_' prefix
4925
					$new_array_options['options_'.$key] = $value;
4926
			}
4927
4928
			foreach($new_array_options as $key => $value)
4929
			{
4930
			   	$attributeKey      = substr($key,8);   // Remove 'options_' prefix
4931
			   	$attributeType     = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
4932
			   	$attributeLabel    = $extrafields->attributes[$this->table_element]['label'][$attributeKey];
4933
			   	$attributeParam    = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
4934
			   	$attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
4935
4936
			   	if ($attributeRequired)
4937
			   	{
4938
			   		$mandatorypb=false;
4939
			   		if ($attributeType == 'link' && $this->array_options[$key] == '-1') $mandatorypb=true;
4940
			   		if ($this->array_options[$key] === '') $mandatorypb=true;
4941
			   		if ($mandatorypb)
4942
			   		{
4943
			   			$this->errors[]=$langs->trans('ErrorFieldRequired', $attributeLabel);
4944
			   			return -1;
4945
			   		}
4946
			   	}
4947
4948
				//dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
4949
				//dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
4950
4951
			   	switch ($attributeType)
4952
			   	{
4953
			   		case 'int':
4954
			  			if (!is_numeric($value) && $value!='')
4955
			   			{
4956
			   				$this->errors[]=$langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
4957
			   				return -1;
4958
			  			}
4959
			   			elseif ($value=='')
4960
			   			{
4961
			   				$new_array_options[$key] = null;
4962
			   			}
4963
			 			break;
4964
					case 'double':
4965
						$value = price2num($value);
4966
						if (!is_numeric($value) && $value!='')
4967
						{
4968
							dol_syslog($langs->trans("ExtraFieldHasWrongValue")." sur ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
4969
							$this->errors[]=$langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
4970
							return -1;
4971
						}
4972
						elseif ($value=='')
4973
						{
4974
							$new_array_options[$key] = null;
4975
						}
4976
						//dol_syslog("double value"." sur ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
4977
						$new_array_options[$key] = $value;
4978
						break;
4979
			 		/*case 'select':	// Not required, we chosed value='0' for undefined values
4980
             			if ($value=='-1')
4981
             			{
4982
             				$this->array_options[$key] = null;
4983
             			}
4984
             			break;*/
4985
			   		case 'password':
4986
			   			$algo='';
4987
			   			if ($this->array_options[$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']))
4988
			   			{
4989
			   				// If there is an encryption choice, we use it to crypt data before insert
4990
			   				$tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
4991
			   				$algo=reset($tmparrays);
4992
			   				if ($algo != '')
4993
			   				{
4994
			   					//global $action;		// $action may be 'create', 'update', 'update_extras'...
4995
			   					//var_dump($action);
4996
			   					//var_dump($this->oldcopy);exit;
4997
			   					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
4998
			   					{
4999
			   						//var_dump($this->oldcopy->array_options[$key]); var_dump($this->array_options[$key]);
5000
				   					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.
5001
				   					{
5002
				   						$new_array_options[$key] = $this->array_options[$key];	// Value is kept
5003
				   					}
5004
									else
5005
									{
5006
										// var_dump($algo);
5007
										$newvalue = dol_hash($this->array_options[$key], $algo);
5008
										$new_array_options[$key] = $newvalue;
5009
									}
5010
			   					}
5011
			   					else
5012
			   					{
5013
			   						$new_array_options[$key] = $this->array_options[$key];	// Value is kept
5014
			   					}
5015
			   				}
5016
			   			}
5017
			   			else	// Common usage
5018
			   			{
5019
			   				$new_array_options[$key] = $this->array_options[$key];
5020
			   			}
5021
			   			break;
5022
			   		case 'price':
5023
						$new_array_options[$key] = price2num($this->array_options[$key]);
5024
						break;
5025
					case 'date':
5026
						$new_array_options[$key] = $this->db->idate($this->array_options[$key]);
5027
						break;
5028
					case 'datetime':
5029
						// If data is a string instead of a timestamp, we convert it
5030
						if (! is_int($this->array_options[$key])) {
5031
							$this->array_options[$key] = strtotime($this->array_options[$key]);
5032
						}
5033
						$new_array_options[$key] = $this->db->idate($this->array_options[$key]);
5034
						break;
5035
		   			case 'link':
5036
						$param_list=array_keys($attributeParam['options']);
5037
						// 0 : ObjectName
5038
						// 1 : classPath
5039
						$InfoFieldList = explode(":", $param_list[0]);
5040
						dol_include_once($InfoFieldList[1]);
5041
						if ($InfoFieldList[0] && class_exists($InfoFieldList[0]))
5042
						{
5043
							if ($value == '-1')	// -1 is key for no defined in combo list of objects
5044
							{
5045
								$new_array_options[$key]='';
5046
							}
5047
							elseif ($value)
5048
							{
5049
								$object = new $InfoFieldList[0]($this->db);
5050
								if (is_numeric($value)) $res=$object->fetch($value);
5051
								else $res=$object->fetch('',$value);
5052
5053
								if ($res > 0) $new_array_options[$key]=$object->id;
5054
								else
5055
								{
5056
									$this->error="Id/Ref '".$value."' for object '".$object->element."' not found";
5057
									$this->db->rollback();
5058
									return -1;
5059
								}
5060
							}
5061
						}
5062
						else
5063
						{
5064
							dol_syslog('Error bad setup of extrafield', LOG_WARNING);
5065
						}
5066
						break;
5067
			   	}
5068
			}
5069
5070
			$this->db->begin();
5071
5072
			$table_element = $this->table_element;
5073
			if ($table_element == 'categorie') $table_element = 'categories'; // For compatibility
5074
5075
			$sql_del = "DELETE FROM ".MAIN_DB_PREFIX.$table_element."_extrafields WHERE fk_object = ".$this->id;
5076
			dol_syslog(get_class($this)."::insertExtraFields delete", LOG_DEBUG);
5077
			$this->db->query($sql_del);
5078
5079
			$sql = "INSERT INTO ".MAIN_DB_PREFIX.$table_element."_extrafields (fk_object";
5080
			foreach($new_array_options as $key => $value)
5081
			{
5082
				$attributeKey = substr($key,8);   // Remove 'options_' prefix
5083
				// Add field of attribut
5084
				if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] != 'separate') // Only for other type than separator
5085
					$sql.=",".$attributeKey;
5086
			}
5087
			$sql .= ") VALUES (".$this->id;
5088
5089
			foreach($new_array_options as $key => $value)
5090
			{
5091
				$attributeKey = substr($key,8);   // Remove 'options_' prefix
5092
				// Add field of attribute
5093
				if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] != 'separate') // Only for other type than separator)
5094
				{
5095
					if ($new_array_options[$key] != '')
5096
					{
5097
						$sql.=",'".$this->db->escape($new_array_options[$key])."'";
5098
					}
5099
					else
5100
					{
5101
						$sql.=",null";
5102
					}
5103
				}
5104
			}
5105
			$sql.=")";
5106
5107
			dol_syslog(get_class($this)."::insertExtraFields insert", LOG_DEBUG);
5108
			$resql = $this->db->query($sql);
5109
			if (! $resql)
5110
			{
5111
				$this->error=$this->db->lasterror();
5112
				$error++;
5113
			}
5114
5115
			if (! $error && $trigger)
5116
			{
5117
				// Call trigger
5118
				$this->context=array('extrafieldaddupdate'=>1);
5119
				$result=$this->call_trigger($trigger, $userused);
5120
				if ($result < 0) $error++;
5121
				// End call trigger
5122
			}
5123
5124
			if ($error)
5125
			{
5126
				$this->db->rollback();
5127
				return -1;
5128
			}
5129
			else
5130
			{
5131
				$this->db->commit();
5132
				return 1;
5133
			}
5134
		}
5135
		else return 0;
5136
	}
5137
5138
	/**
5139
	 *	Update an extra field value for the current object.
5140
	 *  Data to describe values to update are stored into $this->array_options=array('options_codeforfield1'=>'valueforfield1', 'options_codeforfield2'=>'valueforfield2', ...)
5141
	 *
5142
	 *  @param  string      $key    		Key of the extrafield (without starting 'options_')
5143
	 *  @param	string		$trigger		If defined, call also the trigger (for example COMPANY_MODIFY)
5144
	 *  @param	User		$userused		Object user
5145
	 *  @return int                 		-1=error, O=did nothing, 1=OK
5146
	 *  @see setValueFrom, insertExtraFields
5147
	 */
5148
	function updateExtraField($key, $trigger=null, $userused=null)
5149
	{
5150
		global $conf,$langs,$user;
5151
5152
		if (empty($userused)) $userused=$user;
5153
5154
		$error=0;
5155
5156
		if (! empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) return 0;	// For avoid conflicts if trigger used
5157
5158
		if (! empty($this->array_options) && isset($this->array_options["options_".$key]))
5159
		{
5160
			// Check parameters
5161
			$langs->load('admin');
5162
			require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5163
			$extrafields = new ExtraFields($this->db);
5164
			$target_extrafields=$extrafields->fetch_name_optionals_label($this->table_element);
5165
5166
			$value=$this->array_options["options_".$key];
5167
5168
			$attributeType     = $extrafields->attributes[$this->table_element]['type'][$key];
5169
			$attributeLabel    = $extrafields->attributes[$this->table_element]['label'][$key];
5170
			$attributeParam    = $extrafields->attributes[$this->table_element]['param'][$key];
5171
			$attributeRequired = $extrafields->attributes[$this->table_element]['required'][$key];
5172
5173
			//dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
5174
			//dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
5175
5176
			switch ($attributeType)
5177
			{
5178
				case 'int':
5179
					if (!is_numeric($value) && $value!='')
5180
					{
5181
						$this->errors[]=$langs->trans("ExtraFieldHasWrongValue",$attributeLabel);
5182
						return -1;
5183
					}
5184
					elseif ($value=='')
5185
					{
5186
						$this->array_options["options_".$key] = null;
5187
					}
5188
					break;
5189
				case 'double':
5190
					$value = price2num($value);
5191
					if (!is_numeric($value) && $value!='')
5192
					{
5193
						dol_syslog($langs->trans("ExtraFieldHasWrongValue")." sur ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
5194
						$this->errors[]=$langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
5195
						return -1;
5196
					}
5197
					elseif ($value=='')
5198
					{
5199
						$this->array_options["options_".$key] = null;
5200
					}
5201
					//dol_syslog("double value"." sur ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
5202
					$this->array_options["options_".$key] = $value;
5203
					break;
5204
			 	/*case 'select':	// Not required, we chosed value='0' for undefined values
5205
             		if ($value=='-1')
5206
             		{
5207
             			$this->array_options[$key] = null;
5208
             		}
5209
             		break;*/
5210
				case 'price':
5211
					$this->array_options["options_".$key] = price2num($this->array_options["options_".$key]);
5212
					break;
5213
				case 'date':
5214
					$this->array_options["options_".$key]=$this->db->idate($this->array_options["options_".$key]);
5215
					break;
5216
				case 'datetime':
5217
					$this->array_options["options_".$key]=$this->db->idate($this->array_options["options_".$key]);
5218
					break;
5219
				case 'link':
5220
					$param_list=array_keys($attributeParam['options']);
5221
					// 0 : ObjectName
5222
					// 1 : classPath
5223
					$InfoFieldList = explode(":", $param_list[0]);
5224
					dol_include_once($InfoFieldList[1]);
5225
					if ($value)
5226
					{
5227
						$object = new $InfoFieldList[0]($this->db);
5228
						$object->fetch(0,$value);
5229
						$this->array_options["options_".$key]=$object->id;
5230
					}
5231
					break;
5232
			}
5233
5234
			$this->db->begin();
5235
			$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element."_extrafields SET ".$key."='".$this->db->escape($this->array_options["options_".$key])."'";
5236
			$sql .= " WHERE fk_object = ".$this->id;
5237
			$resql = $this->db->query($sql);
5238
			if (! $resql)
5239
			{
5240
				$error++;
5241
				$this->error=$this->db->lasterror();
5242
			}
5243
5244
			if (! $error && $trigger)
5245
			{
5246
				// Call trigger
5247
				$this->context=array('extrafieldupdate'=>1);
5248
				$result=$this->call_trigger($trigger, $userused);
5249
				if ($result < 0) $error++;
5250
				// End call trigger
5251
			}
5252
5253
			if ($error)
5254
			{
5255
				dol_syslog(get_class($this) . "::".__METHOD__ . $this->error, LOG_ERR);
5256
				$this->db->rollback();
5257
				return -1;
5258
			}
5259
			else
5260
			{
5261
				$this->db->commit();
5262
				return 1;
5263
			}
5264
		}
5265
		else return 0;
5266
	}
5267
5268
5269
	/**
5270
	 * Return HTML string to put an input field into a page
5271
	 * Code very similar with showInputField of extra fields
5272
	 *
5273
	 * @param  array   		$val	       Array of properties for field to show
5274
	 * @param  string  		$key           Key of attribute
5275
	 * @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)
5276
	 * @param  string  		$moreparam     To add more parameters on html input tag
5277
	 * @param  string  		$keysuffix     Prefix string to add into name and id of field (can be used to avoid duplicate names)
5278
	 * @param  string  		$keyprefix     Suffix string to add into name and id of field (can be used to avoid duplicate names)
5279
	 * @param  string|int		$morecss       Value for css to define style/length of field. May also be a numeric.
5280
	 * @return string
5281
	 */
5282
	function showInputField($val, $key, $value, $moreparam='', $keysuffix='', $keyprefix='', $morecss=0)
5283
	{
5284
		global $conf,$langs,$form;
5285
5286
		if (! is_object($form))
5287
		{
5288
			require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
5289
			$form=new Form($this->db);
5290
		}
5291
5292
		$val=$this->fields[$key];
5293
5294
		$out='';
5295
        $type='';
5296
        $param = array();
5297
        $param['options']=array();
5298
        $size =$this->fields[$key]['size'];
5299
        // Because we work on extrafields
5300
        if(preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)){
5301
            $param['options']=array($reg[1].':'.$reg[2]=>'N');
5302
            $type ='link';
5303
        } elseif(preg_match('/^link:(.*):(.*)/i', $val['type'], $reg)) {
5304
            $param['options']=array($reg[1].':'.$reg[2]=>'N');
5305
            $type ='link';
5306
        } elseif(preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
5307
            $param['options']=array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4]=>'N');
5308
            $type ='sellist';
5309
        } elseif(preg_match('/varchar\((\d+)\)/', $val['type'],$reg)) {
5310
            $param['options']=array();
5311
            $type ='varchar';
5312
            $size=$reg[1];
5313
        } elseif(preg_match('/varchar/', $val['type'])) {
5314
            $param['options']=array();
5315
            $type ='varchar';
5316
        } elseif(is_array($this->fields[$key]['arrayofkeyval'])) {
5317
            $param['options']=$this->fields[$key]['arrayofkeyval'];
5318
            $type ='select';
5319
        } else {
5320
            $param['options']=array();
5321
            $type =$this->fields[$key]['type'];
5322
        }
5323
5324
		$label=$this->fields[$key]['label'];
5325
		//$elementtype=$this->fields[$key]['elementtype'];	// Seems not used
5326
		$default=$this->fields[$key]['default'];
5327
		$computed=$this->fields[$key]['computed'];
5328
		$unique=$this->fields[$key]['unique'];
5329
		$required=$this->fields[$key]['required'];
5330
5331
		$langfile=$this->fields[$key]['langfile'];
5332
		$list=$this->fields[$key]['list'];
5333
		$hidden=abs($this->fields[$key]['visible'])!=1?1:0;
5334
5335
		$objectid = $this->id;
5336
5337
5338
		if ($computed)
5339
		{
5340
			if (! preg_match('/^search_/', $keyprefix)) return '<span class="opacitymedium">'.$langs->trans("AutomaticallyCalculated").'</span>';
5341
			else return '';
5342
		}
5343
5344
5345
		// Use in priority showsize from parameters, then $val['css'] then autodefine
5346
		if (empty($morecss) && ! empty($val['css']))
5347
		{
5348
			$showsize = $val['css'];
5349
		}
5350
		if (empty($morecss))
5351
		{
5352
			if ($type == 'date')
5353
			{
5354
				$morecss = 'minwidth100imp';
5355
			}
5356
			elseif ($type == 'datetime')
5357
			{
5358
				$morecss = 'minwidth200imp';
5359
			}
5360
			elseif (in_array($type,array('int','integer','price')) || preg_match('/^double(\([0-9],[0-9]\)){0,1}/',$type))
5361
			{
5362
				$morecss = 'maxwidth75';
5363
                        }elseif ($type == 'url')
5364
			{
5365
				$morecss='minwidth400';
5366
			}
5367
			elseif ($type == 'boolean')
5368
			{
5369
				$morecss='';
5370
			}
5371
			else
5372
			{
5373
				if (round($size) < 12)
5374
				{
5375
					$morecss = 'minwidth100';
5376
				}
5377
				else if (round($size) <= 48)
5378
				{
5379
					$morecss = 'minwidth200';
5380
				}
5381
				else
5382
				{
5383
					$morecss = 'minwidth400';
5384
				}
5385
			}
5386
		}
5387
5388
		if (in_array($type,array('date','datetime')))
5389
		{
5390
			$tmp=explode(',',$size);
5391
			$newsize=$tmp[0];
5392
5393
			$showtime = in_array($type,array('datetime')) ? 1 : 0;
5394
5395
			// Do not show current date when field not required (see selectDate() method)
5396
			if (!$required && $value == '') $value = '-1';
5397
5398
			// TODO Must also support $moreparam
5399
			$out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1);
5400
		}
5401
		elseif (in_array($type,array('int','integer')))
5402
		{
5403
			$tmp=explode(',',$size);
5404
			$newsize=$tmp[0];
5405
			$out='<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" maxlength="'.$newsize.'" value="'.dol_escape_htmltag($value).'"'.($moreparam?$moreparam:'').'>';
5406
		}
5407
		elseif (preg_match('/varchar/', $type))
5408
		{
5409
			$out='<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" maxlength="'.$size.'" value="'.dol_escape_htmltag($value).'"'.($moreparam?$moreparam:'').'>';
5410
		}
5411
		elseif (in_array($type, array('mail', 'phone', 'url')))
5412
		{
5413
			$out='<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam?$moreparam:'').'>';
5414
		}
5415
		elseif ($type == 'text')
5416
		{
5417
			if (! preg_match('/search_/', $keyprefix))		// If keyprefix is search_ or search_options_, we must just use a simple text field
5418
			{
5419
				require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
5420
				$doleditor=new DolEditor($keyprefix.$key.$keysuffix,$value,'',200,'dolibarr_notes','In',false,false,false,ROWS_5,'90%');
5421
				$out=$doleditor->Create(1);
5422
			}
5423
			else
5424
			{
5425
				$out='<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam?$moreparam:'').'>';
5426
			}
5427
		}
5428
		elseif ($type == 'html')
5429
		{
5430
			if (! preg_match('/search_/', $keyprefix))		// If keyprefix is search_ or search_options_, we must just use a simple text field
5431
			{
5432
				require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
5433
				$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%');
5434
				$out=$doleditor->Create(1);
5435
			}
5436
			else
5437
			{
5438
				$out='<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam?$moreparam:'').'>';
5439
			}
5440
		}
5441
		elseif ($type == 'boolean')
5442
		{
5443
			$checked='';
5444
			if (!empty($value)) {
5445
				$checked=' checked value="1" ';
5446
			} else {
5447
				$checked=' value="1" ';
5448
			}
5449
			$out='<input type="checkbox" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.$checked.' '.($moreparam?$moreparam:'').'>';
5450
		}
5451
		elseif ($type == 'price')
5452
		{
5453
			if (!empty($value)) {		// $value in memory is a php numeric, we format it into user number format.
5454
				$value=price($value);
5455
			}
5456
			$out='<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam?$moreparam:'').'> '.$langs->getCurrencySymbol($conf->currency);
5457
		}
5458
		elseif (preg_match('/^double(\([0-9],[0-9]\)){0,1}/',$type))
5459
		{
5460
			if (!empty($value)) {		// $value in memory is a php numeric, we format it into user number format.
5461
				$value=price($value);
5462
			}
5463
			$out='<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam?$moreparam:'').'> ';
5464
		}
5465
		elseif ($type == 'select')
5466
		{
5467
			$out = '';
5468
			if (! empty($conf->use_javascript_ajax) && ! empty($conf->global->MAIN_EXTRAFIELDS_USE_SELECT2))
5469
			{
5470
				include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
5471
				$out.= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
5472
			}
5473
5474
			$out.='<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam?$moreparam:'').'>';
5475
                if((! isset($this->fields[$key]['default'])) ||($this->fields[$key]['notnull']!=1))$out.='<option value="0">&nbsp;</option>';
5476
			foreach ($param['options'] as $key => $val)
5477
			{
5478
				if ((string) $key == '') continue;
5479
				list($val, $parent) = explode('|', $val);
5480
				$out.='<option value="'.$key.'"';
5481
				$out.= (((string) $value == (string) $key)?' selected':'');
5482
				$out.= (!empty($parent)?' parent="'.$parent.'"':'');
5483
				$out.='>'.$val.'</option>';
5484
			}
5485
			$out.='</select>';
5486
		}
5487
		elseif ($type == 'sellist')
5488
		{
5489
			$out = '';
5490
			if (! empty($conf->use_javascript_ajax) && ! empty($conf->global->MAIN_EXTRAFIELDS_USE_SELECT2))
5491
			{
5492
				include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
5493
				$out.= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
5494
			}
5495
5496
			$out.='<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam?$moreparam:'').'>';
5497
			if (is_array($param['options']))
5498
			{
5499
				$param_list=array_keys($param['options']);
5500
				$InfoFieldList = explode(":", $param_list[0]);
5501
				$parentName='';
5502
				$parentField='';
5503
				// 0 : tableName
5504
				// 1 : label field name
5505
				// 2 : key fields name (if differ of rowid)
5506
				// 3 : key field parent (for dependent lists)
5507
				// 4 : where clause filter on column or table extrafield, syntax field='value' or extra.field=value
5508
				$keyList=(empty($InfoFieldList[2])?'rowid':$InfoFieldList[2].' as rowid');
5509
5510
5511
				if (count($InfoFieldList) > 4 && ! empty($InfoFieldList[4]))
5512
				{
5513
					if (strpos($InfoFieldList[4], 'extra.') !== false)
5514
					{
5515
						$keyList='main.'.$InfoFieldList[2].' as rowid';
5516
					} else {
5517
						$keyList=$InfoFieldList[2].' as rowid';
5518
					}
5519
				}
5520
				if (count($InfoFieldList) > 3 && ! empty($InfoFieldList[3]))
5521
				{
5522
					list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
5523
					$keyList.= ', '.$parentField;
5524
				}
5525
5526
				$fields_label = explode('|',$InfoFieldList[1]);
5527
				if (is_array($fields_label))
5528
				{
5529
					$keyList .=', ';
5530
					$keyList .= implode(', ', $fields_label);
5531
				}
5532
5533
				$sqlwhere='';
5534
				$sql = 'SELECT '.$keyList;
5535
				$sql.= ' FROM '.MAIN_DB_PREFIX .$InfoFieldList[0];
5536
				if (!empty($InfoFieldList[4]))
5537
				{
5538
					// can use SELECT request
5539
					if (strpos($InfoFieldList[4], '$SEL$')!==false) {
5540
						$InfoFieldList[4]=str_replace('$SEL$','SELECT',$InfoFieldList[4]);
5541
					}
5542
5543
					// current object id can be use into filter
5544
					if (strpos($InfoFieldList[4], '$ID$')!==false && !empty($objectid)) {
5545
						$InfoFieldList[4]=str_replace('$ID$',$objectid,$InfoFieldList[4]);
5546
					} else {
5547
						$InfoFieldList[4]=str_replace('$ID$','0',$InfoFieldList[4]);
5548
					}
5549
					//We have to join on extrafield table
5550
					if (strpos($InfoFieldList[4], 'extra')!==false)
5551
					{
5552
						$sql.= ' as main, '.MAIN_DB_PREFIX .$InfoFieldList[0].'_extrafields as extra';
5553
						$sqlwhere.= ' WHERE extra.fk_object=main.'.$InfoFieldList[2]. ' AND '.$InfoFieldList[4];
5554
					}
5555
					else
5556
					{
5557
						$sqlwhere.= ' WHERE '.$InfoFieldList[4];
5558
					}
5559
				}
5560
				else
5561
				{
5562
					$sqlwhere.= ' WHERE 1=1';
5563
				}
5564
				// Some tables may have field, some other not. For the moment we disable it.
5565
				if (in_array($InfoFieldList[0],array('tablewithentity')))
5566
				{
5567
					$sqlwhere.= ' AND entity = '.$conf->entity;
5568
				}
5569
				$sql.=$sqlwhere;
5570
				//print $sql;
5571
5572
				$sql .= ' ORDER BY ' . implode(', ', $fields_label);
5573
5574
				dol_syslog(get_class($this).'::showInputField type=sellist', LOG_DEBUG);
5575
				$resql = $this->db->query($sql);
5576
				if ($resql)
5577
				{
5578
					$out.='<option value="0">&nbsp;</option>';
5579
					$num = $this->db->num_rows($resql);
5580
					$i = 0;
5581
					while ($i < $num)
5582
					{
5583
						$labeltoshow='';
5584
						$obj = $this->db->fetch_object($resql);
5585
5586
						// Several field into label (eq table:code|libelle:rowid)
5587
						$notrans = false;
5588
						$fields_label = explode('|',$InfoFieldList[1]);
5589
						if (is_array($fields_label))
5590
						{
5591
							$notrans = true;
5592
							foreach ($fields_label as $field_toshow)
5593
							{
5594
								$labeltoshow.= $obj->$field_toshow.' ';
5595
							}
5596
						}
5597
						else
5598
						{
5599
							$labeltoshow=$obj->{$InfoFieldList[1]};
5600
						}
5601
						$labeltoshow=dol_trunc($labeltoshow,45);
5602
5603
						if ($value == $obj->rowid)
5604
						{
5605
							foreach ($fields_label as $field_toshow)
5606
							{
5607
								$translabel=$langs->trans($obj->$field_toshow);
5608
								if ($translabel!=$obj->$field_toshow) {
5609
									$labeltoshow=dol_trunc($translabel,18).' ';
5610
								}else {
5611
									$labeltoshow=dol_trunc($obj->$field_toshow,18).' ';
5612
								}
5613
							}
5614
							$out.='<option value="'.$obj->rowid.'" selected>'.$labeltoshow.'</option>';
5615
						}
5616
						else
5617
						{
5618
							if (! $notrans)
5619
							{
5620
								$translabel=$langs->trans($obj->{$InfoFieldList[1]});
5621
								if ($translabel!=$obj->{$InfoFieldList[1]}) {
5622
									$labeltoshow=dol_trunc($translabel,18);
5623
								}
5624
								else {
5625
									$labeltoshow=dol_trunc($obj->{$InfoFieldList[1]},18);
5626
								}
5627
							}
5628
							if (empty($labeltoshow)) $labeltoshow='(not defined)';
5629
							if ($value==$obj->rowid)
5630
							{
5631
								$out.='<option value="'.$obj->rowid.'" selected>'.$labeltoshow.'</option>';
5632
							}
5633
5634
							if (!empty($InfoFieldList[3]) && $parentField)
5635
							{
5636
								$parent = $parentName.':'.$obj->{$parentField};
5637
							}
5638
5639
							$out.='<option value="'.$obj->rowid.'"';
5640
							$out.= ($value==$obj->rowid?' selected':'');
5641
							$out.= (!empty($parent)?' parent="'.$parent.'"':'');
5642
							$out.='>'.$labeltoshow.'</option>';
5643
						}
5644
5645
						$i++;
5646
					}
5647
					$this->db->free($resql);
5648
				}
5649
				else {
5650
					print 'Error in request '.$sql.' '.$this->db->lasterror().'. Check setup of extra parameters.<br>';
5651
				}
5652
			}
5653
			$out.='</select>';
5654
		}
5655
		elseif ($type == 'checkbox')
5656
		{
5657
			$value_arr=explode(',',$value);
5658
			$out=$form->multiselectarray($keyprefix.$key.$keysuffix, (empty($param['options'])?null:$param['options']), $value_arr, '', 0, '', 0, '100%');
5659
		}
5660
		elseif ($type == 'radio')
5661
		{
5662
			$out='';
5663
			foreach ($param['options'] as $keyopt => $val)
5664
			{
5665
				$out.='<input class="flat '.$morecss.'" type="radio" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam?$moreparam:'');
5666
				$out.=' value="'.$keyopt.'"';
5667
				$out.=' id="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'"';
5668
				$out.= ($value==$keyopt?'checked':'');
5669
				$out.='/><label for="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'">'.$val.'</label><br>';
5670
			}
5671
		}
5672
		elseif ($type == 'chkbxlst')
5673
		{
5674
			if (is_array($value)) {
5675
				$value_arr = $value;
5676
			}
5677
			else {
5678
				$value_arr = explode(',', $value);
5679
			}
5680
5681
			if (is_array($param['options'])) {
5682
				$param_list = array_keys($param['options']);
5683
				$InfoFieldList = explode(":", $param_list[0]);
5684
				$parentName='';
5685
				$parentField='';
5686
				// 0 : tableName
5687
				// 1 : label field name
5688
				// 2 : key fields name (if differ of rowid)
5689
				// 3 : key field parent (for dependent lists)
5690
				// 4 : where clause filter on column or table extrafield, syntax field='value' or extra.field=value
5691
				$keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2] . ' as rowid');
5692
5693
				if (count($InfoFieldList) > 3 && ! empty($InfoFieldList[3])) {
5694
					list ( $parentName, $parentField ) = explode('|', $InfoFieldList[3]);
5695
					$keyList .= ', ' . $parentField;
5696
				}
5697
				if (count($InfoFieldList) > 4 && ! empty($InfoFieldList[4])) {
5698
					if (strpos($InfoFieldList[4], 'extra.') !== false) {
5699
						$keyList = 'main.' . $InfoFieldList[2] . ' as rowid';
5700
					} else {
5701
						$keyList = $InfoFieldList[2] . ' as rowid';
5702
					}
5703
				}
5704
5705
				$fields_label = explode('|', $InfoFieldList[1]);
5706
				if (is_array($fields_label)) {
5707
					$keyList .= ', ';
5708
					$keyList .= implode(', ', $fields_label);
5709
				}
5710
5711
				$sqlwhere = '';
5712
				$sql = 'SELECT ' . $keyList;
5713
				$sql .= ' FROM ' . MAIN_DB_PREFIX . $InfoFieldList[0];
5714
				if (! empty($InfoFieldList[4])) {
5715
5716
					// can use SELECT request
5717
					if (strpos($InfoFieldList[4], '$SEL$')!==false) {
5718
						$InfoFieldList[4]=str_replace('$SEL$','SELECT',$InfoFieldList[4]);
5719
					}
5720
5721
					// current object id can be use into filter
5722
					if (strpos($InfoFieldList[4], '$ID$')!==false && !empty($objectid)) {
5723
						$InfoFieldList[4]=str_replace('$ID$',$objectid,$InfoFieldList[4]);
5724
					} else {
5725
						$InfoFieldList[4]=str_replace('$ID$','0',$InfoFieldList[4]);
5726
					}
5727
5728
					// We have to join on extrafield table
5729
					if (strpos($InfoFieldList[4], 'extra') !== false) {
5730
						$sql .= ' as main, ' . MAIN_DB_PREFIX . $InfoFieldList[0] . '_extrafields as extra';
5731
						$sqlwhere .= ' WHERE extra.fk_object=main.' . $InfoFieldList[2] . ' AND ' . $InfoFieldList[4];
5732
					} else {
5733
						$sqlwhere .= ' WHERE ' . $InfoFieldList[4];
5734
					}
5735
				} else {
5736
					$sqlwhere .= ' WHERE 1=1';
5737
				}
5738
				// Some tables may have field, some other not. For the moment we disable it.
5739
				if (in_array($InfoFieldList[0], array ('tablewithentity')))
5740
				{
5741
					$sqlwhere .= ' AND entity = ' . $conf->entity;
5742
				}
5743
				// $sql.=preg_replace('/^ AND /','',$sqlwhere);
5744
				// print $sql;
5745
5746
				$sql .= $sqlwhere;
5747
				dol_syslog(get_class($this) . '::showInputField type=chkbxlst',LOG_DEBUG);
5748
				$resql = $this->db->query($sql);
5749
				if ($resql) {
5750
					$num = $this->db->num_rows($resql);
5751
					$i = 0;
5752
5753
					$data=array();
5754
5755
					while ( $i < $num ) {
5756
						$labeltoshow = '';
5757
						$obj = $this->db->fetch_object($resql);
5758
5759
						$notrans = false;
5760
						// Several field into label (eq table:code|libelle:rowid)
5761
						$fields_label = explode('|', $InfoFieldList[1]);
5762
						if (is_array($fields_label)) {
5763
							$notrans = true;
5764
							foreach ( $fields_label as $field_toshow ) {
5765
								$labeltoshow .= $obj->$field_toshow . ' ';
5766
							}
5767
						} else {
5768
							$labeltoshow = $obj->{$InfoFieldList[1]};
5769
						}
5770
						$labeltoshow = dol_trunc($labeltoshow, 45);
5771
5772
						if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
5773
							foreach ( $fields_label as $field_toshow ) {
5774
								$translabel = $langs->trans($obj->$field_toshow);
5775
								if ($translabel != $obj->$field_toshow) {
5776
									$labeltoshow = dol_trunc($translabel, 18) . ' ';
5777
								} else {
5778
									$labeltoshow = dol_trunc($obj->$field_toshow, 18) . ' ';
5779
								}
5780
							}
5781
5782
							$data[$obj->rowid]=$labeltoshow;
5783
						} else {
5784
							if (! $notrans) {
5785
								$translabel = $langs->trans($obj->{$InfoFieldList[1]});
5786
								if ($translabel != $obj->{$InfoFieldList[1]}) {
5787
									$labeltoshow = dol_trunc($translabel, 18);
5788
								} else {
5789
									$labeltoshow = dol_trunc($obj->{$InfoFieldList[1]}, 18);
5790
								}
5791
							}
5792
							if (empty($labeltoshow))
5793
								$labeltoshow = '(not defined)';
5794
5795
								if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
5796
									$data[$obj->rowid]=$labeltoshow;
5797
								}
5798
5799
								if (! empty($InfoFieldList[3]) && $parentField) {
5800
									$parent = $parentName . ':' . $obj->{$parentField};
5801
								}
5802
5803
								$data[$obj->rowid]=$labeltoshow;
5804
						}
5805
5806
						$i ++;
5807
					}
5808
					$this->db->free($resql);
5809
5810
					$out=$form->multiselectarray($keyprefix.$key.$keysuffix, $data, $value_arr, '', 0, '', 0, '100%');
5811
				} else {
5812
					print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
5813
				}
5814
			}
5815
		}
5816
		elseif ($type == 'link')
5817
		{
5818
			$param_list=array_keys($param['options']);				// $param_list='ObjectName:classPath'
5819
			$showempty=(($required && $default != '')?0:1);
5820
			$out=$form->selectForForms($param_list[0], $keyprefix.$key.$keysuffix, $value, $showempty);
5821
		}
5822
		elseif ($type == 'password')
5823
		{
5824
			// If prefix is 'search_', field is used as a filter, we use a common text field.
5825
			$out='<input type="'.($keyprefix=='search_'?'text':'password').'" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam?$moreparam:'').'>';
5826
		}
5827
		elseif ($type == 'array')
5828
		{
5829
			$newval = $val;
5830
			$newval['type'] = 'varchar(256)';
5831
5832
			$out='';
5833
5834
			$inputs = array();
5835
			if(! empty($value)) {
5836
				foreach($value as $option) {
0 ignored issues
show
Bug introduced by
The expression $value of type string is not traversable.
Loading history...
5837
					$out.= '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
5838
					$out.= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', $option, $moreparam, '', '', $showsize).'<br></span>';
0 ignored issues
show
Bug introduced by
The variable $showsize does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
5839
				}
5840
			}
5841
5842
			$out.= '<a id="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_add" href="javascript:;"><span class="fa fa-plus-circle valignmiddle"></span></a>';
5843
5844
			$newInput = '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
5845
			$newInput.= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', '', $moreparam, '', '', $showsize).'<br></span>';
5846
5847
			if(! empty($conf->use_javascript_ajax)) {
5848
				$out.= '
5849
					<script type="text/javascript">
5850
					$(document).ready(function() {
5851
						$("a#'.dol_escape_js($keyprefix.$key.$keysuffix).'_add").click(function() {
5852
							$("'.dol_escape_js($newInput).'").insertBefore(this);
5853
						});
5854
5855
						$(document).on("click", "a.'.dol_escape_js($keyprefix.$key.$keysuffix).'_del", function() {
5856
							$(this).parent().remove();
5857
						});
5858
					});
5859
					</script>';
5860
			}
5861
		}
5862
		if (!empty($hidden)) {
5863
			$out='<input type="hidden" value="'.$value.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'"/>';
5864
		}
5865
		/* Add comments
5866
		 if ($type == 'date') $out.=' (YYYY-MM-DD)';
5867
		 elseif ($type == 'datetime') $out.=' (YYYY-MM-DD HH:MM:SS)';
5868
		 */
5869
		return $out;
5870
	}
5871
5872
	/**
5873
	 * Return HTML string to show a field into a page
5874
	 * Code very similar with showOutputField of extra fields
5875
	 *
5876
	 * @param  array   $val		       Array of properties of field to show
5877
	 * @param  string  $key            Key of attribute
5878
	 * @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)
5879
	 * @param  string  $moreparam      To add more parametes on html input tag
5880
	 * @param  string  $keysuffix      Prefix string to add into name and id of field (can be used to avoid duplicate names)
5881
	 * @param  string  $keyprefix      Suffix string to add into name and id of field (can be used to avoid duplicate names)
5882
	 * @param  mixed   $showsize       Value for css to define size. May also be a numeric.
5883
	 * @return string
5884
	 */
5885
	function showOutputField($val, $key, $value, $moreparam='', $keysuffix='', $keyprefix='', $showsize=0)
5886
	{
5887
		global $conf,$langs,$form;
5888
5889
		if (! is_object($form))
5890
		{
5891
			require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
5892
			$form=new Form($this->db);
5893
		}
5894
5895
		$objectid = $this->id;
5896
		$label = $val['label'];
5897
		$type  = $val['type'];
5898
		$size  = $val['css'];
5899
5900
		// Convert var to be able to share same code than showOutputField of extrafields
5901
		if (preg_match('/varchar\((\d+)\)/', $type, $reg))
5902
		{
5903
			$type = 'varchar';		// convert varchar(xx) int varchar
5904
			$size = $reg[1];
5905
		}
5906
		elseif (preg_match('/varchar/', $type)) $type = 'varchar';		// convert varchar(xx) int varchar
5907
		if (is_array($val['arrayofkeyval'])) $type='select';
5908
		if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) $type='link';
5909
5910
		$default=$val['default'];
5911
		$computed=$val['computed'];
5912
		$unique=$val['unique'];
5913
		$required=$val['required'];
5914
		$param=$val['param'];
5915
		if (is_array($val['arrayofkeyval'])) $param['options'] = $val['arrayofkeyval'];
5916
		if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg))
5917
		{
5918
			$type='link';
5919
			$param['options']=array($reg[1].':'.$reg[2]=>$reg[1].':'.$reg[2]);
5920
		}
5921
		$langfile=$val['langfile'];
5922
		$list=$val['list'];
5923
		$help=$val['help'];
5924
		$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)
5925
5926
		if ($hidden) return '';
5927
5928
		// If field is a computed field, value must become result of compute
5929
		if ($computed)
5930
		{
5931
			// Make the eval of compute string
5932
			//var_dump($computed);
5933
			$value = dol_eval($computed, 1, 0);
5934
		}
5935
5936
		if (empty($showsize))
5937
		{
5938
			if ($type == 'date')
5939
			{
5940
				//$showsize=10;
5941
				$showsize = 'minwidth100imp';
5942
			}
5943
			elseif ($type == 'datetime')
5944
			{
5945
				//$showsize=19;
5946
				$showsize = 'minwidth200imp';
5947
			}
5948
			elseif (in_array($type,array('int','double','price')))
5949
			{
5950
				//$showsize=10;
5951
				$showsize = 'maxwidth75';
5952
			}
5953
			elseif ($type == 'url')
5954
			{
5955
				$showsize='minwidth400';
5956
			}
5957
			elseif ($type == 'boolean')
5958
			{
5959
				$showsize='';
5960
			}
5961
			else
5962
			{
5963
				if (round($size) < 12)
5964
				{
5965
					$showsize = 'minwidth100';
5966
				}
5967
				else if (round($size) <= 48)
5968
				{
5969
					$showsize = 'minwidth200';
5970
				}
5971
				else
5972
				{
5973
					//$showsize=48;
5974
					$showsize = 'minwidth400';
5975
				}
5976
			}
5977
		}
5978
5979
		// Format output value differently according to properties of field
5980
		if ($key == 'ref' && method_exists($this, 'getNomUrl')) $value=$this->getNomUrl(1, '', 0, '', 1);
5981
		elseif ($key == 'status' && method_exists($this, 'getLibStatut')) $value=$this->getLibStatut(3);
5982
		elseif ($type == 'date')
5983
		{
5984
			if(! empty($value)) {
5985
				$value=dol_print_date($value,'day');
5986
			} else {
5987
				$value='';
5988
			}
5989
		}
5990
		elseif ($type == 'datetime')
5991
		{
5992
			if(! empty($value)) {
5993
				$value=dol_print_date($value,'dayhour');
5994
			} else {
5995
				$value='';
5996
			}
5997
		}
5998
		elseif ($type == 'double')
5999
		{
6000
			if (!empty($value)) {
6001
				$value=price($value);
6002
			}
6003
		}
6004
		elseif ($type == 'boolean')
6005
		{
6006
			$checked='';
6007
			if (!empty($value)) {
6008
				$checked=' checked ';
6009
			}
6010
			$value='<input type="checkbox" '.$checked.' '.($moreparam?$moreparam:'').' readonly disabled>';
6011
		}
6012
		elseif ($type == 'mail')
6013
		{
6014
			$value=dol_print_email($value,0,0,0,64,1,1);
6015
		}
6016
		elseif ($type == 'url')
6017
		{
6018
			$value=dol_print_url($value,'_blank',32,1);
6019
		}
6020
		elseif ($type == 'phone')
6021
		{
6022
			$value=dol_print_phone($value, '', 0, 0, '', '&nbsp;', 1);
6023
		}
6024
		elseif ($type == 'price')
6025
		{
6026
			$value=price($value,0,$langs,0,0,-1,$conf->currency);
6027
		}
6028
		elseif ($type == 'select')
6029
		{
6030
			$value=$param['options'][$value];
6031
		}
6032
		elseif ($type == 'sellist')
6033
		{
6034
			$param_list=array_keys($param['options']);
6035
			$InfoFieldList = explode(":", $param_list[0]);
6036
6037
			$selectkey="rowid";
6038
			$keyList='rowid';
6039
6040
			if (count($InfoFieldList)>=3)
6041
			{
6042
				$selectkey = $InfoFieldList[2];
6043
				$keyList=$InfoFieldList[2].' as rowid';
6044
			}
6045
6046
			$fields_label = explode('|',$InfoFieldList[1]);
6047
			if(is_array($fields_label)) {
6048
				$keyList .=', ';
6049
				$keyList .= implode(', ', $fields_label);
6050
			}
6051
6052
			$sql = 'SELECT '.$keyList;
6053
			$sql.= ' FROM '.MAIN_DB_PREFIX .$InfoFieldList[0];
6054
			if (strpos($InfoFieldList[4], 'extra')!==false)
6055
			{
6056
				$sql.= ' as main';
6057
			}
6058
			if ($selectkey=='rowid' && empty($value)) {
6059
				$sql.= " WHERE ".$selectkey."=0";
6060
			} elseif ($selectkey=='rowid') {
6061
				$sql.= " WHERE ".$selectkey."=".$this->db->escape($value);
6062
			}else {
6063
				$sql.= " WHERE ".$selectkey."='".$this->db->escape($value)."'";
6064
			}
6065
6066
			//$sql.= ' AND entity = '.$conf->entity;
6067
6068
			dol_syslog(get_class($this).':showOutputField:$type=sellist', LOG_DEBUG);
6069
			$resql = $this->db->query($sql);
6070
			if ($resql)
6071
			{
6072
				$value='';	// value was used, so now we reste it to use it to build final output
6073
6074
				$obj = $this->db->fetch_object($resql);
6075
6076
				// Several field into label (eq table:code|libelle:rowid)
6077
				$fields_label = explode('|',$InfoFieldList[1]);
6078
6079
				if(is_array($fields_label) && count($fields_label)>1)
6080
				{
6081
					foreach ($fields_label as $field_toshow)
6082
					{
6083
						$translabel='';
6084
						if (!empty($obj->$field_toshow)) {
6085
							$translabel=$langs->trans($obj->$field_toshow);
6086
						}
6087
						if ($translabel!=$field_toshow) {
6088
							$value.=dol_trunc($translabel,18).' ';
6089
						}else {
6090
							$value.=$obj->$field_toshow.' ';
6091
						}
6092
					}
6093
				}
6094
				else
6095
				{
6096
					$translabel='';
6097
					if (!empty($obj->{$InfoFieldList[1]})) {
6098
						$translabel=$langs->trans($obj->{$InfoFieldList[1]});
6099
					}
6100
					if ($translabel!=$obj->{$InfoFieldList[1]}) {
6101
						$value=dol_trunc($translabel,18);
6102
					}else {
6103
						$value=$obj->{$InfoFieldList[1]};
6104
					}
6105
				}
6106
			}
6107
			else dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
6108
		}
6109
		elseif ($type == 'radio')
6110
		{
6111
			$value=$param['options'][$value];
6112
		}
6113
		elseif ($type == 'checkbox')
6114
		{
6115
			$value_arr=explode(',',$value);
6116
			$value='';
6117
			if (is_array($value_arr))
6118
			{
6119
				foreach ($value_arr as $keyval=>$valueval) {
6120
					$toprint[]='<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #aaa">'.$param['options'][$valueval].'</li>';
0 ignored issues
show
Coding Style Comprehensibility introduced by
$toprint was never initialized. Although not strictly required by PHP, it is generally a good practice to add $toprint = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
6121
				}
6122
			}
6123
			$value='<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
0 ignored issues
show
Bug introduced by
The variable $toprint does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
6124
		}
6125
		elseif ($type == 'chkbxlst')
6126
		{
6127
			$value_arr = explode(',', $value);
6128
6129
			$param_list = array_keys($param['options']);
6130
			$InfoFieldList = explode(":", $param_list[0]);
6131
6132
			$selectkey = "rowid";
6133
			$keyList = 'rowid';
6134
6135
			if (count($InfoFieldList) >= 3) {
6136
				$selectkey = $InfoFieldList[2];
6137
				$keyList = $InfoFieldList[2] . ' as rowid';
6138
			}
6139
6140
			$fields_label = explode('|', $InfoFieldList[1]);
6141
			if (is_array($fields_label)) {
6142
				$keyList .= ', ';
6143
				$keyList .= implode(', ', $fields_label);
6144
			}
6145
6146
			$sql = 'SELECT ' . $keyList;
6147
			$sql .= ' FROM ' . MAIN_DB_PREFIX . $InfoFieldList[0];
6148
			if (strpos($InfoFieldList[4], 'extra') !== false) {
6149
				$sql .= ' as main';
6150
			}
6151
			// $sql.= " WHERE ".$selectkey."='".$this->db->escape($value)."'";
6152
			// $sql.= ' AND entity = '.$conf->entity;
6153
6154
			dol_syslog(get_class($this) . ':showOutputField:$type=chkbxlst',LOG_DEBUG);
6155
			$resql = $this->db->query($sql);
6156
			if ($resql) {
6157
				$value = ''; // value was used, so now we reste it to use it to build final output
6158
				$toprint=array();
6159
				while ( $obj = $this->db->fetch_object($resql) ) {
6160
6161
					// Several field into label (eq table:code|libelle:rowid)
6162
					$fields_label = explode('|', $InfoFieldList[1]);
6163
					if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
6164
						if (is_array($fields_label) && count($fields_label) > 1) {
6165
							foreach ( $fields_label as $field_toshow ) {
6166
								$translabel = '';
6167
								if (! empty($obj->$field_toshow)) {
6168
									$translabel = $langs->trans($obj->$field_toshow);
6169
								}
6170
								if ($translabel != $field_toshow) {
6171
									$toprint[]='<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #aaa">'.dol_trunc($translabel, 18).'</li>';
6172
								} else {
6173
									$toprint[]='<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #aaa">'.$obj->$field_toshow.'</li>';
6174
								}
6175
							}
6176
						} else {
6177
							$translabel = '';
6178
							if (! empty($obj->{$InfoFieldList[1]})) {
6179
								$translabel = $langs->trans($obj->{$InfoFieldList[1]});
6180
							}
6181
							if ($translabel != $obj->{$InfoFieldList[1]}) {
6182
								$toprint[]='<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #aaa">'.dol_trunc($translabel, 18).'</li>';
6183
							} else {
6184
								$toprint[]='<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #aaa">'.$obj->{$InfoFieldList[1]}.'</li>';
6185
							}
6186
						}
6187
					}
6188
				}
6189
				$value='<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
6190
			} else {
6191
				dol_syslog(get_class($this) . '::showOutputField error ' . $this->db->lasterror(), LOG_WARNING);
6192
			}
6193
		}
6194
		elseif ($type == 'link')
6195
		{
6196
			$out='';
6197
6198
			// only if something to display (perf)
6199
			if ($value)
6200
			{
6201
				$param_list=array_keys($param['options']);				// $param_list='ObjectName:classPath'
6202
6203
				$InfoFieldList = explode(":", $param_list[0]);
6204
				$classname=$InfoFieldList[0];
6205
				$classpath=$InfoFieldList[1];
6206
				$getnomurlparam=(empty($InfoFieldList[2]) ? 3 : $InfoFieldList[2]);
6207
				if (! empty($classpath))
6208
				{
6209
					dol_include_once($InfoFieldList[1]);
6210
					if ($classname && class_exists($classname))
6211
					{
6212
						$object = new $classname($this->db);
6213
						$object->fetch($value);
6214
						$value=$object->getNomUrl($getnomurlparam);
6215
					}
6216
				}
6217
				else
6218
				{
6219
					dol_syslog('Error bad setup of extrafield', LOG_WARNING);
6220
					return 'Error bad setup of extrafield';
6221
				}
6222
			}
6223
			else $value='';
6224
		}
6225
		elseif ($type == 'text' || $type == 'html')
6226
		{
6227
			$value=dol_htmlentitiesbr($value);
6228
		}
6229
		elseif ($type == 'password')
6230
		{
6231
			$value=preg_replace('/./i','*',$value);
6232
		}
6233
		elseif ($type == 'array')
6234
		{
6235
			$value = implode('<br>', $value);
6236
		}
6237
6238
		//print $type.'-'.$size;
6239
		$out=$value;
6240
6241
		return $out;
6242
	}
6243
6244
6245
	/**
6246
	 * Function to show lines of extrafields with output datas
6247
	 *
6248
	 * @param 	Extrafields $extrafields    Extrafield Object
6249
	 * @param 	string      $mode           Show output (view) or input (edit) for extrafield
6250
	 * @param 	array       $params         Optional parameters. Example: array('style'=>'class="oddeven"', 'colspan'=>$colspan)
6251
	 * @param 	string      $keysuffix      Suffix string to add after name and id of field (can be used to avoid duplicate names)
6252
	 * @param 	string      $keyprefix      Prefix string to add before name and id of field (can be used to avoid duplicate names)
6253
	 * @param	string		$onetrtd		All fields in same tr td
6254
	 * @return 	string
6255
	 */
6256
	function showOptionals($extrafields, $mode='view', $params=null, $keysuffix='', $keyprefix='', $onetrtd=0)
6257
	{
6258
		global $db, $conf, $langs, $action, $form;
6259
6260
		if (! is_object($form)) $form=new Form($db);
6261
6262
		$out = '';
6263
6264
		if (is_array($extrafields->attributes[$this->table_element]['label']) && count($extrafields->attributes[$this->table_element]['label']) > 0)
6265
		{
6266
			$out .= "\n";
6267
			$out .= '<!-- showOptionalsInput --> ';
6268
			$out .= "\n";
6269
6270
			$e = 0;
6271
			foreach($extrafields->attributes[$this->table_element]['label'] as $key=>$label)
6272
			{
6273
				//Show only the key field in params
6274
				if (is_array($params) && array_key_exists('onlykey',$params) && $key != $params['onlykey']) continue;
6275
6276
				$enabled = 1;
6277
				if ($enabled && isset($extrafields->attributes[$this->table_element]['list'][$key]))
6278
				{
6279
					$enabled = dol_eval($extrafields->attributes[$this->table_element]['list'][$key], 1);
6280
				}
6281
6282
				$perms = 1;
6283
				if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key]))
6284
				{
6285
					$perms = dol_eval($extrafields->attributes[$this->table_element]['perms'][$key], 1);
6286
				}
6287
6288
				if (($mode == 'create' || $mode == 'edit') && abs($enabled) != 1 && abs($enabled) != 3) continue;	// <> -1 and <> 1 and <> 3 = not visible on forms, only on list
6289
				if (empty($perms)) continue;
6290
6291
				// Load language if required
6292
				if (! empty($extrafields->attributes[$this->table_element]['langfile'][$key])) $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
6293
6294
				if (is_array($params) && count($params)>0) {
6295
					if (array_key_exists('colspan',$params)) {
6296
						$colspan=$params['colspan'];
6297
					}
6298
				}else {
6299
					$colspan='3';
6300
				}
6301
6302
				switch($mode) {
6303
					case "view":
6304
						$value=$this->array_options["options_".$key.$keysuffix];
6305
						break;
6306
					case "edit":
6307
						$getposttemp = GETPOST($keyprefix.'options_'.$key.$keysuffix, 'none');				// GETPOST can get value from GET, POST or setup of default values.
6308
						// GETPOST("options_" . $key) can be 'abc' or array(0=>'abc')
6309
						if (is_array($getposttemp) || $getposttemp != '' || GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix))
6310
						{
6311
							if (is_array($getposttemp)) {
6312
								// $getposttemp is an array but following code expects a comma separated string
6313
								$value = implode(",", $getposttemp);
6314
							} else {
6315
								$value = $getposttemp;
6316
							}
6317
						} else {
6318
							$value = $this->array_options["options_" . $key];			// No GET, no POST, no default value, so we take value of object.
6319
						}
6320
						//var_dump($keyprefix.' - '.$key.' - '.$keysuffix.' - '.$keyprefix.'options_'.$key.$keysuffix.' - '.$this->array_options["options_".$key.$keysuffix].' - '.$getposttemp.' - '.$value);
6321
						break;
6322
				}
6323
6324
				if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate')
6325
				{
6326
					$out .= $extrafields->showSeparator($key, $this);
6327
				}
6328
				else
6329
				{
6330
					$csstyle='';
6331
					$class=(!empty($extrafields->attributes[$this->table_element]['hidden'][$key]) ? 'hideobject ' : '');
6332
					if (is_array($params) && count($params)>0) {
6333
						if (array_key_exists('style',$params)) {
6334
							$csstyle=$params['style'];
6335
						}
6336
					}
6337
6338
					// add html5 elements
6339
					$domData  = ' data-element="extrafield"';
6340
					$domData .= ' data-targetelement="'.$this->element.'"';
6341
					$domData .= ' data-targetid="'.$this->id.'"';
6342
6343
					$html_id = !empty($this->id) ? 'extrarow-'.$this->element.'_'.$key.'_'.$this->id : '';
6344
6345
					$out .= '<tr id="'.$html_id.'" '.$csstyle.' class="'.$class.$this->element.'_extras_'.$key.'" '.$domData.' >';
6346
6347
					if (! empty($conf->global->MAIN_EXTRAFIELDS_USE_TWO_COLUMS) && ($e % 2) == 0)
6348
					{
6349
						if (! empty($conf->global->MAIN_EXTRAFIELDS_USE_TWO_COLUMS) && ($e % 2) == 0) { $colspan='0'; }
6350
					}
6351
6352
					if ($action == 'selectlines') { $colspan++; }
0 ignored issues
show
Bug introduced by
The variable $colspan does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
6353
6354
					// Convert date into timestamp format (value in memory must be a timestamp)
6355
					if (in_array($extrafields->attributes[$this->table_element]['type'][$key],array('date','datetime')))
6356
					{
6357
						$datenotinstring = $this->array_options['options_' . $key];
6358
						if (! is_numeric($this->array_options['options_' . $key]))	// For backward compatibility
6359
						{
6360
							$datenotinstring = $this->db->jdate($datenotinstring);
6361
						}
6362
						$value = GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix)?dol_mktime(GETPOST($keyprefix.'options_'.$key.$keysuffix."hour", 'int', 3), GETPOST($keyprefix.'options_'.$key.$keysuffix."min",'int',3), 0, GETPOST($keyprefix.'options_'.$key.$keysuffix."month",'int',3), GETPOST($keyprefix.'options_'.$key.$keysuffix."day",'int',3), GETPOST($keyprefix.'options_'.$key.$keysuffix."year",'int',3)):$datenotinstring;
6363
					}
6364
					// Convert float submited string into real php numeric (value in memory must be a php numeric)
6365
					if (in_array($extrafields->attributes[$this->table_element]['type'][$key],array('price','double')))
6366
					{
6367
						$value = GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix)?price2num(GETPOST($keyprefix.'options_'.$key.$keysuffix, 'alpha', 3)):$this->array_options['options_'.$key];
6368
					}
6369
6370
					$labeltoshow = $langs->trans($label);
6371
6372
					$out .= '<td class="titlefield';
6373
					if (GETPOST('action','none') == 'create') $out.='create';
6374
					if ($mode != 'view' && ! empty($extrafields->attributes[$this->table_element]['required'][$key])) $out .= ' fieldrequired';
6375
					$out .= '">';
6376
					if (! empty($extrafields->attributes[$object->table_element]['help'][$key])) $out .= $form->textwithpicto($labeltoshow, $extrafields->attributes[$object->table_element]['help'][$key]);
0 ignored issues
show
Bug introduced by
The variable $object does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
6377
					else $out .= $labeltoshow;
6378
					$out .= '</td>';
6379
6380
					$html_id = !empty($this->id) ? $this->element.'_extras_'.$key.'_'.$this->id : '';
6381
					$out .='<td id="'.$html_id.'" class="'.$this->element.'_extras_'.$key.'" '.($colspan?' colspan="'.$colspan.'"':'').'>';
6382
6383
					switch($mode) {
6384
						case "view":
6385
							$out .= $extrafields->showOutputField($key, $value);
0 ignored issues
show
Bug introduced by
The variable $value does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
6386
							break;
6387
						case "edit":
6388
							$out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', 0, $this->id);
6389
							break;
6390
					}
6391
6392
					$out .= '</td>';
6393
6394
					if (! empty($conf->global->MAIN_EXTRAFIELDS_USE_TWO_COLUMS) && (($e % 2) == 1)) $out .= '</tr>';
6395
					else $out .= '</tr>';
6396
					$e++;
6397
				}
6398
			}
6399
			$out .= "\n";
6400
			// Add code to manage list depending on others
6401
			if (! empty($conf->use_javascript_ajax)) {
6402
				$out .= '
6403
				<script type="text/javascript">
6404
				    jQuery(document).ready(function() {
6405
				    	function showOptions(child_list, parent_list)
6406
				    	{
6407
				    		var val = $("select[name=\"options_"+parent_list+"\"]").val();
6408
				    		var parentVal = parent_list + ":" + val;
6409
							if(val > 0) {
6410
					    		$("select[name=\""+child_list+"\"] option[parent]").hide();
6411
					    		$("select[name=\""+child_list+"\"] option[parent=\""+parentVal+"\"]").show();
6412
							} else {
6413
								$("select[name=\""+child_list+"\"] option").show();
6414
							}
6415
				    	}
6416
						function setListDependencies() {
6417
					    	jQuery("select option[parent]").parent().each(function() {
6418
					    		var child_list = $(this).attr("name");
6419
								var parent = $(this).find("option[parent]:first").attr("parent");
6420
								var infos = parent.split(":");
6421
								var parent_list = infos[0];
6422
								$("select[name=\""+parent_list+"\"]").change(function() {
6423
									showOptions(child_list, parent_list);
6424
								});
6425
					    	});
6426
						}
6427
6428
						setListDependencies();
6429
				    });
6430
				</script>'."\n";
6431
				$out .= '<!-- /showOptionalsInput --> '."\n";
6432
			}
6433
		}
6434
		return $out;
6435
	}
6436
6437
6438
	/**
6439
	 * Returns the rights used for this class
6440
	 * @return stdClass
6441
	 */
6442
	public function getRights()
6443
	{
6444
		global $user;
6445
6446
		$element = $this->element;
6447
		if ($element == 'facturerec') $element='facture';
6448
6449
		return $user->rights->{$element};
6450
	}
6451
6452
	/**
6453
	 * Function used to replace a thirdparty id with another one.
6454
	 * This function is meant to be called from replaceThirdparty with the appropiate tables
6455
	 * Column name fk_soc MUST be used to identify thirdparties
6456
	 *
6457
	 * @param  DoliDB 	   $db 			  Database handler
6458
	 * @param  int 		   $origin_id     Old thirdparty id (the thirdparty to delete)
6459
	 * @param  int 		   $dest_id       New thirdparty id (the thirdparty that will received element of the other)
6460
	 * @param  string[]    $tables        Tables that need to be changed
6461
	 * @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)
6462
	 * @return bool						  True if success, False if error
6463
	 */
6464
	public static function commonReplaceThirdparty(DoliDB $db, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
6465
	{
6466
		foreach ($tables as $table)
6467
		{
6468
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$table.' SET fk_soc = '.$dest_id.' WHERE fk_soc = '.$origin_id;
6469
6470
			if (! $db->query($sql))
6471
			{
6472
				if ($ignoreerrors) return true;		// TODO Not enough. If there is A-B on kept thirdarty and B-C on old one, we must get A-B-C after merge. Not A-B.
6473
				//$this->errors = $db->lasterror();
6474
				return false;
6475
			}
6476
		}
6477
6478
		return true;
6479
	}
6480
6481
	/**
6482
	 * Get buy price to use for margin calculation. This function is called when buy price is unknown.
6483
	 *	 Set buy price = sell price if ForceBuyingPriceIfNull configured,
6484
	 *   else if calculation MARGIN_TYPE = 'costprice' and costprice is defined, use costprice as buyprice
6485
	 *	 else if calculation MARGIN_TYPE = 'pmp' and pmp is calculated, use pmp as buyprice
6486
	 *	 else set min buy price as buy price
6487
	 *
6488
	 * @param float		$unitPrice		 Product unit price
6489
	 * @param float		$discountPercent Line discount percent
6490
	 * @param int		$fk_product		 Product id
6491
	 * @return	float                    <0 if KO, buyprice if OK
6492
	 */
6493
	public function defineBuyPrice($unitPrice = 0.0, $discountPercent = 0.0, $fk_product = 0)
6494
	{
6495
		global $conf;
6496
6497
		$buyPrice = 0;
6498
6499
		if (($unitPrice > 0) && (isset($conf->global->ForceBuyingPriceIfNull) && $conf->global->ForceBuyingPriceIfNull == 1)) // In most cases, test here is false
6500
		{
6501
			$buyPrice = $unitPrice * (1 - $discountPercent / 100);
6502
		}
6503
		else
6504
		{
6505
			// Get cost price for margin calculation
6506
			if (! empty($fk_product))
6507
			{
6508
				if (isset($conf->global->MARGIN_TYPE) && $conf->global->MARGIN_TYPE == 'costprice')
6509
				{
6510
					require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
6511
					$product = new Product($this->db);
6512
					$result = $product->fetch($fk_product);
6513
					if ($result <= 0)
6514
					{
6515
						$this->errors[] = 'ErrorProductIdDoesNotExists';
6516
						return -1;
6517
					}
6518
					if ($product->cost_price > 0)
6519
					{
6520
						$buyPrice = $product->cost_price;
6521
					}
6522
					else if ($product->pmp > 0)
6523
					{
6524
						$buyPrice = $product->pmp;
6525
					}
6526
				}
6527
				else if (isset($conf->global->MARGIN_TYPE) && $conf->global->MARGIN_TYPE == 'pmp')
6528
				{
6529
					require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
6530
					$product = new Product($this->db);
6531
					$result = $product->fetch($fk_product);
6532
					if ($result <= 0)
6533
					{
6534
						$this->errors[] = 'ErrorProductIdDoesNotExists';
6535
						return -1;
6536
					}
6537
					if ($product->pmp > 0)
6538
					{
6539
						$buyPrice = $product->pmp;
6540
					}
6541
				}
6542
6543
				if (empty($buyPrice) && isset($conf->global->MARGIN_TYPE) && in_array($conf->global->MARGIN_TYPE, array('1','pmp','costprice')))
6544
				{
6545
					require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
6546
					$productFournisseur = new ProductFournisseur($this->db);
6547
					if (($result = $productFournisseur->find_min_price_product_fournisseur($fk_product)) > 0)
6548
					{
6549
						$buyPrice = $productFournisseur->fourn_unitprice;
6550
					}
6551
					else if ($result < 0)
6552
					{
6553
						$this->errors[] = $productFournisseur->error;
6554
						return -2;
6555
					}
6556
				}
6557
			}
6558
		}
6559
		return $buyPrice;
6560
	}
6561
6562
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
6563
	/**
6564
	 *  Show photos of an object (nbmax maximum), into several columns
6565
	 *
6566
	 *  @param		string	$modulepart		'product', 'ticket', ...
6567
	 *  @param      string	$sdir        	Directory to scan (full absolute path)
6568
	 *  @param      int		$size        	0=original size, 1='small' use thumbnail if possible
6569
	 *  @param      int		$nbmax       	Nombre maximum de photos (0=pas de max)
6570
	 *  @param      int		$nbbyrow     	Number of image per line or -1 to use div. Used only if size=1.
6571
	 * 	@param		int		$showfilename	1=Show filename
6572
	 * 	@param		int		$showaction		1=Show icon with action links (resize, delete)
6573
	 * 	@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.
6574
	 * 	@param		int		$maxWidth		Max width of original image when size='small'
6575
	 *  @param      int     $nolink         Do not add a href link to view enlarged imaged into a new tab
6576
	 *  @param      int     $notitle        Do not add title tag on image
6577
	 *  @param		int		$usesharelink	Use the public shared link of image (if not available, the 'nophoto' image will be shown instead)
6578
	 *  @return     string					Html code to show photo. Number of photos shown is saved in this->nbphoto
6579
	 */
6580
	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)
6581
	{
6582
        // phpcs:enable
6583
		global $conf,$user,$langs;
6584
6585
		include_once DOL_DOCUMENT_ROOT .'/core/lib/files.lib.php';
6586
		include_once DOL_DOCUMENT_ROOT .'/core/lib/images.lib.php';
6587
6588
		$sortfield='position_name';
6589
		$sortorder='asc';
6590
6591
		$dir = $sdir . '/';
6592
		$pdir = '/';
6593
		if ($modulepart == 'ticket')
6594
		{
6595
			$dir .= get_exdir(0, 0, 0, 0, $this, $modulepart).$this->track_id.'/';
6596
			$pdir .= get_exdir(0, 0, 0, 0, $this, $modulepart).$this->track_id.'/';
6597
		}
6598
		else
6599
		{
6600
			$dir .= get_exdir(0, 0, 0, 0, $this, $modulepart).$this->ref.'/';
6601
			$pdir .= get_exdir(0, 0, 0, 0, $this, $modulepart).$this->ref.'/';
6602
		}
6603
6604
		// For backward compatibility
6605
		if ($modulepart == 'product' && ! empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO))
6606
		{
6607
			$dir = $sdir . '/'. get_exdir($this->id,2,0,0,$this,$modulepart) . $this->id ."/photos/";
6608
			$pdir = '/' . get_exdir($this->id,2,0,0,$this,$modulepart) . $this->id ."/photos/";
6609
		}
6610
6611
		// Defined relative dir to DOL_DATA_ROOT
6612
		$relativedir = '';
6613
		if ($dir)
6614
		{
6615
			$relativedir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT,'/').'/', '', $dir);
6616
			$relativedir = preg_replace('/^[\\/]/','',$relativedir);
6617
			$relativedir = preg_replace('/[\\/]$/','',$relativedir);
6618
		}
6619
6620
		$dirthumb = $dir.'thumbs/';
6621
		$pdirthumb = $pdir.'thumbs/';
6622
6623
		$return ='<!-- Photo -->'."\n";
6624
		$nbphoto=0;
6625
6626
		$filearray=dol_dir_list($dir,"files",0,'','(\.meta|_preview.*\.png)$',$sortfield,(strtolower($sortorder)=='desc'?SORT_DESC:SORT_ASC),1);
6627
6628
		/*if (! empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO))    // For backward compatiblity, we scan also old dirs
6629
		 {
6630
		 $filearrayold=dol_dir_list($dirold,"files",0,'','(\.meta|_preview.*\.png)$',$sortfield,(strtolower($sortorder)=='desc'?SORT_DESC:SORT_ASC),1);
6631
		 $filearray=array_merge($filearray, $filearrayold);
6632
		 }*/
6633
6634
		completeFileArrayWithDatabaseInfo($filearray, $relativedir);
6635
6636
		if (count($filearray))
6637
		{
6638
			if ($sortfield && $sortorder)
6639
			{
6640
				$filearray=dol_sort_array($filearray, $sortfield, $sortorder);
6641
			}
6642
6643
			foreach($filearray as $key => $val)
6644
			{
6645
				$photo='';
6646
				$file = $val['name'];
6647
6648
				//if (! utf8_check($file)) $file=utf8_encode($file);	// To be sure file is stored in UTF8 in memory
6649
6650
				//if (dol_is_file($dir.$file) && image_format_supported($file) >= 0)
6651
				if (image_format_supported($file) >= 0)
6652
				{
6653
					$nbphoto++;
6654
					$photo = $file;
6655
					$viewfilename = $file;
6656
6657
					if ($size == 1 || $size == 'small') {   // Format vignette
6658
6659
						// Find name of thumb file
6660
						$photo_vignette=basename(getImageFileNameForSize($dir.$file, '_small'));
6661
						if (! dol_is_file($dirthumb.$photo_vignette)) $photo_vignette='';
6662
6663
						// Get filesize of original file
6664
						$imgarray=dol_getImageSize($dir.$photo);
6665
6666
						if ($nbbyrow > 0)
6667
						{
6668
							if ($nbphoto == 1) $return.= '<table width="100%" valign="top" align="center" border="0" cellpadding="2" cellspacing="2">';
6669
6670
							if ($nbphoto % $nbbyrow == 1) $return.= '<tr align=center valign=middle border=1>';
6671
							$return.= '<td width="'.ceil(100/$nbbyrow).'%" class="photo">';
6672
						}
6673
						else if ($nbbyrow < 0) $return .= '<div class="inline-block">';
6674
6675
						$return.= "\n";
6676
6677
						$relativefile=preg_replace('/^\//', '', $pdir.$photo);
6678
						if (empty($nolink))
6679
						{
6680
							$urladvanced=getAdvancedPreviewUrl($modulepart, $relativefile, 0, 'entity='.$this->entity);
6681
							if ($urladvanced) $return.='<a href="'.$urladvanced.'">';
6682
							else $return.= '<a href="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'" class="aphoto" target="_blank">';
6683
						}
6684
6685
						// Show image (width height=$maxHeight)
6686
						// Si fichier vignette disponible et image source trop grande, on utilise la vignette, sinon on utilise photo origine
6687
						$alt=$langs->transnoentitiesnoconv('File').': '.$relativefile;
6688
						$alt.=' - '.$langs->transnoentitiesnoconv('Size').': '.$imgarray['width'].'x'.$imgarray['height'];
6689
						if ($notitle) $alt='';
6690
6691
						if ($usesharelink)
6692
						{
6693
							if ($val['share'])
6694
							{
6695
								if (empty($maxHeight) || $photo_vignette && $imgarray['height'] > $maxHeight)
6696
								{
6697
									$return.= '<!-- Show original file (thumb not yet available with shared links) -->';
6698
									$return.= '<img class="photo photowithmargin" border="0" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/viewimage.php?hashp='.urlencode($val['share']).'" title="'.dol_escape_htmltag($alt).'">';
6699
								}
6700
								else {
6701
									$return.= '<!-- Show original file -->';
6702
									$return.= '<img class="photo photowithmargin" border="0" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/viewimage.php?hashp='.urlencode($val['share']).'" title="'.dol_escape_htmltag($alt).'">';
6703
								}
6704
							}
6705
							else
6706
							{
6707
								$return.= '<!-- Show nophoto file (because file is not shared) -->';
6708
								$return.= '<img class="photo photowithmargin" border="0" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/public/theme/common/nophoto.png" title="'.dol_escape_htmltag($alt).'">';
6709
							}
6710
						}
6711
						else
6712
						{
6713
							if (empty($maxHeight) || $photo_vignette && $imgarray['height'] > $maxHeight)
6714
							{
6715
								$return.= '<!-- Show thumb -->';
6716
								$return.= '<img class="photo photowithmargin" border="0" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdirthumb.$photo_vignette).'" title="'.dol_escape_htmltag($alt).'">';
6717
							}
6718
							else {
6719
								$return.= '<!-- Show original file -->';
6720
								$return.= '<img class="photo photowithmargin" border="0" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'" title="'.dol_escape_htmltag($alt).'">';
6721
							}
6722
						}
6723
6724
						if (empty($nolink)) $return.= '</a>';
6725
						$return.="\n";
6726
6727
						if ($showfilename) $return.= '<br>'.$viewfilename;
6728
						if ($showaction)
6729
						{
6730
							$return.= '<br>';
6731
							// On propose la generation de la vignette si elle n'existe pas et si la taille est superieure aux limites
6732
							if ($photo_vignette && (image_format_supported($photo) > 0) && ($this->imgWidth > $maxWidth || $this->imgHeight > $maxHeight))
6733
							{
6734
								$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>';
6735
							}
6736
							// Special cas for product
6737
							if ($modulepart == 'product' && ($user->rights->produit->creer || $user->rights->service->creer))
6738
							{
6739
								// Link to resize
6740
								$return.= '<a href="'.DOL_URL_ROOT.'/core/photos_resize.php?modulepart='.urlencode('produit|service').'&id='.$this->id.'&amp;file='.urlencode($pdir.$viewfilename).'" title="'.dol_escape_htmltag($langs->trans("Resize")).'">'.img_picto($langs->trans("Resize"), 'resize', '').'</a> &nbsp; ';
6741
6742
								// Link to delete
6743
								$return.= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&amp;action=delete&amp;file='.urlencode($pdir.$viewfilename).'">';
6744
								$return.= img_delete().'</a>';
6745
							}
6746
						}
6747
						$return.= "\n";
6748
6749
						if ($nbbyrow > 0)
6750
						{
6751
							$return.= '</td>';
6752
							if (($nbphoto % $nbbyrow) == 0) $return.= '</tr>';
6753
						}
6754
						else if ($nbbyrow < 0) $return.='</div>';
6755
					}
6756
6757
					if (empty($size)) {     // Format origine
6758
						$return.= '<img class="photo photowithmargin" border="0" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'">';
6759
6760
						if ($showfilename) $return.= '<br>'.$viewfilename;
6761
						if ($showaction)
6762
						{
6763
							// Special case for product
6764
							if ($modulepart == 'product' && ($user->rights->produit->creer || $user->rights->service->creer))
6765
							{
6766
								// Link to resize
6767
								$return.= '<a href="'.DOL_URL_ROOT.'/core/photos_resize.php?modulepart='.urlencode('produit|service').'&id='.$this->id.'&amp;file='.urlencode($pdir.$viewfilename).'" title="'.dol_escape_htmltag($langs->trans("Resize")).'">'.img_picto($langs->trans("Resize"), 'resize', '').'</a> &nbsp; ';
6768
6769
								// Link to delete
6770
								$return.= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&amp;action=delete&amp;file='.urlencode($pdir.$viewfilename).'">';
6771
								$return.= img_delete().'</a>';
6772
							}
6773
						}
6774
					}
6775
6776
					// On continue ou on arrete de boucler ?
6777
					if ($nbmax && $nbphoto >= $nbmax) break;
6778
				}
6779
			}
6780
6781
			if ($size==1 || $size=='small')
6782
			{
6783
				if ($nbbyrow > 0)
6784
				{
6785
					// Ferme tableau
6786
					while ($nbphoto % $nbbyrow)
6787
					{
6788
						$return.= '<td width="'.ceil(100/$nbbyrow).'%">&nbsp;</td>';
6789
						$nbphoto++;
6790
					}
6791
6792
					if ($nbphoto) $return.= '</table>';
6793
				}
6794
			}
6795
		}
6796
6797
		$this->nbphoto = $nbphoto;
6798
6799
		return $return;
6800
	}
6801
6802
6803
	/**
6804
	 * Function test if type is array
6805
	 *
6806
	 * @param   array   $info   content informations of field
6807
	 * @return                  bool
6808
	 */
6809
	protected function isArray($info)
6810
	{
6811
		if(is_array($info))
6812
		{
6813
			if(isset($info['type']) && $info['type']=='array') return true;
6814
			else return false;
6815
		}
6816
		else return false;
6817
	}
6818
6819
	/**
6820
	 * Function test if type is null
6821
	 *
6822
	 * @param   array   $info   content informations of field
6823
	 * @return                  bool
6824
	 */
6825
	protected function isNull($info)
6826
	{
6827
		if(is_array($info))
6828
		{
6829
			if(isset($info['type']) && $info['type']=='null') return true;
6830
			else return false;
6831
		}
6832
		else return false;
6833
	}
6834
6835
	/**
6836
	 * Function test if type is date
6837
	 *
6838
	 * @param   array   $info   content informations of field
6839
	 * @return                  bool
6840
	 */
6841
	public function isDate($info)
6842
	{
6843
		if(isset($info['type']) && ($info['type']=='date' || $info['type']=='datetime' || $info['type']=='timestamp')) return true;
6844
		else return false;
6845
	}
6846
6847
	/**
6848
	 * Function test if type is integer
6849
	 *
6850
	 * @param   array   $info   content informations of field
6851
	 * @return                  bool
6852
	 */
6853
	public function isInt($info)
6854
	{
6855
		if(is_array($info))
6856
		{
6857
			if(isset($info['type']) && ($info['type']=='int' || preg_match('/^integer/i',$info['type']) ) ) return true;
6858
			else return false;
6859
		}
6860
		else return false;
6861
	}
6862
6863
	/**
6864
	 * Function test if type is float
6865
	 *
6866
	 * @param   array   $info   content informations of field
6867
	 * @return                  bool
6868
	 */
6869
	public function isFloat($info)
6870
	{
6871
		if(is_array($info))
6872
		{
6873
			if (isset($info['type']) && (preg_match('/^(double|real)/i', $info['type']))) return true;
6874
			else return false;
6875
		}
6876
		else return false;
6877
	}
6878
6879
	/**
6880
	 * Function test if type is text
6881
	 *
6882
	 * @param   array   $info   content informations of field
6883
	 * @return                  bool
6884
	 */
6885
	public function isText($info)
6886
	{
6887
		if(is_array($info))
6888
		{
6889
			if(isset($info['type']) && $info['type']=='text') return true;
6890
			else return false;
6891
		}
6892
		else return false;
6893
	}
6894
6895
	/**
6896
	 * Function test if is indexed
6897
	 *
6898
	 * @param   array   $info   content informations of field
6899
	 * @return                  bool
6900
	 */
6901
	protected function isIndex($info)
6902
	{
6903
		if(is_array($info))
6904
		{
6905
			if(isset($info['index']) && $info['index']==true) return true;
6906
			else return false;
6907
		}
6908
		else return false;
6909
	}
6910
6911
	/**
6912
	 * Function to prepare the values to insert.
6913
	 * Note $this->${field} are set by the page that make the createCommon or the updateCommon.
6914
	 *
6915
	 * @return array
6916
	 */
6917
	protected function setSaveQuery()
6918
	{
6919
		global $conf;
6920
6921
		$queryarray=array();
6922
		foreach ($this->fields as $field=>$info)	// Loop on definition of fields
6923
		{
6924
			// Depending on field type ('datetime', ...)
6925
			if($this->isDate($info))
6926
			{
6927
				if(empty($this->{$field}))
6928
				{
6929
					$queryarray[$field] = null;
6930
				}
6931
				else
6932
				{
6933
					$queryarray[$field] = $this->db->idate($this->{$field});
6934
				}
6935
			}
6936
			else if($this->isArray($info))
6937
			{
6938
				if(! empty($this->{$field})) {
6939
					if(! is_array($this->{$field})) {
6940
						$this->{$field} = array($this->{$field});
6941
					}
6942
					$queryarray[$field] = serialize($this->{$field});
6943
				} else {
6944
					$queryarray[$field] = null;
6945
				}
6946
			}
6947
			else if($this->isInt($info))
6948
			{
6949
				if ($field == 'entity' && is_null($this->{$field})) $queryarray[$field]=$conf->entity;
6950
				else
6951
				{
6952
					$queryarray[$field] = (int) price2num($this->{$field});
6953
					if (empty($queryarray[$field])) $queryarray[$field]=0;		// May be reset to null later if property 'notnull' is -1 for this field.
6954
				}
6955
			}
6956
			else if($this->isFloat($info))
6957
			{
6958
				$queryarray[$field] = (double) price2num($this->{$field});
6959
				if (empty($queryarray[$field])) $queryarray[$field]=0;
6960
			}
6961
			else
6962
			{
6963
				$queryarray[$field] = $this->{$field};
6964
			}
6965
6966
			if ($info['type'] == 'timestamp' && empty($queryarray[$field])) unset($queryarray[$field]);
6967
			if (! empty($info['notnull']) && $info['notnull'] == -1 && empty($queryarray[$field])) $queryarray[$field] = null;
6968
		}
6969
6970
		return $queryarray;
6971
	}
6972
6973
	/**
6974
	 * Function to load data from a SQL pointer into properties of current object $this
6975
	 *
6976
	 * @param   stdClass    $obj    Contain data of object from database
6977
     * @return void
6978
	 */
6979
	protected function setVarsFromFetchObj(&$obj)
6980
	{
6981
		foreach ($this->fields as $field => $info)
6982
		{
6983
			if($this->isDate($info))
6984
			{
6985
				if(empty($obj->{$field}) || $obj->{$field} === '0000-00-00 00:00:00' || $obj->{$field} === '1000-01-01 00:00:00') $this->{$field} = 0;
6986
				else $this->{$field} = strtotime($obj->{$field});
6987
			}
6988
			elseif($this->isArray($info))
6989
			{
6990
				if(! empty($obj->{$field})) {
6991
					$this->{$field} = @unserialize($obj->{$field});
6992
					// Hack for data not in UTF8
6993
					if($this->{$field } === false) @unserialize(utf8_decode($obj->{$field}));
6994
				} else {
6995
					$this->{$field} = array();
6996
				}
6997
			}
6998
			elseif($this->isInt($info))
6999
			{
7000
				if ($field == 'rowid') $this->id = (int) $obj->{$field};
7001
				else $this->{$field} = (int) $obj->{$field};
7002
			}
7003
			elseif($this->isFloat($info))
7004
			{
7005
				$this->{$field} = (double) $obj->{$field};
7006
			}
7007
			elseif($this->isNull($info))
7008
			{
7009
				$val = $obj->{$field};
7010
				// zero is not null
7011
				$this->{$field} = (is_null($val) || (empty($val) && $val!==0 && $val!=='0') ? null : $val);
7012
			}
7013
			else
7014
			{
7015
				$this->{$field} = $obj->{$field};
7016
			}
7017
		}
7018
7019
		// If there is no 'ref' field, we force property ->ref to ->id for a better compatibility with common functions.
7020
		if (! isset($this->fields['ref']) && isset($this->id)) $this->ref = $this->id;
7021
	}
7022
7023
	/**
7024
	 * Function to concat keys of fields
7025
	 *
7026
	 * @return string
7027
	 */
7028
	protected function getFieldList()
7029
	{
7030
		$keys = array_keys($this->fields);
7031
		return implode(',', $keys);
7032
	}
7033
7034
	/**
7035
	 * Add quote to field value if necessary
7036
	 *
7037
	 * @param 	string|int	$value			Value to protect
7038
	 * @param	array		$fieldsentry	Properties of field
7039
	 * @return 	string
7040
	 */
7041
    protected function quote($value, $fieldsentry)
7042
    {
7043
		if (is_null($value)) return 'NULL';
7044
		else if (preg_match('/^(int|double|real)/i', $fieldsentry['type'])) return $this->db->escape("$value");
7045
		else return "'".$this->db->escape($value)."'";
7046
	}
7047
7048
7049
	/**
7050
	 * Create object into database
7051
	 *
7052
	 * @param  User $user      User that creates
7053
	 * @param  bool $notrigger false=launch triggers after, true=disable triggers
7054
	 * @return int             <0 if KO, Id of created object if OK
7055
	 */
7056
	public function createCommon(User $user, $notrigger = false)
7057
	{
7058
		global $langs;
7059
7060
		$error = 0;
7061
7062
		$now=dol_now();
7063
7064
		$fieldvalues = $this->setSaveQuery();
7065
		if (array_key_exists('date_creation', $fieldvalues) && empty($fieldvalues['date_creation'])) $fieldvalues['date_creation']=$this->db->idate($now);
7066
		if (array_key_exists('fk_user_creat', $fieldvalues) && ! ($fieldvalues['fk_user_creat'] > 0)) $fieldvalues['fk_user_creat']=$user->id;
7067
		unset($fieldvalues['rowid']);	// The field 'rowid' is reserved field name for autoincrement field so we don't need it into insert.
7068
7069
		$keys=array();
7070
		$values = array();
7071
		foreach ($fieldvalues as $k => $v) {
7072
			$keys[$k] = $k;
7073
			$value = $this->fields[$k];
7074
			$values[$k] = $this->quote($v, $value);
7075
		}
7076
7077
		// Clean and check mandatory
7078
		foreach($keys as $key)
7079
		{
7080
			// If field is an implicit foreign key field
7081
			if (preg_match('/^integer:/i', $this->fields[$key]['type']) && $values[$key] == '-1') $values[$key]='';
7082
			if (! empty($this->fields[$key]['foreignkey']) && $values[$key] == '-1') $values[$key]='';
7083
7084
			//var_dump($key.'-'.$values[$key].'-'.($this->fields[$key]['notnull'] == 1));
7085
			if (isset($this->fields[$key]['notnull']) && $this->fields[$key]['notnull'] == 1 && ! isset($values[$key]) && is_null($val['default']))
0 ignored issues
show
Bug introduced by
The variable $val does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
7086
			{
7087
				$error++;
7088
				$this->errors[]=$langs->trans("ErrorFieldRequired", $this->fields[$key]['label']);
7089
			}
7090
7091
			// If field is an implicit foreign key field
7092
			if (preg_match('/^integer:/i', $this->fields[$key]['type']) && empty($values[$key])) $values[$key]='null';
7093
			if (! empty($this->fields[$key]['foreignkey']) && empty($values[$key])) $values[$key]='null';
7094
		}
7095
7096
		if ($error) return -1;
7097
7098
		$this->db->begin();
7099
7100
		if (! $error)
7101
		{
7102
			$sql = 'INSERT INTO '.MAIN_DB_PREFIX.$this->table_element;
7103
			$sql.= ' ('.implode( ", ", $keys ).')';
7104
			$sql.= ' VALUES ('.implode( ", ", $values ).')';
7105
7106
			$res = $this->db->query($sql);
7107
			if ($res===false) {
7108
				$error++;
7109
				$this->errors[] = $this->db->lasterror();
7110
			}
7111
		}
7112
7113
		if (! $error)
7114
		{
7115
			$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX . $this->table_element);
7116
		}
7117
7118
		// Create extrafields
7119
		if (! $error)
7120
		{
7121
			$result=$this->insertExtraFields();
7122
			if ($result < 0) $error++;
7123
		}
7124
7125
		// Triggers
7126
		if (! $error && ! $notrigger)
7127
		{
7128
			// Call triggers
7129
			$result=$this->call_trigger(strtoupper(get_class($this)).'_CREATE',$user);
7130
			if ($result < 0) { $error++; }
7131
			// End call triggers
7132
		}
7133
7134
		// Commit or rollback
7135
		if ($error) {
7136
			$this->db->rollback();
7137
			return -1;
7138
		} else {
7139
			$this->db->commit();
7140
			return $this->id;
7141
		}
7142
	}
7143
7144
7145
	/**
7146
	 * Load object in memory from the database
7147
	 *
7148
	 * @param	int    $id				Id object
7149
	 * @param	string $ref				Ref
7150
	 * @param	string	$morewhere		More SQL filters (' AND ...')
7151
	 * @return 	int         			<0 if KO, 0 if not found, >0 if OK
7152
	 */
7153
	public function fetchCommon($id, $ref = null, $morewhere = '')
7154
	{
7155
		if (empty($id) && empty($ref) && empty($morewhere)) return -1;
7156
7157
		$sql = 'SELECT '.$this->getFieldList();
7158
		$sql.= ' FROM '.MAIN_DB_PREFIX.$this->table_element;
7159
7160
		if (!empty($id))  $sql.= ' WHERE rowid = '.$id;
7161
		elseif (!empty($ref)) $sql.= " WHERE ref = ".$this->quote($ref, $this->fields['ref']);
7162
		else $sql.=' WHERE 1 = 1';	// usage with empty id and empty ref is very rare
7163
		if ($morewhere)   $sql.= $morewhere;
7164
		$sql.=' LIMIT 1';	// This is a fetch, to be sure to get only one record
7165
7166
		$res = $this->db->query($sql);
7167
		if ($res)
7168
		{
7169
			$obj = $this->db->fetch_object($res);
7170
			if ($obj)
7171
			{
7172
				$this->setVarsFromFetchObj($obj);
7173
				return $this->id;
7174
			}
7175
			else
7176
			{
7177
				return 0;
7178
			}
7179
		}
7180
		else
7181
		{
7182
			$this->error = $this->db->lasterror();
7183
			$this->errors[] = $this->error;
7184
			return -1;
7185
		}
7186
	}
7187
7188
	/**
7189
	 * Update object into database
7190
	 *
7191
	 * @param  User $user      	User that modifies
7192
	 * @param  bool $notrigger 	false=launch triggers after, true=disable triggers
7193
	 * @return int             	<0 if KO, >0 if OK
7194
	 */
7195
	public function updateCommon(User $user, $notrigger = false)
7196
	{
7197
		global $conf, $langs;
7198
7199
		$error = 0;
7200
7201
		$now=dol_now();
7202
7203
		$fieldvalues = $this->setSaveQuery();
7204
		if (array_key_exists('date_modification', $fieldvalues) && empty($fieldvalues['date_modification'])) $fieldvalues['date_modification']=$this->db->idate($now);
7205
		if (array_key_exists('fk_user_modif', $fieldvalues) && ! ($fieldvalues['fk_user_modif'] > 0)) $fieldvalues['fk_user_modif']=$user->id;
7206
		unset($fieldvalues['rowid']);	// The field 'rowid' is reserved field name for autoincrement field so we don't need it into update.
7207
7208
		$keys=array();
7209
		$values = array();
7210
		foreach ($fieldvalues as $k => $v) {
7211
			$keys[$k] = $k;
7212
			$value = $this->fields[$k];
7213
			$values[$k] = $this->quote($v, $value);
7214
			$tmp[] = $k.'='.$this->quote($v, $this->fields[$k]);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$tmp was never initialized. Although not strictly required by PHP, it is generally a good practice to add $tmp = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
7215
		}
7216
7217
		// Clean and check mandatory
7218
		foreach($keys as $key)
7219
		{
7220
			if (preg_match('/^integer:/i', $this->fields[$key]['type']) && $values[$key] == '-1') $values[$key]='';		// This is an implicit foreign key field
7221
			if (! empty($this->fields[$key]['foreignkey']) && $values[$key] == '-1') $values[$key]='';					// This is an explicit foreign key field
7222
7223
			//var_dump($key.'-'.$values[$key].'-'.($this->fields[$key]['notnull'] == 1));
7224
			/*
7225
			if ($this->fields[$key]['notnull'] == 1 && empty($values[$key]))
7226
			{
7227
				$error++;
7228
				$this->errors[]=$langs->trans("ErrorFieldRequired", $this->fields[$key]['label']);
7229
			}*/
7230
		}
7231
7232
		$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element.' SET '.implode( ',', $tmp ).' WHERE rowid='.$this->id ;
0 ignored issues
show
Bug introduced by
The variable $tmp does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
7233
7234
		$this->db->begin();
7235
		if (! $error)
7236
		{
7237
			$res = $this->db->query($sql);
7238
			if ($res===false)
7239
			{
7240
				$error++;
7241
				$this->errors[] = $this->db->lasterror();
7242
			}
7243
		}
7244
7245
		// Update extrafield
7246
		if (! $error && empty($conf->global->MAIN_EXTRAFIELDS_DISABLED) && is_array($this->array_options) && count($this->array_options)>0)
7247
		{
7248
			$result=$this->insertExtraFields();
7249
			if ($result < 0)
7250
			{
7251
				$error++;
7252
			}
7253
		}
7254
7255
		// Triggers
7256
		if (! $error && ! $notrigger)
7257
		{
7258
			// Call triggers
7259
			$result=$this->call_trigger(strtoupper(get_class($this)).'_MODIFY',$user);
7260
			if ($result < 0) { $error++; } //Do also here what you must do to rollback action if trigger fail
7261
			// End call triggers
7262
		}
7263
7264
		// Commit or rollback
7265
		if ($error) {
7266
			$this->db->rollback();
7267
			return -1;
7268
		} else {
7269
			$this->db->commit();
7270
			return $this->id;
7271
		}
7272
	}
7273
7274
	/**
7275
	 * Delete object in database
7276
	 *
7277
	 * @param 	User 	$user       			User that deletes
7278
	 * @param 	bool 	$notrigger  			false=launch triggers after, true=disable triggers
7279
	 * @param	int		$forcechilddeletion		0=no, 1=Force deletion of children
7280
	 * @return 	int             				<=0 if KO, >0 if OK
7281
	 */
7282
	public function deleteCommon(User $user, $notrigger=false, $forcechilddeletion=0)
7283
	{
7284
		$error=0;
7285
7286
		$this->db->begin();
7287
7288
		if ($forcechilddeletion)
7289
		{
7290
			foreach($this->childtables as $table)
7291
			{
7292
				$sql = 'DELETE FROM '.MAIN_DB_PREFIX.$table.' WHERE '.$this->fk_element.' = '.$this->id;
7293
				$resql = $this->db->query($sql);
7294
				if (! $resql)
7295
				{
7296
					$this->error=$this->db->lasterror();
7297
					$this->errors[]=$this->error;
7298
					$this->db->rollback();
7299
					return -1;
7300
				}
7301
			}
7302
		}
7303
		elseif (! empty($this->fk_element) && ! empty($this->childtables))	// If object has childs linked with a foreign key field, we check all child tables.
7304
		{
7305
			$objectisused = $this->isObjectUsed($this->id);
7306
			if (! empty($objectisused))
7307
			{
7308
				dol_syslog(get_class($this)."::deleteCommon Can't delete record as it has some child", LOG_WARNING);
7309
				$this->error='ErrorRecordHasChildren';
7310
				$this->errors[]=$this->error;
7311
				$this->db->rollback();
7312
				return 0;
7313
			}
7314
		}
7315
7316
		if (! $error) {
7317
			if (! $notrigger) {
7318
				// Call triggers
7319
				$result=$this->call_trigger(strtoupper(get_class($this)).'_DELETE', $user);
7320
				if ($result < 0) { $error++; } // Do also here what you must do to rollback action if trigger fail
7321
				// End call triggers
7322
			}
7323
		}
7324
7325
		if (! $error && ! empty($this->isextrafieldmanaged))
7326
		{
7327
			$sql = "DELETE FROM " . MAIN_DB_PREFIX . $this->table_element."_extrafields";
7328
			$sql.= " WHERE fk_object=" . $this->id;
7329
7330
			$resql = $this->db->query($sql);
7331
			if (! $resql)
7332
			{
7333
				$this->errors[] = $this->db->lasterror();
7334
				$error++;
7335
			}
7336
		}
7337
7338
		if (! $error)
7339
		{
7340
			$sql = 'DELETE FROM '.MAIN_DB_PREFIX.$this->table_element.' WHERE rowid='.$this->id;
7341
7342
			$res = $this->db->query($sql);
7343
			if($res===false) {
7344
				$error++;
7345
				$this->errors[] = $this->db->lasterror();
7346
			}
7347
		}
7348
7349
		// Commit or rollback
7350
		if ($error) {
7351
			$this->db->rollback();
7352
			return -1;
7353
		} else {
7354
			$this->db->commit();
7355
			return 1;
7356
		}
7357
	}
7358
7359
	/**
7360
	 * Initialise object with example values
7361
	 * Id must be 0 if object instance is a specimen
7362
	 *
7363
	 * @return void
7364
	 */
7365
	public function initAsSpecimenCommon()
7366
	{
7367
		$this->id = 0;
7368
7369
		// TODO...
7370
	}
7371
7372
7373
	/* Part for comments */
7374
7375
	/**
7376
	 * Load comments linked with current task
7377
	 *	@return boolean	1 if ok
7378
	 */
7379
	public function fetchComments()
7380
	{
7381
		require_once DOL_DOCUMENT_ROOT.'/core/class/comment.class.php';
7382
7383
		$comment = new Comment($this->db);
7384
		$result=$comment->fetchAllFor($this->element, $this->id);
7385
		if ($result<0) {
7386
			$this->errors=array_merge($this->errors, $comment->errors);
7387
			return -1;
7388
		} else {
7389
			$this->comments = $comment->comments;
7390
		}
7391
		return count($this->comments);
7392
	}
7393
7394
	/**
7395
	 * Return nb comments already posted
7396
	 *
7397
	 * @return int
7398
	 */
7399
	public function getNbComments()
7400
	{
7401
		return count($this->comments);
7402
	}
7403
7404
    /**
7405
     * Trim object parameters
7406
     * @param string[] $parameters array of parameters to trim
7407
     *
7408
     * @return void
7409
     */
7410
    public function trimParameters($parameters)
7411
    {
7412
        if (!is_array($parameters)) return;
7413
        foreach ($parameters as $parameter) {
7414
            if (isset($this->$parameter)) {
7415
                $this->$parameter = trim($this->$parameter);
7416
            }
7417
        }
7418
    }
7419
}
7420