dolibarr  18.0.0
commonobject.class.php
Go to the documentation of this file.
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-2022 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-2022 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-2023 Frédéric France <frederic.france@netlogic.fr>
16  * Copyright (C) 2018 Josep Lluís Amador <joseplluis@lliuretic.cat>
17  * Copyright (C) 2023 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
18  * Copyright (C) 2021 Grégory Blémand <gregory.blemand@atm-consulting.fr>
19  * Copyright (C) 2023 Lenin Rivas <lenin.rivas777@gmail.com>
20  *
21  * This program is free software; you can redistribute it and/or modify
22  * it under the terms of the GNU General Public License as published by
23  * the Free Software Foundation; either version 3 of the License, or
24  * (at your option) any later version.
25  *
26  * This program is distributed in the hope that it will be useful,
27  * but WITHOUT ANY WARRANTY; without even the implied warranty of
28  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29  * GNU General Public License for more details.
30  *
31  * You should have received a copy of the GNU General Public License
32  * along with this program. If not, see <https://www.gnu.org/licenses/>.
33  */
34 
45 abstract class CommonObject
46 {
47  const TRIGGER_PREFIX = ''; // to be overriden in child class implementations, i.e. 'BILL', 'TASK', 'PROPAL', etc.
48 
52  public $module;
53 
57  public $db;
58 
62  public $id;
63 
67  public $entity;
68 
73  public $error;
74 
78  public $errorhidden;
79 
83  public $errors = array();
84 
88  private $validateFieldsErrors = array();
89 
93  public $element;
94 
98  public $fk_element;
99 
104  public $element_for_permission;
105 
109  public $table_element;
110 
114  public $table_element_line = '';
115 
119  public $ismultientitymanaged;
120 
124  public $import_key;
125 
129  public $array_options = array();
130 
134  public $fields = array();
135 
139  public $array_languages = null; // Value is array() when load already tried
140 
144  public $contacts_ids;
145 
149  public $linked_objects;
150 
154  public $linkedObjectsIds;
155 
159  public $linkedObjects;
160 
164  private $linkedObjectsFullLoaded = array();
165 
169  public $oldcopy;
173  public $oldref;
174 
178  protected $table_ref_field = '';
179 
183  public $restrictiononfksoc = 0;
184 
185 
186  // Following vars are used by some objects only. We keep this property here in CommonObject to be able to provide common method using them.
187 
191  public $context = array();
192 
196  public $canvas;
197 
202  public $project;
203 
208  public $fk_project;
209 
215  public $projet;
216 
221  public $fk_projet;
222 
227  public $contact;
228 
233  public $contact_id;
234 
239  public $thirdparty;
240 
245  public $user;
246 
251  public $origin;
252 
257  public $origin_id;
258 
262  public $ref;
263 
267  public $ref_ext;
268 
272  public $ref_previous;
273 
277  public $ref_next;
278 
282  public $newref;
283 
288  public $statut;
289 
294  public $status;
295 
296 
301  public $country;
302 
307  public $country_id;
308 
313  public $country_code;
314 
319  public $state;
320 
325  public $state_id;
326 
331  public $state_code;
332 
337  public $region_id;
338 
343  public $region_code;
344 
349  public $region;
350 
351 
356  public $barcode_type;
357 
362  public $barcode_type_code;
363 
368  public $barcode_type_label;
369 
374  public $barcode_type_coder;
375 
380  public $mode_reglement_id;
381 
386  public $cond_reglement_id;
387 
391  public $demand_reason_id;
392 
397  public $transport_mode_id;
398 
404  public $cond_reglement;
405 
411  public $fk_delivery_address;
412 
417  public $shipping_method_id;
418 
423  public $shipping_method;
424 
428  public $multicurrency_code;
429 
433  public $multicurrency_tx;
434 
439  public $model_pdf;
440 
446  public $modelpdf;
447 
452  public $last_main_doc;
453 
459  public $fk_bank;
460 
465  public $fk_account;
466 
470  public $openid;
471 
476  public $note_public;
477 
482  public $note_private;
483 
488  public $note;
489 
494  public $total_ht;
495 
500  public $total_tva;
501 
506  public $total_localtax1;
507 
512  public $total_localtax2;
513 
518  public $total_ttc;
519 
523  public $lines;
524 
529  public $comments = array();
530 
534  public $name;
535 
539  public $lastname;
540 
544  public $firstname;
545 
549  public $civility_id;
550 
551  // Dates
555  public $date_creation;
556 
560  public $date_validation; // Date validation
561 
565  public $date_modification; // Date last change (tms field)
570  public $date_update;
571 
575  public $date_cloture; // Date closing (tms field)
576 
581  public $user_author;
586  public $user_creation;
590  public $user_creation_id;
591 
596  public $user_valid;
601  public $user_validation;
605  public $user_validation_id;
609  public $user_closing_id;
610 
615  public $user_modification;
619  public $user_modification_id;
620 
621 
622  public $next_prev_filter;
623 
627  public $specimen = 0;
628 
632  public $sendtoid;
633 
637  public $alreadypaid;
638 
639 
640  public $labelStatus;
641  protected $labelStatusShort;
642 
646  public $showphoto_on_popup;
647 
651  public $nb = array();
652 
656  public $output;
657 
661  public $extraparams = array();
662 
666  protected $childtables = array();
667 
673  protected $childtablesoncascade = array();
674 
675 
676  // No constructor as it is an abstract class
677 
678 
689  public static function isExistingObject($element, $id, $ref = '', $ref_ext = '')
690  {
691  global $db, $conf;
692 
693  $sql = "SELECT rowid, ref, ref_ext";
694  $sql .= " FROM ".$db->prefix().$element;
695  $sql .= " WHERE entity IN (".getEntity($element).")";
696 
697  if ($id > 0) {
698  $sql .= " AND rowid = ".((int) $id);
699  } elseif ($ref) {
700  $sql .= " AND ref = '".$db->escape($ref)."'";
701  } elseif ($ref_ext) {
702  $sql .= " AND ref_ext = '".$db->escape($ref_ext)."'";
703  } else {
704  $error = 'ErrorWrongParameters';
705  dol_print_error(get_class()."::isExistingObject ".$error, LOG_ERR);
706  return -1;
707  }
708  if ($ref || $ref_ext) { // Because the same ref can exists in 2 different entities, we force the current one in priority
709  $sql .= " AND entity = ".((int) $conf->entity);
710  }
711 
712  dol_syslog(get_class()."::isExistingObject", LOG_DEBUG);
713  $resql = $db->query($sql);
714  if ($resql) {
715  $num = $db->num_rows($resql);
716  if ($num > 0) {
717  return 1;
718  } else {
719  return 0;
720  }
721  }
722  return -1;
723  }
724 
731  public function setErrorsFromObject($object)
732  {
733  if (!empty($object->error)) {
734  $this->error = $object->error;
735  }
736  if (!empty($object->errors)) {
737  $this->errors = array_merge($this->errors, $object->errors);
738  }
739  }
740 
748  public function getTooltipContentArray($params)
749  {
750  return [];
751  }
752 
760  public function getTooltipContent($params)
761  {
762  global $action, $extrafields, $langs, $hookmanager;
763 
764  // If there is too much extrafields, we do not include them into tooltip
765  $MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP = getDolGlobalInt('MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP', 3);
766 
767  $datas = $this->getTooltipContentArray($params);
768  $count = 0;
769 
770  // Add extrafields
771  if (!empty($extrafields->attributes[$this->table_element]['label'])) {
772  foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
773  if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
774  continue;
775  }
776  if ($count >= abs($MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP)) {
777  $datas['more_extrafields'] = '<br>...';
778  break;
779  }
780  $enabled = 1;
781  if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
782  $enabled = dol_eval($extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
783  }
784  if ($enabled && isset($extrafields->attributes[$this->table_element]['list'][$key])) {
785  $enabled = dol_eval($extrafields->attributes[$this->table_element]['list'][$key], 1, 1, '2');
786  }
787  $perms = 1;
788  if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
789  $perms = dol_eval($extrafields->attributes[$this->table_element]['perms'][$key], 1, 1, '2');
790  }
791  if (empty($enabled)) {
792  continue; // 0 = Never visible field
793  }
794  if (abs($enabled) != 1 && abs($enabled) != 3 && abs($enabled) != 5 && abs($enabled) != 4) {
795  continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list <> 4 = not visible at the creation
796  }
797  if (empty($perms)) {
798  continue; // 0 = Not visible
799  }
800  if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
801  $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
802  }
803  $labelextra = $langs->trans((string) $extrafields->attributes[$this->table_element]['label'][$key]);
804  if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
805  $datas[$key]= '<br><b><u>'. $labelextra . '</u></b>';
806  } else {
807  $value = (empty($this->array_options['options_' . $key]) ? '' : $this->array_options['options_' . $key]);
808  $datas[$key]= '<br><b>'. $labelextra . ':</b> ' . $extrafields->showOutputField($key, $value, '', $this->table_element);
809  $count++;
810  }
811  }
812  }
813 
814  $hookmanager->initHooks(array($this->element . 'dao'));
815  $parameters = array(
816  'tooltipcontentarray' => &$datas,
817  'params' => $params,
818  );
819  // Note that $action and $object may have been modified by some hooks
820  $hookmanager->executeHooks('getTooltipContent', $parameters, $this, $action);
821 
822  //var_dump($datas);
823  $label = implode($datas);
824 
825  return $label;
826  }
827 
828 
834  public function errorsToString()
835  {
836  return $this->error.(is_array($this->errors) ? (($this->error != '' ? ', ' : '').join(', ', $this->errors)) : '');
837  }
838 
839 
846  public function getFormatedCustomerRef($objref)
847  {
848  global $hookmanager;
849 
850  $parameters = array('objref'=>$objref);
851  $action = '';
852  $reshook = $hookmanager->executeHooks('getFormatedCustomerRef', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
853  if ($reshook > 0) {
854  return $hookmanager->resArray['objref'];
855  }
856  return $objref.(isset($hookmanager->resArray['objref']) ? $hookmanager->resArray['objref'] : '');
857  }
858 
865  public function getFormatedSupplierRef($objref)
866  {
867  global $hookmanager;
868 
869  $parameters = array('objref'=>$objref);
870  $action = '';
871  $reshook = $hookmanager->executeHooks('getFormatedSupplierRef', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
872  if ($reshook > 0) {
873  return $hookmanager->resArray['objref'];
874  }
875  return $objref.(isset($hookmanager->resArray['objref']) ? $hookmanager->resArray['objref'] : '');
876  }
877 
887  public function getFullAddress($withcountry = 0, $sep = "\n", $withregion = 0, $extralangcode = '')
888  {
889  if ($withcountry && $this->country_id && (empty($this->country_code) || empty($this->country))) {
890  require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
891  $tmparray = getCountry($this->country_id, 'all');
892  $this->country_code = $tmparray['code'];
893  $this->country = $tmparray['label'];
894  }
895 
896  if ($withregion && $this->state_id && (empty($this->state_code) || empty($this->state) || empty($this->region) || empty($this->region_code))) {
897  require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
898  $tmparray = getState($this->state_id, 'all', 0, 1);
899  $this->state_code = $tmparray['code'];
900  $this->state = $tmparray['label'];
901  $this->region_code = $tmparray['region_code'];
902  $this->region = $tmparray['region'];
903  }
904 
905  return dol_format_address($this, $withcountry, $sep, '', 0, $extralangcode);
906  }
907 
908 
917  public function getLastMainDocLink($modulepart, $initsharekey = 0, $relativelink = 0)
918  {
919  global $user, $dolibarr_main_url_root;
920 
921  if (empty($this->last_main_doc)) {
922  return ''; // No way to known which document name to use
923  }
924 
925  include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
926  $ecmfile = new EcmFiles($this->db);
927  $result = $ecmfile->fetch(0, '', $this->last_main_doc);
928  if ($result < 0) {
929  $this->error = $ecmfile->error;
930  $this->errors = $ecmfile->errors;
931  return -1;
932  }
933 
934  if (empty($ecmfile->id)) {
935  // Add entry into index
936  if ($initsharekey) {
937  require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
938 
939  // TODO We can't, we dont' have full path of file, only last_main_doc and ->element, so we must first rebuild full path $destfull
940  /*
941  $ecmfile->filepath = $rel_dir;
942  $ecmfile->filename = $filename;
943  $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
944  $ecmfile->fullpath_orig = '';
945  $ecmfile->gen_or_uploaded = 'generated';
946  $ecmfile->description = ''; // indexed content
947  $ecmfile->keywords = ''; // keyword content
948  $ecmfile->share = getRandomPassword(true);
949  $result = $ecmfile->create($user);
950  if ($result < 0)
951  {
952  $this->error = $ecmfile->error;
953  $this->errors = $ecmfile->errors;
954  }
955  */
956  } else {
957  return '';
958  }
959  } elseif (empty($ecmfile->share)) {
960  // Add entry into index
961  if ($initsharekey) {
962  require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
963  $ecmfile->share = getRandomPassword(true);
964  $ecmfile->update($user);
965  } else {
966  return '';
967  }
968  }
969  // Define $urlwithroot
970  $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
971  // This is to use external domain name found into config file
972  //if (DOL_URL_ROOT && ! preg_match('/\/$/', $urlwithouturlroot) && ! preg_match('/^\//', DOL_URL_ROOT)) $urlwithroot=$urlwithouturlroot.'/'.DOL_URL_ROOT;
973  //else
974  $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT;
975  //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current
976 
977  $forcedownload = 0;
978 
979  $paramlink = '';
980  //if (!empty($modulepart)) $paramlink.=($paramlink?'&':'').'modulepart='.$modulepart; // For sharing with hash (so public files), modulepart is not required.
981  //if (!empty($ecmfile->entity)) $paramlink.='&entity='.$ecmfile->entity; // For sharing with hash (so public files), entity is not required.
982  //$paramlink.=($paramlink?'&':'').'file='.urlencode($filepath); // No need of name of file for public link, we will use the hash
983  if (!empty($ecmfile->share)) {
984  $paramlink .= ($paramlink ? '&' : '').'hashp='.$ecmfile->share; // Hash for public share
985  }
986  if ($forcedownload) {
987  $paramlink .= ($paramlink ? '&' : '').'attachment=1';
988  }
989 
990  if ($relativelink) {
991  $linktoreturn = 'document.php'.($paramlink ? '?'.$paramlink : '');
992  } else {
993  $linktoreturn = $urlwithroot.'/document.php'.($paramlink ? '?'.$paramlink : '');
994  }
995 
996  // Here $ecmfile->share is defined
997  return $linktoreturn;
998  }
999 
1000 
1001  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1011  public function add_contact($fk_socpeople, $type_contact, $source = 'external', $notrigger = 0)
1012  {
1013  // phpcs:enable
1014  global $user, $langs;
1015 
1016 
1017  dol_syslog(get_class($this)."::add_contact $fk_socpeople, $type_contact, $source, $notrigger");
1018 
1019  // Check parameters
1020  if ($fk_socpeople <= 0) {
1021  $langs->load("errors");
1022  $this->error = $langs->trans("ErrorWrongValueForParameterX", "1");
1023  dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1024  return -1;
1025  }
1026  if (!$type_contact) {
1027  $langs->load("errors");
1028  $this->error = $langs->trans("ErrorWrongValueForParameterX", "2");
1029  dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1030  return -2;
1031  }
1032 
1033  $id_type_contact = 0;
1034  if (is_numeric($type_contact)) {
1035  $id_type_contact = $type_contact;
1036  } else {
1037  // We look for id type_contact
1038  $sql = "SELECT tc.rowid";
1039  $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1040  $sql .= " WHERE tc.element='".$this->db->escape($this->element)."'";
1041  $sql .= " AND tc.source='".$this->db->escape($source)."'";
1042  $sql .= " AND tc.code='".$this->db->escape($type_contact)."' AND tc.active=1";
1043  //print $sql;
1044  $resql = $this->db->query($sql);
1045  if ($resql) {
1046  $obj = $this->db->fetch_object($resql);
1047  if ($obj) {
1048  $id_type_contact = $obj->rowid;
1049  }
1050  }
1051  }
1052 
1053  if ($id_type_contact == 0) {
1054  $this->error = 'CODE_NOT_VALID_FOR_THIS_ELEMENT';
1055  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");
1056  return -3;
1057  }
1058 
1059  $datecreate = dol_now();
1060 
1061  // Socpeople must have already been added by some trigger, then we have to check it to avoid DB_ERROR_RECORD_ALREADY_EXISTS error
1062  $TListeContacts = $this->liste_contact(-1, $source);
1063  $already_added = false;
1064  if (is_array($TListeContacts) && !empty($TListeContacts)) {
1065  foreach ($TListeContacts as $array_contact) {
1066  if ($array_contact['status'] == 4 && $array_contact['id'] == $fk_socpeople && $array_contact['fk_c_type_contact'] == $id_type_contact) {
1067  $already_added = true;
1068  break;
1069  }
1070  }
1071  }
1072 
1073  if (!$already_added) {
1074  $this->db->begin();
1075 
1076  // Insert into database
1077  $sql = "INSERT INTO ".$this->db->prefix()."element_contact";
1078  $sql .= " (element_id, fk_socpeople, datecreate, statut, fk_c_type_contact) ";
1079  $sql .= " VALUES (".$this->id.", ".((int) $fk_socpeople)." , ";
1080  $sql .= "'".$this->db->idate($datecreate)."'";
1081  $sql .= ", 4, ".((int) $id_type_contact);
1082  $sql .= ")";
1083 
1084  $resql = $this->db->query($sql);
1085  if ($resql) {
1086  if (!$notrigger) {
1087  $result = $this->call_trigger(strtoupper($this->element).'_ADD_CONTACT', $user);
1088  if ($result < 0) {
1089  $this->db->rollback();
1090  return -1;
1091  }
1092  }
1093 
1094  $this->db->commit();
1095  return 1;
1096  } else {
1097  if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1098  $this->error = $this->db->errno();
1099  $this->db->rollback();
1100  return -2;
1101  } else {
1102  $this->error = $this->db->lasterror();
1103  $this->db->rollback();
1104  return -1;
1105  }
1106  }
1107  } else {
1108  return 0;
1109  }
1110  }
1111 
1112  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1120  public function copy_linked_contact($objFrom, $source = 'internal')
1121  {
1122  // phpcs:enable
1123  $contacts = $objFrom->liste_contact(-1, $source);
1124  foreach ($contacts as $contact) {
1125  if ($this->add_contact($contact['id'], $contact['fk_c_type_contact'], $contact['source']) < 0) {
1126  return -1;
1127  }
1128  }
1129  return 1;
1130  }
1131 
1132  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1142  public function update_contact($rowid, $statut, $type_contact_id = 0, $fk_socpeople = 0)
1143  {
1144  // phpcs:enable
1145  // Insert into database
1146  $sql = "UPDATE ".$this->db->prefix()."element_contact set";
1147  $sql .= " statut = ".$statut;
1148  if ($type_contact_id) {
1149  $sql .= ", fk_c_type_contact = ".((int) $type_contact_id);
1150  }
1151  if ($fk_socpeople) {
1152  $sql .= ", fk_socpeople = ".((int) $fk_socpeople);
1153  }
1154  $sql .= " where rowid = ".((int) $rowid);
1155  $resql = $this->db->query($sql);
1156  if ($resql) {
1157  return 0;
1158  } else {
1159  $this->error = $this->db->lasterror();
1160  return -1;
1161  }
1162  }
1163 
1164  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1172  public function delete_contact($rowid, $notrigger = 0)
1173  {
1174  // phpcs:enable
1175  global $user;
1176 
1177 
1178  $this->db->begin();
1179 
1180  $sql = "DELETE FROM ".$this->db->prefix()."element_contact";
1181  $sql .= " WHERE rowid = ".((int) $rowid);
1182 
1183  dol_syslog(get_class($this)."::delete_contact", LOG_DEBUG);
1184  if ($this->db->query($sql)) {
1185  if (!$notrigger) {
1186  $result = $this->call_trigger(strtoupper($this->element).'_DELETE_CONTACT', $user);
1187  if ($result < 0) {
1188  $this->db->rollback();
1189  return -1;
1190  }
1191  }
1192 
1193  $this->db->commit();
1194  return 1;
1195  } else {
1196  $this->error = $this->db->lasterror();
1197  $this->db->rollback();
1198  return -1;
1199  }
1200  }
1201 
1202  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1210  public function delete_linked_contact($source = '', $code = '')
1211  {
1212  // phpcs:enable
1213  $listId = '';
1214  $temp = array();
1215  $typeContact = $this->liste_type_contact($source, '', 0, 0, $code);
1216 
1217  if (!empty($typeContact)) {
1218  foreach ($typeContact as $key => $value) {
1219  array_push($temp, $key);
1220  }
1221  $listId = implode(",", $temp);
1222  }
1223 
1224  // If $listId is empty, we have not criteria on fk_c_type_contact so we will delete record on element_id for
1225  // any type or record instead of only the ones of the current object. So we do nothing in such a case.
1226  if (empty($listId)) {
1227  return 0;
1228  }
1229 
1230  $sql = "DELETE FROM ".$this->db->prefix()."element_contact";
1231  $sql .= " WHERE element_id = ".((int) $this->id);
1232  $sql .= " AND fk_c_type_contact IN (".$this->db->sanitize($listId).")";
1233 
1234  dol_syslog(get_class($this)."::delete_linked_contact", LOG_DEBUG);
1235  if ($this->db->query($sql)) {
1236  return 1;
1237  } else {
1238  $this->error = $this->db->lasterror();
1239  return -1;
1240  }
1241  }
1242 
1243  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1255  public function liste_contact($statusoflink = -1, $source = 'external', $list = 0, $code = '', $status = -1, $arrayoftcids = array())
1256  {
1257  // phpcs:enable
1258  global $langs;
1259 
1260  $tab = array();
1261 
1262  $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
1263  if ($source == 'internal') {
1264  $sql .= ", '-1' as socid, t.statut as statuscontact, t.login, t.photo";
1265  }
1266  if ($source == 'external' || $source == 'thirdparty') {
1267  $sql .= ", t.fk_soc as socid, t.statut as statuscontact";
1268  }
1269  $sql .= ", t.civility as civility, t.lastname as lastname, t.firstname, t.email";
1270  $sql .= ", tc.source, tc.element, tc.code, tc.libelle";
1271  $sql .= " FROM ".$this->db->prefix()."c_type_contact tc,";
1272  $sql .= " ".$this->db->prefix()."element_contact ec";
1273  if ($source == 'internal') { // internal contact (user)
1274  $sql .= " LEFT JOIN ".$this->db->prefix()."user t on ec.fk_socpeople = t.rowid";
1275  }
1276  if ($source == 'external' || $source == 'thirdparty') { // external contact (socpeople)
1277  $sql .= " LEFT JOIN ".$this->db->prefix()."socpeople t on ec.fk_socpeople = t.rowid";
1278  }
1279  $sql .= " WHERE ec.element_id = ".((int) $this->id);
1280  $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1281  $sql .= " AND tc.element = '".$this->db->escape($this->element)."'";
1282  if ($code) {
1283  $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1284  }
1285  if ($source == 'internal') {
1286  $sql .= " AND tc.source = 'internal'";
1287  if ($status >= 0) {
1288  $sql .= " AND t.statut = ".((int) $status);
1289  }
1290  }
1291  if ($source == 'external' || $source == 'thirdparty') {
1292  $sql .= " AND tc.source = 'external'";
1293  if ($status >= 0) {
1294  $sql .= " AND t.statut = ".((int) $status); // t is llx_socpeople
1295  }
1296  }
1297  $sql .= " AND tc.active = 1";
1298  if ($statusoflink >= 0) {
1299  $sql .= " AND ec.statut = ".((int) $statusoflink);
1300  }
1301  $sql .= " ORDER BY t.lastname ASC";
1302 
1303  dol_syslog(get_class($this)."::liste_contact", LOG_DEBUG);
1304  $resql = $this->db->query($sql);
1305  if ($resql) {
1306  $num = $this->db->num_rows($resql);
1307  $i = 0;
1308  while ($i < $num) {
1309  $obj = $this->db->fetch_object($resql);
1310 
1311  if (!$list) {
1312  $transkey = "TypeContact_".$obj->element."_".$obj->source."_".$obj->code;
1313  $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->libelle);
1314  $tab[$i] = array(
1315  'parentId' => $this->id,
1316  'source' => $obj->source,
1317  'socid' => $obj->socid,
1318  'id' => $obj->id,
1319  'nom' => $obj->lastname, // For backward compatibility
1320  'civility' => $obj->civility,
1321  'lastname' => $obj->lastname,
1322  'firstname' => $obj->firstname,
1323  'email'=>$obj->email,
1324  'login'=> (empty($obj->login) ? '' : $obj->login),
1325  'photo' => (empty($obj->photo) ? '' : $obj->photo),
1326  'statuscontact' => $obj->statuscontact,
1327  'rowid' => $obj->rowid,
1328  'code' => $obj->code,
1329  'libelle' => $libelle_type,
1330  'status' => $obj->statuslink,
1331  'fk_c_type_contact' => $obj->fk_c_type_contact
1332  );
1333  } else {
1334  $tab[$i] = $obj->id;
1335  }
1336 
1337  $i++;
1338  }
1339 
1340  return $tab;
1341  } else {
1342  $this->error = $this->db->lasterror();
1343  dol_print_error($this->db);
1344  return -1;
1345  }
1346  }
1347 
1348 
1355  public function swapContactStatus($rowid)
1356  {
1357  $sql = "SELECT ec.datecreate, ec.statut, ec.fk_socpeople, ec.fk_c_type_contact,";
1358  $sql .= " tc.code, tc.libelle";
1359  $sql .= " FROM (".$this->db->prefix()."element_contact as ec, ".$this->db->prefix()."c_type_contact as tc)";
1360  $sql .= " WHERE ec.rowid =".((int) $rowid);
1361  $sql .= " AND ec.fk_c_type_contact=tc.rowid";
1362  $sql .= " AND tc.element = '".$this->db->escape($this->element)."'";
1363 
1364  dol_syslog(get_class($this)."::swapContactStatus", LOG_DEBUG);
1365  $resql = $this->db->query($sql);
1366  if ($resql) {
1367  $obj = $this->db->fetch_object($resql);
1368  $newstatut = ($obj->statut == 4) ? 5 : 4;
1369  $result = $this->update_contact($rowid, $newstatut);
1370  $this->db->free($resql);
1371  return $result;
1372  } else {
1373  $this->error = $this->db->error();
1374  dol_print_error($this->db);
1375  return -1;
1376  }
1377  }
1378 
1379  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1390  public function liste_type_contact($source = 'internal', $order = 'position', $option = 0, $activeonly = 0, $code = '')
1391  {
1392  // phpcs:enable
1393  global $langs;
1394 
1395  if (empty($order)) {
1396  $order = 'position';
1397  }
1398  if ($order == 'position') {
1399  $order .= ',code';
1400  }
1401 
1402  $tab = array();
1403  $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle, tc.position";
1404  $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1405  $sql .= " WHERE tc.element='".$this->db->escape($this->element)."'";
1406  if ($activeonly == 1) {
1407  $sql .= " AND tc.active=1"; // only the active types
1408  }
1409  if (!empty($source) && $source != 'all') {
1410  $sql .= " AND tc.source='".$this->db->escape($source)."'";
1411  }
1412  if (!empty($code)) {
1413  $sql .= " AND tc.code='".$this->db->escape($code)."'";
1414  }
1415  $sql .= $this->db->order($order, 'ASC');
1416 
1417  //print "sql=".$sql;
1418  $resql = $this->db->query($sql);
1419  if ($resql) {
1420  $num = $this->db->num_rows($resql);
1421  $i = 0;
1422  while ($i < $num) {
1423  $obj = $this->db->fetch_object($resql);
1424 
1425  $transkey = "TypeContact_".$this->element."_".$source."_".$obj->code;
1426  $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->libelle);
1427  if (empty($option)) {
1428  $tab[$obj->rowid] = $libelle_type;
1429  } else {
1430  $tab[$obj->code] = $libelle_type;
1431  }
1432  $i++;
1433  }
1434  return $tab;
1435  } else {
1436  $this->error = $this->db->lasterror();
1437  //dol_print_error($this->db);
1438  return null;
1439  }
1440  }
1441 
1453  public function listeTypeContacts($source = 'internal', $option = 0, $activeonly = 0, $code = '', $element = '', $excludeelement = '')
1454  {
1455  global $langs, $conf;
1456 
1457  $langs->loadLangs(array('bills', 'contracts', 'interventions', 'orders', 'projects', 'propal', 'ticket', 'agenda'));
1458 
1459  $tab = array();
1460 
1461  $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle, tc.position, tc.element";
1462  $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1463 
1464  $sqlWhere = array();
1465  if (!empty($element)) {
1466  $sqlWhere[] = " tc.element='".$this->db->escape($element)."'";
1467  }
1468  if (!empty($excludeelement)) {
1469  $sqlWhere[] = " tc.element <> '".$this->db->escape($excludeelement)."'";
1470  }
1471 
1472  if ($activeonly == 1) {
1473  $sqlWhere[] = " tc.active=1"; // only the active types
1474  }
1475 
1476  if (!empty($source) && $source != 'all') {
1477  $sqlWhere[] = " tc.source='".$this->db->escape($source)."'";
1478  }
1479 
1480  if (!empty($code)) {
1481  $sqlWhere[] = " tc.code='".$this->db->escape($code)."'";
1482  }
1483 
1484  if (count($sqlWhere) > 0) {
1485  $sql .= " WHERE ".implode(' AND ', $sqlWhere);
1486  }
1487 
1488  $sql .= $this->db->order('tc.element, tc.position', 'ASC');
1489 
1490  dol_syslog(__METHOD__, LOG_DEBUG);
1491  $resql = $this->db->query($sql);
1492  if ($resql) {
1493  $num = $this->db->num_rows($resql);
1494  if ($num > 0) {
1495  $langs->loadLangs(array("propal", "orders", "bills", "suppliers", "contracts", "supplier_proposal"));
1496 
1497  while ($obj = $this->db->fetch_object($resql)) {
1498  $modulename = $obj->element;
1499  if (strpos($obj->element, 'project') !== false) {
1500  $modulename = 'projet';
1501  } elseif ($obj->element == 'contrat') {
1502  $element = 'contract';
1503  } elseif ($obj->element == 'action') {
1504  $modulename = 'agenda';
1505  } elseif (strpos($obj->element, 'supplier') !== false && $obj->element != 'supplier_proposal') {
1506  $modulename = 'fournisseur';
1507  } elseif (strpos($obj->element, 'supplier') !== false && $obj->element != 'supplier_proposal') {
1508  $modulename = 'fournisseur';
1509  }
1510  if (!empty($conf->{$modulename}->enabled)) {
1511  $libelle_element = $langs->trans('ContactDefault_'.$obj->element);
1512  $tmpelement = $obj->element;
1513  $transkey = "TypeContact_".$tmpelement."_".$source."_".$obj->code;
1514  $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->libelle);
1515  if (empty($option)) {
1516  $tab[$obj->rowid] = $libelle_element.' - '.$libelle_type;
1517  } else {
1518  $tab[$obj->rowid] = $libelle_element.' - '.$libelle_type;
1519  }
1520  }
1521  }
1522  }
1523  return $tab;
1524  } else {
1525  $this->error = $this->db->lasterror();
1526  return null;
1527  }
1528  }
1529 
1541  public function getIdContact($source, $code, $status = 0)
1542  {
1543  global $conf;
1544 
1545  $result = array();
1546  $i = 0;
1547  //cas particulier pour les expeditions
1548  if ($this->element == 'shipping' && $this->origin_id != 0) {
1549  $id = $this->origin_id;
1550  $element = 'commande';
1551  } elseif ($this->element == 'reception' && $this->origin_id != 0) {
1552  $id = $this->origin_id;
1553  $element = 'order_supplier';
1554  } else {
1555  $id = $this->id;
1556  $element = $this->element;
1557  }
1558 
1559  $sql = "SELECT ec.fk_socpeople";
1560  $sql .= " FROM ".$this->db->prefix()."element_contact as ec,";
1561  if ($source == 'internal') {
1562  $sql .= " ".$this->db->prefix()."user as c,";
1563  }
1564  if ($source == 'external') {
1565  $sql .= " ".$this->db->prefix()."socpeople as c,";
1566  }
1567  $sql .= " ".$this->db->prefix()."c_type_contact as tc";
1568  $sql .= " WHERE ec.element_id = ".((int) $id);
1569  $sql .= " AND ec.fk_socpeople = c.rowid";
1570  if ($source == 'internal') {
1571  $sql .= " AND c.entity IN (".getEntity('user').")";
1572  }
1573  if ($source == 'external') {
1574  $sql .= " AND c.entity IN (".getEntity('societe').")";
1575  }
1576  $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1577  $sql .= " AND tc.element = '".$this->db->escape($element)."'";
1578  $sql .= " AND tc.source = '".$this->db->escape($source)."'";
1579  if ($code) {
1580  $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1581  }
1582  $sql .= " AND tc.active = 1";
1583  if ($status) {
1584  $sql .= " AND ec.statut = ".((int) $status);
1585  }
1586 
1587  dol_syslog(get_class($this)."::getIdContact", LOG_DEBUG);
1588  $resql = $this->db->query($sql);
1589  if ($resql) {
1590  while ($obj = $this->db->fetch_object($resql)) {
1591  $result[$i] = $obj->fk_socpeople;
1592  $i++;
1593  }
1594  } else {
1595  $this->error = $this->db->error();
1596  return null;
1597  }
1598 
1599  return $result;
1600  }
1601 
1602  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1609  public function fetch_contact($contactid = null)
1610  {
1611  // phpcs:enable
1612  if (empty($contactid)) {
1613  $contactid = $this->contact_id;
1614  }
1615 
1616  if (empty($contactid)) {
1617  return 0;
1618  }
1619 
1620  require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
1621  $contact = new Contact($this->db);
1622  $result = $contact->fetch($contactid);
1623  $this->contact = $contact;
1624  return $result;
1625  }
1626 
1627  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1634  public function fetch_thirdparty($force_thirdparty_id = 0)
1635  {
1636  // phpcs:enable
1637  global $conf;
1638 
1639  if (empty($this->socid) && empty($this->fk_soc) && empty($force_thirdparty_id)) {
1640  return 0;
1641  }
1642 
1643  require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
1644 
1645  $idtofetch = isset($this->socid) ? $this->socid : (isset($this->fk_soc) ? $this->fk_soc : 0);
1646  if ($force_thirdparty_id) {
1647  $idtofetch = $force_thirdparty_id;
1648  }
1649 
1650  if ($idtofetch) {
1651  $thirdparty = new Societe($this->db);
1652  $result = $thirdparty->fetch($idtofetch);
1653  if ($result<0) {
1654  $this->errors=array_merge($this->errors, $thirdparty->errors);
1655  }
1656  $this->thirdparty = $thirdparty;
1657 
1658  // Use first price level if level not defined for third party
1659  if (!empty($conf->global->PRODUIT_MULTIPRICES) && empty($this->thirdparty->price_level)) {
1660  $this->thirdparty->price_level = 1;
1661  }
1662 
1663  return $result;
1664  } else {
1665  return -1;
1666  }
1667  }
1668 
1669 
1677  public function fetchOneLike($ref)
1678  {
1679  if (!$this->table_ref_field) {
1680  return 0;
1681  }
1682 
1683  $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element." WHERE ".$this->table_ref_field." LIKE '".$this->db->escape($ref)."' LIMIT 1";
1684 
1685  $query = $this->db->query($sql);
1686 
1687  if (!$this->db->num_rows($query)) {
1688  return 0;
1689  }
1690 
1691  $result = $this->db->fetch_object($query);
1692 
1693  return $this->fetch($result->rowid);
1694  }
1695 
1696  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1704  public function fetch_barcode()
1705  {
1706  // phpcs:enable
1707  global $conf;
1708 
1709  dol_syslog(get_class($this).'::fetch_barcode this->element='.$this->element.' this->barcode_type='.$this->barcode_type);
1710 
1711  $idtype = $this->barcode_type;
1712  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
1713  if ($this->element == 'product' && !empty($conf->global->PRODUIT_DEFAULT_BARCODE_TYPE)) {
1714  $idtype = $conf->global->PRODUIT_DEFAULT_BARCODE_TYPE;
1715  } elseif ($this->element == 'societe') {
1716  $idtype = $conf->global->GENBARCODE_BARCODETYPE_THIRDPARTY;
1717  } else {
1718  dol_syslog('Call fetch_barcode with barcode_type not defined and cant be guessed', LOG_WARNING);
1719  }
1720  }
1721 
1722  if ($idtype > 0) {
1723  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
1724  $sql = "SELECT rowid, code, libelle as label, coder";
1725  $sql .= " FROM ".$this->db->prefix()."c_barcode_type";
1726  $sql .= " WHERE rowid = ".((int) $idtype);
1727  dol_syslog(get_class($this).'::fetch_barcode', LOG_DEBUG);
1728  $resql = $this->db->query($sql);
1729  if ($resql) {
1730  $obj = $this->db->fetch_object($resql);
1731  $this->barcode_type = $obj->rowid;
1732  $this->barcode_type_code = $obj->code;
1733  $this->barcode_type_label = $obj->label;
1734  $this->barcode_type_coder = $obj->coder;
1735  return 1;
1736  } else {
1737  dol_print_error($this->db);
1738  return -1;
1739  }
1740  }
1741  }
1742  return 0;
1743  }
1744 
1745  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1751  public function fetch_project()
1752  {
1753  // phpcs:enable
1754  return $this->fetch_projet();
1755  }
1756 
1757  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1763  public function fetch_projet()
1764  {
1765  // phpcs:enable
1766  include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
1767 
1768  if (empty($this->fk_project) && !empty($this->fk_projet)) {
1769  $this->fk_project = $this->fk_projet; // For backward compatibility
1770  }
1771  if (empty($this->fk_project)) {
1772  return 0;
1773  }
1774 
1775  $project = new Project($this->db);
1776  $result = $project->fetch($this->fk_project);
1777 
1778  $this->projet = $project; // deprecated
1779  $this->project = $project;
1780  return $result;
1781  }
1782 
1783  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1789  public function fetch_product()
1790  {
1791  // phpcs:enable
1792  include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
1793 
1794  if (empty($this->fk_product)) {
1795  return 0;
1796  }
1797 
1798  $product = new Product($this->db);
1799  $result = $product->fetch($this->fk_product);
1800 
1801  $this->product = $product;
1802  return $result;
1803  }
1804 
1805  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1812  public function fetch_user($userid)
1813  {
1814  // phpcs:enable
1815  $user = new User($this->db);
1816  $result = $user->fetch($userid);
1817  $this->user = $user;
1818  return $result;
1819  }
1820 
1821  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1827  public function fetch_origin()
1828  {
1829  // phpcs:enable
1830  if ($this->origin == 'shipping') {
1831  $this->origin = 'expedition';
1832  }
1833  if ($this->origin == 'delivery') {
1834  $this->origin = 'livraison';
1835  }
1836  if ($this->origin == 'order_supplier') {
1837  $this->origin = 'commandeFournisseur';
1838  }
1839 
1840  $origin = $this->origin;
1841 
1842  $classname = ucfirst($origin);
1843  $this->$origin = new $classname($this->db);
1844  $this->$origin->fetch($this->origin_id);
1845  }
1846 
1856  public function fetchObjectFrom($table, $field, $key, $element = null)
1857  {
1858  global $conf;
1859 
1860  $result = false;
1861 
1862  $sql = "SELECT rowid FROM ".$this->db->prefix().$table;
1863  $sql .= " WHERE ".$field." = '".$this->db->escape($key)."'";
1864  if (!empty($element)) {
1865  $sql .= " AND entity IN (".getEntity($element).")";
1866  } else {
1867  $sql .= " AND entity = ".((int) $conf->entity);
1868  }
1869 
1870  dol_syslog(get_class($this).'::fetchObjectFrom', LOG_DEBUG);
1871  $resql = $this->db->query($sql);
1872  if ($resql) {
1873  $row = $this->db->fetch_row($resql);
1874  // Test for avoid error -1
1875  if ($row[0] > 0) {
1876  $result = $this->fetch($row[0]);
1877  }
1878  }
1879 
1880  return $result;
1881  }
1882 
1891  public function getValueFrom($table, $id, $field)
1892  {
1893  $result = false;
1894  if (!empty($id) && !empty($field) && !empty($table)) {
1895  $sql = "SELECT ".$field." FROM ".$this->db->prefix().$table;
1896  $sql .= " WHERE rowid = ".((int) $id);
1897 
1898  dol_syslog(get_class($this).'::getValueFrom', LOG_DEBUG);
1899  $resql = $this->db->query($sql);
1900  if ($resql) {
1901  $row = $this->db->fetch_row($resql);
1902  $result = $row[0];
1903  }
1904  }
1905  return $result;
1906  }
1907 
1924  public function setValueFrom($field, $value, $table = '', $id = null, $format = '', $id_field = '', $fuser = null, $trigkey = '', $fk_user_field = 'fk_user_modif')
1925  {
1926  global $user, $langs, $conf;
1927 
1928  if (empty($table)) {
1929  $table = $this->table_element;
1930  }
1931  if (empty($id)) {
1932  $id = $this->id;
1933  }
1934  if (empty($format)) {
1935  $format = 'text';
1936  }
1937  if (empty($id_field)) {
1938  $id_field = 'rowid';
1939  }
1940 
1941  // Special case
1942  if ($table == 'product' && $field == 'note_private') {
1943  $field = 'note';
1944  }
1945  if (in_array($table, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
1946  $fk_user_field = 'fk_user_mod';
1947  }
1948 
1949  if ($trigkey) {
1950  $oldvalue = null;
1951 
1952  $sql = "SELECT " . $field;
1953  $sql .= " FROM " . MAIN_DB_PREFIX . $table;
1954  $sql .= " WHERE " . $id_field . " = " . ((int) $id);
1955 
1956  $resql = $this->db->query($sql);
1957  if ($resql) {
1958  if ($obj = $this->db->fetch_object($resql)) {
1959  if ($format == 'date') {
1960  $oldvalue = $this->db->jdate($obj->$field);
1961  } else {
1962  $oldvalue = $obj->$field;
1963  }
1964  }
1965  } else {
1966  $this->error = $this->db->lasterror();
1967  return -1;
1968  }
1969  }
1970 
1971  $error = 0;
1972 
1973  dol_syslog(__METHOD__, LOG_DEBUG);
1974 
1975  $this->db->begin();
1976 
1977  $sql = "UPDATE ".$this->db->prefix().$table." SET ";
1978 
1979  if ($format == 'text') {
1980  $sql .= $field." = '".$this->db->escape($value)."'";
1981  } elseif ($format == 'int') {
1982  $sql .= $field." = ".((int) $value);
1983  } elseif ($format == 'date') {
1984  $sql .= $field." = ".($value ? "'".$this->db->idate($value)."'" : "null");
1985  } elseif ($format == 'dategmt') {
1986  $sql .= $field." = ".($value ? "'".$this->db->idate($value, 'gmt')."'" : "null");
1987  }
1988 
1989  if ($fk_user_field) {
1990  if (!empty($fuser) && is_object($fuser)) {
1991  $sql .= ", ".$fk_user_field." = ".((int) $fuser->id);
1992  } elseif (empty($fuser) || $fuser != 'none') {
1993  $sql .= ", ".$fk_user_field." = ".((int) $user->id);
1994  }
1995  }
1996 
1997  $sql .= " WHERE ".$id_field." = ".((int) $id);
1998 
1999  $resql = $this->db->query($sql);
2000  if ($resql) {
2001  if ($trigkey) {
2002  // call trigger with updated object values
2003  if (method_exists($this, 'fetch')) {
2004  $result = $this->fetch($id);
2005  } else {
2006  $result = $this->fetchCommon($id);
2007  }
2008  $this->oldcopy = clone $this;
2009  if (property_exists($this->oldcopy, $field)) {
2010  $this->oldcopy->$field = $oldvalue;
2011  }
2012 
2013  if ($result >= 0) {
2014  $result = $this->call_trigger($trigkey, (!empty($fuser) && is_object($fuser)) ? $fuser : $user); // This may set this->errors
2015  }
2016  if ($result < 0) {
2017  $error++;
2018  }
2019  }
2020 
2021  if (!$error) {
2022  if (property_exists($this, $field)) {
2023  $this->$field = $value;
2024  }
2025  $this->db->commit();
2026  return 1;
2027  } else {
2028  $this->db->rollback();
2029  return -2;
2030  }
2031  } else {
2032  if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
2033  $this->error = 'DB_ERROR_RECORD_ALREADY_EXISTS';
2034  } else {
2035  $this->error = $this->db->lasterror();
2036  }
2037  $this->db->rollback();
2038  return -1;
2039  }
2040  }
2041 
2042  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2051  public function load_previous_next_ref($filter, $fieldid, $nodbprefix = 0)
2052  {
2053  // phpcs:enable
2054  global $conf, $user;
2055 
2056  if (!$this->table_element) {
2057  dol_print_error('', get_class($this)."::load_previous_next_ref was called on objet with property table_element not defined");
2058  return -1;
2059  }
2060  if ($fieldid == 'none') {
2061  return 1;
2062  }
2063 
2064  // For backward compatibility
2065  if ($this->table_element == 'facture_rec' && $fieldid == 'title') {
2066  $fieldid = 'titre';
2067  }
2068 
2069  // Security on socid
2070  $socid = 0;
2071  if ($user->socid > 0) {
2072  $socid = $user->socid;
2073  }
2074 
2075  // this->ismultientitymanaged contains
2076  // 0=No test on entity, 1=Test with field entity, 'field@table'=Test with link by field@table
2077  $aliastablesociete = 's';
2078  if ($this->element == 'societe') {
2079  $aliastablesociete = 'te'; // te as table_element
2080  }
2081  $restrictiononfksoc = empty($this->restrictiononfksoc) ? 0 : $this->restrictiononfksoc;
2082  $sql = "SELECT MAX(te.".$fieldid.")";
2083  $sql .= " FROM ".(empty($nodbprefix) ?$this->db->prefix():'').$this->table_element." as te";
2084  if ($this->element == 'user' && !empty($conf->global->MULTICOMPANY_TRANSVERSE_MODE)) {
2085  $sql .= ",".$this->db->prefix()."usergroup_user as ug";
2086  }
2087  if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2088  $tmparray = explode('@', $this->ismultientitymanaged);
2089  $sql .= ", ".$this->db->prefix().$tmparray[1]." as ".($tmparray[1] == 'societe' ? 's' : 'parenttable'); // If we need to link to this table to limit select to entity
2090  } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && empty($user->rights->societe->client->voir) && !$socid) {
2091  $sql .= ", ".$this->db->prefix()."societe as s"; // If we need to link to societe to limit select to socid
2092  } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && empty($user->rights->societe->client->voir) && !$socid) {
2093  $sql .= " LEFT JOIN ".$this->db->prefix()."societe as s ON te.fk_soc = s.rowid"; // If we need to link to societe to limit select to socid
2094  }
2095  if ($restrictiononfksoc && empty($user->rights->societe->client->voir) && !$socid) {
2096  $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
2097  }
2098  $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)
2099  if ($restrictiononfksoc == 1 && empty($user->rights->societe->client->voir) && !$socid) {
2100  $sql .= " AND sc.fk_user = ".((int) $user->id);
2101  }
2102  if ($restrictiononfksoc == 2 && empty($user->rights->societe->client->voir) && !$socid) {
2103  $sql .= " AND (sc.fk_user = ".((int) $user->id).' OR te.fk_soc IS NULL)';
2104  }
2105  if (!empty($filter)) {
2106  if (!preg_match('/^\s*AND/i', $filter)) {
2107  $sql .= " AND "; // For backward compatibility
2108  }
2109  $sql .= $filter;
2110  }
2111  if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2112  $tmparray = explode('@', $this->ismultientitymanaged);
2113  $sql .= " AND te.".$tmparray[0]." = ".($tmparray[1] == "societe" ? "s" : "parenttable").".rowid"; // If we need to link to this table to limit select to entity
2114  } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && empty($user->rights->societe->client->voir) && !$socid) {
2115  $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2116  }
2117  if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2118  if ($this->element == 'user' && !empty($conf->global->MULTICOMPANY_TRANSVERSE_MODE)) {
2119  if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2120  $sql .= " AND te.entity IS NOT NULL"; // Show all users
2121  } else {
2122  $sql .= " AND ug.fk_user = te.rowid";
2123  $sql .= " AND ug.entity IN (".getEntity('usergroup').")";
2124  }
2125  } else {
2126  $sql .= ' AND te.entity IN ('.getEntity($this->element).')';
2127  }
2128  }
2129  if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2130  $tmparray = explode('@', $this->ismultientitymanaged);
2131  $sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
2132  }
2133  if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2134  $sql .= ' AND te.fk_soc = '.((int) $socid);
2135  }
2136  if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2137  $sql .= ' AND (te.fk_soc = '.((int) $socid).' OR te.fk_soc IS NULL)';
2138  }
2139  if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2140  $sql .= ' AND te.rowid = '.((int) $socid);
2141  }
2142  //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2143 
2144  $result = $this->db->query($sql);
2145  if (!$result) {
2146  $this->error = $this->db->lasterror();
2147  return -1;
2148  }
2149  $row = $this->db->fetch_row($result);
2150  $this->ref_previous = $row[0];
2151 
2152  $sql = "SELECT MIN(te.".$fieldid.")";
2153  $sql .= " FROM ".(empty($nodbprefix) ?$this->db->prefix():'').$this->table_element." as te";
2154  if ($this->element == 'user' && !empty($conf->global->MULTICOMPANY_TRANSVERSE_MODE)) {
2155  $sql .= ",".$this->db->prefix()."usergroup_user as ug";
2156  }
2157  if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2158  $tmparray = explode('@', $this->ismultientitymanaged);
2159  $sql .= ", ".$this->db->prefix().$tmparray[1]." as ".($tmparray[1] == 'societe' ? 's' : 'parenttable'); // If we need to link to this table to limit select to entity
2160  } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && empty($user->rights->societe->client->voir) && !$socid) {
2161  $sql .= ", ".$this->db->prefix()."societe as s"; // If we need to link to societe to limit select to socid
2162  } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && empty($user->rights->societe->client->voir) && !$socid) {
2163  $sql .= " LEFT JOIN ".$this->db->prefix()."societe as s ON te.fk_soc = s.rowid"; // If we need to link to societe to limit select to socid
2164  }
2165  if ($restrictiononfksoc && empty($user->rights->societe->client->voir) && !$socid) {
2166  $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
2167  }
2168  $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)
2169  if ($restrictiononfksoc == 1 && empty($user->rights->societe->client->voir) && !$socid) {
2170  $sql .= " AND sc.fk_user = ".((int) $user->id);
2171  }
2172  if ($restrictiononfksoc == 2 && empty($user->rights->societe->client->voir) && !$socid) {
2173  $sql .= " AND (sc.fk_user = ".((int) $user->id).' OR te.fk_soc IS NULL)';
2174  }
2175  if (!empty($filter)) {
2176  if (!preg_match('/^\s*AND/i', $filter)) {
2177  $sql .= " AND "; // For backward compatibility
2178  }
2179  $sql .= $filter;
2180  }
2181  if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2182  $tmparray = explode('@', $this->ismultientitymanaged);
2183  $sql .= " AND te.".$tmparray[0]." = ".($tmparray[1] == "societe" ? "s" : "parenttable").".rowid"; // If we need to link to this table to limit select to entity
2184  } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && empty($user->rights->societe->client->voir) && !$socid) {
2185  $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2186  }
2187  if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2188  if ($this->element == 'user' && !empty($conf->global->MULTICOMPANY_TRANSVERSE_MODE)) {
2189  if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2190  $sql .= " AND te.entity IS NOT NULL"; // Show all users
2191  } else {
2192  $sql .= " AND ug.fk_user = te.rowid";
2193  $sql .= " AND ug.entity IN (".getEntity('usergroup').")";
2194  }
2195  } else {
2196  $sql .= ' AND te.entity IN ('.getEntity($this->element).')';
2197  }
2198  }
2199  if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2200  $tmparray = explode('@', $this->ismultientitymanaged);
2201  $sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
2202  }
2203  if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2204  $sql .= ' AND te.fk_soc = '.((int) $socid);
2205  }
2206  if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2207  $sql .= ' AND (te.fk_soc = '.((int) $socid).' OR te.fk_soc IS NULL)';
2208  }
2209  if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2210  $sql .= ' AND te.rowid = '.((int) $socid);
2211  }
2212  //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2213  // 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
2214 
2215  $result = $this->db->query($sql);
2216  if (!$result) {
2217  $this->error = $this->db->lasterror();
2218  return -2;
2219  }
2220  $row = $this->db->fetch_row($result);
2221  $this->ref_next = $row[0];
2222 
2223  return 1;
2224  }
2225 
2226 
2234  public function getListContactId($source = 'external')
2235  {
2236  $contactAlreadySelected = array();
2237  $tab = $this->liste_contact(-1, $source);
2238  $num = count($tab);
2239  $i = 0;
2240  while ($i < $num) {
2241  if ($source == 'thirdparty') {
2242  $contactAlreadySelected[$i] = $tab[$i]['socid'];
2243  } else {
2244  $contactAlreadySelected[$i] = $tab[$i]['id'];
2245  }
2246  $i++;
2247  }
2248  return $contactAlreadySelected;
2249  }
2250 
2251 
2259  public function setProject($projectid, $notrigger = 0)
2260  {
2261  global $user;
2262  $error = 0;
2263 
2264  if (!$this->table_element) {
2265  dol_syslog(get_class($this)."::setProject was called on objet with property table_element not defined", LOG_ERR);
2266  return -1;
2267  }
2268 
2269  $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2270  if (!empty($this->fields['fk_project'])) { // Common case
2271  if ($projectid) {
2272  $sql .= " SET fk_project = ".((int) $projectid);
2273  } else {
2274  $sql .= " SET fk_project = NULL";
2275  }
2276  $sql .= ' WHERE rowid = '.((int) $this->id);
2277  } elseif ($this->table_element == 'actioncomm') { // Special case for actioncomm
2278  if ($projectid) {
2279  $sql .= " SET fk_project = ".((int) $projectid);
2280  } else {
2281  $sql .= " SET fk_project = NULL";
2282  }
2283  $sql .= ' WHERE id = '.((int) $this->id);
2284  } else // Special case for old architecture objects
2285  {
2286  if ($projectid) {
2287  $sql .= ' SET fk_projet = '.((int) $projectid);
2288  } else {
2289  $sql .= ' SET fk_projet = NULL';
2290  }
2291  $sql .= " WHERE rowid = ".((int) $this->id);
2292  }
2293 
2294  $this->db->begin();
2295 
2296  dol_syslog(get_class($this)."::setProject", LOG_DEBUG);
2297  if ($this->db->query($sql)) {
2298  $this->fk_project = ((int) $projectid);
2299  } else {
2300  dol_print_error($this->db);
2301  $error++;
2302  }
2303 
2304  // Triggers
2305  if (!$error && !$notrigger) {
2306  // Call triggers
2307  $result = $this->call_trigger(strtoupper($this->element) . '_MODIFY', $user);
2308  if ($result < 0) {
2309  $error++;
2310  } //Do also here what you must do to rollback action if trigger fail
2311  // End call triggers
2312  }
2313 
2314  // Commit or rollback
2315  if ($error) {
2316  $this->db->rollback();
2317  return -1;
2318  } else {
2319  $this->db->commit();
2320  return 1;
2321  }
2322  }
2323 
2330  public function setPaymentMethods($id)
2331  {
2332  global $user;
2333 
2334  $error = 0; $notrigger = 0;
2335 
2336  dol_syslog(get_class($this).'::setPaymentMethods('.$id.')');
2337 
2338  if ($this->statut >= 0 || $this->element == 'societe') {
2339  // TODO uniformize field name
2340  $fieldname = 'fk_mode_reglement';
2341  if ($this->element == 'societe') {
2342  $fieldname = 'mode_reglement';
2343  }
2344  if (get_class($this) == 'Fournisseur') {
2345  $fieldname = 'mode_reglement_supplier';
2346  }
2347  if (get_class($this) == 'Tva') {
2348  $fieldname = 'fk_typepayment';
2349  }
2350  if (get_class($this) == 'Salary') {
2351  $fieldname = 'fk_typepayment';
2352  }
2353 
2354  $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2355  $sql .= " SET ".$fieldname." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
2356  $sql .= ' WHERE rowid='.((int) $this->id);
2357 
2358  if ($this->db->query($sql)) {
2359  $this->mode_reglement_id = $id;
2360  // for supplier
2361  if (get_class($this) == 'Fournisseur') {
2362  $this->mode_reglement_supplier_id = $id;
2363  }
2364  // Triggers
2365  if (!$error && !$notrigger) {
2366  // Call triggers
2367  if (get_class($this) == 'Commande') {
2368  $result = $this->call_trigger('ORDER_MODIFY', $user);
2369  } else {
2370  $result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $user);
2371  }
2372  if ($result < 0) {
2373  $error++;
2374  }
2375  // End call triggers
2376  }
2377  return 1;
2378  } else {
2379  dol_syslog(get_class($this).'::setPaymentMethods Error '.$this->db->error());
2380  $this->error = $this->db->error();
2381  return -1;
2382  }
2383  } else {
2384  dol_syslog(get_class($this).'::setPaymentMethods, status of the object is incompatible');
2385  $this->error = 'Status of the object is incompatible '.$this->statut;
2386  return -2;
2387  }
2388  }
2389 
2396  public function setMulticurrencyCode($code)
2397  {
2398  dol_syslog(get_class($this).'::setMulticurrencyCode('.$code.')');
2399  if ($this->statut >= 0 || $this->element == 'societe') {
2400  $fieldname = 'multicurrency_code';
2401 
2402  $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2403  $sql .= " SET ".$fieldname." = '".$this->db->escape($code)."'";
2404  $sql .= ' WHERE rowid='.((int) $this->id);
2405 
2406  if ($this->db->query($sql)) {
2407  $this->multicurrency_code = $code;
2408 
2409  list($fk_multicurrency, $rate) = MultiCurrency::getIdAndTxFromCode($this->db, $code);
2410  if ($rate) {
2411  $this->setMulticurrencyRate($rate, 2);
2412  }
2413 
2414  return 1;
2415  } else {
2416  dol_syslog(get_class($this).'::setMulticurrencyCode Error '.$sql.' - '.$this->db->error());
2417  $this->error = $this->db->error();
2418  return -1;
2419  }
2420  } else {
2421  dol_syslog(get_class($this).'::setMulticurrencyCode, status of the object is incompatible');
2422  $this->error = 'Status of the object is incompatible '.$this->statut;
2423  return -2;
2424  }
2425  }
2426 
2434  public function setMulticurrencyRate($rate, $mode = 1)
2435  {
2436  dol_syslog(get_class($this).'::setMulticurrencyRate('.$rate.','.$mode.')');
2437  if ($this->statut >= 0 || $this->element == 'societe') {
2438  $fieldname = 'multicurrency_tx';
2439 
2440  $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2441  $sql .= " SET ".$fieldname." = ".((float) $rate);
2442  $sql .= ' WHERE rowid='.((int) $this->id);
2443 
2444  if ($this->db->query($sql)) {
2445  $this->multicurrency_tx = $rate;
2446 
2447  // Update line price
2448  if (!empty($this->lines)) {
2449  foreach ($this->lines as &$line) {
2450  // Amounts in company currency will be recalculated
2451  if ($mode == 1) {
2452  $line->subprice = 0;
2453  }
2454 
2455  // Amounts in foreign currency will be recalculated
2456  if ($mode == 2) {
2457  $line->multicurrency_subprice = 0;
2458  }
2459 
2460  switch ($this->element) {
2461  case 'propal':
2464  $this->updateline(
2465  $line->id,
2466  $line->subprice,
2467  $line->qty,
2468  $line->remise_percent,
2469  $line->tva_tx,
2470  $line->localtax1_tx,
2471  $line->localtax2_tx,
2472  ($line->description ? $line->description : $line->desc),
2473  'HT',
2474  $line->info_bits,
2475  $line->special_code,
2476  $line->fk_parent_line,
2477  $line->skip_update_total,
2478  $line->fk_fournprice,
2479  $line->pa_ht,
2480  $line->label,
2481  $line->product_type,
2482  $line->date_start,
2483  $line->date_end,
2484  $line->array_options,
2485  $line->fk_unit,
2486  $line->multicurrency_subprice
2487  );
2488  break;
2489  case 'commande':
2492  $this->updateline(
2493  $line->id,
2494  ($line->description ? $line->description : $line->desc),
2495  $line->subprice,
2496  $line->qty,
2497  $line->remise_percent,
2498  $line->tva_tx,
2499  $line->localtax1_tx,
2500  $line->localtax2_tx,
2501  'HT',
2502  $line->info_bits,
2503  $line->date_start,
2504  $line->date_end,
2505  $line->product_type,
2506  $line->fk_parent_line,
2507  $line->skip_update_total,
2508  $line->fk_fournprice,
2509  $line->pa_ht,
2510  $line->label,
2511  $line->special_code,
2512  $line->array_options,
2513  $line->fk_unit,
2514  $line->multicurrency_subprice
2515  );
2516  break;
2517  case 'facture':
2520  $this->updateline(
2521  $line->id,
2522  ($line->description ? $line->description : $line->desc),
2523  $line->subprice,
2524  $line->qty,
2525  $line->remise_percent,
2526  $line->date_start,
2527  $line->date_end,
2528  $line->tva_tx,
2529  $line->localtax1_tx,
2530  $line->localtax2_tx,
2531  'HT',
2532  $line->info_bits,
2533  $line->product_type,
2534  $line->fk_parent_line,
2535  $line->skip_update_total,
2536  $line->fk_fournprice,
2537  $line->pa_ht,
2538  $line->label,
2539  $line->special_code,
2540  $line->array_options,
2541  $line->situation_percent,
2542  $line->fk_unit,
2543  $line->multicurrency_subprice
2544  );
2545  break;
2546  case 'supplier_proposal':
2549  $this->updateline(
2550  $line->id,
2551  $line->subprice,
2552  $line->qty,
2553  $line->remise_percent,
2554  $line->tva_tx,
2555  $line->localtax1_tx,
2556  $line->localtax2_tx,
2557  ($line->description ? $line->description : $line->desc),
2558  'HT',
2559  $line->info_bits,
2560  $line->special_code,
2561  $line->fk_parent_line,
2562  $line->skip_update_total,
2563  $line->fk_fournprice,
2564  $line->pa_ht,
2565  $line->label,
2566  $line->product_type,
2567  $line->array_options,
2568  $line->ref_fourn,
2569  $line->multicurrency_subprice
2570  );
2571  break;
2572  case 'order_supplier':
2575  $this->updateline(
2576  $line->id,
2577  ($line->description ? $line->description : $line->desc),
2578  $line->subprice,
2579  $line->qty,
2580  $line->remise_percent,
2581  $line->tva_tx,
2582  $line->localtax1_tx,
2583  $line->localtax2_tx,
2584  'HT',
2585  $line->info_bits,
2586  $line->product_type,
2587  false,
2588  $line->date_start,
2589  $line->date_end,
2590  $line->array_options,
2591  $line->fk_unit,
2592  $line->multicurrency_subprice,
2593  $line->ref_supplier
2594  );
2595  break;
2596  case 'invoice_supplier':
2599  $this->updateline(
2600  $line->id,
2601  ($line->description ? $line->description : $line->desc),
2602  $line->subprice,
2603  $line->tva_tx,
2604  $line->localtax1_tx,
2605  $line->localtax2_tx,
2606  $line->qty,
2607  0,
2608  'HT',
2609  $line->info_bits,
2610  $line->product_type,
2611  $line->remise_percent,
2612  false,
2613  $line->date_start,
2614  $line->date_end,
2615  $line->array_options,
2616  $line->fk_unit,
2617  $line->multicurrency_subprice,
2618  $line->ref_supplier
2619  );
2620  break;
2621  default:
2622  dol_syslog(get_class($this).'::setMulticurrencyRate no updateline defined', LOG_DEBUG);
2623  break;
2624  }
2625  }
2626  }
2627 
2628  return 1;
2629  } else {
2630  dol_syslog(get_class($this).'::setMulticurrencyRate Error '.$sql.' - '.$this->db->error());
2631  $this->error = $this->db->error();
2632  return -1;
2633  }
2634  } else {
2635  dol_syslog(get_class($this).'::setMulticurrencyRate, status of the object is incompatible');
2636  $this->error = 'Status of the object is incompatible '.$this->statut;
2637  return -2;
2638  }
2639  }
2640 
2648  public function setPaymentTerms($id, $deposit_percent = null)
2649  {
2650  dol_syslog(get_class($this).'::setPaymentTerms('.$id.', '.var_export($deposit_percent, true).')');
2651  if ($this->statut >= 0 || $this->element == 'societe') {
2652  // TODO uniformize field name
2653  $fieldname = 'fk_cond_reglement';
2654  if ($this->element == 'societe') {
2655  $fieldname = 'cond_reglement';
2656  }
2657  if (get_class($this) == 'Fournisseur') {
2658  $fieldname = 'cond_reglement_supplier';
2659  }
2660 
2661  if (empty($deposit_percent) || $deposit_percent < 0) {
2662  $deposit_percent = getDictionaryValue('c_payment_term', 'deposit_percent', $id);
2663  }
2664 
2665  if ($deposit_percent > 100) {
2666  $deposit_percent = 100;
2667  }
2668 
2669  $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2670  $sql .= " SET ".$fieldname." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
2671  if (in_array($this->table_element, array('propal', 'commande', 'societe'))) {
2672  $sql .= " , deposit_percent = " . (empty($deposit_percent) ? 'NULL' : "'".$this->db->escape($deposit_percent)."'");
2673  }
2674  $sql .= ' WHERE rowid='.((int) $this->id);
2675 
2676  if ($this->db->query($sql)) {
2677  $this->cond_reglement_id = $id;
2678  // for supplier
2679  if (get_class($this) == 'Fournisseur') {
2680  $this->cond_reglement_supplier_id = $id;
2681  }
2682  $this->cond_reglement = $id; // for compatibility
2683  $this->deposit_percent = $deposit_percent;
2684  return 1;
2685  } else {
2686  dol_syslog(get_class($this).'::setPaymentTerms Error '.$sql.' - '.$this->db->error());
2687  $this->error = $this->db->error();
2688  return -1;
2689  }
2690  } else {
2691  dol_syslog(get_class($this).'::setPaymentTerms, status of the object is incompatible');
2692  $this->error = 'Status of the object is incompatible '.$this->statut;
2693  return -2;
2694  }
2695  }
2696 
2703  public function setTransportMode($id)
2704  {
2705  dol_syslog(get_class($this).'::setTransportMode('.$id.')');
2706  if ($this->statut >= 0 || $this->element == 'societe') {
2707  $fieldname = 'fk_transport_mode';
2708  if ($this->element == 'societe') {
2709  $fieldname = 'transport_mode';
2710  }
2711  if (get_class($this) == 'Fournisseur') {
2712  $fieldname = 'transport_mode_supplier';
2713  }
2714 
2715  $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2716  $sql .= " SET ".$fieldname." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
2717  $sql .= ' WHERE rowid='.((int) $this->id);
2718 
2719  if ($this->db->query($sql)) {
2720  $this->transport_mode_id = $id;
2721  // for supplier
2722  if (get_class($this) == 'Fournisseur') {
2723  $this->transport_mode_supplier_id = $id;
2724  }
2725  return 1;
2726  } else {
2727  dol_syslog(get_class($this).'::setTransportMode Error '.$sql.' - '.$this->db->error());
2728  $this->error = $this->db->error();
2729  return -1;
2730  }
2731  } else {
2732  dol_syslog(get_class($this).'::setTransportMode, status of the object is incompatible');
2733  $this->error = 'Status of the object is incompatible '.$this->statut;
2734  return -2;
2735  }
2736  }
2737 
2744  public function setRetainedWarrantyPaymentTerms($id)
2745  {
2746  dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms('.$id.')');
2747  if ($this->statut >= 0 || $this->element == 'societe') {
2748  $fieldname = 'retained_warranty_fk_cond_reglement';
2749 
2750  $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2751  $sql .= " SET ".$fieldname." = ".((int) $id);
2752  $sql .= ' WHERE rowid='.((int) $this->id);
2753 
2754  if ($this->db->query($sql)) {
2755  $this->retained_warranty_fk_cond_reglement = $id;
2756  return 1;
2757  } else {
2758  dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms Error '.$sql.' - '.$this->db->error());
2759  $this->error = $this->db->error();
2760  return -1;
2761  }
2762  } else {
2763  dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms, status of the object is incompatible');
2764  $this->error = 'Status of the object is incompatible '.$this->statut;
2765  return -2;
2766  }
2767  }
2768 
2776  public function setDeliveryAddress($id)
2777  {
2778  $fieldname = 'fk_delivery_address';
2779  if ($this->element == 'delivery' || $this->element == 'shipping') {
2780  $fieldname = 'fk_address';
2781  }
2782 
2783  $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ".$fieldname." = ".((int) $id);
2784  $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = 0";
2785 
2786  if ($this->db->query($sql)) {
2787  $this->fk_delivery_address = $id;
2788  return 1;
2789  } else {
2790  $this->error = $this->db->error();
2791  dol_syslog(get_class($this).'::setDeliveryAddress Error '.$this->error);
2792  return -1;
2793  }
2794  }
2795 
2796 
2806  public function setShippingMethod($shipping_method_id, $notrigger = false, $userused = null)
2807  {
2808  global $user;
2809 
2810  if (empty($userused)) {
2811  $userused = $user;
2812  }
2813 
2814  $error = 0;
2815 
2816  if (!$this->table_element) {
2817  dol_syslog(get_class($this)."::setShippingMethod was called on objet with property table_element not defined", LOG_ERR);
2818  return -1;
2819  }
2820 
2821  $this->db->begin();
2822 
2823  if ($shipping_method_id < 0) {
2824  $shipping_method_id = 'NULL';
2825  }
2826  dol_syslog(get_class($this).'::setShippingMethod('.$shipping_method_id.')');
2827 
2828  $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2829  $sql .= " SET fk_shipping_method = ".((int) $shipping_method_id);
2830  $sql .= " WHERE rowid=".((int) $this->id);
2831  $resql = $this->db->query($sql);
2832  if (!$resql) {
2833  dol_syslog(get_class($this).'::setShippingMethod Error ', LOG_DEBUG);
2834  $this->error = $this->db->lasterror();
2835  $error++;
2836  } else {
2837  if (!$notrigger) {
2838  // Call trigger
2839  $this->context = array('shippingmethodupdate'=>1);
2840  $result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $userused);
2841  if ($result < 0) {
2842  $error++;
2843  }
2844  // End call trigger
2845  }
2846  }
2847  if ($error) {
2848  $this->db->rollback();
2849  return -1;
2850  } else {
2851  $this->shipping_method_id = ($shipping_method_id == 'NULL') ?null:$shipping_method_id;
2852  $this->db->commit();
2853  return 1;
2854  }
2855  }
2856 
2857 
2864  public function setWarehouse($warehouse_id)
2865  {
2866  if (!$this->table_element) {
2867  dol_syslog(get_class($this)."::setWarehouse was called on objet with property table_element not defined", LOG_ERR);
2868  return -1;
2869  }
2870  if ($warehouse_id < 0) {
2871  $warehouse_id = 'NULL';
2872  }
2873  dol_syslog(get_class($this).'::setWarehouse('.$warehouse_id.')');
2874 
2875  $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2876  $sql .= " SET fk_warehouse = ".((int) $warehouse_id);
2877  $sql .= " WHERE rowid=".((int) $this->id);
2878 
2879  if ($this->db->query($sql)) {
2880  $this->warehouse_id = ($warehouse_id == 'NULL') ?null:$warehouse_id;
2881  return 1;
2882  } else {
2883  dol_syslog(get_class($this).'::setWarehouse Error ', LOG_DEBUG);
2884  $this->error = $this->db->error();
2885  return 0;
2886  }
2887  }
2888 
2889 
2897  public function setDocModel($user, $modelpdf)
2898  {
2899  if (!$this->table_element) {
2900  dol_syslog(get_class($this)."::setDocModel was called on objet with property table_element not defined", LOG_ERR);
2901  return -1;
2902  }
2903 
2904  $newmodelpdf = dol_trunc($modelpdf, 255);
2905 
2906  $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2907  $sql .= " SET model_pdf = '".$this->db->escape($newmodelpdf)."'";
2908  $sql .= " WHERE rowid = ".((int) $this->id);
2909 
2910  dol_syslog(get_class($this)."::setDocModel", LOG_DEBUG);
2911  $resql = $this->db->query($sql);
2912  if ($resql) {
2913  $this->model_pdf = $modelpdf;
2914  $this->modelpdf = $modelpdf; // For bakward compatibility
2915  return 1;
2916  } else {
2917  dol_print_error($this->db);
2918  return 0;
2919  }
2920  }
2921 
2922 
2931  public function setBankAccount($fk_account, $notrigger = false, $userused = null)
2932  {
2933  global $user;
2934 
2935  if (empty($userused)) {
2936  $userused = $user;
2937  }
2938 
2939  $error = 0;
2940 
2941  if (!$this->table_element) {
2942  dol_syslog(get_class($this)."::setBankAccount was called on objet with property table_element not defined", LOG_ERR);
2943  return -1;
2944  }
2945  $this->db->begin();
2946 
2947  if ($fk_account < 0) {
2948  $fk_account = 'NULL';
2949  }
2950  dol_syslog(get_class($this).'::setBankAccount('.$fk_account.')');
2951 
2952  $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2953  $sql .= " SET fk_account = ".((int) $fk_account);
2954  $sql .= " WHERE rowid=".((int) $this->id);
2955 
2956  $resql = $this->db->query($sql);
2957  if (!$resql) {
2958  dol_syslog(get_class($this).'::setBankAccount Error '.$sql.' - '.$this->db->error());
2959  $this->error = $this->db->lasterror();
2960  $error++;
2961  } else {
2962  if (!$notrigger) {
2963  // Call trigger
2964  $this->context = array('bankaccountupdate'=>1);
2965  $result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $userused);
2966  if ($result < 0) {
2967  $error++;
2968  }
2969  // End call trigger
2970  }
2971  }
2972  if ($error) {
2973  $this->db->rollback();
2974  return -1;
2975  } else {
2976  $this->fk_account = ($fk_account == 'NULL') ?null:$fk_account;
2977  $this->db->commit();
2978  return 1;
2979  }
2980  }
2981 
2982 
2983  // TODO: Move line related operations to CommonObjectLine?
2984 
2985  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2995  public function line_order($renum = false, $rowidorder = 'ASC', $fk_parent_line = true)
2996  {
2997  // phpcs:enable
2998  if (!$this->table_element_line) {
2999  dol_syslog(get_class($this)."::line_order was called on objet with property table_element_line not defined", LOG_ERR);
3000  return -1;
3001  }
3002  if (!$this->fk_element) {
3003  dol_syslog(get_class($this)."::line_order was called on objet with property fk_element not defined", LOG_ERR);
3004  return -1;
3005  }
3006 
3007  $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3008  if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3009  $fieldposition = 'position';
3010  }
3011 
3012  // Count number of lines to reorder (according to choice $renum)
3013  $nl = 0;
3014  $sql = "SELECT count(rowid) FROM ".$this->db->prefix().$this->table_element_line;
3015  $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3016  if (!$renum) {
3017  $sql .= " AND " . $fieldposition . " = 0";
3018  }
3019  if ($renum) {
3020  $sql .= " AND " . $fieldposition . " <> 0";
3021  }
3022 
3023  dol_syslog(get_class($this)."::line_order", LOG_DEBUG);
3024  $resql = $this->db->query($sql);
3025  if ($resql) {
3026  $row = $this->db->fetch_row($resql);
3027  $nl = $row[0];
3028  } else {
3029  dol_print_error($this->db);
3030  }
3031  if ($nl > 0) {
3032  // The goal of this part is to reorder all lines, with all children lines sharing the same counter that parents.
3033  $rows = array();
3034 
3035  // We first search all lines that are parent lines (for multilevel details lines)
3036  $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element_line;
3037  $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3038  if ($fk_parent_line) {
3039  $sql .= ' AND fk_parent_line IS NULL';
3040  }
3041  $sql .= " ORDER BY " . $fieldposition . " ASC, rowid " . $rowidorder;
3042 
3043  dol_syslog(get_class($this)."::line_order search all parent lines", LOG_DEBUG);
3044  $resql = $this->db->query($sql);
3045  if ($resql) {
3046  $i = 0;
3047  $num = $this->db->num_rows($resql);
3048  while ($i < $num) {
3049  $row = $this->db->fetch_row($resql);
3050  $rows[] = $row[0]; // Add parent line into array rows
3051  $childrens = $this->getChildrenOfLine($row[0]);
3052  if (!empty($childrens)) {
3053  foreach ($childrens as $child) {
3054  array_push($rows, $child);
3055  }
3056  }
3057  $i++;
3058  }
3059 
3060  // Now we set a new number for each lines (parent and children with children included into parent tree)
3061  if (!empty($rows)) {
3062  foreach ($rows as $key => $row) {
3063  $this->updateRangOfLine($row, ($key + 1));
3064  }
3065  }
3066  } else {
3067  dol_print_error($this->db);
3068  }
3069  }
3070  return 1;
3071  }
3072 
3080  public function getChildrenOfLine($id, $includealltree = 0)
3081  {
3082  $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3083  if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3084  $fieldposition = 'position';
3085  }
3086 
3087  $rows = array();
3088 
3089  $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element_line;
3090  $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3091  $sql .= ' AND fk_parent_line = '.((int) $id);
3092  $sql .= " ORDER BY " . $fieldposition . " ASC";
3093 
3094  dol_syslog(get_class($this)."::getChildrenOfLine search children lines for line ".$id, LOG_DEBUG);
3095  $resql = $this->db->query($sql);
3096  if ($resql) {
3097  if ($this->db->num_rows($resql) > 0) {
3098  while ($row = $this->db->fetch_row($resql)) {
3099  $rows[] = $row[0];
3100  if (!empty($includealltree)) {
3101  $rows = array_merge($rows, $this->getChildrenOfLine($row[0]), $includealltree);
3102  }
3103  }
3104  }
3105  }
3106  return $rows;
3107  }
3108 
3109  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3117  public function line_up($rowid, $fk_parent_line = true)
3118  {
3119  // phpcs:enable
3120  $this->line_order(false, 'ASC', $fk_parent_line);
3121 
3122  // Get rang of line
3123  $rang = $this->getRangOfLine($rowid);
3124 
3125  // Update position of line
3126  $this->updateLineUp($rowid, $rang);
3127  }
3128 
3129  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3137  public function line_down($rowid, $fk_parent_line = true)
3138  {
3139  // phpcs:enable
3140  $this->line_order(false, 'ASC', $fk_parent_line);
3141 
3142  // Get rang of line
3143  $rang = $this->getRangOfLine($rowid);
3144 
3145  // Get max value for rang
3146  $max = $this->line_max();
3147 
3148  // Update position of line
3149  $this->updateLineDown($rowid, $rang, $max);
3150  }
3151 
3159  public function updateRangOfLine($rowid, $rang)
3160  {
3161  global $hookmanager;
3162  $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3163  if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3164  $fieldposition = 'position';
3165  }
3166 
3167  $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) $rang);
3168  $sql .= ' WHERE rowid = '.((int) $rowid);
3169 
3170  dol_syslog(get_class($this)."::updateRangOfLine", LOG_DEBUG);
3171  if (!$this->db->query($sql)) {
3172  dol_print_error($this->db);
3173  return -1;
3174  } else {
3175  $parameters=array('rowid'=>$rowid, 'rang'=>$rang, 'fieldposition' => $fieldposition);
3176  $action='';
3177  $reshook = $hookmanager->executeHooks('afterRankOfLineUpdate', $parameters, $this, $action);
3178  return 1;
3179  }
3180  }
3181 
3182  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3189  public function line_ajaxorder($rows)
3190  {
3191  // phpcs:enable
3192  $num = count($rows);
3193  for ($i = 0; $i < $num; $i++) {
3194  $this->updateRangOfLine($rows[$i], ($i + 1));
3195  }
3196  }
3197 
3205  public function updateLineUp($rowid, $rang)
3206  {
3207  if ($rang > 1) {
3208  $fieldposition = 'rang';
3209  if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3210  $fieldposition = 'position';
3211  }
3212 
3213  $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) $rang);
3214  $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3215  $sql .= " AND " . $fieldposition . " = " . ((int) ($rang - 1));
3216  if ($this->db->query($sql)) {
3217  $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) ($rang - 1));
3218  $sql .= ' WHERE rowid = '.((int) $rowid);
3219  if (!$this->db->query($sql)) {
3220  dol_print_error($this->db);
3221  }
3222  } else {
3223  dol_print_error($this->db);
3224  }
3225  }
3226  }
3227 
3236  public function updateLineDown($rowid, $rang, $max)
3237  {
3238  if ($rang < $max) {
3239  $fieldposition = 'rang';
3240  if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3241  $fieldposition = 'position';
3242  }
3243 
3244  $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) $rang);
3245  $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3246  $sql .= " AND " . $fieldposition . " = " . ((int) ($rang + 1));
3247  if ($this->db->query($sql)) {
3248  $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) ($rang + 1));
3249  $sql .= ' WHERE rowid = '.((int) $rowid);
3250  if (!$this->db->query($sql)) {
3251  dol_print_error($this->db);
3252  }
3253  } else {
3254  dol_print_error($this->db);
3255  }
3256  }
3257  }
3258 
3265  public function getRangOfLine($rowid)
3266  {
3267  $fieldposition = 'rang';
3268  if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3269  $fieldposition = 'position';
3270  }
3271 
3272  $sql = "SELECT " . $fieldposition . " FROM ".$this->db->prefix().$this->table_element_line;
3273  $sql .= " WHERE rowid = ".((int) $rowid);
3274 
3275  dol_syslog(get_class($this)."::getRangOfLine", LOG_DEBUG);
3276  $resql = $this->db->query($sql);
3277  if ($resql) {
3278  $row = $this->db->fetch_row($resql);
3279  return $row[0];
3280  }
3281 
3282  return 0;
3283  }
3284 
3291  public function getIdOfLine($rang)
3292  {
3293  $fieldposition = 'rang';
3294  if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3295  $fieldposition = 'position';
3296  }
3297 
3298  $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element_line;
3299  $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3300  $sql .= " AND " . $fieldposition . " = ".((int) $rang);
3301  $resql = $this->db->query($sql);
3302  if ($resql) {
3303  $row = $this->db->fetch_row($resql);
3304  return $row[0];
3305  }
3306 
3307  return 0;
3308  }
3309 
3310  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3317  public function line_max($fk_parent_line = 0)
3318  {
3319  // phpcs:enable
3320  $positionfield = 'rang';
3321  if (in_array($this->table_element, array('bom_bom', 'product_attribute'))) {
3322  $positionfield = 'position';
3323  }
3324 
3325  // Search the last rang with fk_parent_line
3326  if ($fk_parent_line) {
3327  $sql = "SELECT max(".$positionfield.") FROM ".$this->db->prefix().$this->table_element_line;
3328  $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3329  $sql .= " AND fk_parent_line = ".((int) $fk_parent_line);
3330 
3331  dol_syslog(get_class($this)."::line_max", LOG_DEBUG);
3332  $resql = $this->db->query($sql);
3333  if ($resql) {
3334  $row = $this->db->fetch_row($resql);
3335  if (!empty($row[0])) {
3336  return $row[0];
3337  } else {
3338  return $this->getRangOfLine($fk_parent_line);
3339  }
3340  }
3341  } else {
3342  // If not, search the last rang of element
3343  $sql = "SELECT max(".$positionfield.") FROM ".$this->db->prefix().$this->table_element_line;
3344  $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3345 
3346  dol_syslog(get_class($this)."::line_max", LOG_DEBUG);
3347  $resql = $this->db->query($sql);
3348  if ($resql) {
3349  $row = $this->db->fetch_row($resql);
3350  return $row[0];
3351  }
3352  }
3353 
3354  return 0;
3355  }
3356 
3357  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3364  public function update_ref_ext($ref_ext)
3365  {
3366  // phpcs:enable
3367  if (!$this->table_element) {
3368  dol_syslog(get_class($this)."::update_ref_ext was called on objet with property table_element not defined", LOG_ERR);
3369  return -1;
3370  }
3371 
3372  $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3373  $sql .= " SET ref_ext = '".$this->db->escape($ref_ext)."'";
3374  $sql .= " WHERE ".(isset($this->table_rowid) ? $this->table_rowid : 'rowid')." = ".((int) $this->id);
3375 
3376  dol_syslog(get_class($this)."::update_ref_ext", LOG_DEBUG);
3377  if ($this->db->query($sql)) {
3378  $this->ref_ext = $ref_ext;
3379  return 1;
3380  } else {
3381  $this->error = $this->db->error();
3382  return -1;
3383  }
3384  }
3385 
3386  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3394  public function update_note($note, $suffix = '')
3395  {
3396  // phpcs:enable
3397  global $user;
3398 
3399  if (!$this->table_element) {
3400  $this->error = 'update_note was called on objet with property table_element not defined';
3401  dol_syslog(get_class($this)."::update_note was called on objet with property table_element not defined", LOG_ERR);
3402  return -1;
3403  }
3404  if (!in_array($suffix, array('', '_public', '_private'))) {
3405  $this->error = 'update_note Parameter suffix must be empty, \'_private\' or \'_public\'';
3406  dol_syslog(get_class($this)."::update_note Parameter suffix must be empty, '_private' or '_public'", LOG_ERR);
3407  return -2;
3408  }
3409 
3410  $newsuffix = $suffix;
3411 
3412  // Special cas
3413  if ($this->table_element == 'product' && $newsuffix == '_private') {
3414  $newsuffix = '';
3415  }
3416  if (in_array($this->table_element, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
3417  $fieldusermod = "fk_user_mod";
3418  } elseif ($this->table_element == 'ecm_files') {
3419  $fieldusermod = "fk_user_m";
3420  } else {
3421  $fieldusermod = "fk_user_modif";
3422  }
3423  $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3424  $sql .= " SET note".$newsuffix." = ".(!empty($note) ? ("'".$this->db->escape($note)."'") : "NULL");
3425  $sql .= ", ".$fieldusermod." = ".((int) $user->id);
3426  $sql .= " WHERE rowid = ".((int) $this->id);
3427 
3428  dol_syslog(get_class($this)."::update_note", LOG_DEBUG);
3429  if ($this->db->query($sql)) {
3430  if ($suffix == '_public') {
3431  $this->note_public = $note;
3432  } elseif ($suffix == '_private') {
3433  $this->note_private = $note;
3434  } else {
3435  $this->note = $note; // deprecated
3436  $this->note_private = $note;
3437  }
3438  return 1;
3439  } else {
3440  $this->error = $this->db->lasterror();
3441  return -1;
3442  }
3443  }
3444 
3445  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3454  public function update_note_public($note)
3455  {
3456  // phpcs:enable
3457  return $this->update_note($note, '_public');
3458  }
3459 
3460  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3471  public function update_price($exclspec = 0, $roundingadjust = 'none', $nodatabaseupdate = 0, $seller = null)
3472  {
3473  // phpcs:enable
3474  global $conf, $hookmanager, $action;
3475 
3476  $parameters = array('exclspec' => $exclspec, 'roundingadjust' => $roundingadjust, 'nodatabaseupdate' => $nodatabaseupdate, 'seller' => $seller);
3477  $reshook = $hookmanager->executeHooks('updateTotalPrice', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3478  if ($reshook > 0) {
3479  return 1; // replacement code
3480  } elseif ($reshook < 0) {
3481  return -1; // failure
3482  } // reshook = 0 => execute normal code
3483 
3484  // Some external module want no update price after a trigger because they have another method to calculate the total (ex: with an extrafield)
3485  $MODULE = "";
3486  if ($this->element == 'propal') {
3487  $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_PROPOSAL";
3488  } elseif ($this->element == 'commande' || $this->element == 'order') {
3489  $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_ORDER";
3490  } elseif ($this->element == 'facture' || $this->element == 'invoice') {
3491  $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_INVOICE";
3492  } elseif ($this->element == 'facture_fourn' || $this->element == 'supplier_invoice' || $this->element == 'invoice_supplier' || $this->element == 'invoice_supplier_rec') {
3493  $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_INVOICE";
3494  } elseif ($this->element == 'order_supplier' || $this->element == 'supplier_order') {
3495  $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_ORDER";
3496  } elseif ($this->element == 'supplier_proposal') {
3497  $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_PROPOSAL";
3498  }
3499 
3500  if (!empty($MODULE)) {
3501  if (!empty($conf->global->$MODULE)) {
3502  $modsactivated = explode(',', $conf->global->$MODULE);
3503  foreach ($modsactivated as $mod) {
3504  if (isModEnabled($mod)) {
3505  return 1; // update was disabled by specific setup
3506  }
3507  }
3508  }
3509  }
3510 
3511  include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
3512 
3513  $forcedroundingmode = $roundingadjust;
3514  if ($forcedroundingmode == 'auto' && isset($conf->global->MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND)) {
3515  $forcedroundingmode = getDolGlobalString('MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND');
3516  } elseif ($forcedroundingmode == 'auto') {
3517  $forcedroundingmode = '0';
3518  }
3519 
3520  $error = 0;
3521 
3522  $multicurrency_tx = !empty($this->multicurrency_tx) ? $this->multicurrency_tx : 1;
3523 
3524  // Define constants to find lines to sum (field name int the table_element_line not into table_element)
3525  $fieldtva = 'total_tva';
3526  $fieldlocaltax1 = 'total_localtax1';
3527  $fieldlocaltax2 = 'total_localtax2';
3528  $fieldup = 'subprice';
3529  if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
3530  $fieldtva = 'tva';
3531  $fieldup = 'pu_ht';
3532  }
3533  if ($this->element == 'invoice_supplier_rec') {
3534  $fieldup = 'pu_ht';
3535  }
3536  if ($this->element == 'expensereport') {
3537  $fieldup = 'value_unit';
3538  }
3539 
3540  $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,";
3541  $sql .= ' tva_tx as vatrate, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, info_bits, product_type';
3542  if ($this->table_element_line == 'facturedet') {
3543  $sql .= ', situation_percent';
3544  }
3545  $sql .= ', multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
3546  $sql .= " FROM ".$this->db->prefix().$this->table_element_line;
3547  $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3548  if ($exclspec) {
3549  $product_field = 'product_type';
3550  if ($this->table_element_line == 'contratdet') {
3551  $product_field = ''; // contratdet table has no product_type field
3552  }
3553  if ($product_field) {
3554  $sql .= " AND ".$product_field." <> 9";
3555  }
3556  }
3557  $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
3558 
3559  dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
3560 
3561  $resql = $this->db->query($sql);
3562  if ($resql) {
3563  $this->total_ht = 0;
3564  $this->total_tva = 0;
3565  $this->total_localtax1 = 0;
3566  $this->total_localtax2 = 0;
3567  $this->total_ttc = 0;
3568  $total_ht_by_vats = array();
3569  $total_tva_by_vats = array();
3570  $total_ttc_by_vats = array();
3571  $this->multicurrency_total_ht = 0;
3572  $this->multicurrency_total_tva = 0;
3573  $this->multicurrency_total_ttc = 0;
3574 
3575  $this->db->begin();
3576 
3577  $num = $this->db->num_rows($resql);
3578  $i = 0;
3579  while ($i < $num) {
3580  $obj = $this->db->fetch_object($resql);
3581 
3582  // Note: There is no check on detail line and no check on total, if $forcedroundingmode = 'none'
3583  $parameters = array('fk_element' => $obj->rowid);
3584  $reshook = $hookmanager->executeHooks('changeRoundingMode', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3585 
3586  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'
3587  // This part of code is to fix data. We should not call it too often.
3588  $localtax_array = array($obj->localtax1_type, $obj->localtax1_tx, $obj->localtax2_type, $obj->localtax2_tx);
3589  $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);
3590 
3591  $diff_when_using_price_ht = price2num($tmpcal[1] - $obj->total_tva, 'MT', 1); // If price was set with tax price and unit price HT has a low number of digits, then we may have a diff on recalculation from unit price HT.
3592  $diff_on_current_total = price2num($obj->total_ttc - $obj->total_ht - $obj->total_tva - $obj->total_localtax1 - $obj->total_localtax2, 'MT', 1);
3593  //var_dump($obj->total_ht.' '.$obj->total_tva.' '.$obj->total_localtax1.' '.$obj->total_localtax2.' => '.$obj->total_ttc);
3594  //var_dump($diff_when_using_price_ht.' '.$diff_on_current_total);
3595 
3596  if ($diff_on_current_total) {
3597  // This should not happen, we should always have in table: total_ttc = total_ht + total_vat + total_localtax1 + total_localtax2
3598  $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldtva." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2])." WHERE rowid = ".((int) $obj->rowid);
3599  dol_syslog('We found unconsistent data into detailed line (diff_on_current_total = '.$diff_on_current_total.') for line rowid = '.$obj->rowid." (ht=".$obj->total_ht." vat=".$obj->total_tva." tax1=".$obj->total_localtax1." tax2=".$obj->total_localtax2." ttc=".$obj->total_ttc."). We fix the total_vat and total_ttc of line by running sqlfix = ".$sqlfix, LOG_WARNING);
3600  $resqlfix = $this->db->query($sqlfix);
3601  if (!$resqlfix) {
3602  dol_print_error($this->db, 'Failed to update line');
3603  }
3604  $obj->total_tva = $tmpcal[1];
3605  $obj->total_ttc = $tmpcal[2];
3606  } elseif ($diff_when_using_price_ht && $roundingadjust == '0') {
3607  // After calculation from HT, total is consistent but we have found a difference between VAT part in calculation and into database and
3608  // we ask to force the use of rounding on line (like done on calculation) so we force update of line
3609  $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldtva." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2])." WHERE rowid = ".((int) $obj->rowid);
3610  dol_syslog('We found a line with different rounding 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);
3611  $resqlfix = $this->db->query($sqlfix);
3612  if (!$resqlfix) {
3613  dol_print_error($this->db, 'Failed to update line');
3614  }
3615  $obj->total_tva = $tmpcal[1];
3616  $obj->total_ttc = $tmpcal[2];
3617  }
3618  }
3619 
3620  $this->total_ht += $obj->total_ht; // The field visible at end of line detail
3621  $this->total_tva += $obj->total_tva;
3622  $this->total_localtax1 += $obj->total_localtax1;
3623  $this->total_localtax2 += $obj->total_localtax2;
3624  $this->total_ttc += $obj->total_ttc;
3625  $this->multicurrency_total_ht += $obj->multicurrency_total_ht; // The field visible at end of line detail
3626  $this->multicurrency_total_tva += $obj->multicurrency_total_tva;
3627  $this->multicurrency_total_ttc += $obj->multicurrency_total_ttc;
3628 
3629  if (!isset($total_ht_by_vats[$obj->vatrate])) {
3630  $total_ht_by_vats[$obj->vatrate] = 0;
3631  }
3632  if (!isset($total_tva_by_vats[$obj->vatrate])) {
3633  $total_tva_by_vats[$obj->vatrate] = 0;
3634  }
3635  if (!isset($total_ttc_by_vats[$obj->vatrate])) {
3636  $total_ttc_by_vats[$obj->vatrate] = 0;
3637  }
3638  $total_ht_by_vats[$obj->vatrate] += $obj->total_ht;
3639  $total_tva_by_vats[$obj->vatrate] += $obj->total_tva;
3640  $total_ttc_by_vats[$obj->vatrate] += $obj->total_ttc;
3641 
3642  if ($forcedroundingmode == '1') { // Check if we need adjustement onto line for vat. TODO This works on the company currency but not on foreign currency
3643  $tmpvat = price2num($total_ht_by_vats[$obj->vatrate] * $obj->vatrate / 100, 'MT', 1);
3644  $diff = price2num($total_tva_by_vats[$obj->vatrate] - $tmpvat, 'MT', 1);
3645  //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";
3646  if ($diff) {
3647  if (abs($diff) > (10 * pow(10, -1 * getDolGlobalInt('MAIN_MAX_DECIMALS_TOT', 0)))) {
3648  // If error is more than 10 times the accurancy of rounding. This should not happen.
3649  $errmsg = 'A rounding difference was detected into TOTAL but is too high to be corrected. Some data in your lines may be corrupted. Try to edit each line manually to fix this before restarting.';
3650  dol_syslog($errmsg, LOG_WARNING);
3651  $this->error = $errmsg;
3652  $error++;
3653  break;
3654  }
3655  $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldtva." = ".price2num($obj->total_tva - $diff).", total_ttc = ".price2num($obj->total_ttc - $diff)." WHERE rowid = ".((int) $obj->rowid);
3656  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);
3657 
3658  $resqlfix = $this->db->query($sqlfix);
3659 
3660  if (!$resqlfix) {
3661  dol_print_error($this->db, 'Failed to update line');
3662  }
3663 
3664  $this->total_tva = (float) price2num($this->total_tva - $diff, '', 1);
3665  $this->total_ttc = (float) price2num($this->total_ttc - $diff, '', 1);
3666  $total_tva_by_vats[$obj->vatrate] = (float) price2num($total_tva_by_vats[$obj->vatrate] - $diff, '', 1);
3667  $total_ttc_by_vats[$obj->vatrate] = (float) price2num($total_ttc_by_vats[$obj->vatrate] - $diff, '', 1);
3668  }
3669  }
3670 
3671  $i++;
3672  }
3673 
3674  // Add revenue stamp to total
3675  $this->total_ttc += isset($this->revenuestamp) ? $this->revenuestamp : 0;
3676  $this->multicurrency_total_ttc += isset($this->revenuestamp) ? ($this->revenuestamp * $multicurrency_tx) : 0;
3677 
3678  // Situations totals
3679  if (!empty($this->situation_cycle_ref) && $this->situation_counter > 1 && method_exists($this, 'get_prev_sits') && $this->type != $this::TYPE_CREDIT_NOTE) {
3680  $prev_sits = $this->get_prev_sits();
3681 
3682  foreach ($prev_sits as $sit) { // $sit is an object Facture loaded with a fetch.
3683  $this->total_ht -= $sit->total_ht;
3684  $this->total_tva -= $sit->total_tva;
3685  $this->total_localtax1 -= $sit->total_localtax1;
3686  $this->total_localtax2 -= $sit->total_localtax2;
3687  $this->total_ttc -= $sit->total_ttc;
3688  $this->multicurrency_total_ht -= $sit->multicurrency_total_ht;
3689  $this->multicurrency_total_tva -= $sit->multicurrency_total_tva;
3690  $this->multicurrency_total_ttc -= $sit->multicurrency_total_ttc;
3691  }
3692  }
3693 
3694  // Clean total
3695  $this->total_ht = (float) price2num($this->total_ht);
3696  $this->total_tva = (float) price2num($this->total_tva);
3697  $this->total_localtax1 = (float) price2num($this->total_localtax1);
3698  $this->total_localtax2 = (float) price2num($this->total_localtax2);
3699  $this->total_ttc = (float) price2num($this->total_ttc);
3700 
3701  $this->db->free($resql);
3702 
3703  // Now update global fields total_ht, total_ttc, total_tva, total_localtax1, total_localtax2, multicurrency_total_* of main object
3704  $fieldht = 'total_ht';
3705  $fieldtva = 'tva';
3706  $fieldlocaltax1 = 'localtax1';
3707  $fieldlocaltax2 = 'localtax2';
3708  $fieldttc = 'total_ttc';
3709  // Specific code for backward compatibility with old field names
3710  if (in_array($this->element, array('propal', 'commande', 'facture', 'facturerec', 'supplier_proposal', 'order_supplier', 'facture_fourn', 'invoice_supplier', 'invoice_supplier_rec', 'expensereport'))) {
3711  $fieldtva = 'total_tva';
3712  }
3713 
3714  if (!$error && empty($nodatabaseupdate)) {
3715  $sql = "UPDATE ".$this->db->prefix().$this->table_element.' SET';
3716  $sql .= " ".$fieldht." = ".((float) price2num($this->total_ht, 'MT', 1)).",";
3717  $sql .= " ".$fieldtva." = ".((float) price2num($this->total_tva, 'MT', 1)).",";
3718  $sql .= " ".$fieldlocaltax1." = ".((float) price2num($this->total_localtax1, 'MT', 1)).",";
3719  $sql .= " ".$fieldlocaltax2." = ".((float) price2num($this->total_localtax2, 'MT', 1)).",";
3720  $sql .= " ".$fieldttc." = ".((float) price2num($this->total_ttc, 'MT', 1));
3721  $sql .= ", multicurrency_total_ht = ".((float) price2num($this->multicurrency_total_ht, 'MT', 1));
3722  $sql .= ", multicurrency_total_tva = ".((float) price2num($this->multicurrency_total_tva, 'MT', 1));
3723  $sql .= ", multicurrency_total_ttc = ".((float) price2num($this->multicurrency_total_ttc, 'MT', 1));
3724  $sql .= " WHERE rowid = ".((int) $this->id);
3725 
3726  dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
3727  $resql = $this->db->query($sql);
3728 
3729  if (!$resql) {
3730  $error++;
3731  $this->error = $this->db->lasterror();
3732  $this->errors[] = $this->db->lasterror();
3733  }
3734  }
3735 
3736  if (!$error) {
3737  $this->db->commit();
3738  return 1;
3739  } else {
3740  $this->db->rollback();
3741  return -1;
3742  }
3743  } else {
3744  dol_print_error($this->db, 'Bad request in update_price');
3745  return -1;
3746  }
3747  }
3748 
3749  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3760  public function add_object_linked($origin = null, $origin_id = null, $f_user = null, $notrigger = 0)
3761  {
3762  // phpcs:enable
3763  global $user, $hookmanager, $action;
3764  $origin = (!empty($origin) ? $origin : $this->origin);
3765  $origin_id = (!empty($origin_id) ? $origin_id : $this->origin_id);
3766  $f_user = isset($f_user) ? $f_user : $user;
3767 
3768  // Special case
3769  if ($origin == 'order') {
3770  $origin = 'commande';
3771  }
3772  if ($origin == 'invoice') {
3773  $origin = 'facture';
3774  }
3775  if ($origin == 'invoice_template') {
3776  $origin = 'facturerec';
3777  }
3778  if ($origin == 'supplierorder') {
3779  $origin = 'order_supplier';
3780  }
3781 
3782  // Elements of the core modules which have `$module` property but may to which we don't want to prefix module part to the element name for finding the linked object in llx_element_element.
3783  // It's because an entry for this element may be exist in llx_element_element before this modification (version <=14.2) and ave named only with their element name in fk_source or fk_target.
3784  $coremodule = array('knowledgemanagement', 'partnership', 'workstation', 'ticket', 'recruitment', 'eventorganization', 'asset');
3785  // Add module part to target type if object has $module property and isn't in core modules.
3786  $targettype = ((!empty($this->module) && ! in_array($this->module, $coremodule)) ? $this->module.'_' : '').$this->element;
3787 
3788  $parameters = array('targettype'=>$targettype);
3789  // Hook for explicitly set the targettype if it must be differtent than $this->element
3790  $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3791  if ($reshook > 0) {
3792  if (!empty($hookmanager->resArray['targettype'])) $targettype = $hookmanager->resArray['targettype'];
3793  }
3794 
3795  $this->db->begin();
3796  $error = 0;
3797 
3798  $sql = "INSERT INTO " . $this->db->prefix() . "element_element (";
3799  $sql .= "fk_source";
3800  $sql .= ", sourcetype";
3801  $sql .= ", fk_target";
3802  $sql .= ", targettype";
3803  $sql .= ") VALUES (";
3804  $sql .= ((int) $origin_id);
3805  $sql .= ", '" . $this->db->escape($origin) . "'";
3806  $sql .= ", " . ((int) $this->id);
3807  $sql .= ", '" . $this->db->escape($targettype) . "'";
3808  $sql .= ")";
3809 
3810  dol_syslog(get_class($this) . "::add_object_linked", LOG_DEBUG);
3811  if ($this->db->query($sql)) {
3812  if (!$notrigger) {
3813  // Call trigger
3814  $this->context['link_origin'] = $origin;
3815  $this->context['link_origin_id'] = $origin_id;
3816  $result = $this->call_trigger('OBJECT_LINK_INSERT', $f_user);
3817  if ($result < 0) {
3818  $error++;
3819  }
3820  // End call triggers
3821  }
3822  } else {
3823  $this->error = $this->db->lasterror();
3824  $error++;
3825  }
3826 
3827  if (!$error) {
3828  $this->db->commit();
3829  return 1;
3830  } else {
3831  $this->db->rollback();
3832  return 0;
3833  }
3834  }
3835 
3858  public function fetchObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $clause = 'OR', $alsosametype = 1, $orderby = 'sourcetype', $loadalsoobjects = 1)
3859  {
3860  global $conf, $hookmanager, $action;
3861 
3862  // Important for pdf generation time reduction
3863  // This boolean is true if $this->linkedObjects has already been loaded with all objects linked without filter
3864  if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
3865  return 1;
3866  }
3867 
3868  $this->linkedObjectsIds = array();
3869  $this->linkedObjects = array();
3870 
3871  $justsource = false;
3872  $justtarget = false;
3873  $withtargettype = false;
3874  $withsourcetype = false;
3875 
3876  $parameters = array('sourcetype'=>$sourcetype, 'sourceid'=>$sourceid, 'targettype'=>$targettype, 'targetid'=>$targetid);
3877  // Hook for explicitly set the targettype if it must be differtent than $this->element
3878  $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3879  if ($reshook > 0) {
3880  if (!empty($hookmanager->resArray['sourcetype'])) $sourcetype = $hookmanager->resArray['sourcetype'];
3881  if (!empty($hookmanager->resArray['sourceid'])) $sourceid = $hookmanager->resArray['sourceid'];
3882  if (!empty($hookmanager->resArray['targettype'])) $targettype = $hookmanager->resArray['targettype'];
3883  if (!empty($hookmanager->resArray['targetid'])) $targetid = $hookmanager->resArray['targetid'];
3884  }
3885 
3886  if (!empty($sourceid) && !empty($sourcetype) && empty($targetid)) {
3887  $justsource = true; // the source (id and type) is a search criteria
3888  if (!empty($targettype)) {
3889  $withtargettype = true;
3890  }
3891  }
3892  if (!empty($targetid) && !empty($targettype) && empty($sourceid)) {
3893  $justtarget = true; // the target (id and type) is a search criteria
3894  if (!empty($sourcetype)) {
3895  $withsourcetype = true;
3896  }
3897  }
3898 
3899  $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
3900  $targetid = (!empty($targetid) ? $targetid : $this->id);
3901  $sourcetype = (!empty($sourcetype) ? $sourcetype : $this->element);
3902  $targettype = (!empty($targettype) ? $targettype : $this->element);
3903 
3904  /*if (empty($sourceid) && empty($targetid))
3905  {
3906  dol_syslog('Bad usage of function. No source nor target id defined (nor as parameter nor as object id)', LOG_ERR);
3907  return -1;
3908  }*/
3909 
3910  // Links between objects are stored in table element_element
3911  $sql = "SELECT rowid, fk_source, sourcetype, fk_target, targettype";
3912  $sql .= " FROM ".$this->db->prefix()."element_element";
3913  $sql .= " WHERE ";
3914  if ($justsource || $justtarget) {
3915  if ($justsource) {
3916  $sql .= "fk_source = ".((int) $sourceid)." AND sourcetype = '".$this->db->escape($sourcetype)."'";
3917  if ($withtargettype) {
3918  $sql .= " AND targettype = '".$this->db->escape($targettype)."'";
3919  }
3920  } elseif ($justtarget) {
3921  $sql .= "fk_target = ".((int) $targetid)." AND targettype = '".$this->db->escape($targettype)."'";
3922  if ($withsourcetype) {
3923  $sql .= " AND sourcetype = '".$this->db->escape($sourcetype)."'";
3924  }
3925  }
3926  } else {
3927  $sql .= "(fk_source = ".((int) $sourceid)." AND sourcetype = '".$this->db->escape($sourcetype)."')";
3928  $sql .= " ".$clause." (fk_target = ".((int) $targetid)." AND targettype = '".$this->db->escape($targettype)."')";
3929  if ($loadalsoobjects && $this->id > 0 && $sourceid == $this->id && $sourcetype == $this->element && $targetid == $this->id && $targettype == $this->element && $clause == 'OR') {
3930  $this->linkedObjectsFullLoaded[$this->id] = true;
3931  }
3932  }
3933  $sql .= " ORDER BY ".$orderby;
3934 
3935  dol_syslog(get_class($this)."::fetchObjectLink", LOG_DEBUG);
3936  $resql = $this->db->query($sql);
3937  if ($resql) {
3938  $num = $this->db->num_rows($resql);
3939  $i = 0;
3940  while ($i < $num) {
3941  $obj = $this->db->fetch_object($resql);
3942  if ($justsource || $justtarget) {
3943  if ($justsource) {
3944  $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
3945  } elseif ($justtarget) {
3946  $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
3947  }
3948  } else {
3949  if ($obj->fk_source == $sourceid && $obj->sourcetype == $sourcetype) {
3950  $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
3951  }
3952  if ($obj->fk_target == $targetid && $obj->targettype == $targettype) {
3953  $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
3954  }
3955  }
3956  $i++;
3957  }
3958 
3959  if (!empty($this->linkedObjectsIds)) {
3960  $tmparray = $this->linkedObjectsIds;
3961  foreach ($tmparray as $objecttype => $objectids) { // $objecttype is a module name ('facture', 'mymodule', ...) or a module name with a suffix ('project_task', 'mymodule_myobj', ...)
3962  // Parse element/subelement (ex: project_task, cabinetmed_consultation, ...)
3963  $module = $element = $subelement = $objecttype;
3964  $regs = array();
3965  if ($objecttype != 'supplier_proposal' && $objecttype != 'order_supplier' && $objecttype != 'invoice_supplier'
3966  && preg_match('/^([^_]+)_([^_]+)/i', $objecttype, $regs)) {
3967  $module = $element = $regs[1];
3968  $subelement = $regs[2];
3969  }
3970 
3971  $classpath = $element.'/class';
3972  // To work with non standard classpath or module name
3973  if ($objecttype == 'facture') {
3974  $classpath = 'compta/facture/class';
3975  } elseif ($objecttype == 'facturerec') {
3976  $classpath = 'compta/facture/class';
3977  $module = 'facture';
3978  } elseif ($objecttype == 'propal') {
3979  $classpath = 'comm/propal/class';
3980  } elseif ($objecttype == 'supplier_proposal') {
3981  $classpath = 'supplier_proposal/class';
3982  } elseif ($objecttype == 'shipping') {
3983  $classpath = 'expedition/class';
3984  $subelement = 'expedition';
3985  $module = 'expedition';
3986  } elseif ($objecttype == 'delivery') {
3987  $classpath = 'delivery/class';
3988  $subelement = 'delivery';
3989  $module = 'delivery_note';
3990  } elseif ($objecttype == 'invoice_supplier' || $objecttype == 'order_supplier') {
3991  $classpath = 'fourn/class';
3992  $module = 'fournisseur';
3993  } elseif ($objecttype == 'fichinter') {
3994  $classpath = 'fichinter/class';
3995  $subelement = 'fichinter';
3996  $module = 'ficheinter';
3997  } elseif ($objecttype == 'subscription') {
3998  $classpath = 'adherents/class';
3999  $module = 'adherent';
4000  } elseif ($objecttype == 'contact') {
4001  $module = 'societe';
4002  }
4003  // Set classfile
4004  $classfile = strtolower($subelement);
4005  $classname = ucfirst($subelement);
4006 
4007  if ($objecttype == 'order') {
4008  $classfile = 'commande';
4009  $classname = 'Commande';
4010  } elseif ($objecttype == 'invoice_supplier') {
4011  $classfile = 'fournisseur.facture';
4012  $classname = 'FactureFournisseur';
4013  } elseif ($objecttype == 'order_supplier') {
4014  $classfile = 'fournisseur.commande';
4015  $classname = 'CommandeFournisseur';
4016  } elseif ($objecttype == 'supplier_proposal') {
4017  $classfile = 'supplier_proposal';
4018  $classname = 'SupplierProposal';
4019  } elseif ($objecttype == 'facturerec') {
4020  $classfile = 'facture-rec';
4021  $classname = 'FactureRec';
4022  } elseif ($objecttype == 'subscription') {
4023  $classfile = 'subscription';
4024  $classname = 'Subscription';
4025  } elseif ($objecttype == 'project' || $objecttype == 'projet') {
4026  $classpath = 'projet/class';
4027  $classfile = 'project';
4028  $classname = 'Project';
4029  } elseif ($objecttype == 'conferenceorboothattendee') {
4030  $classpath = 'eventorganization/class';
4031  $classfile = 'conferenceorboothattendee';
4032  $classname = 'ConferenceOrBoothAttendee';
4033  $module = 'eventorganization';
4034  } elseif ($objecttype == 'conferenceorbooth') {
4035  $classpath = 'eventorganization/class';
4036  $classfile = 'conferenceorbooth';
4037  $classname = 'ConferenceOrBooth';
4038  $module = 'eventorganization';
4039  } elseif ($objecttype == 'mo') {
4040  $classpath = 'mrp/class';
4041  $classfile = 'mo';
4042  $classname = 'Mo';
4043  $module = 'mrp';
4044  }
4045 
4046  // Here $module, $classfile and $classname are set, we can use them.
4047  if (isModEnabled($module) && (($element != $this->element) || $alsosametype)) {
4048  if ($loadalsoobjects && (is_numeric($loadalsoobjects) || ($loadalsoobjects === $objecttype))) {
4049  dol_include_once('/'.$classpath.'/'.$classfile.'.class.php');
4050  //print '/'.$classpath.'/'.$classfile.'.class.php '.class_exists($classname);
4051  if (class_exists($classname)) {
4052  foreach ($objectids as $i => $objectid) { // $i is rowid into llx_element_element
4053  $object = new $classname($this->db);
4054  $ret = $object->fetch($objectid);
4055  if ($ret >= 0) {
4056  $this->linkedObjects[$objecttype][$i] = $object;
4057  }
4058  }
4059  }
4060  }
4061  } else {
4062  unset($this->linkedObjectsIds[$objecttype]);
4063  }
4064  }
4065  }
4066  return 1;
4067  } else {
4068  dol_print_error($this->db);
4069  return -1;
4070  }
4071  }
4072 
4079  public function clearObjectLinkedCache()
4080  {
4081  if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
4082  unset($this->linkedObjectsFullLoaded[$this->id]);
4083  }
4084 
4085  return 1;
4086  }
4087 
4100  public function updateObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $f_user = null, $notrigger = 0)
4101  {
4102  global $user;
4103  $updatesource = false;
4104  $updatetarget = false;
4105  $f_user = isset($f_user) ? $f_user : $user;
4106 
4107  if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
4108  $updatesource = true;
4109  } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
4110  $updatetarget = true;
4111  }
4112 
4113  $this->db->begin();
4114  $error = 0;
4115 
4116  $sql = "UPDATE " . $this->db->prefix() . "element_element SET ";
4117  if ($updatesource) {
4118  $sql .= "fk_source = " . ((int) $sourceid);
4119  $sql .= ", sourcetype = '" . $this->db->escape($sourcetype) . "'";
4120  $sql .= " WHERE fk_target = " . ((int) $this->id);
4121  $sql .= " AND targettype = '" . $this->db->escape($this->element) . "'";
4122  } elseif ($updatetarget) {
4123  $sql .= "fk_target = " . ((int) $targetid);
4124  $sql .= ", targettype = '" . $this->db->escape($targettype) . "'";
4125  $sql .= " WHERE fk_source = " . ((int) $this->id);
4126  $sql .= " AND sourcetype = '" . $this->db->escape($this->element) . "'";
4127  }
4128 
4129  dol_syslog(get_class($this) . "::updateObjectLinked", LOG_DEBUG);
4130  if ($this->db->query($sql)) {
4131  if (!$notrigger) {
4132  // Call trigger
4133  $this->context['link_source_id'] = $sourceid;
4134  $this->context['link_source_type'] = $sourcetype;
4135  $this->context['link_target_id'] = $targetid;
4136  $this->context['link_target_type'] = $targettype;
4137  $result = $this->call_trigger('OBJECT_LINK_MODIFY', $f_user);
4138  if ($result < 0) {
4139  $error++;
4140  }
4141  // End call triggers
4142  }
4143  } else {
4144  $this->error = $this->db->lasterror();
4145  $error++;
4146  }
4147 
4148  if (!$error) {
4149  $this->db->commit();
4150  return 1;
4151  } else {
4152  $this->db->rollback();
4153  return -1;
4154  }
4155  }
4156 
4170  public function deleteObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $rowid = '', $f_user = null, $notrigger = 0)
4171  {
4172  global $user;
4173  $deletesource = false;
4174  $deletetarget = false;
4175  $f_user = isset($f_user) ? $f_user : $user;
4176 
4177  if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
4178  $deletesource = true;
4179  } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
4180  $deletetarget = true;
4181  }
4182 
4183  $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
4184  $sourcetype = (!empty($sourcetype) ? $sourcetype : $this->element);
4185  $targetid = (!empty($targetid) ? $targetid : $this->id);
4186  $targettype = (!empty($targettype) ? $targettype : $this->element);
4187  $this->db->begin();
4188  $error = 0;
4189 
4190  if (!$notrigger) {
4191  // Call trigger
4192  $this->context['link_id'] = $rowid;
4193  $this->context['link_source_id'] = $sourceid;
4194  $this->context['link_source_type'] = $sourcetype;
4195  $this->context['link_target_id'] = $targetid;
4196  $this->context['link_target_type'] = $targettype;
4197  $result = $this->call_trigger('OBJECT_LINK_DELETE', $f_user);
4198  if ($result < 0) {
4199  $error++;
4200  }
4201  // End call triggers
4202  }
4203 
4204  if (!$error) {
4205  $sql = "DELETE FROM " . $this->db->prefix() . "element_element";
4206  $sql .= " WHERE";
4207  if ($rowid > 0) {
4208  $sql .= " rowid = " . ((int) $rowid);
4209  } else {
4210  if ($deletesource) {
4211  $sql .= " fk_source = " . ((int) $sourceid) . " AND sourcetype = '" . $this->db->escape($sourcetype) . "'";
4212  $sql .= " AND fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($this->element) . "'";
4213  } elseif ($deletetarget) {
4214  $sql .= " fk_target = " . ((int) $targetid) . " AND targettype = '" . $this->db->escape($targettype) . "'";
4215  $sql .= " AND fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($this->element) . "'";
4216  } else {
4217  $sql .= " (fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($this->element) . "')";
4218  $sql .= " OR";
4219  $sql .= " (fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($this->element) . "')";
4220  }
4221  }
4222 
4223  dol_syslog(get_class($this) . "::deleteObjectLinked", LOG_DEBUG);
4224  if (!$this->db->query($sql)) {
4225  $this->error = $this->db->lasterror();
4226  $this->errors[] = $this->error;
4227  $error++;
4228  }
4229  }
4230 
4231  if (!$error) {
4232  $this->db->commit();
4233  return 1;
4234  } else {
4235  $this->db->rollback();
4236  return 0;
4237  }
4238  }
4239 
4249  public static function getAllItemsLinkedByObjectID($fk_object_where, $field_select, $field_where, $table_element)
4250  {
4251  if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4252  return -1;
4253  }
4254 
4255  global $db;
4256 
4257  $sql = "SELECT ".$field_select." FROM ".$db->prefix().$table_element." WHERE ".$field_where." = ".((int) $fk_object_where);
4258  $resql = $db->query($sql);
4259 
4260  $TRes = array();
4261  if (!empty($resql)) {
4262  while ($res = $db->fetch_object($resql)) {
4263  $TRes[] = $res->{$field_select};
4264  }
4265  }
4266 
4267  return $TRes;
4268  }
4269 
4278  public static function deleteAllItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
4279  {
4280  if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4281  return -1;
4282  }
4283 
4284  global $db;
4285 
4286  $sql = "DELETE FROM ".$db->prefix().$table_element." WHERE ".$field_where." = ".((int) $fk_object_where);
4287  $resql = $db->query($sql);
4288 
4289  if (empty($resql)) {
4290  return 0;
4291  }
4292 
4293  return 1;
4294  }
4295 
4306  public function setStatut($status, $elementId = null, $elementType = '', $trigkey = '', $fieldstatus = 'fk_statut')
4307  {
4308  global $user, $langs, $conf;
4309 
4310  $savElementId = $elementId; // To be used later to know if we were using the method using the id of this or not.
4311 
4312  $elementId = (!empty($elementId) ? $elementId : $this->id);
4313  $elementTable = (!empty($elementType) ? $elementType : $this->table_element);
4314 
4315  $this->db->begin();
4316 
4317  if ($elementTable == 'facture_rec') {
4318  $fieldstatus = "suspended";
4319  }
4320  if ($elementTable == 'mailing') {
4321  $fieldstatus = "statut";
4322  }
4323  if ($elementTable == 'cronjob') {
4324  $fieldstatus = "status";
4325  }
4326  if ($elementTable == 'user') {
4327  $fieldstatus = "statut";
4328  }
4329  if ($elementTable == 'expensereport') {
4330  $fieldstatus = "fk_statut";
4331  }
4332  if ($elementTable == 'commande_fournisseur_dispatch') {
4333  $fieldstatus = "status";
4334  }
4335  if (isset($this->fields) && is_array($this->fields) && array_key_exists('status', $this->fields)) {
4336  $fieldstatus = 'status';
4337  }
4338 
4339  $sql = "UPDATE ".$this->db->prefix().$elementTable;
4340  $sql .= " SET ".$fieldstatus." = ".((int) $status);
4341  // If status = 1 = validated, update also fk_user_valid
4342  // TODO Replace the test on $elementTable by doing a test on existence of the field in $this->fields
4343  if ($status == 1 && in_array($elementTable, array('expensereport', 'inventory'))) {
4344  $sql .= ", fk_user_valid = ".((int) $user->id);
4345  }
4346  if ($status == 1 && in_array($elementTable, array('expensereport'))) {
4347  $sql .= ", date_valid = '".$this->db->idate(dol_now())."'";
4348  }
4349  if ($status == 1 && in_array($elementTable, array('inventory'))) {
4350  $sql .= ", date_validation = '".$this->db->idate(dol_now())."'";
4351  }
4352  $sql .= " WHERE rowid = ".((int) $elementId);
4353  $sql .= " AND ".$fieldstatus." <> ".((int) $status); // We avoid update if status already correct
4354 
4355  dol_syslog(get_class($this)."::setStatut", LOG_DEBUG);
4356  $resql = $this->db->query($sql);
4357  if ($resql) {
4358  $error = 0;
4359 
4360  $nb_rows_affected = $this->db->affected_rows($resql); // should be 1 or 0 if status was already correct
4361 
4362  if ($nb_rows_affected > 0) {
4363  if (empty($trigkey)) {
4364  // Try to guess trigkey (for backward compatibility, now we should have trigkey defined into the call of setStatus)
4365  if ($this->element == 'supplier_proposal' && $status == 2) {
4366  $trigkey = 'SUPPLIER_PROPOSAL_SIGN'; // 2 = SupplierProposal::STATUS_SIGNED. Can't use constant into this generic class
4367  }
4368  if ($this->element == 'supplier_proposal' && $status == 3) {
4369  $trigkey = 'SUPPLIER_PROPOSAL_REFUSE'; // 3 = SupplierProposal::STATUS_REFUSED. Can't use constant into this generic class
4370  }
4371  if ($this->element == 'supplier_proposal' && $status == 4) {
4372  $trigkey = 'SUPPLIER_PROPOSAL_CLOSE'; // 4 = SupplierProposal::STATUS_CLOSED. Can't use constant into this generic class
4373  }
4374  if ($this->element == 'fichinter' && $status == 3) {
4375  $trigkey = 'FICHINTER_CLASSIFY_DONE';
4376  }
4377  if ($this->element == 'fichinter' && $status == 2) {
4378  $trigkey = 'FICHINTER_CLASSIFY_BILLED';
4379  }
4380  if ($this->element == 'fichinter' && $status == 1) {
4381  $trigkey = 'FICHINTER_CLASSIFY_UNBILLED';
4382  }
4383  }
4384 
4385  if ($trigkey) {
4386  // Call trigger
4387  $result = $this->call_trigger($trigkey, $user);
4388  if ($result < 0) {
4389  $error++;
4390  }
4391  // End call triggers
4392  }
4393  } else {
4394  // The status was probably already good. We do nothing more, no triggers.
4395  }
4396 
4397  if (!$error) {
4398  $this->db->commit();
4399 
4400  if (empty($savElementId)) {
4401  // If the element we update is $this (so $elementId was provided as null)
4402  if ($fieldstatus == 'tosell') {
4403  $this->status = $status;
4404  } elseif ($fieldstatus == 'tobuy') {
4405  $this->status_buy = $status;
4406  } else {
4407  $this->statut = $status;
4408  $this->status = $status;
4409  }
4410  }
4411 
4412  return 1;
4413  } else {
4414  $this->db->rollback();
4415  dol_syslog(get_class($this)."::setStatut ".$this->error, LOG_ERR);
4416  return -1;
4417  }
4418  } else {
4419  $this->error = $this->db->lasterror();
4420  $this->db->rollback();
4421  return -1;
4422  }
4423  }
4424 
4425 
4433  public function getCanvas($id = 0, $ref = '')
4434  {
4435  global $conf;
4436 
4437  if (empty($id) && empty($ref)) {
4438  return 0;
4439  }
4440  if (!empty($conf->global->MAIN_DISABLE_CANVAS)) {
4441  return 0; // To increase speed. Not enabled by default.
4442  }
4443 
4444  // Clean parameters
4445  $ref = trim($ref);
4446 
4447  $sql = "SELECT rowid, canvas";
4448  $sql .= " FROM ".$this->db->prefix().$this->table_element;
4449  $sql .= " WHERE entity IN (".getEntity($this->element).")";
4450  if (!empty($id)) {
4451  $sql .= " AND rowid = ".((int) $id);
4452  }
4453  if (!empty($ref)) {
4454  $sql .= " AND ref = '".$this->db->escape($ref)."'";
4455  }
4456 
4457  $resql = $this->db->query($sql);
4458  if ($resql) {
4459  $obj = $this->db->fetch_object($resql);
4460  if ($obj) {
4461  $this->canvas = $obj->canvas;
4462  return 1;
4463  } else {
4464  return 0;
4465  }
4466  } else {
4467  dol_print_error($this->db);
4468  return -1;
4469  }
4470  }
4471 
4472 
4479  public function getSpecialCode($lineid)
4480  {
4481  $sql = "SELECT special_code FROM ".$this->db->prefix().$this->table_element_line;
4482  $sql .= " WHERE rowid = ".((int) $lineid);
4483  $resql = $this->db->query($sql);
4484  if ($resql) {
4485  $row = $this->db->fetch_row($resql);
4486  return $row[0];
4487  }
4488 
4489  return 0;
4490  }
4491 
4500  public function isObjectUsed($id = 0, $entity = 0)
4501  {
4502  global $langs;
4503 
4504  if (empty($id)) {
4505  $id = $this->id;
4506  }
4507 
4508  // Check parameters
4509  if (!isset($this->childtables) || !is_array($this->childtables) || count($this->childtables) == 0) {
4510  dol_print_error('Called isObjectUsed on a class with property this->childtables not defined');
4511  return -1;
4512  }
4513 
4514  $arraytoscan = $this->childtables; // array('tablename'=>array('fk_element'=>'parentfield'), ...) or array('tablename'=>array('parent'=>table_parent, 'parentkey'=>'nameoffieldforparentfkkey'), ...)
4515  // For backward compatibility, we check if array is old format array('tablename1', 'tablename2', ...)
4516  $tmparray = array_keys($this->childtables);
4517  if (is_numeric($tmparray[0])) {
4518  $arraytoscan = array_flip($this->childtables);
4519  }
4520 
4521  // Test if child exists
4522  $haschild = 0;
4523  foreach ($arraytoscan as $table => $element) {
4524  //print $id.'-'.$table.'-'.$elementname.'<br>';
4525  // Check if element can be deleted
4526  $sql = "SELECT COUNT(*) as nb";
4527  $sql.= " FROM ".$this->db->prefix().$table." as c";
4528  if (!empty($element['parent']) && !empty($element['parentkey'])) {
4529  $sql.= ", ".$this->db->prefix().$element['parent']." as p";
4530  }
4531  if (!empty($element['fk_element'])) {
4532  $sql.= " WHERE c.".$element['fk_element']." = ".((int) $id);
4533  } else {
4534  $sql.= " WHERE c.".$this->fk_element." = ".((int) $id);
4535  }
4536  if (!empty($element['parent']) && !empty($element['parentkey'])) {
4537  $sql.= " AND c.".$element['parentkey']." = p.rowid";
4538  }
4539  if (!empty($element['parent']) && !empty($element['parenttypefield']) && !empty($element['parenttypevalue'])) {
4540  $sql.= " AND c.".$element['parenttypefield']." = '".$this->db->escape($element['parenttypevalue'])."'";
4541  }
4542  if (!empty($entity)) {
4543  if (!empty($element['parent']) && !empty($element['parentkey'])) {
4544  $sql.= " AND p.entity = ".((int) $entity);
4545  } else {
4546  $sql.= " AND c.entity = ".((int) $entity);
4547  }
4548  }
4549 
4550  $resql = $this->db->query($sql);
4551  if ($resql) {
4552  $obj = $this->db->fetch_object($resql);
4553  if ($obj->nb > 0) {
4554  $langs->load("errors");
4555  //print 'Found into table '.$table.', type '.$langs->transnoentitiesnoconv($elementname).', haschild='.$haschild;
4556  $haschild += $obj->nb;
4557  if (is_numeric($element)) { // very old usage array('table1', 'table2', ...)
4558  $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $table);
4559  } elseif (is_string($element)) { // old usage array('table1' => 'TranslateKey1', 'table2' => 'TranslateKey2', ...)
4560  $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element));
4561  } else { // new usage: $element['name']=Translation key
4562  $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element['name']));
4563  }
4564  break; // We found at least one, we stop here
4565  }
4566  } else {
4567  $this->errors[] = $this->db->lasterror();
4568  return -1;
4569  }
4570  }
4571  if ($haschild > 0) {
4572  $this->errors[] = "ErrorRecordHasChildren";
4573  return $haschild;
4574  } else {
4575  return 0;
4576  }
4577  }
4578 
4585  public function hasProductsOrServices($predefined = -1)
4586  {
4587  $nb = 0;
4588 
4589  foreach ($this->lines as $key => $val) {
4590  $qualified = 0;
4591  if ($predefined == -1) {
4592  $qualified = 1;
4593  }
4594  if ($predefined == 1 && $val->fk_product > 0) {
4595  $qualified = 1;
4596  }
4597  if ($predefined == 0 && $val->fk_product <= 0) {
4598  $qualified = 1;
4599  }
4600  if ($predefined == 2 && $val->fk_product > 0 && $val->product_type == 0) {
4601  $qualified = 1;
4602  }
4603  if ($predefined == 3 && $val->fk_product > 0 && $val->product_type == 1) {
4604  $qualified = 1;
4605  }
4606  if ($qualified) {
4607  $nb++;
4608  }
4609  }
4610  dol_syslog(get_class($this).'::hasProductsOrServices we found '.$nb.' qualified lines of products/servcies');
4611  return $nb;
4612  }
4613 
4619  public function getTotalDiscount()
4620  {
4621  if (!empty($this->table_element_line) ) {
4622  $total_discount = 0.00;
4623 
4624  $sql = "SELECT subprice as pu_ht, qty, remise_percent, total_ht";
4625  $sql .= " FROM ".$this->db->prefix().$this->table_element_line;
4626  $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
4627 
4628  dol_syslog(get_class($this).'::getTotalDiscount', LOG_DEBUG);
4629  $resql = $this->db->query($sql);
4630  if ($resql) {
4631  $num = $this->db->num_rows($resql);
4632  $i = 0;
4633  while ($i < $num) {
4634  $obj = $this->db->fetch_object($resql);
4635 
4636  $pu_ht = $obj->pu_ht;
4637  $qty = $obj->qty;
4638  $total_ht = $obj->total_ht;
4639 
4640  $total_discount_line = floatval(price2num(($pu_ht * $qty) - $total_ht, 'MT'));
4641  $total_discount += $total_discount_line;
4642 
4643  $i++;
4644  }
4645  }
4646 
4647  //print $total_discount; exit;
4648  return price2num($total_discount);
4649  }
4650 
4651  return null;
4652  }
4653 
4654 
4661  public function getTotalWeightVolume()
4662  {
4663  $totalWeight = 0;
4664  $totalVolume = 0;
4665  // defined for shipment only
4666  $totalOrdered = '';
4667  // defined for shipment only
4668  $totalToShip = '';
4669 
4670  foreach ($this->lines as $line) {
4671  if (isset($line->qty_asked)) {
4672  if (empty($totalOrdered)) {
4673  $totalOrdered = 0; // Avoid warning because $totalOrdered is ''
4674  }
4675  $totalOrdered += $line->qty_asked; // defined for shipment only
4676  }
4677  if (isset($line->qty_shipped)) {
4678  if (empty($totalToShip)) {
4679  $totalToShip = 0; // Avoid warning because $totalToShip is ''
4680  }
4681  $totalToShip += $line->qty_shipped; // defined for shipment only
4682  } elseif ($line->element == 'commandefournisseurdispatch' && isset($line->qty)) {
4683  if (empty($totalToShip)) {
4684  $totalToShip = 0;
4685  }
4686  $totalToShip += $line->qty; // defined for reception only
4687  }
4688 
4689  // Define qty, weight, volume, weight_units, volume_units
4690  if ($this->element == 'shipping') {
4691  // for shipments
4692  $qty = $line->qty_shipped ? $line->qty_shipped : 0;
4693  } else {
4694  $qty = $line->qty ? $line->qty : 0;
4695  }
4696 
4697  $weight = !empty($line->weight) ? $line->weight : 0;
4698  ($weight == 0 && !empty($line->product->weight)) ? $weight = $line->product->weight : 0;
4699  $volume = !empty($line->volume) ? $line->volume : 0;
4700  ($volume == 0 && !empty($line->product->volume)) ? $volume = $line->product->volume : 0;
4701 
4702  $weight_units = !empty($line->weight_units) ? $line->weight_units : 0;
4703  ($weight_units == 0 && !empty($line->product->weight_units)) ? $weight_units = $line->product->weight_units : 0;
4704  $volume_units = !empty($line->volume_units) ? $line->volume_units : 0;
4705  ($volume_units == 0 && !empty($line->product->volume_units)) ? $volume_units = $line->product->volume_units : 0;
4706 
4707  $weightUnit = 0;
4708  $volumeUnit = 0;
4709  if (!empty($weight_units)) {
4710  $weightUnit = $weight_units;
4711  }
4712  if (!empty($volume_units)) {
4713  $volumeUnit = $volume_units;
4714  }
4715 
4716  if (empty($totalWeight)) {
4717  $totalWeight = 0; // Avoid warning because $totalWeight is ''
4718  }
4719  if (empty($totalVolume)) {
4720  $totalVolume = 0; // Avoid warning because $totalVolume is ''
4721  }
4722 
4723  //var_dump($line->volume_units);
4724  if ($weight_units < 50) { // < 50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
4725  $trueWeightUnit = pow(10, $weightUnit);
4726  $totalWeight += $weight * $qty * $trueWeightUnit;
4727  } else {
4728  if ($weight_units == 99) {
4729  // conversion 1 Pound = 0.45359237 KG
4730  $trueWeightUnit = 0.45359237;
4731  $totalWeight += $weight * $qty * $trueWeightUnit;
4732  } elseif ($weight_units == 98) {
4733  // conversion 1 Ounce = 0.0283495 KG
4734  $trueWeightUnit = 0.0283495;
4735  $totalWeight += $weight * $qty * $trueWeightUnit;
4736  } else {
4737  $totalWeight += $weight * $qty; // This may be wrong if we mix different units
4738  }
4739  }
4740  if ($volume_units < 50) { // >50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
4741  //print $line->volume."x".$line->volume_units."x".($line->volume_units < 50)."x".$volumeUnit;
4742  $trueVolumeUnit = pow(10, $volumeUnit);
4743  //print $line->volume;
4744  $totalVolume += $volume * $qty * $trueVolumeUnit;
4745  } else {
4746  $totalVolume += $volume * $qty; // This may be wrong if we mix different units
4747  }
4748  }
4749 
4750  return array('weight'=>$totalWeight, 'volume'=>$totalVolume, 'ordered'=>$totalOrdered, 'toship'=>$totalToShip);
4751  }
4752 
4753 
4759  public function setExtraParameters()
4760  {
4761  $this->db->begin();
4762 
4763  $extraparams = (!empty($this->extraparams) ? json_encode($this->extraparams) : null);
4764 
4765  $sql = "UPDATE ".$this->db->prefix().$this->table_element;
4766  $sql .= " SET extraparams = ".(!empty($extraparams) ? "'".$this->db->escape($extraparams)."'" : "null");
4767  $sql .= " WHERE rowid = ".((int) $this->id);
4768 
4769  dol_syslog(get_class($this)."::setExtraParameters", LOG_DEBUG);
4770  $resql = $this->db->query($sql);
4771  if (!$resql) {
4772  $this->error = $this->db->lasterror();
4773  $this->db->rollback();
4774  return -1;
4775  } else {
4776  $this->db->commit();
4777  return 1;
4778  }
4779  }
4780 
4781 
4782  // --------------------
4783  // TODO: All functions here must be redesigned and moved as they are not business functions but output functions
4784  // --------------------
4785 
4786  /* This is to show add lines */
4787 
4797  public function formAddObjectLine($dateSelector, $seller, $buyer, $defaulttpldir = '/core/tpl')
4798  {
4799  global $conf, $user, $langs, $object, $hookmanager, $extrafields;
4800  global $form;
4801 
4802  // Line extrafield
4803  if (!is_object($extrafields)) {
4804  require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
4805  $extrafields = new ExtraFields($this->db);
4806  }
4807  $extrafields->fetch_name_optionals_label($this->table_element_line);
4808 
4809  // Output template part (modules that overwrite templates must declare this into descriptor)
4810  // Use global variables + $dateSelector + $seller and $buyer
4811  // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook 'formAddObjectLine'.
4812  $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
4813  foreach ($dirtpls as $module => $reldir) {
4814  if (!empty($module)) {
4815  $tpl = dol_buildpath($reldir.'/objectline_create.tpl.php');
4816  } else {
4817  $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_create.tpl.php';
4818  }
4819 
4820  if (empty($conf->file->strict_mode)) {
4821  $res = @include $tpl;
4822  } else {
4823  $res = include $tpl; // for debug
4824  }
4825  if ($res) {
4826  break;
4827  }
4828  }
4829  }
4830 
4831 
4832 
4833  /* This is to show array of line of details */
4834 
4835 
4850  public function printObjectLines($action, $seller, $buyer, $selected = 0, $dateSelector = 0, $defaulttpldir = '/core/tpl')
4851  {
4852  global $conf, $hookmanager, $langs, $user, $form, $extrafields, $object;
4853  // TODO We should not use global var for this
4854  global $inputalsopricewithtax, $usemargins, $disableedit, $disablemove, $disableremove, $outputalsopricetotalwithtax;
4855 
4856  // Define usemargins
4857  $usemargins = 0;
4858  if (isModEnabled('margin') && !empty($this->element) && in_array($this->element, array('facture', 'facturerec', 'propal', 'commande'))) {
4859  $usemargins = 1;
4860  }
4861 
4862  $num = count($this->lines);
4863 
4864  // Line extrafield
4865  if (!is_object($extrafields)) {
4866  require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
4867  $extrafields = new ExtraFields($this->db);
4868  }
4869  $extrafields->fetch_name_optionals_label($this->table_element_line);
4870 
4871  $parameters = array('num'=>$num, 'dateSelector'=>$dateSelector, 'seller'=>$seller, 'buyer'=>$buyer, 'selected'=>$selected, 'table_element_line'=>$this->table_element_line);
4872  $reshook = $hookmanager->executeHooks('printObjectLineTitle', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4873  if (empty($reshook)) {
4874  // Output template part (modules that overwrite templates must declare this into descriptor)
4875  // Use global variables + $dateSelector + $seller and $buyer
4876  // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook.
4877  $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
4878  foreach ($dirtpls as $module => $reldir) {
4879  $res = 0;
4880  if (!empty($module)) {
4881  $tpl = dol_buildpath($reldir.'/objectline_title.tpl.php');
4882  } else {
4883  $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_title.tpl.php';
4884  }
4885  if (file_exists($tpl)) {
4886  if (empty($conf->file->strict_mode)) {
4887  $res = @include $tpl;
4888  } else {
4889  $res = include $tpl; // for debug
4890  }
4891  }
4892  if ($res) {
4893  break;
4894  }
4895  }
4896  }
4897 
4898  $i = 0;
4899 
4900  print "<!-- begin printObjectLines() --><tbody>\n";
4901  foreach ($this->lines as $line) {
4902  //Line extrafield
4903  $line->fetch_optionals();
4904 
4905  //if (is_object($hookmanager) && (($line->product_type == 9 && !empty($line->special_code)) || !empty($line->fk_parent_line)))
4906  if (is_object($hookmanager)) { // Old code is commented on preceding line.
4907  if (empty($line->fk_parent_line)) {
4908  $parameters = array('line'=>$line, 'num'=>$num, 'i'=>$i, 'dateSelector'=>$dateSelector, 'seller'=>$seller, 'buyer'=>$buyer, 'selected'=>$selected, 'table_element_line'=>$line->table_element);
4909  $reshook = $hookmanager->executeHooks('printObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4910  } else {
4911  $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);
4912  $reshook = $hookmanager->executeHooks('printObjectSubLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4913  }
4914  }
4915  if (empty($reshook)) {
4916  $this->printObjectLine($action, $line, '', $num, $i, $dateSelector, $seller, $buyer, $selected, $extrafields, $defaulttpldir);
4917  }
4918 
4919  $i++;
4920  }
4921  print "</tbody><!-- end printObjectLines() -->\n";
4922  }
4923 
4941  public function printObjectLine($action, $line, $var, $num, $i, $dateSelector, $seller, $buyer, $selected = 0, $extrafields = null, $defaulttpldir = '/core/tpl')
4942  {
4943  global $conf, $langs, $user, $object, $hookmanager;
4944  global $form;
4945  global $object_rights, $disableedit, $disablemove, $disableremove; // TODO We should not use global var for this !
4946 
4947  $object_rights = $this->getRights();
4948 
4949  $text = '';
4950  $description = '';
4951 
4952  // Line in view mode
4953  if ($action != 'editline' || $selected != $line->id) {
4954  // Product
4955  if (!empty($line->fk_product) && $line->fk_product > 0) {
4956  $product_static = new Product($this->db);
4957  $product_static->fetch($line->fk_product);
4958 
4959  $product_static->ref = $line->ref; //can change ref in hook
4960  $product_static->label = !empty($line->label) ? $line->label : ""; //can change label in hook
4961 
4962  $text = $product_static->getNomUrl(1);
4963 
4964  // Define output language and label
4965  if (getDolGlobalInt('MAIN_MULTILANGS')) {
4966  if (property_exists($this, 'socid') && !is_object($this->thirdparty)) {
4967  dol_print_error('', 'Error: Method printObjectLine was called on an object and object->fetch_thirdparty was not done before');
4968  return;
4969  }
4970 
4971  $prod = new Product($this->db);
4972  $prod->fetch($line->fk_product);
4973 
4974  $outputlangs = $langs;
4975  $newlang = '';
4976  if (empty($newlang) && GETPOST('lang_id', 'aZ09')) {
4977  $newlang = GETPOST('lang_id', 'aZ09');
4978  }
4979  if (!empty($conf->global->PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE) && empty($newlang) && is_object($this->thirdparty)) {
4980  $newlang = $this->thirdparty->default_lang; // To use language of customer
4981  }
4982  if (!empty($newlang)) {
4983  $outputlangs = new Translate("", $conf);
4984  $outputlangs->setDefaultLang($newlang);
4985  }
4986 
4987  $label = (!empty($prod->multilangs[$outputlangs->defaultlang]["label"])) ? $prod->multilangs[$outputlangs->defaultlang]["label"] : $line->product_label;
4988  } else {
4989  $label = $line->product_label;
4990  }
4991 
4992  $text .= ' - '.(!empty($line->label) ? $line->label : $label);
4993  $description .= (getDolGlobalInt('PRODUIT_DESC_IN_FORM_ACCORDING_TO_DEVICE') ? '' : (!empty($line->description) ? dol_htmlentitiesbr($line->description) : '')); // Description is what to show on popup. We shown nothing if already into desc.
4994  }
4995 
4996  $line->pu_ttc = price2num((!empty($line->subprice) ? $line->subprice : 0) * (1 + ((!empty($line->tva_tx) ? $line->tva_tx : 0) / 100)), 'MU');
4997 
4998  // Output template part (modules that overwrite templates must declare this into descriptor)
4999  // Use global variables + $dateSelector + $seller and $buyer
5000  // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5001  $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5002  foreach ($dirtpls as $module => $reldir) {
5003  $res = 0;
5004  if (!empty($module)) {
5005  $tpl = dol_buildpath($reldir.'/objectline_view.tpl.php');
5006  } else {
5007  $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_view.tpl.php';
5008  }
5009  if (file_exists($tpl)) {
5010  if (empty($conf->file->strict_mode)) {
5011  $res = @include $tpl;
5012  } else {
5013  $res = include $tpl; // for debug
5014  }
5015  }
5016  if ($res) {
5017  break;
5018  }
5019  }
5020  }
5021 
5022  // Line in update mode
5023  if ($this->statut == 0 && $action == 'editline' && $selected == $line->id) {
5024  $label = (!empty($line->label) ? $line->label : (($line->fk_product > 0) ? $line->product_label : ''));
5025 
5026  $line->pu_ttc = price2num($line->subprice * (1 + ($line->tva_tx / 100)), 'MU');
5027 
5028  // Output template part (modules that overwrite templates must declare this into descriptor)
5029  // Use global variables + $dateSelector + $seller and $buyer
5030  // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5031  $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5032  foreach ($dirtpls as $module => $reldir) {
5033  if (!empty($module)) {
5034  $tpl = dol_buildpath($reldir.'/objectline_edit.tpl.php');
5035  } else {
5036  $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_edit.tpl.php';
5037  }
5038 
5039  if (empty($conf->file->strict_mode)) {
5040  $res = @include $tpl;
5041  } else {
5042  $res = include $tpl; // for debug
5043  }
5044  if ($res) {
5045  break;
5046  }
5047  }
5048  }
5049  }
5050 
5051 
5052  /* This is to show array of line of details of source object */
5053 
5054 
5065  public function printOriginLinesList($restrictlist = '', $selectedLines = array())
5066  {
5067  global $langs, $hookmanager, $conf, $form, $action;
5068 
5069  print '<tr class="liste_titre">';
5070  print '<td class="linecolref">'.$langs->trans('Ref').'</td>';
5071  print '<td class="linecoldescription">'.$langs->trans('Description').'</td>';
5072  print '<td class="linecolvat right">'.$langs->trans('VATRate').'</td>';
5073  print '<td class="linecoluht right">'.$langs->trans('PriceUHT').'</td>';
5074  if (isModEnabled("multicurrency")) {
5075  print '<td class="linecoluht_currency right">'.$langs->trans('PriceUHTCurrency').'</td>';
5076  }
5077  print '<td class="linecolqty right">'.$langs->trans('Qty').'</td>';
5078  if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
5079  print '<td class="linecoluseunit left">'.$langs->trans('Unit').'</td>';
5080  }
5081  print '<td class="linecoldiscount right">'.$langs->trans('ReductionShort').'</td>';
5082  print '<td class="linecolht right">'.$langs->trans('TotalHT').'</td>';
5083  print '<td class="center">'.$form->showCheckAddButtons('checkforselect', 1).'</td>';
5084  print '</tr>';
5085  $i = 0;
5086 
5087  if (!empty($this->lines)) {
5088  foreach ($this->lines as $line) {
5089  $reshook = 0;
5090  //if (is_object($hookmanager) && (($line->product_type == 9 && !empty($line->special_code)) || !empty($line->fk_parent_line))) {
5091  if (is_object($hookmanager)) { // Old code is commented on preceding line.
5092  $parameters = array('line'=>$line, 'i'=>$i, 'restrictlist'=>$restrictlist, 'selectedLines'=> $selectedLines);
5093  if (!empty($line->fk_parent_line)) { $parameters['fk_parent_line'] = $line->fk_parent_line; }
5094  $reshook = $hookmanager->executeHooks('printOriginObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5095  }
5096  if (empty($reshook)) {
5097  $this->printOriginLine($line, '', $restrictlist, '/core/tpl', $selectedLines);
5098  }
5099 
5100  $i++;
5101  }
5102  }
5103  }
5104 
5118  public function printOriginLine($line, $var, $restrictlist = '', $defaulttpldir = '/core/tpl', $selectedLines = array())
5119  {
5120  global $langs, $conf;
5121 
5122  //var_dump($line);
5123  if (!empty($line->date_start)) {
5124  $date_start = $line->date_start;
5125  } else {
5126  $date_start = $line->date_debut_prevue;
5127  if ($line->date_debut_reel) {
5128  $date_start = $line->date_debut_reel;
5129  }
5130  }
5131  if (!empty($line->date_end)) {
5132  $date_end = $line->date_end;
5133  } else {
5134  $date_end = $line->date_fin_prevue;
5135  if ($line->date_fin_reel) {
5136  $date_end = $line->date_fin_reel;
5137  }
5138  }
5139 
5140  $this->tpl['id'] = $line->id;
5141 
5142  $this->tpl['label'] = '';
5143  if (!empty($line->fk_parent_line)) {
5144  $this->tpl['label'] .= img_picto('', 'rightarrow');
5145  }
5146 
5147  if (($line->info_bits & 2) == 2) { // TODO Not sure this is used for source object
5148  $discount = new DiscountAbsolute($this->db);
5149  $discount->fk_soc = $this->socid;
5150  $this->tpl['label'] .= $discount->getNomUrl(0, 'discount');
5151  } elseif (!empty($line->fk_product)) {
5152  $productstatic = new Product($this->db);
5153  $productstatic->id = $line->fk_product;
5154  $productstatic->ref = $line->ref;
5155  $productstatic->type = $line->fk_product_type;
5156  if (empty($productstatic->ref)) {
5157  $line->fetch_product();
5158  $productstatic = $line->product;
5159  }
5160 
5161  $this->tpl['label'] .= $productstatic->getNomUrl(1);
5162  $this->tpl['label'] .= ' - '.(!empty($line->label) ? $line->label : $line->product_label);
5163  // Dates
5164  if ($line->product_type == 1 && ($date_start || $date_end)) {
5165  $this->tpl['label'] .= get_date_range($date_start, $date_end);
5166  }
5167  } else {
5168  $this->tpl['label'] .= ($line->product_type == -1 ? '&nbsp;' : ($line->product_type == 1 ? img_object($langs->trans(''), 'service') : img_object($langs->trans(''), 'product')));
5169  if (!empty($line->desc)) {
5170  $this->tpl['label'] .= $line->desc;
5171  } else {
5172  $this->tpl['label'] .= ($line->label ? '&nbsp;'.$line->label : '');
5173  }
5174 
5175  // Dates
5176  if ($line->product_type == 1 && ($date_start || $date_end)) {
5177  $this->tpl['label'] .= get_date_range($date_start, $date_end);
5178  }
5179  }
5180 
5181  if (!empty($line->desc)) {
5182  if ($line->desc == '(CREDIT_NOTE)') { // TODO Not sure this is used for source object
5183  $discount = new DiscountAbsolute($this->db);
5184  $discount->fetch($line->fk_remise_except);
5185  $this->tpl['description'] = $langs->transnoentities("DiscountFromCreditNote", $discount->getNomUrl(0));
5186  } elseif ($line->desc == '(DEPOSIT)') { // TODO Not sure this is used for source object
5187  $discount = new DiscountAbsolute($this->db);
5188  $discount->fetch($line->fk_remise_except);
5189  $this->tpl['description'] = $langs->transnoentities("DiscountFromDeposit", $discount->getNomUrl(0));
5190  } elseif ($line->desc == '(EXCESS RECEIVED)') {
5191  $discount = new DiscountAbsolute($this->db);
5192  $discount->fetch($line->fk_remise_except);
5193  $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessReceived", $discount->getNomUrl(0));
5194  } elseif ($line->desc == '(EXCESS PAID)') {
5195  $discount = new DiscountAbsolute($this->db);
5196  $discount->fetch($line->fk_remise_except);
5197  $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessPaid", $discount->getNomUrl(0));
5198  } else {
5199  $this->tpl['description'] = dol_trunc($line->desc, 60);
5200  }
5201  } else {
5202  $this->tpl['description'] = '&nbsp;';
5203  }
5204 
5205  // VAT Rate
5206  $this->tpl['vat_rate'] = vatrate($line->tva_tx, true);
5207  $this->tpl['vat_rate'] .= (($line->info_bits & 1) == 1) ? '*' : '';
5208  if (!empty($line->vat_src_code) && !preg_match('/\(/', $this->tpl['vat_rate'])) {
5209  $this->tpl['vat_rate'] .= ' ('.$line->vat_src_code.')';
5210  }
5211 
5212  $this->tpl['price'] = price($line->subprice);
5213  $this->tpl['total_ht'] = price($line->total_ht);
5214  $this->tpl['multicurrency_price'] = price($line->multicurrency_subprice);
5215  $this->tpl['qty'] = (($line->info_bits & 2) != 2) ? $line->qty : '&nbsp;';
5216  if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
5217  $this->tpl['unit'] = $langs->transnoentities($line->getLabelOfUnit('long'));
5218  }
5219  $this->tpl['remise_percent'] = (($line->info_bits & 2) != 2) ? vatrate($line->remise_percent, true) : '&nbsp;';
5220 
5221  // Is the line strike or not
5222  $this->tpl['strike'] = 0;
5223  if ($restrictlist == 'services' && $line->product_type != Product::TYPE_SERVICE) {
5224  $this->tpl['strike'] = 1;
5225  }
5226 
5227  // Output template part (modules that overwrite templates must declare this into descriptor)
5228  // Use global variables + $dateSelector + $seller and $buyer
5229  $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5230  foreach ($dirtpls as $module => $reldir) {
5231  if (!empty($module)) {
5232  $tpl = dol_buildpath($reldir.'/originproductline.tpl.php');
5233  } else {
5234  $tpl = DOL_DOCUMENT_ROOT.$reldir.'/originproductline.tpl.php';
5235  }
5236 
5237  if (empty($conf->file->strict_mode)) {
5238  $res = @include $tpl;
5239  } else {
5240  $res = include $tpl; // for debug
5241  }
5242  if ($res) {
5243  break;
5244  }
5245  }
5246  }
5247 
5248 
5249  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5260  public function add_element_resource($resource_id, $resource_type, $busy = 0, $mandatory = 0)
5261  {
5262  // phpcs:enable
5263  $this->db->begin();
5264 
5265  $sql = "INSERT INTO ".$this->db->prefix()."element_resources (";
5266  $sql .= "resource_id";
5267  $sql .= ", resource_type";
5268  $sql .= ", element_id";
5269  $sql .= ", element_type";
5270  $sql .= ", busy";
5271  $sql .= ", mandatory";
5272  $sql .= ") VALUES (";
5273  $sql .= ((int) $resource_id);
5274  $sql .= ", '".$this->db->escape($resource_type)."'";
5275  $sql .= ", '".$this->db->escape($this->id)."'";
5276  $sql .= ", '".$this->db->escape($this->element)."'";
5277  $sql .= ", '".$this->db->escape($busy)."'";
5278  $sql .= ", '".$this->db->escape($mandatory)."'";
5279  $sql .= ")";
5280 
5281  dol_syslog(get_class($this)."::add_element_resource", LOG_DEBUG);
5282  if ($this->db->query($sql)) {
5283  $this->db->commit();
5284  return 1;
5285  } else {
5286  $this->error = $this->db->lasterror();
5287  $this->db->rollback();
5288  return 0;
5289  }
5290  }
5291 
5292  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5301  public function delete_resource($rowid, $element, $notrigger = 0)
5302  {
5303  // phpcs:enable
5304  global $user;
5305 
5306  $this->db->begin();
5307 
5308  $sql = "DELETE FROM ".$this->db->prefix()."element_resources";
5309  $sql .= " WHERE rowid = ".((int) $rowid);
5310 
5311  dol_syslog(get_class($this)."::delete_resource", LOG_DEBUG);
5312 
5313  $resql = $this->db->query($sql);
5314  if (!$resql) {
5315  $this->error = $this->db->lasterror();
5316  $this->db->rollback();
5317  return -1;
5318  } else {
5319  if (!$notrigger) {
5320  $result = $this->call_trigger(strtoupper($element).'_DELETE_RESOURCE', $user);
5321  if ($result < 0) {
5322  $this->db->rollback();
5323  return -1;
5324  }
5325  }
5326  $this->db->commit();
5327  return 1;
5328  }
5329  }
5330 
5331 
5337  public function __clone()
5338  {
5339  // Force a copy of this->lines, otherwise it will point to same object.
5340  if (isset($this->lines) && is_array($this->lines)) {
5341  $nboflines = count($this->lines);
5342  for ($i = 0; $i < $nboflines; $i++) {
5343  $this->lines[$i] = clone $this->lines[$i];
5344  }
5345  }
5346  }
5347 
5361  protected function commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams = null)
5362  {
5363  global $conf, $langs, $user, $hookmanager, $action;
5364 
5365  $srctemplatepath = '';
5366 
5367  $parameters = array('modelspath'=>$modelspath, 'modele'=>$modele, 'outputlangs'=>$outputlangs, 'hidedetails'=>$hidedetails, 'hidedesc'=>$hidedesc, 'hideref'=>$hideref, 'moreparams'=>$moreparams);
5368  $reshook = $hookmanager->executeHooks('commonGenerateDocument', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5369 
5370  if (!empty($reshook)) {
5371  return $reshook;
5372  }
5373 
5374  dol_syslog("commonGenerateDocument modele=".$modele." outputlangs->defaultlang=".(is_object($outputlangs) ? $outputlangs->defaultlang : 'null'));
5375 
5376  if (empty($modele)) {
5377  $this->error = 'BadValueForParameterModele';
5378  return -1;
5379  }
5380 
5381  // Increase limit for PDF build
5382  $err = error_reporting();
5383  error_reporting(0);
5384  @set_time_limit(120);
5385  error_reporting($err);
5386 
5387  // If selected model is a filename template (then $modele="modelname" or "modelname:filename")
5388  $tmp = explode(':', $modele, 2);
5389  if (!empty($tmp[1])) {
5390  $modele = $tmp[0];
5391  $srctemplatepath = $tmp[1];
5392  }
5393 
5394  // Search template files
5395  $file = '';
5396  $classname = '';
5397  $filefound = '';
5398  $dirmodels = array('/');
5399  if (is_array($conf->modules_parts['models'])) {
5400  $dirmodels = array_merge($dirmodels, $conf->modules_parts['models']);
5401  }
5402  foreach ($dirmodels as $reldir) {
5403  foreach (array('doc', 'pdf') as $prefix) {
5404  if (in_array(get_class($this), array('Adherent'))) {
5405  // Member module use prefix_modele.class.php
5406  $file = $prefix."_".$modele.".class.php";
5407  } else {
5408  // Other module use prefix_modele.modules.php
5409  $file = $prefix."_".$modele.".modules.php";
5410  }
5411 
5412  // On verifie l'emplacement du modele
5413  $file = dol_buildpath($reldir.$modelspath.$file, 0);
5414  if (file_exists($file)) {
5415  $filefound = $file;
5416  $classname = $prefix.'_'.$modele;
5417  break;
5418  }
5419  }
5420  if ($filefound) {
5421  break;
5422  }
5423  }
5424 
5425  if (!$filefound) {
5426  $this->error = $langs->trans("Error").' Failed to load doc generator with modelpaths='.$modelspath.' - modele='.$modele;
5427  $this->errors[] = $this->error;
5428  dol_syslog($this->error, LOG_ERR);
5429  return -1;
5430  }
5431 
5432  // If generator was found
5433  global $db; // Required to solve a conception default making an include of code using $db instead of $this->db just after.
5434 
5435  require_once $file;
5436 
5437  $obj = new $classname($this->db);
5438 
5439  // If generator is ODT, we must have srctemplatepath defined, if not we set it.
5440  if ($obj->type == 'odt' && empty($srctemplatepath)) {
5441  $varfortemplatedir = $obj->scandir;
5442  if ($varfortemplatedir && !empty($conf->global->$varfortemplatedir)) {
5443  $dirtoscan = $conf->global->$varfortemplatedir;
5444 
5445  $listoffiles = array();
5446 
5447  // Now we add first model found in directories scanned
5448  $listofdir = explode(',', $dirtoscan);
5449  foreach ($listofdir as $key => $tmpdir) {
5450  $tmpdir = trim($tmpdir);
5451  $tmpdir = preg_replace('/DOL_DATA_ROOT/', DOL_DATA_ROOT, $tmpdir);
5452  if (!$tmpdir) {
5453  unset($listofdir[$key]);
5454  continue;
5455  }
5456  if (is_dir($tmpdir)) {
5457  $tmpfiles = dol_dir_list($tmpdir, 'files', 0, '\.od(s|t)$', '', 'name', SORT_ASC, 0);
5458  if (count($tmpfiles)) {
5459  $listoffiles = array_merge($listoffiles, $tmpfiles);
5460  }
5461  }
5462  }
5463 
5464  if (count($listoffiles)) {
5465  foreach ($listoffiles as $record) {
5466  $srctemplatepath = $record['fullname'];
5467  break;
5468  }
5469  }
5470  }
5471 
5472  if (empty($srctemplatepath)) {
5473  $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotDefined';
5474  return -1;
5475  }
5476  }
5477 
5478  if ($obj->type == 'odt' && !empty($srctemplatepath)) {
5479  if (!dol_is_file($srctemplatepath)) {
5480  dol_syslog("Failed to locate template file ".$srctemplatepath, LOG_WARNING);
5481  $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotFound';
5482  return -1;
5483  }
5484  }
5485 
5486  // We save charset_output to restore it because write_file can change it if needed for
5487  // output format that does not support UTF8.
5488  $sav_charset_output = empty($outputlangs->charset_output) ? '' : $outputlangs->charset_output;
5489 
5490  if (in_array(get_class($this), array('Adherent'))) {
5491  $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, 'member', 1, 'tmp_cards', $moreparams);
5492  } else {
5493  $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, $hidedetails, $hidedesc, $hideref, $moreparams);
5494  }
5495  // After call of write_file $obj->result['fullpath'] is set with generated file. It will be used to update the ECM database index.
5496 
5497  if ($resultwritefile > 0) {
5498  $outputlangs->charset_output = $sav_charset_output;
5499 
5500  // We delete old preview
5501  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
5502  dol_delete_preview($this);
5503 
5504  // Index file in database
5505  if (!empty($obj->result['fullpath'])) {
5506  $destfull = $obj->result['fullpath'];
5507 
5508  // Update the last_main_doc field into main object (if document generator has property ->update_main_doc_field set)
5509  $update_main_doc_field = 0;
5510  if (!empty($obj->update_main_doc_field)) {
5511  $update_main_doc_field = 1;
5512  }
5513 
5514  // Check that the file exists, before indexing it.
5515  // Hint: It does not exist, if we create a PDF and auto delete the ODT File
5516  if (dol_is_file($destfull)) {
5517  $this->indexFile($destfull, $update_main_doc_field);
5518  }
5519  } else {
5520  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);
5521  }
5522 
5523  // Success in building document. We build meta file.
5524  dol_meta_create($this);
5525 
5526  return 1;
5527  } else {
5528  $outputlangs->charset_output = $sav_charset_output;
5529  $this->error = $obj->error;
5530  $this->errors = $obj->errors;
5531  dol_syslog("Error generating document for ".__CLASS__.". Error: ".$obj->error, LOG_ERR);
5532  return -1;
5533  }
5534  }
5535 
5545  public function indexFile($destfull, $update_main_doc_field)
5546  {
5547  global $conf, $user;
5548 
5549  $upload_dir = dirname($destfull);
5550  $destfile = basename($destfull);
5551  $rel_dir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $upload_dir);
5552 
5553  if (!preg_match('/[\\/]temp[\\/]|[\\/]thumbs|\.meta$/', $rel_dir)) { // If not a tmp dir
5554  $filename = basename($destfile);
5555  $rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
5556  $rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
5557 
5558  include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
5559  $ecmfile = new EcmFiles($this->db);
5560  $result = $ecmfile->fetch(0, '', ($rel_dir ? $rel_dir.'/' : '').$filename);
5561 
5562  // Set the public "share" key
5563  $setsharekey = false;
5564  if ($this->element == 'propal' || $this->element == 'proposal') {
5565  if (!isset($conf->global->PROPOSAL_ALLOW_ONLINESIGN) || !empty($conf->global->PROPOSAL_ALLOW_ONLINESIGN)) {
5566  $setsharekey = true; // feature to make online signature is not set or set to on (default)
5567  }
5568  if (!empty($conf->global->PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD)) {
5569  $setsharekey = true;
5570  }
5571  }
5572  if ($this->element == 'commande' && !empty($conf->global->ORDER_ALLOW_EXTERNAL_DOWNLOAD)) {
5573  $setsharekey = true;
5574  }
5575  if ($this->element == 'facture' && !empty($conf->global->INVOICE_ALLOW_EXTERNAL_DOWNLOAD)) {
5576  $setsharekey = true;
5577  }
5578  if ($this->element == 'bank_account' && !empty($conf->global->BANK_ACCOUNT_ALLOW_EXTERNAL_DOWNLOAD)) {
5579  $setsharekey = true;
5580  }
5581  if ($this->element == 'product' && !empty($conf->global->PRODUCT_ALLOW_EXTERNAL_DOWNLOAD)) {
5582  $setsharekey = true;
5583  }
5584  if ($this->element == 'contrat' && !empty($conf->global->CONTRACT_ALLOW_EXTERNAL_DOWNLOAD)) {
5585  $setsharekey = true;
5586  }
5587  if ($this->element == 'fichinter' && !empty($conf->global->FICHINTER_ALLOW_EXTERNAL_DOWNLOAD)) {
5588  $setsharekey = true;
5589  }
5590  if ($this->element == 'supplier_proposal' && !empty($conf->global->SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD)) {
5591  $setsharekey = true;
5592  }
5593 
5594  if ($setsharekey) {
5595  if (empty($ecmfile->share)) { // Because object not found or share not set yet
5596  require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
5597  $ecmfile->share = getRandomPassword(true);
5598  }
5599  }
5600 
5601  if ($result > 0) {
5602  $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
5603  $ecmfile->fullpath_orig = '';
5604  $ecmfile->gen_or_uploaded = 'generated';
5605  $ecmfile->description = ''; // indexed content
5606  $ecmfile->keywords = ''; // keyword content
5607  $result = $ecmfile->update($user);
5608  if ($result < 0) {
5609  setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
5610  return -1;
5611  }
5612  } else {
5613  $ecmfile->entity = $conf->entity;
5614  $ecmfile->filepath = $rel_dir;
5615  $ecmfile->filename = $filename;
5616  $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
5617  $ecmfile->fullpath_orig = '';
5618  $ecmfile->gen_or_uploaded = 'generated';
5619  $ecmfile->description = ''; // indexed content
5620  $ecmfile->keywords = ''; // keyword content
5621  $ecmfile->src_object_type = $this->table_element; // $this->table_name is 'myobject' or 'mymodule_myobject'.
5622  $ecmfile->src_object_id = $this->id;
5623 
5624  $result = $ecmfile->create($user);
5625  if ($result < 0) {
5626  setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
5627  return -1;
5628  }
5629  }
5630 
5631  /*$this->result['fullname']=$destfull;
5632  $this->result['filepath']=$ecmfile->filepath;
5633  $this->result['filename']=$ecmfile->filename;*/
5634  //var_dump($obj->update_main_doc_field);exit;
5635 
5636  if ($update_main_doc_field && !empty($this->table_element)) {
5637  $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET last_main_doc = '".$this->db->escape($ecmfile->filepath."/".$ecmfile->filename)."'";
5638  $sql .= " WHERE rowid = ".((int) $this->id);
5639 
5640  $resql = $this->db->query($sql);
5641  if (!$resql) {
5642  dol_print_error($this->db);
5643  return -1;
5644  } else {
5645  $this->last_main_doc = $ecmfile->filepath.'/'.$ecmfile->filename;
5646  }
5647  }
5648  }
5649 
5650  return 1;
5651  }
5652 
5660  public function addThumbs($file)
5661  {
5662  $file_osencoded = dol_osencode($file);
5663 
5664  if (file_exists($file_osencoded)) {
5665  require_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
5666 
5667  $tmparraysize = getDefaultImageSizes();
5668  $maxwidthsmall = $tmparraysize['maxwidthsmall'];
5669  $maxheightsmall = $tmparraysize['maxheightsmall'];
5670  $maxwidthmini = $tmparraysize['maxwidthmini'];
5671  $maxheightmini = $tmparraysize['maxheightmini'];
5672  //$quality = $tmparraysize['quality'];
5673  $quality = 50; // For thumbs, we force quality to 50
5674 
5675  // Create small thumbs for company (Ratio is near 16/9)
5676  // Used on logon for example
5677  vignette($file_osencoded, $maxwidthsmall, $maxheightsmall, '_small', $quality);
5678 
5679  // Create mini thumbs for company (Ratio is near 16/9)
5680  // Used on menu or for setup page for example
5681  vignette($file_osencoded, $maxwidthmini, $maxheightmini, '_mini', $quality);
5682  }
5683  }
5684 
5692  public function delThumbs($file)
5693  {
5694  $imgThumbName = getImageFileNameForSize($file, '_small'); // Full path of thumb file
5695  dol_delete_file($imgThumbName);
5696  $imgThumbName = getImageFileNameForSize($file, '_mini'); // Full path of thumb file
5697  dol_delete_file($imgThumbName);
5698  }
5699 
5700 
5701  /* Functions common to commonobject and commonobjectline */
5702 
5703  /* For default values */
5704 
5718  public function getDefaultCreateValueFor($fieldname, $alternatevalue = null, $type = 'alphanohtml')
5719  {
5720  global $conf, $_POST;
5721 
5722  // If param here has been posted, we use this value first.
5723  if (GETPOSTISSET($fieldname)) {
5724  return GETPOST($fieldname, $type, 3);
5725  }
5726 
5727  if (isset($alternatevalue)) {
5728  return $alternatevalue;
5729  }
5730 
5731  $newelement = $this->element;
5732  if ($newelement == 'facture') {
5733  $newelement = 'invoice';
5734  }
5735  if ($newelement == 'commande') {
5736  $newelement = 'order';
5737  }
5738  if (empty($newelement)) {
5739  dol_syslog("Ask a default value using common method getDefaultCreateValueForField on an object with no property ->element defined. Return empty string.", LOG_WARNING);
5740  return '';
5741  }
5742 
5743  $keyforfieldname = strtoupper($newelement.'_DEFAULT_'.$fieldname);
5744  //var_dump($keyforfieldname);
5745  if (isset($conf->global->$keyforfieldname)) {
5746  return $conf->global->$keyforfieldname;
5747  }
5748 
5749  // TODO Ad here a scan into table llx_overwrite_default with a filter on $this->element and $fieldname
5750  // store content into $conf->cache['overwrite_default']
5751 
5752  return '';
5753  }
5754 
5755 
5756  /* For triggers */
5757 
5758 
5759  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5770  public function call_trigger($triggerName, $user)
5771  {
5772  // phpcs:enable
5773  global $langs, $conf;
5774  if (!empty(self::TRIGGER_PREFIX) && strpos($triggerName, self::TRIGGER_PREFIX . '_') !== 0) {
5775  dol_print_error('', 'The trigger "' . $triggerName . '" does not start with "' . self::TRIGGER_PREFIX . '_" as required.');
5776  exit;
5777  }
5778  if (!is_object($langs)) { // If lang was not defined, we set it. It is required by run_triggers.
5779  include_once DOL_DOCUMENT_ROOT.'/core/class/translate.class.php';
5780  $langs = new Translate('', $conf);
5781  }
5782 
5783  include_once DOL_DOCUMENT_ROOT.'/core/class/interfaces.class.php';
5784  $interface = new Interfaces($this->db);
5785  $result = $interface->run_triggers($triggerName, $this, $user, $langs, $conf);
5786 
5787  if ($result < 0) {
5788  if (!empty($this->errors)) {
5789  $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.
5790  } else {
5791  $this->errors = $interface->errors;
5792  }
5793  }
5794  return $result;
5795  }
5796 
5797 
5798  /* Functions for data in other language */
5799 
5800 
5809  {
5810  // To avoid SQL errors. Probably not the better solution though
5811  if (!$this->element) {
5812  return 0;
5813  }
5814  if (!($this->id > 0)) {
5815  return 0;
5816  }
5817  if (is_array($this->array_languages)) {
5818  return 1;
5819  }
5820 
5821  $this->array_languages = array();
5822 
5823  $element = $this->element;
5824  if ($element == 'categorie') {
5825  $element = 'categories'; // For compatibility
5826  }
5827 
5828  // Request to get translation values for object
5829  $sql = "SELECT rowid, property, lang , value";
5830  $sql .= " FROM ".$this->db->prefix()."object_lang";
5831  $sql .= " WHERE type_object = '".$this->db->escape($element)."'";
5832  $sql .= " AND fk_object = ".((int) $this->id);
5833 
5834  //dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG); // Too verbose
5835  $resql = $this->db->query($sql);
5836  if ($resql) {
5837  $numrows = $this->db->num_rows($resql);
5838  if ($numrows) {
5839  $i = 0;
5840  while ($i < $numrows) {
5841  $obj = $this->db->fetch_object($resql);
5842  $key = $obj->property;
5843  $value = $obj->value;
5844  $codelang = $obj->lang;
5845  $type = $this->fields[$key]['type'];
5846 
5847  // we can add this attribute to object
5848  if (preg_match('/date/', $type)) {
5849  $this->array_languages[$key][$codelang] = $this->db->jdate($value);
5850  } else {
5851  $this->array_languages[$key][$codelang] = $value;
5852  }
5853 
5854  $i++;
5855  }
5856  }
5857 
5858  $this->db->free($resql);
5859 
5860  if ($numrows) {
5861  return $numrows;
5862  } else {
5863  return 0;
5864  }
5865  } else {
5866  dol_print_error($this->db);
5867  return -1;
5868  }
5869  }
5870 
5877  public function setValuesForExtraLanguages($onlykey = '')
5878  {
5879  global $_POST, $langs;
5880 
5881  // Get extra fields
5882  foreach ($_POST as $postfieldkey => $postfieldvalue) {
5883  $tmparray = explode('-', $postfieldkey);
5884  if ($tmparray[0] != 'field') {
5885  continue;
5886  }
5887 
5888  $element = $tmparray[1];
5889  $key = $tmparray[2];
5890  $codelang = $tmparray[3];
5891  //var_dump("postfieldkey=".$postfieldkey." element=".$element." key=".$key." codelang=".$codelang);
5892 
5893  if (!empty($onlykey) && $key != $onlykey) {
5894  continue;
5895  }
5896  if ($element != $this->element) {
5897  continue;
5898  }
5899 
5900  $key_type = $this->fields[$key]['type'];
5901 
5902  $enabled = 1;
5903  if (isset($this->fields[$key]['enabled'])) {
5904  $enabled = dol_eval($this->fields[$key]['enabled'], 1, 1, '1');
5905  }
5906  /*$perms = 1;
5907  if (isset($this->fields[$key]['perms']))
5908  {
5909  $perms = dol_eval($this->fields[$key]['perms'], 1, 1, '1');
5910  }*/
5911  if (empty($enabled)) {
5912  continue;
5913  }
5914  //if (empty($perms)) continue;
5915 
5916  if (in_array($key_type, array('date'))) {
5917  // Clean parameters
5918  // TODO GMT date in memory must be GMT so we should add gm=true in parameters
5919  $value_key = dol_mktime(0, 0, 0, GETPOST($postfieldkey."month", 'int'), GETPOST($postfieldkey."day", 'int'), GETPOST($postfieldkey."year", 'int'));
5920  } elseif (in_array($key_type, array('datetime'))) {
5921  // Clean parameters
5922  // TODO GMT date in memory must be GMT so we should add gm=true in parameters
5923  $value_key = dol_mktime(GETPOST($postfieldkey."hour", 'int'), GETPOST($postfieldkey."min", 'int'), 0, GETPOST($postfieldkey."month", 'int'), GETPOST($postfieldkey."day", 'int'), GETPOST($postfieldkey."year", 'int'));
5924  } elseif (in_array($key_type, array('checkbox', 'chkbxlst'))) {
5925  $value_arr = GETPOST($postfieldkey, 'array'); // check if an array
5926  if (!empty($value_arr)) {
5927  $value_key = implode(',', $value_arr);
5928  } else {
5929  $value_key = '';
5930  }
5931  } elseif (in_array($key_type, array('price', 'double'))) {
5932  $value_arr = GETPOST($postfieldkey, 'alpha');
5933  $value_key = price2num($value_arr);
5934  } else {
5935  $value_key = GETPOST($postfieldkey);
5936  if (in_array($key_type, array('link')) && $value_key == '-1') {
5937  $value_key = '';
5938  }
5939  }
5940 
5941  $this->array_languages[$key][$codelang] = $value_key;
5942 
5943  /*if ($nofillrequired) {
5944  $langs->load('errors');
5945  setEventMessages($langs->trans('ErrorFieldsRequired').' : '.implode(', ', $error_field_required), null, 'errors');
5946  return -1;
5947  }*/
5948  }
5949 
5950  return 1;
5951  }
5952 
5953 
5954  /* Functions for extrafields */
5955 
5962  public function fetchNoCompute($id)
5963  {
5964  global $conf;
5965 
5966  $savDisableCompute = $conf->disable_compute;
5967  $conf->disable_compute = 1;
5968 
5969  $ret = $this->fetch($id);
5970 
5971  $conf->disable_compute = $savDisableCompute;
5972 
5973  return $ret;
5974  }
5975 
5976  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5986  public function fetch_optionals($rowid = null, $optionsArray = null)
5987  {
5988  // phpcs:enable
5989  global $conf, $extrafields;
5990 
5991  if (empty($rowid)) {
5992  $rowid = $this->id;
5993  }
5994  if (empty($rowid) && isset($this->rowid)) {
5995  $rowid = $this->rowid; // deprecated
5996  }
5997 
5998  // To avoid SQL errors. Probably not the better solution though
5999  if (!$this->table_element) {
6000  return 0;
6001  }
6002 
6003  $this->array_options = array();
6004 
6005  if (!is_array($optionsArray)) {
6006  // If $extrafields is not a known object, we initialize it. Best practice is to have $extrafields defined into card.php or list.php page.
6007  if (!isset($extrafields) || !is_object($extrafields)) {
6008  require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6009  $extrafields = new ExtraFields($this->db);
6010  }
6011 
6012  // Load array of extrafields for elementype = $this->table_element
6013  if (empty($extrafields->attributes[$this->table_element]['loaded'])) {
6014  $extrafields->fetch_name_optionals_label($this->table_element);
6015  }
6016  $optionsArray = (!empty($extrafields->attributes[$this->table_element]['label']) ? $extrafields->attributes[$this->table_element]['label'] : null);
6017  } else {
6018  global $extrafields;
6019  dol_syslog("Warning: fetch_optionals was called with param optionsArray defined when you should pass null now", LOG_WARNING);
6020  }
6021 
6022  $table_element = $this->table_element;
6023  if ($table_element == 'categorie') {
6024  $table_element = 'categories'; // For compatibility
6025  }
6026 
6027  // Request to get complementary values
6028  if (is_array($optionsArray) && count($optionsArray) > 0) {
6029  $sql = "SELECT rowid";
6030  foreach ($optionsArray as $name => $label) {
6031  if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || $extrafields->attributes[$this->table_element]['type'][$name] != 'separate') {
6032  $sql .= ", ".$name;
6033  }
6034  }
6035  $sql .= " FROM ".$this->db->prefix().$table_element."_extrafields";
6036  $sql .= " WHERE fk_object = ".((int) $rowid);
6037 
6038  //dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG); // Too verbose
6039  $resql = $this->db->query($sql);
6040  if ($resql) {
6041  $numrows = $this->db->num_rows($resql);
6042  if ($numrows) {
6043  $tab = $this->db->fetch_array($resql);
6044 
6045  foreach ($tab as $key => $value) {
6046  // 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)
6047  if ($key != 'rowid' && $key != 'tms' && $key != 'fk_member' && !is_int($key)) {
6048  // we can add this attribute to object
6049  if (!empty($extrafields->attributes[$this->table_element]) && in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date', 'datetime'))) {
6050  //var_dump($extrafields->attributes[$this->table_element]['type'][$key]);
6051  $this->array_options["options_".$key] = $this->db->jdate($value);
6052  } else {
6053  $this->array_options["options_".$key] = $value;
6054  }
6055 
6056  //var_dump('key '.$key.' '.$value.' type='.$extrafields->attributes[$this->table_element]['type'][$key].' '.$this->array_options["options_".$key]);
6057  }
6058  }
6059  }
6060 
6061  // If field is a computed field, value must become result of compute (regardless of whether a row exists
6062  // in the element's extrafields table)
6063  if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6064  foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6065  if (!empty($extrafields->attributes[$this->table_element]) && !empty($extrafields->attributes[$this->table_element]['computed'][$key])) {
6066  //var_dump($conf->disable_compute);
6067  if (empty($conf->disable_compute)) {
6068  global $objectoffield; // We set a global variable to $objectoffield so
6069  $objectoffield = $this; // we can use it inside computed formula
6070  $this->array_options['options_' . $key] = dol_eval($extrafields->attributes[$this->table_element]['computed'][$key], 1, 0, '');
6071  }
6072  }
6073  }
6074  }
6075 
6076  $this->db->free($resql);
6077 
6078  if ($numrows) {
6079  return $numrows;
6080  } else {
6081  return 0;
6082  }
6083  } else {
6084  $this->errors[]=$this->db->lasterror;
6085  return -1;
6086  }
6087  }
6088  return 0;
6089  }
6090 
6097  public function deleteExtraFields()
6098  {
6099  global $conf;
6100 
6101  if (!empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) {
6102  return 0;
6103  }
6104 
6105  $this->db->begin();
6106 
6107  $table_element = $this->table_element;
6108  if ($table_element == 'categorie') {
6109  $table_element = 'categories'; // For compatibility
6110  }
6111 
6112  dol_syslog(get_class($this)."::deleteExtraFields delete", LOG_DEBUG);
6113 
6114  $sql_del = "DELETE FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
6115 
6116  $resql = $this->db->query($sql_del);
6117  if (!$resql) {
6118  $this->error = $this->db->lasterror();
6119  $this->db->rollback();
6120  return -1;
6121  } else {
6122  $this->db->commit();
6123  return 1;
6124  }
6125  }
6126 
6137  public function insertExtraFields($trigger = '', $userused = null)
6138  {
6139  global $conf, $langs, $user;
6140 
6141  if (!empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) {
6142  return 0;
6143  }
6144 
6145  if (empty($userused)) {
6146  $userused = $user;
6147  }
6148 
6149  $error = 0;
6150 
6151  if (!empty($this->array_options)) {
6152  // Check parameters
6153  $langs->load('admin');
6154  require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6155  $extrafields = new ExtraFields($this->db);
6156  $target_extrafields = $extrafields->fetch_name_optionals_label($this->table_element);
6157 
6158  // Eliminate copied source object extra fields that do not exist in target object
6159  $new_array_options = array();
6160  foreach ($this->array_options as $key => $value) {
6161  if (in_array(substr($key, 8), array_keys($target_extrafields))) { // We remove the 'options_' from $key for test
6162  $new_array_options[$key] = $value;
6163  } elseif (in_array($key, array_keys($target_extrafields))) { // We test on $key that does not contain the 'options_' prefix
6164  $new_array_options['options_'.$key] = $value;
6165  }
6166  }
6167 
6168  foreach ($new_array_options as $key => $value) {
6169  $attributeKey = substr($key, 8); // Remove 'options_' prefix
6170  $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
6171  $attributeLabel = $extrafields->attributes[$this->table_element]['label'][$attributeKey];
6172  $attributeParam = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
6173  $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
6174  $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
6175  $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$attributeKey];
6176 
6177  // If we clone, we have to clean unique extrafields to prevent duplicates.
6178  // This behaviour can be prevented by external code by changing $this->context['createfromclone'] value in createFrom hook
6179  if (!empty($this->context['createfromclone']) && $this->context['createfromclone'] == 'createfromclone' && !empty($attributeUnique)) {
6180  $new_array_options[$key] = null;
6181  }
6182 
6183  // Similar code than into insertExtraFields
6184  if ($attributeRequired) {
6185  $mandatorypb = false;
6186  if ($attributeType == 'link' && $this->array_options[$key] == '-1') {
6187  $mandatorypb = true;
6188  }
6189  if ($this->array_options[$key] === '') {
6190  $mandatorypb = true;
6191  }
6192  if ($attributeType == 'sellist' && $this->array_options[$key] == '0') {
6193  $mandatorypb = true;
6194  }
6195  if ($mandatorypb) {
6196  $langs->load("errors");
6197  dol_syslog("Mandatory field '".$key."' is empty during create and set to required into definition of extrafields");
6198  $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
6199  return -1;
6200  }
6201  }
6202 
6203  //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
6204  //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
6205 
6206  if (!empty($attrfieldcomputed)) {
6207  if (!empty($conf->global->MAIN_STORE_COMPUTED_EXTRAFIELDS)) {
6208  $value = dol_eval($attrfieldcomputed, 1, 0, '');
6209  dol_syslog($langs->trans("Extrafieldcomputed")." sur ".$attributeLabel."(".$value.")", LOG_DEBUG);
6210  $new_array_options[$key] = $value;
6211  } else {
6212  $new_array_options[$key] = null;
6213  }
6214  }
6215 
6216  switch ($attributeType) {
6217  case 'int':
6218  if (!is_numeric($value) && $value != '') {
6219  $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
6220  return -1;
6221  } elseif ($value == '') {
6222  $new_array_options[$key] = null;
6223  }
6224  break;
6225  case 'price':
6226  case 'double':
6227  $value = price2num($value);
6228  if (!is_numeric($value) && $value != '') {
6229  dol_syslog($langs->trans("ExtraFieldHasWrongValue")." for ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
6230  $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
6231  return -1;
6232  } elseif ($value == '') {
6233  $value = null;
6234  }
6235  //dol_syslog("double value"." sur ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
6236  $new_array_options[$key] = $value;
6237  break;
6238  /*case 'select': // Not required, we chosed value='0' for undefined values
6239  if ($value=='-1')
6240  {
6241  $this->array_options[$key] = null;
6242  }
6243  break;*/
6244  case 'password':
6245  $algo = '';
6246  if ($this->array_options[$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
6247  // If there is an encryption choice, we use it to crypt data before insert
6248  $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
6249  $algo = reset($tmparrays);
6250  if ($algo != '') {
6251  //global $action; // $action may be 'create', 'update', 'update_extras'...
6252  //var_dump($action);
6253  //var_dump($this->oldcopy);exit;
6254  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
6255  //var_dump($this->oldcopy->array_options[$key]); var_dump($this->array_options[$key]);
6256  if (isset($this->oldcopy->array_options[$key]) && $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.
6257  $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6258  } else {
6259  // var_dump($algo);
6260  $newvalue = dol_hash($this->array_options[$key], $algo);
6261  $new_array_options[$key] = $newvalue;
6262  }
6263  } else {
6264  $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6265  }
6266  }
6267  } else // Common usage
6268  {
6269  $new_array_options[$key] = $this->array_options[$key];
6270  }
6271  break;
6272  case 'date':
6273  case 'datetime':
6274  // If data is a string instead of a timestamp, we convert it
6275  if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
6276  $this->array_options[$key] = strtotime($this->array_options[$key]);
6277  }
6278  $new_array_options[$key] = $this->db->idate($this->array_options[$key]);
6279  break;
6280  case 'datetimegmt':
6281  // If data is a string instead of a timestamp, we convert it
6282  if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
6283  $this->array_options[$key] = strtotime($this->array_options[$key]);
6284  }
6285  $new_array_options[$key] = $this->db->idate($this->array_options[$key], 'gmt');
6286  break;
6287  case 'link':
6288  $param_list = array_keys($attributeParam['options']);
6289  // 0 : ObjectName
6290  // 1 : classPath
6291  $InfoFieldList = explode(":", $param_list[0]);
6292  dol_include_once($InfoFieldList[1]);
6293  if ($InfoFieldList[0] && class_exists($InfoFieldList[0])) {
6294  if ($value == '-1') { // -1 is key for no defined in combo list of objects
6295  $new_array_options[$key] = '';
6296  } elseif ($value) {
6297  $object = new $InfoFieldList[0]($this->db);
6298  if (is_numeric($value)) {
6299  $res = $object->fetch($value); // Common case
6300  } else {
6301  $res = $object->fetch('', $value); // For compatibility
6302  }
6303 
6304  if ($res > 0) {
6305  $new_array_options[$key] = $object->id;
6306  } else {
6307  $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
6308  return -1;
6309  }
6310  }
6311  } else {
6312  dol_syslog('Error bad setup of extrafield', LOG_WARNING);
6313  }
6314  break;
6315  case 'checkbox':
6316  case 'chkbxlst':
6317  if (is_array($this->array_options[$key])) {
6318  $new_array_options[$key] = implode(',', $this->array_options[$key]);
6319  } else {
6320  $new_array_options[$key] = $this->array_options[$key];
6321  }
6322  break;
6323  }
6324  }
6325 
6326  $this->db->begin();
6327 
6328  $table_element = $this->table_element;
6329  if ($table_element == 'categorie') {
6330  $table_element = 'categories'; // For compatibility
6331  }
6332 
6333  dol_syslog(get_class($this)."::insertExtraFields delete then insert", LOG_DEBUG);
6334 
6335  $sql_del = "DELETE FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
6336  $this->db->query($sql_del);
6337 
6338  $sql = "INSERT INTO ".$this->db->prefix().$table_element."_extrafields (fk_object";
6339  foreach ($new_array_options as $key => $value) {
6340  $attributeKey = substr($key, 8); // Remove 'options_' prefix
6341  // Add field of attribut
6342  if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] != 'separate') { // Only for other type than separator
6343  $sql .= ",".$attributeKey;
6344  }
6345  }
6346  // We must insert a default value for fields for other entities that are mandatory to avoid not null error
6347  if (!empty($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities']) && is_array($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'])) {
6348  foreach ($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'] as $tmpkey => $tmpval) {
6349  if (!isset($extrafields->attributes[$this->table_element]['type'][$tmpkey])) { // If field not already added previously
6350  $sql .= ",".$tmpkey;
6351  }
6352  }
6353  }
6354  $sql .= ") VALUES (".$this->id;
6355 
6356  foreach ($new_array_options as $key => $value) {
6357  $attributeKey = substr($key, 8); // Remove 'options_' prefix
6358  // Add field of attribute
6359  if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] != 'separate') { // Only for other type than separator)
6360  if ($new_array_options[$key] != '' || $new_array_options[$key] == '0') {
6361  $sql .= ",'".$this->db->escape($new_array_options[$key])."'";
6362  } else {
6363  $sql .= ",null";
6364  }
6365  }
6366  }
6367  // We must insert a default value for fields for other entities that are mandatory to avoid not null error
6368  if (!empty($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities']) && is_array($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'])) {
6369  foreach ($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'] as $tmpkey => $tmpval) {
6370  if (!isset($extrafields->attributes[$this->table_element]['type'][$tmpkey])) { // If field not already added previously
6371  if (in_array($tmpval, array('int', 'double', 'price'))) {
6372  $sql .= ", 0";
6373  } else {
6374  $sql .= ", ''";
6375  }
6376  }
6377  }
6378  }
6379 
6380  $sql .= ")";
6381 
6382  $resql = $this->db->query($sql);
6383  if (!$resql) {
6384  $this->error = $this->db->lasterror();
6385  $error++;
6386  }
6387 
6388  if (!$error && $trigger) {
6389  // Call trigger
6390  $this->context = array('extrafieldaddupdate'=>1);
6391  $result = $this->call_trigger($trigger, $userused);
6392  if ($result < 0) {
6393  $error++;
6394  }
6395  // End call trigger
6396  }
6397 
6398  if ($error) {
6399  $this->db->rollback();
6400  return -1;
6401  } else {
6402  $this->db->commit();
6403  return 1;
6404  }
6405  } else {
6406  return 0;
6407  }
6408  }
6409 
6420  public function insertExtraLanguages($trigger = '', $userused = null)
6421  {
6422  global $conf, $langs, $user;
6423 
6424  if (empty($userused)) {
6425  $userused = $user;
6426  }
6427 
6428  $error = 0;
6429 
6430  if (!empty($conf->global->MAIN_EXTRALANGUAGES_DISABLED)) {
6431  return 0; // For avoid conflicts if trigger used
6432  }
6433 
6434  if (is_array($this->array_languages)) {
6435  $new_array_languages = $this->array_languages;
6436 
6437  foreach ($new_array_languages as $key => $value) {
6438  $attributeKey = $key;
6439  $attributeType = $this->fields[$attributeKey]['type'];
6440  $attributeLabel = $this->fields[$attributeKey]['label'];
6441 
6442  //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
6443  //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
6444 
6445  switch ($attributeType) {
6446  case 'int':
6447  if (!is_numeric($value) && $value != '') {
6448  $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
6449  return -1;
6450  } elseif ($value == '') {
6451  $new_array_languages[$key] = null;
6452  }
6453  break;
6454  case 'double':
6455  $value = price2num($value);
6456  if (!is_numeric($value) && $value != '') {
6457  dol_syslog($langs->trans("ExtraLanguageHasWrongValue")." sur ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
6458  $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
6459  return -1;
6460  } elseif ($value == '') {
6461  $new_array_languages[$key] = null;
6462  } else {
6463  $new_array_languages[$key] = $value;
6464  }
6465  break;
6466  /*case 'select': // Not required, we chosed value='0' for undefined values
6467  if ($value=='-1')
6468  {
6469  $this->array_options[$key] = null;
6470  }
6471  break;*/
6472  }
6473  }
6474 
6475  $this->db->begin();
6476 
6477  $table_element = $this->table_element;
6478  if ($table_element == 'categorie') {
6479  $table_element = 'categories'; // For compatibility
6480  }
6481 
6482  dol_syslog(get_class($this)."::insertExtraLanguages delete then insert", LOG_DEBUG);
6483 
6484  foreach ($new_array_languages as $key => $langcodearray) { // $key = 'name', 'town', ...
6485  foreach ($langcodearray as $langcode => $value) {
6486  $sql_del = "DELETE FROM ".$this->db->prefix()."object_lang";
6487  $sql_del .= " WHERE fk_object = ".((int) $this->id)." AND property = '".$this->db->escape($key)."' AND type_object = '".$this->db->escape($table_element)."'";
6488  $sql_del .= " AND lang = '".$this->db->escape($langcode)."'";
6489  $this->db->query($sql_del);
6490 
6491  if ($value !== '') {
6492  $sql = "INSERT INTO ".$this->db->prefix()."object_lang (fk_object, property, type_object, lang, value";
6493  $sql .= ") VALUES (".$this->id.", '".$this->db->escape($key)."', '".$this->db->escape($table_element)."', '".$this->db->escape($langcode)."', '".$this->db->escape($value)."'";
6494  $sql .= ")";
6495 
6496  $resql = $this->db->query($sql);
6497  if (!$resql) {
6498  $this->error = $this->db->lasterror();
6499  $error++;
6500  break;
6501  }
6502  }
6503  }
6504  }
6505 
6506  if (!$error && $trigger) {
6507  // Call trigger
6508  $this->context = array('extralanguagesaddupdate'=>1);
6509  $result = $this->call_trigger($trigger, $userused);
6510  if ($result < 0) {
6511  $error++;
6512  }
6513  // End call trigger
6514  }
6515 
6516  if ($error) {
6517  $this->db->rollback();
6518  return -1;
6519  } else {
6520  $this->db->commit();
6521  return 1;
6522  }
6523  } else {
6524  return 0;
6525  }
6526  }
6527 
6538  public function updateExtraField($key, $trigger = null, $userused = null)
6539  {
6540  global $conf, $langs, $user;
6541 
6542  if (!empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) {
6543  return 0;
6544  }
6545 
6546  if (empty($userused)) {
6547  $userused = $user;
6548  }
6549 
6550  $error = 0;
6551 
6552  if (!empty($this->array_options) && isset($this->array_options["options_".$key])) {
6553  // Check parameters
6554  $langs->load('admin');
6555  require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6556  $extrafields = new ExtraFields($this->db);
6557  $extrafields->fetch_name_optionals_label($this->table_element);
6558 
6559  $value = $this->array_options["options_".$key];
6560 
6561  $attributeType = $extrafields->attributes[$this->table_element]['type'][$key];
6562  $attributeLabel = $extrafields->attributes[$this->table_element]['label'][$key];
6563  $attributeParam = $extrafields->attributes[$this->table_element]['param'][$key];
6564  $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$key];
6565  $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$key];
6566 
6567  // Similar code than into insertExtraFields
6568  if ($attributeRequired) {
6569  $mandatorypb = false;
6570  if ($attributeType == 'link' && $this->array_options["options_".$key] == '-1') {
6571  $mandatorypb = true;
6572  }
6573  if ($this->array_options["options_".$key] === '') {
6574  $mandatorypb = true;
6575  }
6576  if ($mandatorypb) {
6577  $langs->load("errors");
6578  dol_syslog("Mandatory field 'options_".$key."' is empty during update and set to required into definition of extrafields");
6579  $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
6580  return -1;
6581  }
6582  }
6583 
6584  //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
6585  //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
6586 
6587  if (!empty($attrfieldcomputed)) {
6588  if (!empty($conf->global->MAIN_STORE_COMPUTED_EXTRAFIELDS)) {
6589  $value = dol_eval($attrfieldcomputed, 1, 0, '');
6590  dol_syslog($langs->trans("Extrafieldcomputed")." sur ".$attributeLabel."(".$value.")", LOG_DEBUG);
6591  $this->array_options["options_".$key] = $value;
6592  } else {
6593  $this->array_options["options_".$key] = null;
6594  }
6595  }
6596 
6597  switch ($attributeType) {
6598  case 'int':
6599  if (!is_numeric($value) && $value != '') {
6600  $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
6601  return -1;
6602  } elseif ($value === '') {
6603  $this->array_options["options_".$key] = null;
6604  }
6605  break;
6606  case 'double':
6607  $value = price2num($value);
6608  if (!is_numeric($value) && $value != '') {
6609  dol_syslog($langs->trans("ExtraFieldHasWrongValue")." sur ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
6610  $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
6611  return -1;
6612  } elseif ($value === '') {
6613  $value = null;
6614  }
6615  //dol_syslog("double value"." sur ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
6616  $this->array_options["options_".$key] = $value;
6617  break;
6618  /*case 'select': // Not required, we chosed value='0' for undefined values
6619  if ($value=='-1')
6620  {
6621  $this->array_options[$key] = null;
6622  }
6623  break;*/
6624  case 'price':
6625  $this->array_options["options_".$key] = price2num($this->array_options["options_".$key]);
6626  break;
6627  case 'date':
6628  case 'datetime':
6629  if (empty($this->array_options["options_".$key])) {
6630  $this->array_options["options_".$key] = null;
6631  } else {
6632  $this->array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key]);
6633  }
6634  break;
6635  case 'datetimegmt':
6636  if (empty($this->array_options["options_".$key])) {
6637  $this->array_options["options_".$key] = null;
6638  } else {
6639  $this->array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key], 'gmt');
6640  }
6641  break;
6642  case 'boolean':
6643  if (empty($this->array_options["options_".$key])) {
6644  $this->array_options["options_".$key] = null;
6645  }
6646  break;
6647  case 'link':
6648  if ($this->array_options["options_".$key] === '') {
6649  $this->array_options["options_".$key] = null;
6650  }
6651  break;
6652  /*
6653  case 'link':
6654  $param_list = array_keys($attributeParam['options']);
6655  // 0 : ObjectName
6656  // 1 : classPath
6657  $InfoFieldList = explode(":", $param_list[0]);
6658  dol_include_once($InfoFieldList[1]);
6659  if ($InfoFieldList[0] && class_exists($InfoFieldList[0]))
6660  {
6661  if ($value == '-1') // -1 is key for no defined in combo list of objects
6662  {
6663  $new_array_options[$key] = '';
6664  } elseif ($value) {
6665  $object = new $InfoFieldList[0]($this->db);
6666  if (is_numeric($value)) $res = $object->fetch($value); // Common case
6667  else $res = $object->fetch('', $value); // For compatibility
6668 
6669  if ($res > 0) $new_array_options[$key] = $object->id;
6670  else {
6671  $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
6672  $this->db->rollback();
6673  return -1;
6674  }
6675  }
6676  } else {
6677  dol_syslog('Error bad setup of extrafield', LOG_WARNING);
6678  }
6679  break;
6680  */
6681  case 'checkbox':
6682  case 'chkbxlst':
6683  if (is_array($this->array_options[$key])) {
6684  $new_array_options[$key] = implode(',', $this->array_options[$key]);
6685  } else {
6686  $new_array_options[$key] = $this->array_options[$key];
6687  }
6688  break;
6689  }
6690 
6691  $this->db->begin();
6692 
6693  $linealreadyfound = 0;
6694 
6695  // 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)
6696  $sql = "SELECT COUNT(rowid) as nb FROM ".$this->db->prefix().$this->table_element."_extrafields WHERE fk_object = ".((int) $this->id);
6697  $resql = $this->db->query($sql);
6698  if ($resql) {
6699  $tmpobj = $this->db->fetch_object($resql);
6700  if ($tmpobj) {
6701  $linealreadyfound = $tmpobj->nb;
6702  }
6703  }
6704 
6705  if ($linealreadyfound) {
6706  if ($this->array_options["options_".$key] === null) {
6707  $sql = "UPDATE ".$this->db->prefix().$this->table_element."_extrafields SET ".$key." = null";
6708  } else {
6709  $sql = "UPDATE ".$this->db->prefix().$this->table_element."_extrafields SET ".$key." = '".$this->db->escape($this->array_options["options_".$key])."'";
6710  }
6711  $sql .= " WHERE fk_object = ".((int) $this->id);
6712  } else {
6713  $result = $this->insertExtraFields('', $user);
6714  if ($result < 0) {
6715  $error++;
6716  }
6717  }
6718 
6719  $resql = $this->db->query($sql);
6720  if (!$resql) {
6721  $error++;
6722  $this->error = $this->db->lasterror();
6723  }
6724  if (!$error && $trigger) {
6725  // Call trigger
6726  $this->context = array('extrafieldupdate'=>1);
6727  $result = $this->call_trigger($trigger, $userused);
6728  if ($result < 0) {
6729  $error++;
6730  }
6731  // End call trigger
6732  }
6733 
6734  if ($error) {
6735  dol_syslog(__METHOD__.$this->error, LOG_ERR);
6736  $this->db->rollback();
6737  return -1;
6738  } else {
6739  $this->db->commit();
6740  return 1;
6741  }
6742  } else {
6743  return 0;
6744  }
6745  }
6746 
6757  public function updateExtraLanguages($key, $trigger = null, $userused = null)
6758  {
6759  global $conf, $langs, $user;
6760 
6761  if (empty($userused)) {
6762  $userused = $user;
6763  }
6764 
6765  $error = 0;
6766 
6767  if (!empty($conf->global->MAIN_EXTRALANGUAGES_DISABLED)) {
6768  return 0; // For avoid conflicts if trigger used
6769  }
6770 
6771  return 0;
6772  }
6773 
6774 
6789  public function showInputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = 0, $nonewbutton = 0)
6790  {
6791  global $conf, $langs, $form;
6792 
6793  if (!is_object($form)) {
6794  require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
6795  $form = new Form($this->db);
6796  }
6797 
6798  if (!empty($this->fields)) {
6799  $val = $this->fields[$key];
6800  }
6801 
6802  // Validation tests and output
6803  $fieldValidationErrorMsg = '';
6804  $validationClass = '';
6805  $fieldValidationErrorMsg = $this->getFieldError($key);
6806  if (!empty($fieldValidationErrorMsg)) {
6807  $validationClass = ' --error'; // the -- is use as class state in css : .--error can't be be defined alone it must be define with another class like .my-class.--error or input.--error
6808  } else {
6809  $validationClass = ' --success'; // the -- is use as class state in css : .--success can't be be defined alone it must be define with another class like .my-class.--success or input.--success
6810  }
6811 
6812  $out = '';
6813  $type = '';
6814  $isDependList = 0;
6815  $param = array();
6816  $param['options'] = array();
6817  $reg = array();
6818  $size = !empty($this->fields[$key]['size']) ? $this->fields[$key]['size'] : 0;
6819  // Because we work on extrafields
6820  if (preg_match('/^(integer|link):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
6821  $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
6822  $type = 'link';
6823  } elseif (preg_match('/^(integer|link):(.*):(.*):(.*)/i', $val['type'], $reg)) {
6824  $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
6825  $type = 'link';
6826  } elseif (preg_match('/^(integer|link):(.*):(.*)/i', $val['type'], $reg)) {
6827  $param['options'] = array($reg[2].':'.$reg[3] => 'N');
6828  $type = 'link';
6829  } elseif (preg_match('/^(sellist):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
6830  $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
6831  $type = 'sellist';
6832  } elseif (preg_match('/^(sellist):(.*):(.*):(.*)/i', $val['type'], $reg)) {
6833  $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
6834  $type = 'sellist';
6835  } elseif (preg_match('/^(sellist):(.*):(.*)/i', $val['type'], $reg)) {
6836  $param['options'] = array($reg[2].':'.$reg[3] => 'N');
6837  $type = 'sellist';
6838  } elseif (preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
6839  $param['options'] = array($reg[1] => 'N');
6840  $type = 'chkbxlst';
6841  } elseif (preg_match('/varchar\((\d+)\)/', $val['type'], $reg)) {
6842  $param['options'] = array();
6843  $type = 'varchar';
6844  $size = $reg[1];
6845  } elseif (preg_match('/varchar/', $val['type'])) {
6846  $param['options'] = array();
6847  $type = 'varchar';
6848  } else {
6849  $param['options'] = array();
6850  $type = $this->fields[$key]['type'];
6851  }
6852 
6853  // Special case that force options and type ($type can be integer, varchar, ...)
6854  if (!empty($this->fields[$key]['arrayofkeyval']) && is_array($this->fields[$key]['arrayofkeyval'])) {
6855  $param['options'] = $this->fields[$key]['arrayofkeyval'];
6856  $type = 'select';
6857  }
6858 
6859  $label = $this->fields[$key]['label'];
6860  //$elementtype=$this->fields[$key]['elementtype']; // Seems not used
6861  $default = (!empty($this->fields[$key]['default']) ? $this->fields[$key]['default'] : '');
6862  $computed = (!empty($this->fields[$key]['computed']) ? $this->fields[$key]['computed'] : '');
6863  $unique = (!empty($this->fields[$key]['unique']) ? $this->fields[$key]['unique'] : 0);
6864  $required = (!empty($this->fields[$key]['required']) ? $this->fields[$key]['required'] : 0);
6865  $autofocusoncreate = (!empty($this->fields[$key]['autofocusoncreate']) ? $this->fields[$key]['autofocusoncreate'] : 0);
6866 
6867  $langfile = (!empty($this->fields[$key]['langfile']) ? $this->fields[$key]['langfile'] : '');
6868  $list = (!empty($this->fields[$key]['list']) ? $this->fields[$key]['list'] : 0);
6869  $hidden = (in_array(abs($this->fields[$key]['visible']), array(0, 2)) ? 1 : 0);
6870 
6871  $objectid = $this->id;
6872 
6873  if ($computed) {
6874  if (!preg_match('/^search_/', $keyprefix)) {
6875  return '<span class="opacitymedium">'.$langs->trans("AutomaticallyCalculated").'</span>';
6876  } else {
6877  return '';
6878  }
6879  }
6880 
6881  // Set value of $morecss. For this, we use in priority showsize from parameters, then $val['css'] then autodefine
6882  if (empty($morecss) && !empty($val['css'])) {
6883  $morecss = $val['css'];
6884  } elseif (empty($morecss)) {
6885  if ($type == 'date') {
6886  $morecss = 'minwidth100imp';
6887  } elseif ($type == 'datetime' || $type == 'link') { // link means an foreign key to another primary id
6888  $morecss = 'minwidth200imp';
6889  } elseif (in_array($type, array('int', 'integer', 'price')) || preg_match('/^double(\([0-9],[0-9]\)){0,1}/', $type)) {
6890  $morecss = 'maxwidth75';
6891  } elseif ($type == 'url') {
6892  $morecss = 'minwidth400';
6893  } elseif ($type == 'boolean') {
6894  $morecss = '';
6895  } else {
6896  if (round($size) < 12) {
6897  $morecss = 'minwidth100';
6898  } elseif (round($size) <= 48) {
6899  $morecss = 'minwidth200';
6900  } else {
6901  $morecss = 'minwidth400';
6902  }
6903  }
6904  }
6905 
6906  // Add validation state class
6907  if (!empty($validationClass)) {
6908  $morecss.= $validationClass;
6909  }
6910 
6911  if (in_array($type, array('date'))) {
6912  $tmp = explode(',', $size);
6913  $newsize = $tmp[0];
6914  $showtime = 0;
6915 
6916  // Do not show current date when field not required (see selectDate() method)
6917  if (!$required && $value == '') {
6918  $value = '-1';
6919  }
6920 
6921  // TODO Must also support $moreparam
6922  $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1);
6923  } elseif (in_array($type, array('datetime'))) {
6924  $tmp = explode(',', $size);
6925  $newsize = $tmp[0];
6926  $showtime = 1;
6927 
6928  // Do not show current date when field not required (see selectDate() method)
6929  if (!$required && $value == '') $value = '-1';
6930 
6931  // TODO Must also support $moreparam
6932  $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1, '', '', '', 1, '', '', 'tzuserrel');
6933  } elseif (in_array($type, array('duration'))) {
6934  $out = $form->select_duration($keyprefix.$key.$keysuffix, $value, 0, 'text', 0, 1);
6935  } elseif (in_array($type, array('int', 'integer'))) {
6936  $tmp = explode(',', $size);
6937  $newsize = $tmp[0];
6938  $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'"'.($newsize > 0 ? ' maxlength="'.$newsize.'"' : '').' value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
6939  } elseif (in_array($type, array('real'))) {
6940  $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
6941  } elseif (preg_match('/varchar/', $type)) {
6942  $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'"'.($size > 0 ? ' maxlength="'.$size.'"' : '').' value="'.dol_escape_htmltag($value).'"'.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
6943  } elseif (in_array($type, array('email', 'mail', 'phone', 'url', 'ip'))) {
6944  $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').($autofocusoncreate ? ' autofocus' : '').'>';
6945  } elseif (preg_match('/^text/', $type)) {
6946  if (!preg_match('/search_/', $keyprefix)) { // If keyprefix is search_ or search_options_, we must just use a simple text field
6947  require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
6948  $doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, false, ROWS_5, '90%');
6949  $out = $doleditor->Create(1);
6950  } else {
6951  $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
6952  }
6953  } elseif (preg_match('/^html/', $type)) {
6954  if (!preg_match('/search_/', $keyprefix)) { // If keyprefix is search_ or search_options_, we must just use a simple text field
6955  require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
6956  $doleditor = new DolEditor($keyprefix.$key.$keysuffix, $value, '', 200, 'dolibarr_notes', 'In', false, false, isModEnabled('fckeditor') && $conf->global->FCKEDITOR_ENABLE_SOCIETE, ROWS_5, '90%');
6957  $out = $doleditor->Create(1, '', true, '', '', $moreparam, $morecss);
6958  } else {
6959  $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.dol_escape_htmltag($value).'" '.($moreparam ? $moreparam : '').'>';
6960  }
6961  } elseif ($type == 'boolean') {
6962  $checked = '';
6963  if (!empty($value)) {
6964  $checked = ' checked value="1" ';
6965  } else {
6966  $checked = ' value="1" ';
6967  }
6968  $out = '<input type="checkbox" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.$checked.' '.($moreparam ? $moreparam : '').'>';
6969  } elseif ($type == 'price') {
6970  if (!empty($value)) { // $value in memory is a php numeric, we format it into user number format.
6971  $value = price($value);
6972  }
6973  $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'> '.$langs->getCurrencySymbol($conf->currency);
6974  } elseif (preg_match('/^double(\([0-9],[0-9]\)){0,1}/', $type)) {
6975  if (!empty($value)) { // $value in memory is a php numeric, we format it into user number format.
6976  $value = price($value);
6977  }
6978  $out = '<input type="text" class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" value="'.$value.'" '.($moreparam ? $moreparam : '').'> ';
6979  } elseif ($type == 'select') {
6980  $out = '';
6981  if (!empty($conf->use_javascript_ajax) && empty($conf->global->MAIN_EXTRAFIELDS_DISABLE_SELECT2)) {
6982  include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
6983  $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
6984  }
6985 
6986  $out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
6987  if ((!isset($this->fields[$key]['default'])) || ($this->fields[$key]['notnull'] != 1)) {
6988  $out .= '<option value="0">&nbsp;</option>';
6989  }
6990  foreach ($param['options'] as $keyb => $valb) {
6991  if ((string) $keyb == '') {
6992  continue;
6993  }
6994  if (strpos($valb, "|") !== false) {
6995  list($valb, $parent) = explode('|', $valb);
6996  }
6997  $out .= '<option value="'.$keyb.'"';
6998  $out .= (((string) $value == (string) $keyb) ? ' selected' : '');
6999  $out .= (!empty($parent) ? ' parent="'.$parent.'"' : '');
7000  $out .= '>'.$valb.'</option>';
7001  }
7002  $out .= '</select>';
7003  } elseif ($type == 'sellist') {
7004  $out = '';
7005  if (!empty($conf->use_javascript_ajax) && empty($conf->global->MAIN_EXTRAFIELDS_DISABLE_SELECT2)) {
7006  include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
7007  $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0);
7008  }
7009 
7010  $out .= '<select class="flat '.$morecss.' maxwidthonsmartphone" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '').'>';
7011  if (is_array($param['options'])) {
7012  $param_list = array_keys($param['options']);
7013  $InfoFieldList = explode(":", $param_list[0]);
7014  $parentName = '';
7015  $parentField = '';
7016  // 0 : tableName
7017  // 1 : label field name
7018  // 2 : key fields name (if differ of rowid)
7019  // 3 : key field parent (for dependent lists)
7020  // 4 : where clause filter on column or table extrafield, syntax field='value' or extra.field=value
7021  // 5 : id category type
7022  // 6 : ids categories list separated by comma for category root
7023  $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
7024 
7025  if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
7026  if (strpos($InfoFieldList[4], 'extra.') !== false) {
7027  $keyList = 'main.'.$InfoFieldList[2].' as rowid';
7028  } else {
7029  $keyList = $InfoFieldList[2].' as rowid';
7030  }
7031  }
7032  if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
7033  list($parentName, $parentField) = explode('|', $InfoFieldList[3]);
7034  $keyList .= ', '.$parentField;
7035  }
7036 
7037  $filter_categorie = false;
7038  if (count($InfoFieldList) > 5) {
7039  if ($InfoFieldList[0] == 'categorie') {
7040  $filter_categorie = true;
7041  }
7042  }
7043 
7044  if ($filter_categorie === false) {
7045  $fields_label = explode('|', $InfoFieldList[1]);
7046  if (is_array($fields_label)) {
7047  $keyList .= ', ';
7048  $keyList .= implode(', ', $fields_label);
7049  }
7050 
7051  $sqlwhere = '';
7052  $sql = "SELECT " . $keyList;
7053  $sql .= " FROM " . $this->db->prefix() . $InfoFieldList[0];
7054  if (!empty($InfoFieldList[4])) {
7055  // can use SELECT request
7056  if (strpos($InfoFieldList[4], '$SEL$') !== false) {
7057  $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
7058  }
7059 
7060  // current object id can be use into filter
7061  if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
7062  $InfoFieldList[4] = str_replace('$ID$', $objectid, $InfoFieldList[4]);
7063  } else {
7064  $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
7065  }
7066 
7067  //We have to join on extrafield table
7068  if (strpos($InfoFieldList[4], 'extra') !== false) {
7069  $sql .= " as main, " . $this->db->prefix() . $InfoFieldList[0] . "_extrafields as extra";
7070  $sqlwhere .= " WHERE extra.fk_object=main." . $InfoFieldList[2] . " AND " . $InfoFieldList[4];
7071  } else {
7072  $sqlwhere .= " WHERE " . $InfoFieldList[4];
7073  }
7074  } else {
7075  $sqlwhere .= ' WHERE 1=1';
7076  }
7077  // Some tables may have field, some other not. For the moment we disable it.
7078  if (in_array($InfoFieldList[0], array('tablewithentity'))) {
7079  $sqlwhere .= " AND entity = " . ((int) $conf->entity);
7080  }
7081  $sql .= $sqlwhere;
7082  //print $sql;
7083 
7084  $sql .= ' ORDER BY ' . implode(', ', $fields_label);
7085 
7086  dol_syslog(get_class($this) . '::showInputField type=sellist', LOG_DEBUG);
7087  $resql = $this->db->query($sql);
7088  if ($resql) {
7089  $out .= '<option value="0">&nbsp;</option>';
7090  $num = $this->db->num_rows($resql);
7091  $i = 0;
7092  while ($i < $num) {
7093  $labeltoshow = '';
7094  $obj = $this->db->fetch_object($resql);
7095 
7096  // Several field into label (eq table:code|libelle:rowid)
7097  $notrans = false;
7098  $fields_label = explode('|', $InfoFieldList[1]);
7099  if (count($fields_label) > 1) {
7100  $notrans = true;
7101  foreach ($fields_label as $field_toshow) {
7102  $labeltoshow .= $obj->$field_toshow . ' ';
7103  }
7104  } else {
7105  $labeltoshow = $obj->{$InfoFieldList[1]};
7106  }
7107  $labeltoshow = dol_trunc($labeltoshow, 45);
7108 
7109  if ($value == $obj->rowid) {
7110  foreach ($fields_label as $field_toshow) {
7111  $translabel = $langs->trans($obj->$field_toshow);
7112  if ($translabel != $obj->$field_toshow) {
7113  $labeltoshow = dol_trunc($translabel) . ' ';
7114  } else {
7115  $labeltoshow = dol_trunc($obj->$field_toshow) . ' ';
7116  }
7117  }
7118  $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
7119  } else {
7120  if (!$notrans) {
7121  $translabel = $langs->trans($obj->{$InfoFieldList[1]});
7122  if ($translabel != $obj->{$InfoFieldList[1]}) {
7123  $labeltoshow = dol_trunc($translabel, 18);
7124  } else {
7125  $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]});
7126  }
7127  }
7128  if (empty($labeltoshow)) {
7129  $labeltoshow = '(not defined)';
7130  }
7131  if ($value == $obj->rowid) {
7132  $out .= '<option value="' . $obj->rowid . '" selected>' . $labeltoshow . '</option>';
7133  }
7134 
7135  if (!empty($InfoFieldList[3]) && $parentField) {
7136  $parent = $parentName . ':' . $obj->{$parentField};
7137  $isDependList = 1;
7138  }
7139 
7140  $out .= '<option value="' . $obj->rowid . '"';
7141  $out .= ($value == $obj->rowid ? ' selected' : '');
7142  $out .= (!empty($parent) ? ' parent="' . $parent . '"' : '');
7143  $out .= '>' . $labeltoshow . '</option>';
7144  }
7145 
7146  $i++;
7147  }
7148  $this->db->free($resql);
7149  } else {
7150  print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
7151  }
7152  } else {
7153  require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
7154  $data = $form->select_all_categories(Categorie::$MAP_ID_TO_CODE[$InfoFieldList[5]], '', 'parent', 64, $InfoFieldList[6], 1, 1);
7155  $out .= '<option value="0">&nbsp;</option>';
7156  foreach ($data as $data_key => $data_value) {
7157  $out .= '<option value="' . $data_key . '"';
7158  $out .= ($value == $data_key ? ' selected' : '');
7159  $out .= '>' . $data_value . '</option>';
7160  }
7161  }
7162  }
7163  $out .= '</select>';
7164  } elseif ($type == 'checkbox') {
7165  $value_arr = explode(',', $value);
7166  $out = $form->multiselectarray($keyprefix.$key.$keysuffix, (empty($param['options']) ?null:$param['options']), $value_arr, '', 0, $morecss, 0, '100%');
7167  } elseif ($type == 'radio') {
7168  $out = '';
7169  foreach ($param['options'] as $keyopt => $valopt) {
7170  $out .= '<input class="flat '.$morecss.'" type="radio" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'" '.($moreparam ? $moreparam : '');
7171  $out .= ' value="'.$keyopt.'"';
7172  $out .= ' id="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'"';
7173  $out .= ($value == $keyopt ? 'checked' : '');
7174  $out .= '/><label for="'.$keyprefix.$key.$keysuffix.'_'.$keyopt.'">'.$valopt.'</label><br>';
7175  }
7176  } elseif ($type == 'chkbxlst') {
7177  if (is_array($value)) {
7178  $value_arr = $value;
7179  } else {
7180  $value_arr = explode(',', $value);
7181  }
7182 
7183  if (is_array($param['options'])) {
7184  $param_list = array_keys($param['options']);
7185  $InfoFieldList = explode(":", $param_list[0]);
7186  $parentName = '';
7187  $parentField = '';
7188  // 0 : tableName
7189  // 1 : label field name
7190  // 2 : key fields name (if differ of rowid)
7191  // 3 : key field parent (for dependent lists)
7192  // 4 : where clause filter on column or table extrafield, syntax field='value' or extra.field=value
7193  // 5 : id category type
7194  // 6 : ids categories list separated by comma for category root
7195  $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid');
7196 
7197  if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) {
7198  list ($parentName, $parentField) = explode('|', $InfoFieldList[3]);
7199  $keyList .= ', '.$parentField;
7200  }
7201  if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) {
7202  if (strpos($InfoFieldList[4], 'extra.') !== false) {
7203  $keyList = 'main.'.$InfoFieldList[2].' as rowid';
7204  } else {
7205  $keyList = $InfoFieldList[2].' as rowid';
7206  }
7207  }
7208 
7209  $filter_categorie = false;
7210  if (count($InfoFieldList) > 5) {
7211  if ($InfoFieldList[0] == 'categorie') {
7212  $filter_categorie = true;
7213  }
7214  }
7215 
7216  if ($filter_categorie === false) {
7217  $fields_label = explode('|', $InfoFieldList[1]);
7218  if (is_array($fields_label)) {
7219  $keyList .= ', ';
7220  $keyList .= implode(', ', $fields_label);
7221  }
7222 
7223  $sqlwhere = '';
7224  $sql = "SELECT " . $keyList;
7225  $sql .= ' FROM ' . $this->db->prefix() . $InfoFieldList[0];
7226  if (!empty($InfoFieldList[4])) {
7227  // can use SELECT request
7228  if (strpos($InfoFieldList[4], '$SEL$') !== false) {
7229  $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]);
7230  }
7231 
7232  // current object id can be use into filter
7233  if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) {
7234  $InfoFieldList[4] = str_replace('$ID$', $objectid, $InfoFieldList[4]);
7235  } else {
7236  $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]);
7237  }
7238 
7239  // We have to join on extrafield table
7240  if (strpos($InfoFieldList[4], 'extra') !== false) {
7241  $sql .= ' as main, ' . $this->db->prefix() . $InfoFieldList[0] . '_extrafields as extra';
7242  $sqlwhere .= " WHERE extra.fk_object=main." . $InfoFieldList[2] . " AND " . $InfoFieldList[4];
7243  } else {
7244  $sqlwhere .= " WHERE " . $InfoFieldList[4];
7245  }
7246  } else {
7247  $sqlwhere .= ' WHERE 1=1';
7248  }
7249  // Some tables may have field, some other not. For the moment we disable it.
7250  if (in_array($InfoFieldList[0], array('tablewithentity'))) {
7251  $sqlwhere .= " AND entity = " . ((int) $conf->entity);
7252  }
7253  // $sql.=preg_replace('/^ AND /','',$sqlwhere);
7254  // print $sql;
7255 
7256  $sql .= $sqlwhere;
7257  dol_syslog(get_class($this) . '::showInputField type=chkbxlst', LOG_DEBUG);
7258  $resql = $this->db->query($sql);
7259  if ($resql) {
7260  $num = $this->db->num_rows($resql);
7261  $i = 0;
7262 
7263  $data = array();
7264 
7265  while ($i < $num) {
7266  $labeltoshow = '';
7267  $obj = $this->db->fetch_object($resql);
7268 
7269  $notrans = false;
7270  // Several field into label (eq table:code|libelle:rowid)
7271  $fields_label = explode('|', $InfoFieldList[1]);
7272  if (count($fields_label) > 1) {
7273  $notrans = true;
7274  foreach ($fields_label as $field_toshow) {
7275  $labeltoshow .= $obj->$field_toshow . ' ';
7276  }
7277  } else {
7278  $labeltoshow = $obj->{$InfoFieldList[1]};
7279  }
7280  $labeltoshow = dol_trunc($labeltoshow, 45);
7281 
7282  if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
7283  foreach ($fields_label as $field_toshow) {
7284  $translabel = $langs->trans($obj->$field_toshow);
7285  if ($translabel != $obj->$field_toshow) {
7286  $labeltoshow = dol_trunc($translabel, 18) . ' ';
7287  } else {
7288  $labeltoshow = dol_trunc($obj->$field_toshow, 18) . ' ';
7289  }
7290  }
7291 
7292  $data[$obj->rowid] = $labeltoshow;
7293  } else {
7294  if (!$notrans) {
7295  $translabel = $langs->trans($obj->{$InfoFieldList[1]});
7296  if ($translabel != $obj->{$InfoFieldList[1]}) {
7297  $labeltoshow = dol_trunc($translabel, 18);
7298  } else {
7299  $labeltoshow = dol_trunc($obj->{$InfoFieldList[1]}, 18);
7300  }
7301  }
7302  if (empty($labeltoshow)) {
7303  $labeltoshow = '(not defined)';
7304  }
7305 
7306  if (is_array($value_arr) && in_array($obj->rowid, $value_arr)) {
7307  $data[$obj->rowid] = $labeltoshow;
7308  }
7309 
7310  if (!empty($InfoFieldList[3]) && $parentField) {
7311  $parent = $parentName . ':' . $obj->{$parentField};
7312  $isDependList = 1;
7313  }
7314 
7315  $data[$obj->rowid] = $labeltoshow;
7316  }
7317 
7318  $i++;
7319  }
7320  $this->db->free($resql);
7321 
7322  $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, '', 0, $morecss, 0, '100%');
7323  } else {
7324  print 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
7325  }
7326  } else {
7327  require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
7328  $data = $form->select_all_categories(Categorie::$MAP_ID_TO_CODE[$InfoFieldList[5]], '', 'parent', 64, $InfoFieldList[6], 1, 1);
7329  $out = $form->multiselectarray($keyprefix . $key . $keysuffix, $data, $value_arr, '', 0, $morecss, 0, '100%');
7330  }
7331  }
7332  } elseif ($type == 'link') {
7333  $param_list = array_keys($param['options']); // $param_list='ObjectName:classPath[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'
7334  $param_list_array = explode(&