Passed
Push — master ( 1007d6...492b54 )
by Alxarafe
26:10
created

Base/CommonObject.php (1 issue)

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

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

1948
				/** @scrutinizer ignore-deprecated */ $this->cond_reglement = $id;	// for compatibility

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

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