Passed
Push — master ( 65bdac...4e88da )
by Alxarafe
32:38
created

Base/CommonObject.php (1 issue)

Check for undefined variables.

Best Practice Comprehensibility Minor
1
<?php
2
/* Copyright (C) 2018       Alxarafe            <[email protected]>
3
 *
4
 * This program is free software; you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation; either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16
 */
17
namespace Alixar\Base;
18
19
use Alixar\Base\ExtraFields;
20
21
abstract class CommonObject
22
{
23
	/**
24
     * @var int The object identifier
25
     */
26
	public $id;
27
28
	/**
29
	 * @var string 		Error string
30
	 * @see             errors
31
	 */
32
	public $error;
33
34
	/**
35
	 * @var string[]	Array of error strings
36
	 */
37
	public $errors=array();
38
39
	/**
40
	 * @var string ID to identify managed object
41
	 */
42
	public $element;
43
44
	/**
45
	 * @var string Name of table without prefix where object is stored
46
	 */
47
	public $table_element;
48
49
	/**
50
	 * @var int    Name of subtable line
51
	 */
52
	public $table_element_line='';
53
54
	/**
55
	 * @var string		Key value used to track if data is coming from import wizard
56
	 */
57
	public $import_key;
58
59
	/**
60
	 * @var mixed		Contains data to manage extrafields
61
	 */
62
	public $array_options=array();
63
64
	/**
65
	 * @var int[][]		Array of linked objects ids. Loaded by ->fetchObjectLinked
66
	 */
67
	public $linkedObjectsIds;
68
69
	/**
70
	 * @var mixed		Array of linked objects. Loaded by ->fetchObjectLinked
71
	 */
72
	public $linkedObjects;
73
74
	/**
75
	 * @var Object      To store a cloned copy of object before to edit it and keep track of old properties
76
	 */
77
	public $oldcopy;
78
79
	/**
80
	 * @var string		Column name of the ref field.
81
	 */
82
	protected $table_ref_field = '';
83
84
85
86
	// Following vars are used by some objects only. We keep this property here in CommonObject to be able to provide common method using them.
87
88
	/**
89
	 * @var array<string,mixed>		Can be used to pass information when only object is provided to method
90
	 */
91
	public $context=array();
92
93
	/**
94
	 * @var string		Contains canvas name if record is an alternative canvas record
95
	 */
96
	public $canvas;
97
98
	/**
99
	 * @var Project The related project
100
	 * @see fetch_projet()
101
	 */
102
	public $project;
103
104
	/**
105
	 * @var int The related project ID
106
	 * @see setProject(), project
107
	 */
108
	public $fk_project;
109
110
	/**
111
	 * @deprecated
112
	 * @see project
113
	 */
114
	public $projet;
115
116
	/**
117
	 * @var Contact a related contact
118
	 * @see fetch_contact()
119
	 */
120
	public $contact;
121
122
	/**
123
	 * @var int The related contact ID
124
	 * @see fetch_contact()
125
	 */
126
	public $contact_id;
127
128
	/**
129
	 * @var Societe A related thirdparty
130
	 * @see fetch_thirdparty()
131
	 */
132
	public $thirdparty;
133
134
	/**
135
	 * @var User A related user
136
	 * @see fetch_user()
137
	 */
138
	public $user;
139
140
	/**
141
	 * @var string 	The type of originating object ('commande', 'facture', ...)
142
	 * @see fetch_origin()
143
	 */
144
	public $origin;
145
146
	/**
147
	 * @var int 	The id of originating object
148
	 * @see fetch_origin()
149
	 */
150
	public $origin_id;
151
152
	/**
153
	 * @var string The object's reference
154
	 */
155
	public $ref;
156
157
	/**
158
	 * @var string The object's previous reference
159
	 */
160
	public $ref_previous;
161
162
	/**
163
	 * @var string The object's next reference
164
	 */
165
	public $ref_next;
166
167
	/**
168
	 * @var string An external reference for the object
169
	 */
170
	public $ref_ext;
171
172
	/**
173
	 * @var int The object's status
174
	 * @see setStatut()
175
	 */
176
	public $statut;
177
178
	/**
179
	 * @var string
180
	 * @see getFullAddress()
181
	 */
182
	public $country;
183
184
	/**
185
	 * @var int
186
	 * @see getFullAddress(), country
187
	 */
188
	public $country_id;
189
190
	/**
191
	 * @var string
192
	 * @see getFullAddress(), isInEEC(), country
193
	 */
194
    public $country_code;
195
196
    /**
197
	 * @var string
198
	 * @see getFullAddress()
199
	 */
200
	public $state;
201
202
	/**
203
	 * @var int
204
	 * @see getFullAddress(), state
205
	 */
206
	public $state_id;
207
208
	/**
209
	 * @var string
210
	 * @see getFullAddress(), state
211
	 */
212
    public $state_code;
213
214
    /**
215
	 * @var string
216
	 * @see getFullAddress(), region
217
	 */
218
	public $region;
219
220
	/**
221
	 * @var string
222
	 * @see getFullAddress(), region
223
	 */
224
    public $region_code;
225
226
	/**
227
	 * @var int
228
	 * @see fetch_barcode()
229
	 */
230
	public $barcode_type;
231
232
	/**
233
	 * @var string
234
	 * @see fetch_barcode(), barcode_type
235
	 */
236
	public $barcode_type_code;
237
238
	/**
239
	 * @var string
240
	 * @see fetch_barcode(), barcode_type
241
	 */
242
	public $barcode_type_label;
243
244
	/**
245
	 * @var string
246
	 * @see fetch_barcode(), barcode_type
247
	 */
248
	public $barcode_type_coder;
249
250
	/**
251
	 * @var int Payment method ID (cheque, cash, ...)
252
	 * @see setPaymentMethods()
253
	 */
254
	public $mode_reglement_id;
255
256
	/**
257
	 * @var int Payment terms ID
258
	 * @see setPaymentTerms()
259
	 */
260
	public $cond_reglement_id;
261
262
	/**
263
	 * @var int Payment terms ID
264
	 * @deprecated Kept for compatibility
265
	 * @see cond_reglement_id;
266
	 */
267
	public $cond_reglement;
268
269
	/**
270
	 * @var int Delivery address ID
271
	 * @deprecated
272
	 * @see setDeliveryAddress()
273
	 */
274
	public $fk_delivery_address;
275
276
	/**
277
	 * @var int Shipping method ID
278
	 * @see setShippingMethod()
279
	 */
280
	public $shipping_method_id;
281
282
	/**
283
	 * @var string
284
	 * @see SetDocModel()
285
	 */
286
	public $modelpdf;
287
288
	/**
289
	 * @var int Bank account ID
290
	 * @see SetBankAccount()
291
	 */
292
	public $fk_account;
293
294
	/**
295
	 * @var string Public note
296
	 * @see update_note()
297
	 */
298
	public $note_public;
299
300
	/**
301
	 * @var string Private note
302
	 * @see update_note()
303
	 */
304
	public $note_private;
305
306
	/**
307
	 * @deprecated
308
	 * @see note_public
309
	 */
310
	public $note;
311
312
	/**
313
	 * @var float Total amount before taxes
314
	 * @see update_price()
315
	 */
316
	public $total_ht;
317
318
	/**
319
	 * @var float Total VAT amount
320
	 * @see update_price()
321
	 */
322
	public $total_tva;
323
324
	/**
325
	 * @var float Total local tax 1 amount
326
	 * @see update_price()
327
	 */
328
	public $total_localtax1;
329
330
	/**
331
	 * @var float Total local tax 2 amount
332
	 * @see update_price()
333
	 */
334
	public $total_localtax2;
335
336
	/**
337
	 * @var float Total amount with taxes
338
	 * @see update_price()
339
	 */
340
	public $total_ttc;
341
342
	/**
343
	 * @var CommonObjectLine[]
344
	 */
345
	public $lines;
346
347
	/**
348
	 * @var mixed		Contains comments
349
	 * @see fetchComments()
350
	 */
351
	public $comments=array();
352
353
	/**
354
	 * @var int
355
	 * @see setIncoterms()
356
	 */
357
	public $fk_incoterms;
358
359
	/**
360
	 * @var string
361
	 * @see SetIncoterms()
362
	 */
363
	public $libelle_incoterms;
364
365
	/**
366
	 * @var string
367
	 * @see display_incoterms()
368
	 */
369
	public $location_incoterms;
370
371
	public $name;
372
	public $lastname;
373
	public $firstname;
374
	public $civility_id;
375
376
	// Dates
377
	public $date_creation;			// Date creation
378
	public $date_validation;		// Date validation
379
	public $date_modification;		// Date last change (tms field)
380
381
382
383
	// No constructor as it is an abstract class
384
385
	/**
386
	 * Check an object id/ref exists
387
	 * If you don't need/want to instantiate object and just need to know if object exists, use this method instead of fetch
388
	 *
389
	 *  @param	string	$element   	String of element ('product', 'facture', ...)
390
	 *  @param	int		$id      	Id of object
391
	 *  @param  string	$ref     	Ref of object to check
392
	 *  @param	string	$ref_ext	Ref ext of object to check
393
	 *  @return int     			<0 if KO, 0 if OK but not found, >0 if OK and exists
394
	 */
395
	static function isExistingObject($element, $id, $ref='', $ref_ext='')
396
	{
397
		global $db,$conf;
398
399
		$sql = "SELECT rowid, ref, ref_ext";
400
		$sql.= " FROM ".MAIN_DB_PREFIX.$element;
401
		$sql.= " WHERE entity IN (".getEntity($element).")" ;
402
403
		if ($id > 0) $sql.= " AND rowid = ".$db->escape($id);
404
		else if ($ref) $sql.= " AND ref = '".$db->escape($ref)."'";
405
		else if ($ref_ext) $sql.= " AND ref_ext = '".$db->escape($ref_ext)."'";
406
		else {
407
			$error='ErrorWrongParameters';
408
			dol_print_error(get_class()."::isExistingObject ".$error, LOG_ERR);
409
			return -1;
410
		}
411
		if ($ref || $ref_ext) $sql.= " AND entity = ".$conf->entity;
412
413
		dol_syslog(get_class()."::isExistingObject", LOG_DEBUG);
414
		$resql = $db->query($sql);
415
		if ($resql)
416
		{
417
			$num=$db->num_rows($resql);
418
			if ($num > 0) return 1;
419
			else return 0;
420
		}
421
		return -1;
422
	}
423
424
	/**
425
	 * Method to output saved errors
426
	 *
427
	 * @return	string		String with errors
428
	 */
429
	function errorsToString()
430
	{
431
		return $this->error.(is_array($this->errors)?(($this->error!=''?', ':'').join(', ',$this->errors)):'');
432
	}
433
434
	/**
435
	 *	Return full name (civility+' '+name+' '+lastname)
436
	 *
437
	 *	@param	Translate	$langs			Language object for translation of civility (used only if option is 1)
438
	 *	@param	int			$option			0=No option, 1=Add civility
439
	 * 	@param	int			$nameorder		-1=Auto, 0=Lastname+Firstname, 1=Firstname+Lastname, 2=Firstname
440
	 * 	@param	int			$maxlen			Maximum length
441
	 * 	@return	string						String with full name
442
	 */
443
	function getFullName($langs,$option=0,$nameorder=-1,$maxlen=0)
444
	{
445
		//print "lastname=".$this->lastname." name=".$this->name." nom=".$this->nom."<br>\n";
446
		$lastname=$this->lastname;
447
		$firstname=$this->firstname;
448
		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:'')))));
449
450
		$ret='';
451
		if ($option && $this->civility_id)
452
		{
453
			if ($langs->transnoentitiesnoconv("Civility".$this->civility_id)!="Civility".$this->civility_id) $ret.=$langs->transnoentitiesnoconv("Civility".$this->civility_id).' ';
454
			else $ret.=$this->civility_id.' ';
455
		}
456
457
		$ret.=dolGetFirstLastname($firstname, $lastname, $nameorder);
458
459
		return dol_trunc($ret,$maxlen);
460
	}
461
462
	/**
463
	 * 	Return full address of contact
464
	 *
465
	 * 	@param		int			$withcountry		1=Add country into address string
466
	 *  @param		string		$sep				Separator to use to build string
467
	 *  @param		int		    $withregion			1=Add region into address string
468
	 *	@return		string							Full address string
469
	 */
470
	function getFullAddress($withcountry=0, $sep="\n", $withregion=0)
471
	{
472
		if ($withcountry && $this->country_id && (empty($this->country_code) || empty($this->country)))
473
		{
474
			require_once DOL_DOCUMENT_ROOT .'/core/lib/company.lib.php';
475
			$tmparray=getCountry($this->country_id,'all');
476
			$this->country_code=$tmparray['code'];
477
			$this->country     =$tmparray['label'];
478
		}
479
480
        if ($withregion && $this->state_id && (empty($this->state_code) || empty($this->state) || empty($this->region) || empty($this->region_cpde)))
481
    	{
482
    		require_once DOL_DOCUMENT_ROOT .'/core/lib/company.lib.php';
483
    		$tmparray=getState($this->state_id,'all',0,1);
484
			$this->state_code   =$tmparray['code'];
485
			$this->state        =$tmparray['label'];
486
			$this->region_code  =$tmparray['region_code'];
487
			$this->region       =$tmparray['region'];
488
        }
489
490
		return dol_format_address($this, $withcountry, $sep);
491
	}
492
493
494
	/**
495
	 * 	Return full address for banner
496
	 *
497
	 * 	@param		string		$htmlkey            HTML id to make banner content unique
498
	 *  @param      Object      $object				Object (thirdparty, thirdparty of contact for contact, null for a member)
499
	 *	@return		string							Full address string
500
	 */
501
	function getBannerAddress($htmlkey, $object)
502
	{
503
		global $conf, $langs;
504
505
		$countriesusingstate=array('AU','US','IN','GB','ES','UK','TR');    // See also option MAIN_FORCE_STATE_INTO_ADDRESS
506
507
		$contactid=0;
508
		$thirdpartyid=0;
509
		if ($this->element == 'societe')
510
		{
511
			$thirdpartyid=$this->id;
512
		}
513
		if ($this->element == 'contact')
514
		{
515
			$contactid=$this->id;
516
			$thirdpartyid=$object->fk_soc;
517
		}
518
		if ($this->element == 'user')
519
		{
520
			$contactid=$this->contact_id;
521
			$thirdpartyid=$object->fk_soc;
522
		}
523
524
		$out='<!-- BEGIN part to show address block -->';
525
526
		$outdone=0;
527
		$coords = $this->getFullAddress(1,', ',$conf->global->MAIN_SHOW_REGION_IN_STATE_SELECT);
528
		if ($coords)
529
		{
530
			if (! empty($conf->use_javascript_ajax))
531
			{
532
				$namecoords = $this->getFullName($langs,1).'<br>'.$coords;
533
				// hideonsmatphone because copyToClipboard call jquery dialog that does not work with jmobile
534
				$out.='<a href="#" class="hideonsmartphone" onclick="return copyToClipboard(\''.dol_escape_js($namecoords).'\',\''.dol_escape_js($langs->trans("HelpCopyToClipboard")).'\');">';
535
				$out.=img_picto($langs->trans("Address"), 'object_address.png');
536
				$out.='</a> ';
537
			}
538
			$out.=dol_print_address($coords, 'address_'.$htmlkey.'_'.$this->id, $this->element, $this->id, 1, ', '); $outdone++;
539
			$outdone++;
540
		}
541
542
		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
543
				&& empty($conf->global->SOCIETE_DISABLE_STATE) && $this->state)
544
		{
545
            if (!empty($conf->global->MAIN_SHOW_REGION_IN_STATE_SELECT) && $conf->global->MAIN_SHOW_REGION_IN_STATE_SELECT == 1 && $this->region) {
546
                $out.=($outdone?' - ':'').$this->region.' - '.$this->state;
547
            }
548
            else {
549
                $out.=($outdone?' - ':'').$this->state;
550
            }
551
			$outdone++;
552
		}
553
554
		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>':'');
555
		if (! empty($this->phone) && empty($this->phone_pro)) {		// For objects that store pro phone into ->phone
556
			$out.=dol_print_phone($this->phone,$this->country_code,$contactid,$thirdpartyid,'AC_TEL','&nbsp;','phone',$langs->trans("PhonePro")); $outdone++;
557
		}
558
		if (! empty($this->phone_pro)) {
559
			$out.=dol_print_phone($this->phone_pro,$this->country_code,$contactid,$thirdpartyid,'AC_TEL','&nbsp;','phone',$langs->trans("PhonePro")); $outdone++;
560
		}
561
		if (! empty($this->phone_mobile)) {
562
			$out.=dol_print_phone($this->phone_mobile,$this->country_code,$contactid,$thirdpartyid,'AC_TEL','&nbsp;','mobile',$langs->trans("PhoneMobile")); $outdone++;
563
		}
564
		if (! empty($this->phone_perso)) {
565
			$out.=dol_print_phone($this->phone_perso,$this->country_code,$contactid,$thirdpartyid,'AC_TEL','&nbsp;','phone',$langs->trans("PhonePerso")); $outdone++;
566
		}
567
		if (! empty($this->office_phone)) {
568
			$out.=dol_print_phone($this->office_phone,$this->country_code,$contactid,$thirdpartyid,'AC_TEL','&nbsp;','phone',$langs->trans("PhonePro")); $outdone++;
569
		}
570
		if (! empty($this->user_mobile)) {
571
			$out.=dol_print_phone($this->user_mobile,$this->country_code,$contactid,$thirdpartyid,'AC_TEL','&nbsp;','mobile',$langs->trans("PhoneMobile")); $outdone++;
572
		}
573
		if (! empty($this->fax)) {
574
			$out.=dol_print_phone($this->fax,$this->country_code,$contactid,$thirdpartyid,'AC_FAX','&nbsp;','fax',$langs->trans("Fax")); $outdone++;
575
		}
576
		if (! empty($this->office_fax)) {
577
			$out.=dol_print_phone($this->office_fax,$this->country_code,$contactid,$thirdpartyid,'AC_FAX','&nbsp;','fax',$langs->trans("Fax")); $outdone++;
578
		}
579
580
		$out.='<div style="clear: both;"></div>';
581
		$outdone=0;
582
		if (! empty($this->email))
583
		{
584
			$out.=dol_print_email($this->email,$this->id,$object->id,'AC_EMAIL',0,0,1);
585
			$outdone++;
586
		}
587
		if (! empty($this->url))
588
		{
589
			$out.=dol_print_url($this->url,'_goout',0,1);
590
			$outdone++;
591
		}
592
		$out.='<div style="clear: both;">';
593
		if (! empty($conf->socialnetworks->enabled))
594
		{
595
			if ($this->skype) $out.=dol_print_socialnetworks($this->skype,$this->id,$object->id,'skype');
596
			$outdone++;
597
			if ($this->jabberid) $out.=dol_print_socialnetworks($this->jabberid,$this->id,$object->id,'jabber');
598
			$outdone++;
599
			if ($this->twitter) $out.=dol_print_socialnetworks($this->twitter,$this->id,$object->id,'twitter');
600
			$outdone++;
601
			if ($this->facebook) $out.=dol_print_socialnetworks($this->facebook,$this->id,$object->id,'facebook');
602
			$outdone++;
603
		}
604
		$out.='</div>';
605
606
		$out.='<!-- END Part to show address block -->';
607
608
		return $out;
609
	}
610
611
	/**
612
	 * Return the link of last main doc file for direct public download.
613
	 *
614
	 * @param	string	$modulepart			Module related to document
615
	 * @param	int		$initsharekey		Init the share key if it was not yet defined
616
	 * @param	int		$relativelink		0=Return full external link, 1=Return link relative to root of file
617
	 * @return	string						Link or empty string if there is no download link
618
	 */
619
	function getLastMainDocLink($modulepart, $initsharekey=0, $relativelink=0)
620
	{
621
		global $user, $dolibarr_main_url_root;
622
623
		if (empty($this->last_main_doc))
624
		{
625
			return '';		// No way to known which document name to use
626
		}
627
628
		include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
629
		$ecmfile=new EcmFiles($this->db);
630
		$result = $ecmfile->fetch(0, '', $this->last_main_doc);
631
		if ($result < 0)
632
		{
633
			$this->error = $ecmfile->error;
634
			$this->errors = $ecmfile->errors;
635
			return -1;
636
		}
637
638
		if (empty($ecmfile->id))
639
		{
640
			// Add entry into index
641
			if ($initsharekey)
642
			{
643
				require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
644
				// TODO We can't, we dont' have full path of file, only last_main_doc adn ->element, so we must rebuild full path first
645
				/*
646
				$ecmfile->filepath = $rel_dir;
647
				$ecmfile->filename = $filename;
648
				$ecmfile->label = md5_file(dol_osencode($destfull));	// hash of file content
649
				$ecmfile->fullpath_orig = '';
650
				$ecmfile->gen_or_uploaded = 'generated';
651
				$ecmfile->description = '';    // indexed content
652
				$ecmfile->keyword = '';        // keyword content
653
				$ecmfile->share = getRandomPassword(true);
654
				$result = $ecmfile->create($user);
655
				if ($result < 0)
656
				{
657
					$this->error = $ecmfile->error;
658
					$this->errors = $ecmfile->errors;
659
				}
660
				*/
661
			}
662
			else return '';
663
		}
664
		elseif (empty($ecmfile->share))
665
		{
666
			// Add entry into index
667
			if ($initsharekey)
668
			{
669
				require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
670
				$ecmfile->share = getRandomPassword(true);
671
				$ecmfile->update($user);
672
			}
673
			else return '';
674
		}
675
676
		// Define $urlwithroot
677
		$urlwithouturlroot=preg_replace('/'.preg_quote(DOL_URL_ROOT,'/').'$/i','',trim($dolibarr_main_url_root));
678
		$urlwithroot=$urlwithouturlroot.DOL_URL_ROOT;		// This is to use external domain name found into config file
679
		//$urlwithroot=DOL_MAIN_URL_ROOT;					// This is to use same domain name than current
680
681
		$forcedownload=0;
682
683
		$paramlink='';
684
		//if (! empty($modulepart)) $paramlink.=($paramlink?'&':'').'modulepart='.$modulepart;		// For sharing with hash (so public files), modulepart is not required.
685
		//if (! empty($ecmfile->entity)) $paramlink.='&entity='.$ecmfile->entity; 					// For sharing with hash (so public files), entity is not required.
686
		//$paramlink.=($paramlink?'&':'').'file='.urlencode($filepath);								// No need of name of file for public link, we will use the hash
687
		if (! empty($ecmfile->share)) $paramlink.=($paramlink?'&':'').'hashp='.$ecmfile->share;			// Hash for public share
688
		if ($forcedownload) $paramlink.=($paramlink?'&':'').'attachment=1';
689
690
		if ($relativelink)
691
		{
692
			$linktoreturn='document.php'.($paramlink?'?'.$paramlink:'');
693
		}
694
		else
695
		{
696
			$linktoreturn=$urlwithroot.'/document.php'.($paramlink?'?'.$paramlink:'');
697
		}
698
699
		// Here $ecmfile->share is defined
700
		return $linktoreturn;
701
	}
702
703
704
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
705
	/**
706
	 *  Add a link between element $this->element and a contact
707
	 *
708
	 *  @param	int		$fk_socpeople       Id of thirdparty contact (if source = 'external') or id of user (if souce = 'internal') to link
709
	 *  @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
710
	 *  @param  string	$source             external=Contact extern (llx_socpeople), internal=Contact intern (llx_user)
711
	 *  @param  int		$notrigger			Disable all triggers
712
	 *  @return int                 		<0 if KO, >0 if OK
713
	 */
714
	function add_contact($fk_socpeople, $type_contact, $source='external',$notrigger=0)
715
	{
716
        // phpcs:enable
717
		global $user,$langs;
718
719
720
		dol_syslog(get_class($this)."::add_contact $fk_socpeople, $type_contact, $source, $notrigger");
721
722
		// Check parameters
723
		if ($fk_socpeople <= 0)
724
		{
725
			$langs->load("errors");
726
			$this->error=$langs->trans("ErrorWrongValueForParameterX","1");
727
			dol_syslog(get_class($this)."::add_contact ".$this->error,LOG_ERR);
728
			return -1;
729
		}
730
		if (! $type_contact)
731
		{
732
			$langs->load("errors");
733
			$this->error=$langs->trans("ErrorWrongValueForParameterX","2");
734
			dol_syslog(get_class($this)."::add_contact ".$this->error,LOG_ERR);
735
			return -2;
736
		}
737
738
		$id_type_contact=0;
739
		if (is_numeric($type_contact))
740
		{
741
			$id_type_contact=$type_contact;
742
		}
743
		else
744
		{
745
			// We look for id type_contact
746
			$sql = "SELECT tc.rowid";
747
			$sql.= " FROM ".MAIN_DB_PREFIX."c_type_contact as tc";
748
			$sql.= " WHERE tc.element='".$this->db->escape($this->element)."'";
749
			$sql.= " AND tc.source='".$this->db->escape($source)."'";
750
			$sql.= " AND tc.code='".$this->db->escape($type_contact)."' AND tc.active=1";
751
			//print $sql;
752
			$resql=$this->db->query($sql);
753
			if ($resql)
754
			{
755
				$obj = $this->db->fetch_object($resql);
756
				if ($obj) $id_type_contact=$obj->rowid;
757
			}
758
		}
759
760
		if ($id_type_contact == 0)
761
		{
762
			$this->error='CODE_NOT_VALID_FOR_THIS_ELEMENT';
763
			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");
764
			return -3;
765
		}
766
767
		$datecreate = dol_now();
768
769
		// Socpeople must have already been added by some trigger, then we have to check it to avoid DB_ERROR_RECORD_ALREADY_EXISTS error
770
		$TListeContacts=$this->liste_contact(-1, $source);
771
		$already_added=false;
772
		if(!empty($TListeContacts)) {
773
			foreach($TListeContacts as $array_contact) {
774
				if($array_contact['status'] == 4 && $array_contact['id'] == $fk_socpeople && $array_contact['fk_c_type_contact'] == $id_type_contact) {
775
					$already_added=true;
776
					break;
777
				}
778
			}
779
		}
780
781
		if(!$already_added) {
782
783
			$this->db->begin();
784
785
			// Insert into database
786
			$sql = "INSERT INTO ".MAIN_DB_PREFIX."element_contact";
787
			$sql.= " (element_id, fk_socpeople, datecreate, statut, fk_c_type_contact) ";
788
			$sql.= " VALUES (".$this->id.", ".$fk_socpeople." , " ;
789
			$sql.= "'".$this->db->idate($datecreate)."'";
790
			$sql.= ", 4, ". $id_type_contact;
791
			$sql.= ")";
792
793
			$resql=$this->db->query($sql);
794
			if ($resql)
795
			{
796
				if (! $notrigger)
797
				{
798
					$result=$this->call_trigger(strtoupper($this->element).'_ADD_CONTACT', $user);
799
					if ($result < 0)
800
					{
801
						$this->db->rollback();
802
						return -1;
803
					}
804
				}
805
806
				$this->db->commit();
807
				return 1;
808
			}
809
			else
810
			{
811
				if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS')
812
				{
813
					$this->error=$this->db->errno();
814
					$this->db->rollback();
815
					echo 'err rollback';
816
					return -2;
817
				}
818
				else
819
				{
820
					$this->error=$this->db->error();
821
					$this->db->rollback();
822
					return -1;
823
				}
824
			}
825
		} else return 0;
826
	}
827
828
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
829
	/**
830
	 *    Copy contact from one element to current
831
	 *
832
	 *    @param    CommonObject    $objFrom    Source element
833
	 *    @param    string          $source     Nature of contact ('internal' or 'external')
834
	 *    @return   int                         >0 if OK, <0 if KO
835
	 */
836
	function copy_linked_contact($objFrom, $source='internal')
837
	{
838
        // phpcs:enable
839
		$contacts = $objFrom->liste_contact(-1, $source);
840
		foreach($contacts as $contact)
841
		{
842
			if ($this->add_contact($contact['id'], $contact['fk_c_type_contact'], $contact['source']) < 0)
843
			{
844
				$this->error=$this->db->lasterror();
845
				return -1;
846
			}
847
		}
848
		return 1;
849
	}
850
851
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
852
	/**
853
	 *      Update a link to contact line
854
	 *
855
	 *      @param	int		$rowid              Id of line contact-element
856
	 * 		@param	int		$statut	            New status of link
857
	 *      @param  int		$type_contact_id    Id of contact type (not modified if 0)
858
	 *      @param  int		$fk_socpeople	    Id of soc_people to update (not modified if 0)
859
	 *      @return int                 		<0 if KO, >= 0 if OK
860
	 */
861
	function update_contact($rowid, $statut, $type_contact_id=0, $fk_socpeople=0)
862
	{
863
        // phpcs:enable
864
		// Insert into database
865
		$sql = "UPDATE ".MAIN_DB_PREFIX."element_contact set";
866
		$sql.= " statut = ".$statut;
867
		if ($type_contact_id) $sql.= ", fk_c_type_contact = '".$type_contact_id ."'";
868
		if ($fk_socpeople) $sql.= ", fk_socpeople = '".$fk_socpeople ."'";
869
		$sql.= " where rowid = ".$rowid;
870
		$resql=$this->db->query($sql);
871
		if ($resql)
872
		{
873
			return 0;
874
		}
875
		else
876
		{
877
			$this->error=$this->db->lasterror();
878
			return -1;
879
		}
880
	}
881
882
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
883
	/**
884
	 *    Delete a link to contact line
885
	 *
886
	 *    @param	int		$rowid			Id of contact link line to delete
887
	 *    @param	int		$notrigger		Disable all triggers
888
	 *    @return   int						>0 if OK, <0 if KO
889
	 */
890
	function delete_contact($rowid, $notrigger=0)
891
	{
892
        // phpcs:enable
893
		global $user;
894
895
896
		$this->db->begin();
897
898
		$sql = "DELETE FROM ".MAIN_DB_PREFIX."element_contact";
899
		$sql.= " WHERE rowid =".$rowid;
900
901
		dol_syslog(get_class($this)."::delete_contact", LOG_DEBUG);
902
		if ($this->db->query($sql))
903
		{
904
			if (! $notrigger)
905
			{
906
				$result=$this->call_trigger(strtoupper($this->element).'_DELETE_CONTACT', $user);
907
				if ($result < 0) { $this->db->rollback(); return -1; }
908
			}
909
910
			$this->db->commit();
911
			return 1;
912
		}
913
		else
914
		{
915
			$this->error=$this->db->lasterror();
916
			$this->db->rollback();
917
			return -1;
918
		}
919
	}
920
921
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
922
	/**
923
	 *    Delete all links between an object $this and all its contacts
924
	 *
925
	 *	  @param	string	$source		'' or 'internal' or 'external'
926
	 *	  @param	string	$code		Type of contact (code or id)
927
	 *    @return   int					>0 if OK, <0 if KO
928
	 */
929
	function delete_linked_contact($source='',$code='')
930
	{
931
        // phpcs:enable
932
		$temp = array();
933
		$typeContact = $this->liste_type_contact($source,'',0,0,$code);
934
935
		foreach($typeContact as $key => $value)
936
		{
937
			array_push($temp,$key);
938
		}
939
		$listId = implode(",", $temp);
940
941
		$sql = "DELETE FROM ".MAIN_DB_PREFIX."element_contact";
942
		$sql.= " WHERE element_id = ".$this->id;
943
		if ($listId)
944
			$sql.= " AND fk_c_type_contact IN (".$listId.")";
945
946
		dol_syslog(get_class($this)."::delete_linked_contact", LOG_DEBUG);
947
		if ($this->db->query($sql))
948
		{
949
			return 1;
950
		}
951
		else
952
		{
953
			$this->error=$this->db->lasterror();
954
			return -1;
955
		}
956
	}
957
958
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
959
	/**
960
	 *    Get array of all contacts for an object
961
	 *
962
	 *    @param	int			$statut		Status of links to get (-1=all)
963
	 *    @param	string		$source		Source of contact: external or thirdparty (llx_socpeople) or internal (llx_user)
964
	 *    @param	int         $list       0:Return array contains all properties, 1:Return array contains just id
965
	 *    @param    string      $code       Filter on this code of contact type ('SHIPPING', 'BILLING', ...)
966
	 *    @return	array|int		        Array of contacts, -1 if error
967
	 */
968
	function liste_contact($statut=-1,$source='external',$list=0,$code='')
969
	{
970
        // phpcs:enable
971
		global $langs;
972
973
		$tab=array();
974
975
		$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
976
		if ($source == 'internal') $sql.=", '-1' as socid, t.statut as statuscontact, t.login, t.photo";
977
		if ($source == 'external' || $source == 'thirdparty') $sql.=", t.fk_soc as socid, t.statut as statuscontact";
978
		$sql.= ", t.civility as civility, t.lastname as lastname, t.firstname, t.email";
979
		$sql.= ", tc.source, tc.element, tc.code, tc.libelle";
980
		$sql.= " FROM ".MAIN_DB_PREFIX."c_type_contact tc";
981
		$sql.= ", ".MAIN_DB_PREFIX."element_contact ec";
982
		if ($source == 'internal') $sql.=" LEFT JOIN ".MAIN_DB_PREFIX."user t on ec.fk_socpeople = t.rowid";
983
		if ($source == 'external'|| $source == 'thirdparty') $sql.=" LEFT JOIN ".MAIN_DB_PREFIX."socpeople t on ec.fk_socpeople = t.rowid";
984
		$sql.= " WHERE ec.element_id =".$this->id;
985
		$sql.= " AND ec.fk_c_type_contact=tc.rowid";
986
		$sql.= " AND tc.element='".$this->db->escape($this->element)."'";
987
		if ($code) $sql.= " AND tc.code = '".$this->db->escape($code)."'";
988
		if ($source == 'internal') $sql.= " AND tc.source = 'internal'";
989
		if ($source == 'external' || $source == 'thirdparty') $sql.= " AND tc.source = 'external'";
990
		$sql.= " AND tc.active=1";
991
		if ($statut >= 0) $sql.= " AND ec.statut = '".$statut."'";
992
		$sql.=" ORDER BY t.lastname ASC";
993
994
		dol_syslog(get_class($this)."::liste_contact", LOG_DEBUG);
995
		$resql=$this->db->query($sql);
996
		if ($resql)
997
		{
998
			$num=$this->db->num_rows($resql);
999
			$i=0;
1000
			while ($i < $num)
1001
			{
1002
				$obj = $this->db->fetch_object($resql);
1003
1004
				if (! $list)
1005
				{
1006
					$transkey="TypeContact_".$obj->element."_".$obj->source."_".$obj->code;
1007
					$libelle_type=($langs->trans($transkey)!=$transkey ? $langs->trans($transkey) : $obj->libelle);
1008
					$tab[$i]=array('source'=>$obj->source,'socid'=>$obj->socid,'id'=>$obj->id,
1009
								   'nom'=>$obj->lastname,      // For backward compatibility
1010
								   'civility'=>$obj->civility, 'lastname'=>$obj->lastname, 'firstname'=>$obj->firstname, 'email'=>$obj->email, 'login'=>$obj->login, 'photo'=>$obj->photo, 'statuscontact'=>$obj->statuscontact,
1011
								   'rowid'=>$obj->rowid, 'code'=>$obj->code, 'libelle'=>$libelle_type, 'status'=>$obj->statuslink, 'fk_c_type_contact'=>$obj->fk_c_type_contact);
1012
				}
1013
				else
1014
				{
1015
					$tab[$i]=$obj->id;
1016
				}
1017
1018
				$i++;
1019
			}
1020
1021
			return $tab;
1022
		}
1023
		else
1024
		{
1025
			$this->error=$this->db->lasterror();
1026
			dol_print_error($this->db);
1027
			return -1;
1028
		}
1029
	}
1030
1031
1032
	/**
1033
	 * 		Update status of a contact linked to object
1034
	 *
1035
	 * 		@param	int		$rowid		Id of link between object and contact
1036
	 * 		@return	int					<0 if KO, >=0 if OK
1037
	 */
1038
	function swapContactStatus($rowid)
1039
	{
1040
		$sql = "SELECT ec.datecreate, ec.statut, ec.fk_socpeople, ec.fk_c_type_contact,";
1041
		$sql.= " tc.code, tc.libelle";
1042
		//$sql.= ", s.fk_soc";
1043
		$sql.= " FROM (".MAIN_DB_PREFIX."element_contact as ec, ".MAIN_DB_PREFIX."c_type_contact as tc)";
1044
		//$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
1045
		$sql.= " WHERE ec.rowid =".$rowid;
1046
		$sql.= " AND ec.fk_c_type_contact=tc.rowid";
1047
		$sql.= " AND tc.element = '".$this->db->escape($this->element)."'";
1048
1049
		dol_syslog(get_class($this)."::swapContactStatus", LOG_DEBUG);
1050
		$resql=$this->db->query($sql);
1051
		if ($resql)
1052
		{
1053
			$obj = $this->db->fetch_object($resql);
1054
			$newstatut = ($obj->statut == 4) ? 5 : 4;
1055
			$result = $this->update_contact($rowid, $newstatut);
1056
			$this->db->free($resql);
1057
			return $result;
1058
		}
1059
		else
1060
		{
1061
			$this->error=$this->db->error();
1062
			dol_print_error($this->db);
1063
			return -1;
1064
		}
1065
	}
1066
1067
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1068
	/**
1069
	 *      Return array with list of possible values for type of contacts
1070
	 *
1071
	 *      @param	string	$source     'internal', 'external' or 'all'
1072
	 *      @param	string	$order		Sort order by : 'position', 'code', 'rowid'...
1073
	 *      @param  int		$option     0=Return array id->label, 1=Return array code->label
1074
	 *      @param  int		$activeonly 0=all status of contact, 1=only the active
1075
	 *		@param	string	$code		Type of contact (Example: 'CUSTOMER', 'SERVICE')
1076
	 *      @return array       		Array list of type of contacts (id->label if option=0, code->label if option=1)
1077
	 */
1078
	function liste_type_contact($source='internal', $order='position', $option=0, $activeonly=0, $code='')
1079
	{
1080
        // phpcs:enable
1081
		global $langs;
1082
1083
		if (empty($order)) $order='position';
1084
		if ($order == 'position') $order.=',code';
1085
1086
		$tab = array();
1087
		$sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle, tc.position";
1088
		$sql.= " FROM ".MAIN_DB_PREFIX."c_type_contact as tc";
1089
		$sql.= " WHERE tc.element='".$this->db->escape($this->element)."'";
1090
		if ($activeonly == 1) $sql.= " AND tc.active=1"; // only the active types
1091
		if (! empty($source) && $source != 'all') $sql.= " AND tc.source='".$this->db->escape($source)."'";
1092
		if (! empty($code)) $sql.= " AND tc.code='".$this->db->escape($code)."'";
1093
		$sql.= $this->db->order($order,'ASC');
1094
1095
		//print "sql=".$sql;
1096
		$resql=$this->db->query($sql);
1097
		if ($resql)
1098
		{
1099
			$num=$this->db->num_rows($resql);
1100
			$i=0;
1101
			while ($i < $num)
1102
			{
1103
				$obj = $this->db->fetch_object($resql);
1104
1105
				$transkey="TypeContact_".$this->element."_".$source."_".$obj->code;
1106
				$libelle_type=($langs->trans($transkey)!=$transkey ? $langs->trans($transkey) : $obj->libelle);
1107
				if (empty($option)) $tab[$obj->rowid]=$libelle_type;
1108
				else $tab[$obj->code]=$libelle_type;
1109
				$i++;
1110
			}
1111
			return $tab;
1112
		}
1113
		else
1114
		{
1115
			$this->error=$this->db->lasterror();
1116
			//dol_print_error($this->db);
1117
			return null;
1118
		}
1119
	}
1120
1121
	/**
1122
	 *      Return id of contacts for a source and a contact code.
1123
	 *      Example: contact client de facturation ('external', 'BILLING')
1124
	 *      Example: contact client de livraison ('external', 'SHIPPING')
1125
	 *      Example: contact interne suivi paiement ('internal', 'SALESREPFOLL')
1126
	 *
1127
	 *		@param	string	$source		'external' or 'internal'
1128
	 *		@param	string	$code		'BILLING', 'SHIPPING', 'SALESREPFOLL', ...
1129
	 *		@param	int		$status		limited to a certain status
1130
	 *      @return array       		List of id for such contacts
1131
	 */
1132
	function getIdContact($source,$code,$status=0)
1133
	{
1134
		global $conf;
1135
1136
		$result=array();
1137
		$i=0;
1138
		//cas particulier pour les expeditions
1139
		if($this->element=='shipping' && $this->origin_id != 0) {
1140
			$id=$this->origin_id;
1141
			$element='commande';
1142
        } else if($this->element=='reception' && $this->origin_id != 0) {
1143
            $id=$this->origin_id;
1144
            $element='order_supplier';
1145
		} else {
1146
			$id=$this->id;
1147
			$element=$this->element;
1148
		}
1149
1150
		$sql = "SELECT ec.fk_socpeople";
1151
		$sql.= " FROM ".MAIN_DB_PREFIX."element_contact as ec,";
1152
		if ($source == 'internal') $sql.= " ".MAIN_DB_PREFIX."user as c,";
1153
		if ($source == 'external') $sql.= " ".MAIN_DB_PREFIX."socpeople as c,";
1154
		$sql.= " ".MAIN_DB_PREFIX."c_type_contact as tc";
1155
		$sql.= " WHERE ec.element_id = ".$id;
1156
		$sql.= " AND ec.fk_socpeople = c.rowid";
1157
		if ($source == 'internal') $sql.= " AND c.entity IN (".getEntity('user').")";
1158
		if ($source == 'external') $sql.= " AND c.entity IN (".getEntity('societe').")";
1159
		$sql.= " AND ec.fk_c_type_contact = tc.rowid";
1160
		$sql.= " AND tc.element = '".$element."'";
1161
		$sql.= " AND tc.source = '".$source."'";
1162
		$sql.= " AND tc.code = '".$code."'";
1163
		$sql.= " AND tc.active = 1";
1164
		if ($status) $sql.= " AND ec.statut = ".$status;
1165
1166
		dol_syslog(get_class($this)."::getIdContact", LOG_DEBUG);
1167
		$resql=$this->db->query($sql);
1168
		if ($resql)
1169
		{
1170
			while ($obj = $this->db->fetch_object($resql))
1171
			{
1172
				$result[$i]=$obj->fk_socpeople;
1173
				$i++;
1174
			}
1175
		}
1176
		else
1177
		{
1178
			$this->error=$this->db->error();
1179
			return null;
1180
		}
1181
1182
		return $result;
1183
	}
1184
1185
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1186
	/**
1187
	 *		Load object contact with id=$this->contactid into $this->contact
1188
	 *
1189
	 *		@param	int		$contactid      Id du contact. Use this->contactid if empty.
1190
	 *		@return	int						<0 if KO, >0 if OK
1191
	 */
1192
	function fetch_contact($contactid=null)
1193
	{
1194
        // phpcs:enable
1195
		if (empty($contactid)) $contactid=$this->contactid;
1196
1197
		if (empty($contactid)) return 0;
1198
1199
		require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
1200
		$contact = new Contact($this->db);
1201
		$result=$contact->fetch($contactid);
1202
		$this->contact = $contact;
1203
		return $result;
1204
	}
1205
1206
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1207
	/**
1208
	 *    	Load the third party of object, from id $this->socid or $this->fk_soc, into this->thirdparty
1209
	 *
1210
	 *		@param		int		$force_thirdparty_id	Force thirdparty id
1211
	 *		@return		int								<0 if KO, >0 if OK
1212
	 */
1213
	function fetch_thirdparty($force_thirdparty_id=0)
1214
	{
1215
        // phpcs:enable
1216
		global $conf;
1217
1218
		if (empty($this->socid) && empty($this->fk_soc) && empty($this->fk_thirdparty) && empty($force_thirdparty_id))
1219
			return 0;
1220
1221
		require_once DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php';
1222
1223
		$idtofetch = isset($this->socid) ? $this->socid : (isset($this->fk_soc) ? $this->fk_soc : $this->fk_thirdparty);
1224
		if ($force_thirdparty_id)
1225
			$idtofetch = $force_thirdparty_id;
1226
1227
		if ($idtofetch) {
1228
			$thirdparty = new Societe($this->db);
1229
			$result = $thirdparty->fetch($idtofetch);
1230
			$this->thirdparty = $thirdparty;
1231
1232
			// Use first price level if level not defined for third party
1233
			if (!empty($conf->global->PRODUIT_MULTIPRICES) && empty($this->thirdparty->price_level)) {
1234
				$this->thirdparty->price_level = 1;
1235
			}
1236
1237
			return $result;
1238
		} else
1239
			return -1;
1240
	}
1241
1242
1243
	/**
1244
	 * Looks for an object with ref matching the wildcard provided
1245
	 * It does only work when $this->table_ref_field is set
1246
	 *
1247
	 * @param string $ref Wildcard
1248
	 * @return int >1 = OK, 0 = Not found or table_ref_field not defined, <0 = KO
1249
	 */
1250
	public function fetchOneLike($ref)
1251
	{
1252
		if (!$this->table_ref_field) {
1253
			return 0;
1254
		}
1255
1256
		$sql = 'SELECT rowid FROM '.MAIN_DB_PREFIX.$this->table_element.' WHERE '.$this->table_ref_field.' LIKE "'.$this->db->escape($ref).'" LIMIT 1';
1257
1258
		$query = $this->db->query($sql);
1259
1260
		if (!$this->db->num_rows($query)) {
1261
			return 0;
1262
		}
1263
1264
		$result = $this->db->fetch_object($query);
1265
1266
		return $this->fetch($result->rowid);
1267
	}
1268
1269
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1270
	/**
1271
	 *	Load data for barcode into properties ->barcode_type*
1272
	 *	Properties ->barcode_type that is id of barcode. Type is used to find other properties, but
1273
	 *  if it is not defined, ->element must be defined to know default barcode type.
1274
	 *
1275
	 *	@return		int			<0 if KO, 0 if can't guess type of barcode (ISBN, EAN13...), >0 if OK (all barcode properties loaded)
1276
	 */
1277
	function fetch_barcode()
1278
	{
1279
        // phpcs:enable
1280
		global $conf;
1281
1282
		dol_syslog(get_class($this).'::fetch_barcode this->element='.$this->element.' this->barcode_type='.$this->barcode_type);
1283
1284
		$idtype=$this->barcode_type;
1285
		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
1286
		{
1287
			if ($this->element == 'product')      $idtype = $conf->global->PRODUIT_DEFAULT_BARCODE_TYPE;
1288
			else if ($this->element == 'societe') $idtype = $conf->global->GENBARCODE_BARCODETYPE_THIRDPARTY;
1289
			else dol_syslog('Call fetch_barcode with barcode_type not defined and cant be guessed', LOG_WARNING);
1290
		}
1291
1292
		if ($idtype > 0)
1293
		{
1294
			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
1295
			{
1296
				$sql = "SELECT rowid, code, libelle as label, coder";
1297
				$sql.= " FROM ".MAIN_DB_PREFIX."c_barcode_type";
1298
				$sql.= " WHERE rowid = ".$idtype;
1299
				dol_syslog(get_class($this).'::fetch_barcode', LOG_DEBUG);
1300
				$resql = $this->db->query($sql);
1301
				if ($resql)
1302
				{
1303
					$obj = $this->db->fetch_object($resql);
1304
					$this->barcode_type       = $obj->rowid;
1305
					$this->barcode_type_code  = $obj->code;
1306
					$this->barcode_type_label = $obj->label;
1307
					$this->barcode_type_coder = $obj->coder;
1308
					return 1;
1309
				}
1310
				else
1311
				{
1312
					dol_print_error($this->db);
1313
					return -1;
1314
				}
1315
			}
1316
		}
1317
		return 0;
1318
	}
1319
1320
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1321
	/**
1322
	 *		Load the project with id $this->fk_project into this->project
1323
	 *
1324
	 *		@return		int			<0 if KO, >=0 if OK
1325
	 */
1326
	function fetch_projet()
1327
	{
1328
        // phpcs:enable
1329
		include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
1330
1331
		if (empty($this->fk_project) && ! empty($this->fk_projet)) $this->fk_project = $this->fk_projet;	// For backward compatibility
1332
		if (empty($this->fk_project)) return 0;
1333
1334
		$project = new Project($this->db);
1335
		$result = $project->fetch($this->fk_project);
1336
1337
		$this->projet = $project;	// deprecated
1338
		$this->project = $project;
1339
		return $result;
1340
	}
1341
1342
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1343
	/**
1344
	 *		Load the product with id $this->fk_product into this->product
1345
	 *
1346
	 *		@return		int			<0 if KO, >=0 if OK
1347
	 */
1348
	function fetch_product()
1349
	{
1350
        // phpcs:enable
1351
		include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
1352
1353
		if (empty($this->fk_product)) return 0;
1354
1355
		$product = new Product($this->db);
1356
		$result = $product->fetch($this->fk_product);
1357
1358
		$this->product = $product;
1359
		return $result;
1360
	}
1361
1362
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1363
	/**
1364
	 *		Load the user with id $userid into this->user
1365
	 *
1366
	 *		@param	int		$userid 		Id du contact
1367
	 *		@return	int						<0 if KO, >0 if OK
1368
	 */
1369
	function fetch_user($userid)
1370
	{
1371
        // phpcs:enable
1372
		$user = new User($this->db);
1373
		$result=$user->fetch($userid);
1374
		$this->user = $user;
1375
		return $result;
1376
	}
1377
1378
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1379
	/**
1380
	 *	Read linked origin object
1381
	 *
1382
	 *	@return		void
1383
	 */
1384
	function fetch_origin()
1385
	{
1386
        // phpcs:enable
1387
		if ($this->origin == 'shipping') $this->origin = 'expedition';
1388
		if ($this->origin == 'delivery') $this->origin = 'livraison';
1389
        if ($this->origin == 'order_supplier') $this->origin = 'commandeFournisseur';
1390
1391
		$origin = $this->origin;
1392
1393
		$classname = ucfirst($origin);
1394
		$this->$origin = new $classname($this->db);
1395
		$this->$origin->fetch($this->origin_id);
1396
	}
1397
1398
	/**
1399
     *  Load object from specific field
1400
     *
1401
     *  @param	string	$table		Table element or element line
1402
     *  @param	string	$field		Field selected
1403
     *  @param	string	$key		Import key
1404
     *  @param	string	$element	Element name
1405
     *	@return	int					<0 if KO, >0 if OK
1406
     */
1407
	function fetchObjectFrom($table, $field, $key, $element = null)
1408
	{
1409
		global $conf;
1410
1411
		$result=false;
1412
1413
		$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX.$table;
1414
		$sql.= " WHERE ".$field." = '".$key."'";
1415
		if (! empty($element)) {
1416
			$sql.= " AND entity IN (".getEntity($element).")";
1417
		} else {
1418
			$sql.= " AND entity = ".$conf->entity;
1419
		}
1420
1421
		dol_syslog(get_class($this).'::fetchObjectFrom', LOG_DEBUG);
1422
		$resql = $this->db->query($sql);
1423
		if ($resql)
1424
		{
1425
			$row = $this->db->fetch_row($resql);
1426
			// Test for avoid error -1
1427
			if ($row[0] > 0) {
1428
				$result = $this->fetch($row[0]);
1429
			}
1430
		}
1431
1432
		return $result;
1433
	}
1434
1435
	/**
1436
	 *	Getter generic. Load value from a specific field
1437
	 *
1438
	 *	@param	string	$table		Table of element or element line
1439
	 *	@param	int		$id			Element id
1440
	 *	@param	string	$field		Field selected
1441
	 *	@return	int					<0 if KO, >0 if OK
1442
	 */
1443
	function getValueFrom($table, $id, $field)
1444
	{
1445
		$result=false;
1446
		if (!empty($id) && !empty($field) && !empty($table)) {
1447
			$sql = "SELECT ".$field." FROM ".MAIN_DB_PREFIX.$table;
1448
			$sql.= " WHERE rowid = ".$id;
1449
1450
			dol_syslog(get_class($this).'::getValueFrom', LOG_DEBUG);
1451
			$resql = $this->db->query($sql);
1452
			if ($resql)
1453
			{
1454
				$row = $this->db->fetch_row($resql);
1455
				$result = $row[0];
1456
			}
1457
		}
1458
		return $result;
1459
	}
1460
1461
	/**
1462
	 *	Setter generic. Update a specific field into database.
1463
	 *  Warning: Trigger is run only if param trigkey is provided.
1464
	 *
1465
	 *	@param	string		$field			Field to update
1466
	 *	@param	mixed		$value			New value
1467
	 *	@param	string		$table			To force other table element or element line (should not be used)
1468
	 *	@param	int			$id				To force other object id (should not be used)
1469
	 *	@param	string		$format			Data format ('text', 'date'). 'text' is used if not defined
1470
	 *	@param	string		$id_field		To force rowid field name. 'rowid' is used if not defined
1471
	 *	@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'
1472
	 *  @param  string      $trigkey    	Trigger key to run (in most cases something like 'XXX_MODIFY')
1473
	 *  @param	string		$fk_user_field	Name of field to save user id making change
1474
	 *	@return	int							<0 if KO, >0 if OK
1475
	 *  @see updateExtraField
1476
	 */
1477
	function setValueFrom($field, $value, $table='', $id=null, $format='', $id_field='', $fuser=null, $trigkey='', $fk_user_field='fk_user_modif')
1478
	{
1479
		global $user,$langs,$conf;
1480
1481
		if (empty($table)) 	  $table=$this->table_element;
1482
		if (empty($id))    	  $id=$this->id;
1483
		if (empty($format))   $format='text';
1484
		if (empty($id_field)) $id_field='rowid';
1485
1486
		$error=0;
1487
1488
		$this->db->begin();
1489
1490
		// Special case
1491
		if ($table == 'product' && $field == 'note_private') $field='note';
1492
		if (in_array($table, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) $fk_user_field = 'fk_user_mod';
1493
1494
		$sql = "UPDATE ".MAIN_DB_PREFIX.$table." SET ";
1495
1496
		if ($format == 'text') $sql.= $field." = '".$this->db->escape($value)."'";
1497
		else if ($format == 'int') $sql.= $field." = ".$this->db->escape($value);
1498
		else if ($format == 'date') $sql.= $field." = ".($value ? "'".$this->db->idate($value)."'" : "null");
1499
1500
		if ($fk_user_field)
1501
		{
1502
			if (! empty($fuser) && is_object($fuser)) $sql.=", ".$fk_user_field." = ".$fuser->id;
1503
			elseif (empty($fuser) || $fuser != 'none') $sql.=", ".$fk_user_field." = ".$user->id;
1504
		}
1505
1506
		$sql.= " WHERE ".$id_field." = ".$id;
1507
1508
		dol_syslog(get_class($this)."::".__FUNCTION__."", LOG_DEBUG);
1509
		$resql = $this->db->query($sql);
1510
		if ($resql)
1511
		{
1512
			if ($trigkey)
1513
			{
1514
				// call trigger with updated object values
1515
				if (empty($this->fields) && method_exists($this, 'fetch'))
1516
				{
1517
					$result = $this->fetch($id);
1518
				}
1519
				else
1520
				{
1521
					$result = $this->fetchCommon($id);
1522
				}
1523
				if ($result >= 0) $result=$this->call_trigger($trigkey, (! empty($fuser) && is_object($fuser)) ? $fuser : $user);   // This may set this->errors
1524
				if ($result < 0) $error++;
1525
			}
1526
1527
			if (! $error)
1528
			{
1529
				if (property_exists($this, $field)) $this->$field = $value;
1530
				$this->db->commit();
1531
				return 1;
1532
			}
1533
			else
1534
			{
1535
				$this->db->rollback();
1536
				return -2;
1537
			}
1538
		}
1539
		else
1540
		{
1541
			$this->error=$this->db->lasterror();
1542
			$this->db->rollback();
1543
			return -1;
1544
		}
1545
	}
1546
1547
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1548
	/**
1549
	 *      Load properties id_previous and id_next by comparing $fieldid with $this->ref
1550
	 *
1551
	 *      @param	string	$filter		Optional filter. Example: " AND (t.field1 = 'aa' OR t.field2 = 'bb')"
1552
	 *	 	@param  string	$fieldid   	Name of field to use for the select MAX and MIN
1553
	 *		@param	int		$nodbprefix	Do not include DB prefix to forge table name
1554
	 *      @return int         		<0 if KO, >0 if OK
1555
	 */
1556
	function load_previous_next_ref($filter, $fieldid, $nodbprefix=0)
1557
	{
1558
        // phpcs:enable
1559
		global $conf, $user;
1560
1561
		if (! $this->table_element)
1562
		{
1563
			dol_print_error('',get_class($this)."::load_previous_next_ref was called on objet with property table_element not defined");
1564
			return -1;
1565
		}
1566
		if ($fieldid == 'none') return 1;
1567
1568
		// Security on socid
1569
		$socid = 0;
1570
		if ($user->societe_id > 0) $socid = $user->societe_id;
1571
1572
		// this->ismultientitymanaged contains
1573
		// 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
1574
		$alias = 's';
1575
		if ($this->element == 'societe') $alias = 'te';
1576
1577
		$sql = "SELECT MAX(te.".$fieldid.")";
1578
		$sql.= " FROM ".(empty($nodbprefix)?MAIN_DB_PREFIX:'').$this->table_element." as te";
1579
		if ($this->element == 'user' && ! empty($conf->global->MULTICOMPANY_TRANSVERSE_MODE)) {
1580
			$sql.= ",".MAIN_DB_PREFIX."usergroup_user as ug";
1581
		}
1582
		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
1583
		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
1584
		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
1585
		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";
1586
		$sql.= " WHERE te.".$fieldid." < '".$this->db->escape($this->ref)."'";  // ->ref must always be defined (set to id if field does not exists)
1587
		if ($this->restrictiononfksoc == 1 && !$user->rights->societe->client->voir && !$socid) $sql.= " AND sc.fk_user = " .$user->id;
1588
		if ($this->restrictiononfksoc == 2 && !$user->rights->societe->client->voir && !$socid) $sql.= " AND (sc.fk_user = " .$user->id.' OR te.fk_soc IS NULL)';
1589
		if (! empty($filter))
1590
		{
1591
			if (! preg_match('/^\s*AND/i', $filter)) $sql.=" AND ";   // For backward compatibility
1592
			$sql.=$filter;
1593
		}
1594
		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
1595
		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
1596
		if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
1597
			if ($this->element == 'user' && ! empty($conf->global->MULTICOMPANY_TRANSVERSE_MODE)) {
1598
				if (! empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
1599
					$sql.= " AND te.entity IS NOT NULL"; // Show all users
1600
				} else {
1601
					$sql.= " AND ug.fk_user = te.rowid";
1602
					$sql.= " AND ug.entity IN (".getEntity($this->element).")";
1603
				}
1604
			} else {
1605
				$sql.= ' AND te.entity IN ('.getEntity($this->element).')';
1606
			}
1607
		}
1608
		if ($this->restrictiononfksoc == 1 && $socid && $this->element != 'societe') $sql.= ' AND te.fk_soc = ' . $socid;
1609
		if ($this->restrictiononfksoc == 2 && $socid && $this->element != 'societe') $sql.= ' AND (te.fk_soc = ' . $socid.' OR te.fk_soc IS NULL)';
1610
		if ($this->restrictiononfksoc && $socid && $this->element == 'societe') $sql.= ' AND te.rowid = ' . $socid;
1611
		//print 'socid='.$socid.' restrictiononfksoc='.$this->restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
1612
1613
		$result = $this->db->query($sql);
1614
		if (! $result)
1615
		{
1616
			$this->error=$this->db->lasterror();
1617
			return -1;
1618
		}
1619
		$row = $this->db->fetch_row($result);
1620
		$this->ref_previous = $row[0];
1621
1622
1623
		$sql = "SELECT MIN(te.".$fieldid.")";
1624
		$sql.= " FROM ".(empty($nodbprefix)?MAIN_DB_PREFIX:'').$this->table_element." as te";
1625
		if ($this->element == 'user' && ! empty($conf->global->MULTICOMPANY_TRANSVERSE_MODE)) {
1626
			$sql.= ",".MAIN_DB_PREFIX."usergroup_user as ug";
1627
		}
1628
		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
1629
		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
1630
		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
1631
		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";
1632
		$sql.= " WHERE te.".$fieldid." > '".$this->db->escape($this->ref)."'";  // ->ref must always be defined (set to id if field does not exists)
1633
		if ($this->restrictiononfksoc == 1 && !$user->rights->societe->client->voir && !$socid) $sql.= " AND sc.fk_user = " .$user->id;
1634
		if ($this->restrictiononfksoc == 2 && !$user->rights->societe->client->voir && !$socid) $sql.= " AND (sc.fk_user = " .$user->id.' OR te.fk_soc IS NULL)';
1635
		if (! empty($filter))
1636
		{
1637
			if (! preg_match('/^\s*AND/i', $filter)) $sql.=" AND ";   // For backward compatibility
1638
			$sql.=$filter;
1639
		}
1640
		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
1641
		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
1642
		if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
1643
			if ($this->element == 'user' && ! empty($conf->global->MULTICOMPANY_TRANSVERSE_MODE)) {
1644
				if (! empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
1645
					$sql.= " AND te.entity IS NOT NULL"; // Show all users
1646
				} else {
1647
					$sql.= " AND ug.fk_user = te.rowid";
1648
					$sql.= " AND ug.entity IN (".getEntity($this->element).")";
1649
				}
1650
			} else {
1651
				$sql.= ' AND te.entity IN ('.getEntity($this->element).')';
1652
			}
1653
		}
1654
		if ($this->restrictiononfksoc == 1 && $socid && $this->element != 'societe') $sql.= ' AND te.fk_soc = ' . $socid;
1655
		if ($this->restrictiononfksoc == 2 && $socid && $this->element != 'societe') $sql.= ' AND (te.fk_soc = ' . $socid.' OR te.fk_soc IS NULL)';
1656
		if ($this->restrictiononfksoc && $socid && $this->element == 'societe') $sql.= ' AND te.rowid = ' . $socid;
1657
		//print 'socid='.$socid.' restrictiononfksoc='.$this->restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
1658
		// 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
1659
1660
		$result = $this->db->query($sql);
1661
		if (! $result)
1662
		{
1663
			$this->error=$this->db->lasterror();
1664
			return -2;
1665
		}
1666
		$row = $this->db->fetch_row($result);
1667
		$this->ref_next = $row[0];
1668
1669
		return 1;
1670
	}
1671
1672
1673
	/**
1674
	 *      Return list of id of contacts of object
1675
	 *
1676
	 *      @param	string	$source     Source of contact: external (llx_socpeople) or internal (llx_user) or thirdparty (llx_societe)
1677
	 *      @return array				Array of id of contacts (if source=external or internal)
1678
	 * 									Array of id of third parties with at least one contact on object (if source=thirdparty)
1679
	 */
1680
	function getListContactId($source='external')
1681
	{
1682
		$contactAlreadySelected = array();
1683
		$tab = $this->liste_contact(-1,$source);
1684
		$num=count($tab);
1685
		$i = 0;
1686
		while ($i < $num)
1687
		{
1688
			if ($source == 'thirdparty') $contactAlreadySelected[$i] = $tab[$i]['socid'];
1689
			else  $contactAlreadySelected[$i] = $tab[$i]['id'];
1690
			$i++;
1691
		}
1692
		return $contactAlreadySelected;
1693
	}
1694
1695
1696
	/**
1697
	 *	Link element with a project
1698
	 *
1699
	 *	@param     	int		$projectid		Project id to link element to
1700
	 *	@return		int						<0 if KO, >0 if OK
1701
	 */
1702
	function setProject($projectid)
1703
	{
1704
		if (! $this->table_element)
1705
		{
1706
			dol_syslog(get_class($this)."::setProject was called on objet with property table_element not defined",LOG_ERR);
1707
			return -1;
1708
		}
1709
1710
		$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1711
		if ($this->table_element == 'actioncomm')
1712
		{
1713
			if ($projectid) $sql.= ' SET fk_project = '.$projectid;
1714
			else $sql.= ' SET fk_project = NULL';
1715
			$sql.= ' WHERE id = '.$this->id;
1716
		}
1717
		else
1718
		{
1719
			if ($projectid) $sql.= ' SET fk_projet = '.$projectid;
1720
			else $sql.= ' SET fk_projet = NULL';
1721
			$sql.= ' WHERE rowid = '.$this->id;
1722
		}
1723
1724
		dol_syslog(get_class($this)."::setProject", LOG_DEBUG);
1725
		if ($this->db->query($sql))
1726
		{
1727
			$this->fk_project = $projectid;
1728
			return 1;
1729
		}
1730
		else
1731
		{
1732
			dol_print_error($this->db);
1733
			return -1;
1734
		}
1735
	}
1736
1737
	/**
1738
	 *  Change the payments methods
1739
	 *
1740
	 *  @param		int		$id		Id of new payment method
1741
	 *  @return		int				>0 if OK, <0 if KO
1742
	 */
1743
	function setPaymentMethods($id)
1744
	{
1745
		dol_syslog(get_class($this).'::setPaymentMethods('.$id.')');
1746
		if ($this->statut >= 0 || $this->element == 'societe')
1747
		{
1748
			// TODO uniformize field name
1749
			$fieldname = 'fk_mode_reglement';
1750
			if ($this->element == 'societe') $fieldname = 'mode_reglement';
1751
			if (get_class($this) == 'Fournisseur') $fieldname = 'mode_reglement_supplier';
1752
1753
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1754
			$sql .= ' SET '.$fieldname.' = '.$id;
1755
			$sql .= ' WHERE rowid='.$this->id;
1756
1757
			if ($this->db->query($sql))
1758
			{
1759
				$this->mode_reglement_id = $id;
1760
				// for supplier
1761
				if (get_class($this) == 'Fournisseur') $this->mode_reglement_supplier_id = $id;
1762
				return 1;
1763
			}
1764
			else
1765
			{
1766
				dol_syslog(get_class($this).'::setPaymentMethods Erreur '.$sql.' - '.$this->db->error());
1767
				$this->error=$this->db->error();
1768
				return -1;
1769
			}
1770
		}
1771
		else
1772
		{
1773
			dol_syslog(get_class($this).'::setPaymentMethods, status of the object is incompatible');
1774
			$this->error='Status of the object is incompatible '.$this->statut;
1775
			return -2;
1776
		}
1777
	}
1778
1779
	/**
1780
	 *  Change the multicurrency code
1781
	 *
1782
	 *  @param		string	$code	multicurrency code
1783
	 *  @return		int				>0 if OK, <0 if KO
1784
	 */
1785
	function setMulticurrencyCode($code)
1786
	{
1787
		dol_syslog(get_class($this).'::setMulticurrencyCode('.$id.')');
1788
		if ($this->statut >= 0 || $this->element == 'societe')
1789
		{
1790
			$fieldname = 'multicurrency_code';
1791
1792
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1793
			$sql .= ' SET '.$fieldname." = '".$this->db->escape($code)."'";
1794
			$sql .= ' WHERE rowid='.$this->id;
1795
1796
			if ($this->db->query($sql))
1797
			{
1798
				$this->multicurrency_code = $code;
1799
1800
				list($fk_multicurrency, $rate) = MultiCurrency::getIdAndTxFromCode($this->db, $code);
1801
				if ($rate) $this->setMulticurrencyRate($rate,2);
1802
1803
				return 1;
1804
			}
1805
			else
1806
			{
1807
				dol_syslog(get_class($this).'::setMulticurrencyCode Erreur '.$sql.' - '.$this->db->error());
1808
				$this->error=$this->db->error();
1809
				return -1;
1810
			}
1811
		}
1812
		else
1813
		{
1814
			dol_syslog(get_class($this).'::setMulticurrencyCode, status of the object is incompatible');
1815
			$this->error='Status of the object is incompatible '.$this->statut;
1816
			return -2;
1817
		}
1818
	}
1819
1820
	/**
1821
	 *  Change the multicurrency rate
1822
	 *
1823
	 *  @param		double	$rate	multicurrency rate
1824
	 *  @param		int		$mode	mode 1 : amounts in company currency will be recalculated, mode 2 : amounts in foreign currency
1825
	 *  @return		int				>0 if OK, <0 if KO
1826
	 */
1827
	function setMulticurrencyRate($rate, $mode=1)
1828
	{
1829
		dol_syslog(get_class($this).'::setMulticurrencyRate('.$id.')');
1830
		if ($this->statut >= 0 || $this->element == 'societe')
1831
		{
1832
			$fieldname = 'multicurrency_tx';
1833
1834
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1835
			$sql .= ' SET '.$fieldname.' = '.$rate;
1836
			$sql .= ' WHERE rowid='.$this->id;
1837
1838
			if ($this->db->query($sql))
1839
			{
1840
				$this->multicurrency_tx = $rate;
1841
1842
				// Update line price
1843
				if (!empty($this->lines))
1844
				{
1845
					foreach ($this->lines as &$line)
1846
					{
1847
						if($mode == 1) {
1848
							$line->subprice = 0;
1849
						}
1850
1851
						switch ($this->element) {
1852
							case 'propal':
1853
								$this->updateline(
1854
									$line->id, $line->subprice, $line->qty, $line->remise_percent, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx,
1855
									($line->description?$line->description:$line->desc), 'HT', $line->info_bits, $line->special_code, $line->fk_parent_line,
1856
									$line->skip_update_total, $line->fk_fournprice, $line->pa_ht, $line->label, $line->product_type, $line->date_start,
1857
									$line->date_end, $line->array_options, $line->fk_unit, $line->multicurrency_subprice
1858
								);
1859
								break;
1860
							case 'commande':
1861
								$this->updateline(
1862
									$line->id, ($line->description?$line->description:$line->desc), $line->subprice, $line->qty, $line->remise_percent,
1863
									$line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, 'HT', $line->info_bits, $line->date_start, $line->date_end,
1864
									$line->product_type, $line->fk_parent_line, $line->skip_update_total, $line->fk_fournprice, $line->pa_ht, $line->label,
1865
									$line->special_code, $line->array_options, $line->fk_unit, $line->multicurrency_subprice
1866
								);
1867
								break;
1868
							case 'facture':
1869
								$this->updateline(
1870
									$line->id, ($line->description?$line->description:$line->desc), $line->subprice, $line->qty, $line->remise_percent,
1871
									$line->date_start, $line->date_end, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, 'HT', $line->info_bits,
1872
									$line->product_type, $line->fk_parent_line, $line->skip_update_total, $line->fk_fournprice, $line->pa_ht, $line->label,
1873
									$line->special_code, $line->array_options, $line->situation_percent, $line->fk_unit, $line->multicurrency_subprice
1874
								);
1875
								break;
1876
							case 'supplier_proposal':
1877
								$this->updateline(
1878
									$line->id, $line->subprice, $line->qty, $line->remise_percent, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx,
1879
									($line->description?$line->description:$line->desc), 'HT', $line->info_bits, $line->special_code, $line->fk_parent_line,
1880
									$line->skip_update_total, $line->fk_fournprice, $line->pa_ht, $line->label, $line->product_type, $line->array_options,
1881
									$line->ref_fourn, $line->multicurrency_subprice
1882
								);
1883
								break;
1884
							case 'order_supplier':
1885
								$this->updateline(
1886
									$line->id, ($line->description?$line->description:$line->desc), $line->subprice, $line->qty, $line->remise_percent,
1887
									$line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, 'HT', $line->info_bits, $line->product_type, false,
1888
									$line->date_start, $line->date_end, $line->array_options, $line->fk_unit, $line->multicurrency_subprice
1889
								);
1890
								break;
1891
							case 'invoice_supplier':
1892
								$this->updateline(
1893
									$line->id, ($line->description?$line->description:$line->desc), $line->subprice, $line->tva_tx, $line->localtax1_tx,
1894
									$line->localtax2_tx, $line->qty, 0, 'HT', $line->info_bits, $line->product_type, $line->remise_percent, false,
1895
									$line->date_start, $line->date_end, $line->array_options, $line->fk_unit, $line->multicurrency_subprice
1896
								);
1897
								break;
1898
							default:
1899
								dol_syslog(get_class($this).'::setMulticurrencyRate no updateline defined', LOG_DEBUG);
1900
								break;
1901
						}
1902
					}
1903
				}
1904
1905
				return 1;
1906
			}
1907
			else
1908
			{
1909
				dol_syslog(get_class($this).'::setMulticurrencyRate Erreur '.$sql.' - '.$this->db->error());
1910
				$this->error=$this->db->error();
1911
				return -1;
1912
			}
1913
		}
1914
		else
1915
		{
1916
			dol_syslog(get_class($this).'::setMulticurrencyRate, status of the object is incompatible');
1917
			$this->error='Status of the object is incompatible '.$this->statut;
1918
			return -2;
1919
		}
1920
	}
1921
1922
	/**
1923
	 *  Change the payments terms
1924
	 *
1925
	 *  @param		int		$id		Id of new payment terms
1926
	 *  @return		int				>0 if OK, <0 if KO
1927
	 */
1928
	function setPaymentTerms($id)
1929
	{
1930
		dol_syslog(get_class($this).'::setPaymentTerms('.$id.')');
1931
		if ($this->statut >= 0 || $this->element == 'societe')
1932
		{
1933
			// TODO uniformize field name
1934
			$fieldname = 'fk_cond_reglement';
1935
			if ($this->element == 'societe') $fieldname = 'cond_reglement';
1936
			if (get_class($this) == 'Fournisseur') $fieldname = 'cond_reglement_supplier';
1937
1938
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1939
			$sql .= ' SET '.$fieldname.' = '.$id;
1940
			$sql .= ' WHERE rowid='.$this->id;
1941
1942
			if ($this->db->query($sql))
1943
			{
1944
				$this->cond_reglement_id = $id;
1945
				// for supplier
1946
				if (get_class($this) == 'Fournisseur') $this->cond_reglement_supplier_id = $id;
1947
				$this->cond_reglement = $id;	// for compatibility
1948
				return 1;
1949
			}
1950
			else
1951
			{
1952
				dol_syslog(get_class($this).'::setPaymentTerms Erreur '.$sql.' - '.$this->db->error());
1953
				$this->error=$this->db->error();
1954
				return -1;
1955
			}
1956
		}
1957
		else
1958
		{
1959
			dol_syslog(get_class($this).'::setPaymentTerms, status of the object is incompatible');
1960
			$this->error='Status of the object is incompatible '.$this->statut;
1961
			return -2;
1962
		}
1963
	}
1964
1965
	/**
1966
	 *	Define delivery address
1967
	 *  @deprecated
1968
	 *
1969
	 *	@param      int		$id		Address id
1970
	 *	@return     int				<0 si ko, >0 si ok
1971
	 */
1972
	function setDeliveryAddress($id)
1973
	{
1974
		$fieldname = 'fk_delivery_address';
1975
		if ($this->element == 'delivery' || $this->element == 'shipping') $fieldname = 'fk_address';
1976
1977
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET ".$fieldname." = ".$id;
1978
		$sql.= " WHERE rowid = ".$this->id." AND fk_statut = 0";
1979
1980
		if ($this->db->query($sql))
1981
		{
1982
			$this->fk_delivery_address = $id;
1983
			return 1;
1984
		}
1985
		else
1986
		{
1987
			$this->error=$this->db->error();
1988
			dol_syslog(get_class($this).'::setDeliveryAddress Erreur '.$sql.' - '.$this->error);
1989
			return -1;
1990
		}
1991
	}
1992
1993
1994
	/**
1995
	 *  Change the shipping method
1996
	 *
1997
	 *  @param      int     $shipping_method_id     Id of shipping method
1998
     *  @param      bool    $notrigger              false=launch triggers after, true=disable triggers
1999
     *  @param      User	$userused               Object user
2000
	 *
2001
	 *  @return     int              1 if OK, 0 if KO
2002
	 */
2003
	function setShippingMethod($shipping_method_id, $notrigger=false, $userused=null)
2004
	{
2005
        global $user;
2006
2007
        if (empty($userused)) $userused=$user;
2008
2009
        $error = 0;
2010
2011
		if (! $this->table_element) {
2012
			dol_syslog(get_class($this)."::setShippingMethod was called on objet with property table_element not defined",LOG_ERR);
2013
			return -1;
2014
		}
2015
2016
        $this->db->begin();
2017
2018
		if ($shipping_method_id<0) $shipping_method_id='NULL';
2019
		dol_syslog(get_class($this).'::setShippingMethod('.$shipping_method_id.')');
2020
2021
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2022
		$sql.= " SET fk_shipping_method = ".$shipping_method_id;
2023
		$sql.= " WHERE rowid=".$this->id;
2024
        $resql = $this->db->query($sql);
2025
		if (! $resql) {
2026
			dol_syslog(get_class($this).'::setShippingMethod Error ', LOG_DEBUG);
2027
			$this->error = $this->db->lasterror();
2028
			$error++;
2029
        } else {
2030
            if (!$notrigger)
2031
            {
2032
                // Call trigger
2033
                $this->context=array('shippingmethodupdate'=>1);
2034
                $result = $this->call_trigger(strtoupper(get_class($this)) . '_MODIFY', $userused);
2035
                if ($result < 0) $error++;
2036
                // End call trigger
2037
            }
2038
        }
2039
        if ($error)
2040
        {
2041
            $this->db->rollback();
2042
            return -1;
2043
        } else {
2044
            $this->shipping_method_id = ($shipping_method_id=='NULL')?null:$shipping_method_id;
2045
            $this->db->commit();
2046
            return 1;
2047
        }
2048
	}
2049
2050
2051
	/**
2052
	 *  Change the warehouse
2053
	 *
2054
	 *  @param      int     $warehouse_id     Id of warehouse
2055
	 *  @return     int              1 if OK, 0 if KO
2056
	 */
2057
	function setWarehouse($warehouse_id)
2058
	{
2059
		if (! $this->table_element) {
2060
			dol_syslog(get_class($this)."::setWarehouse was called on objet with property table_element not defined",LOG_ERR);
2061
			return -1;
2062
		}
2063
		if ($warehouse_id<0) $warehouse_id='NULL';
2064
		dol_syslog(get_class($this).'::setWarehouse('.$warehouse_id.')');
2065
2066
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2067
		$sql.= " SET fk_warehouse = ".$warehouse_id;
2068
		$sql.= " WHERE rowid=".$this->id;
2069
2070
		if ($this->db->query($sql)) {
2071
			$this->warehouse_id = ($warehouse_id=='NULL')?null:$warehouse_id;
2072
			return 1;
2073
		} else {
2074
			dol_syslog(get_class($this).'::setWarehouse Error ', LOG_DEBUG);
2075
			$this->error=$this->db->error();
2076
			return 0;
2077
		}
2078
	}
2079
2080
2081
	/**
2082
	 *		Set last model used by doc generator
2083
	 *
2084
	 *		@param		User	$user		User object that make change
2085
	 *		@param		string	$modelpdf	Modele name
2086
	 *		@return		int					<0 if KO, >0 if OK
2087
	 */
2088
	function setDocModel($user, $modelpdf)
2089
	{
2090
		if (! $this->table_element)
2091
		{
2092
			dol_syslog(get_class($this)."::setDocModel was called on objet with property table_element not defined",LOG_ERR);
2093
			return -1;
2094
		}
2095
2096
		$newmodelpdf=dol_trunc($modelpdf,255);
2097
2098
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2099
		$sql.= " SET model_pdf = '".$this->db->escape($newmodelpdf)."'";
2100
		$sql.= " WHERE rowid = ".$this->id;
2101
		// if ($this->element == 'facture') $sql.= " AND fk_statut < 2";
2102
		// if ($this->element == 'propal')  $sql.= " AND fk_statut = 0";
2103
2104
		dol_syslog(get_class($this)."::setDocModel", LOG_DEBUG);
2105
		$resql=$this->db->query($sql);
2106
		if ($resql)
2107
		{
2108
			$this->modelpdf=$modelpdf;
2109
			return 1;
2110
		}
2111
		else
2112
		{
2113
			dol_print_error($this->db);
2114
			return 0;
2115
		}
2116
	}
2117
2118
2119
	/**
2120
	 *  Change the bank account
2121
	 *
2122
	 *  @param		int		$fk_account		Id of bank account
2123
	 *  @param      bool    $notrigger      false=launch triggers after, true=disable triggers
2124
	 *  @param      User	$userused		Object user
2125
	 *  @return		int				1 if OK, 0 if KO
2126
	 */
2127
	function setBankAccount($fk_account, $notrigger=false, $userused=null)
2128
	{
2129
        global $user;
2130
2131
        if (empty($userused)) $userused=$user;
2132
2133
        $error = 0;
2134
2135
		if (! $this->table_element) {
2136
			dol_syslog(get_class($this)."::setBankAccount was called on objet with property table_element not defined",LOG_ERR);
2137
			return -1;
2138
		}
2139
        $this->db->begin();
2140
2141
		if ($fk_account<0) $fk_account='NULL';
2142
		dol_syslog(get_class($this).'::setBankAccount('.$fk_account.')');
2143
2144
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2145
		$sql.= " SET fk_account = ".$fk_account;
2146
		$sql.= " WHERE rowid=".$this->id;
2147
2148
        $resql = $this->db->query($sql);
2149
        if (! $resql)
2150
        {
2151
            dol_syslog(get_class($this).'::setBankAccount Error '.$sql.' - '.$this->db->error());
2152
            $this->error = $this->db->lasterror();
2153
            $error++;
2154
        }
2155
        else
2156
        {
2157
            if (!$notrigger)
2158
            {
2159
                // Call trigger
2160
                $this->context=array('bankaccountupdate'=>1);
2161
                $result = $this->call_trigger(strtoupper(get_class($this)) . '_MODIFY', $userused);
2162
                if ($result < 0) $error++;
2163
                // End call trigger
2164
            }
2165
        }
2166
        if ($error)
2167
        {
2168
            $this->db->rollback();
2169
            return -1;
2170
        }
2171
        else
2172
        {
2173
            $this->fk_account = ($fk_account=='NULL')?null:$fk_account;
2174
            $this->db->commit();
2175
            return 1;
2176
        }
2177
    }
2178
2179
2180
	// TODO: Move line related operations to CommonObjectLine?
2181
2182
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2183
	/**
2184
	 *  Save a new position (field rang) for details lines.
2185
	 *  You can choose to set position for lines with already a position or lines without any position defined.
2186
	 *
2187
	 * 	@param		boolean		$renum			   True to renum all already ordered lines, false to renum only not already ordered lines.
2188
	 * 	@param		string		$rowidorder		   ASC or DESC
2189
	 * 	@param		boolean		$fk_parent_line    Table with fk_parent_line field or not
2190
	 * 	@return		int                            <0 if KO, >0 if OK
2191
	 */
2192
	function line_order($renum=false, $rowidorder='ASC', $fk_parent_line=true)
2193
	{
2194
        // phpcs:enable
2195
		if (! $this->table_element_line)
2196
		{
2197
			dol_syslog(get_class($this)."::line_order was called on objet with property table_element_line not defined",LOG_ERR);
2198
			return -1;
2199
		}
2200
		if (! $this->fk_element)
2201
		{
2202
			dol_syslog(get_class($this)."::line_order was called on objet with property fk_element not defined",LOG_ERR);
2203
			return -1;
2204
		}
2205
2206
		// Count number of lines to reorder (according to choice $renum)
2207
		$nl=0;
2208
		$sql = 'SELECT count(rowid) FROM '.MAIN_DB_PREFIX.$this->table_element_line;
2209
		$sql.= ' WHERE '.$this->fk_element.'='.$this->id;
2210
		if (! $renum) $sql.= ' AND rang = 0';
2211
		if ($renum) $sql.= ' AND rang <> 0';
2212
2213
		dol_syslog(get_class($this)."::line_order", LOG_DEBUG);
2214
		$resql = $this->db->query($sql);
2215
		if ($resql)
2216
		{
2217
			$row = $this->db->fetch_row($resql);
2218
			$nl = $row[0];
2219
		}
2220
		else dol_print_error($this->db);
2221
		if ($nl > 0)
2222
		{
2223
			// The goal of this part is to reorder all lines, with all children lines sharing the same
2224
			// counter that parents.
2225
			$rows=array();
2226
2227
			// We first search all lines that are parent lines (for multilevel details lines)
2228
			$sql = 'SELECT rowid FROM '.MAIN_DB_PREFIX.$this->table_element_line;
2229
			$sql.= ' WHERE '.$this->fk_element.' = '.$this->id;
2230
			if ($fk_parent_line) $sql.= ' AND fk_parent_line IS NULL';
2231
			$sql.= ' ORDER BY rang ASC, rowid '.$rowidorder;
2232
2233
			dol_syslog(get_class($this)."::line_order search all parent lines", LOG_DEBUG);
2234
			$resql = $this->db->query($sql);
2235
			if ($resql)
2236
			{
2237
				$i=0;
2238
				$num = $this->db->num_rows($resql);
2239
				while ($i < $num)
2240
				{
2241
					$row = $this->db->fetch_row($resql);
2242
					$rows[] = $row[0];	// Add parent line into array rows
2243
					$childrens = $this->getChildrenOfLine($row[0]);
2244
					if (! empty($childrens))
2245
					{
2246
						foreach($childrens as $child)
2247
						{
2248
							array_push($rows, $child);
2249
						}
2250
					}
2251
					$i++;
2252
				}
2253
2254
				// Now we set a new number for each lines (parent and children with children included into parent tree)
2255
				if (! empty($rows))
2256
				{
2257
					foreach($rows as $key => $row)
2258
					{
2259
						$this->updateRangOfLine($row, ($key+1));
2260
					}
2261
				}
2262
			}
2263
			else
2264
			{
2265
				dol_print_error($this->db);
2266
			}
2267
		}
2268
		return 1;
2269
	}
2270
2271
	/**
2272
	 * 	Get children of line
2273
	 *
2274
	 * 	@param	int		$id		Id of parent line
2275
	 * 	@return	array			Array with list of children lines id
2276
	 */
2277
	function getChildrenOfLine($id)
2278
	{
2279
		$rows=array();
2280
2281
		$sql = 'SELECT rowid FROM '.MAIN_DB_PREFIX.$this->table_element_line;
2282
		$sql.= ' WHERE '.$this->fk_element.' = '.$this->id;
2283
		$sql.= ' AND fk_parent_line = '.$id;
2284
		$sql.= ' ORDER BY rang ASC';
2285
2286
		dol_syslog(get_class($this)."::getChildrenOfLine search children lines for line ".$id."", LOG_DEBUG);
2287
		$resql = $this->db->query($sql);
2288
		if ($resql)
2289
		{
2290
			$i=0;
2291
			$num = $this->db->num_rows($resql);
2292
			while ($i < $num)
2293
			{
2294
				$row = $this->db->fetch_row($resql);
2295
				$rows[$i] = $row[0];
2296
				$i++;
2297
			}
2298
		}
2299
2300
		return $rows;
2301
	}
2302
2303
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2304
	/**
2305
	 * 	Update a line to have a lower rank
2306
	 *
2307
	 * 	@param 	int			$rowid				Id of line
2308
	 * 	@param	boolean		$fk_parent_line		Table with fk_parent_line field or not
2309
	 * 	@return	void
2310
	 */
2311
	function line_up($rowid, $fk_parent_line=true)
2312
	{
2313
        // phpcs:enable
2314
		$this->line_order(false, 'ASC', $fk_parent_line);
2315
2316
		// Get rang of line
2317
		$rang = $this->getRangOfLine($rowid);
2318
2319
		// Update position of line
2320
		$this->updateLineUp($rowid, $rang);
2321
	}
2322
2323
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2324
	/**
2325
	 * 	Update a line to have a higher rank
2326
	 *
2327
	 * 	@param	int			$rowid				Id of line
2328
	 * 	@param	boolean		$fk_parent_line		Table with fk_parent_line field or not
2329
	 * 	@return	void
2330
	 */
2331
	function line_down($rowid, $fk_parent_line=true)
2332
	{
2333
        // phpcs:enable
2334
		$this->line_order(false, 'ASC', $fk_parent_line);
2335
2336
		// Get rang of line
2337
		$rang = $this->getRangOfLine($rowid);
2338
2339
		// Get max value for rang
2340
		$max = $this->line_max();
2341
2342
		// Update position of line
2343
		$this->updateLineDown($rowid, $rang, $max);
2344
	}
2345
2346
	/**
2347
	 * 	Update position of line (rang)
2348
	 *
2349
	 * 	@param	int		$rowid		Id of line
2350
	 * 	@param	int		$rang		Position
2351
	 * 	@return	void
2352
	 */
2353
	function updateRangOfLine($rowid,$rang)
2354
	{
2355
		$fieldposition = 'rang';
2356
		if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction'))) $fieldposition = 'position';
2357
2358
		$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element_line.' SET '.$fieldposition.' = '.$rang;
2359
		$sql.= ' WHERE rowid = '.$rowid;
2360
2361
		dol_syslog(get_class($this)."::updateRangOfLine", LOG_DEBUG);
2362
		if (! $this->db->query($sql))
2363
		{
2364
			dol_print_error($this->db);
2365
		}
2366
	}
2367
2368
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2369
	/**
2370
	 * 	Update position of line with ajax (rang)
2371
	 *
2372
	 * 	@param	array	$rows	Array of rows
2373
	 * 	@return	void
2374
	 */
2375
	function line_ajaxorder($rows)
2376
	{
2377
        // phpcs:enable
2378
		$num = count($rows);
2379
		for ($i = 0 ; $i < $num ; $i++)
2380
		{
2381
			$this->updateRangOfLine($rows[$i], ($i+1));
2382
		}
2383
	}
2384
2385
	/**
2386
	 * 	Update position of line up (rang)
2387
	 *
2388
	 * 	@param	int		$rowid		Id of line
2389
	 * 	@param	int		$rang		Position
2390
	 * 	@return	void
2391
	 */
2392
	function updateLineUp($rowid,$rang)
2393
	{
2394
		if ($rang > 1)
2395
		{
2396
			$fieldposition = 'rang';
2397
			if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction'))) $fieldposition = 'position';
2398
2399
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element_line.' SET '.$fieldposition.' = '.$rang ;
2400
			$sql.= ' WHERE '.$this->fk_element.' = '.$this->id;
2401
			$sql.= ' AND rang = '.($rang - 1);
2402
			if ($this->db->query($sql) )
2403
			{
2404
				$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element_line.' SET '.$fieldposition.' = '.($rang - 1);
2405
				$sql.= ' WHERE rowid = '.$rowid;
2406
				if (! $this->db->query($sql) )
2407
				{
2408
					dol_print_error($this->db);
2409
				}
2410
			}
2411
			else
2412
			{
2413
				dol_print_error($this->db);
2414
			}
2415
		}
2416
	}
2417
2418
	/**
2419
	 * 	Update position of line down (rang)
2420
	 *
2421
	 * 	@param	int		$rowid		Id of line
2422
	 * 	@param	int		$rang		Position
2423
	 * 	@param	int		$max		Max
2424
	 * 	@return	void
2425
	 */
2426
	function updateLineDown($rowid,$rang,$max)
2427
	{
2428
		if ($rang < $max)
2429
		{
2430
			$fieldposition = 'rang';
2431
			if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction'))) $fieldposition = 'position';
2432
2433
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element_line.' SET '.$fieldposition.' = '.$rang;
2434
			$sql.= ' WHERE '.$this->fk_element.' = '.$this->id;
2435
			$sql.= ' AND rang = '.($rang+1);
2436
			if ($this->db->query($sql) )
2437
			{
2438
				$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element_line.' SET '.$fieldposition.' = '.($rang+1);
2439
				$sql.= ' WHERE rowid = '.$rowid;
2440
				if (! $this->db->query($sql) )
2441
				{
2442
					dol_print_error($this->db);
2443
				}
2444
			}
2445
			else
2446
			{
2447
				dol_print_error($this->db);
2448
			}
2449
		}
2450
	}
2451
2452
	/**
2453
	 * 	Get position of line (rang)
2454
	 *
2455
	 * 	@param		int		$rowid		Id of line
2456
	 *  @return		int     			Value of rang in table of lines
2457
	 */
2458
	function getRangOfLine($rowid)
2459
	{
2460
		$sql = 'SELECT rang FROM '.MAIN_DB_PREFIX.$this->table_element_line;
2461
		$sql.= ' WHERE rowid ='.$rowid;
2462
2463
		dol_syslog(get_class($this)."::getRangOfLine", LOG_DEBUG);
2464
		$resql = $this->db->query($sql);
2465
		if ($resql)
2466
		{
2467
			$row = $this->db->fetch_row($resql);
2468
			return $row[0];
2469
		}
2470
	}
2471
2472
	/**
2473
	 * 	Get rowid of the line relative to its position
2474
	 *
2475
	 * 	@param		int		$rang		Rang value
2476
	 *  @return     int     			Rowid of the line
2477
	 */
2478
	function getIdOfLine($rang)
2479
	{
2480
		$sql = 'SELECT rowid FROM '.MAIN_DB_PREFIX.$this->table_element_line;
2481
		$sql.= ' WHERE '.$this->fk_element.' = '.$this->id;
2482
		$sql.= ' AND rang = '.$rang;
2483
		$resql = $this->db->query($sql);
2484
		if ($resql)
2485
		{
2486
			$row = $this->db->fetch_row($resql);
2487
			return $row[0];
2488
		}
2489
	}
2490
2491
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2492
	/**
2493
	 * 	Get max value used for position of line (rang)
2494
	 *
2495
	 * 	@param		int		$fk_parent_line		Parent line id
2496
	 *  @return     int  			   			Max value of rang in table of lines
2497
	 */
2498
	function line_max($fk_parent_line=0)
2499
	{
2500
        // phpcs:enable
2501
		// Search the last rang with fk_parent_line
2502
		if ($fk_parent_line)
2503
		{
2504
			$sql = 'SELECT max(rang) FROM '.MAIN_DB_PREFIX.$this->table_element_line;
2505
			$sql.= ' WHERE '.$this->fk_element.' = '.$this->id;
2506
			$sql.= ' AND fk_parent_line = '.$fk_parent_line;
2507
2508
			dol_syslog(get_class($this)."::line_max", LOG_DEBUG);
2509
			$resql = $this->db->query($sql);
2510
			if ($resql)
2511
			{
2512
				$row = $this->db->fetch_row($resql);
2513
				if (! empty($row[0]))
2514
				{
2515
					return $row[0];
2516
				}
2517
				else
2518
				{
2519
					return $this->getRangOfLine($fk_parent_line);
2520
				}
2521
			}
2522
		}
2523
		// If not, search the last rang of element
2524
		else
2525
		{
2526
			$sql = 'SELECT max(rang) FROM '.MAIN_DB_PREFIX.$this->table_element_line;
2527
			$sql.= ' WHERE '.$this->fk_element.' = '.$this->id;
2528
2529
			dol_syslog(get_class($this)."::line_max", LOG_DEBUG);
2530
			$resql = $this->db->query($sql);
2531
			if ($resql)
2532
			{
2533
				$row = $this->db->fetch_row($resql);
2534
				return $row[0];
2535
			}
2536
		}
2537
	}
2538
2539
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2540
	/**
2541
	 *  Update external ref of element
2542
	 *
2543
	 *  @param      string		$ref_ext	Update field ref_ext
2544
	 *  @return     int      		   		<0 if KO, >0 if OK
2545
	 */
2546
	function update_ref_ext($ref_ext)
2547
	{
2548
        // phpcs:enable
2549
		if (! $this->table_element)
2550
		{
2551
			dol_syslog(get_class($this)."::update_ref_ext was called on objet with property table_element not defined", LOG_ERR);
2552
			return -1;
2553
		}
2554
2555
		$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2556
		$sql.= " SET ref_ext = '".$this->db->escape($ref_ext)."'";
2557
		$sql.= " WHERE ".(isset($this->table_rowid)?$this->table_rowid:'rowid')." = ". $this->id;
2558
2559
		dol_syslog(get_class($this)."::update_ref_ext", LOG_DEBUG);
2560
		if ($this->db->query($sql))
2561
		{
2562
			$this->ref_ext = $ref_ext;
2563
			return 1;
2564
		}
2565
		else
2566
		{
2567
			$this->error=$this->db->error();
2568
			return -1;
2569
		}
2570
	}
2571
2572
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2573
	/**
2574
	 *  Update note of element
2575
	 *
2576
	 *  @param      string		$note		New value for note
2577
	 *  @param		string		$suffix		'', '_public' or '_private'
2578
	 *  @return     int      		   		<0 if KO, >0 if OK
2579
	 */
2580
	function update_note($note, $suffix='')
2581
	{
2582
        // phpcs:enable
2583
		global $user;
2584
2585
		if (! $this->table_element)
2586
		{
2587
			$this->error='update_note was called on objet with property table_element not defined';
2588
			dol_syslog(get_class($this)."::update_note was called on objet with property table_element not defined", LOG_ERR);
2589
			return -1;
2590
		}
2591
		if (! in_array($suffix,array('','_public','_private')))
2592
		{
2593
			$this->error='update_note Parameter suffix must be empty, \'_private\' or \'_public\'';
2594
			dol_syslog(get_class($this)."::update_note Parameter suffix must be empty, '_private' or '_public'", LOG_ERR);
2595
			return -2;
2596
		}
2597
		// Special cas
2598
		//var_dump($this->table_element);exit;
2599
		if ($this->table_element == 'product') $suffix='';
2600
2601
		$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2602
		$sql.= " SET note".$suffix." = ".(!empty($note)?("'".$this->db->escape($note)."'"):"NULL");
2603
		$sql.= " ,".(in_array($this->table_element, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))?"fk_user_mod":"fk_user_modif")." = ".$user->id;
2604
		$sql.= " WHERE rowid =". $this->id;
2605
2606
		dol_syslog(get_class($this)."::update_note", LOG_DEBUG);
2607
		if ($this->db->query($sql))
2608
		{
2609
			if ($suffix == '_public') $this->note_public = $note;
2610
			else if ($suffix == '_private') $this->note_private = $note;
2611
			else
2612
			{
2613
				$this->note = $note;      // deprecated
2614
				$this->note_private = $note;
2615
			}
2616
			return 1;
2617
		}
2618
		else
2619
		{
2620
			$this->error=$this->db->lasterror();
2621
			return -1;
2622
		}
2623
	}
2624
2625
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2626
	/**
2627
	 * 	Update public note (kept for backward compatibility)
2628
	 *
2629
	 * @param      string		$note		New value for note
2630
	 * @return     int      		   		<0 if KO, >0 if OK
2631
	 * @deprecated
2632
	 * @see update_note()
2633
	 */
2634
	function update_note_public($note)
2635
	{
2636
        // phpcs:enable
2637
		return $this->update_note($note,'_public');
2638
	}
2639
2640
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2641
	/**
2642
	 *	Update total_ht, total_ttc, total_vat, total_localtax1, total_localtax2 for an object (sum of lines).
2643
	 *  Must be called at end of methods addline or updateline.
2644
	 *
2645
	 *	@param	int		$exclspec          	>0 = Exclude special product (product_type=9)
2646
	 *  @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
2647
	 *  @param	int		$nodatabaseupdate	1=Do not update database. Update only properties of object.
2648
	 *  @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.
2649
	 *	@return	int    			           	<0 if KO, >0 if OK
2650
	 */
2651
	function update_price($exclspec=0,$roundingadjust='none',$nodatabaseupdate=0,$seller=null)
2652
	{
2653
        // phpcs:enable
2654
		global $conf, $hookmanager, $action;
2655
2656
		// Some external module want no update price after a trigger because they have another method to calculate the total (ex: with an extrafield)
2657
		$MODULE = "";
2658
		if ($this->element == 'propal')
2659
			$MODULE = "MODULE_DISALLOW_UPDATE_PRICE_PROPOSAL";
2660
		elseif ($this->element == 'order')
2661
			$MODULE = "MODULE_DISALLOW_UPDATE_PRICE_ORDER";
2662
		elseif ($this->element == 'facture')
2663
			$MODULE = "MODULE_DISALLOW_UPDATE_PRICE_INVOICE";
2664
		elseif ($this->element == 'facture_fourn')
2665
			$MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_INVOICE";
2666
		elseif ($this->element == 'order_supplier')
2667
			$MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_ORDER";
2668
		elseif ($this->element == 'supplier_proposal')
2669
			$MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_PROPOSAL";
2670
2671
		if (! empty($MODULE)) {
2672
			if (! empty($conf->global->$MODULE)) {
2673
				$modsactivated = explode(',', $conf->global->$MODULE);
2674
				foreach ($modsactivated as $mod) {
2675
					if ($conf->$mod->enabled)
2676
						return 1; // update was disabled by specific setup
2677
				}
2678
			}
2679
		}
2680
2681
		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2682
2683
		if ($roundingadjust == '-1') $roundingadjust='auto';	// For backward compatibility
2684
2685
		$forcedroundingmode=$roundingadjust;
2686
		if ($forcedroundingmode == 'auto' && isset($conf->global->MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND)) $forcedroundingmode=$conf->global->MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND;
2687
		elseif ($forcedroundingmode == 'auto') $forcedroundingmode='0';
2688
2689
		$error=0;
2690
2691
		$multicurrency_tx = !empty($this->multicurrency_tx) ? $this->multicurrency_tx : 1;
2692
2693
		// Define constants to find lines to sum
2694
		$fieldtva='total_tva';
2695
		$fieldlocaltax1='total_localtax1';
2696
		$fieldlocaltax2='total_localtax2';
2697
		$fieldup='subprice';
2698
		if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier')
2699
		{
2700
			$fieldtva='tva';
2701
			$fieldup='pu_ht';
2702
		}
2703
		if ($this->element == 'expensereport')
2704
		{
2705
			$fieldup='value_unit';
2706
		}
2707
2708
		$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,';
2709
		$sql.= ' tva_tx as vatrate, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, info_bits, product_type';
2710
			if ($this->table_element_line == 'facturedet') $sql.= ', situation_percent';
2711
			$sql.= ', multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
2712
		$sql.= ' FROM '.MAIN_DB_PREFIX.$this->table_element_line;
2713
		$sql.= ' WHERE '.$this->fk_element.' = '.$this->id;
2714
		if ($exclspec)
2715
		{
2716
			$product_field='product_type';
2717
			if ($this->table_element_line == 'contratdet') $product_field='';    // contratdet table has no product_type field
2718
			if ($product_field) $sql.= ' AND '.$product_field.' <> 9';
2719
		}
2720
		$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
2721
2722
		dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
2723
		$resql = $this->db->query($sql);
2724
		if ($resql)
2725
		{
2726
			$this->total_ht  = 0;
2727
			$this->total_tva = 0;
2728
			$this->total_localtax1 = 0;
2729
			$this->total_localtax2 = 0;
2730
			$this->total_ttc = 0;
2731
			$total_ht_by_vats  = array();
2732
			$total_tva_by_vats = array();
2733
			$total_ttc_by_vats = array();
2734
			$this->multicurrency_total_ht	= 0;
2735
			$this->multicurrency_total_tva	= 0;
2736
			$this->multicurrency_total_ttc	= 0;
2737
2738
			$num = $this->db->num_rows($resql);
2739
			$i = 0;
2740
			while ($i < $num)
2741
			{
2742
				$obj = $this->db->fetch_object($resql);
2743
2744
				// Note: There is no check on detail line and no check on total, if $forcedroundingmode = 'none'
2745
				$parameters=array('fk_element' => $obj->rowid);
2746
				$reshook = $hookmanager->executeHooks('changeRoundingMode', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2747
2748
				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'
2749
				{
2750
					$localtax_array=array($obj->localtax1_type,$obj->localtax1_tx,$obj->localtax2_type,$obj->localtax2_tx);
2751
					$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);
2752
					$diff=price2num($tmpcal[1] - $obj->total_tva, 'MT', 1);
2753
					if ($diff)
2754
					{
2755
						$sqlfix="UPDATE ".MAIN_DB_PREFIX.$this->table_element_line." SET ".$fieldtva." = ".$tmpcal[1].", total_ttc = ".$tmpcal[2]." WHERE rowid = ".$obj->rowid;
2756
						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);
2757
								$resqlfix=$this->db->query($sqlfix);
2758
								if (! $resqlfix) dol_print_error($this->db,'Failed to update line');
2759
								$obj->total_tva = $tmpcal[1];
2760
								$obj->total_ttc = $tmpcal[2];
2761
						//
2762
					}
2763
				}
2764
2765
				$this->total_ht        += $obj->total_ht;		// The field visible at end of line detail
2766
				$this->total_tva       += $obj->total_tva;
2767
				$this->total_localtax1 += $obj->total_localtax1;
2768
				$this->total_localtax2 += $obj->total_localtax2;
2769
				$this->total_ttc       += $obj->total_ttc;
2770
				$this->multicurrency_total_ht        += $obj->multicurrency_total_ht;		// The field visible at end of line detail
2771
				$this->multicurrency_total_tva       += $obj->multicurrency_total_tva;
2772
				$this->multicurrency_total_ttc       += $obj->multicurrency_total_ttc;
2773
2774
				if (! isset($total_ht_by_vats[$obj->vatrate]))  $total_ht_by_vats[$obj->vatrate]=0;
2775
				if (! isset($total_tva_by_vats[$obj->vatrate])) $total_tva_by_vats[$obj->vatrate]=0;
2776
				if (! isset($total_ttc_by_vats[$obj->vatrate])) $total_ttc_by_vats[$obj->vatrate]=0;
2777
				$total_ht_by_vats[$obj->vatrate]  += $obj->total_ht;
2778
				$total_tva_by_vats[$obj->vatrate] += $obj->total_tva;
2779
				$total_ttc_by_vats[$obj->vatrate] += $obj->total_ttc;
2780
2781
				if ($forcedroundingmode == '1')	// Check if we need adjustement onto line for vat. TODO This works on the company currency but not on multicurrency
2782
				{
2783
					$tmpvat=price2num($total_ht_by_vats[$obj->vatrate] * $obj->vatrate / 100, 'MT', 1);
2784
					$diff=price2num($total_tva_by_vats[$obj->vatrate]-$tmpvat, 'MT', 1);
2785
					//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";
2786
					if ($diff)
2787
					{
2788
						if (abs($diff) > 0.1) { dol_syslog('A rounding difference was detected into TOTAL but is too high to be corrected', LOG_WARNING); exit; }
2789
						$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;
2790
						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);
2791
								$resqlfix=$this->db->query($sqlfix);
2792
								if (! $resqlfix) dol_print_error($this->db,'Failed to update line');
2793
								$this->total_tva -= $diff;
2794
								$this->total_ttc -= $diff;
2795
								$total_tva_by_vats[$obj->vatrate] -= $diff;
2796
								$total_ttc_by_vats[$obj->vatrate] -= $diff;
2797
					}
2798
				}
2799
2800
				$i++;
2801
			}
2802
2803
			// Add revenue stamp to total
2804
			$this->total_ttc       			+= isset($this->revenuestamp)?$this->revenuestamp:0;
2805
			$this->multicurrency_total_ttc  += isset($this->revenuestamp)?($this->revenuestamp * $multicurrency_tx):0;
2806
2807
			// Situations totals
2808
			if ($this->situation_cycle_ref && $this->situation_counter > 1 && method_exists($this, 'get_prev_sits') && $this->type != $this::TYPE_CREDIT_NOTE )
2809
			{
2810
				$prev_sits = $this->get_prev_sits();
2811
2812
				foreach ($prev_sits as $sit) {				// $sit is an object Facture loaded with a fetch.
2813
					$this->total_ht -= $sit->total_ht;
2814
					$this->total_tva -= $sit->total_tva;
2815
					$this->total_localtax1 -= $sit->total_localtax1;
2816
					$this->total_localtax2 -= $sit->total_localtax2;
2817
					$this->total_ttc -= $sit->total_ttc;
2818
					$this->multicurrency_total_ht -= $sit->multicurrency_total_ht;
2819
					$this->multicurrency_total_tva -= $sit->multicurrency_total_tva;
2820
					$this->multicurrency_total_ttc -= $sit->multicurrency_total_ttc;
2821
				}
2822
			}
2823
2824
			$this->db->free($resql);
2825
2826
			// Now update global field total_ht, total_ttc and tva
2827
			$fieldht='total_ht';
2828
			$fieldtva='tva';
2829
			$fieldlocaltax1='localtax1';
2830
			$fieldlocaltax2='localtax2';
2831
			$fieldttc='total_ttc';
2832
			// Specific code for backward compatibility with old field names
2833
			if ($this->element == 'facture' || $this->element == 'facturerec')             $fieldht='total';
2834
			if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') $fieldtva='total_tva';
2835
			if ($this->element == 'propal')                                                $fieldttc='total';
2836
			if ($this->element == 'expensereport')                                         $fieldtva='total_tva';
2837
			if ($this->element == 'supplier_proposal')                                     $fieldttc='total';
2838
2839
			if (empty($nodatabaseupdate))
2840
			{
2841
				$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element.' SET';
2842
				$sql .= " ".$fieldht."='".price2num($this->total_ht)."',";
2843
				$sql .= " ".$fieldtva."='".price2num($this->total_tva)."',";
2844
				$sql .= " ".$fieldlocaltax1."='".price2num($this->total_localtax1)."',";
2845
				$sql .= " ".$fieldlocaltax2."='".price2num($this->total_localtax2)."',";
2846
				$sql .= " ".$fieldttc."='".price2num($this->total_ttc)."'";
2847
						$sql .= ", multicurrency_total_ht='".price2num($this->multicurrency_total_ht, 'MT', 1)."'";
2848
						$sql .= ", multicurrency_total_tva='".price2num($this->multicurrency_total_tva, 'MT', 1)."'";
2849
						$sql .= ", multicurrency_total_ttc='".price2num($this->multicurrency_total_ttc, 'MT', 1)."'";
2850
				$sql .= ' WHERE rowid = '.$this->id;
2851
2852
2853
				dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
2854
				$resql=$this->db->query($sql);
2855
				if (! $resql)
2856
				{
2857
					$error++;
2858
					$this->error=$this->db->lasterror();
2859
					$this->errors[]=$this->db->lasterror();
2860
				}
2861
			}
2862
2863
			if (! $error)
2864
			{
2865
				return 1;
2866
			}
2867
			else
2868
			{
2869
				return -1;
2870
			}
2871
		}
2872
		else
2873
		{
2874
			dol_print_error($this->db,'Bad request in update_price');
2875
			return -1;
2876
		}
2877
	}
2878
2879
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2880
	/**
2881
	 *	Add objects linked in llx_element_element.
2882
	 *
2883
	 *	@param		string	$origin		Linked element type
2884
	 *	@param		int		$origin_id	Linked element id
2885
	 *	@return		int					<=0 if KO, >0 if OK
2886
	 *	@see		fetchObjectLinked, updateObjectLinked, deleteObjectLinked
2887
	 */
2888
	function add_object_linked($origin=null, $origin_id=null)
2889
	{
2890
        // phpcs:enable
2891
		$origin = (! empty($origin) ? $origin : $this->origin);
2892
		$origin_id = (! empty($origin_id) ? $origin_id : $this->origin_id);
2893
2894
		// Special case
2895
		if ($origin == 'order') $origin='commande';
2896
		if ($origin == 'invoice') $origin='facture';
2897
		if ($origin == 'invoice_template') $origin='facturerec';
2898
    	if ($origin == 'supplierorder') $origin='order_supplier';
2899
		$this->db->begin();
2900
2901
		$sql = "INSERT INTO ".MAIN_DB_PREFIX."element_element (";
2902
		$sql.= "fk_source";
2903
		$sql.= ", sourcetype";
2904
		$sql.= ", fk_target";
2905
		$sql.= ", targettype";
2906
		$sql.= ") VALUES (";
2907
		$sql.= $origin_id;
2908
		$sql.= ", '".$this->db->escape($origin)."'";
2909
		$sql.= ", ".$this->id;
2910
		$sql.= ", '".$this->db->escape($this->element)."'";
2911
		$sql.= ")";
2912
2913
		dol_syslog(get_class($this)."::add_object_linked", LOG_DEBUG);
2914
		if ($this->db->query($sql))
2915
	  	{
2916
	  		$this->db->commit();
2917
	  		return 1;
2918
	  	}
2919
	  	else
2920
	  	{
2921
	  		$this->error=$this->db->lasterror();
2922
	  		$this->db->rollback();
2923
	  		return 0;
2924
	  	}
2925
	}
2926
2927
	/**
2928
	 *	Fetch array of objects linked to current object (object of enabled modules only). Links are loaded into
2929
	 *		this->linkedObjectsIds array and
2930
	 *		this->linkedObjects array if $loadalsoobjects = 1
2931
	 *  Possible usage for parameters:
2932
	 *  - all parameters empty -> we look all link to current object (current object can be source or target)
2933
	 *  - source id+type -> will get target list linked to source
2934
	 *  - target id+type -> will get source list linked to target
2935
	 *  - source id+type + target type -> will get target list of the type
2936
	 *  - target id+type + target source -> will get source list of the type
2937
	 *
2938
	 *	@param	int		$sourceid			Object source id (if not defined, id of object)
2939
	 *	@param  string	$sourcetype			Object source type (if not defined, element name of object)
2940
	 *	@param  int		$targetid			Object target id (if not defined, id of object)
2941
	 *	@param  string	$targettype			Object target type (if not defined, elemennt name of object)
2942
	 *	@param  string	$clause				'OR' or 'AND' clause used when both source id and target id are provided
2943
	 *  @param  int		$alsosametype		0=Return only links to object that differs from source type. 1=Include also link to objects of same type.
2944
	 *  @param  string	$orderby			SQL 'ORDER BY' clause
2945
	 *  @param	int		$loadalsoobjects	Load also array this->linkedObjects (Use 0 to increase performances)
2946
	 *	@return int							<0 if KO, >0 if OK
2947
	 *  @see	add_object_linked, updateObjectLinked, deleteObjectLinked
2948
	 */
2949
	function fetchObjectLinked($sourceid=null,$sourcetype='',$targetid=null,$targettype='',$clause='OR',$alsosametype=1,$orderby='sourcetype',$loadalsoobjects=1)
2950
	{
2951
		global $conf;
2952
2953
		$this->linkedObjectsIds=array();
2954
		$this->linkedObjects=array();
2955
2956
		$justsource=false;
2957
		$justtarget=false;
2958
		$withtargettype=false;
2959
		$withsourcetype=false;
2960
2961
		if (! empty($sourceid) && ! empty($sourcetype) && empty($targetid))
2962
		{
2963
			$justsource=true;  // the source (id and type) is a search criteria
2964
			if (! empty($targettype)) $withtargettype=true;
2965
		}
2966
		if (! empty($targetid) && ! empty($targettype) && empty($sourceid))
2967
		{
2968
			$justtarget=true;  // the target (id and type) is a search criteria
2969
			if (! empty($sourcetype)) $withsourcetype=true;
2970
		}
2971
2972
		$sourceid = (! empty($sourceid) ? $sourceid : $this->id);
2973
		$targetid = (! empty($targetid) ? $targetid : $this->id);
2974
		$sourcetype = (! empty($sourcetype) ? $sourcetype : $this->element);
2975
		$targettype = (! empty($targettype) ? $targettype : $this->element);
2976
2977
		/*if (empty($sourceid) && empty($targetid))
2978
		 {
2979
		 dol_syslog('Bad usage of function. No source nor target id defined (nor as parameter nor as object id)', LOG_ERR);
2980
		 return -1;
2981
		 }*/
2982
2983
		// Links between objects are stored in table element_element
2984
		$sql = 'SELECT rowid, fk_source, sourcetype, fk_target, targettype';
2985
		$sql.= ' FROM '.MAIN_DB_PREFIX.'element_element';
2986
		$sql.= " WHERE ";
2987
		if ($justsource || $justtarget)
2988
		{
2989
			if ($justsource)
2990
			{
2991
				$sql.= "fk_source = ".$sourceid." AND sourcetype = '".$sourcetype."'";
2992
				if ($withtargettype) $sql.= " AND targettype = '".$targettype."'";
2993
			}
2994
			else if ($justtarget)
2995
			{
2996
				$sql.= "fk_target = ".$targetid." AND targettype = '".$targettype."'";
2997
				if ($withsourcetype) $sql.= " AND sourcetype = '".$sourcetype."'";
2998
			}
2999
		}
3000
		else
3001
		{
3002
			$sql.= "(fk_source = ".$sourceid." AND sourcetype = '".$sourcetype."')";
3003
			$sql.= " ".$clause." (fk_target = ".$targetid." AND targettype = '".$targettype."')";
3004
		}
3005
		$sql .= ' ORDER BY '.$orderby;
3006
3007
		dol_syslog(get_class($this)."::fetchObjectLink", LOG_DEBUG);
3008
		$resql = $this->db->query($sql);
3009
		if ($resql)
3010
		{
3011
			$num = $this->db->num_rows($resql);
3012
			$i = 0;
3013
			while ($i < $num)
3014
			{
3015
				$obj = $this->db->fetch_object($resql);
3016
				if ($justsource || $justtarget)
3017
				{
3018
					if ($justsource)
3019
					{
3020
						$this->linkedObjectsIds[$obj->targettype][$obj->rowid]=$obj->fk_target;
3021
					}
3022
					else if ($justtarget)
3023
					{
3024
						$this->linkedObjectsIds[$obj->sourcetype][$obj->rowid]=$obj->fk_source;
3025
					}
3026
				}
3027
				else
3028
				{
3029
					if ($obj->fk_source == $sourceid && $obj->sourcetype == $sourcetype)
3030
					{
3031
						$this->linkedObjectsIds[$obj->targettype][$obj->rowid]=$obj->fk_target;
3032
					}
3033
					if ($obj->fk_target == $targetid && $obj->targettype == $targettype)
3034
					{
3035
						$this->linkedObjectsIds[$obj->sourcetype][$obj->rowid]=$obj->fk_source;
3036
					}
3037
				}
3038
				$i++;
3039
			}
3040
3041
			if (! empty($this->linkedObjectsIds))
3042
			{
3043
				$tmparray = $this->linkedObjectsIds;
3044
				foreach($tmparray as $objecttype => $objectids)       // $objecttype is a module name ('facture', 'mymodule', ...) or a module name with a suffix ('project_task', 'mymodule_myobj', ...)
3045
				{
3046
					// Parse element/subelement (ex: project_task, cabinetmed_consultation, ...)
3047
					$module = $element = $subelement = $objecttype;
3048
					if ($objecttype != 'supplier_proposal' && $objecttype != 'order_supplier' && $objecttype != 'invoice_supplier'
3049
						&& preg_match('/^([^_]+)_([^_]+)/i',$objecttype,$regs))
3050
					{
3051
						$module = $element = $regs[1];
3052
						$subelement = $regs[2];
3053
					}
3054
3055
					$classpath = $element.'/class';
3056
					// To work with non standard classpath or module name
3057
					if ($objecttype == 'facture')			{
3058
						$classpath = 'compta/facture/class';
3059
					}
3060
					else if ($objecttype == 'facturerec')			{
3061
						$classpath = 'compta/facture/class'; $module = 'facture';
3062
					}
3063
					else if ($objecttype == 'propal')			{
3064
						$classpath = 'comm/propal/class';
3065
					}
3066
					else if ($objecttype == 'supplier_proposal')			{
3067
						$classpath = 'supplier_proposal/class';
3068
					}
3069
					else if ($objecttype == 'shipping')			{
3070
						$classpath = 'expedition/class'; $subelement = 'expedition'; $module = 'expedition_bon';
3071
					}
3072
					else if ($objecttype == 'delivery')			{
3073
						$classpath = 'livraison/class'; $subelement = 'livraison'; $module = 'livraison_bon';
3074
					}
3075
					else if ($objecttype == 'invoice_supplier' || $objecttype == 'order_supplier')	{
3076
						$classpath = 'fourn/class'; $module = 'fournisseur';
3077
					}
3078
					else if ($objecttype == 'fichinter')			{
3079
						$classpath = 'fichinter/class'; $subelement = 'fichinter'; $module = 'ficheinter';
3080
					}
3081
					else if ($objecttype == 'subscription')			{
3082
						$classpath = 'adherents/class'; $module = 'adherent';
3083
					}
3084
3085
					// Set classfile
3086
					$classfile = strtolower($subelement); $classname = ucfirst($subelement);
3087
3088
					if ($objecttype == 'order') {
3089
						$classfile = 'commande'; $classname = 'Commande';
3090
					}
3091
					else if ($objecttype == 'invoice_supplier') {
3092
						$classfile = 'fournisseur.facture'; $classname = 'FactureFournisseur';
3093
					}
3094
					else if ($objecttype == 'order_supplier')   {
3095
						$classfile = 'fournisseur.commande'; $classname = 'CommandeFournisseur';
3096
					}
3097
					else if ($objecttype == 'supplier_proposal')   {
3098
						$classfile = 'supplier_proposal'; $classname = 'SupplierProposal';
3099
					}
3100
					else if ($objecttype == 'facturerec')   {
3101
						$classfile = 'facture-rec'; $classname = 'FactureRec';
3102
					}
3103
					else if ($objecttype == 'subscription')   {
3104
						$classfile = 'subscription'; $classname = 'Subscription';
3105
					}
3106
3107
					// Here $module, $classfile and $classname are set
3108
					if ($conf->$module->enabled && (($element != $this->element) || $alsosametype))
3109
					{
3110
						if ($loadalsoobjects)
3111
						{
3112
							dol_include_once('/'.$classpath.'/'.$classfile.'.class.php');
3113
							//print '/'.$classpath.'/'.$classfile.'.class.php '.class_exists($classname);
3114
							if (class_exists($classname))
3115
							{
3116
								foreach($objectids as $i => $objectid)	// $i is rowid into llx_element_element
3117
								{
3118
									$object = new $classname($this->db);
3119
									$ret = $object->fetch($objectid);
3120
									if ($ret >= 0)
3121
									{
3122
										$this->linkedObjects[$objecttype][$i] = $object;
3123
									}
3124
								}
3125
							}
3126
						}
3127
					}
3128
					else
3129
					{
3130
						unset($this->linkedObjectsIds[$objecttype]);
3131
					}
3132
				}
3133
			}
3134
			return 1;
3135
		}
3136
		else
3137
		{
3138
			dol_print_error($this->db);
3139
			return -1;
3140
		}
3141
	}
3142
3143
	/**
3144
	 *	Update object linked of a current object
3145
	 *
3146
	 *	@param	int		$sourceid		Object source id
3147
	 *	@param  string	$sourcetype		Object source type
3148
	 *	@param  int		$targetid		Object target id
3149
	 *	@param  string	$targettype		Object target type
3150
	 *	@return							int	>0 if OK, <0 if KO
3151
	 *	@see	add_object_linked, fetObjectLinked, deleteObjectLinked
3152
	 */
3153
	function updateObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='')
3154
	{
3155
		$updatesource=false;
3156
		$updatetarget=false;
3157
3158
		if (! empty($sourceid) && ! empty($sourcetype) && empty($targetid) && empty($targettype)) $updatesource=true;
3159
		else if (empty($sourceid) && empty($sourcetype) && ! empty($targetid) && ! empty($targettype)) $updatetarget=true;
3160
3161
		$sql = "UPDATE ".MAIN_DB_PREFIX."element_element SET ";
3162
		if ($updatesource)
3163
		{
3164
			$sql.= "fk_source = ".$sourceid;
3165
			$sql.= ", sourcetype = '".$this->db->escape($sourcetype)."'";
3166
			$sql.= " WHERE fk_target = ".$this->id;
3167
			$sql.= " AND targettype = '".$this->db->escape($this->element)."'";
3168
		}
3169
		else if ($updatetarget)
3170
		{
3171
			$sql.= "fk_target = ".$targetid;
3172
			$sql.= ", targettype = '".$this->db->escape($targettype)."'";
3173
			$sql.= " WHERE fk_source = ".$this->id;
3174
			$sql.= " AND sourcetype = '".$this->db->escape($this->element)."'";
3175
		}
3176
3177
		dol_syslog(get_class($this)."::updateObjectLinked", LOG_DEBUG);
3178
		if ($this->db->query($sql))
3179
		{
3180
			return 1;
3181
		}
3182
		else
3183
		{
3184
			$this->error=$this->db->lasterror();
3185
			return -1;
3186
		}
3187
	}
3188
3189
	/**
3190
	 *	Delete all links between an object $this
3191
	 *
3192
	 *	@param	int		$sourceid		Object source id
3193
	 *	@param  string	$sourcetype		Object source type
3194
	 *	@param  int		$targetid		Object target id
3195
	 *	@param  string	$targettype		Object target type
3196
	 *  @param	int		$rowid			Row id of line to delete. If defined, other parameters are not used.
3197
	 *	@return     					int	>0 if OK, <0 if KO
3198
	 *	@see	add_object_linked, updateObjectLinked, fetchObjectLinked
3199
	 */
3200
	function deleteObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $rowid='')
3201
	{
3202
		$deletesource=false;
3203
		$deletetarget=false;
3204
3205
		if (! empty($sourceid) && ! empty($sourcetype) && empty($targetid) && empty($targettype)) $deletesource=true;
3206
		else if (empty($sourceid) && empty($sourcetype) && ! empty($targetid) && ! empty($targettype)) $deletetarget=true;
3207
3208
		$sourceid = (! empty($sourceid) ? $sourceid : $this->id);
3209
		$sourcetype = (! empty($sourcetype) ? $sourcetype : $this->element);
3210
		$targetid = (! empty($targetid) ? $targetid : $this->id);
3211
		$targettype = (! empty($targettype) ? $targettype : $this->element);
3212
3213
		$sql = "DELETE FROM ".MAIN_DB_PREFIX."element_element";
3214
		$sql.= " WHERE";
3215
		if ($rowid > 0)
3216
		{
3217
			$sql.=" rowid = ".$rowid;
3218
		}
3219
		else
3220
		{
3221
			if ($deletesource)
3222
			{
3223
				$sql.= " fk_source = ".$sourceid." AND sourcetype = '".$this->db->escape($sourcetype)."'";
3224
				$sql.= " AND fk_target = ".$this->id." AND targettype = '".$this->db->escape($this->element)."'";
3225
			}
3226
			else if ($deletetarget)
3227
			{
3228
				$sql.= " fk_target = ".$targetid." AND targettype = '".$this->db->escape($targettype)."'";
3229
				$sql.= " AND fk_source = ".$this->id." AND sourcetype = '".$this->db->escape($this->element)."'";
3230
			}
3231
			else
3232
			{
3233
				$sql.= " (fk_source = ".$this->id." AND sourcetype = '".$this->db->escape($this->element)."')";
3234
				$sql.= " OR";
3235
				$sql.= " (fk_target = ".$this->id." AND targettype = '".$this->db->escape($this->element)."')";
3236
			}
3237
		}
3238
3239
		dol_syslog(get_class($this)."::deleteObjectLinked", LOG_DEBUG);
3240
		if ($this->db->query($sql))
3241
		{
3242
			return 1;
3243
		}
3244
		else
3245
		{
3246
			$this->error=$this->db->lasterror();
3247
			$this->errors[]=$this->error;
3248
			return -1;
3249
		}
3250
	}
3251
3252
	/**
3253
	 *      Set status of an object
3254
	 *
3255
	 *      @param	int		$status			Status to set
3256
	 *      @param	int		$elementId		Id of element to force (use this->id by default)
3257
	 *      @param	string	$elementType	Type of element to force (use this->table_element by default)
3258
	 *      @param	string	$trigkey		Trigger key to use for trigger
3259
	 *      @return int						<0 if KO, >0 if OK
3260
	 */
3261
	function setStatut($status, $elementId=null, $elementType='', $trigkey='')
3262
	{
3263
		global $user,$langs,$conf;
3264
3265
		$savElementId=$elementId;  // To be used later to know if we were using the method using the id of this or not.
3266
3267
		$elementId = (!empty($elementId)?$elementId:$this->id);
3268
		$elementTable = (!empty($elementType)?$elementType:$this->table_element);
3269
3270
		$this->db->begin();
3271
3272
		$fieldstatus="fk_statut";
3273
		if ($elementTable == 'facture_rec') $fieldstatus="suspended";
3274
		if ($elementTable == 'mailing') $fieldstatus="statut";
3275
		if ($elementTable == 'cronjob') $fieldstatus="status";
3276
		if ($elementTable == 'user') $fieldstatus="statut";
3277
		if ($elementTable == 'expensereport') $fieldstatus="fk_statut";
3278
		if ($elementTable == 'commande_fournisseur_dispatch') $fieldstatus="status";
3279
3280
		$sql = "UPDATE ".MAIN_DB_PREFIX.$elementTable;
3281
		$sql.= " SET ".$fieldstatus." = ".$status;
3282
		// If status = 1 = validated, update also fk_user_valid
3283
		if ($status == 1 && $elementTable == 'expensereport') $sql.=", fk_user_valid = ".$user->id;
3284
		$sql.= " WHERE rowid=".$elementId;
3285
3286
		dol_syslog(get_class($this)."::setStatut", LOG_DEBUG);
3287
		if ($this->db->query($sql))
3288
		{
3289
			$error = 0;
3290
3291
			// Try autoset of trigkey
3292
			if (empty($trigkey))
3293
			{
3294
				if ($this->element == 'supplier_proposal' && $status == 2) $trigkey='SUPPLIER_PROPOSAL_SIGN';   // 2 = SupplierProposal::STATUS_SIGNED. Can't use constant into this generic class
3295
				if ($this->element == 'supplier_proposal' && $status == 3) $trigkey='SUPPLIER_PROPOSAL_REFUSE'; // 3 = SupplierProposal::STATUS_REFUSED. Can't use constant into this generic class
3296
				if ($this->element == 'supplier_proposal' && $status == 4) $trigkey='SUPPLIER_PROPOSAL_CLOSE';  // 4 = SupplierProposal::STATUS_CLOSED. Can't use constant into this generic class
3297
				if ($this->element == 'fichinter' && $status == 3) $trigkey='FICHINTER_CLASSIFY_DONE';
3298
				if ($this->element == 'fichinter' && $status == 2) $trigkey='FICHINTER_CLASSIFY_BILLED';
3299
				if ($this->element == 'fichinter' && $status == 1) $trigkey='FICHINTER_CLASSIFY_UNBILLED';
3300
			}
3301
3302
			if ($trigkey)
3303
			{
3304
				// Appel des triggers
3305
				include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
3306
				$interface=new Interfaces($this->db);
3307
				$result=$interface->run_triggers($trigkey,$this,$user,$langs,$conf);
3308
				if ($result < 0) {
3309
					$error++; $this->errors=$interface->errors;
3310
				}
3311
				// Fin appel triggers
3312
			}
3313
3314
			if (! $error)
3315
			{
3316
				$this->db->commit();
3317
3318
				if (empty($savElementId))    // If the element we update was $this (so $elementId is null)
3319
				{
3320
					$this->statut = $status;
3321
					$this->status = $status;
3322
				}
3323
3324
				return 1;
3325
			}
3326
			else
3327
			{
3328
				$this->db->rollback();
3329
				dol_syslog(get_class($this)."::setStatus ".$this->error,LOG_ERR);
3330
				return -1;
3331
			}
3332
		}
3333
		else
3334
		{
3335
			$this->error=$this->db->lasterror();
3336
			$this->db->rollback();
3337
			return -1;
3338
		}
3339
	}
3340
3341
3342
	/**
3343
	 *  Load type of canvas of an object if it exists
3344
	 *
3345
	 *  @param      int		$id     Record id
3346
	 *  @param      string	$ref    Record ref
3347
	 *  @return		int				<0 if KO, 0 if nothing done, >0 if OK
3348
	 */
3349
	function getCanvas($id=0,$ref='')
3350
	{
3351
		global $conf;
3352
3353
		if (empty($id) && empty($ref)) return 0;
3354
		if (! empty($conf->global->MAIN_DISABLE_CANVAS)) return 0;    // To increase speed. Not enabled by default.
3355
3356
		// Clean parameters
3357
		$ref = trim($ref);
3358
3359
		$sql = "SELECT rowid, canvas";
3360
		$sql.= " FROM ".MAIN_DB_PREFIX.$this->table_element;
3361
		$sql.= " WHERE entity IN (".getEntity($this->element).")";
3362
		if (! empty($id))  $sql.= " AND rowid = ".$id;
3363
		if (! empty($ref)) $sql.= " AND ref = '".$this->db->escape($ref)."'";
3364
3365
		$resql = $this->db->query($sql);
3366
		if ($resql)
3367
		{
3368
			$obj = $this->db->fetch_object($resql);
3369
			if ($obj)
3370
			{
3371
				$this->canvas   = $obj->canvas;
3372
				return 1;
3373
			}
3374
			else return 0;
3375
		}
3376
		else
3377
		{
3378
			dol_print_error($this->db);
3379
			return -1;
3380
		}
3381
	}
3382
3383
3384
	/**
3385
	 * 	Get special code of a line
3386
	 *
3387
	 * 	@param	int		$lineid		Id of line
3388
	 * 	@return	int					Special code
3389
	 */
3390
	function getSpecialCode($lineid)
3391
	{
3392
		$sql = 'SELECT special_code FROM '.MAIN_DB_PREFIX.$this->table_element_line;
3393
		$sql.= ' WHERE rowid = '.$lineid;
3394
		$resql = $this->db->query($sql);
3395
		if ($resql)
3396
		{
3397
			$row = $this->db->fetch_row($resql);
3398
			return $row[0];
3399
		}
3400
	}
3401
3402
	/**
3403
	 *  Function to check if an object is used by others.
3404
	 *  Check is done into this->childtables. There is no check into llx_element_element.
3405
	 *
3406
	 *  @param	int		$id			Force id of object
3407
	 *  @return	int					<0 if KO, 0 if not used, >0 if already used
3408
	 */
3409
	function isObjectUsed($id=0)
3410
	{
3411
		global $langs;
3412
3413
		if (empty($id)) $id=$this->id;
3414
3415
		// Check parameters
3416
		if (! isset($this->childtables) || ! is_array($this->childtables) || count($this->childtables) == 0)
3417
		{
3418
			dol_print_error('Called isObjectUsed on a class with property this->childtables not defined');
3419
			return -1;
3420
		}
3421
3422
		$arraytoscan = $this->childtables;
3423
		// For backward compatibility, we check if array is old format array('table1', 'table2', ...)
3424
		$tmparray=array_keys($this->childtables);
3425
		if (is_numeric($tmparray[0]))
3426
		{
3427
			$arraytoscan = array_flip($this->childtables);
3428
		}
3429
3430
		// Test if child exists
3431
		$haschild=0;
3432
		foreach($arraytoscan as $table => $elementname)
3433
		{
3434
			//print $id.'-'.$table.'-'.$elementname.'<br>';
3435
			// Check if third party can be deleted
3436
			$sql = "SELECT COUNT(*) as nb from ".MAIN_DB_PREFIX.$table;
3437
			$sql.= " WHERE ".$this->fk_element." = ".$id;
3438
			$resql=$this->db->query($sql);
3439
			if ($resql)
3440
			{
3441
				$obj=$this->db->fetch_object($resql);
3442
				if ($obj->nb > 0)
3443
				{
3444
					$langs->load("errors");
3445
					//print 'Found into table '.$table.', type '.$langs->transnoentitiesnoconv($elementname).', haschild='.$haschild;
3446
					$haschild += $obj->nb;
3447
					if (is_numeric($elementname))	// old usage
3448
					{
3449
						$this->errors[]=$langs->trans("ErrorRecordHasAtLeastOneChildOfType", $table);
3450
					}
3451
					else	// new usage: $elementname=Translation key
3452
					{
3453
						$this->errors[]=$langs->trans("ErrorRecordHasAtLeastOneChildOfType", $langs->transnoentitiesnoconv($elementname));
3454
					}
3455
					break;    // We found at least one, we stop here
3456
				}
3457
			}
3458
			else
3459
			{
3460
				$this->errors[]=$this->db->lasterror();
3461
				return -1;
3462
			}
3463
		}
3464
		if ($haschild > 0)
3465
		{
3466
			$this->errors[]="ErrorRecordHasChildren";
3467
			return $haschild;
3468
		}
3469
		else return 0;
3470
	}
3471
3472
	/**
3473
	 *  Function to say how many lines object contains
3474
	 *
3475
	 *	@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
3476
	 *  @return	int						<0 if KO, 0 if no predefined products, nb of lines with predefined products if found
3477
	 */
3478
	function hasProductsOrServices($predefined=-1)
3479
	{
3480
		$nb=0;
3481
3482
		foreach($this->lines as $key => $val)
3483
		{
3484
			$qualified=0;
3485
			if ($predefined == -1) $qualified=1;
3486
			if ($predefined == 1 && $val->fk_product > 0) $qualified=1;
3487
			if ($predefined == 0 && $val->fk_product <= 0) $qualified=1;
3488
			if ($predefined == 2 && $val->fk_product > 0 && $val->product_type==0) $qualified=1;
3489
			if ($predefined == 3 && $val->fk_product > 0 && $val->product_type==1) $qualified=1;
3490
			if ($qualified) $nb++;
3491
		}
3492
		dol_syslog(get_class($this).'::hasProductsOrServices we found '.$nb.' qualified lines of products/servcies');
3493
		return $nb;
3494
	}
3495
3496
	/**
3497
	 * Function that returns the total amount HT of discounts applied for all lines.
3498
	 *
3499
	 * @return 	float
3500
	 */
3501
	function getTotalDiscount()
3502
	{
3503
		$total_discount=0.00;
3504
3505
		$sql = "SELECT subprice as pu_ht, qty, remise_percent, total_ht";
3506
		$sql.= " FROM ".MAIN_DB_PREFIX.$this->table_element."det";
3507
		$sql.= " WHERE ".$this->fk_element." = ".$this->id;
3508
3509
		dol_syslog(get_class($this).'::getTotalDiscount', LOG_DEBUG);
3510
		$resql = $this->db->query($sql);
3511
		if ($resql)
3512
		{
3513
			$num=$this->db->num_rows($resql);
3514
			$i=0;
3515
			while ($i < $num)
3516
			{
3517
				$obj = $this->db->fetch_object($resql);
3518
3519
				$pu_ht = $obj->pu_ht;
3520
				$qty= $obj->qty;
3521
				$total_ht = $obj->total_ht;
3522
3523
				$total_discount_line = floatval(price2num(($pu_ht * $qty) - $total_ht, 'MT'));
3524
				$total_discount += $total_discount_line;
3525
3526
				$i++;
3527
			}
3528
		}
3529
3530
		//print $total_discount; exit;
3531
		return price2num($total_discount);
3532
	}
3533
3534
3535
	/**
3536
	 * Return into unit=0, the calculated total of weight and volume of all lines * qty
3537
	 * Calculate by adding weight and volume of each product line, so properties ->volume/volume_units/weight/weight_units must be loaded on line.
3538
	 *
3539
	 * @return  array                           array('weight'=>...,'volume'=>...)
3540
	 */
3541
	function getTotalWeightVolume()
3542
	{
3543
		$totalWeight = 0;
3544
		$totalVolume = 0;
3545
		// defined for shipment only
3546
		$totalOrdered = '';
3547
		// defined for shipment only
3548
		$totalToShip = '';
3549
3550
		foreach ($this->lines as $line)
3551
		{
3552
			if (isset($line->qty_asked))
3553
			{
3554
				if (empty($totalOrdered)) $totalOrdered=0;  // Avoid warning because $totalOrdered is ''
3555
				$totalOrdered+=$line->qty_asked;    // defined for shipment only
3556
			}
3557
			if (isset($line->qty_shipped))
3558
			{
3559
				if (empty($totalToShip)) $totalToShip=0;    // Avoid warning because $totalToShip is ''
3560
				$totalToShip+=$line->qty_shipped;   // defined for shipment only
3561
            }else if ($line->element == 'commandefournisseurdispatch' && isset($line->qty))
3562
            {
3563
                if (empty($totalToShip)) $totalToShip=0;
3564
                $totalToShip+=$line->qty;   // defined for reception only
3565
			}
3566
3567
			// Define qty, weight, volume, weight_units, volume_units
3568
			if ($this->element == 'shipping') {
3569
				// for shipments
3570
				$qty = $line->qty_shipped ? $line->qty_shipped : 0;
3571
			}
3572
			else {
3573
				$qty = $line->qty ? $line->qty : 0;
3574
			}
3575
3576
			$weight = $line->weight ? $line->weight : 0;
3577
            ($weight==0 && !empty($line->product->weight))? $weight=$line->product->weight: 0;
3578
			$volume = $line->volume ? $line->volume : 0;
3579
			($volume==0 && !empty($line->product->volume))? $volume=$line->product->volume: 0;
3580
3581
			$weight_units=$line->weight_units;
3582
			($weight_units==0 && !empty($line->product->weight_units))? $weight_units=$line->product->weight_units: 0;
3583
			$volume_units=$line->volume_units;
3584
			($volume_units==0 && !empty($line->product->volume_units))? $volume_units=$line->product->volume_units: 0;
3585
3586
			$weightUnit=0;
3587
			$volumeUnit=0;
3588
			if (! empty($weight_units)) $weightUnit = $weight_units;
3589
			if (! empty($volume_units)) $volumeUnit = $volume_units;
3590
3591
			if (empty($totalWeight)) $totalWeight=0;  // Avoid warning because $totalWeight is ''
3592
			if (empty($totalVolume)) $totalVolume=0;  // Avoid warning because $totalVolume is ''
3593
3594
			//var_dump($line->volume_units);
3595
			if ($weight_units < 50)   // >50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
3596
			{
3597
				$trueWeightUnit=pow(10, $weightUnit);
3598
				$totalWeight += $weight * $qty * $trueWeightUnit;
3599
			}
3600
			else {
3601
		if ($weight_units == 99) {
3602
			// conversion 1 Pound = 0.45359237 KG
3603
			$trueWeightUnit = 0.45359237;
3604
			$totalWeight += $weight * $qty * $trueWeightUnit;
3605
		} elseif ($weight_units == 98) {
3606
			// conversion 1 Ounce = 0.0283495 KG
3607
			$trueWeightUnit = 0.0283495;
3608
			$totalWeight += $weight * $qty * $trueWeightUnit;
3609
		}
3610
		else
3611
					$totalWeight += $weight * $qty;   // This may be wrong if we mix different units
3612
			}
3613
			if ($volume_units < 50)   // >50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
3614
			{
3615
				//print $line->volume."x".$line->volume_units."x".($line->volume_units < 50)."x".$volumeUnit;
3616
				$trueVolumeUnit=pow(10, $volumeUnit);
3617
				//print $line->volume;
3618
				$totalVolume += $volume * $qty * $trueVolumeUnit;
3619
			}
3620
			else
3621
			{
3622
				$totalVolume += $volume * $qty;   // This may be wrong if we mix different units
3623
			}
3624
		}
3625
3626
		return array('weight'=>$totalWeight, 'volume'=>$totalVolume, 'ordered'=>$totalOrdered, 'toship'=>$totalToShip);
3627
	}
3628
3629
3630
	/**
3631
	 *	Set extra parameters
3632
	 *
3633
	 *	@return	int      <0 if KO, >0 if OK
3634
	 */
3635
	function setExtraParameters()
3636
	{
3637
		$this->db->begin();
3638
3639
		$extraparams = (! empty($this->extraparams) ? json_encode($this->extraparams) : null);
3640
3641
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
3642
		$sql.= " SET extraparams = ".(! empty($extraparams) ? "'".$this->db->escape($extraparams)."'" : "null");
3643
		$sql.= " WHERE rowid = ".$this->id;
3644
3645
		dol_syslog(get_class($this)."::setExtraParameters", LOG_DEBUG);
3646
		$resql = $this->db->query($sql);
3647
		if (! $resql)
3648
		{
3649
			$this->error=$this->db->lasterror();
3650
			$this->db->rollback();
3651
			return -1;
3652
		}
3653
		else
3654
		{
3655
			$this->db->commit();
3656
			return 1;
3657
		}
3658
	}
3659
3660
3661
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3662
	/**
3663
	 *    Return incoterms informations
3664
	 *    TODO Use a cache for label get
3665
	 *
3666
	 *    @return	string	incoterms info
3667
	 */
3668
	function display_incoterms()
3669
	{
3670
        // phpcs:enable
3671
		$out = '';
3672
		$this->libelle_incoterms = '';
3673
		if (!empty($this->fk_incoterms))
3674
		{
3675
			$sql = 'SELECT code FROM '.MAIN_DB_PREFIX.'c_incoterms WHERE rowid = '.(int) $this->fk_incoterms;
3676
			$result = $this->db->query($sql);
3677
			if ($result)
3678
			{
3679
				$res = $this->db->fetch_object($result);
3680
				$out .= $res->code;
3681
			}
3682
		}
3683
3684
		$out .= (($res->code && $this->location_incoterms)?' - ':'').$this->location_incoterms;
3685
3686
		return $out;
3687
	}
3688
3689
	/**
3690
	 *    Return incoterms informations for pdf display
3691
	 *
3692
	 *    @return	string		incoterms info
3693
	 */
3694
	function getIncotermsForPDF()
3695
	{
3696
		$sql = 'SELECT code FROM '.MAIN_DB_PREFIX.'c_incoterms WHERE rowid = '.(int) $this->fk_incoterms;
3697
		$resql = $this->db->query($sql);
3698
		if ($resql)
3699
		{
3700
			$num = $this->db->num_rows($resql);
3701
			if ($num > 0)
3702
			{
3703
				$res = $this->db->fetch_object($resql);
3704
				return 'Incoterm : '.$res->code.' - '.$this->location_incoterms;
3705
			}
3706
			else
3707
			{
3708
				return '';
3709
			}
3710
		}
3711
		else
3712
		{
3713
			$this->errors[] = $this->db->lasterror();
3714
			return false;
3715
		}
3716
	}
3717
3718
	/**
3719
	 *    Define incoterms values of current object
3720
	 *
3721
	 *    @param	int		$id_incoterm     Id of incoterm to set or '' to remove
3722
	 * 	  @param 	string  $location		 location of incoterm
3723
	 *    @return	int     		<0 if KO, >0 if OK
3724
	 */
3725
	function setIncoterms($id_incoterm, $location)
3726
	{
3727
		if ($this->id && $this->table_element)
3728
		{
3729
			$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
3730
			$sql.= " SET fk_incoterms = ".($id_incoterm > 0 ? $id_incoterm : "null");
3731
			$sql.= ", location_incoterms = ".($id_incoterm > 0 ? "'".$this->db->escape($location)."'" : "null");
3732
			$sql.= " WHERE rowid = " . $this->id;
3733
			dol_syslog(get_class($this).'::setIncoterms', LOG_DEBUG);
3734
			$resql=$this->db->query($sql);
3735
			if ($resql)
3736
			{
3737
				$this->fk_incoterms = $id_incoterm;
3738
				$this->location_incoterms = $location;
3739
3740
				$sql = 'SELECT libelle FROM '.MAIN_DB_PREFIX.'c_incoterms WHERE rowid = '.(int) $this->fk_incoterms;
3741
				$res = $this->db->query($sql);
3742
				if ($res)
3743
				{
3744
					$obj = $this->db->fetch_object($res);
3745
					$this->libelle_incoterms = $obj->libelle;
3746
				}
3747
				return 1;
3748
			}
3749
			else
3750
			{
3751
				$this->errors[] = $this->db->lasterror();
3752
				return -1;
3753
			}
3754
		}
3755
		else return -1;
3756
	}
3757
3758
3759
	// --------------------
3760
	// TODO: All functions here must be redesigned and moved as they are not business functions but output functions
3761
	// --------------------
3762
3763
	/* This is to show add lines */
3764
3765
	/**
3766
	 *	Show add free and predefined products/services form
3767
	 *
3768
	 *  @param	int		        $dateSelector       1=Show also date range input fields
3769
	 *  @param	Societe			$seller				Object thirdparty who sell
3770
	 *  @param	Societe			$buyer				Object thirdparty who buy
3771
	 *	@return	void
3772
	 */
3773
	function formAddObjectLine($dateSelector, $seller, $buyer)
3774
	{
3775
		global $conf,$user,$langs,$object,$hookmanager;
3776
		global $form,$bcnd,$var;
3777
3778
		// Line extrafield
3779
		require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
3780
		$extrafieldsline = new ExtraFields($this->db);
3781
		$extralabelslines=$extrafieldsline->fetch_name_optionals_label($this->table_element_line);
3782
3783
		// Output template part (modules that overwrite templates must declare this into descriptor)
3784
		// Use global variables + $dateSelector + $seller and $buyer
3785
		$dirtpls=array_merge($conf->modules_parts['tpl'],array('/core/tpl'));
3786
		foreach($dirtpls as $reldir)
3787
		{
3788
			$tpl = dol_buildpath($reldir.'/objectline_create.tpl.php');
3789
			if (empty($conf->file->strict_mode)) {
3790
				$res=@include $tpl;
3791
			} else {
3792
				$res=include $tpl; // for debug
3793
			}
3794
			if ($res) break;
3795
		}
3796
	}
3797
3798
3799
3800
	/* This is to show array of line of details */
3801
3802
3803
	/**
3804
	 *	Return HTML table for object lines
3805
	 *	TODO Move this into an output class file (htmlline.class.php)
3806
	 *	If lines are into a template, title must also be into a template
3807
	 *	But for the moment we don't know if it's possible as we keep a method available on overloaded objects.
3808
	 *
3809
	 *	@param	string		$action				Action code
3810
	 *	@param  string		$seller            	Object of seller third party
3811
	 *	@param  string  	$buyer             	Object of buyer third party
3812
	 *	@param	int			$selected		   	Object line selected
3813
	 *	@param  int	    	$dateSelector      	1=Show also date range input fields
3814
	 *	@return	void
3815
	 */
3816
	function printObjectLines($action, $seller, $buyer, $selected=0, $dateSelector=0)
3817
	{
3818
		global $conf, $hookmanager, $langs, $user;
3819
		// TODO We should not use global var for this !
3820
		global $inputalsopricewithtax, $usemargins, $disableedit, $disablemove, $disableremove, $outputalsopricetotalwithtax;
3821
3822
		// Define usemargins
3823
		$usemargins=0;
3824
		if (! empty($conf->margin->enabled) && ! empty($this->element) && in_array($this->element,array('facture','propal','commande'))) $usemargins=1;
3825
3826
		$num = count($this->lines);
3827
3828
		// Line extrafield
3829
		require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
3830
		$extrafieldsline = new ExtraFields($this->db);
3831
		$extralabelslines=$extrafieldsline->fetch_name_optionals_label($this->table_element_line);
3832
3833
		$parameters = array('num'=>$num,'i'=>$i,'dateSelector'=>$dateSelector,'seller'=>$seller,'buyer'=>$buyer,'selected'=>$selected, 'extrafieldsline'=>$extrafieldsline);
3834
		$reshook = $hookmanager->executeHooks('printObjectLineTitle', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3835
		if (empty($reshook))
3836
		{
3837
			// Title line
3838
		    print "<thead>\n";
3839
3840
			print '<tr class="liste_titre nodrag nodrop">';
3841
3842
			// Adds a line numbering column
3843
			if (! empty($conf->global->MAIN_VIEW_LINE_NUMBER)) print '<td class="linecolnum" align="center" width="5">&nbsp;</td>';
3844
3845
			// Description
3846
			print '<td class="linecoldescription">'.$langs->trans('Description').'</td>';
3847
3848
			if ($this->element == 'supplier_proposal' || $this->element == 'order_supplier' || $this->element == 'invoice_supplier')
3849
			{
3850
				print '<td class="linerefsupplier"><span id="title_fourn_ref">'.$langs->trans("SupplierRef").'</span></td>';
3851
			}
3852
3853
			// VAT
3854
			print '<td class="linecolvat" align="right" width="80">'.$langs->trans('VAT').'</td>';
3855
3856
			// Price HT
3857
			print '<td class="linecoluht" align="right" width="80">'.$langs->trans('PriceUHT').'</td>';
3858
3859
			// Multicurrency
3860
			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>';
3861
3862
			if ($inputalsopricewithtax) print '<td align="right" width="80">'.$langs->trans('PriceUTTC').'</td>';
3863
3864
			// Qty
3865
			print '<td class="linecolqty" align="right">'.$langs->trans('Qty').'</td>';
3866
3867
			if($conf->global->PRODUCT_USE_UNITS)
3868
			{
3869
				print '<td class="linecoluseunit" align="left">'.$langs->trans('Unit').'</td>';
3870
			}
3871
3872
			// Reduction short
3873
			print '<td class="linecoldiscount" align="right">'.$langs->trans('ReductionShort').'</td>';
3874
3875
			if ($this->situation_cycle_ref) {
3876
				print '<td class="linecolcycleref" align="right">' . $langs->trans('Progress') . '</td>';
3877
			}
3878
3879
			if ($usemargins && ! empty($conf->margin->enabled) && empty($user->societe_id))
3880
			{
3881
				if (!empty($user->rights->margins->creer))
3882
				{
3883
					if ($conf->global->MARGIN_TYPE == "1")
3884
						print '<td class="linecolmargin1 margininfos" align="right" width="80">'.$langs->trans('BuyingPrice').'</td>';
3885
					else
3886
						print '<td class="linecolmargin1 margininfos" align="right" width="80">'.$langs->trans('CostPrice').'</td>';
3887
				}
3888
3889
				if (! empty($conf->global->DISPLAY_MARGIN_RATES) && $user->rights->margins->liretous)
3890
					print '<td class="linecolmargin2 margininfos" align="right" width="50">'.$langs->trans('MarginRate').'</td>';
3891
				if (! empty($conf->global->DISPLAY_MARK_RATES) && $user->rights->margins->liretous)
3892
					print '<td class="linecolmargin2 margininfos" align="right" width="50">'.$langs->trans('MarkRate').'</td>';
3893
			}
3894
3895
			// Total HT
3896
			print '<td class="linecolht" align="right">'.$langs->trans('TotalHTShort').'</td>';
3897
3898
			// Multicurrency
3899
			if (!empty($conf->multicurrency->enabled) && $this->multicurrency_code != $conf->currency) print '<td class="linecoltotalht_currency" align="right">'.$langs->trans('TotalHTShortCurrency', $this->multicurrency_code).'</td>';
3900
3901
			if ($outputalsopricetotalwithtax) print '<td align="right" width="80">'.$langs->trans('TotalTTCShort').'</td>';
3902
3903
			print '<td class="linecoledit"></td>';  // No width to allow autodim
3904
3905
			print '<td class="linecoldelete" width="10"></td>';
3906
3907
			print '<td class="linecolmove" width="10"></td>';
3908
3909
			if($action == 'selectlines')
3910
			{
3911
			    print '<td class="linecolcheckall" align="center">';
3912
			    print '<input type="checkbox" class="linecheckboxtoggle" />';
3913
			    print '<script type="text/javascript">$(document).ready(function() {$(".linecheckboxtoggle").click(function() {var checkBoxes = $(".linecheckbox");checkBoxes.prop("checked", this.checked);})});</script>';
3914
			    print '</td>';
3915
			}
3916
3917
			print "</tr>\n";
3918
			print "</thead>\n";
3919
		}
3920
3921
		$var = true;
3922
		$i	 = 0;
3923
3924
		print "<tbody>\n";
3925
		foreach ($this->lines as $line)
3926
		{
3927
			//Line extrafield
3928
			$line->fetch_optionals();
3929
3930
			//if (is_object($hookmanager) && (($line->product_type == 9 && ! empty($line->special_code)) || ! empty($line->fk_parent_line)))
3931
			if (is_object($hookmanager))   // Old code is commented on preceding line.
3932
			{
3933
				if (empty($line->fk_parent_line))
3934
				{
3935
					$parameters = array('line'=>$line,'var'=>$var,'num'=>$num,'i'=>$i,'dateSelector'=>$dateSelector,'seller'=>$seller,'buyer'=>$buyer,'selected'=>$selected, 'extrafieldsline'=>$extrafieldsline);
3936
					$reshook = $hookmanager->executeHooks('printObjectLine', $parameters, $this, $action);    // Note that $action and $object may have been modified by some hooks
3937
				}
3938
				else
3939
				{
3940
					$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);
3941
					$reshook = $hookmanager->executeHooks('printObjectSubLine', $parameters, $this, $action);    // Note that $action and $object may have been modified by some hooks
3942
				}
3943
			}
3944
			if (empty($reshook))
3945
			{
3946
				$this->printObjectLine($action,$line,$var,$num,$i,$dateSelector,$seller,$buyer,$selected,$extrafieldsline);
3947
			}
3948
3949
			$i++;
3950
		}
3951
		print "</tbody>\n";
3952
	}
3953
3954
	/**
3955
	 *	Return HTML content of a detail line
3956
	 *	TODO Move this into an output class file (htmlline.class.php)
3957
	 *
3958
	 *	@param	string		$action				GET/POST action
3959
	 *	@param CommonObjectLine $line		       	Selected object line to output
3960
	 *	@param  string	    $var               	Is it a an odd line (true)
3961
	 *	@param  int		    $num               	Number of line (0)
3962
	 *	@param  int		    $i					I
3963
	 *	@param  int		    $dateSelector      	1=Show also date range input fields
3964
	 *	@param  string	    $seller            	Object of seller third party
3965
	 *	@param  string	    $buyer             	Object of buyer third party
3966
	 *	@param	int			$selected		   	Object line selected
3967
	 *  @param  int			$extrafieldsline	Object of extrafield line attribute
3968
	 *	@return	void
3969
	 */
3970
	function printObjectLine($action,$line,$var,$num,$i,$dateSelector,$seller,$buyer,$selected=0,$extrafieldsline=0)
3971
	{
3972
		global $conf,$langs,$user,$object,$hookmanager;
3973
		global $form,$bc,$bcdd;
3974
		global $object_rights, $disableedit, $disablemove, $disableremove;   // TODO We should not use global var for this !
3975
3976
		$object_rights = $this->getRights();
3977
3978
		$element=$this->element;
3979
3980
		$text=''; $description=''; $type=0;
3981
3982
		// Show product and description
3983
		$type=(! empty($line->product_type)?$line->product_type:$line->fk_product_type);
3984
		// Try to enhance type detection using date_start and date_end for free lines where type was not saved.
3985
		if (! empty($line->date_start)) $type=1; // deprecated
3986
		if (! empty($line->date_end)) $type=1; // deprecated
3987
3988
		// Ligne en mode visu
3989
		if ($action != 'editline' || $selected != $line->id)
3990
		{
3991
			// Product
3992
			if ($line->fk_product > 0)
3993
			{
3994
				$product_static = new Product($this->db);
3995
				$product_static->fetch($line->fk_product);
3996
3997
				$product_static->ref = $line->ref; //can change ref in hook
3998
				$product_static->label = $line->label; //can change label in hook
3999
				$text=$product_static->getNomUrl(1);
4000
4001
				// Define output language and label
4002
				if (! empty($conf->global->MAIN_MULTILANGS))
4003
				{
4004
					if (! is_object($this->thirdparty))
4005
					{
4006
						dol_print_error('','Error: Method printObjectLine was called on an object and object->fetch_thirdparty was not done before');
4007
						return;
4008
					}
4009
4010
					$prod = new Product($this->db);
4011
					$prod->fetch($line->fk_product);
4012
4013
					$outputlangs = $langs;
4014
					$newlang='';
4015
					if (empty($newlang) && GETPOST('lang_id','aZ09')) $newlang=GETPOST('lang_id','aZ09');
4016
					if (! empty($conf->global->PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE) && empty($newlang)) $newlang=$this->thirdparty->default_lang;		// For language to language of customer
4017
					if (! empty($newlang))
4018
					{
4019
						$outputlangs = new Translate("",$conf);
4020
						$outputlangs->setDefaultLang($newlang);
4021
					}
4022
4023
					$label = (! empty($prod->multilangs[$outputlangs->defaultlang]["label"])) ? $prod->multilangs[$outputlangs->defaultlang]["label"] : $line->product_label;
4024
				}
4025
				else
4026
				{
4027
					$label = $line->product_label;
4028
				}
4029
4030
				$text.= ' - '.(! empty($line->label)?$line->label:$label);
4031
				$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.
4032
			}
4033
4034
			$line->pu_ttc = price2num($line->subprice * (1 + ($line->tva_tx/100)), 'MU');
4035
4036
			// Output template part (modules that overwrite templates must declare this into descriptor)
4037
			// Use global variables + $dateSelector + $seller and $buyer
4038
			$dirtpls=array_merge($conf->modules_parts['tpl'],array('/core/tpl'));
4039
			foreach($dirtpls as $reldir)
4040
			{
4041
				$tpl = dol_buildpath($reldir.'/objectline_view.tpl.php');
4042
				if (empty($conf->file->strict_mode)) {
4043
					$res=@include $tpl;
4044
				} else {
4045
					$res=include $tpl; // for debug
4046
				}
4047
				if ($res) break;
4048
			}
4049
		}
4050
4051
		// Ligne en mode update
4052
		if ($this->statut == 0 && $action == 'editline' && $selected == $line->id)
4053
		{
4054
			$label = (! empty($line->label) ? $line->label : (($line->fk_product > 0) ? $line->product_label : ''));
4055
			$placeholder=' placeholder="'.$langs->trans("Label").'"';
4056
4057
			$line->pu_ttc = price2num($line->subprice * (1 + ($line->tva_tx/100)), 'MU');
4058
4059
			// Output template part (modules that overwrite templates must declare this into descriptor)
4060
			// Use global variables + $dateSelector + $seller and $buyer
4061
			$dirtpls=array_merge($conf->modules_parts['tpl'],array('/core/tpl'));
4062
			foreach($dirtpls as $reldir)
4063
			{
4064
				$tpl = dol_buildpath($reldir.'/objectline_edit.tpl.php');
4065
				if (empty($conf->file->strict_mode)) {
4066
					$res=@include $tpl;
4067
				} else {
4068
					$res=include $tpl; // for debug
4069
				}
4070
				if ($res) break;
4071
			}
4072
		}
4073
	}
4074
4075
4076
	/* This is to show array of line of details of source object */
4077
4078
4079
	/**
4080
	 * 	Return HTML table table of source object lines
4081
	 *  TODO Move this and previous function into output html class file (htmlline.class.php).
4082
	 *  If lines are into a template, title must also be into a template
4083
	 *  But for the moment we don't know if it's possible, so we keep the method available on overloaded objects.
4084
	 *
4085
	 *	@param	string		$restrictlist		''=All lines, 'services'=Restrict to services only
4086
	 *  @return	void
4087
	 */
4088
	function printOriginLinesList($restrictlist='')
4089
	{
4090
		global $langs, $hookmanager, $conf;
4091
4092
		print '<tr class="liste_titre">';
4093
		print '<td>'.$langs->trans('Ref').'</td>';
4094
		print '<td>'.$langs->trans('Description').'</td>';
4095
		print '<td align="right">'.$langs->trans('VATRate').'</td>';
4096
		print '<td align="right">'.$langs->trans('PriceUHT').'</td>';
4097
		if (!empty($conf->multicurrency->enabled)) print '<td align="right">'.$langs->trans('PriceUHTCurrency').'</td>';
4098
		print '<td align="right">'.$langs->trans('Qty').'</td>';
4099
		if($conf->global->PRODUCT_USE_UNITS)
4100
		{
4101
			print '<td align="left">'.$langs->trans('Unit').'</td>';
4102
		}
4103
		print '<td align="right">'.$langs->trans('ReductionShort').'</td></tr>';
4104
4105
		$var = true;
4106
		$i	 = 0;
4107
4108
		if (! empty($this->lines))
4109
		{
4110
			foreach ($this->lines as $line)
4111
			{
4112
				if (is_object($hookmanager) && (($line->product_type == 9 && ! empty($line->special_code)) || ! empty($line->fk_parent_line)))
4113
				{
4114
					if (empty($line->fk_parent_line))
4115
					{
4116
						$parameters=array('line'=>$line,'var'=>$var,'i'=>$i);
4117
						$action='';
4118
						$hookmanager->executeHooks('printOriginObjectLine',$parameters,$this,$action);    // Note that $action and $object may have been modified by some hooks
4119
					}
4120
				}
4121
				else
4122
				{
4123
					$this->printOriginLine($line, $var, $restrictlist);
4124
				}
4125
4126
				$i++;
4127
			}
4128
		}
4129
	}
4130
4131
	/**
4132
	 * 	Return HTML with a line of table array of source object lines
4133
	 *  TODO Move this and previous function into output html class file (htmlline.class.php).
4134
	 *  If lines are into a template, title must also be into a template
4135
	 *  But for the moment we don't know if it's possible as we keep a method available on overloaded objects.
4136
	 *
4137
	 * 	@param	CommonObjectLine	$line				Line
4138
	 * 	@param	string				$var				Var
4139
	 *	@param	string				$restrictlist		''=All lines, 'services'=Restrict to services only (strike line if not)
4140
	 * 	@return	void
4141
	 */
4142
	function printOriginLine($line, $var, $restrictlist='')
4143
	{
4144
		global $langs, $conf;
4145
4146
		//var_dump($line);
4147
		if (!empty($line->date_start))
4148
		{
4149
			$date_start=$line->date_start;
4150
		}
4151
		else
4152
		{
4153
			$date_start=$line->date_debut_prevue;
4154
			if ($line->date_debut_reel) $date_start=$line->date_debut_reel;
4155
		}
4156
		if (!empty($line->date_end))
4157
		{
4158
			$date_end=$line->date_end;
4159
		}
4160
		else
4161
		{
4162
			$date_end=$line->date_fin_prevue;
4163
			if ($line->date_fin_reel) $date_end=$line->date_fin_reel;
4164
		}
4165
4166
		$this->tpl['label'] = '';
4167
		if (! empty($line->fk_parent_line)) $this->tpl['label'].= img_picto('', 'rightarrow');
4168
4169
		if (($line->info_bits & 2) == 2)  // TODO Not sure this is used for source object
4170
		{
4171
			$discount=new DiscountAbsolute($this->db);
4172
			$discount->fk_soc = $this->socid;
4173
			$this->tpl['label'].= $discount->getNomUrl(0,'discount');
4174
		}
4175
		else if (! empty($line->fk_product))
4176
		{
4177
			$productstatic = new Product($this->db);
4178
			$productstatic->id = $line->fk_product;
4179
			$productstatic->ref = $line->ref;
4180
			$productstatic->type = $line->fk_product_type;
4181
            if(empty($productstatic->ref)){
4182
				$line->fetch_product();
4183
				$productstatic = $line->product;
4184
			}
4185
			
4186
			$this->tpl['label'].= $productstatic->getNomUrl(1);
4187
			$this->tpl['label'].= ' - '.(! empty($line->label)?$line->label:$line->product_label);
4188
			// Dates
4189
			if ($line->product_type == 1 && ($date_start || $date_end))
4190
			{
4191
				$this->tpl['label'].= get_date_range($date_start,$date_end);
4192
			}
4193
		}
4194
		else
4195
		{
4196
			$this->tpl['label'].= ($line->product_type == -1 ? '&nbsp;' : ($line->product_type == 1 ? img_object($langs->trans(''),'service') : img_object($langs->trans(''),'product')));
4197
			if (!empty($line->desc)) {
4198
				$this->tpl['label'].=$line->desc;
4199
			}else {
4200
				$this->tpl['label'].= ($line->label ? '&nbsp;'.$line->label : '');
4201
			}
4202
			
4203
			// Dates
4204
			if ($line->product_type == 1 && ($date_start || $date_end))
4205
			{
4206
				$this->tpl['label'].= get_date_range($date_start,$date_end);
4207
			}
4208
		}
4209
4210
		if (! empty($line->desc))
4211
		{
4212
			if ($line->desc == '(CREDIT_NOTE)')  // TODO Not sure this is used for source object
4213
			{
4214
				$discount=new DiscountAbsolute($this->db);
4215
				$discount->fetch($line->fk_remise_except);
4216
				$this->tpl['description'] = $langs->transnoentities("DiscountFromCreditNote",$discount->getNomUrl(0));
4217
			}
4218
			elseif ($line->desc == '(DEPOSIT)')  // TODO Not sure this is used for source object
4219
			{
4220
				$discount=new DiscountAbsolute($this->db);
4221
				$discount->fetch($line->fk_remise_except);
4222
				$this->tpl['description'] = $langs->transnoentities("DiscountFromDeposit",$discount->getNomUrl(0));
4223
			}
4224
			elseif ($line->desc == '(EXCESS RECEIVED)')
4225
			{
4226
				$discount=new DiscountAbsolute($this->db);
4227
				$discount->fetch($line->fk_remise_except);
4228
				$this->tpl['description'] = $langs->transnoentities("DiscountFromExcessReceived",$discount->getNomUrl(0));
4229
			}
4230
			elseif ($line->desc == '(EXCESS PAID)')
4231
			{
4232
				$discount=new DiscountAbsolute($this->db);
4233
				$discount->fetch($line->fk_remise_except);
4234
				$this->tpl['description'] = $langs->transnoentities("DiscountFromExcessPaid",$discount->getNomUrl(0));
4235
			}
4236
			else
4237
			{
4238
				$this->tpl['description'] = dol_trunc($line->desc,60);
4239
			}
4240
		}
4241
		else
4242
		{
4243
			$this->tpl['description'] = '&nbsp;';
4244
		}
4245
4246
        // VAT Rate
4247
        $this->tpl['vat_rate'] = vatrate($line->tva_tx, true);
4248
        $this->tpl['vat_rate'] .= (($line->info_bits & 1) == 1) ? '*' : '';
4249
        if (! empty($line->vat_src_code) && ! preg_match('/\(/', $this->tpl['vat_rate'])) $this->tpl['vat_rate'].=' ('.$line->vat_src_code.')';
4250
4251
		$this->tpl['price'] = price($line->subprice);
4252
		$this->tpl['multicurrency_price'] = price($line->multicurrency_subprice);
4253
		$this->tpl['qty'] = (($line->info_bits & 2) != 2) ? $line->qty : '&nbsp;';
4254
		if ($conf->global->PRODUCT_USE_UNITS) $this->tpl['unit'] = $langs->transnoentities($line->getLabelOfUnit('long'));
4255
		$this->tpl['remise_percent'] = (($line->info_bits & 2) != 2) ? vatrate($line->remise_percent, true) : '&nbsp;';
4256
4257
		// Is the line strike or not
4258
		$this->tpl['strike']=0;
4259
		if ($restrictlist == 'services' && $line->product_type != Product::TYPE_SERVICE) $this->tpl['strike']=1;
4260
4261
		// Output template part (modules that overwrite templates must declare this into descriptor)
4262
		// Use global variables + $dateSelector + $seller and $buyer
4263
		$dirtpls=array_merge($conf->modules_parts['tpl'],array('/core/tpl'));
4264
		foreach($dirtpls as $reldir)
4265
		{
4266
			$tpl = dol_buildpath($reldir.'/originproductline.tpl.php');
4267
			if (empty($conf->file->strict_mode)) {
4268
				$res=@include $tpl;
4269
			} else {
4270
				$res=include $tpl; // for debug
4271
			}
4272
			if ($res) break;
4273
		}
4274
	}
4275
4276
4277
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4278
	/**
4279
	 *	Add resources to the current object : add entry into llx_element_resources
4280
	 *	Need $this->element & $this->id
4281
	 *
4282
	 *	@param		int		$resource_id		Resource id
4283
	 *	@param		string	$resource_type		'resource'
4284
	 *	@param		int		$busy				Busy or not
4285
	 *	@param		int		$mandatory			Mandatory or not
4286
	 *	@return		int							<=0 if KO, >0 if OK
4287
	 */
4288
	function add_element_resource($resource_id, $resource_type, $busy=0, $mandatory=0)
4289
	{
4290
        // phpcs:enable
4291
		$this->db->begin();
4292
4293
		$sql = "INSERT INTO ".MAIN_DB_PREFIX."element_resources (";
4294
		$sql.= "resource_id";
4295
		$sql.= ", resource_type";
4296
		$sql.= ", element_id";
4297
		$sql.= ", element_type";
4298
		$sql.= ", busy";
4299
		$sql.= ", mandatory";
4300
		$sql.= ") VALUES (";
4301
		$sql.= $resource_id;
4302
		$sql.= ", '".$this->db->escape($resource_type)."'";
4303
		$sql.= ", '".$this->db->escape($this->id)."'";
4304
		$sql.= ", '".$this->db->escape($this->element)."'";
4305
		$sql.= ", '".$this->db->escape($busy)."'";
4306
		$sql.= ", '".$this->db->escape($mandatory)."'";
4307
		$sql.= ")";
4308
4309
		dol_syslog(get_class($this)."::add_element_resource", LOG_DEBUG);
4310
		if ($this->db->query($sql))
4311
		{
4312
			$this->db->commit();
4313
			return 1;
4314
		}
4315
		else
4316
		{
4317
			$this->error=$this->db->lasterror();
4318
			$this->db->rollback();
4319
			return  0;
4320
		}
4321
	}
4322
4323
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4324
	/**
4325
	 *    Delete a link to resource line
4326
	 *
4327
	 *    @param	int		$rowid			Id of resource line to delete
4328
	 *    @param	int		$element		element name (for trigger) TODO: use $this->element into commonobject class
4329
	 *    @param	int		$notrigger		Disable all triggers
4330
	 *    @return   int						>0 if OK, <0 if KO
4331
	 */
4332
	function delete_resource($rowid, $element, $notrigger=0)
4333
	{
4334
        // phpcs:enable
4335
		global $user;
4336
4337
		$this->db->begin();
4338
4339
		$sql = "DELETE FROM ".MAIN_DB_PREFIX."element_resources";
4340
		$sql.= " WHERE rowid=".$rowid;
4341
4342
		dol_syslog(get_class($this)."::delete_resource", LOG_DEBUG);
4343
4344
		$resql=$this->db->query($sql);
4345
		if (! $resql)
4346
		{
4347
			$this->error=$this->db->lasterror();
4348
			$this->db->rollback();
4349
			return -1;
4350
		}
4351
		else
4352
		{
4353
			if (! $notrigger)
4354
			{
4355
				$result=$this->call_trigger(strtoupper($element).'_DELETE_RESOURCE', $user);
4356
				if ($result < 0) { $this->db->rollback(); return -1; }
4357
			}
4358
			$this->db->commit();
4359
			return 1;
4360
		}
4361
	}
4362
4363
4364
	/**
4365
	 * Overwrite magic function to solve problem of cloning object that are kept as references
4366
	 *
4367
	 * @return void
4368
	 */
4369
	function __clone()
4370
	{
4371
		// Force a copy of this->lines, otherwise it will point to same object.
4372
		if (isset($this->lines) && is_array($this->lines))
4373
		{
4374
			$nboflines=count($this->lines);
4375
			for($i=0; $i < $nboflines; $i++)
4376
			{
4377
				$this->lines[$i] = clone $this->lines[$i];
4378
			}
4379
		}
4380
	}
4381
4382
	/**
4383
	 * Common function for all objects extending CommonObject for generating documents
4384
	 *
4385
	 * @param 	string 		$modelspath 	Relative folder where generators are placed
4386
	 * @param 	string 		$modele 		Generator to use. Caller must set it to obj->modelpdf or GETPOST('modelpdf') for example.
4387
	 * @param 	Translate 	$outputlangs 	Output language to use
4388
	 * @param 	int 		$hidedetails 	1 to hide details. 0 by default
4389
	 * @param 	int 		$hidedesc 		1 to hide product description. 0 by default
4390
	 * @param 	int 		$hideref 		1 to hide product reference. 0 by default
4391
	 * @param   null|array  $moreparams     Array to provide more information
4392
	 * @return 	int 						>0 if OK, <0 if KO
4393
	 * @see	addFileIntoDatabaseIndex
4394
	 */
4395
	protected function commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
4396
	{
4397
		global $conf, $langs, $user;
4398
4399
		$srctemplatepath='';
4400
4401
		// Increase limit for PDF build
4402
		$err=error_reporting();
4403
		error_reporting(0);
4404
		@set_time_limit(120);
4405
		error_reporting($err);
4406
4407
		// If selected model is a filename template (then $modele="modelname" or "modelname:filename")
4408
		$tmp=explode(':',$modele,2);
4409
		if (! empty($tmp[1]))
4410
		{
4411
			$modele=$tmp[0];
4412
			$srctemplatepath=$tmp[1];
4413
		}
4414
4415
		// Search template files
4416
		$file=''; $classname=''; $filefound=0;
4417
		$dirmodels=array('/');
4418
		if (is_array($conf->modules_parts['models'])) $dirmodels=array_merge($dirmodels,$conf->modules_parts['models']);
4419
		foreach($dirmodels as $reldir)
4420
		{
4421
			foreach(array('doc','pdf') as $prefix)
4422
			{
4423
				if (in_array(get_class($this), array('Adherent'))) $file = $prefix."_".$modele.".class.php";     // Member module use prefix_module.class.php
4424
				else $file = $prefix."_".$modele.".modules.php";
4425
4426
				// On verifie l'emplacement du modele
4427
				$file=dol_buildpath($reldir.$modelspath.$file,0);
4428
				if (file_exists($file))
4429
				{
4430
					$filefound=1;
4431
					$classname=$prefix.'_'.$modele;
4432
					break;
4433
				}
4434
			}
4435
			if ($filefound) break;
4436
		}
4437
4438
		// If generator was found
4439
		if ($filefound)
4440
		{
4441
			global $db;  // Required to solve a conception default in commonstickergenerator.class.php making an include of code using $db
4442
4443
			require_once $file;
4444
4445
			$obj = new $classname($this->db);
4446
4447
			// If generator is ODT, we must have srctemplatepath defined, if not we set it.
4448
			if ($obj->type == 'odt' && empty($srctemplatepath))
4449
			{
4450
				$varfortemplatedir=$obj->scandir;
4451
				if ($varfortemplatedir && ! empty($conf->global->$varfortemplatedir))
4452
				{
4453
					$dirtoscan=$conf->global->$varfortemplatedir;
4454
4455
					$listoffiles=array();
4456
4457
					// Now we add first model found in directories scanned
4458
					$listofdir=explode(',',$dirtoscan);
4459
					foreach($listofdir as $key => $tmpdir)
4460
					{
4461
						$tmpdir=trim($tmpdir);
4462
						$tmpdir=preg_replace('/DOL_DATA_ROOT/',DOL_DATA_ROOT,$tmpdir);
4463
						if (! $tmpdir) { unset($listofdir[$key]); continue; }
4464
						if (is_dir($tmpdir))
4465
						{
4466
							$tmpfiles=dol_dir_list($tmpdir,'files',0,'\.od(s|t)$','','name',SORT_ASC,0);
4467
							if (count($tmpfiles)) $listoffiles=array_merge($listoffiles,$tmpfiles);
4468
						}
4469
					}
4470
4471
					if (count($listoffiles))
4472
					{
4473
						foreach($listoffiles as $record)
4474
						{
4475
							$srctemplatepath=$record['fullname'];
4476
							break;
4477
						}
4478
					}
4479
				}
4480
4481
				if (empty($srctemplatepath))
4482
				{
4483
					$this->error='ErrorGenerationAskedForOdtTemplateWithSrcFileNotDefined';
4484
					return -1;
4485
				}
4486
			}
4487
4488
			if ($obj->type == 'odt' && ! empty($srctemplatepath))
4489
			{
4490
				if (! dol_is_file($srctemplatepath))
4491
				{
4492
					$this->error='ErrorGenerationAskedForOdtTemplateWithSrcFileNotFound';
4493
					return -1;
4494
				}
4495
			}
4496
4497
			// We save charset_output to restore it because write_file can change it if needed for
4498
			// output format that does not support UTF8.
4499
			$sav_charset_output=$outputlangs->charset_output;
4500
4501
			if (in_array(get_class($this), array('Adherent')))
4502
			{
4503
				$arrayofrecords = array();   // The write_file of templates of adherent class need this var
4504
				$resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, 'member', 1, $moreparams);
4505
			}
4506
			else
4507
			{
4508
				$resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, $hidedetails, $hidedesc, $hideref, $moreparams);
4509
			}
4510
			// After call of write_file $obj->result['fullpath'] is set with generated file. It will be used to update the ECM database index.
4511
4512
			if ($resultwritefile > 0)
4513
			{
4514
				$outputlangs->charset_output=$sav_charset_output;
4515
4516
				// We delete old preview
4517
				require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
4518
				dol_delete_preview($this);
4519
4520
				// Index file in database
4521
				if (! empty($obj->result['fullpath']))
4522
				{
4523
					$destfull = $obj->result['fullpath'];
4524
					$upload_dir = dirname($destfull);
4525
					$destfile = basename($destfull);
4526
					$rel_dir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT,'/').'/', '', $upload_dir);
4527
4528
					if (! preg_match('/[\\/]temp[\\/]|[\\/]thumbs|\.meta$/', $rel_dir))     // If not a tmp dir
4529
					{
4530
						$filename = basename($destfile);
4531
						$rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
4532
						$rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
4533
4534
						include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
4535
						$ecmfile=new EcmFiles($this->db);
4536
						$result = $ecmfile->fetch(0, '', ($rel_dir?$rel_dir.'/':'').$filename);
4537
4538
						// Set the public "share" key
4539
						$setsharekey = false;
4540
						if ($this->element == 'propal')
4541
						{
4542
							$useonlinesignature = $conf->global->MAIN_FEATURES_LEVEL;	// Replace this with 1 when feature to make online signature is ok
4543
							if ($useonlinesignature) $setsharekey=true;
4544
							if (! empty($conf->global->PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD)) $setsharekey=true;
4545
						}
4546
						if ($this->element == 'commande'     && ! empty($conf->global->ORDER_ALLOW_EXTERNAL_DOWNLOAD))        $setsharekey=true;
4547
						if ($this->element == 'facture'      && ! empty($conf->global->INVOICE_ALLOW_EXTERNAL_DOWNLOAD))      $setsharekey=true;
4548
						if ($this->element == 'bank_account' && ! empty($conf->global->BANK_ACCOUNT_ALLOW_EXTERNAL_DOWNLOAD)) $setsharekey=true;
4549
4550
						if ($setsharekey)
4551
						{
4552
							if (empty($ecmfile->share))	// Because object not found or share not set yet
4553
							{
4554
								require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
4555
								$ecmfile->share = getRandomPassword(true);
4556
							}
4557
						}
4558
4559
						if ($result > 0)
4560
						{
4561
							$ecmfile->label = md5_file(dol_osencode($destfull));	// hash of file content
4562
							$ecmfile->fullpath_orig = '';
4563
							$ecmfile->gen_or_uploaded = 'generated';
4564
							$ecmfile->description = '';    // indexed content
4565
							$ecmfile->keyword = '';        // keyword content
4566
							$result = $ecmfile->update($user);
4567
							if ($result < 0)
4568
							{
4569
								setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
4570
							}
4571
						}
4572
						else
4573
						{
4574
							$ecmfile->entity = $conf->entity;
4575
							$ecmfile->filepath = $rel_dir;
4576
							$ecmfile->filename = $filename;
4577
							$ecmfile->label = md5_file(dol_osencode($destfull));	// hash of file content
4578
							$ecmfile->fullpath_orig = '';
4579
							$ecmfile->gen_or_uploaded = 'generated';
4580
							$ecmfile->description = '';    // indexed content
4581
							$ecmfile->keyword = '';        // keyword content
4582
							$ecmfile->src_object_type = $this->table_element;
4583
							$ecmfile->src_object_id   = $this->id;
4584
4585
							$result = $ecmfile->create($user);
4586
							if ($result < 0)
4587
							{
4588
								setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
4589
							}
4590
						}
4591
4592
						/*$this->result['fullname']=$destfull;
4593
						$this->result['filepath']=$ecmfile->filepath;
4594
						$this->result['filename']=$ecmfile->filename;*/
4595
						//var_dump($obj->update_main_doc_field);exit;
4596
4597
						// Update the last_main_doc field into main object (if documenent generator has property ->update_main_doc_field set)
4598
						$update_main_doc_field=0;
4599
						if (! empty($obj->update_main_doc_field)) $update_main_doc_field=1;
4600
						if ($update_main_doc_field && ! empty($this->table_element))
4601
						{
4602
							$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element." SET last_main_doc = '".($ecmfile->filepath.'/'.$ecmfile->filename)."'";
4603
							$sql.= ' WHERE rowid = '.$this->id;
4604
							$resql = $this->db->query($sql);
4605
							if (! $resql) dol_print_error($this->db);
4606
						}
4607
					}
4608
				}
4609
				else
4610
				{
4611
					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);
4612
				}
4613
4614
				// Success in building document. We build meta file.
4615
				dol_meta_create($this);
4616
4617
				return 1;
4618
			}
4619
			else
4620
			{
4621
				$outputlangs->charset_output=$sav_charset_output;
4622
				dol_print_error($this->db, "Error generating document for ".__CLASS__.". Error: ".$obj->error, $obj->errors);
4623
				return -1;
4624
			}
4625
		}
4626
		else
4627
		{
4628
			$this->error=$langs->trans("Error")." ".$langs->trans("ErrorFileDoesNotExists",$file);
4629
			dol_print_error('',$this->error);
4630
			return -1;
4631
		}
4632
	}
4633
4634
	/**
4635
	 *  Build thumb
4636
	 *  @TODO Move this into files.lib.php
4637
	 *
4638
	 *  @param      string	$file           Path file in UTF8 to original file to create thumbs from.
4639
	 *	@return		void
4640
	 */
4641
	function addThumbs($file)
4642
	{
4643
		global $maxwidthsmall, $maxheightsmall, $maxwidthmini, $maxheightmini, $quality;
4644
4645
		require_once DOL_DOCUMENT_ROOT .'/core/lib/images.lib.php';		// This define also $maxwidthsmall, $quality, ...
4646
4647
		$file_osencoded=dol_osencode($file);
4648
		if (file_exists($file_osencoded))
4649
		{
4650
			// Create small thumbs for company (Ratio is near 16/9)
4651
			// Used on logon for example
4652
			vignette($file_osencoded, $maxwidthsmall, $maxheightsmall, '_small', $quality);
4653
4654
			// Create mini thumbs for company (Ratio is near 16/9)
4655
			// Used on menu or for setup page for example
4656
			vignette($file_osencoded, $maxwidthmini, $maxheightmini, '_mini', $quality);
4657
		}
4658
	}
4659
4660
4661
	/* Functions common to commonobject and commonobjectline */
4662
4663
	/* For default values */
4664
4665
	/**
4666
	 * Return the default value to use for a field when showing the create form of object.
4667
	 * Return values in this order:
4668
	 * 1) If parameter is available into POST, we return it first.
4669
	 * 2) If not but an alternate value was provided as parameter of function, we return it.
4670
	 * 3) If not but a constant $conf->global->OBJECTELEMENT_FIELDNAME is set, we return it (It is better to use the dedicated table).
4671
	 * 4) Return value found into database (TODO No yet implemented)
4672
	 *
4673
	 * @param   string              $fieldname          Name of field
4674
	 * @param   string              $alternatevalue     Alternate value to use
4675
	 * @return  string|string[]                         Default value (can be an array if the GETPOST return an array)
4676
	 **/
4677
	function getDefaultCreateValueFor($fieldname, $alternatevalue=null)
4678
	{
4679
		global $conf, $_POST;
4680
4681
		// If param here has been posted, we use this value first.
4682
		if (isset($_POST[$fieldname])) return GETPOST($fieldname, 2);
4683
4684
		if (isset($alternatevalue)) return $alternatevalue;
4685
4686
		$newelement=$this->element;
4687
		if ($newelement == 'facture') $newelement='invoice';
4688
		if ($newelement == 'commande') $newelement='order';
4689
		if (empty($newelement))
4690
		{
4691
			dol_syslog("Ask a default value using common method getDefaultCreateValueForField on an object with no property ->element defined. Return empty string.", LOG_WARNING);
4692
			return '';
4693
		}
4694
4695
		$keyforfieldname=strtoupper($newelement.'_DEFAULT_'.$fieldname);
4696
		//var_dump($keyforfieldname);
4697
		if (isset($conf->global->$keyforfieldname)) return $conf->global->$keyforfieldname;
4698
4699
		// TODO Ad here a scan into table llx_overwrite_default with a filter on $this->element and $fieldname
4700
	}
4701
4702
4703
	/* For triggers */
4704
4705
4706
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4707
	/**
4708
	 * Call trigger based on this instance.
4709
	 * Some context information may also be provided into array property this->context.
4710
	 * NB:  Error from trigger are stacked in interface->errors
4711
	 * NB2: If return code of triggers are < 0, action calling trigger should cancel all transaction.
4712
	 *
4713
	 * @param   string    $trigger_name   trigger's name to execute
4714
	 * @param   User      $user           Object user
4715
	 * @return  int                       Result of run_triggers
4716
	 */
4717
	function call_trigger($trigger_name, $user)
4718
	{
4719
        // phpcs:enable
4720
		global $langs,$conf;
4721
4722
		include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
4723
		$interface=new Interfaces($this->db);
4724
		$result=$interface->run_triggers($trigger_name,$this,$user,$langs,$conf);
4725
4726
		if ($result < 0)
4727
		{
4728
			if (!empty($this->errors))
4729
			{
4730
				$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.
4731
			}
4732
			else
4733
			{
4734
				$this->errors=$interface->errors;
4735
			}
4736
		}
4737
		return $result;
4738
	}
4739
4740
4741
	/* Functions for extrafields */
4742
4743
4744
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4745
	/**
4746
	 *  Function to get extra fields of an object into $this->array_options
4747
	 *  This method is in most cases called by method fetch of objects but you can call it separately.
4748
	 *
4749
	 *  @param	int		$rowid			Id of line. Use the id of object if not defined. Deprecated. Function must be called without parameters.
4750
	 *  @param  array	$optionsArray   Array resulting of call of extrafields->fetch_name_optionals_label(). Deprecated. Function must be called without parameters.
4751
	 *  @return	int						<0 if error, 0 if no values of extrafield to find nor found, 1 if an attribute is found and value loaded
4752
	 */
4753
	function fetch_optionals($rowid=null, $optionsArray=null)
4754
	{
4755
        // phpcs:enable
4756
		if (empty($rowid)) $rowid=$this->id;
4757
4758
		// To avoid SQL errors. Probably not the better solution though
4759
		if (!$this->table_element) {
4760
			return 0;
4761
		}
4762
4763
		$this->array_options=array();
4764
4765
		if (! is_array($optionsArray))
4766
		{
4767
			// If $extrafields is not a known object, we initialize it. Best practice is to have $extrafields defined into card.php or list.php page.
4768
			// TODO Use of existing $extrafield is not yet ready (must mutualize code that use extrafields in form first)
4769
			// global $extrafields;
4770
			//if (! is_object($extrafields))
4771
			//{
4772
				// require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
4773
            $extrafields = new ExtraFields();
4774
            //}
4775
4776
			// Load array of extrafields for elementype = $this->table_element
4777
			if (empty($extrafields->attributes[$this->table_element]['loaded']))
4778
			{
4779
				$extrafields->fetch_name_optionals_label($this->table_element);
4780
			}
4781
			$optionsArray = (! empty($extrafields->attributes[$this->table_element]['label'])?$extrafields->attributes[$this->table_element]['label']:null);
4782
		}
4783
		else
4784
		{
4785
			global $extrafields;
4786
			dol_syslog("Warning: fetch_optionals was called with param optionsArray defined when you should pass null now", LOG_WARNING);
4787
		}
4788
4789
		$table_element = $this->table_element;
4790
		if ($table_element == 'categorie') $table_element = 'categories'; // For compatibility
4791
4792
		// Request to get complementary values
4793
		if (is_array($optionsArray) && count($optionsArray) > 0)
4794
		{
4795
			$sql = "SELECT rowid";
4796
			foreach ($optionsArray as $name => $label)
4797
			{
4798
				if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || $extrafields->attributes[$this->table_element]['type'][$name] != 'separate')
4799
				{
4800
					$sql.= ", ".$name;
4801
				}
4802
			}
4803
			$sql.= " FROM ".MAIN_DB_PREFIX.$table_element."_extrafields";
4804
			$sql.= " WHERE fk_object = ".$rowid;
4805
4806
			//dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG);		// Too verbose
4807
			$resql=$this->db->query($sql);
4808
			if ($resql)
4809
			{
4810
				$this->array_options = array();
4811
				$numrows=$this->db->num_rows($resql);
4812
				if ($numrows)
4813
				{
4814
					$tab = $this->db->fetch_array($resql);
4815
4816
					foreach ($tab as $key => $value)
4817
					{
4818
						// 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)
4819
						if ($key != 'rowid' && $key != 'tms' && $key != 'fk_member' && ! is_int($key))
4820
						{
4821
							// we can add this attribute to object
4822
							if (! empty($extrafields) && in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date','datetime')))
4823
							{
4824
								//var_dump($extrafields->attributes[$this->table_element]['type'][$key]);
4825
								$this->array_options["options_".$key]=$this->db->jdate($value);
4826
							}
4827
							else
4828
							{
4829
								$this->array_options["options_".$key]=$value;
4830
							}
4831
4832
							//var_dump('key '.$key.' '.$value.' type='.$extrafields->attributes[$this->table_element]['type'][$key].' '.$this->array_options["options_".$key]);
4833
						}
4834
					}
4835
				}
4836
4837
				$this->db->free($resql);
4838
4839
				if ($numrows) return $numrows;
4840
				else return 0;
4841
			}
4842
			else
4843
			{
4844
				dol_print_error($this->db);
4845
				return -1;
4846
			}
4847
		}
4848
		return 0;
4849
	}
4850
4851
	/**
4852
	 *	Delete all extra fields values for the current object.
4853
	 *
4854
	 *  @return	int		<0 if KO, >0 if OK
4855
	 */
4856
	function deleteExtraFields()
4857
	{
4858
		$this->db->begin();
4859
4860
		$table_element = $this->table_element;
4861
		if ($table_element == 'categorie') $table_element = 'categories'; // For compatibility
4862
4863
		$sql_del = "DELETE FROM ".MAIN_DB_PREFIX.$table_element."_extrafields WHERE fk_object = ".$this->id;
4864
		dol_syslog(get_class($this)."::deleteExtraFields delete", LOG_DEBUG);
4865
		$resql=$this->db->query($sql_del);
4866
		if (! $resql)
4867
		{
4868
			$this->error=$this->db->lasterror();
4869
			$this->db->rollback();
4870
			return -1;
4871
		}
4872
		else
4873
		{
4874
			$this->db->commit();
4875
			return 1;
4876
		}
4877
	}
4878
4879
	/**
4880
	 *	Add/Update all extra fields values for the current object.
4881
	 *  Data to describe values to insert/update are stored into $this->array_options=array('options_codeforfield1'=>'valueforfield1', 'options_codeforfield2'=>'valueforfield2', ...)
4882
	 *  This function delete record with all extrafields and insert them again from the array $this->array_options.
4883
	 *
4884
	 *  @param	string		$trigger		If defined, call also the trigger (for example COMPANY_MODIFY)
4885
	 *  @param	User		$userused		Object user
4886
	 *  @return int 						-1=error, O=did nothing, 1=OK
4887
	 *  @see updateExtraField, setValueFrom
4888
	 */
4889
	function insertExtraFields($trigger='', $userused=null)
4890
	{
4891
		global $conf,$langs,$user;
4892
4893
		if (empty($userused)) $userused=$user;
4894
4895
		$error=0;
4896
4897
		if (! empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) return 0;	// For avoid conflicts if trigger used
4898
4899
		if (! empty($this->array_options))
4900
		{
4901
			// Check parameters
4902
			$langs->load('admin');
4903
			require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
4904
			$extrafields = new ExtraFields($this->db);
4905
			$target_extrafields=$extrafields->fetch_name_optionals_label($this->table_element);
4906
4907
			//Eliminate copied source object extra_fields that do not exist in target object
4908
			$new_array_options=array();
4909
			foreach ($this->array_options as $key => $value) {
4910
				if (in_array(substr($key,8), array_keys($target_extrafields)))	// We remove the 'options_' from $key for test
4911
					$new_array_options[$key] = $value;
4912
				elseif (in_array($key, array_keys($target_extrafields)))		// We test on $key that does not contains the 'options_' prefix
4913
					$new_array_options['options_'.$key] = $value;
4914
			}
4915
4916
			foreach($new_array_options as $key => $value)
4917
			{
4918
			   	$attributeKey      = substr($key,8);   // Remove 'options_' prefix
4919
			   	$attributeType     = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
4920
			   	$attributeLabel    = $extrafields->attributes[$this->table_element]['label'][$attributeKey];
4921
			   	$attributeParam    = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
4922
			   	$attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
4923
4924
			   	if ($attributeRequired)
4925
			   	{
4926
			   		$mandatorypb=false;
4927
			   		if ($attributeType == 'link' && $this->array_options[$key] == '-1') $mandatorypb=true;
4928
			   		if ($this->array_options[$key] === '') $mandatorypb=true;
4929
			   		if ($mandatorypb)
4930
			   		{
4931
			   			dol_syslog($this->error);
4932
			   			$this->errors[]=$langs->trans('ErrorFieldRequired', $attributeLabel);
4933
			   			return -1;
4934
			   		}
4935
			   	}
4936
4937
				//dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
4938
				//dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
4939
4940
			   	switch ($attributeType)
4941
			   	{
4942
			   		case 'int':
4943
			  			if (!is_numeric($value) && $value!='')
4944
			   			{
4945
			   				$this->errors[]=$langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
4946
			   				return -1;
4947
			  			}
4948
			   			elseif ($value=='')
4949
			   			{
4950
			   				$new_array_options[$key] = null;
4951
			   			}
4952
			 			break;
4953
					case 'double':
4954
						$value = price2num($value);
4955
						if (!is_numeric($value) && $value!='')
4956
						{
4957
							dol_syslog($langs->trans("ExtraFieldHasWrongValue")." sur ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
4958
							$this->errors[]=$langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
4959
							return -1;
4960
						}
4961
						elseif ($value=='')
4962
						{
4963
							$new_array_options[$key] = null;
4964
						}
4965
						//dol_syslog("double value"." sur ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
4966
						$new_array_options[$key] = $value;
4967
						break;
4968
			 		/*case 'select':	// Not required, we chosed value='0' for undefined values
4969
             			if ($value=='-1')
4970
             			{
4971
             				$this->array_options[$key] = null;
4972
             			}
4973
             			break;*/
4974
			   		case 'password':
4975
			   			$algo='';
4976
			   			if ($this->array_options[$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']))
4977
			   			{
4978
			   				// If there is an encryption choice, we use it to crypt data before insert
4979
			   				$tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
4980
			   				$algo=reset($tmparrays);
4981
			   				if ($algo != '')
4982
			   				{
4983
			   					//global $action;		// $action may be 'create', 'update', 'update_extras'...
4984
			   					//var_dump($action);
4985
			   					//var_dump($this->oldcopy);exit;
4986
			   					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
4987
			   					{
4988
			   						//var_dump($this->oldcopy->array_options[$key]); var_dump($this->array_options[$key]);
4989
				   					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.
4990
				   					{
4991
				   						$new_array_options[$key] = $this->array_options[$key];	// Value is kept
4992
				   					}
4993
									else
4994
									{
4995
										// var_dump($algo);
4996
										$newvalue = dol_hash($this->array_options[$key], $algo);
4997
										$new_array_options[$key] = $newvalue;
4998
									}
4999
			   					}
5000
			   					else
5001
			   					{
5002
			   						$new_array_options[$key] = $this->array_options[$key];	// Value is kept
5003
			   					}
5004
			   				}
5005
			   			}
5006
			   			else	// Common usage
5007
			   			{
5008
			   				$new_array_options[$key] = $this->array_options[$key];
5009
			   			}
5010
			   			break;
5011
			   		case 'price':
5012
						$new_array_options[$key] = price2num($this->array_options[$key]);
5013
						break;
5014
					case 'date':
5015
						$new_array_options[$key] = $this->db->idate($this->array_options[$key]);
5016
						break;
5017
					case 'datetime':
5018
						// If data is a string instead of a timestamp, we convert it
5019
						if (! is_int($this->array_options[$key])) {
5020
							$this->array_options[$key] = strtotime($this->array_options[$key]);
5021
						}
5022
						$new_array_options[$key] = $this->db->idate($this->array_options[$key]);
5023
						break;
5024
		   			case 'link':
5025
						$param_list=array_keys($attributeParam['options']);
5026
						// 0 : ObjectName
5027
						// 1 : classPath
5028
						$InfoFieldList = explode(":", $param_list[0]);
5029
						dol_include_once($InfoFieldList[1]);
5030
						if ($InfoFieldList[0] && class_exists($InfoFieldList[0]))
5031
						{
5032
							if ($value == '-1')	// -1 is key for no defined in combo list of objects
5033
							{
5034
								$new_array_options[$key]='';
5035
							}
5036
							elseif ($value)
5037
							{
5038
								$object = new $InfoFieldList[0]($this->db);
5039
								if (is_numeric($value)) $res=$object->fetch($value);
5040
								else $res=$object->fetch('',$value);
5041
5042
								if ($res > 0) $new_array_options[$key]=$object->id;
5043
								else
5044
								{
5045
									$this->error="Id/Ref '".$value."' for object '".$object->element."' not found";
5046
									$this->db->rollback();
5047
									return -1;
5048
								}
5049
							}
5050
						}
5051
						else
5052
						{
5053
							dol_syslog('Error bad setup of extrafield', LOG_WARNING);
5054
						}
5055
						break;
5056
			   	}
5057
			}
5058
5059
			$this->db->begin();
5060
5061
			$table_element = $this->table_element;
5062
			if ($table_element == 'categorie') $table_element = 'categories'; // For compatibility
5063
5064
			$sql_del = "DELETE FROM ".MAIN_DB_PREFIX.$table_element."_extrafields WHERE fk_object = ".$this->id;
5065
			dol_syslog(get_class($this)."::insertExtraFields delete", LOG_DEBUG);
5066
			$this->db->query($sql_del);
5067
5068
			$sql = "INSERT INTO ".MAIN_DB_PREFIX.$table_element."_extrafields (fk_object";
5069
			foreach($new_array_options as $key => $value)
5070
			{
5071
				$attributeKey = substr($key,8);   // Remove 'options_' prefix
5072
				// Add field of attribut
5073
				if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] != 'separate') // Only for other type than separator
5074
					$sql.=",".$attributeKey;
5075
			}
5076
			$sql .= ") VALUES (".$this->id;
5077
5078
			foreach($new_array_options as $key => $value)
5079
			{
5080
				$attributeKey = substr($key,8);   // Remove 'options_' prefix
5081
				// Add field of attribute
5082
				if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] != 'separate') // Only for other type than separator)
5083
				{
5084
					if ($new_array_options[$key] != '')
5085
					{
5086
						$sql.=",'".$this->db->escape($new_array_options[$key])."'";
5087
					}
5088
					else
5089
					{
5090
						$sql.=",null";
5091
					}
5092
				}
5093
			}
5094
			$sql.=")";
5095
5096
			dol_syslog(get_class($this)."::insertExtraFields insert", LOG_DEBUG);
5097
			$resql = $this->db->query($sql);
5098
			if (! $resql)
5099
			{
5100
				$this->error=$this->db->lasterror();
5101
				$error++;
5102
			}
5103
5104
			if (! $error && $trigger)
5105
			{
5106
				// Call trigger
5107
				$this->context=array('extrafieldaddupdate'=>1);
5108
				$result=$this->call_trigger($trigger, $userused);
5109
				if ($result < 0) $error++;
5110
				// End call trigger
5111
			}
5112
5113
			if ($error)
5114
			{
5115
				$this->db->rollback();
5116
				return -1;
5117
			}
5118
			else
5119
			{
5120
				$this->db->commit();
5121
				return 1;
5122
			}
5123
		}
5124
		else return 0;
5125
	}
5126
5127
	/**
5128
	 *	Update an extra field value for the current object.
5129
	 *  Data to describe values to update are stored into $this->array_options=array('options_codeforfield1'=>'valueforfield1', 'options_codeforfield2'=>'valueforfield2', ...)
5130
	 *
5131
	 *  @param  string      $key    		Key of the extrafield (without starting 'options_')
5132
	 *  @param	string		$trigger		If defined, call also the trigger (for example COMPANY_MODIFY)
5133
	 *  @param	User		$userused		Object user
5134
	 *  @return int                 		-1=error, O=did nothing, 1=OK
5135
	 *  @see setValueFrom, insertExtraFields
5136
	 */
5137
	function updateExtraField($key, $trigger=null, $userused=null)
5138
	{
5139
		global $conf,$langs,$user;
5140
5141
		if (empty($userused)) $userused=$user;
5142
5143
		$error=0;
5144
5145
		if (! empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) return 0;	// For avoid conflicts if trigger used
5146
5147
		if (! empty($this->array_options) && isset($this->array_options["options_".$key]))
5148
		{
5149
			// Check parameters
5150
			$langs->load('admin');
5151
			require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5152
			$extrafields = new ExtraFields($this->db);
5153
			$target_extrafields=$extrafields->fetch_name_optionals_label($this->table_element);
5154
5155
			$value=$this->array_options["options_".$key];
5156
5157
			$attributeType     = $extrafields->attributes[$this->table_element]['type'][$key];
5158
			$attributeLabel    = $extrafields->attributes[$this->table_element]['label'][$key];
5159
			$attributeParam    = $extrafields->attributes[$this->table_element]['param'][$key];
5160
			$attributeRequired = $extrafields->attributes[$this->table_element]['required'][$key];
5161
5162
			//dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
5163
			//dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
5164
5165
			switch ($attributeType)
5166
			{
5167
				case 'int':
5168
					if (!is_numeric($value) && $value!='')
5169
					{
5170
						$this->errors[]=$langs->trans("ExtraFieldHasWrongValue",$attributeLabel);
5171
						return -1;
5172
					}
5173
					elseif ($value=='')
5174
					{
5175
						$this->array_options["options_".$key] = null;
5176
					}
5177
					break;
5178
				case 'double':
5179
					$value = price2num($value);
5180
					if (!is_numeric($value) && $value!='')
5181
					{
5182
						dol_syslog($langs->trans("ExtraFieldHasWrongValue")." sur ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
5183
						$this->errors[]=$langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
5184
						return -1;
5185
					}
5186
					elseif ($value=='')
5187
					{
5188
						$this->array_options["options_".$key] = null;
5189
					}
5190
					//dol_syslog("double value"." sur ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
5191
					$this->array_options["options_".$key] = $value;
5192
					break;
5193
			 	/*case 'select':	// Not required, we chosed value='0' for undefined values
5194
             		if ($value=='-1')
5195
             		{
5196
             			$this->array_options[$key] = null;
5197
             		}
5198
             		break;*/
5199
				case 'price':
5200
					$this->array_options["options_".$key] = price2num($this->array_options["options_".$key]);
5201
					break;
5202
				case 'date':
5203
					$this->array_options["options_".$key]=$this->db->idate($this->array_options["options_".$key]);
5204
					break;
5205
				case 'datetime':
5206
					$this->array_options["options_".$key]=$this->db->idate($this->array_options["options_".$key]);
5207
					break;
5208
				case 'link':
5209
					$param_list=array_keys($attributeParam['options']);
5210
					// 0 : ObjectName
5211
					// 1 : classPath
5212
					$InfoFieldList = explode(":", $param_list[0]);
5213
					dol_include_once($InfoFieldList[1]);
5214
					if ($value)
5215
					{
5216
						$object = new $InfoFieldList[0]($this->db);
5217
						$object->fetch(0,$value);
5218
						$this->array_options["options_".$key]=$object->id;
5219
					}
5220
					break;
5221
			}
5222
5223
			$this->db->begin();
5224
			$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element."_extrafields SET ".$key."='".$this->db->escape($this->array_options["options_".$key])."'";
5225
			$sql .= " WHERE fk_object = ".$this->id;
5226
			$resql = $this->db->query($sql);
5227
			if (! $resql)
5228
			{
5229
				$error++;
5230
				$this->error=$this->db->lasterror();
5231
			}
5232
5233
			if (! $error && $trigger)
5234
			{
5235
				// Call trigger
5236
				$this->context=array('extrafieldupdate'=>1);
5237
				$result=$this->call_trigger($trigger, $userused);
5238
				if ($result < 0) $error++;
5239
				// End call trigger
5240
			}
5241
5242
			if ($error)
5243
			{
5244
				dol_syslog(get_class($this) . "::".__METHOD__ . $this->error, LOG_ERR);
5245
				$this->db->rollback();
5246
				return -1;
5247
			}
5248
			else
5249
			{
5250
				$this->db->commit();
5251
				return 1;
5252
			}
5253
		}
5254
		else return 0;
5255
	}
5256
5257
5258
	/**
5259
	 * Return HTML string to put an input field into a page
5260
	 * Code very similar with showInputField of extra fields
5261
	 *
5262
	 * @param  array   		$val	       Array of properties for field to show
5263
	 * @param  string  		$key           Key of attribute
5264
	 * @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)
5265
	 * @param  string  		$moreparam     To add more parameters on html input tag
5266
	 * @param  string  		$keysuffix     Prefix string to add into name and id of field (can be used to avoid duplicate names)
5267
	 * @param  string  		$keyprefix     Suffix string to add into name and id of field (can be used to avoid duplicate names)
5268
	 * @param  string|int		$morecss       Value for css to define style/length of field. May also be a numeric.
5269
	 * @return string
5270
	 */
5271
	function showInputField($val, $key, $value, $moreparam='', $keysuffix='', $keyprefix='', $morecss=0)
5272
	{
5273
		global $conf,$langs,$form;
5274
5275
		if (! is_object($form))
5276
		{
5277
			require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
5278
			$form=new Form($this->db);
5279
		}
5280
5281
		$val=$this->fields[$key];
5282
5283
		$out='';
5284
        $type='';
5285
        $param = array();
5286
        $param['options']=array();
5287
        $size =$this->fields[$key]['size'];
5288
        // Because we work on extrafields
5289
        if(preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)){
5290
            $param['options']=array($reg[1].':'.$reg[2]=>'N');
5291
            $type ='link';
5292
        } elseif(preg_match('/^link:(.*):(.*)/i', $val['type'], $reg)) {
5293
            $param['options']=array($reg[1].':'.$reg[2]=>'N');
5294
            $type ='link';
5295
        } elseif(preg_match('/^sellist:(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
5296
            $param['options']=array($reg[1].':'.$reg[2].':'.$reg[3].':'.$reg[4]=>'N');
5297
            $type ='sellist';
5298
        } elseif(preg_match('/varchar\((\d+)\)/', $val['type'],$reg)) {
5299
            $param['options']=array();
5300
            $type ='varchar';
5301
            $size=$reg[1];
5302
        } elseif(preg_match('/varchar/', $val['type'])) {
5303
            $param['options']=array();
5304
            $type ='varchar';
5305
        } elseif(is_array($this->fields[$key]['arrayofkeyval'])) {
5306
            $param['options']=$this->fields[$key]['arrayofkeyval'];
5307
            $type ='select';
5308
        } else {
5309
            $param['options']=array();
5310
            $type =$this->fields[$key]['type'];
5311
        }
5312
5313
		$label=$this->fields[$key]['label'];
5314
		//$elementtype=$this->fields[$key]['elementtype'];	// Seems not used
5315
		$default=$this->fields[$key]['default'];
5316
		$computed=$this->fields[$key]['computed'];
5317
		$unique=$this->fields[$key]['unique'];
5318
		$required=$this->fields[$key]['required'];
5319
5320
		$langfile=$this->fields[$key]['langfile'];
5321
		$list=$this->fields[$key]['list'];
5322
		$hidden=abs($this->fields[$key]['visible'])!=1?1:0;
5323
5324
		$objectid = $this->id;
5325
5326
5327
		if ($computed)
5328
		{
5329
			if (! preg_match('/^search_/', $keyprefix)) return '<span class="opacitymedium">'.$langs->trans("AutomaticallyCalculated").'</span>';
5330
			else return '';
5331
		}
5332
5333
5334
		// Use in priority showsize from parameters, then $val['css'] then autodefine
5335
		if (empty($morecss) && ! empty($val['css']))
5336
		{
5337
			$showsize = $val['css'];
5338
		}
5339
		if (empty($morecss))
5340
		{
5341
			if ($type == 'date')
5342
			{
5343
				$morecss = 'minwidth100imp';
5344
			}
5345
			elseif ($type == 'datetime')
5346
			{
5347
				$morecss = 'minwidth200imp';
5348
			}
5349
			elseif (in_array($type,array('int','integer','price')) || preg_match('/^double(\([0-9],[0-9]\)){0,1}/',$type))
5350
			{
5351
				$morecss = 'maxwidth75';
5352
                        }elseif ($type == 'url')
5353
			{
5354
				$morecss='minwidth400';
5355
			}
5356
			elseif ($type == 'boolean')
5357
			{
5358
				$morecss='';
5359
			}
5360
			else
5361
			{
5362
				if (round($size) < 12)
5363
				{
5364
					$morecss = 'minwidth100';
5365
				}
5366
				else if (round($size) <= 48)
5367
				{
5368
					$morecss = 'minwidth200';
5369
				}
5370
				else
5371
				{
5372
					$morecss = 'minwidth400';
5373
				}
5374
			}
5375
		}
5376
5377
		if (in_array($type,array('date','datetime')))
5378
		{
5379
			$tmp=explode(',',$size);
5380
			$newsize=$tmp[0];
5381
5382
			$showtime = in_array($type,array('datetime')) ? 1 : 0;
5383
5384
			// Do not show current date when field not required (see selectDate() method)
5385
			if (!$required && $value == '') $value = '-1';
5386
5387
			// TODO Must also support $moreparam
5388
			$out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1);
5389
		}
5390
		elseif (in_array($type,array('int','integer')))
5391
		{
5392
			$tmp=explode(',',$size);
5393
			$newsize=$tmp[0];
5394
			$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:'').'>';
5395
		}
5396
		elseif (preg_match('/varchar/', $type))
5397
		{
5398
			$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:'').'>';
5399
		}
5400
		elseif (in_array($type, array('mail', 'phone', 'url')))
5401
		{
5402
			$out='<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam?$moreparam:'').'>';
5403
		}
5404
		elseif ($type == 'text')
5405
		{
5406
			if (! preg_match('/search_/', $keyprefix))		// If keyprefix is search_ or search_options_, we must just use a simple text field
5407
			{
5408
				require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
5409
				$doleditor=new DolEditor($keyprefix.$key.$keysuffix,$value,'',200,'dolibarr_notes','In',false,false,false,ROWS_5,'90%');
5410
				$out=$doleditor->Create(1);
5411
			}
5412
			else
5413
			{
5414
				$out='<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam?$moreparam:'').'>';
5415
			}
5416
		}
5417
		elseif ($type == 'html')
5418
		{
5419
			if (! preg_match('/search_/', $keyprefix))		// If keyprefix is search_ or search_options_, we must just use a simple text field
5420
			{
5421
				require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
5422
				$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%');
5423
				$out=$doleditor->Create(1);
5424
			}
5425
			else
5426
			{
5427
				$out='<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam?$moreparam:'').'>';
5428
			}
5429
		}
5430
		elseif ($type == 'boolean')
5431
		{
5432
			$checked='';
5433
			if (!empty($value)) {
5434
				$checked=' checked value="1" ';
5435
			} else {
5436
				$checked=' value="1" ';
5437
			}
5438
			$out='<input type="checkbox" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.$checked.' '.($moreparam?$moreparam:'').'>';
5439
		}
5440
		elseif ($type == 'price')
5441
		{
5442
			if (!empty($value)) {		// $value in memory is a php numeric, we format it into user number format.
5443
				$value=price($value);
5444
			}
5445
			$out='<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam?$moreparam:'').'> '.$langs->getCurrencySymbol($conf->currency);
5446
		}
5447
		elseif (preg_match('/^double(\([0-9],[0-9]\)){0,1}/',$type))
5448
		{
5449
			if (!empty($value)) {		// $value in memory is a php numeric, we format it into user number format.
5450
				$value=price($value);
5451
			}
5452
			$out='<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam?$moreparam:'').'> ';
5453
		}
5454
		elseif ($type == 'select')
5455
		{
5456
			$out = '';
5457
			if (! empty($conf->use_javascript_ajax) && ! empty($conf->global->MAIN_EXTRAFIELDS_USE_SELECT2))
5458
			{
5459
				include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
5460
				$out.= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
5461
			}
5462
5463
			$out.='<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam?$moreparam:'').'>';
5464
                if((! isset($this->fields[$key]['default'])) ||($this->fields[$key]['notnull']!=1))$out.='<option value="0">&nbsp;</option>';
5465
			foreach ($param['options'] as $key => $val)
5466
			{
5467
				if ((string) $key == '') continue;
5468
				list($val, $parent) = explode('|', $val);
5469
				$out.='<option value="'.$key.'"';
5470
				$out.= (((string) $value == (string) $key)?' selected':'');
5471
				$out.= (!empty($parent)?' parent="'.$parent.'"':'');
5472
				$out.='>'.$val.'</option>';
5473
			}
5474
			$out.='</select>';
5475
		}
5476
		elseif ($type == 'sellist')
5477
		{
5478
			$out = '';
5479
			if (! empty($conf->use_javascript_ajax) && ! empty($conf->global->MAIN_EXTRAFIELDS_USE_SELECT2))
5480
			{
5481
				include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
5482
				$out.= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
5483
			}
5484
5485
			$out.='<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam?$moreparam:'').'>';
5486
			if (is_array($param['options']))
5487
			{
5488
				$param_list=array_keys($param['options']);
5489
				$InfoFieldList = explode(":", $param_list[0]);
5490
				$parentName='';
5491
				$parentField='';
5492
				// 0 : tableName
5493
				// 1 : label field name
5494
				// 2 : key fields name (if differ of rowid)
5495
				// 3 : key field parent (for dependent lists)
5496
				// 4 : where clause filter on column or table extrafield, syntax field='value' or extra.field=value
5497
				$keyList=(empty($InfoFieldList[2])?'rowid':$InfoFieldList[2].' as rowid');
5498
5499
5500
				if (count($InfoFieldList) > 4 && ! empty($InfoFieldList[4]))
5501
				{
5502
					if (strpos($InfoFieldList[4], 'extra.') !== false)
5503
					{
5504
						$keyList='main.'.$InfoFieldList[2].' as rowid';
5505
					} else {
5506
						$keyList=$InfoFieldList[2].' as rowid';
5507
					}
5508
				}
5509
				if (count($InfoFieldList) > 3 && ! empty($InfoFieldList[3]))
5510
				{
5511
					list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
5512
					$keyList.= ', '.$parentField;
5513
				}
5514
5515
				$fields_label = explode('|',$InfoFieldList[1]);
5516
				if (is_array($fields_label))
5517
				{
5518
					$keyList .=', ';
5519
					$keyList .= implode(', ', $fields_label);
5520
				}
5521
5522
				$sqlwhere='';
5523
				$sql = 'SELECT '.$keyList;
5524
				$sql.= ' FROM '.MAIN_DB_PREFIX .$InfoFieldList[0];
5525
				if (!empty($InfoFieldList[4]))
5526
				{
5527
					// can use SELECT request
5528
					if (strpos($InfoFieldList[4], '$SEL$')!==false) {
5529
						$InfoFieldList[4]=str_replace('$SEL$','SELECT',$InfoFieldList[4]);
5530
					}
5531
5532
					// current object id can be use into filter
5533
					if (strpos($InfoFieldList[4], '$ID$')!==false && !empty($objectid)) {
5534
						$InfoFieldList[4]=str_replace('$ID$',$objectid,$InfoFieldList[4]);
5535
					} else {
5536
						$InfoFieldList[4]=str_replace('$ID$','0',$InfoFieldList[4]);
5537
					}
5538
					//We have to join on extrafield table
5539
					if (strpos($InfoFieldList[4], 'extra')!==false)
5540
					{
5541
						$sql.= ' as main, '.MAIN_DB_PREFIX .$InfoFieldList[0].'_extrafields as extra';
5542
						$sqlwhere.= ' WHERE extra.fk_object=main.'.$InfoFieldList[2]. ' AND '.$InfoFieldList[4];
5543
					}
5544
					else
5545
					{
5546
						$sqlwhere.= ' WHERE '.$InfoFieldList[4];
5547
					}
5548
				}
5549
				else
5550
				{
5551
					$sqlwhere.= ' WHERE 1=1';
5552
				}
5553
				// Some tables may have field, some other not. For the moment we disable it.
5554
				if (in_array($InfoFieldList[0],array('tablewithentity')))
5555
				{
5556
					$sqlwhere.= ' AND entity = '.$conf->entity;
5557
				}
5558
				$sql.=$sqlwhere;
5559
				//print $sql;
5560
5561
				$sql .= ' ORDER BY ' . implode(', ', $fields_label);
5562
5563
				dol_syslog(get_class($this).'::showInputField type=sellist', LOG_DEBUG);
5564
				$resql = $this->db->query($sql);
5565
				if ($resql)
5566
				{
5567
					$out.='<option value="0">&nbsp;</option>';
5568
					$num = $this->db->num_rows($resql);
5569
					$i = 0;
5570
					while ($i < $num)
5571
					{
5572
						$labeltoshow='';
5573
						$obj = $this->db->fetch_object($resql);
5574
5575
						// Several field into label (eq table:code|libelle:rowid)
5576
						$notrans = false;
5577
						$fields_label = explode('|',$InfoFieldList[1]);
5578
						if (is_array($fields_label))
5579
						{
5580
							$notrans = true;
5581
							foreach ($fields_label as $field_toshow)
5582
							{
5583
								$labeltoshow.= $obj->$field_toshow.' ';
5584
							}
5585
						}
5586
						else
5587
						{
5588
							$labeltoshow=$obj->{$InfoFieldList[1]};
5589
						}
5590
						$labeltoshow=dol_trunc($labeltoshow,45);
5591
5592
						if ($value == $obj->rowid)
5593
						{
5594
							foreach ($fields_label as $field_toshow)
5595
							{
5596
								$translabel=$langs->trans($obj->$field_toshow);
5597
								if ($translabel!=$obj->$field_toshow) {
5598
									$labeltoshow=dol_trunc($translabel,18).' ';
5599
								}else {
5600
									$labeltoshow=dol_trunc($obj->$field_toshow,18).' ';
5601
								}
5602
							}
5603
							$out.='<option value="'.$obj->rowid.'" selected>'.$labeltoshow.'</option>';
5604
						}
5605
						else
5606
						{
5607
							if (! $notrans)
5608
							{
5609
								$translabel=$langs->trans($obj->{$InfoFieldList[1]});
5610
								if ($translabel!=$obj->{$InfoFieldList[1]}) {
5611
									$labeltoshow=dol_trunc($translabel,18);
5612
								}
5613
								else {
5614
									$labeltoshow=dol_trunc($obj->{$InfoFieldList[1]},18);
5615
								}
5616
							}
5617
							if (empty($labeltoshow)) $labeltoshow='(not defined)';
5618
							if ($value==$obj->rowid)
5619
							{
5620
								$out.='<option value="'.$obj->rowid.'" selected>'.$labeltoshow.'</option>';
5621
							}
5622
5623
							if (!empty($InfoFieldList[3]) && $parentField)
5624
							{
5625
								$parent = $parentName.':'.$obj->{$parentField};
5626
							}
5627
5628
							$out.='<option value="'.$obj->rowid.'"';
5629
							$out.= ($value==$obj->rowid?' selected':'');
5630
							$out.= (!empty($parent)?' parent="'.$parent.'"':'');
5631
							$out.='>'.$labeltoshow.'</option>';
5632
						}
5633
5634
						$i++;
5635
					}
5636
					$this->db->free($resql);
5637
				}
5638
				else {
5639
					print 'Error in request '.$sql.' '.$this->db->lasterror().'. Check setup of extra parameters.<br>';
5640
				}
5641
			}
5642
			$out.='</select>';
5643
		}
5644
		elseif ($type == 'checkbox')
5645
		{
5646
			$value_arr=explode(',',$value);
5647
			$out=$form->multiselectarray($keyprefix.$key.$keysuffix, (empty($param['options'])?null:$param['options']), $value_arr, '', 0, '', 0, '100%');
5648
		}
5649
		elseif ($type == 'radio')
5650
		{
5651
			$out='';
5652
			foreach ($param['options'] as $keyopt => $val)
5653
			{
5654
				$out.='<input class="flat '.$morecss.'" type="radio" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam?$moreparam:'');
5655
				$out.=' value="'.$keyopt.'"';
5656
				$out.=' id="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'"';
5657
				$out.= ($value==$keyopt?'checked':'');
5658
				$out.='/><label for="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'">'.$val.'</label><br>';
5659
			}
5660
		}
5661
		elseif ($type == 'chkbxlst')
5662
		{
5663
			if (is_array($value)) {
5664
				$value_arr = $value;
5665
			}
5666
			else {
5667
				$value_arr = explode(',', $value);
5668
			}
5669
5670
			if (is_array($param['options'])) {
5671
				$param_list = array_keys($param['options']);
5672
				$InfoFieldList = explode(":", $param_list[0]);
5673
				$parentName='';
5674
				$parentField='';
5675
				// 0 : tableName
5676
				// 1 : label field name
5677
				// 2 : key fields name (if differ of rowid)
5678
				// 3 : key field parent (for dependent lists)
5679
				// 4 : where clause filter on column or table extrafield, syntax field='value' or extra.field=value
5680
				$keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2] . ' as rowid');
5681
5682
				if (count($InfoFieldList) > 3 && ! empty($InfoFieldList[3])) {
5683
					list ( $parentName, $parentField ) = explode('|', $InfoFieldList[3]);
5684
					$keyList .= ', ' . $parentField;
5685
				}
5686
				if (count($InfoFieldList) > 4 && ! empty($InfoFieldList[4])) {
5687
					if (strpos($InfoFieldList[4], 'extra.') !== false) {
5688
						$keyList = 'main.' . $InfoFieldList[2] . ' as rowid';
5689
					} else {
5690
						$keyList = $InfoFieldList[2] . ' as rowid';
5691
					}
5692
				}
5693
5694
				$fields_label = explode('|', $InfoFieldList[1]);
5695
				if (is_array($fields_label)) {
5696
					$keyList .= ', ';
5697
					$keyList .= implode(', ', $fields_label);
5698
				}
5699
5700
				$sqlwhere = '';
5701
				$sql = 'SELECT ' . $keyList;
5702
				$sql .= ' FROM ' . MAIN_DB_PREFIX . $InfoFieldList[0];
5703
				if (! empty($InfoFieldList[4])) {
5704
5705
					// can use SELECT request
5706
					if (strpos($InfoFieldList[4], '$SEL$')!==false) {
5707
						$InfoFieldList[4]=str_replace('$SEL$','SELECT',$InfoFieldList[4]);
5708
					}
5709
5710
					// current object id can be use into filter
5711
					if (strpos($InfoFieldList[4], '$ID$')!==false && !empty($objectid)) {
5712
						$InfoFieldList[4]=str_replace('$ID$',$objectid,$InfoFieldList[4]);
5713
					} else {
5714
						$InfoFieldList[4]=str_replace('$ID$','0',$InfoFieldList[4]);
5715
					}
5716
5717
					// We have to join on extrafield table
5718
					if (strpos($InfoFieldList[4], 'extra') !== false) {
5719
						$sql .= ' as main, ' . MAIN_DB_PREFIX . $InfoFieldList[0] . '_extrafields as extra';
5720
						$sqlwhere .= ' WHERE extra.fk_object=main.' . $InfoFieldList[2] . ' AND ' . $InfoFieldList[4];
5721
					} else {
5722
						$sqlwhere .= ' WHERE ' . $InfoFieldList[4];
5723
					}
5724
				} else {
5725
					$sqlwhere .= ' WHERE 1=1';
5726
				}
5727
				// Some tables may have field, some other not. For the moment we disable it.
5728
				if (in_array($InfoFieldList[0], array ('tablewithentity')))
5729
				{
5730
					$sqlwhere .= ' AND entity = ' . $conf->entity;
5731
				}
5732
				// $sql.=preg_replace('/^ AND /','',$sqlwhere);
5733
				// print $sql;
5734
5735
				$sql .= $sqlwhere;
5736
				dol_syslog(get_class($this) . '::showInputField type=chkbxlst',LOG_DEBUG);
5737
				$resql = $this->db->query($sql);
5738
				if ($resql) {
5739
					$num = $this->db->num_rows($resql);
5740
					$i = 0;
5741
5742
					$data=array();
5743
5744
					while ( $i < $num ) {
5745
						$labeltoshow = '';
5746
						$obj = $this->db->fetch_object($resql);
5747
5748
						$notrans = false;
5749
						// Several field into label (eq table:code|libelle:rowid)
5750
						$fields_label = explode('|', $InfoFieldList[1]);
5751
						if (is_array($fields_label)) {
5752
							$notrans = true;
5753
							foreach ( $fields_label as $field_toshow ) {
5754
								$labeltoshow .= $obj->$field_toshow . ' ';
5755
							}
5756
						} else {
5757
							$labeltoshow = $obj->{$InfoFieldList[1]};
5758
						}
5759
						$labeltoshow = dol_trunc($labeltoshow, 45);
5760
5761
						if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
5762
							foreach ( $fields_label as $field_toshow ) {
5763
								$translabel = $langs->trans($obj->$field_toshow);
5764
								if ($translabel != $obj->$field_toshow) {
5765
									$labeltoshow = dol_trunc($translabel, 18) . ' ';
5766
								} else {
5767
									$labeltoshow = dol_trunc($obj->$field_toshow, 18) . ' ';
5768
								}
5769
							}
5770
5771
							$data[$obj->rowid]=$labeltoshow;
5772
						} else {
5773
							if (! $notrans) {
5774
								$translabel = $langs->trans($obj->{$InfoFieldList[1]});
5775
								if ($translabel != $obj->{$InfoFieldList[1]}) {
5776
									$labeltoshow = dol_trunc($translabel, 18);
5777
								} else {
5778
									$labeltoshow = dol_trunc($obj->{$InfoFieldList[1]}, 18);
5779
								}
5780
							}
5781
							if (empty($labeltoshow))
5782
								$labeltoshow = '(not defined)';
5783
5784
								if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
5785
									$data[$obj->rowid]=$labeltoshow;
5786
								}
5787
5788
								if (! empty($InfoFieldList[3]) && $parentField) {
5789
									$parent = $parentName . ':' . $obj->{$parentField};
5790
								}
5791
5792
								$data[$obj->rowid]=$labeltoshow;
5793
						}
5794
5795
						$i ++;
5796
					}
5797
					$this->db->free($resql);
5798
5799
					$out=$form->multiselectarray($keyprefix.$key.$keysuffix, $data, $value_arr, '', 0, '', 0, '100%');
5800
				} else {
5801
					print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
5802
				}
5803
			}
5804
		}
5805
		elseif ($type == 'link')
5806
		{
5807
			$param_list=array_keys($param['options']);				// $param_list='ObjectName:classPath'
5808
			$showempty=(($required && $default != '')?0:1);
5809
			$out=$form->selectForForms($param_list[0], $keyprefix.$key.$keysuffix, $value, $showempty);
5810
			if ($conf->global->MAIN_FEATURES_LEVEL >= 2)
5811
			{
5812
            			list($class,$classfile)=explode(':',$param_list[0]);
5813
            			if (file_exists(dol_buildpath(dirname(dirname($classfile)).'/card.php'))) $url_path=dol_buildpath(dirname(dirname($classfile)).'/card.php',1);
5814
            			else $url_path=dol_buildpath(dirname(dirname($classfile)).'/'.$class.'_card.php',1);
5815
            			$out.='<a class="butActionNew" href="'.$url_path.'?action=create&backtopage='.$_SERVER['PHP_SELF'].'"><span class="fa fa-plus-circle valignmiddle"></span></a>';
5816
            			// TODO Add Javascript code to add input fields contents to new elements urls
5817
			}
5818
		}
5819
		elseif ($type == 'password')
5820
		{
5821
			// If prefix is 'search_', field is used as a filter, we use a common text field.
5822
			$out='<input type="'.($keyprefix=='search_'?'text':'password').'" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam?$moreparam:'').'>';
5823
		}
5824
		elseif ($type == 'array')
5825
		{
5826
			$newval = $val;
5827
			$newval['type'] = 'varchar(256)';
5828
5829
			$out='';
5830
5831
			$inputs = array();
5832
			if(! empty($value)) {
5833
				foreach($value as $option) {
5834
					$out.= '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
5835
					$out.= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', $option, $moreparam, '', '', $showsize).'<br></span>';
5836
				}
5837
			}
5838
5839
			$out.= '<a id="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_add" href="javascript:;"><span class="fa fa-plus-circle valignmiddle"></span></a>';
5840
5841
			$newInput = '<span><a class="'.dol_escape_htmltag($keyprefix.$key.$keysuffix).'_del" href="javascript:;"><span class="fa fa-minus-circle valignmiddle"></span></a> ';
5842
			$newInput.= $this->showInputField($newval, $keyprefix.$key.$keysuffix.'[]', '', $moreparam, '', '', $showsize).'<br></span>';
5843
5844
			if(! empty($conf->use_javascript_ajax)) {
5845
				$out.= '
5846
					<script type="text/javascript">
5847
					$(document).ready(function() {
5848
						$("a#'.dol_escape_js($keyprefix.$key.$keysuffix).'_add").click(function() {
5849
							$("'.dol_escape_js($newInput).'").insertBefore(this);
5850
						});
5851
5852
						$(document).on("click", "a.'.dol_escape_js($keyprefix.$key.$keysuffix).'_del", function() {
5853
							$(this).parent().remove();
5854
						});
5855
					});
5856
					</script>';
5857
			}
5858
		}
5859
		if (!empty($hidden)) {
5860
			$out='<input type="hidden" value="'.$value.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'"/>';
5861
		}
5862
		/* Add comments
5863
		 if ($type == 'date') $out.=' (YYYY-MM-DD)';
5864
		 elseif ($type == 'datetime') $out.=' (YYYY-MM-DD HH:MM:SS)';
5865
		 */
5866
		return $out;
5867
	}
5868
5869
	/**
5870
	 * Return HTML string to show a field into a page
5871
	 * Code very similar with showOutputField of extra fields
5872
	 *
5873
	 * @param  array   $val		       Array of properties of field to show
5874
	 * @param  string  $key            Key of attribute
5875
	 * @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)
5876
	 * @param  string  $moreparam      To add more parametes on html input tag
5877
	 * @param  string  $keysuffix      Prefix string to add into name and id of field (can be used to avoid duplicate names)
5878
	 * @param  string  $keyprefix      Suffix string to add into name and id of field (can be used to avoid duplicate names)
5879
	 * @param  mixed   $showsize       Value for css to define size. May also be a numeric.
5880
	 * @return string
5881
	 */
5882
	function showOutputField($val, $key, $value, $moreparam='', $keysuffix='', $keyprefix='', $showsize=0)
5883
	{
5884
		global $conf,$langs,$form;
5885
5886
		if (! is_object($form))
5887
		{
5888
			require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
5889
			$form=new Form($this->db);
5890
		}
5891
5892
		$objectid = $this->id;
5893
		$label = $val['label'];
5894
		$type  = $val['type'];
5895
		$size  = $val['css'];
5896
5897
		// Convert var to be able to share same code than showOutputField of extrafields
5898
		if (preg_match('/varchar\((\d+)\)/', $type, $reg))
5899
		{
5900
			$type = 'varchar';		// convert varchar(xx) int varchar
5901
			$size = $reg[1];
5902
		}
5903
		elseif (preg_match('/varchar/', $type)) $type = 'varchar';		// convert varchar(xx) int varchar
5904
		if (is_array($val['arrayofkeyval'])) $type='select';
5905
		if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg)) $type='link';
5906
5907
		$default=$val['default'];
5908
		$computed=$val['computed'];
5909
		$unique=$val['unique'];
5910
		$required=$val['required'];
5911
		$param=$val['param'];
5912
		if (is_array($val['arrayofkeyval'])) $param['options'] = $val['arrayofkeyval'];
5913
		if (preg_match('/^integer:(.*):(.*)/i', $val['type'], $reg))
5914
		{
5915
			$type='link';
5916
			$param['options']=array($reg[1].':'.$reg[2]=>$reg[1].':'.$reg[2]);
5917
		}
5918
		$langfile=$val['langfile'];
5919
		$list=$val['list'];
5920
		$help=$val['help'];
5921
		$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)
5922
5923
		if ($hidden) return '';
5924
5925
		// If field is a computed field, value must become result of compute
5926
		if ($computed)
5927
		{
5928
			// Make the eval of compute string
5929
			//var_dump($computed);
5930
			$value = dol_eval($computed, 1, 0);
5931
		}
5932
5933
		if (empty($showsize))
5934
		{
5935
			if ($type == 'date')
5936
			{
5937
				//$showsize=10;
5938
				$showsize = 'minwidth100imp';
5939
			}
5940
			elseif ($type == 'datetime')
5941
			{
5942
				//$showsize=19;
5943
				$showsize = 'minwidth200imp';
5944
			}
5945
			elseif (in_array($type,array('int','double','price')))
5946
			{
5947
				//$showsize=10;
5948
				$showsize = 'maxwidth75';
5949
			}
5950
			elseif ($type == 'url')
5951
			{
5952
				$showsize='minwidth400';
5953
			}
5954
			elseif ($type == 'boolean')
5955
			{
5956
				$showsize='';
5957
			}
5958
			else
5959
			{
5960
				if (round($size) < 12)
5961
				{
5962
					$showsize = 'minwidth100';
5963
				}
5964
				else if (round($size) <= 48)
5965
				{
5966
					$showsize = 'minwidth200';
5967
				}
5968
				else
5969
				{
5970
					//$showsize=48;
5971
					$showsize = 'minwidth400';
5972
				}
5973
			}
5974
		}
5975
5976
		// Format output value differently according to properties of field
5977
		if ($key == 'ref' && method_exists($this, 'getNomUrl')) $value=$this->getNomUrl(1, '', 0, '', 1);
5978
		elseif ($key == 'status' && method_exists($this, 'getLibStatut')) $value=$this->getLibStatut(3);
5979
		elseif ($type == 'date')
5980
		{
5981
			if(! empty($value)) {
5982
				$value=dol_print_date($value,'day');
5983
			} else {
5984
				$value='';
5985
			}
5986
		}
5987
		elseif ($type == 'datetime')
5988
		{
5989
			if(! empty($value)) {
5990
				$value=dol_print_date($value,'dayhour');
5991
			} else {
5992
				$value='';
5993
			}
5994
		}
5995
		elseif ($type == 'double')
5996
		{
5997
			if (!empty($value)) {
5998
				$value=price($value);
5999
			}
6000
		}
6001
		elseif ($type == 'boolean')
6002
		{
6003
			$checked='';
6004
			if (!empty($value)) {
6005
				$checked=' checked ';
6006
			}
6007
			$value='<input type="checkbox" '.$checked.' '.($moreparam?$moreparam:'').' readonly disabled>';
6008
		}
6009
		elseif ($type == 'mail')
6010
		{
6011
			$value=dol_print_email($value,0,0,0,64,1,1);
6012
		}
6013
		elseif ($type == 'url')
6014
		{
6015
			$value=dol_print_url($value,'_blank',32,1);
6016
		}
6017
		elseif ($type == 'phone')
6018
		{
6019
			$value=dol_print_phone($value, '', 0, 0, '', '&nbsp;', 1);
6020
		}
6021
		elseif ($type == 'price')
6022
		{
6023
			$value=price($value,0,$langs,0,0,-1,$conf->currency);
6024
		}
6025
		elseif ($type == 'select')
6026
		{
6027
			$value=$param['options'][$value];
6028
		}
6029
		elseif ($type == 'sellist')
6030
		{
6031
			$param_list=array_keys($param['options']);
6032
			$InfoFieldList = explode(":", $param_list[0]);
6033
6034
			$selectkey="rowid";
6035
			$keyList='rowid';
6036
6037
			if (count($InfoFieldList)>=3)
6038
			{
6039
				$selectkey = $InfoFieldList[2];
6040
				$keyList=$InfoFieldList[2].' as rowid';
6041
			}
6042
6043
			$fields_label = explode('|',$InfoFieldList[1]);
6044
			if(is_array($fields_label)) {
6045
				$keyList .=', ';
6046
				$keyList .= implode(', ', $fields_label);
6047
			}
6048
6049
			$sql = 'SELECT '.$keyList;
6050
			$sql.= ' FROM '.MAIN_DB_PREFIX .$InfoFieldList[0];
6051
			if (strpos($InfoFieldList[4], 'extra')!==false)
6052
			{
6053
				$sql.= ' as main';
6054
			}
6055
			if ($selectkey=='rowid' && empty($value)) {
6056
				$sql.= " WHERE ".$selectkey."=0";
6057
			} elseif ($selectkey=='rowid') {
6058
				$sql.= " WHERE ".$selectkey."=".$this->db->escape($value);
6059
			}else {
6060
				$sql.= " WHERE ".$selectkey."='".$this->db->escape($value)."'";
6061
			}
6062
6063
			//$sql.= ' AND entity = '.$conf->entity;
6064
6065
			dol_syslog(get_class($this).':showOutputField:$type=sellist', LOG_DEBUG);
6066
			$resql = $this->db->query($sql);
6067
			if ($resql)
6068
			{
6069
				$value='';	// value was used, so now we reste it to use it to build final output
6070
6071
				$obj = $this->db->fetch_object($resql);
6072
6073
				// Several field into label (eq table:code|libelle:rowid)
6074
				$fields_label = explode('|',$InfoFieldList[1]);
6075
6076
				if(is_array($fields_label) && count($fields_label)>1)
6077
				{
6078
					foreach ($fields_label as $field_toshow)
6079
					{
6080
						$translabel='';
6081
						if (!empty($obj->$field_toshow)) {
6082
							$translabel=$langs->trans($obj->$field_toshow);
6083
						}
6084
						if ($translabel!=$field_toshow) {
6085
							$value.=dol_trunc($translabel,18).' ';
6086
						}else {
6087
							$value.=$obj->$field_toshow.' ';
6088
						}
6089
					}
6090
				}
6091
				else
6092
				{
6093
					$translabel='';
6094
					if (!empty($obj->{$InfoFieldList[1]})) {
6095
						$translabel=$langs->trans($obj->{$InfoFieldList[1]});
6096
					}
6097
					if ($translabel!=$obj->{$InfoFieldList[1]}) {
6098
						$value=dol_trunc($translabel,18);
6099
					}else {
6100
						$value=$obj->{$InfoFieldList[1]};
6101
					}
6102
				}
6103
			}
6104
			else dol_syslog(get_class($this).'::showOutputField error '.$this->db->lasterror(), LOG_WARNING);
6105
		}
6106
		elseif ($type == 'radio')
6107
		{
6108
			$value=$param['options'][$value];
6109
		}
6110
		elseif ($type == 'checkbox')
6111
		{
6112
			$value_arr=explode(',',$value);
6113
			$value='';
6114
			if (is_array($value_arr) && count($value_arr)>0)
6115
			{
6116
				foreach ($value_arr as $keyval=>$valueval) {
6117
					$toprint[]='<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #aaa">'.$param['options'][$valueval].'</li>';
6118
				}
6119
				$value='<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
6120
			}
6121
		}
6122
		elseif ($type == 'chkbxlst')
6123
		{
6124
			$value_arr = explode(',', $value);
6125
6126
			$param_list = array_keys($param['options']);
6127
			$InfoFieldList = explode(":", $param_list[0]);
6128
6129
			$selectkey = "rowid";
6130
			$keyList = 'rowid';
6131
6132
			if (count($InfoFieldList) >= 3) {
6133
				$selectkey = $InfoFieldList[2];
6134
				$keyList = $InfoFieldList[2] . ' as rowid';
6135
			}
6136
6137
			$fields_label = explode('|', $InfoFieldList[1]);
6138
			if (is_array($fields_label)) {
6139
				$keyList .= ', ';
6140
				$keyList .= implode(', ', $fields_label);
6141
			}
6142
6143
			$sql = 'SELECT ' . $keyList;
6144
			$sql .= ' FROM ' . MAIN_DB_PREFIX . $InfoFieldList[0];
6145
			if (strpos($InfoFieldList[4], 'extra') !== false) {
6146
				$sql .= ' as main';
6147
			}
6148
			// $sql.= " WHERE ".$selectkey."='".$this->db->escape($value)."'";
6149
			// $sql.= ' AND entity = '.$conf->entity;
6150
6151
			dol_syslog(get_class($this) . ':showOutputField:$type=chkbxlst',LOG_DEBUG);
6152
			$resql = $this->db->query($sql);
6153
			if ($resql) {
6154
				$value = ''; // value was used, so now we reste it to use it to build final output
6155
				$toprint=array();
6156
				while ( $obj = $this->db->fetch_object($resql) ) {
6157
6158
					// Several field into label (eq table:code|libelle:rowid)
6159
					$fields_label = explode('|', $InfoFieldList[1]);
6160
					if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
6161
						if (is_array($fields_label) && count($fields_label) > 1) {
6162
							foreach ( $fields_label as $field_toshow ) {
6163
								$translabel = '';
6164
								if (! empty($obj->$field_toshow)) {
6165
									$translabel = $langs->trans($obj->$field_toshow);
6166
								}
6167
								if ($translabel != $field_toshow) {
6168
									$toprint[]='<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #aaa">'.dol_trunc($translabel, 18).'</li>';
6169
								} else {
6170
									$toprint[]='<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #aaa">'.$obj->$field_toshow.'</li>';
6171
								}
6172
							}
6173
						} else {
6174
							$translabel = '';
6175
							if (! empty($obj->{$InfoFieldList[1]})) {
6176
								$translabel = $langs->trans($obj->{$InfoFieldList[1]});
6177
							}
6178
							if ($translabel != $obj->{$InfoFieldList[1]}) {
6179
								$toprint[]='<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #aaa">'.dol_trunc($translabel, 18).'</li>';
6180
							} else {
6181
								$toprint[]='<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #aaa">'.$obj->{$InfoFieldList[1]}.'</li>';
6182
							}
6183
						}
6184
					}
6185
				}
6186
				$value='<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">'.implode(' ', $toprint).'</ul></div>';
6187
			} else {
6188
				dol_syslog(get_class($this) . '::showOutputField error ' . $this->db->lasterror(), LOG_WARNING);
6189
			}
6190
		}
6191
		elseif ($type == 'link')
6192
		{
6193
			$out='';
6194
6195
			// only if something to display (perf)
6196
			if ($value)
6197
			{
6198
				$param_list=array_keys($param['options']);				// $param_list='ObjectName:classPath'
6199
6200
				$InfoFieldList = explode(":", $param_list[0]);
6201
				$classname=$InfoFieldList[0];
6202
				$classpath=$InfoFieldList[1];
6203
				$getnomurlparam=(empty($InfoFieldList[2]) ? 3 : $InfoFieldList[2]);
6204
				if (! empty($classpath))
6205
				{
6206
					dol_include_once($InfoFieldList[1]);
6207
					if ($classname && class_exists($classname))
6208
					{
6209
						$object = new $classname($this->db);
6210
						$object->fetch($value);
6211
						$value=$object->getNomUrl($getnomurlparam);
6212
					}
6213
				}
6214
				else
6215
				{
6216
					dol_syslog('Error bad setup of extrafield', LOG_WARNING);
6217
					return 'Error bad setup of extrafield';
6218
				}
6219
			}
6220
			else $value='';
6221
		}
6222
		elseif ($type == 'text' || $type == 'html')
6223
		{
6224
			$value=dol_htmlentitiesbr($value);
6225
		}
6226
		elseif ($type == 'password')
6227
		{
6228
			$value=preg_replace('/./i','*',$value);
6229
		}
6230
		elseif ($type == 'array')
6231
		{
6232
			$value = implode('<br>', $value);
6233
		}
6234
6235
		//print $type.'-'.$size;
6236
		$out=$value;
6237
6238
		return $out;
6239
	}
6240
6241
6242
	/**
6243
	 * Function to show lines of extrafields with output datas
6244
	 *
6245
	 * @param 	Extrafields $extrafields    Extrafield Object
6246
	 * @param 	string      $mode           Show output (view) or input (edit) for extrafield
6247
	 * @param 	array       $params         Optional parameters. Example: array('style'=>'class="oddeven"', 'colspan'=>$colspan)
6248
	 * @param 	string      $keysuffix      Suffix string to add after name and id of field (can be used to avoid duplicate names)
6249
	 * @param 	string      $keyprefix      Prefix string to add before name and id of field (can be used to avoid duplicate names)
6250
	 * @param	string		$onetrtd		All fields in same tr td
6251
	 * @return 	string
6252
	 */
6253
	function showOptionals($extrafields, $mode='view', $params=null, $keysuffix='', $keyprefix='', $onetrtd=0)
6254
	{
6255
		global $db, $conf, $langs, $action, $form;
6256
6257
		if (! is_object($form)) $form=new Form($db);
6258
6259
		$out = '';
6260
6261
		if (is_array($extrafields->attributes[$this->table_element]['label']) && count($extrafields->attributes[$this->table_element]['label']) > 0)
6262
		{
6263
			$out .= "\n";
6264
			$out .= '<!-- showOptionalsInput --> ';
6265
			$out .= "\n";
6266
6267
			$e = 0;
6268
			foreach($extrafields->attributes[$this->table_element]['label'] as $key=>$label)
6269
			{
6270
				// Show only the key field in params
6271
				if (is_array($params) && array_key_exists('onlykey',$params) && $key != $params['onlykey']) continue;
6272
6273
				$enabled = 1;
6274
				if ($enabled && isset($extrafields->attributes[$this->table_element]['list'][$key]))
6275
				{
6276
					$enabled = dol_eval($extrafields->attributes[$this->table_element]['list'][$key], 1);
6277
				}
6278
6279
				$perms = 1;
6280
				if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key]))
6281
				{
6282
					$perms = dol_eval($extrafields->attributes[$this->table_element]['perms'][$key], 1);
6283
				}
6284
6285
				if (($mode == 'create' || $mode == 'edit') && abs($enabled) != 1 && abs($enabled) != 3) continue;	// <> -1 and <> 1 and <> 3 = not visible on forms, only on list
6286
				if (empty($perms)) continue;
6287
6288
				// Load language if required
6289
				if (! empty($extrafields->attributes[$this->table_element]['langfile'][$key])) $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
6290
6291
				$colspan='3';
6292
				if (is_array($params) && count($params)>0) {
6293
					if (array_key_exists('colspan',$params)) {
6294
						$colspan=$params['colspan'];
6295
					}
6296
				}
6297
6298
				switch($mode) {
6299
					case "view":
6300
						$value=$this->array_options["options_".$key.$keysuffix];
6301
						break;
6302
					case "edit":
6303
						$getposttemp = GETPOST($keyprefix.'options_'.$key.$keysuffix, 'none');				// GETPOST can get value from GET, POST or setup of default values.
6304
						// GETPOST("options_" . $key) can be 'abc' or array(0=>'abc')
6305
						if (is_array($getposttemp) || $getposttemp != '' || GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix))
6306
						{
6307
							if (is_array($getposttemp)) {
6308
								// $getposttemp is an array but following code expects a comma separated string
6309
								$value = implode(",", $getposttemp);
6310
							} else {
6311
								$value = $getposttemp;
6312
							}
6313
						} else {
6314
							$value = $this->array_options["options_" . $key];			// No GET, no POST, no default value, so we take value of object.
6315
						}
6316
						//var_dump($keyprefix.' - '.$key.' - '.$keysuffix.' - '.$keyprefix.'options_'.$key.$keysuffix.' - '.$this->array_options["options_".$key.$keysuffix].' - '.$getposttemp.' - '.$value);
6317
						break;
6318
				}
6319
6320
				if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate')
6321
				{
6322
					$out .= $extrafields->showSeparator($key, $this);
6323
				}
6324
				else
6325
				{
6326
					$csstyle='';
6327
					$class=(!empty($extrafields->attributes[$this->table_element]['hidden'][$key]) ? 'hideobject ' : '');
6328
					if (is_array($params) && count($params)>0) {
6329
						if (array_key_exists('style',$params)) {
6330
							$csstyle=$params['style'];
6331
						}
6332
					}
6333
6334
					// add html5 elements
6335
					$domData  = ' data-element="extrafield"';
6336
					$domData .= ' data-targetelement="'.$this->element.'"';
6337
					$domData .= ' data-targetid="'.$this->id.'"';
6338
6339
					$html_id = !empty($this->id) ? 'extrarow-'.$this->element.'_'.$key.'_'.$this->id : '';
6340
6341
					$out .= '<tr id="'.$html_id.'" '.$csstyle.' class="'.$class.$this->element.'_extras_'.$key.'" '.$domData.' >';
6342
6343
					if (! empty($conf->global->MAIN_EXTRAFIELDS_USE_TWO_COLUMS) && ($e % 2) == 0)
6344
					{
6345
						if (! empty($conf->global->MAIN_EXTRAFIELDS_USE_TWO_COLUMS) && ($e % 2) == 0) { $colspan='0'; }
6346
					}
6347
6348
					if ($action == 'selectlines') { $colspan++; }
6349
6350
					// Convert date into timestamp format (value in memory must be a timestamp)
6351
					if (in_array($extrafields->attributes[$this->table_element]['type'][$key],array('date','datetime')))
6352
					{
6353
						$datenotinstring = $this->array_options['options_' . $key];
6354
						if (! is_numeric($this->array_options['options_' . $key]))	// For backward compatibility
6355
						{
6356
							$datenotinstring = $this->db->jdate($datenotinstring);
6357
						}
6358
						$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;
6359
					}
6360
					// Convert float submited string into real php numeric (value in memory must be a php numeric)
6361
					if (in_array($extrafields->attributes[$this->table_element]['type'][$key],array('price','double')))
6362
					{
6363
						$value = GETPOSTISSET($keyprefix.'options_'.$key.$keysuffix)?price2num(GETPOST($keyprefix.'options_'.$key.$keysuffix, 'alpha', 3)):$this->array_options['options_'.$key];
6364
					}
6365
6366
					$labeltoshow = $langs->trans($label);
6367
6368
					$out .= '<td class="titlefield';
6369
					if (GETPOST('action','none') == 'create') $out.='create';
6370
					if ($mode != 'view' && ! empty($extrafields->attributes[$this->table_element]['required'][$key])) $out .= ' fieldrequired';
6371
					$out .= '">';
6372
					if (! empty($extrafields->attributes[$object->table_element]['help'][$key])) $out .= $form->textwithpicto($labeltoshow, $extrafields->attributes[$object->table_element]['help'][$key]);
6373
					else $out .= $labeltoshow;
6374
					$out .= '</td>';
6375
6376
					$html_id = !empty($this->id) ? $this->element.'_extras_'.$key.'_'.$this->id : '';
6377
					$out .='<td id="'.$html_id.'" class="'.$this->element.'_extras_'.$key.'" '.($colspan?' colspan="'.$colspan.'"':'').'>';
6378
6379
					switch($mode) {
6380
						case "view":
6381
							$out .= $extrafields->showOutputField($key, $value);
6382
							break;
6383
						case "edit":
6384
							$out .= $extrafields->showInputField($key, $value, '', $keysuffix, '', 0, $this->id);
6385
							break;
6386
					}
6387
6388
					$out .= '</td>';
6389
6390
					if (! empty($conf->global->MAIN_EXTRAFIELDS_USE_TWO_COLUMS) && (($e % 2) == 1)) $out .= '</tr>';
6391
					else $out .= '</tr>';
6392
					$e++;
6393
				}
6394
			}
6395
			$out .= "\n";
6396
			// Add code to manage list depending on others
6397
			if (! empty($conf->use_javascript_ajax)) {
6398
				$out .= '
6399
				<script type="text/javascript">
6400
				    jQuery(document).ready(function() {
6401
				    	function showOptions(child_list, parent_list)
6402
				    	{
6403
				    		var val = $("select[name=\"options_"+parent_list+"\"]").val();
6404
				    		var parentVal = parent_list + ":" + val;
6405
							if(val > 0) {
6406
					    		$("select[name=\""+child_list+"\"] option[parent]").hide();
6407
					    		$("select[name=\""+child_list+"\"] option[parent=\""+parentVal+"\"]").show();
6408
							} else {
6409
								$("select[name=\""+child_list+"\"] option").show();
6410
							}
6411
				    	}
6412
						function setListDependencies() {
6413
					    	jQuery("select option[parent]").parent().each(function() {
6414
					    		var child_list = $(this).attr("name");
6415
								var parent = $(this).find("option[parent]:first").attr("parent");
6416
								var infos = parent.split(":");
6417
								var parent_list = infos[0];
6418
								$("select[name=\""+parent_list+"\"]").change(function() {
6419
									showOptions(child_list, parent_list);
6420
								});
6421
					    	});
6422
						}
6423
6424
						setListDependencies();
6425
				    });
6426
				</script>'."\n";
6427
				$out .= '<!-- /showOptionalsInput --> '."\n";
6428
			}
6429
		}
6430
		return $out;
6431
	}
6432
6433
6434
	/**
6435
	 * Returns the rights used for this class
6436
	 * @return stdClass
6437
	 */
6438
	public function getRights()
6439
	{
6440
		global $user;
6441
6442
		$element = $this->element;
6443
		if ($element == 'facturerec') $element='facture';
6444
6445
		return $user->rights->{$element};
6446
	}
6447
6448
	/**
6449
	 * Function used to replace a thirdparty id with another one.
6450
	 * This function is meant to be called from replaceThirdparty with the appropiate tables
6451
	 * Column name fk_soc MUST be used to identify thirdparties
6452
	 *
6453
	 * @param  DoliDB 	   $db 			  Database handler
6454
	 * @param  int 		   $origin_id     Old thirdparty id (the thirdparty to delete)
6455
	 * @param  int 		   $dest_id       New thirdparty id (the thirdparty that will received element of the other)
6456
	 * @param  string[]    $tables        Tables that need to be changed
6457
	 * @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)
6458
	 * @return bool						  True if success, False if error
6459
	 */
6460
	public static function commonReplaceThirdparty(DoliDB $db, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
6461
	{
6462
		foreach ($tables as $table)
6463
		{
6464
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$table.' SET fk_soc = '.$dest_id.' WHERE fk_soc = '.$origin_id;
6465
6466
			if (! $db->query($sql))
6467
			{
6468
				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.
6469
				//$this->errors = $db->lasterror();
6470
				return false;
6471
			}
6472
		}
6473
6474
		return true;
6475
	}
6476
6477
	/**
6478
	 * Get buy price to use for margin calculation. This function is called when buy price is unknown.
6479
	 *	 Set buy price = sell price if ForceBuyingPriceIfNull configured,
6480
	 *   else if calculation MARGIN_TYPE = 'costprice' and costprice is defined, use costprice as buyprice
6481
	 *	 else if calculation MARGIN_TYPE = 'pmp' and pmp is calculated, use pmp as buyprice
6482
	 *	 else set min buy price as buy price
6483
	 *
6484
	 * @param float		$unitPrice		 Product unit price
6485
	 * @param float		$discountPercent Line discount percent
6486
	 * @param int		$fk_product		 Product id
6487
	 * @return	float                    <0 if KO, buyprice if OK
6488
	 */
6489
	public function defineBuyPrice($unitPrice = 0.0, $discountPercent = 0.0, $fk_product = 0)
6490
	{
6491
		global $conf;
6492
6493
		$buyPrice = 0;
6494
6495
		if (($unitPrice > 0) && (isset($conf->global->ForceBuyingPriceIfNull) && $conf->global->ForceBuyingPriceIfNull == 1)) // In most cases, test here is false
6496
		{
6497
			$buyPrice = $unitPrice * (1 - $discountPercent / 100);
6498
		}
6499
		else
6500
		{
6501
			// Get cost price for margin calculation
6502
			if (! empty($fk_product))
6503
			{
6504
				if (isset($conf->global->MARGIN_TYPE) && $conf->global->MARGIN_TYPE == 'costprice')
6505
				{
6506
					require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
6507
					$product = new Product($this->db);
6508
					$result = $product->fetch($fk_product);
6509
					if ($result <= 0)
6510
					{
6511
						$this->errors[] = 'ErrorProductIdDoesNotExists';
6512
						return -1;
6513
					}
6514
					if ($product->cost_price > 0)
6515
					{
6516
						$buyPrice = $product->cost_price;
6517
					}
6518
					else if ($product->pmp > 0)
6519
					{
6520
						$buyPrice = $product->pmp;
6521
					}
6522
				}
6523
				else if (isset($conf->global->MARGIN_TYPE) && $conf->global->MARGIN_TYPE == 'pmp')
6524
				{
6525
					require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
6526
					$product = new Product($this->db);
6527
					$result = $product->fetch($fk_product);
6528
					if ($result <= 0)
6529
					{
6530
						$this->errors[] = 'ErrorProductIdDoesNotExists';
6531
						return -1;
6532
					}
6533
					if ($product->pmp > 0)
6534
					{
6535
						$buyPrice = $product->pmp;
6536
					}
6537
				}
6538
6539
				if (empty($buyPrice) && isset($conf->global->MARGIN_TYPE) && in_array($conf->global->MARGIN_TYPE, array('1','pmp','costprice')))
6540
				{
6541
					require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
6542
					$productFournisseur = new ProductFournisseur($this->db);
6543
					if (($result = $productFournisseur->find_min_price_product_fournisseur($fk_product)) > 0)
6544
					{
6545
						$buyPrice = $productFournisseur->fourn_unitprice;
6546
					}
6547
					else if ($result < 0)
6548
					{
6549
						$this->errors[] = $productFournisseur->error;
6550
						return -2;
6551
					}
6552
				}
6553
			}
6554
		}
6555
		return $buyPrice;
6556
	}
6557
6558
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
6559
	/**
6560
	 *  Show photos of an object (nbmax maximum), into several columns
6561
	 *
6562
	 *  @param		string	$modulepart		'product', 'ticket', ...
6563
	 *  @param      string	$sdir        	Directory to scan (full absolute path)
6564
	 *  @param      int		$size        	0=original size, 1='small' use thumbnail if possible
6565
	 *  @param      int		$nbmax       	Nombre maximum de photos (0=pas de max)
6566
	 *  @param      int		$nbbyrow     	Number of image per line or -1 to use div. Used only if size=1.
6567
	 * 	@param		int		$showfilename	1=Show filename
6568
	 * 	@param		int		$showaction		1=Show icon with action links (resize, delete)
6569
	 * 	@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.
6570
	 * 	@param		int		$maxWidth		Max width of original image when size='small'
6571
	 *  @param      int     $nolink         Do not add a href link to view enlarged imaged into a new tab
6572
	 *  @param      int     $notitle        Do not add title tag on image
6573
	 *  @param		int		$usesharelink	Use the public shared link of image (if not available, the 'nophoto' image will be shown instead)
6574
	 *  @return     string					Html code to show photo. Number of photos shown is saved in this->nbphoto
6575
	 */
6576
	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)
6577
	{
6578
        // phpcs:enable
6579
		global $conf,$user,$langs;
6580
6581
		include_once DOL_DOCUMENT_ROOT .'/core/lib/files.lib.php';
6582
		include_once DOL_DOCUMENT_ROOT .'/core/lib/images.lib.php';
6583
6584
		$sortfield='position_name';
6585
		$sortorder='asc';
6586
6587
		$dir = $sdir . '/';
6588
		$pdir = '/';
6589
		if ($modulepart == 'ticket')
6590
		{
6591
			$dir .= get_exdir(0, 0, 0, 0, $this, $modulepart).$this->track_id.'/';
6592
			$pdir .= get_exdir(0, 0, 0, 0, $this, $modulepart).$this->track_id.'/';
6593
		}
6594
		else
6595
		{
6596
			$dir .= get_exdir(0, 0, 0, 0, $this, $modulepart).$this->ref.'/';
6597
			$pdir .= get_exdir(0, 0, 0, 0, $this, $modulepart).$this->ref.'/';
6598
		}
6599
6600
		// For backward compatibility
6601
		if ($modulepart == 'product' && ! empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO))
6602
		{
6603
			$dir = $sdir . '/'. get_exdir($this->id,2,0,0,$this,$modulepart) . $this->id ."/photos/";
6604
			$pdir = '/' . get_exdir($this->id,2,0,0,$this,$modulepart) . $this->id ."/photos/";
6605
		}
6606
6607
		// Defined relative dir to DOL_DATA_ROOT
6608
		$relativedir = '';
6609
		if ($dir)
6610
		{
6611
			$relativedir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT,'/').'/', '', $dir);
6612
			$relativedir = preg_replace('/^[\\/]/','',$relativedir);
6613
			$relativedir = preg_replace('/[\\/]$/','',$relativedir);
6614
		}
6615
6616
		$dirthumb = $dir.'thumbs/';
6617
		$pdirthumb = $pdir.'thumbs/';
6618
6619
		$return ='<!-- Photo -->'."\n";
6620
		$nbphoto=0;
6621
6622
		$filearray=dol_dir_list($dir,"files",0,'','(\.meta|_preview.*\.png)$',$sortfield,(strtolower($sortorder)=='desc'?SORT_DESC:SORT_ASC),1);
6623
6624
		/*if (! empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO))    // For backward compatiblity, we scan also old dirs
6625
		 {
6626
		 $filearrayold=dol_dir_list($dirold,"files",0,'','(\.meta|_preview.*\.png)$',$sortfield,(strtolower($sortorder)=='desc'?SORT_DESC:SORT_ASC),1);
6627
		 $filearray=array_merge($filearray, $filearrayold);
6628
		 }*/
6629
6630
		completeFileArrayWithDatabaseInfo($filearray, $relativedir);
6631
6632
		if (count($filearray))
6633
		{
6634
			if ($sortfield && $sortorder)
6635
			{
6636
				$filearray=dol_sort_array($filearray, $sortfield, $sortorder);
6637
			}
6638
6639
			foreach($filearray as $key => $val)
6640
			{
6641
				$photo='';
6642
				$file = $val['name'];
6643
6644
				//if (! utf8_check($file)) $file=utf8_encode($file);	// To be sure file is stored in UTF8 in memory
6645
6646
				//if (dol_is_file($dir.$file) && image_format_supported($file) >= 0)
6647
				if (image_format_supported($file) >= 0)
6648
				{
6649
					$nbphoto++;
6650
					$photo = $file;
6651
					$viewfilename = $file;
6652
6653
					if ($size == 1 || $size == 'small') {   // Format vignette
6654
6655
						// Find name of thumb file
6656
						$photo_vignette=basename(getImageFileNameForSize($dir.$file, '_small'));
6657
						if (! dol_is_file($dirthumb.$photo_vignette)) $photo_vignette='';
6658
6659
						// Get filesize of original file
6660
						$imgarray=dol_getImageSize($dir.$photo);
6661
6662
						if ($nbbyrow > 0)
6663
						{
6664
							if ($nbphoto == 1) $return.= '<table width="100%" valign="top" align="center" border="0" cellpadding="2" cellspacing="2">';
6665
6666
							if ($nbphoto % $nbbyrow == 1) $return.= '<tr align=center valign=middle border=1>';
6667
							$return.= '<td width="'.ceil(100/$nbbyrow).'%" class="photo">';
6668
						}
6669
						else if ($nbbyrow < 0) $return .= '<div class="inline-block">';
6670
6671
						$return.= "\n";
6672
6673
						$relativefile=preg_replace('/^\//', '', $pdir.$photo);
6674
						if (empty($nolink))
6675
						{
6676
							$urladvanced=getAdvancedPreviewUrl($modulepart, $relativefile, 0, 'entity='.$this->entity);
6677
							if ($urladvanced) $return.='<a href="'.$urladvanced.'">';
6678
							else $return.= '<a href="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'" class="aphoto" target="_blank">';
6679
						}
6680
6681
						// Show image (width height=$maxHeight)
6682
						// Si fichier vignette disponible et image source trop grande, on utilise la vignette, sinon on utilise photo origine
6683
						$alt=$langs->transnoentitiesnoconv('File').': '.$relativefile;
6684
						$alt.=' - '.$langs->transnoentitiesnoconv('Size').': '.$imgarray['width'].'x'.$imgarray['height'];
6685
						if ($notitle) $alt='';
6686
6687
						if ($usesharelink)
6688
						{
6689
							if ($val['share'])
6690
							{
6691
								if (empty($maxHeight) || $photo_vignette && $imgarray['height'] > $maxHeight)
6692
								{
6693
									$return.= '<!-- Show original file (thumb not yet available with shared links) -->';
6694
									$return.= '<img class="photo photowithmargin" border="0" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/viewimage.php?hashp='.urlencode($val['share']).'" title="'.dol_escape_htmltag($alt).'">';
6695
								}
6696
								else {
6697
									$return.= '<!-- Show original file -->';
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
							}
6701
							else
6702
							{
6703
								$return.= '<!-- Show nophoto file (because file is not shared) -->';
6704
								$return.= '<img class="photo photowithmargin" border="0" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/public/theme/common/nophoto.png" title="'.dol_escape_htmltag($alt).'">';
6705
							}
6706
						}
6707
						else
6708
						{
6709
							if (empty($maxHeight) || $photo_vignette && $imgarray['height'] > $maxHeight)
6710
							{
6711
								$return.= '<!-- Show thumb -->';
6712
								$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).'">';
6713
							}
6714
							else {
6715
								$return.= '<!-- Show original file -->';
6716
								$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).'">';
6717
							}
6718
						}
6719
6720
						if (empty($nolink)) $return.= '</a>';
6721
						$return.="\n";
6722
6723
						if ($showfilename) $return.= '<br>'.$viewfilename;
6724
						if ($showaction)
6725
						{
6726
							$return.= '<br>';
6727
							// On propose la generation de la vignette si elle n'existe pas et si la taille est superieure aux limites
6728
							if ($photo_vignette && (image_format_supported($photo) > 0) && ($this->imgWidth > $maxWidth || $this->imgHeight > $maxHeight))
6729
							{
6730
								$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>';
6731
							}
6732
							// Special cas for product
6733
							if ($modulepart == 'product' && ($user->rights->produit->creer || $user->rights->service->creer))
6734
							{
6735
								// Link to resize
6736
								$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; ';
6737
6738
								// Link to delete
6739
								$return.= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&amp;action=delete&amp;file='.urlencode($pdir.$viewfilename).'">';
6740
								$return.= img_delete().'</a>';
6741
							}
6742
						}
6743
						$return.= "\n";
6744
6745
						if ($nbbyrow > 0)
6746
						{
6747
							$return.= '</td>';
6748
							if (($nbphoto % $nbbyrow) == 0) $return.= '</tr>';
6749
						}
6750
						else if ($nbbyrow < 0) $return.='</div>';
6751
					}
6752
6753
					if (empty($size)) {     // Format origine
6754
						$return.= '<img class="photo photowithmargin" border="0" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$modulepart.'&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'">';
6755
6756
						if ($showfilename) $return.= '<br>'.$viewfilename;
6757
						if ($showaction)
6758
						{
6759
							// Special case for product
6760
							if ($modulepart == 'product' && ($user->rights->produit->creer || $user->rights->service->creer))
6761
							{
6762
								// Link to resize
6763
								$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; ';
6764
6765
								// Link to delete
6766
								$return.= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&amp;action=delete&amp;file='.urlencode($pdir.$viewfilename).'">';
6767
								$return.= img_delete().'</a>';
6768
							}
6769
						}
6770
					}
6771
6772
					// On continue ou on arrete de boucler ?
6773
					if ($nbmax && $nbphoto >= $nbmax) break;
6774
				}
6775
			}
6776
6777
			if ($size==1 || $size=='small')
6778
			{
6779
				if ($nbbyrow > 0)
6780
				{
6781
					// Ferme tableau
6782
					while ($nbphoto % $nbbyrow)
6783
					{
6784
						$return.= '<td width="'.ceil(100/$nbbyrow).'%">&nbsp;</td>';
6785
						$nbphoto++;
6786
					}
6787
6788
					if ($nbphoto) $return.= '</table>';
6789
				}
6790
			}
6791
		}
6792
6793
		$this->nbphoto = $nbphoto;
6794
6795
		return $return;
6796
	}
6797
6798
6799
	/**
6800
	 * Function test if type is array
6801
	 *
6802
	 * @param   array   $info   content informations of field
6803
	 * @return                  bool
6804
	 */
6805
	protected function isArray($info)
6806
	{
6807
		if(is_array($info))
6808
		{
6809
			if(isset($info['type']) && $info['type']=='array') return true;
6810
			else return false;
6811
		}
6812
		else return false;
6813
	}
6814
6815
	/**
6816
	 * Function test if type is null
6817
	 *
6818
	 * @param   array   $info   content informations of field
6819
	 * @return                  bool
6820
	 */
6821
	protected function isNull($info)
6822
	{
6823
		if(is_array($info))
6824
		{
6825
			if(isset($info['type']) && $info['type']=='null') return true;
6826
			else return false;
6827
		}
6828
		else return false;
6829
	}
6830
6831
	/**
6832
	 * Function test if type is date
6833
	 *
6834
	 * @param   array   $info   content informations of field
6835
	 * @return                  bool
6836
	 */
6837
	public function isDate($info)
6838
	{
6839
		if(isset($info['type']) && ($info['type']=='date' || $info['type']=='datetime' || $info['type']=='timestamp')) return true;
6840
		else return false;
6841
	}
6842
6843
	/**
6844
	 * Function test if type is integer
6845
	 *
6846
	 * @param   array   $info   content informations of field
6847
	 * @return                  bool
6848
	 */
6849
	public function isInt($info)
6850
	{
6851
		if(is_array($info))
6852
		{
6853
			if(isset($info['type']) && ($info['type']=='int' || preg_match('/^integer/i',$info['type']) ) ) return true;
6854
			else return false;
6855
		}
6856
		else return false;
6857
	}
6858
6859
	/**
6860
	 * Function test if type is float
6861
	 *
6862
	 * @param   array   $info   content informations of field
6863
	 * @return                  bool
6864
	 */
6865
	public function isFloat($info)
6866
	{
6867
		if(is_array($info))
6868
		{
6869
			if (isset($info['type']) && (preg_match('/^(double|real)/i', $info['type']))) return true;
6870
			else return false;
6871
		}
6872
		else return false;
6873
	}
6874
6875
	/**
6876
	 * Function test if type is text
6877
	 *
6878
	 * @param   array   $info   content informations of field
6879
	 * @return                  bool
6880
	 */
6881
	public function isText($info)
6882
	{
6883
		if(is_array($info))
6884
		{
6885
			if(isset($info['type']) && $info['type']=='text') return true;
6886
			else return false;
6887
		}
6888
		else return false;
6889
	}
6890
6891
	/**
6892
	 * Function test if is indexed
6893
	 *
6894
	 * @param   array   $info   content informations of field
6895
	 * @return                  bool
6896
	 */
6897
	protected function isIndex($info)
6898
	{
6899
		if(is_array($info))
6900
		{
6901
			if(isset($info['index']) && $info['index']==true) return true;
6902
			else return false;
6903
		}
6904
		else return false;
6905
	}
6906
6907
	/**
6908
	 * Function to prepare the values to insert.
6909
	 * Note $this->${field} are set by the page that make the createCommon or the updateCommon.
6910
	 *
6911
	 * @return array
6912
	 */
6913
	protected function setSaveQuery()
6914
	{
6915
		global $conf;
6916
6917
		$queryarray=array();
6918
		foreach ($this->fields as $field=>$info)	// Loop on definition of fields
6919
		{
6920
			// Depending on field type ('datetime', ...)
6921
			if($this->isDate($info))
6922
			{
6923
				if(empty($this->{$field}))
6924
				{
6925
					$queryarray[$field] = null;
6926
				}
6927
				else
6928
				{
6929
					$queryarray[$field] = $this->db->idate($this->{$field});
6930
				}
6931
			}
6932
			else if($this->isArray($info))
6933
			{
6934
				if(! empty($this->{$field})) {
6935
					if(! is_array($this->{$field})) {
6936
						$this->{$field} = array($this->{$field});
6937
					}
6938
					$queryarray[$field] = serialize($this->{$field});
6939
				} else {
6940
					$queryarray[$field] = null;
6941
				}
6942
			}
6943
			else if($this->isInt($info))
6944
			{
6945
				if ($field == 'entity' && is_null($this->{$field})) $queryarray[$field]=$conf->entity;
6946
				else
6947
				{
6948
					$queryarray[$field] = (int) price2num($this->{$field});
6949
					if (empty($queryarray[$field])) $queryarray[$field]=0;		// May be reset to null later if property 'notnull' is -1 for this field.
6950
				}
6951
			}
6952
			else if($this->isFloat($info))
6953
			{
6954
				$queryarray[$field] = (double) price2num($this->{$field});
6955
				if (empty($queryarray[$field])) $queryarray[$field]=0;
6956
			}
6957
			else
6958
			{
6959
				$queryarray[$field] = $this->{$field};
6960
			}
6961
6962
			if ($info['type'] == 'timestamp' && empty($queryarray[$field])) unset($queryarray[$field]);
6963
			if (! empty($info['notnull']) && $info['notnull'] == -1 && empty($queryarray[$field])) $queryarray[$field] = null;
6964
		}
6965
6966
		return $queryarray;
6967
	}
6968
6969
	/**
6970
	 * Function to load data from a SQL pointer into properties of current object $this
6971
	 *
6972
	 * @param   stdClass    $obj    Contain data of object from database
6973
     * @return void
6974
	 */
6975
	protected function setVarsFromFetchObj(&$obj)
6976
	{
6977
		foreach ($this->fields as $field => $info)
6978
		{
6979
			if($this->isDate($info))
6980
			{
6981
				if(empty($obj->{$field}) || $obj->{$field} === '0000-00-00 00:00:00' || $obj->{$field} === '1000-01-01 00:00:00') $this->{$field} = 0;
6982
				else $this->{$field} = strtotime($obj->{$field});
6983
			}
6984
			elseif($this->isArray($info))
6985
			{
6986
				if(! empty($obj->{$field})) {
6987
					$this->{$field} = @unserialize($obj->{$field});
6988
					// Hack for data not in UTF8
6989
					if($this->{$field } === false) @unserialize(utf8_decode($obj->{$field}));
6990
				} else {
6991
					$this->{$field} = array();
6992
				}
6993
			}
6994
			elseif($this->isInt($info))
6995
			{
6996
				if ($field == 'rowid') $this->id = (int) $obj->{$field};
6997
				else $this->{$field} = (int) $obj->{$field};
6998
			}
6999
			elseif($this->isFloat($info))
7000
			{
7001
				$this->{$field} = (double) $obj->{$field};
7002
			}
7003
			elseif($this->isNull($info))
7004
			{
7005
				$val = $obj->{$field};
7006
				// zero is not null
7007
				$this->{$field} = (is_null($val) || (empty($val) && $val!==0 && $val!=='0') ? null : $val);
7008
			}
7009
			else
7010
			{
7011
				$this->{$field} = $obj->{$field};
7012
			}
7013
		}
7014
7015
		// If there is no 'ref' field, we force property ->ref to ->id for a better compatibility with common functions.
7016
		if (! isset($this->fields['ref']) && isset($this->id)) $this->ref = $this->id;
7017
	}
7018
7019
	/**
7020
	 * Function to concat keys of fields
7021
	 *
7022
	 * @return string
7023
	 */
7024
	protected function getFieldList()
7025
	{
7026
		$keys = array_keys($this->fields);
7027
		return implode(',', $keys);
7028
	}
7029
7030
	/**
7031
	 * Add quote to field value if necessary
7032
	 *
7033
	 * @param 	string|int	$value			Value to protect
7034
	 * @param	array		$fieldsentry	Properties of field
7035
	 * @return 	string
7036
	 */
7037
    protected function quote($value, $fieldsentry)
7038
    {
7039
		if (is_null($value)) return 'NULL';
7040
		else if (preg_match('/^(int|double|real)/i', $fieldsentry['type'])) return $this->db->escape("$value");
7041
		else return "'".$this->db->escape($value)."'";
7042
	}
7043
7044
7045
	/**
7046
	 * Create object into database
7047
	 *
7048
	 * @param  User $user      User that creates
7049
	 * @param  bool $notrigger false=launch triggers after, true=disable triggers
7050
	 * @return int             <0 if KO, Id of created object if OK
7051
	 */
7052
	public function createCommon(User $user, $notrigger = false)
7053
	{
7054
		global $langs;
7055
7056
		$error = 0;
7057
7058
		$now=dol_now();
7059
7060
		$fieldvalues = $this->setSaveQuery();
7061
		if (array_key_exists('date_creation', $fieldvalues) && empty($fieldvalues['date_creation'])) $fieldvalues['date_creation']=$this->db->idate($now);
7062
		if (array_key_exists('fk_user_creat', $fieldvalues) && ! ($fieldvalues['fk_user_creat'] > 0)) $fieldvalues['fk_user_creat']=$user->id;
7063
		unset($fieldvalues['rowid']);	// The field 'rowid' is reserved field name for autoincrement field so we don't need it into insert.
7064
7065
		$keys=array();
7066
		$values = array();
7067
		foreach ($fieldvalues as $k => $v) {
7068
			$keys[$k] = $k;
7069
			$value = $this->fields[$k];
7070
			$values[$k] = $this->quote($v, $value);
7071
		}
7072
7073
		// Clean and check mandatory
7074
		foreach($keys as $key)
7075
		{
7076
			// If field is an implicit foreign key field
7077
			if (preg_match('/^integer:/i', $this->fields[$key]['type']) && $values[$key] == '-1') $values[$key]='';
7078
			if (! empty($this->fields[$key]['foreignkey']) && $values[$key] == '-1') $values[$key]='';
7079
7080
			//var_dump($key.'-'.$values[$key].'-'.($this->fields[$key]['notnull'] == 1));
7081
			if (isset($this->fields[$key]['notnull']) && $this->fields[$key]['notnull'] == 1 && ! isset($values[$key]) && is_null($val['default']))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $val does not exist. Did you maybe mean $value?
Loading history...
7082
			{
7083
				$error++;
7084
				$this->errors[]=$langs->trans("ErrorFieldRequired", $this->fields[$key]['label']);
7085
			}
7086
7087
			// If field is an implicit foreign key field
7088
			if (preg_match('/^integer:/i', $this->fields[$key]['type']) && empty($values[$key])) $values[$key]='null';
7089
			if (! empty($this->fields[$key]['foreignkey']) && empty($values[$key])) $values[$key]='null';
7090
		}
7091
7092
		if ($error) return -1;
7093
7094
		$this->db->begin();
7095
7096
		if (! $error)
7097
		{
7098
			$sql = 'INSERT INTO '.MAIN_DB_PREFIX.$this->table_element;
7099
			$sql.= ' ('.implode( ", ", $keys ).')';
7100
			$sql.= ' VALUES ('.implode( ", ", $values ).')';
7101
7102
			$res = $this->db->query($sql);
7103
			if ($res===false) {
7104
				$error++;
7105
				$this->errors[] = $this->db->lasterror();
7106
			}
7107
		}
7108
7109
		if (! $error)
7110
		{
7111
			$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX . $this->table_element);
7112
		}
7113
7114
		// Create extrafields
7115
		if (! $error)
7116
		{
7117
			$result=$this->insertExtraFields();
7118
			if ($result < 0) $error++;
7119
		}
7120
7121
		// Triggers
7122
		if (! $error && ! $notrigger)
7123
		{
7124
			// Call triggers
7125
			$result=$this->call_trigger(strtoupper(get_class($this)).'_CREATE',$user);
7126
			if ($result < 0) { $error++; }
7127
			// End call triggers
7128
		}
7129
7130
		// Commit or rollback
7131
		if ($error) {
7132
			$this->db->rollback();
7133
			return -1;
7134
		} else {
7135
			$this->db->commit();
7136
			return $this->id;
7137
		}
7138
	}
7139
7140
7141
	/**
7142
	 * Load object in memory from the database
7143
	 *
7144
	 * @param	int    $id				Id object
7145
	 * @param	string $ref				Ref
7146
	 * @param	string	$morewhere		More SQL filters (' AND ...')
7147
	 * @return 	int         			<0 if KO, 0 if not found, >0 if OK
7148
	 */
7149
	public function fetchCommon($id, $ref = null, $morewhere = '')
7150
	{
7151
		if (empty($id) && empty($ref) && empty($morewhere)) return -1;
7152
7153
		$sql = 'SELECT '.$this->getFieldList();
7154
		$sql.= ' FROM '.MAIN_DB_PREFIX.$this->table_element;
7155
7156
		if (!empty($id))  $sql.= ' WHERE rowid = '.$id;
7157
		elseif (!empty($ref)) $sql.= " WHERE ref = ".$this->quote($ref, $this->fields['ref']);
7158
		else $sql.=' WHERE 1 = 1';	// usage with empty id and empty ref is very rare
7159
		if ($morewhere)   $sql.= $morewhere;
7160
		$sql.=' LIMIT 1';	// This is a fetch, to be sure to get only one record
7161
7162
		$res = $this->db->query($sql);
7163
		if ($res)
7164
		{
7165
			$obj = $this->db->fetch_object($res);
7166
			if ($obj)
7167
			{
7168
				$this->setVarsFromFetchObj($obj);
7169
				return $this->id;
7170
			}
7171
			else
7172
			{
7173
				return 0;
7174
			}
7175
		}
7176
		else
7177
		{
7178
			$this->error = $this->db->lasterror();
7179
			$this->errors[] = $this->error;
7180
			return -1;
7181
		}
7182
	}
7183
7184
	/**
7185
	 * Update object into database
7186
	 *
7187
	 * @param  User $user      	User that modifies
7188
	 * @param  bool $notrigger 	false=launch triggers after, true=disable triggers
7189
	 * @return int             	<0 if KO, >0 if OK
7190
	 */
7191
	public function updateCommon(User $user, $notrigger = false)
7192
	{
7193
		global $conf, $langs;
7194
7195
		$error = 0;
7196
7197
		$now=dol_now();
7198
7199
		$fieldvalues = $this->setSaveQuery();
7200
		if (array_key_exists('date_modification', $fieldvalues) && empty($fieldvalues['date_modification'])) $fieldvalues['date_modification']=$this->db->idate($now);
7201
		if (array_key_exists('fk_user_modif', $fieldvalues) && ! ($fieldvalues['fk_user_modif'] > 0)) $fieldvalues['fk_user_modif']=$user->id;
7202
		unset($fieldvalues['rowid']);	// The field 'rowid' is reserved field name for autoincrement field so we don't need it into update.
7203
7204
		$keys=array();
7205
		$values = array();
7206
		foreach ($fieldvalues as $k => $v) {
7207
			$keys[$k] = $k;
7208
			$value = $this->fields[$k];
7209
			$values[$k] = $this->quote($v, $value);
7210
			$tmp[] = $k.'='.$this->quote($v, $this->fields[$k]);
7211
		}
7212
7213
		// Clean and check mandatory
7214
		foreach($keys as $key)
7215
		{
7216
			if (preg_match('/^integer:/i', $this->fields[$key]['type']) && $values[$key] == '-1') $values[$key]='';		// This is an implicit foreign key field
7217
			if (! empty($this->fields[$key]['foreignkey']) && $values[$key] == '-1') $values[$key]='';					// This is an explicit foreign key field
7218
7219
			//var_dump($key.'-'.$values[$key].'-'.($this->fields[$key]['notnull'] == 1));
7220
			/*
7221
			if ($this->fields[$key]['notnull'] == 1 && empty($values[$key]))
7222
			{
7223
				$error++;
7224
				$this->errors[]=$langs->trans("ErrorFieldRequired", $this->fields[$key]['label']);
7225
			}*/
7226
		}
7227
7228
		$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element.' SET '.implode( ',', $tmp ).' WHERE rowid='.$this->id ;
7229
7230
		$this->db->begin();
7231
		if (! $error)
7232
		{
7233
			$res = $this->db->query($sql);
7234
			if ($res===false)
7235
			{
7236
				$error++;
7237
				$this->errors[] = $this->db->lasterror();
7238
			}
7239
		}
7240
7241
		// Update extrafield
7242
		if (! $error && empty($conf->global->MAIN_EXTRAFIELDS_DISABLED) && is_array($this->array_options) && count($this->array_options)>0)
7243
		{
7244
			$result=$this->insertExtraFields();
7245
			if ($result < 0)
7246
			{
7247
				$error++;
7248
			}
7249
		}
7250
7251
		// Triggers
7252
		if (! $error && ! $notrigger)
7253
		{
7254
			// Call triggers
7255
			$result=$this->call_trigger(strtoupper(get_class($this)).'_MODIFY',$user);
7256
			if ($result < 0) { $error++; } //Do also here what you must do to rollback action if trigger fail
7257
			// End call triggers
7258
		}
7259
7260
		// Commit or rollback
7261
		if ($error) {
7262
			$this->db->rollback();
7263
			return -1;
7264
		} else {
7265
			$this->db->commit();
7266
			return $this->id;
7267
		}
7268
	}
7269
7270
	/**
7271
	 * Delete object in database
7272
	 *
7273
	 * @param 	User 	$user       			User that deletes
7274
	 * @param 	bool 	$notrigger  			false=launch triggers after, true=disable triggers
7275
	 * @param	int		$forcechilddeletion		0=no, 1=Force deletion of children
7276
	 * @return 	int             				<=0 if KO, >0 if OK
7277
	 */
7278
	public function deleteCommon(User $user, $notrigger=false, $forcechilddeletion=0)
7279
	{
7280
		$error=0;
7281
7282
		$this->db->begin();
7283
7284
		if ($forcechilddeletion)
7285
		{
7286
			foreach($this->childtables as $table)
7287
			{
7288
				$sql = 'DELETE FROM '.MAIN_DB_PREFIX.$table.' WHERE '.$this->fk_element.' = '.$this->id;
7289
				$resql = $this->db->query($sql);
7290
				if (! $resql)
7291
				{
7292
					$this->error=$this->db->lasterror();
7293
					$this->errors[]=$this->error;
7294
					$this->db->rollback();
7295
					return -1;
7296
				}
7297
			}
7298
		}
7299
		elseif (! empty($this->fk_element) && ! empty($this->childtables))	// If object has childs linked with a foreign key field, we check all child tables.
7300
		{
7301
			$objectisused = $this->isObjectUsed($this->id);
7302
			if (! empty($objectisused))
7303
			{
7304
				dol_syslog(get_class($this)."::deleteCommon Can't delete record as it has some child", LOG_WARNING);
7305
				$this->error='ErrorRecordHasChildren';
7306
				$this->errors[]=$this->error;
7307
				$this->db->rollback();
7308
				return 0;
7309
			}
7310
		}
7311
7312
		if (! $error) {
7313
			if (! $notrigger) {
7314
				// Call triggers
7315
				$result=$this->call_trigger(strtoupper(get_class($this)).'_DELETE', $user);
7316
				if ($result < 0) { $error++; } // Do also here what you must do to rollback action if trigger fail
7317
				// End call triggers
7318
			}
7319
		}
7320
7321
		if (! $error && ! empty($this->isextrafieldmanaged))
7322
		{
7323
			$sql = "DELETE FROM " . MAIN_DB_PREFIX . $this->table_element."_extrafields";
7324
			$sql.= " WHERE fk_object=" . $this->id;
7325
7326
			$resql = $this->db->query($sql);
7327
			if (! $resql)
7328
			{
7329
				$this->errors[] = $this->db->lasterror();
7330
				$error++;
7331
			}
7332
		}
7333
7334
		if (! $error)
7335
		{
7336
			$sql = 'DELETE FROM '.MAIN_DB_PREFIX.$this->table_element.' WHERE rowid='.$this->id;
7337
7338
			$res = $this->db->query($sql);
7339
			if($res===false) {
7340
				$error++;
7341
				$this->errors[] = $this->db->lasterror();
7342
			}
7343
		}
7344
7345
		// Commit or rollback
7346
		if ($error) {
7347
			$this->db->rollback();
7348
			return -1;
7349
		} else {
7350
			$this->db->commit();
7351
			return 1;
7352
		}
7353
	}
7354
7355
	/**
7356
	 * Initialise object with example values
7357
	 * Id must be 0 if object instance is a specimen
7358
	 *
7359
	 * @return void
7360
	 */
7361
	public function initAsSpecimenCommon()
7362
	{
7363
		$this->id = 0;
7364
7365
		// TODO...
7366
	}
7367
7368
7369
	/* Part for comments */
7370
7371
	/**
7372
	 * Load comments linked with current task
7373
	 *	@return boolean	1 if ok
7374
	 */
7375
	public function fetchComments()
7376
	{
7377
		require_once DOL_DOCUMENT_ROOT.'/core/class/comment.class.php';
7378
7379
		$comment = new Comment($this->db);
7380
		$result=$comment->fetchAllFor($this->element, $this->id);
7381
		if ($result<0) {
7382
			$this->errors=array_merge($this->errors, $comment->errors);
7383
			return -1;
7384
		} else {
7385
			$this->comments = $comment->comments;
7386
		}
7387
		return count($this->comments);
7388
	}
7389
7390
	/**
7391
	 * Return nb comments already posted
7392
	 *
7393
	 * @return int
7394
	 */
7395
	public function getNbComments()
7396
	{
7397
		return count($this->comments);
7398
	}
7399
7400
    /**
7401
     * Trim object parameters
7402
     * @param string[] $parameters array of parameters to trim
7403
     *
7404
     * @return void
7405
     */
7406
    public function trimParameters($parameters)
7407
    {
7408
        if (!is_array($parameters)) return;
7409
        foreach ($parameters as $parameter) {
7410
            if (isset($this->$parameter)) {
7411
                $this->$parameter = trim($this->$parameter);
7412
            }
7413
        }
7414
    }
7415
}
7416