Passed
Branch develop (944e2a)
by
unknown
27:19
created

CommonObject::getIdOfLine()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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

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

1553
		return $this->/** @scrutinizer ignore-call */ fetch($result->rowid);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1554
	}
1555
1556
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1557
	/**
1558
	 *	Load data for barcode into properties ->barcode_type*
1559
	 *	Properties ->barcode_type that is id of barcode. Type is used to find other properties, but
1560
	 *  if it is not defined, ->element must be defined to know default barcode type.
1561
	 *
1562
	 *	@return		int			<0 if KO, 0 if can't guess type of barcode (ISBN, EAN13...), >0 if OK (all barcode properties loaded)
1563
	 */
1564
	public function fetch_barcode()
1565
	{
1566
		// phpcs:enable
1567
		global $conf;
1568
1569
		dol_syslog(get_class($this).'::fetch_barcode this->element='.$this->element.' this->barcode_type='.$this->barcode_type);
1570
1571
		$idtype = $this->barcode_type;
1572
		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
1573
		{
1574
			if ($this->element == 'product')      $idtype = $conf->global->PRODUIT_DEFAULT_BARCODE_TYPE;
1575
			elseif ($this->element == 'societe') $idtype = $conf->global->GENBARCODE_BARCODETYPE_THIRDPARTY;
1576
			else dol_syslog('Call fetch_barcode with barcode_type not defined and cant be guessed', LOG_WARNING);
1577
		}
1578
1579
		if ($idtype > 0)
1580
		{
1581
			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
1582
			{
1583
				$sql = "SELECT rowid, code, libelle as label, coder";
1584
				$sql .= " FROM ".MAIN_DB_PREFIX."c_barcode_type";
1585
				$sql .= " WHERE rowid = ".$idtype;
1586
				dol_syslog(get_class($this).'::fetch_barcode', LOG_DEBUG);
1587
				$resql = $this->db->query($sql);
1588
				if ($resql)
1589
				{
1590
					$obj = $this->db->fetch_object($resql);
1591
					$this->barcode_type       = $obj->rowid;
1592
					$this->barcode_type_code  = $obj->code;
1593
					$this->barcode_type_label = $obj->label;
1594
					$this->barcode_type_coder = $obj->coder;
1595
					return 1;
1596
				} else {
1597
					dol_print_error($this->db);
1598
					return -1;
1599
				}
1600
			}
1601
		}
1602
		return 0;
1603
	}
1604
1605
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1606
	/**
1607
	 *		Load the project with id $this->fk_project into this->project
1608
	 *
1609
	 *		@return		int			<0 if KO, >=0 if OK
1610
	 */
1611
	public function fetch_projet()
1612
	{
1613
		// phpcs:enable
1614
		include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
1615
1616
		if (empty($this->fk_project) && !empty($this->fk_projet)) $this->fk_project = $this->fk_projet; // For backward compatibility
1617
		if (empty($this->fk_project)) return 0;
1618
1619
		$project = new Project($this->db);
1620
		$result = $project->fetch($this->fk_project);
1621
1622
		$this->projet = $project; // deprecated
1 ignored issue
show
Deprecated Code introduced by
The property CommonObject::$projet has been deprecated. ( Ignorable by Annotation )

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

1622
		/** @scrutinizer ignore-deprecated */ $this->projet = $project; // deprecated
Loading history...
1623
		$this->project = $project;
1624
		return $result;
1625
	}
1626
1627
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1628
	/**
1629
	 *		Load the product with id $this->fk_product into this->product
1630
	 *
1631
	 *		@return		int			<0 if KO, >=0 if OK
1632
	 */
1633
	public function fetch_product()
1634
	{
1635
		// phpcs:enable
1636
		include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
1637
1638
		if (empty($this->fk_product)) return 0;
1639
1640
		$product = new Product($this->db);
1641
		$result = $product->fetch($this->fk_product);
1642
1643
		$this->product = $product;
1644
		return $result;
1645
	}
1646
1647
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1648
	/**
1649
	 *		Load the user with id $userid into this->user
1650
	 *
1651
	 *		@param	int		$userid 		Id du contact
1652
	 *		@return	int						<0 if KO, >0 if OK
1653
	 */
1654
	public function fetch_user($userid)
1655
	{
1656
		// phpcs:enable
1657
		$user = new User($this->db);
1658
		$result = $user->fetch($userid);
1659
		$this->user = $user;
1660
		return $result;
1661
	}
1662
1663
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1664
	/**
1665
	 *	Read linked origin object
1666
	 *
1667
	 *	@return		void
1668
	 */
1669
	public function fetch_origin()
1670
	{
1671
		// phpcs:enable
1672
		if ($this->origin == 'shipping') $this->origin = 'expedition';
1673
		if ($this->origin == 'delivery') $this->origin = 'livraison';
1674
		if ($this->origin == 'order_supplier') $this->origin = 'commandeFournisseur';
1675
1676
		$origin = $this->origin;
1677
1678
		$classname = ucfirst($origin);
1679
		$this->$origin = new $classname($this->db);
1680
		$this->$origin->fetch($this->origin_id);
1681
	}
1682
1683
	/**
1684
	 *  Load object from specific field
1685
	 *
1686
	 *  @param	string	$table		Table element or element line
1687
	 *  @param	string	$field		Field selected
1688
	 *  @param	string	$key		Import key
1689
	 *  @param	string	$element	Element name
1690
	 *	@return	int					<0 if KO, >0 if OK
1691
	 */
1692
	public function fetchObjectFrom($table, $field, $key, $element = null)
1693
	{
1694
		global $conf;
1695
1696
		$result = false;
1697
1698
		$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX.$table;
1699
		$sql .= " WHERE ".$field." = '".$key."'";
1700
		if (!empty($element)) {
1701
			$sql .= " AND entity IN (".getEntity($element).")";
1702
		} else {
1703
			$sql .= " AND entity = ".$conf->entity;
1704
		}
1705
1706
		dol_syslog(get_class($this).'::fetchObjectFrom', LOG_DEBUG);
1707
		$resql = $this->db->query($sql);
1708
		if ($resql)
1709
		{
1710
			$row = $this->db->fetch_row($resql);
1711
			// Test for avoid error -1
1712
			if ($row[0] > 0) {
1713
				$result = $this->fetch($row[0]);
1714
			}
1715
		}
1716
1717
		return $result;
1718
	}
1719
1720
	/**
1721
	 *	Getter generic. Load value from a specific field
1722
	 *
1723
	 *	@param	string	$table		Table of element or element line
1724
	 *	@param	int		$id			Element id
1725
	 *	@param	string	$field		Field selected
1726
	 *	@return	int					<0 if KO, >0 if OK
1727
	 */
1728
	public function getValueFrom($table, $id, $field)
1729
	{
1730
		$result = false;
1731
		if (!empty($id) && !empty($field) && !empty($table)) {
1732
			$sql = "SELECT ".$field." FROM ".MAIN_DB_PREFIX.$table;
1733
			$sql .= " WHERE rowid = ".$id;
1734
1735
			dol_syslog(get_class($this).'::getValueFrom', LOG_DEBUG);
1736
			$resql = $this->db->query($sql);
1737
			if ($resql)
1738
			{
1739
				$row = $this->db->fetch_row($resql);
1740
				$result = $row[0];
1741
			}
1742
		}
1743
		return $result;
1744
	}
1745
1746
	/**
1747
	 *	Setter generic. Update a specific field into database.
1748
	 *  Warning: Trigger is run only if param trigkey is provided.
1749
	 *
1750
	 *	@param	string		$field			Field to update
1751
	 *	@param	mixed		$value			New value
1752
	 *	@param	string		$table			To force other table element or element line (should not be used)
1753
	 *	@param	int			$id				To force other object id (should not be used)
1754
	 *	@param	string		$format			Data format ('text', 'date'). 'text' is used if not defined
1755
	 *	@param	string		$id_field		To force rowid field name. 'rowid' is used if not defined
1756
	 *	@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'
1757
	 *  @param  string      $trigkey    	Trigger key to run (in most cases something like 'XXX_MODIFY')
1758
	 *  @param	string		$fk_user_field	Name of field to save user id making change
1759
	 *	@return	int							<0 if KO, >0 if OK
1760
	 *  @see updateExtraField()
1761
	 */
1762
	public function setValueFrom($field, $value, $table = '', $id = null, $format = '', $id_field = '', $fuser = null, $trigkey = '', $fk_user_field = 'fk_user_modif')
1763
	{
1764
		global $user, $langs, $conf;
1765
1766
		if (empty($table)) 	  $table = $this->table_element;
1767
		if (empty($id))    	  $id = $this->id;
1768
		if (empty($format))   $format = 'text';
1769
		if (empty($id_field)) $id_field = 'rowid';
1770
1771
		$error = 0;
1772
1773
		$this->db->begin();
1774
1775
		// Special case
1776
		if ($table == 'product' && $field == 'note_private') $field = 'note';
1777
		if (in_array($table, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) $fk_user_field = 'fk_user_mod';
1778
1779
		$sql = "UPDATE ".MAIN_DB_PREFIX.$table." SET ";
1780
1781
		if ($format == 'text') $sql .= $field." = '".$this->db->escape($value)."'";
1782
		elseif ($format == 'int') $sql .= $field." = ".$this->db->escape($value);
1783
		elseif ($format == 'date') $sql .= $field." = ".($value ? "'".$this->db->idate($value)."'" : "null");
1784
1785
		if ($fk_user_field)
1786
		{
1787
			if (!empty($fuser) && is_object($fuser)) $sql .= ", ".$fk_user_field." = ".$fuser->id;
1788
			elseif (empty($fuser) || $fuser != 'none') $sql .= ", ".$fk_user_field." = ".$user->id;
1789
		}
1790
1791
		$sql .= " WHERE ".$id_field." = ".$id;
1792
1793
		dol_syslog(__METHOD__."", LOG_DEBUG);
1794
		$resql = $this->db->query($sql);
1795
		if ($resql)
1796
		{
1797
			if ($trigkey)
1798
			{
1799
				// call trigger with updated object values
1800
				if (empty($this->fields) && method_exists($this, 'fetch'))
1801
				{
1802
					$result = $this->fetch($id);
1803
				} else {
1804
					$result = $this->fetchCommon($id);
1805
				}
1806
				if ($result >= 0) $result = $this->call_trigger($trigkey, (!empty($fuser) && is_object($fuser)) ? $fuser : $user); // This may set this->errors
1807
				if ($result < 0) $error++;
1808
			}
1809
1810
			if (!$error)
1811
			{
1812
				if (property_exists($this, $field)) $this->$field = $value;
1813
				$this->db->commit();
1814
				return 1;
1815
			} else {
1816
				$this->db->rollback();
1817
				return -2;
1818
			}
1819
		} else {
1820
			$this->error = $this->db->lasterror();
1821
			$this->db->rollback();
1822
			return -1;
1823
		}
1824
	}
1825
1826
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1827
	/**
1828
	 *      Load properties id_previous and id_next by comparing $fieldid with $this->ref
1829
	 *
1830
	 *      @param	string	$filter		Optional filter. Example: " AND (t.field1 = 'aa' OR t.field2 = 'bb')"
1831
	 *	 	@param  string	$fieldid   	Name of field to use for the select MAX and MIN
1832
	 *		@param	int		$nodbprefix	Do not include DB prefix to forge table name
1833
	 *      @return int         		<0 if KO, >0 if OK
1834
	 */
1835
	public function load_previous_next_ref($filter, $fieldid, $nodbprefix = 0)
1836
	{
1837
		// phpcs:enable
1838
		global $conf, $user;
1839
1840
		if (!$this->table_element)
1841
		{
1842
			dol_print_error('', get_class($this)."::load_previous_next_ref was called on objet with property table_element not defined");
1843
			return -1;
1844
		}
1845
		if ($fieldid == 'none') return 1;
1846
1847
		// Security on socid
1848
		$socid = 0;
1849
		if ($user->socid > 0) $socid = $user->socid;
1850
1851
		// this->ismultientitymanaged contains
1852
		// 0=No test on entity, 1=Test with field entity, 'field@table'=Test with link by field@table
1853
		$aliastablesociete = 's';
1854
		if ($this->element == 'societe') $aliastablesociete = 'te'; // te as table_element
1855
1856
		$sql = "SELECT MAX(te.".$fieldid.")";
1857
		$sql .= " FROM ".(empty($nodbprefix) ?MAIN_DB_PREFIX:'').$this->table_element." as te";
1858
		if ($this->element == 'user' && !empty($conf->global->MULTICOMPANY_TRANSVERSE_MODE)) {
1859
			$sql .= ",".MAIN_DB_PREFIX."usergroup_user as ug";
1860
		}
1861
		if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
1862
			$tmparray = explode('@', $this->ismultientitymanaged);
1863
			$sql .= ", ".MAIN_DB_PREFIX.$tmparray[1]." as ".($tmparray[1] == 'societe' ? 's' : 'parenttable'); // If we need to link to this table to limit select to entity
1864
		} elseif ($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
1865
		elseif ($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
1866
		if ($this->restrictiononfksoc && !$user->rights->societe->client->voir && !$socid)  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
1867
		$sql .= " WHERE te.".$fieldid." < '".$this->db->escape($fieldid == 'rowid' ? $this->id : $this->ref)."'"; // ->ref must always be defined (set to id if field does not exists)
1868
		if ($this->restrictiononfksoc == 1 && !$user->rights->societe->client->voir && !$socid) $sql .= " AND sc.fk_user = ".$user->id;
1869
		if ($this->restrictiononfksoc == 2 && !$user->rights->societe->client->voir && !$socid) $sql .= " AND (sc.fk_user = ".$user->id.' OR te.fk_soc IS NULL)';
1870
		if (!empty($filter))
1871
		{
1872
			if (!preg_match('/^\s*AND/i', $filter)) $sql .= " AND "; // For backward compatibility
1873
			$sql .= $filter;
1874
		}
1875
		if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
1876
			$tmparray = explode('@', $this->ismultientitymanaged);
1877
			$sql .= ' AND te.'.$tmparray[0].' = '.($tmparray[1] == 'societe' ? 's' : 'parenttable').'.rowid'; // If we need to link to this table to limit select to entity
1878
		} elseif ($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
1879
		if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
1880
			if ($this->element == 'user' && !empty($conf->global->MULTICOMPANY_TRANSVERSE_MODE)) {
1881
				if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
1882
					$sql .= " AND te.entity IS NOT NULL"; // Show all users
1883
				} else {
1884
					$sql .= " AND ug.fk_user = te.rowid";
1885
					$sql .= " AND ug.entity IN (".getEntity($this->element).")";
1886
				}
1887
			} else {
1888
				$sql .= ' AND te.entity IN ('.getEntity($this->element).')';
1889
			}
1890
		}
1891
		if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
1892
			$tmparray = explode('@', $this->ismultientitymanaged);
1893
			$sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
1894
		}
1895
		if ($this->restrictiononfksoc == 1 && $socid && $this->element != 'societe') $sql .= ' AND te.fk_soc = '.$socid;
1896
		if ($this->restrictiononfksoc == 2 && $socid && $this->element != 'societe') $sql .= ' AND (te.fk_soc = '.$socid.' OR te.fk_soc IS NULL)';
1897
		if ($this->restrictiononfksoc && $socid && $this->element == 'societe') $sql .= ' AND te.rowid = '.$socid;
1898
		//print 'socid='.$socid.' restrictiononfksoc='.$this->restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
1899
1900
		$result = $this->db->query($sql);
1901
		if (!$result)
1902
		{
1903
			$this->error = $this->db->lasterror();
1904
			return -1;
1905
		}
1906
		$row = $this->db->fetch_row($result);
1907
		$this->ref_previous = $row[0];
1908
1909
		$sql = "SELECT MIN(te.".$fieldid.")";
1910
		$sql .= " FROM ".(empty($nodbprefix) ?MAIN_DB_PREFIX:'').$this->table_element." as te";
1911
		if ($this->element == 'user' && !empty($conf->global->MULTICOMPANY_TRANSVERSE_MODE)) {
1912
			$sql .= ",".MAIN_DB_PREFIX."usergroup_user as ug";
1913
		}
1914
		if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
1915
			$tmparray = explode('@', $this->ismultientitymanaged);
1916
			$sql .= ", ".MAIN_DB_PREFIX.$tmparray[1]." as ".($tmparray[1] == 'societe' ? 's' : 'parenttable'); // If we need to link to this table to limit select to entity
1917
		} elseif ($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
1918
		elseif ($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
1919
		if ($this->restrictiononfksoc && !$user->rights->societe->client->voir && !$socid) $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
1920
		$sql .= " WHERE te.".$fieldid." > '".$this->db->escape($fieldid == 'rowid' ? $this->id : $this->ref)."'"; // ->ref must always be defined (set to id if field does not exists)
1921
		if ($this->restrictiononfksoc == 1 && !$user->rights->societe->client->voir && !$socid) $sql .= " AND sc.fk_user = ".$user->id;
1922
		if ($this->restrictiononfksoc == 2 && !$user->rights->societe->client->voir && !$socid) $sql .= " AND (sc.fk_user = ".$user->id.' OR te.fk_soc IS NULL)';
1923
		if (!empty($filter))
1924
		{
1925
			if (!preg_match('/^\s*AND/i', $filter)) $sql .= " AND "; // For backward compatibility
1926
			$sql .= $filter;
1927
		}
1928
		if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
1929
			$tmparray = explode('@', $this->ismultientitymanaged);
1930
			$sql .= ' AND te.'.$tmparray[0].' = '.($tmparray[1] == 'societe' ? 's' : 'parenttable').'.rowid'; // If we need to link to this table to limit select to entity
1931
		} elseif ($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
1932
		if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
1933
			if ($this->element == 'user' && !empty($conf->global->MULTICOMPANY_TRANSVERSE_MODE)) {
1934
				if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
1935
					$sql .= " AND te.entity IS NOT NULL"; // Show all users
1936
				} else {
1937
					$sql .= " AND ug.fk_user = te.rowid";
1938
					$sql .= " AND ug.entity IN (".getEntity($this->element).")";
1939
				}
1940
			} else {
1941
				$sql .= ' AND te.entity IN ('.getEntity($this->element).')';
1942
			}
1943
		}
1944
		if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
1945
			$tmparray = explode('@', $this->ismultientitymanaged);
1946
			$sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
1947
		}
1948
		if ($this->restrictiononfksoc == 1 && $socid && $this->element != 'societe') $sql .= ' AND te.fk_soc = '.$socid;
1949
		if ($this->restrictiononfksoc == 2 && $socid && $this->element != 'societe') $sql .= ' AND (te.fk_soc = '.$socid.' OR te.fk_soc IS NULL)';
1950
		if ($this->restrictiononfksoc && $socid && $this->element == 'societe') $sql .= ' AND te.rowid = '.$socid;
1951
		//print 'socid='.$socid.' restrictiononfksoc='.$this->restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
1952
		// 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
1953
1954
		$result = $this->db->query($sql);
1955
		if (!$result)
1956
		{
1957
			$this->error = $this->db->lasterror();
1958
			return -2;
1959
		}
1960
		$row = $this->db->fetch_row($result);
1961
		$this->ref_next = $row[0];
1962
1963
		return 1;
1964
	}
1965
1966
1967
	/**
1968
	 *      Return list of id of contacts of object
1969
	 *
1970
	 *      @param	string	$source     Source of contact: external (llx_socpeople) or internal (llx_user) or thirdparty (llx_societe)
1971
	 *      @return array				Array of id of contacts (if source=external or internal)
1972
	 * 									Array of id of third parties with at least one contact on object (if source=thirdparty)
1973
	 */
1974
	public function getListContactId($source = 'external')
1975
	{
1976
		$contactAlreadySelected = array();
1977
		$tab = $this->liste_contact(-1, $source);
1978
		$num = count($tab);
1979
		$i = 0;
1980
		while ($i < $num)
1981
		{
1982
			if ($source == 'thirdparty') $contactAlreadySelected[$i] = $tab[$i]['socid'];
1983
			else $contactAlreadySelected[$i] = $tab[$i]['id'];
1984
			$i++;
1985
		}
1986
		return $contactAlreadySelected;
1987
	}
1988
1989
1990
	/**
1991
	 *	Link element with a project
1992
	 *
1993
	 *	@param     	int		$projectid		Project id to link element to
1994
	 *	@return		int						<0 if KO, >0 if OK
1995
	 */
1996
	public function setProject($projectid)
1997
	{
1998
		if (!$this->table_element)
1999
		{
2000
			dol_syslog(get_class($this)."::setProject was called on objet with property table_element not defined", LOG_ERR);
2001
			return -1;
2002
		}
2003
2004
		$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2005
		if (!empty($this->fields['fk_project']))		// Common case
2006
		{
2007
			if ($projectid) $sql .= ' SET fk_project = '.$projectid;
2008
			else $sql .= ' SET fk_project = NULL';
2009
			$sql .= ' WHERE rowid = '.$this->id;
2010
		} elseif ($this->table_element == 'actioncomm')	// Special case for actioncomm
2011
		{
2012
			if ($projectid) $sql .= ' SET fk_project = '.$projectid;
2013
			else $sql .= ' SET fk_project = NULL';
2014
			$sql .= ' WHERE id = '.$this->id;
2015
		} else // Special case for old architecture objects
2016
		{
2017
			if ($projectid) $sql .= ' SET fk_projet = '.$projectid;
2018
			else $sql .= ' SET fk_projet = NULL';
2019
			$sql .= ' WHERE rowid = '.$this->id;
2020
		}
2021
2022
		dol_syslog(get_class($this)."::setProject", LOG_DEBUG);
2023
		if ($this->db->query($sql))
2024
		{
2025
			$this->fk_project = $projectid;
2026
			return 1;
2027
		} else {
2028
			dol_print_error($this->db);
2029
			return -1;
2030
		}
2031
	}
2032
2033
	/**
2034
	 *  Change the payments methods
2035
	 *
2036
	 *  @param		int		$id		Id of new payment method
2037
	 *  @return		int				>0 if OK, <0 if KO
2038
	 */
2039
	public function setPaymentMethods($id)
2040
	{
2041
		dol_syslog(get_class($this).'::setPaymentMethods('.$id.')');
2042
		if ($this->statut >= 0 || $this->element == 'societe')
2043
		{
2044
			// TODO uniformize field name
2045
			$fieldname = 'fk_mode_reglement';
2046
			if ($this->element == 'societe') $fieldname = 'mode_reglement';
2047
			if (get_class($this) == 'Fournisseur') $fieldname = 'mode_reglement_supplier';
2048
2049
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2050
			$sql .= ' SET '.$fieldname.' = '.(($id > 0 || $id == '0') ? $id : 'NULL');
2051
			$sql .= ' WHERE rowid='.$this->id;
2052
2053
			if ($this->db->query($sql))
2054
			{
2055
				$this->mode_reglement_id = $id;
2056
				// for supplier
2057
				if (get_class($this) == 'Fournisseur') $this->mode_reglement_supplier_id = $id;
2058
				return 1;
2059
			} else {
2060
				dol_syslog(get_class($this).'::setPaymentMethods Error '.$sql.' - '.$this->db->error());
2061
				$this->error = $this->db->error();
2062
				return -1;
2063
			}
2064
		} else {
2065
			dol_syslog(get_class($this).'::setPaymentMethods, status of the object is incompatible');
2066
			$this->error = 'Status of the object is incompatible '.$this->statut;
2067
			return -2;
2068
		}
2069
	}
2070
2071
	/**
2072
	 *  Change the multicurrency code
2073
	 *
2074
	 *  @param		string	$code	multicurrency code
2075
	 *  @return		int				>0 if OK, <0 if KO
2076
	 */
2077
	public function setMulticurrencyCode($code)
2078
	{
2079
		dol_syslog(get_class($this).'::setMulticurrencyCode('.$code.')');
2080
		if ($this->statut >= 0 || $this->element == 'societe')
2081
		{
2082
			$fieldname = 'multicurrency_code';
2083
2084
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2085
			$sql .= ' SET '.$fieldname." = '".$this->db->escape($code)."'";
2086
			$sql .= ' WHERE rowid='.$this->id;
2087
2088
			if ($this->db->query($sql))
2089
			{
2090
				$this->multicurrency_code = $code;
2091
2092
				list($fk_multicurrency, $rate) = MultiCurrency::getIdAndTxFromCode($this->db, $code);
2093
				if ($rate) $this->setMulticurrencyRate($rate, 2);
2094
2095
				return 1;
2096
			} else {
2097
				dol_syslog(get_class($this).'::setMulticurrencyCode Error '.$sql.' - '.$this->db->error());
2098
				$this->error = $this->db->error();
2099
				return -1;
2100
			}
2101
		} else {
2102
			dol_syslog(get_class($this).'::setMulticurrencyCode, status of the object is incompatible');
2103
			$this->error = 'Status of the object is incompatible '.$this->statut;
2104
			return -2;
2105
		}
2106
	}
2107
2108
	/**
2109
	 *  Change the multicurrency rate
2110
	 *
2111
	 *  @param		double	$rate	multicurrency rate
2112
	 *  @param		int		$mode	mode 1 : amounts in company currency will be recalculated, mode 2 : amounts in foreign currency will be recalculated
2113
	 *  @return		int				>0 if OK, <0 if KO
2114
	 */
2115
	public function setMulticurrencyRate($rate, $mode = 1)
2116
	{
2117
		dol_syslog(get_class($this).'::setMulticurrencyRate('.$rate.','.$mode.')');
2118
		if ($this->statut >= 0 || $this->element == 'societe')
2119
		{
2120
			$fieldname = 'multicurrency_tx';
2121
2122
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2123
			$sql .= ' SET '.$fieldname.' = '.$rate;
2124
			$sql .= ' WHERE rowid='.$this->id;
2125
2126
			if ($this->db->query($sql))
2127
			{
2128
				$this->multicurrency_tx = $rate;
2129
2130
				// Update line price
2131
				if (!empty($this->lines))
2132
				{
2133
					foreach ($this->lines as &$line)
2134
					{
2135
						// Amounts in company currency will be recalculated
2136
						if ($mode == 1) {
2137
							$line->subprice = 0;
2138
						}
2139
2140
						// Amounts in foreign currency will be recalculated
2141
						if ($mode == 2) {
2142
							$line->multicurrency_subprice = 0;
2143
						}
2144
2145
						switch ($this->element) {
2146
							case 'propal':
2147
								$this->updateline(
1 ignored issue
show
Bug introduced by
The method updateline() does not exist on CommonObject. Did you maybe mean updateLineUp()? ( Ignorable by Annotation )

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

2147
								$this->/** @scrutinizer ignore-call */ 
2148
               updateline(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
2148
									$line->id, $line->subprice, $line->qty, $line->remise_percent, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx,
2149
									($line->description ? $line->description : $line->desc), 'HT', $line->info_bits, $line->special_code, $line->fk_parent_line,
2150
									$line->skip_update_total, $line->fk_fournprice, $line->pa_ht, $line->label, $line->product_type, $line->date_start,
2151
									$line->date_end, $line->array_options, $line->fk_unit, $line->multicurrency_subprice
2152
								);
2153
								break;
2154
							case 'commande':
2155
								$this->updateline(
2156
									$line->id, ($line->description ? $line->description : $line->desc), $line->subprice, $line->qty, $line->remise_percent,
2157
									$line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, 'HT', $line->info_bits, $line->date_start, $line->date_end,
2158
									$line->product_type, $line->fk_parent_line, $line->skip_update_total, $line->fk_fournprice, $line->pa_ht, $line->label,
2159
									$line->special_code, $line->array_options, $line->fk_unit, $line->multicurrency_subprice
2160
								);
2161
								break;
2162
							case 'facture':
2163
								$this->updateline(
2164
									$line->id, ($line->description ? $line->description : $line->desc), $line->subprice, $line->qty, $line->remise_percent,
2165
									$line->date_start, $line->date_end, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, 'HT', $line->info_bits,
2166
									$line->product_type, $line->fk_parent_line, $line->skip_update_total, $line->fk_fournprice, $line->pa_ht, $line->label,
2167
									$line->special_code, $line->array_options, $line->situation_percent, $line->fk_unit, $line->multicurrency_subprice
2168
								);
2169
								break;
2170
							case 'supplier_proposal':
2171
								$this->updateline(
2172
									$line->id, $line->subprice, $line->qty, $line->remise_percent, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx,
2173
									($line->description ? $line->description : $line->desc), 'HT', $line->info_bits, $line->special_code, $line->fk_parent_line,
2174
									$line->skip_update_total, $line->fk_fournprice, $line->pa_ht, $line->label, $line->product_type, $line->array_options,
2175
									$line->ref_fourn, $line->multicurrency_subprice
2176
								);
2177
								break;
2178
							case 'order_supplier':
2179
								$this->updateline(
2180
									$line->id, ($line->description ? $line->description : $line->desc), $line->subprice, $line->qty, $line->remise_percent,
2181
									$line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, 'HT', $line->info_bits, $line->product_type, false,
2182
									$line->date_start, $line->date_end, $line->array_options, $line->fk_unit, $line->multicurrency_subprice
2183
								);
2184
								break;
2185
							case 'invoice_supplier':
2186
								$this->updateline(
2187
									$line->id, ($line->description ? $line->description : $line->desc), $line->subprice, $line->tva_tx, $line->localtax1_tx,
2188
									$line->localtax2_tx, $line->qty, 0, 'HT', $line->info_bits, $line->product_type, $line->remise_percent, false,
2189
									$line->date_start, $line->date_end, $line->array_options, $line->fk_unit, $line->multicurrency_subprice
2190
								);
2191
								break;
2192
							default:
2193
								dol_syslog(get_class($this).'::setMulticurrencyRate no updateline defined', LOG_DEBUG);
2194
								break;
2195
						}
2196
					}
2197
				}
2198
2199
				return 1;
2200
			} else {
2201
				dol_syslog(get_class($this).'::setMulticurrencyRate Error '.$sql.' - '.$this->db->error());
2202
				$this->error = $this->db->error();
2203
				return -1;
2204
			}
2205
		} else {
2206
			dol_syslog(get_class($this).'::setMulticurrencyRate, status of the object is incompatible');
2207
			$this->error = 'Status of the object is incompatible '.$this->statut;
2208
			return -2;
2209
		}
2210
	}
2211
2212
	/**
2213
	 *  Change the payments terms
2214
	 *
2215
	 *  @param		int		$id		Id of new payment terms
2216
	 *  @return		int				>0 if OK, <0 if KO
2217
	 */
2218
	public function setPaymentTerms($id)
2219
	{
2220
		dol_syslog(get_class($this).'::setPaymentTerms('.$id.')');
2221
		if ($this->statut >= 0 || $this->element == 'societe')
2222
		{
2223
			// TODO uniformize field name
2224
			$fieldname = 'fk_cond_reglement';
2225
			if ($this->element == 'societe') $fieldname = 'cond_reglement';
2226
			if (get_class($this) == 'Fournisseur') $fieldname = 'cond_reglement_supplier';
2227
2228
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2229
			$sql .= ' SET '.$fieldname.' = '.(($id > 0 || $id == '0') ? $id : 'NULL');
2230
			$sql .= ' WHERE rowid='.$this->id;
2231
2232
			if ($this->db->query($sql))
2233
			{
2234
				$this->cond_reglement_id = $id;
2235
				// for supplier
2236
				if (get_class($this) == 'Fournisseur') $this->cond_reglement_supplier_id = $id;
2237
				$this->cond_reglement = $id; // for compatibility
2238
				return 1;
2239
			} else {
2240
				dol_syslog(get_class($this).'::setPaymentTerms Error '.$sql.' - '.$this->db->error());
2241
				$this->error = $this->db->error();
2242
				return -1;
2243
			}
2244
		} else {
2245
			dol_syslog(get_class($this).'::setPaymentTerms, status of the object is incompatible');
2246
			$this->error = 'Status of the object is incompatible '.$this->statut;
2247
			return -2;
2248
		}
2249
	}
2250
2251
	/**
2252
	 *  Change the transport mode methods
2253
	 *
2254
	 *  @param		int		$id		Id of new payment method
2255
	 *  @return		int				>0 if OK, <0 if KO
2256
	 */
2257
	public function setTransportMode($id)
2258
	{
2259
		dol_syslog(get_class($this).'::setTransportMode('.$id.')');
2260
		if ($this->statut >= 0 || $this->element == 'societe')
2261
		{
2262
			$fieldname = 'fk_transport_mode';
2263
			if ($this->element == 'societe') $fieldname = 'transport_mode';
2264
			if (get_class($this) == 'Fournisseur') $fieldname = 'transport_mode_supplier';
2265
2266
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2267
			$sql .= ' SET '.$fieldname.' = '.(($id > 0 || $id == '0') ? $id : 'NULL');
2268
			$sql .= ' WHERE rowid='.$this->id;
2269
2270
			if ($this->db->query($sql))
2271
			{
2272
				$this->transport_mode_id = $id;
2273
				// for supplier
2274
				if (get_class($this) == 'Fournisseur') $this->transport_mode_supplier_id = $id;
2275
				return 1;
2276
			} else {
2277
				dol_syslog(get_class($this).'::setTransportMode Error '.$sql.' - '.$this->db->error());
2278
				$this->error = $this->db->error();
2279
				return -1;
2280
			}
2281
		} else {
2282
			dol_syslog(get_class($this).'::setTransportMode, status of the object is incompatible');
2283
			$this->error = 'Status of the object is incompatible '.$this->statut;
2284
			return -2;
2285
		}
2286
	}
2287
2288
	/**
2289
	 *  Change the retained warranty payments terms
2290
	 *
2291
	 *  @param		int		$id		Id of new payment terms
2292
	 *  @return		int				>0 if OK, <0 if KO
2293
	 */
2294
	public function setRetainedWarrantyPaymentTerms($id)
2295
	{
2296
		dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms('.$id.')');
2297
		if ($this->statut >= 0 || $this->element == 'societe')
2298
		{
2299
			$fieldname = 'retained_warranty_fk_cond_reglement';
2300
2301
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2302
			$sql .= ' SET '.$fieldname.' = '.$id;
2303
			$sql .= ' WHERE rowid='.$this->id;
2304
2305
			if ($this->db->query($sql))
2306
			{
2307
				$this->retained_warranty_fk_cond_reglement = $id;
2308
				return 1;
2309
			} else {
2310
				dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms Error '.$sql.' - '.$this->db->error());
2311
				$this->error = $this->db->error();
2312
				return -1;
2313
			}
2314
		} else {
2315
			dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms, status of the object is incompatible');
2316
			$this->error = 'Status of the object is incompatible '.$this->statut;
2317
			return -2;
2318
		}
2319
	}
2320
2321
	/**
2322
	 *	Define delivery address
2323
	 *  @deprecated
2324
	 *
2325
	 *	@param      int		$id		Address id
2326
	 *	@return     int				<0 si ko, >0 si ok
2327
	 */
2328
	public function setDeliveryAddress($id)
2329
	{
2330
		$fieldname = 'fk_delivery_address';
2331
		if ($this->element == 'delivery' || $this->element == 'shipping') $fieldname = 'fk_address';
2332
2333
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET ".$fieldname." = ".$id;
2334
		$sql .= " WHERE rowid = ".$this->id." AND fk_statut = 0";
2335
2336
		if ($this->db->query($sql))
2337
		{
2338
			$this->fk_delivery_address = $id;
1 ignored issue
show
Deprecated Code introduced by
The property CommonObject::$fk_delivery_address has been deprecated. ( Ignorable by Annotation )

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

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