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