dolibarr  21.0.0-alpha
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-2024 Frédéric France <frederic.france@free.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  * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
21  *
22  * This program is free software; you can redistribute it and/or modify
23  * it under the terms of the GNU General Public License as published by
24  * the Free Software Foundation; either version 3 of the License, or
25  * (at your option) any later version.
26  *
27  * This program is distributed in the hope that it will be useful,
28  * but WITHOUT ANY WARRANTY; without even the implied warranty of
29  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30  * GNU General Public License for more details.
31  *
32  * You should have received a copy of the GNU General Public License
33  * along with this program. If not, see <https://www.gnu.org/licenses/>.
34  */
35 
42 require_once DOL_DOCUMENT_ROOT.'/core/class/doldeprecationhandler.class.php';
43 
49 abstract class CommonObject
50 {
51  use DolDeprecationHandler;
52 
53  const TRIGGER_PREFIX = ''; // to be overridden in child class implementations, i.e. 'BILL', 'TASK', 'PROPAL', etc.
54 
58  public $module;
59 
63  public $db;
64 
68  public $id;
69 
73  public $entity;
74 
79  public $error;
80 
84  public $errorhidden;
85 
89  public $errors = array();
90 
94  private $validateFieldsErrors = array();
95 
99  public $element;
100 
105  public $fk_element;
106 
112  public $element_for_permission;
113 
117  public $table_element;
118 
122  public $table_element_line = '';
123 
128  public $ismultientitymanaged;
129 
133  public $import_key;
134 
138  public $array_options = array();
139 
140 
144  public $fields = array();
145 
150  public $array_languages = null; // Value is array() when load already tried
151 
155  public $contacts_ids;
156 
160  public $linked_objects;
161 
165  public $linkedObjectsIds;
166 
170  public $linkedObjects;
171 
175  private $linkedObjectsFullLoaded = array();
176 
180  public $oldcopy;
181 
185  public $oldref;
186 
190  protected $table_ref_field = '';
191 
195  public $restrictiononfksoc = 0;
196 
197 
198  // The following vars are used by some objects only.
199  // We keep these properties in CommonObject in order to provide common methods using them.
200 
204  public $context = array();
205 
209  public $actionmsg;
213  public $actionmsg2;
214 
218  public $canvas;
219 
224  public $project;
225 
230  public $fk_project;
231 
237  private $projet;
238 
244  public $fk_projet;
245 
250  public $contact;
251 
256  public $contact_id;
257 
262  public $thirdparty;
263 
268  public $user;
269 
274  public $origin_type;
275 
280  public $origin_id;
281 
286 
292  public $origin;
293 
302  private $expedition;
303 
309  private $livraison;
310 
316  private $commandeFournisseur;
317 
318 
322  public $ref;
323 
327  public $ref_ext;
328 
332  public $ref_previous;
333 
337  public $ref_next;
338 
342  public $newref;
343 
350  public $statut;
351 
359  public $status;
360 
361 
366  public $country;
367 
372  public $country_id;
373 
378  public $country_code;
379 
384  public $state;
385 
390  public $state_id;
391 
396  public $fk_departement;
397 
402  public $state_code;
403 
408  public $region_id;
409 
414  public $region_code;
415 
420  public $region;
421 
422 
427  public $barcode_type;
428 
433  public $barcode_type_code;
434 
439  public $barcode_type_label;
440 
445  public $barcode_type_coder;
446 
451  public $mode_reglement_id;
452 
457  public $cond_reglement_id;
458 
462  public $demand_reason_id;
463 
468  public $transport_mode_id;
469 
477  private $cond_reglement; // Private to call DolDeprecationHandler
481  protected $depr_cond_reglement; // Internal value for deprecation
482 
488  public $fk_delivery_address;
489 
494  public $shipping_method_id;
495 
500  public $shipping_method;
501 
502  // Multicurrency
506  public $fk_multicurrency;
507 
512  public $multicurrency_code;
513 
518  public $multicurrency_tx;
519 
523  public $multicurrency_total_ht;
524 
528  public $multicurrency_total_tva;
529 
533  public $multicurrency_total_ttc;
534 
538  public $multicurrency_total_localtax1; // not in database
539 
543  public $multicurrency_total_localtax2; // not in database
544 
549  public $model_pdf;
550 
555  public $last_main_doc;
556 
562  public $fk_bank;
563 
568  public $fk_account;
569 
574  public $note_public;
575 
580  public $note_private;
581 
587  public $note;
588 
593  public $total_ht;
594 
599  public $total_tva;
600 
605  public $total_localtax1;
606 
611  public $total_localtax2;
612 
617  public $total_ttc;
618 
619 
623  public $lines;
624 
628  public $actiontypecode;
629 
634  public $comments = array();
635 
639  public $name;
640 
644  public $lastname;
645 
649  public $firstname;
650 
654  public $civility_id;
655 
656  // Dates
660  public $date_creation;
661 
665  public $date_validation;
666 
670  public $date_modification;
671 
677  public $tms;
678 
684  private $date_update;
685 
689  public $date_cloture;
690 
695  public $user_author;
696 
701  public $user_creation;
702 
706  public $user_creation_id;
707 
712  public $user_valid;
713 
718  public $user_validation;
719 
723  public $user_validation_id;
724 
728  public $user_closing_id;
729 
734  public $user_modification;
735 
739  public $user_modification_id;
740 
745  public $fk_user_creat;
746 
751  public $fk_user_modif;
752 
753 
757  public $next_prev_filter;
758 
762  public $specimen = 0;
763 
767  public $sendtoid;
768 
774  private $alreadypaid;
775 
779  public $totalpaid;
780 
784  public $labelStatus = array();
785 
789  public $labelStatusShort = array();
790 
794  public $tpl;
795 
796 
800  public $showphoto_on_popup;
801 
805  public $nb = array();
806 
810  public $nbphoto;
811 
815  public $output;
816 
820  public $extraparams = array();
821 
825  protected $childtables = array();
826 
832  protected $childtablesoncascade = array();
833 
837  public $product;
838 
842  public $cond_reglement_supplier_id;
843 
849  public $deposit_percent;
850 
851 
855  public $retained_warranty_fk_cond_reglement;
856 
860  public $warehouse_id;
861 
865  public $isextrafieldmanaged = 0;
866 
867 
868  // No constructor as it is an abstract class
869 
875  protected function deprecatedProperties()
876  {
877  return array(
878  'alreadypaid' => 'totalpaid',
879  'cond_reglement' => 'depr_cond_reglement',
880  //'note' => 'note_private', // Some classes needs ->note and others need ->note_public/private so we can't manage deprecation for this field with dolDeprecationHandler
881  'commandeFournisseur' => 'origin_object',
882  'expedition' => 'origin_object',
883  'fk_project' => 'fk_project',
884  'livraison' => 'origin_object',
885  'projet' => 'project',
886  'statut' => 'status',
887  );
888  }
889 
890 
901  public static function isExistingObject($element, $id, $ref = '', $ref_ext = '')
902  {
903  global $db, $conf;
904 
905  $sql = "SELECT rowid, ref, ref_ext";
906  $sql .= " FROM ".$db->prefix().$element;
907  $sql .= " WHERE entity IN (".getEntity($element).")";
908 
909  if ($id > 0) {
910  $sql .= " AND rowid = ".((int) $id);
911  } elseif ($ref) {
912  $sql .= " AND ref = '".$db->escape($ref)."'";
913  } elseif ($ref_ext) {
914  $sql .= " AND ref_ext = '".$db->escape($ref_ext)."'";
915  } else {
916  $error = 'ErrorWrongParameters';
917  dol_syslog(get_class()."::isExistingObject ".$error, LOG_ERR);
918  return -1;
919  }
920  if ($ref || $ref_ext) { // Because the same ref can exists in 2 different entities, we force the current one in priority
921  $sql .= " AND entity = ".((int) $conf->entity);
922  }
923 
924  dol_syslog(get_class()."::isExistingObject", LOG_DEBUG);
925  $resql = $db->query($sql);
926  if ($resql) {
927  $num = $db->num_rows($resql);
928  if ($num > 0) {
929  return 1;
930  } else {
931  return 0;
932  }
933  }
934  return -1;
935  }
936 
942  public function isEmpty()
943  {
944  return (empty($this->id));
945  }
946 
953  public function setErrorsFromObject($object)
954  {
955  if (!empty($object->error)) {
956  $this->error = $object->error;
957  }
958  if (!empty($object->errors)) {
959  $this->errors = array_merge($this->errors, $object->errors);
960  }
961  }
962 
970  public function getTooltipContentArray($params)
971  {
972  return [];
973  }
974 
982  public function getTooltipContent($params)
983  {
984  global $action, $extrafields, $langs, $hookmanager;
985 
986  // If there is too much extrafields, we do not include them into tooltip
987  $MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP = getDolGlobalInt('MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP', 3);
988 
989  $data = $this->getTooltipContentArray($params);
990  $count = 0;
991 
992  // Add extrafields
993  if (!empty($extrafields->attributes[$this->table_element]['label'])) {
994  $data['opendivextra'] = '<div class="centpercent wordbreak divtooltipextra">';
995  foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
996  if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
997  continue;
998  }
999  if ($count >= abs($MAX_EXTRAFIELDS_TO_SHOW_IN_TOOLTIP)) {
1000  $data['more_extrafields'] = '<br>...';
1001  break;
1002  }
1003  $enabled = 1;
1004  if ($enabled && isset($extrafields->attributes[$this->table_element]['enabled'][$key])) {
1005  $enabled = (int) dol_eval($extrafields->attributes[$this->table_element]['enabled'][$key], 1, 1, '2');
1006  }
1007  if ($enabled && isset($extrafields->attributes[$this->table_element]['list'][$key])) {
1008  $enabled = (int) dol_eval($extrafields->attributes[$this->table_element]['list'][$key], 1, 1, '2');
1009  }
1010  $perms = 1;
1011  if ($perms && isset($extrafields->attributes[$this->table_element]['perms'][$key])) {
1012  $perms = (int) dol_eval($extrafields->attributes[$this->table_element]['perms'][$key], 1, 1, '2');
1013  }
1014  if (empty($enabled)) {
1015  continue; // 0 = Never visible field
1016  }
1017  if (abs($enabled) != 1 && abs($enabled) != 3 && abs($enabled) != 5 && abs($enabled) != 4) {
1018  continue; // <> -1 and <> 1 and <> 3 = not visible on forms, only on list <> 4 = not visible at the creation
1019  }
1020  if (empty($perms)) {
1021  continue; // 0 = Not visible
1022  }
1023  if (!empty($extrafields->attributes[$this->table_element]['langfile'][$key])) {
1024  $langs->load($extrafields->attributes[$this->table_element]['langfile'][$key]);
1025  }
1026  $labelextra = $langs->trans((string) $extrafields->attributes[$this->table_element]['label'][$key]);
1027  if ($extrafields->attributes[$this->table_element]['type'][$key] == 'separate') {
1028  $data[$key] = '<br><b><u>'. $labelextra . '</u></b>';
1029  } else {
1030  $value = (empty($this->array_options['options_' . $key]) ? '' : $this->array_options['options_' . $key]);
1031  $data[$key] = '<br><b>'. $labelextra . ':</b> ' . $extrafields->showOutputField($key, $value, '', $this->table_element);
1032  $count++;
1033  }
1034  }
1035  $data['closedivextra'] = '</div>';
1036  }
1037 
1038  $hookmanager->initHooks(array($this->element . 'dao'));
1039  $parameters = array(
1040  'tooltipcontentarray' => &$data,
1041  'params' => $params,
1042  );
1043  // Note that $action and $object may have been modified by some hooks
1044  $hookmanager->executeHooks('getTooltipContent', $parameters, $this, $action);
1045 
1046  //var_dump($data);
1047  $label = implode($data);
1048 
1049  return $label;
1050  }
1051 
1052 
1058  public function errorsToString()
1059  {
1060  return $this->error.(is_array($this->errors) ? (($this->error != '' ? ', ' : '').implode(', ', $this->errors)) : '');
1061  }
1062 
1063 
1070  public function getFormatedCustomerRef($objref)
1071  {
1072  global $hookmanager;
1073 
1074  $parameters = array('objref' => $objref);
1075  $action = '';
1076  $reshook = $hookmanager->executeHooks('getFormatedCustomerRef', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1077  if ($reshook > 0) {
1078  return $hookmanager->resArray['objref'];
1079  }
1080  return $objref.(isset($hookmanager->resArray['objref']) ? $hookmanager->resArray['objref'] : '');
1081  }
1082 
1089  public function getFormatedSupplierRef($objref)
1090  {
1091  global $hookmanager;
1092 
1093  $parameters = array('objref' => $objref);
1094  $action = '';
1095  $reshook = $hookmanager->executeHooks('getFormatedSupplierRef', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1096  if ($reshook > 0) {
1097  return $hookmanager->resArray['objref'];
1098  }
1099  return $objref.(isset($hookmanager->resArray['objref']) ? $hookmanager->resArray['objref'] : '');
1100  }
1101 
1111  public function getFullAddress($withcountry = 0, $sep = "\n", $withregion = 0, $extralangcode = '')
1112  {
1113  if ($withcountry && $this->country_id && (empty($this->country_code) || empty($this->country))) {
1114  require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
1115  $tmparray = getCountry($this->country_id, 'all');
1116  $this->country_code = $tmparray['code'];
1117  $this->country = $tmparray['label'];
1118  }
1119 
1120  if ($withregion && $this->state_id && (empty($this->state_code) || empty($this->state) || empty($this->region) || empty($this->region_code))) {
1121  require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
1122  $tmparray = getState($this->state_id, 'all', null, 1);
1123  $this->state_code = $tmparray['code'];
1124  $this->state = $tmparray['label'];
1125  $this->region_code = $tmparray['region_code'];
1126  $this->region = $tmparray['region'];
1127  }
1128 
1129  return dol_format_address($this, $withcountry, $sep, null, 0, $extralangcode);
1130  }
1131 
1132 
1141  public function getLastMainDocLink($modulepart, $initsharekey = 0, $relativelink = 0)
1142  {
1143  global $user, $dolibarr_main_url_root;
1144 
1145  if (empty($this->last_main_doc)) {
1146  return ''; // No way to known which document name to use
1147  }
1148 
1149  include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
1150  $ecmfile = new EcmFiles($this->db);
1151  $result = $ecmfile->fetch(0, '', $this->last_main_doc);
1152  if ($result < 0) {
1153  $this->error = $ecmfile->error;
1154  $this->errors = $ecmfile->errors;
1155  return -1;
1156  }
1157 
1158  if (empty($ecmfile->id)) { // No entry into file index already exists, we should initialize the shared key manually.
1159  // Add entry into index
1160  if ($initsharekey) {
1161  require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
1162 
1163  // TODO We can't, we don't have full path of file, only last_main_doc and ->element, so we must first rebuild full path $destfull
1164  /*
1165  $ecmfile->filepath = $rel_dir;
1166  $ecmfile->filename = $filename;
1167  $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
1168  $ecmfile->fullpath_orig = '';
1169  $ecmfile->gen_or_uploaded = 'generated';
1170  $ecmfile->description = ''; // indexed content
1171  $ecmfile->keywords = ''; // keyword content
1172  $ecmfile->share = getRandomPassword(true);
1173  $result = $ecmfile->create($user);
1174  if ($result < 0)
1175  {
1176  $this->error = $ecmfile->error;
1177  $this->errors = $ecmfile->errors;
1178  }
1179  */
1180  } else {
1181  return '';
1182  }
1183  } elseif (empty($ecmfile->share)) { // Entry into file index already exists but no share key is defined.
1184  // Add entry into index
1185  if ($initsharekey) {
1186  require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
1187  $ecmfile->share = getRandomPassword(true);
1188  $ecmfile->update($user);
1189  } else {
1190  return '';
1191  }
1192  }
1193  // Define $urlwithroot
1194  $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
1195  // This is to use external domain name found into config file
1196  //if (DOL_URL_ROOT && ! preg_match('/\/$/', $urlwithouturlroot) && ! preg_match('/^\//', DOL_URL_ROOT)) $urlwithroot=$urlwithouturlroot.'/'.DOL_URL_ROOT;
1197  //else
1198  $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT;
1199  //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current
1200 
1201  $forcedownload = 0;
1202 
1203  $paramlink = '';
1204  //if (!empty($modulepart)) $paramlink.=($paramlink?'&':'').'modulepart='.$modulepart; // For sharing with hash (so public files), modulepart is not required.
1205  //if (!empty($ecmfile->entity)) $paramlink.='&entity='.$ecmfile->entity; // For sharing with hash (so public files), entity is not required.
1206  //$paramlink.=($paramlink?'&':'').'file='.urlencode($filepath); // No need of name of file for public link, we will use the hash
1207  if (!empty($ecmfile->share)) {
1208  $paramlink .= ($paramlink ? '&' : '').'hashp='.$ecmfile->share; // Hash for public share
1209  }
1210  if ($forcedownload) {
1211  $paramlink .= ($paramlink ? '&' : '').'attachment=1';
1212  }
1213 
1214  if ($relativelink) {
1215  $linktoreturn = 'document.php'.($paramlink ? '?'.$paramlink : '');
1216  } else {
1217  $linktoreturn = $urlwithroot.'/document.php'.($paramlink ? '?'.$paramlink : '');
1218  }
1219 
1220  // Here $ecmfile->share is defined
1221  return $linktoreturn;
1222  }
1223 
1224 
1225  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1235  public function add_contact($fk_socpeople, $type_contact, $source = 'external', $notrigger = 0)
1236  {
1237  // phpcs:enable
1238  global $user, $langs;
1239 
1240 
1241  dol_syslog(get_class($this)."::add_contact $fk_socpeople, $type_contact, $source, $notrigger");
1242 
1243  // Check parameters
1244  if ($fk_socpeople <= 0) {
1245  $langs->load("errors");
1246  $this->error = $langs->trans("ErrorWrongValueForParameterX", "1");
1247  dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1248  return -1;
1249  }
1250  if (!$type_contact) {
1251  $langs->load("errors");
1252  $this->error = $langs->trans("ErrorWrongValueForParameterX", "2");
1253  dol_syslog(get_class($this)."::add_contact ".$this->error, LOG_ERR);
1254  return -2;
1255  }
1256 
1257  $id_type_contact = 0;
1258  if (is_numeric($type_contact)) {
1259  $id_type_contact = $type_contact;
1260  } else {
1261  // We look for id type_contact
1262  $sql = "SELECT tc.rowid";
1263  $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1264  $sql .= " WHERE tc.element='".$this->db->escape($this->element)."'";
1265  $sql .= " AND tc.source='".$this->db->escape($source)."'";
1266  $sql .= " AND tc.code='".$this->db->escape($type_contact)."' AND tc.active=1";
1267  //print $sql;
1268  $resql = $this->db->query($sql);
1269  if ($resql) {
1270  $obj = $this->db->fetch_object($resql);
1271  if ($obj) {
1272  $id_type_contact = $obj->rowid;
1273  }
1274  }
1275  }
1276 
1277  if ($id_type_contact == 0) {
1278  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");
1279  return 0;
1280  }
1281 
1282  $datecreate = dol_now();
1283 
1284  // Socpeople must have already been added by some trigger, then we have to check it to avoid DB_ERROR_RECORD_ALREADY_EXISTS error
1285  $TListeContacts = $this->liste_contact(-1, $source);
1286  $already_added = false;
1287  if (is_array($TListeContacts) && !empty($TListeContacts)) {
1288  foreach ($TListeContacts as $array_contact) {
1289  if ($array_contact['status'] == 4 && $array_contact['id'] == $fk_socpeople && $array_contact['fk_c_type_contact'] == $id_type_contact) {
1290  $already_added = true;
1291  break;
1292  }
1293  }
1294  }
1295 
1296  if (!$already_added) {
1297  $this->db->begin();
1298 
1299  // Insert into database
1300  $sql = "INSERT INTO ".$this->db->prefix()."element_contact";
1301  $sql .= " (element_id, fk_socpeople, datecreate, statut, fk_c_type_contact) ";
1302  $sql .= " VALUES (".$this->id.", ".((int) $fk_socpeople)." , ";
1303  $sql .= "'".$this->db->idate($datecreate)."'";
1304  $sql .= ", 4, ".((int) $id_type_contact);
1305  $sql .= ")";
1306 
1307  $resql = $this->db->query($sql);
1308  if ($resql) {
1309  if (!$notrigger) {
1310  $result = $this->call_trigger(strtoupper($this->element).'_ADD_CONTACT', $user);
1311  if ($result < 0) {
1312  $this->db->rollback();
1313  return -1;
1314  }
1315  }
1316 
1317  $this->db->commit();
1318  return 1;
1319  } else {
1320  if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1321  $this->error = $this->db->errno();
1322  $this->db->rollback();
1323  return -2;
1324  } else {
1325  $this->error = $this->db->lasterror();
1326  $this->db->rollback();
1327  return -1;
1328  }
1329  }
1330  } else {
1331  return 0;
1332  }
1333  }
1334 
1335  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1343  public function copy_linked_contact($objFrom, $source = 'internal')
1344  {
1345  // phpcs:enable
1346  $contacts = $objFrom->liste_contact(-1, $source);
1347  foreach ($contacts as $contact) {
1348  if ($this->add_contact($contact['id'], $contact['fk_c_type_contact'], $contact['source']) < 0) {
1349  return -1;
1350  }
1351  }
1352  return 1;
1353  }
1354 
1355  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1365  public function update_contact($rowid, $statut, $type_contact_id = 0, $fk_socpeople = 0)
1366  {
1367  // phpcs:enable
1368  // Insert into database
1369  $sql = "UPDATE ".$this->db->prefix()."element_contact set";
1370  $sql .= " statut = ".$statut;
1371  if ($type_contact_id) {
1372  $sql .= ", fk_c_type_contact = ".((int) $type_contact_id);
1373  }
1374  if ($fk_socpeople) {
1375  $sql .= ", fk_socpeople = ".((int) $fk_socpeople);
1376  }
1377  $sql .= " where rowid = ".((int) $rowid);
1378  $resql = $this->db->query($sql);
1379  if ($resql) {
1380  return 0;
1381  } else {
1382  $this->error = $this->db->lasterror();
1383  return -1;
1384  }
1385  }
1386 
1387  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1395  public function delete_contact($rowid, $notrigger = 0)
1396  {
1397  // phpcs:enable
1398  global $user;
1399 
1400  $error = 0;
1401 
1402  $this->db->begin();
1403 
1404  if (!$error && empty($notrigger)) {
1405  // Call trigger
1406  $this->context['contact_id'] = ((int) $rowid);
1407  $result = $this->call_trigger(strtoupper($this->element).'_DELETE_CONTACT', $user);
1408  if ($result < 0) {
1409  $error++;
1410  }
1411  // End call triggers
1412  }
1413 
1414  if (!$error) {
1415  dol_syslog(get_class($this)."::delete_contact", LOG_DEBUG);
1416 
1417  $sql = "DELETE FROM ".MAIN_DB_PREFIX."element_contact";
1418  $sql .= " WHERE rowid = ".((int) $rowid);
1419 
1420  $result = $this->db->query($sql);
1421  if (!$result) {
1422  $error++;
1423  $this->errors[] = $this->db->lasterror();
1424  }
1425  }
1426 
1427  if (!$error) {
1428  $this->db->commit();
1429  return 1;
1430  } else {
1431  $this->error = $this->db->lasterror();
1432  $this->db->rollback();
1433  return -1;
1434  }
1435  }
1436 
1437  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1445  public function delete_linked_contact($source = '', $code = '')
1446  {
1447  // phpcs:enable
1448  $listId = '';
1449  $temp = array();
1450  $typeContact = $this->liste_type_contact($source, '', 0, 0, $code);
1451 
1452  if (!empty($typeContact)) {
1453  foreach ($typeContact as $key => $value) {
1454  array_push($temp, $key);
1455  }
1456  $listId = implode(",", $temp);
1457  }
1458 
1459  // If $listId is empty, we have not criteria on fk_c_type_contact so we will delete record on element_id for
1460  // any type or record instead of only the ones of the current object. So we do nothing in such a case.
1461  if (empty($listId)) {
1462  return 0;
1463  }
1464 
1465  $sql = "DELETE FROM ".$this->db->prefix()."element_contact";
1466  $sql .= " WHERE element_id = ".((int) $this->id);
1467  $sql .= " AND fk_c_type_contact IN (".$this->db->sanitize($listId).")";
1468 
1469  dol_syslog(get_class($this)."::delete_linked_contact", LOG_DEBUG);
1470  if ($this->db->query($sql)) {
1471  return 1;
1472  } else {
1473  $this->error = $this->db->lasterror();
1474  return -1;
1475  }
1476  }
1477 
1478  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1490  public function liste_contact($statusoflink = -1, $source = 'external', $list = 0, $code = '', $status = -1, $arrayoftcids = array())
1491  {
1492  // phpcs:enable
1493  global $langs;
1494 
1495  $tab = array();
1496 
1497  $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
1498  if ($source == 'internal') {
1499  $sql .= ", '-1' as socid, t.statut as statuscontact, t.login, t.photo, t.gender";
1500  }
1501  if ($source == 'external' || $source == 'thirdparty') {
1502  $sql .= ", t.fk_soc as socid, t.statut as statuscontact";
1503  }
1504  $sql .= ", t.civility as civility, t.lastname as lastname, t.firstname, t.email";
1505  $sql .= ", tc.source, tc.element, tc.code, tc.libelle as type_label";
1506  $sql .= " FROM ".$this->db->prefix()."c_type_contact tc,";
1507  $sql .= " ".$this->db->prefix()."element_contact ec";
1508  if ($source == 'internal') { // internal contact (user)
1509  $sql .= " LEFT JOIN ".$this->db->prefix()."user t on ec.fk_socpeople = t.rowid";
1510  }
1511  if ($source == 'external' || $source == 'thirdparty') { // external contact (socpeople)
1512  $sql .= " LEFT JOIN ".$this->db->prefix()."socpeople t on ec.fk_socpeople = t.rowid";
1513  }
1514  $sql .= " WHERE ec.element_id = ".((int) $this->id);
1515  $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1516  $sql .= " AND tc.element = '".$this->db->escape($this->element)."'";
1517  if ($code) {
1518  $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1519  }
1520  if ($source == 'internal') {
1521  $sql .= " AND tc.source = 'internal'";
1522  if ($status >= 0) {
1523  $sql .= " AND t.statut = ".((int) $status);
1524  }
1525  }
1526  if ($source == 'external' || $source == 'thirdparty') {
1527  $sql .= " AND tc.source = 'external'";
1528  if ($status >= 0) {
1529  $sql .= " AND t.statut = ".((int) $status); // t is llx_socpeople
1530  }
1531  }
1532  $sql .= " AND tc.active = 1";
1533  if ($statusoflink >= 0) {
1534  $sql .= " AND ec.statut = ".((int) $statusoflink);
1535  }
1536  $sql .= " ORDER BY t.lastname ASC";
1537 
1538  dol_syslog(get_class($this)."::liste_contact", LOG_DEBUG);
1539  $resql = $this->db->query($sql);
1540  if ($resql) {
1541  $num = $this->db->num_rows($resql);
1542  $i = 0;
1543  while ($i < $num) {
1544  $obj = $this->db->fetch_object($resql);
1545 
1546  if (!$list) {
1547  $transkey = "TypeContact_".$obj->element."_".$obj->source."_".$obj->code;
1548  $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1549  $tab[$i] = array(
1550  'parentId' => $this->id,
1551  'source' => $obj->source,
1552  'socid' => $obj->socid,
1553  'id' => $obj->id,
1554  'nom' => $obj->lastname, // For backward compatibility
1555  'civility' => $obj->civility,
1556  'lastname' => $obj->lastname,
1557  'firstname' => $obj->firstname,
1558  'email' => $obj->email,
1559  'login' => (empty($obj->login) ? '' : $obj->login),
1560  'photo' => (empty($obj->photo) ? '' : $obj->photo),
1561  'gender' => (empty($obj->gender) ? '' : $obj->gender),
1562  'statuscontact' => $obj->statuscontact,
1563  'rowid' => $obj->rowid,
1564  'code' => $obj->code,
1565  'libelle' => $libelle_type,
1566  'status' => $obj->statuslink,
1567  'fk_c_type_contact' => $obj->fk_c_type_contact
1568  );
1569  } else {
1570  $tab[$i] = $obj->id;
1571  }
1572 
1573  $i++;
1574  }
1575 
1576  return $tab;
1577  } else {
1578  $this->error = $this->db->lasterror();
1579  dol_print_error($this->db);
1580  return -1;
1581  }
1582  }
1583 
1584 
1591  public function swapContactStatus($rowid)
1592  {
1593  $sql = "SELECT ec.datecreate, ec.statut, ec.fk_socpeople, ec.fk_c_type_contact,";
1594  $sql .= " tc.code, tc.libelle as type_label";
1595  $sql .= " FROM (".$this->db->prefix()."element_contact as ec, ".$this->db->prefix()."c_type_contact as tc)";
1596  $sql .= " WHERE ec.rowid =".((int) $rowid);
1597  $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1598  $sql .= " AND tc.element = '".$this->db->escape($this->element)."'";
1599 
1600  dol_syslog(get_class($this)."::swapContactStatus", LOG_DEBUG);
1601  $resql = $this->db->query($sql);
1602  if ($resql) {
1603  $obj = $this->db->fetch_object($resql);
1604  $newstatut = ($obj->statut == 4) ? 5 : 4;
1605  $result = $this->update_contact($rowid, $newstatut);
1606  $this->db->free($resql);
1607  return $result;
1608  } else {
1609  $this->error = $this->db->error();
1610  dol_print_error($this->db);
1611  return -1;
1612  }
1613  }
1614 
1615  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1626  public function liste_type_contact($source = 'internal', $order = 'position', $option = 0, $activeonly = 0, $code = '')
1627  {
1628  // phpcs:enable
1629  global $langs;
1630 
1631  if (empty($order)) {
1632  $order = 'position';
1633  }
1634  if ($order == 'position') {
1635  $order .= ',code';
1636  }
1637 
1638  $tab = array();
1639  $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position";
1640  $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1641  $sql .= " WHERE tc.element='".$this->db->escape($this->element)."'";
1642  if ($activeonly == 1) {
1643  $sql .= " AND tc.active=1"; // only the active types
1644  }
1645  if (!empty($source) && $source != 'all') {
1646  $sql .= " AND tc.source='".$this->db->escape($source)."'";
1647  }
1648  if (!empty($code)) {
1649  $sql .= " AND tc.code='".$this->db->escape($code)."'";
1650  }
1651  $sql .= $this->db->order($order, 'ASC');
1652 
1653  //print "sql=".$sql;
1654  $resql = $this->db->query($sql);
1655  if ($resql) {
1656  $num = $this->db->num_rows($resql);
1657  $i = 0;
1658  while ($i < $num) {
1659  $obj = $this->db->fetch_object($resql);
1660 
1661  $transkey = "TypeContact_".$this->element."_".$source."_".$obj->code;
1662  $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1663  if (empty($option)) {
1664  $tab[$obj->rowid] = $libelle_type;
1665  } else {
1666  $tab[$obj->code] = $libelle_type;
1667  }
1668  $i++;
1669  }
1670  return $tab;
1671  } else {
1672  $this->error = $this->db->lasterror();
1673  //dol_print_error($this->db);
1674  return null;
1675  }
1676  }
1677 
1689  public function listeTypeContacts($source = 'internal', $option = 0, $activeonly = 0, $code = '', $element = '', $excludeelement = '')
1690  {
1691  global $langs, $conf;
1692 
1693  $langs->loadLangs(array('bills', 'contracts', 'interventions', 'orders', 'projects', 'propal', 'ticket', 'agenda'));
1694 
1695  $tab = array();
1696 
1697  $sql = "SELECT DISTINCT tc.rowid, tc.code, tc.libelle as type_label, tc.position, tc.element";
1698  $sql .= " FROM ".$this->db->prefix()."c_type_contact as tc";
1699 
1700  $sqlWhere = array();
1701  if (!empty($element)) {
1702  $sqlWhere[] = " tc.element='".$this->db->escape($element)."'";
1703  }
1704  if (!empty($excludeelement)) {
1705  $sqlWhere[] = " tc.element <> '".$this->db->escape($excludeelement)."'";
1706  }
1707 
1708  if ($activeonly == 1) {
1709  $sqlWhere[] = " tc.active=1"; // only the active types
1710  }
1711 
1712  if (!empty($source) && $source != 'all') {
1713  $sqlWhere[] = " tc.source='".$this->db->escape($source)."'";
1714  }
1715 
1716  if (!empty($code)) {
1717  $sqlWhere[] = " tc.code='".$this->db->escape($code)."'";
1718  }
1719 
1720  if (count($sqlWhere) > 0) {
1721  $sql .= " WHERE ".implode(' AND ', $sqlWhere);
1722  }
1723 
1724  $sql .= $this->db->order('tc.element, tc.position', 'ASC');
1725 
1726  dol_syslog(__METHOD__, LOG_DEBUG);
1727  $resql = $this->db->query($sql);
1728  if ($resql) {
1729  $num = $this->db->num_rows($resql);
1730  if ($num > 0) {
1731  $langs->loadLangs(array("propal", "orders", "bills", "suppliers", "contracts", "supplier_proposal"));
1732 
1733  while ($obj = $this->db->fetch_object($resql)) {
1734  $modulename = $obj->element;
1735  if (strpos($obj->element, 'project') !== false) {
1736  $modulename = 'projet';
1737  } elseif ($obj->element == 'contrat') {
1738  $element = 'contract';
1739  } elseif ($obj->element == 'action') {
1740  $modulename = 'agenda';
1741  } elseif (strpos($obj->element, 'supplier') !== false && $obj->element != 'supplier_proposal') {
1742  $modulename = 'fournisseur';
1743  }
1744  if (!empty($conf->{$modulename}->enabled)) {
1745  $libelle_element = $langs->trans('ContactDefault_'.$obj->element);
1746  $tmpelement = $obj->element;
1747  $transkey = "TypeContact_".$tmpelement."_".$source."_".$obj->code;
1748  $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_label);
1749  $tab[$obj->rowid] = $libelle_element.' - '.$libelle_type;
1750  }
1751  }
1752  }
1753  return $tab;
1754  } else {
1755  $this->error = $this->db->lasterror();
1756  return null;
1757  }
1758  }
1759 
1771  public function getIdContact($source, $code, $status = 0)
1772  {
1773  global $conf;
1774 
1775  $result = array();
1776  $i = 0;
1777  // Particular case for shipping
1778  if ($this->element == 'shipping' && $this->origin_id != 0) {
1779  $id = $this->origin_id;
1780  $element = 'commande';
1781  } elseif ($this->element == 'reception' && $this->origin_id != 0) {
1782  $id = $this->origin_id;
1783  $element = 'order_supplier';
1784  } else {
1785  $id = $this->id;
1786  $element = $this->element;
1787  }
1788 
1789  $sql = "SELECT ec.fk_socpeople";
1790  $sql .= " FROM ".$this->db->prefix()."element_contact as ec,";
1791  if ($source == 'internal') {
1792  $sql .= " ".$this->db->prefix()."user as c,";
1793  }
1794  if ($source == 'external') {
1795  $sql .= " ".$this->db->prefix()."socpeople as c,";
1796  }
1797  $sql .= " ".$this->db->prefix()."c_type_contact as tc";
1798  $sql .= " WHERE ec.element_id = ".((int) $id);
1799  $sql .= " AND ec.fk_socpeople = c.rowid";
1800  if ($source == 'internal') {
1801  $sql .= " AND c.entity IN (".getEntity('user').")";
1802  }
1803  if ($source == 'external') {
1804  $sql .= " AND c.entity IN (".getEntity('societe').")";
1805  }
1806  $sql .= " AND ec.fk_c_type_contact = tc.rowid";
1807  $sql .= " AND tc.element = '".$this->db->escape($element)."'";
1808  $sql .= " AND tc.source = '".$this->db->escape($source)."'";
1809  if ($code) {
1810  $sql .= " AND tc.code = '".$this->db->escape($code)."'";
1811  }
1812  $sql .= " AND tc.active = 1";
1813  if ($status) {
1814  $sql .= " AND ec.statut = ".((int) $status);
1815  }
1816 
1817  dol_syslog(get_class($this)."::getIdContact", LOG_DEBUG);
1818  $resql = $this->db->query($sql);
1819  if ($resql) {
1820  while ($obj = $this->db->fetch_object($resql)) {
1821  $result[$i] = $obj->fk_socpeople;
1822  $i++;
1823  }
1824  } else {
1825  $this->error = $this->db->error();
1826  return null;
1827  }
1828 
1829  return $result;
1830  }
1831 
1832  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1839  public function fetch_contact($contactid = null)
1840  {
1841  // phpcs:enable
1842  if (empty($contactid)) {
1843  $contactid = $this->contact_id;
1844  }
1845 
1846  if (empty($contactid)) {
1847  return 0;
1848  }
1849 
1850  require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
1851  $contact = new Contact($this->db);
1852  $result = $contact->fetch($contactid);
1853  $this->contact = $contact;
1854  return $result;
1855  }
1856 
1857  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1865  public function fetch_thirdparty($force_thirdparty_id = 0)
1866  {
1867  // phpcs:enable
1868  if (empty($this->socid) && empty($this->fk_soc) && empty($force_thirdparty_id)) {
1869  return 0;
1870  }
1871 
1872  require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
1873 
1874  $idtofetch = isset($this->socid) ? $this->socid : (isset($this->fk_soc) ? $this->fk_soc : 0);
1875  if ($force_thirdparty_id) {
1876  $idtofetch = $force_thirdparty_id;
1877  }
1878 
1879  if ($idtofetch) {
1880  $thirdparty = new Societe($this->db);
1881  $result = $thirdparty->fetch($idtofetch);
1882  if ($result < 0) {
1883  $this->errors = array_merge($this->errors, $thirdparty->errors);
1884  }
1885  $this->thirdparty = $thirdparty;
1886 
1887  // Use first price level if level not defined for third party
1888  if (getDolGlobalString('PRODUIT_MULTIPRICES') && empty($this->thirdparty->price_level)) {
1889  $this->thirdparty->price_level = 1;
1890  }
1891 
1892  return $result;
1893  } else {
1894  return -1;
1895  }
1896  }
1897 
1898 
1906  public function fetchOneLike($ref)
1907  {
1908  if (!$this->table_ref_field) {
1909  return 0;
1910  }
1911 
1912  $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element;
1913  $sql .= " WHERE ".$this->table_ref_field." LIKE '".$this->db->escape($ref)."'"; // no escapeforlike here
1914  $sql .= " LIMIT 1";
1915 
1916  $query = $this->db->query($sql);
1917 
1918  if (!$this->db->num_rows($query)) {
1919  return 0;
1920  }
1921 
1922  $result = $this->db->fetch_object($query);
1923 
1924  if (method_exists($this, 'fetch')) {
1925  return $this->fetch($result->rowid);
1926  } else {
1927  $this->error = 'Fetch method not implemented on '.get_class($this);
1928  dol_syslog(get_class($this).'::fetchOneLike Error='.$this->error, LOG_ERR);
1929  array_push($this->errors, $this->error);
1930  return -1;
1931  }
1932  }
1933 
1934  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1942  public function fetch_barcode()
1943  {
1944  // phpcs:enable
1945  global $conf;
1946 
1947  dol_syslog(get_class($this).'::fetch_barcode this->element='.$this->element.' this->barcode_type='.$this->barcode_type);
1948 
1949  $idtype = $this->barcode_type;
1950  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
1951  if ($this->element == 'product' && getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE')) {
1952  $idtype = getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE');
1953  } elseif ($this->element == 'societe') {
1954  $idtype = getDolGlobalString('GENBARCODE_BARCODETYPE_THIRDPARTY');
1955  } else {
1956  dol_syslog('Call fetch_barcode with barcode_type not defined and cannot be guessed', LOG_WARNING);
1957  }
1958  }
1959 
1960  if ($idtype > 0) {
1961  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
1962  $sql = "SELECT rowid, code, libelle as label, coder";
1963  $sql .= " FROM ".$this->db->prefix()."c_barcode_type";
1964  $sql .= " WHERE rowid = ".((int) $idtype);
1965  dol_syslog(get_class($this).'::fetch_barcode', LOG_DEBUG);
1966  $resql = $this->db->query($sql);
1967  if ($resql) {
1968  $obj = $this->db->fetch_object($resql);
1969  $this->barcode_type = $obj->rowid;
1970  $this->barcode_type_code = $obj->code;
1971  $this->barcode_type_label = $obj->label;
1972  $this->barcode_type_coder = $obj->coder;
1973  return 1;
1974  } else {
1975  dol_print_error($this->db);
1976  return -1;
1977  }
1978  }
1979  }
1980  return 0;
1981  }
1982 
1983  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1989  public function fetch_project()
1990  {
1991  // phpcs:enable
1992  return $this->fetch_projet();
1993  }
1994 
1995  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2001  public function fetch_projet()
2002  {
2003  // phpcs:enable
2004  include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
2005 
2006  if (empty($this->fk_project) && !empty($this->fk_projet)) {
2007  $this->fk_project = $this->fk_projet; // For backward compatibility
2008  }
2009  if (empty($this->fk_project)) {
2010  return 0;
2011  }
2012 
2013  $project = new Project($this->db);
2014  $result = $project->fetch($this->fk_project);
2015 
2016  $this->projet = $project; // deprecated
2017  $this->project = $project;
2018  return $result;
2019  }
2020 
2021  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2027  public function fetch_product()
2028  {
2029  // phpcs:enable
2030  include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
2031 
2032  // @phan-suppress-next-line PhanUndeclaredProperty
2033  if (empty($this->fk_product)) {
2034  return 0;
2035  }
2036 
2037  $product = new Product($this->db);
2038  // @phan-suppress-next-line PhanUndeclaredProperty
2039  $result = $product->fetch($this->fk_product);
2040 
2041  $this->product = $product;
2042  return $result;
2043  }
2044 
2045  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2052  public function fetch_user($userid)
2053  {
2054  // phpcs:enable
2055  $user = new User($this->db);
2056  $result = $user->fetch($userid);
2057  $this->user = $user;
2058  return $result;
2059  }
2060 
2061  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2068  public function fetch_origin()
2069  {
2070  // phpcs:enable
2071  $origin = $this->origin ? $this->origin : $this->origin_type;
2072 
2073  // Manage classes with non standard name
2074  if ($origin == 'shipping') {
2075  $origin = 'expedition';
2076  }
2077  if ($origin == 'delivery') {
2078  $origin = 'livraison';
2079  }
2080  if ($origin == 'order_supplier' || $origin == 'supplier_order') {
2081  $origin = 'commandeFournisseur';
2082  }
2083 
2084  $classname = ucfirst($origin);
2085  $this->origin_object = new $classname($this->db);
2086  // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
2087  $this->origin_object->fetch($this->origin_id);
2088  }
2089 
2099  public function fetchObjectFrom($table, $field, $key, $element = null)
2100  {
2101  global $conf;
2102 
2103  $result = false;
2104 
2105  $sql = "SELECT rowid FROM ".$this->db->prefix().$table;
2106  $sql .= " WHERE ".$field." = '".$this->db->escape($key)."'";
2107  if (!empty($element)) {
2108  $sql .= " AND entity IN (".getEntity($element).")";
2109  } else {
2110  $sql .= " AND entity = ".((int) $conf->entity);
2111  }
2112 
2113  dol_syslog(get_class($this).'::fetchObjectFrom', LOG_DEBUG);
2114  $resql = $this->db->query($sql);
2115  if ($resql) {
2116  $obj = $this->db->fetch_object($resql);
2117  // Test for avoid error -1
2118  if ($obj) {
2119  if (method_exists($this, 'fetch')) {
2120  $result = $this->fetch($obj->rowid);
2121  } else {
2122  $this->error = 'fetch() method not implemented on '.get_class($this);
2123  dol_syslog(get_class($this).'::fetchOneLike Error='.$this->error, LOG_ERR);
2124  array_push($this->errors, $this->error);
2125  $result = -1;
2126  }
2127  }
2128  }
2129 
2130  return $result;
2131  }
2132 
2141  public function getValueFrom($table, $id, $field)
2142  {
2143  $result = false;
2144  if (!empty($id) && !empty($field) && !empty($table)) {
2145  $sql = "SELECT ".$field." FROM ".$this->db->prefix().$table;
2146  $sql .= " WHERE rowid = ".((int) $id);
2147 
2148  dol_syslog(get_class($this).'::getValueFrom', LOG_DEBUG);
2149  $resql = $this->db->query($sql);
2150  if ($resql) {
2151  $row = $this->db->fetch_row($resql);
2152  $result = $row[0];
2153  }
2154  }
2155  return $result;
2156  }
2157 
2174  public function setValueFrom($field, $value, $table = '', $id = null, $format = '', $id_field = '', $fuser = null, $trigkey = '', $fk_user_field = 'fk_user_modif')
2175  {
2176  global $user;
2177 
2178  if (empty($table)) {
2179  $table = $this->table_element;
2180  }
2181  if (empty($id)) {
2182  $id = $this->id;
2183  }
2184  if (empty($format)) {
2185  $format = 'text';
2186  }
2187  if (empty($id_field)) {
2188  $id_field = 'rowid';
2189  }
2190 
2191  // Special case
2192  if ($table == 'product' && $field == 'note_private') {
2193  $field = 'note';
2194  }
2195 
2196  if (in_array($table, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
2197  $fk_user_field = 'fk_user_mod';
2198  }
2199  if (in_array($table, array('prelevement_bons'))) { // TODO Add a field fk_user_modif into llx_prelevement_bons
2200  $fk_user_field = '';
2201  }
2202 
2203  $oldvalue = null;
2204  if ($trigkey) {
2205  $sql = "SELECT " . $field;
2206  $sql .= " FROM " . MAIN_DB_PREFIX . $table;
2207  $sql .= " WHERE " . $id_field . " = " . ((int) $id);
2208 
2209  $resql = $this->db->query($sql);
2210  if ($resql) {
2211  if ($obj = $this->db->fetch_object($resql)) {
2212  if ($format == 'date') {
2213  $oldvalue = $this->db->jdate($obj->$field);
2214  } else {
2215  $oldvalue = $obj->$field;
2216  }
2217  }
2218  } else {
2219  $this->error = $this->db->lasterror();
2220  return -1;
2221  }
2222  }
2223 
2224  $error = 0;
2225 
2226  dol_syslog(__METHOD__, LOG_DEBUG);
2227 
2228  $this->db->begin();
2229 
2230  $sql = "UPDATE ".$this->db->prefix().$table." SET ";
2231 
2232  if ($format == 'text') {
2233  $sql .= $field." = '".$this->db->escape($value)."'";
2234  } elseif ($format == 'int') {
2235  $sql .= $field." = ".((int) $value);
2236  } elseif ($format == 'date') {
2237  $sql .= $field." = ".($value ? "'".$this->db->idate($value)."'" : "null");
2238  } elseif ($format == 'dategmt') {
2239  $sql .= $field." = ".($value ? "'".$this->db->idate($value, 'gmt')."'" : "null");
2240  }
2241 
2242  if ($fk_user_field) {
2243  if (!empty($fuser) && is_object($fuser)) {
2244  $sql .= ", ".$fk_user_field." = ".((int) $fuser->id);
2245  } elseif (empty($fuser) || $fuser != 'none') {
2246  $sql .= ", ".$fk_user_field." = ".((int) $user->id);
2247  }
2248  }
2249 
2250  $sql .= " WHERE ".$id_field." = ".((int) $id);
2251 
2252  $resql = $this->db->query($sql);
2253  if ($resql) {
2254  if ($trigkey) {
2255  // call trigger with updated object values
2256  if (method_exists($this, 'fetch')) {
2257  $result = $this->fetch($id);
2258  } else {
2259  $result = $this->fetchCommon($id);
2260  }
2261  $this->oldcopy = clone $this;
2262  if (property_exists($this->oldcopy, $field)) {
2263  $this->oldcopy->$field = $oldvalue;
2264  }
2265 
2266  if ($result >= 0) {
2267  $result = $this->call_trigger($trigkey, (!empty($fuser) && is_object($fuser)) ? $fuser : $user); // This may set this->errors
2268  }
2269  if ($result < 0) {
2270  $error++;
2271  }
2272  }
2273 
2274  if (!$error) {
2275  if (property_exists($this, $field)) {
2276  $this->$field = $value;
2277  }
2278  $this->db->commit();
2279  return 1;
2280  } else {
2281  $this->db->rollback();
2282  return -2;
2283  }
2284  } else {
2285  if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
2286  $this->error = 'DB_ERROR_RECORD_ALREADY_EXISTS';
2287  } else {
2288  $this->error = $this->db->lasterror();
2289  }
2290  $this->db->rollback();
2291  return -1;
2292  }
2293  }
2294 
2295  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2306  public function load_previous_next_ref($filter, $fieldid, $nodbprefix = 0)
2307  {
2308  // phpcs:enable
2309  global $conf, $user;
2310 
2311  if (!$this->table_element) {
2312  dol_print_error(null, get_class($this)."::load_previous_next_ref was called on object with property table_element not defined");
2313  return -1;
2314  }
2315  if ($fieldid == 'none') {
2316  return 1;
2317  }
2318 
2319  // For backward compatibility
2320  if (in_array($this->table_element, array('facture_rec', 'facture_fourn_rec')) && $fieldid == 'title') {
2321  $fieldid = 'titre';
2322  }
2323 
2324  // Security on socid
2325  $socid = 0;
2326  if ($user->socid > 0) {
2327  $socid = $user->socid;
2328  }
2329 
2330  // this->ismultientitymanaged contains
2331  // 0=No test on entity, 1=Test with field entity, 'field@table'=Test with link by field@table
2332  $aliastablesociete = 's';
2333  if ($this->element == 'societe') {
2334  $aliastablesociete = 'te'; // te as table_element
2335  }
2336  $restrictiononfksoc = empty($this->restrictiononfksoc) ? 0 : $this->restrictiononfksoc;
2337  $sql = "SELECT MAX(te.".$fieldid.")";
2338  $sql .= " FROM ".(empty($nodbprefix) ? $this->db->prefix() : '').$this->table_element." as te";
2339  if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2340  $tmparray = explode('@', $this->ismultientitymanaged);
2341  $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
2342  } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2343  $sql .= ", ".$this->db->prefix()."societe as s"; // If we need to link to societe to limit select to socid
2344  } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2345  $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
2346  }
2347  if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2348  $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
2349  }
2350  if ($fieldid == 'rowid') {
2351  $sql .= " WHERE te.".$fieldid." < ".((int) $this->id);
2352  } else {
2353  $sql .= " WHERE te.".$fieldid." < '".$this->db->escape($this->ref)."'"; // ->ref must always be defined (set to id if field does not exists)
2354  }
2355  if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2356  $sql .= " AND sc.fk_user = ".((int) $user->id);
2357  }
2358  if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2359  $sql .= " AND (sc.fk_user = ".((int) $user->id).' OR te.fk_soc IS NULL)';
2360  }
2361 
2362  $filtermax = $filter;
2363 
2364  // Manage filter
2365  $errormessage = '';
2366  $tmpsql = forgeSQLFromUniversalSearchCriteria($filtermax, $errormessage);
2367  if ($errormessage) {
2368  if (!preg_match('/^\s*AND/i', $filtermax)) {
2369  $sql .= " AND ";
2370  }
2371  $sql .= $filtermax;
2372  } else {
2373  $sql .= $tmpsql;
2374  }
2375 
2376  if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2377  $tmparray = explode('@', $this->ismultientitymanaged);
2378  $sql .= " AND te.".$tmparray[0]." = ".($tmparray[1] == "societe" ? "s" : "parenttable").".rowid"; // If we need to link to this table to limit select to entity
2379  } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2380  $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2381  }
2382  if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2383  if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2384  if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2385  $sql .= " AND te.entity IS NOT NULL"; // Show all users
2386  } else {
2387  $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM ".$this->db->prefix()."usergroup_user as ug WHERE ug.entity IN (".getEntity('usergroup')."))";
2388  }
2389  } else {
2390  $sql .= ' AND te.entity IN ('.getEntity($this->element).')';
2391  }
2392  }
2393  if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2394  $tmparray = explode('@', $this->ismultientitymanaged);
2395  $sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
2396  }
2397  if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2398  $sql .= ' AND te.fk_soc = '.((int) $socid);
2399  }
2400  if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2401  $sql .= ' AND (te.fk_soc = '.((int) $socid).' OR te.fk_soc IS NULL)';
2402  }
2403  if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2404  $sql .= ' AND te.rowid = '.((int) $socid);
2405  }
2406  //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2407 
2408  $result = $this->db->query($sql);
2409  if (!$result) {
2410  $this->error = $this->db->lasterror();
2411  return -1;
2412  }
2413  $row = $this->db->fetch_row($result);
2414  $this->ref_previous = $row[0];
2415 
2416  $sql = "SELECT MIN(te.".$fieldid.")";
2417  $sql .= " FROM ".(empty($nodbprefix) ? $this->db->prefix() : '').$this->table_element." as te";
2418  if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2419  $tmparray = explode('@', $this->ismultientitymanaged);
2420  $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
2421  } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2422  $sql .= ", ".$this->db->prefix()."societe as s"; // If we need to link to societe to limit select to socid
2423  } elseif ($restrictiononfksoc == 2 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2424  $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
2425  }
2426  if ($restrictiononfksoc && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2427  $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON ".$aliastablesociete.".rowid = sc.fk_soc";
2428  }
2429  if ($fieldid == 'rowid') {
2430  $sql .= " WHERE te.".$fieldid." > ".((int) $this->id);
2431  } else {
2432  $sql .= " WHERE te.".$fieldid." > '".$this->db->escape($this->ref)."'"; // ->ref must always be defined (set to id if field does not exists)
2433  }
2434  if ($restrictiononfksoc == 1 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2435  $sql .= " AND (sc.fk_user = ".((int) $user->id);
2436  if (getDolGlobalInt('MAIN_SEE_SUBORDINATES')) {
2437  $userschilds = $user->getAllChildIds();
2438  $sql .= " OR sc.fk_user IN (".$this->db->sanitize(implode(',', $userschilds)).")";
2439  }
2440  $sql .= ')';
2441  }
2442  if ($restrictiononfksoc == 2 && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2443  $sql .= " AND (sc.fk_user = ".((int) $user->id).' OR te.fk_soc IS NULL)';
2444  }
2445 
2446  $filtermin = $filter;
2447 
2448  // Manage filter
2449  $errormessage = '';
2450  $tmpsql = forgeSQLFromUniversalSearchCriteria($filtermin, $errormessage);
2451  if ($errormessage) {
2452  if (!preg_match('/^\s*AND/i', $filtermin)) {
2453  $sql .= " AND ";
2454  }
2455  $sql .= $filtermin;
2456 
2457  $filtermin = '';
2458  } else {
2459  $sql .= $tmpsql;
2460  }
2461 
2462  if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged)) {
2463  $tmparray = explode('@', $this->ismultientitymanaged);
2464  $sql .= " AND te.".$tmparray[0]." = ".($tmparray[1] == "societe" ? "s" : "parenttable").".rowid"; // If we need to link to this table to limit select to entity
2465  } elseif ($restrictiononfksoc == 1 && $this->element != 'societe' && !$user->hasRight('societe', 'client', 'voir') && !$socid) {
2466  $sql .= ' AND te.fk_soc = s.rowid'; // If we need to link to societe to limit select to socid
2467  }
2468  if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
2469  if ($this->element == 'user' && getDolGlobalInt('MULTICOMPANY_TRANSVERSE_MODE')) {
2470  if (!empty($user->admin) && empty($user->entity) && $conf->entity == 1) {
2471  $sql .= " AND te.entity IS NOT NULL"; // Show all users
2472  } else {
2473  $sql .= " AND te.rowid IN (SELECT ug.fk_user FROM ".$this->db->prefix()."usergroup_user as ug WHERE ug.entity IN (".getEntity('usergroup')."))";
2474  }
2475  } else {
2476  $sql .= ' AND te.entity IN ('.getEntity($this->element).')';
2477  }
2478  }
2479  if (isset($this->ismultientitymanaged) && !is_numeric($this->ismultientitymanaged) && $this->element != 'societe') {
2480  $tmparray = explode('@', $this->ismultientitymanaged);
2481  $sql .= ' AND parenttable.entity IN ('.getEntity($tmparray[1]).')';
2482  }
2483  if ($restrictiononfksoc == 1 && $socid && $this->element != 'societe') {
2484  $sql .= ' AND te.fk_soc = '.((int) $socid);
2485  }
2486  if ($restrictiononfksoc == 2 && $socid && $this->element != 'societe') {
2487  $sql .= ' AND (te.fk_soc = '.((int) $socid).' OR te.fk_soc IS NULL)';
2488  }
2489  if ($restrictiononfksoc && $socid && $this->element == 'societe') {
2490  $sql .= ' AND te.rowid = '.((int) $socid);
2491  }
2492  //print 'socid='.$socid.' restrictiononfksoc='.$restrictiononfksoc.' ismultientitymanaged = '.$this->ismultientitymanaged.' filter = '.$filter.' -> '.$sql."<br>";
2493  // 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
2494 
2495  $result = $this->db->query($sql);
2496  if (!$result) {
2497  $this->error = $this->db->lasterror();
2498  return -2;
2499  }
2500  $row = $this->db->fetch_row($result);
2501  $this->ref_next = $row[0];
2502 
2503  return 1;
2504  }
2505 
2506 
2514  public function getListContactId($source = 'external')
2515  {
2516  $contactAlreadySelected = array();
2517  $tab = $this->liste_contact(-1, $source);
2518  $num = count($tab);
2519  $i = 0;
2520  while ($i < $num) {
2521  if ($source == 'thirdparty') {
2522  $contactAlreadySelected[$i] = $tab[$i]['socid'];
2523  } else {
2524  $contactAlreadySelected[$i] = $tab[$i]['id'];
2525  }
2526  $i++;
2527  }
2528  return $contactAlreadySelected;
2529  }
2530 
2531 
2539  public function setProject($projectid, $notrigger = 0)
2540  {
2541  global $user;
2542  $error = 0;
2543 
2544  if (!$this->table_element) {
2545  dol_syslog(get_class($this)."::setProject was called on object with property table_element not defined", LOG_ERR);
2546  return -1;
2547  }
2548 
2549  $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2550  if (!empty($this->fields['fk_project'])) { // Common case
2551  if ($projectid) {
2552  $sql .= " SET fk_project = ".((int) $projectid);
2553  } else {
2554  $sql .= " SET fk_project = NULL";
2555  }
2556  $sql .= ' WHERE rowid = '.((int) $this->id);
2557  } elseif ($this->table_element == 'actioncomm') { // Special case for actioncomm
2558  if ($projectid) {
2559  $sql .= " SET fk_project = ".((int) $projectid);
2560  } else {
2561  $sql .= " SET fk_project = NULL";
2562  }
2563  $sql .= ' WHERE id = '.((int) $this->id);
2564  } else { // Special case for old architecture objects
2565  if ($projectid) {
2566  $sql .= ' SET fk_projet = '.((int) $projectid);
2567  } else {
2568  $sql .= ' SET fk_projet = NULL';
2569  }
2570  $sql .= " WHERE rowid = ".((int) $this->id);
2571  }
2572 
2573  $this->db->begin();
2574 
2575  dol_syslog(get_class($this)."::setProject", LOG_DEBUG);
2576  if ($this->db->query($sql)) {
2577  $this->fk_project = ((int) $projectid);
2578  } else {
2579  dol_print_error($this->db);
2580  $error++;
2581  }
2582 
2583  // Triggers
2584  if (!$error && !$notrigger) {
2585  // Call triggers
2586  $result = $this->call_trigger(strtoupper($this->element) . '_MODIFY', $user);
2587  if ($result < 0) {
2588  $error++;
2589  } //Do also here what you must do to rollback action if trigger fail
2590  // End call triggers
2591  }
2592 
2593  // Commit or rollback
2594  if ($error) {
2595  $this->db->rollback();
2596  return -1;
2597  } else {
2598  $this->db->commit();
2599  return 1;
2600  }
2601  }
2602 
2609  public function setPaymentMethods($id)
2610  {
2611  global $user;
2612 
2613  $error = 0;
2614  $notrigger = 0;
2615 
2616  dol_syslog(get_class($this).'::setPaymentMethods('.$id.')');
2617 
2618  if ($this->status >= 0 || $this->element == 'societe') {
2619  // TODO uniformize field name
2620  $fieldname = 'fk_mode_reglement';
2621  if ($this->element == 'societe') {
2622  $fieldname = 'mode_reglement';
2623  }
2624  if (get_class($this) == 'Fournisseur') {
2625  $fieldname = 'mode_reglement_supplier';
2626  }
2627  if (get_class($this) == 'Tva') {
2628  $fieldname = 'fk_typepayment';
2629  }
2630  if (get_class($this) == 'Salary') {
2631  $fieldname = 'fk_typepayment';
2632  }
2633 
2634  $sql = "UPDATE ".$this->db->prefix().$this->table_element;
2635  $sql .= " SET ".$fieldname." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
2636  $sql .= ' WHERE rowid='.((int) $this->id);
2637 
2638  if ($this->db->query($sql)) {
2639  $this->mode_reglement_id = $id;
2640  // for supplier
2641  if (get_class($this) == 'Fournisseur') {
2642  $this->mode_reglement_supplier_id = $id;
2643  }
2644  // Triggers
2645  if (!$error && !$notrigger) {
2646  // Call triggers
2647  if (get_class($this) == 'Commande') {
2648  $result = $this->call_trigger('ORDER_MODIFY', $user);
2649  } else {
2650  $result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $user);
2651  }
2652  if ($result < 0) {
2653  $error++;
2654  }
2655  // End call triggers
2656  }
2657  return 1;
2658  } else {
2659  dol_syslog(get_class($this).'::setPaymentMethods Error '.$this->db->error());
2660  $this->error = $this->db->error();
2661  return -1;
2662  }
2663  } else {
2664  dol_syslog(get_class($this).'::setPaymentMethods, status of the object is incompatible');
2665  $this->error = 'Status of the object is incompatible '.$this->status;
2666  return -2;
2667  }
2668  }
2669 
2676  public function setMulticurrencyCode($code)
2677  {
2678  dol_syslog(get_class($this).'::setMulticurrencyCode('.$code.')');
2679  if ($this->status >= 0 || $this->element == 'societe') {
2680  $fieldname = 'multicurrency_code';
2681 
2682  $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2683  $sql .= " SET ".$fieldname." = '".$this->db->escape($code)."'";
2684  $sql .= ' WHERE rowid='.((int) $this->id);
2685 
2686  if ($this->db->query($sql)) {
2687  $this->multicurrency_code = $code;
2688 
2689  list($fk_multicurrency, $rate) = MultiCurrency::getIdAndTxFromCode($this->db, $code);
2690  if ($rate) {
2691  $this->setMulticurrencyRate($rate, 2);
2692  }
2693 
2694  return 1;
2695  } else {
2696  dol_syslog(get_class($this).'::setMulticurrencyCode Error '.$sql.' - '.$this->db->error());
2697  $this->error = $this->db->error();
2698  return -1;
2699  }
2700  } else {
2701  dol_syslog(get_class($this).'::setMulticurrencyCode, status of the object is incompatible');
2702  $this->error = 'Status of the object is incompatible '.$this->status;
2703  return -2;
2704  }
2705  }
2706 
2714  public function setMulticurrencyRate($rate, $mode = 1)
2715  {
2716  dol_syslog(get_class($this).'::setMulticurrencyRate('.$rate.', '.$mode.')');
2717  if ($this->status >= 0 || $this->element == 'societe') {
2718  $fieldname = 'multicurrency_tx';
2719 
2720  $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2721  $sql .= " SET ".$fieldname." = ".((float) $rate);
2722  $sql .= ' WHERE rowid='.((int) $this->id);
2723 
2724  if ($this->db->query($sql)) {
2725  $this->multicurrency_tx = $rate;
2726 
2727  // Update line price
2728  if (!empty($this->lines)) {
2729  foreach ($this->lines as &$line) {
2730  // Amounts in company currency will be recalculated
2731  if ($mode == 1) {
2732  $line->subprice = 0;
2733  }
2734 
2735  // Amounts in foreign currency will be recalculated
2736  if ($mode == 2) {
2737  $line->multicurrency_subprice = 0;
2738  }
2739 
2740  switch ($this->element) {
2741  case 'propal':
2744  $this->updateline(
2745  $line->id,
2746  $line->subprice,
2747  $line->qty,
2748  $line->remise_percent,
2749  $line->tva_tx,
2750  $line->localtax1_tx,
2751  $line->localtax2_tx,
2752  ($line->description ? $line->description : $line->desc),
2753  'HT',
2754  $line->info_bits,
2755  $line->special_code,
2756  $line->fk_parent_line,
2757  $line->skip_update_total,
2758  $line->fk_fournprice,
2759  $line->pa_ht,
2760  $line->label,
2761  $line->product_type,
2762  $line->date_start,
2763  $line->date_end,
2764  $line->array_options,
2765  $line->fk_unit,
2766  $line->multicurrency_subprice
2767  );
2768  break;
2769  case 'commande':
2772  $this->updateline(
2773  $line->id,
2774  ($line->description ? $line->description : $line->desc),
2775  $line->subprice,
2776  $line->qty,
2777  $line->remise_percent,
2778  $line->tva_tx,
2779  $line->localtax1_tx,
2780  $line->localtax2_tx,
2781  'HT',
2782  $line->info_bits,
2783  $line->date_start,
2784  $line->date_end,
2785  $line->product_type,
2786  $line->fk_parent_line,
2787  $line->skip_update_total,
2788  $line->fk_fournprice,
2789  $line->pa_ht,
2790  $line->label,
2791  $line->special_code,
2792  $line->array_options,
2793  $line->fk_unit,
2794  $line->multicurrency_subprice
2795  );
2796  break;
2797  case 'facture':
2800  $this->updateline(
2801  $line->id,
2802  ($line->description ? $line->description : $line->desc),
2803  $line->subprice,
2804  $line->qty,
2805  $line->remise_percent,
2806  $line->date_start,
2807  $line->date_end,
2808  $line->tva_tx,
2809  $line->localtax1_tx,
2810  $line->localtax2_tx,
2811  'HT',
2812  $line->info_bits,
2813  $line->product_type,
2814  $line->fk_parent_line,
2815  $line->skip_update_total,
2816  $line->fk_fournprice,
2817  $line->pa_ht,
2818  $line->label,
2819  $line->special_code,
2820  $line->array_options,
2821  $line->situation_percent,
2822  $line->fk_unit,
2823  $line->multicurrency_subprice
2824  );
2825  break;
2826  case 'supplier_proposal':
2829  $this->updateline(
2830  $line->id,
2831  $line->subprice,
2832  $line->qty,
2833  $line->remise_percent,
2834  $line->tva_tx,
2835  $line->localtax1_tx,
2836  $line->localtax2_tx,
2837  ($line->description ? $line->description : $line->desc),
2838  'HT',
2839  $line->info_bits,
2840  $line->special_code,
2841  $line->fk_parent_line,
2842  $line->skip_update_total,
2843  $line->fk_fournprice,
2844  $line->pa_ht,
2845  $line->label,
2846  $line->product_type,
2847  $line->array_options,
2848  $line->ref_fourn,
2849  $line->multicurrency_subprice
2850  );
2851  break;
2852  case 'order_supplier':
2855  $this->updateline(
2856  $line->id,
2857  ($line->description ? $line->description : $line->desc),
2858  $line->subprice,
2859  $line->qty,
2860  $line->remise_percent,
2861  $line->tva_tx,
2862  $line->localtax1_tx,
2863  $line->localtax2_tx,
2864  'HT',
2865  $line->info_bits,
2866  $line->product_type,
2867  false,
2868  $line->date_start,
2869  $line->date_end,
2870  $line->array_options,
2871  $line->fk_unit,
2872  $line->multicurrency_subprice,
2873  $line->ref_supplier
2874  );
2875  break;
2876  case 'invoice_supplier':
2879  $this->updateline(
2880  $line->id,
2881  ($line->description ? $line->description : $line->desc),
2882  $line->subprice,
2883  $line->tva_tx,
2884  $line->localtax1_tx,
2885  $line->localtax2_tx,
2886  $line->qty,
2887  0,
2888  'HT',
2889  $line->info_bits,
2890  $line->product_type,
2891  $line->remise_percent,
2892  false,
2893  $line->date_start,
2894  $line->date_end,
2895  $line->array_options,
2896  $line->fk_unit,
2897  $line->multicurrency_subprice,
2898  $line->ref_supplier
2899  );
2900  break;
2901  default:
2902  dol_syslog(get_class($this).'::setMulticurrencyRate no updateline defined', LOG_DEBUG);
2903  break;
2904  }
2905  }
2906  }
2907 
2908  return 1;
2909  } else {
2910  dol_syslog(get_class($this).'::setMulticurrencyRate Error '.$sql.' - '.$this->db->error());
2911  $this->error = $this->db->error();
2912  return -1;
2913  }
2914  } else {
2915  dol_syslog(get_class($this).'::setMulticurrencyRate, status of the object is incompatible');
2916  $this->error = 'Status of the object is incompatible '.$this->status;
2917  return -2;
2918  }
2919  }
2920 
2928  public function setPaymentTerms($id, $deposit_percent = null)
2929  {
2930  dol_syslog(get_class($this).'::setPaymentTerms('.$id.', '.var_export($deposit_percent, true).')');
2931  if ($this->status >= 0 || $this->element == 'societe') {
2932  // TODO uniformize field name
2933  $fieldname = 'fk_cond_reglement';
2934  if ($this->element == 'societe') {
2935  $fieldname = 'cond_reglement';
2936  }
2937  if (get_class($this) == 'Fournisseur') {
2938  $fieldname = 'cond_reglement_supplier';
2939  }
2940 
2941  if (empty($deposit_percent) || $deposit_percent < 0) {
2942  $deposit_percent = (float) getDictionaryValue('c_payment_term', 'deposit_percent', $id);
2943  }
2944 
2945  if ($deposit_percent > 100) {
2946  $deposit_percent = 100;
2947  }
2948 
2949  $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2950  $sql .= " SET ".$fieldname." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
2951  if (in_array($this->table_element, array('propal', 'commande', 'societe'))) {
2952  $sql .= " , deposit_percent = " . (empty($deposit_percent) ? 'NULL' : "'".$this->db->escape($deposit_percent)."'");
2953  }
2954  $sql .= ' WHERE rowid='.((int) $this->id);
2955 
2956  if ($this->db->query($sql)) {
2957  $this->cond_reglement_id = $id;
2958  // for supplier
2959  if (get_class($this) == 'Fournisseur') {
2960  $this->cond_reglement_supplier_id = $id;
2961  }
2962  $this->cond_reglement = $id; // for compatibility
2963  $this->deposit_percent = $deposit_percent;
2964  return 1;
2965  } else {
2966  dol_syslog(get_class($this).'::setPaymentTerms Error '.$sql.' - '.$this->db->error());
2967  $this->error = $this->db->error();
2968  return -1;
2969  }
2970  } else {
2971  dol_syslog(get_class($this).'::setPaymentTerms, status of the object is incompatible');
2972  $this->error = 'Status of the object is incompatible '.$this->status;
2973  return -2;
2974  }
2975  }
2976 
2983  public function setTransportMode($id)
2984  {
2985  dol_syslog(get_class($this).'::setTransportMode('.$id.')');
2986  if ($this->status >= 0 || $this->element == 'societe') {
2987  $fieldname = 'fk_transport_mode';
2988  if ($this->element == 'societe') {
2989  $fieldname = 'transport_mode';
2990  }
2991  if (get_class($this) == 'Fournisseur') {
2992  $fieldname = 'transport_mode_supplier';
2993  }
2994 
2995  $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
2996  $sql .= " SET ".$fieldname." = ".(($id > 0 || $id == '0') ? ((int) $id) : 'NULL');
2997  $sql .= ' WHERE rowid='.((int) $this->id);
2998 
2999  if ($this->db->query($sql)) {
3000  $this->transport_mode_id = $id;
3001  // for supplier
3002  if (get_class($this) == 'Fournisseur') {
3003  $this->transport_mode_supplier_id = $id;
3004  }
3005  return 1;
3006  } else {
3007  dol_syslog(get_class($this).'::setTransportMode Error '.$sql.' - '.$this->db->error());
3008  $this->error = $this->db->error();
3009  return -1;
3010  }
3011  } else {
3012  dol_syslog(get_class($this).'::setTransportMode, status of the object is incompatible');
3013  $this->error = 'Status of the object is incompatible '.$this->status;
3014  return -2;
3015  }
3016  }
3017 
3024  public function setRetainedWarrantyPaymentTerms($id)
3025  {
3026  dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms('.$id.')');
3027  if ($this->status >= 0 || $this->element == 'societe') {
3028  $fieldname = 'retained_warranty_fk_cond_reglement';
3029 
3030  $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
3031  $sql .= " SET ".$fieldname." = ".((int) $id);
3032  $sql .= ' WHERE rowid='.((int) $this->id);
3033 
3034  if ($this->db->query($sql)) {
3035  $this->retained_warranty_fk_cond_reglement = $id;
3036  return 1;
3037  } else {
3038  dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms Error '.$sql.' - '.$this->db->error());
3039  $this->error = $this->db->error();
3040  return -1;
3041  }
3042  } else {
3043  dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms, status of the object is incompatible');
3044  $this->error = 'Status of the object is incompatible '.$this->status;
3045  return -2;
3046  }
3047  }
3048 
3056  public function setDeliveryAddress($id)
3057  {
3058  $fieldname = 'fk_delivery_address';
3059  if ($this->element == 'delivery' || $this->element == 'shipping') {
3060  $fieldname = 'fk_address';
3061  }
3062 
3063  $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ".$fieldname." = ".((int) $id);
3064  $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = 0";
3065 
3066  if ($this->db->query($sql)) {
3067  $this->fk_delivery_address = $id;
3068  return 1;
3069  } else {
3070  $this->error = $this->db->error();
3071  dol_syslog(get_class($this).'::setDeliveryAddress Error '.$this->error);
3072  return -1;
3073  }
3074  }
3075 
3076 
3085  public function setShippingMethod($shipping_method_id, $notrigger = 0, $userused = null)
3086  {
3087  global $user;
3088 
3089  if (empty($userused)) {
3090  $userused = $user;
3091  }
3092 
3093  $error = 0;
3094 
3095  if (!$this->table_element) {
3096  dol_syslog(get_class($this)."::setShippingMethod was called on object with property table_element not defined", LOG_ERR);
3097  return -1;
3098  }
3099 
3100  $this->db->begin();
3101 
3102  if ($shipping_method_id < 0) {
3103  $shipping_method_id = 'NULL';
3104  }
3105  dol_syslog(get_class($this).'::setShippingMethod('.$shipping_method_id.')');
3106 
3107  $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3108  $sql .= " SET fk_shipping_method = ".((int) $shipping_method_id);
3109  $sql .= " WHERE rowid=".((int) $this->id);
3110  $resql = $this->db->query($sql);
3111  if (!$resql) {
3112  dol_syslog(get_class($this).'::setShippingMethod Error ', LOG_DEBUG);
3113  $this->error = $this->db->lasterror();
3114  $error++;
3115  } else {
3116  if (!$notrigger) {
3117  // Call trigger
3118  $this->context = array('shippingmethodupdate' => 1);
3119  $result = $this->call_trigger(strtoupper(get_class($this)).'_MODIFY', $userused);
3120  if ($result < 0) {
3121  $error++;
3122  }
3123  // End call trigger
3124  }
3125  }
3126  if ($error) {
3127  $this->db->rollback();
3128  return -1;
3129  } else {
3130  $this->shipping_method_id = ($shipping_method_id == 'NULL') ? null : $shipping_method_id;
3131  $this->db->commit();
3132  return 1;
3133  }
3134  }
3135 
3136 
3143  public function setWarehouse($warehouse_id)
3144  {
3145  if (!$this->table_element) {
3146  dol_syslog(get_class($this)."::setWarehouse was called on object with property table_element not defined", LOG_ERR);
3147  return -1;
3148  }
3149  if ($warehouse_id < 0) {
3150  $warehouse_id = 'NULL';
3151  }
3152  dol_syslog(get_class($this).'::setWarehouse('.$warehouse_id.')');
3153 
3154  $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3155  $sql .= " SET fk_warehouse = ".((int) $warehouse_id);
3156  $sql .= " WHERE rowid=".((int) $this->id);
3157 
3158  if ($this->db->query($sql)) {
3159  $this->warehouse_id = ($warehouse_id == 'NULL') ? null : $warehouse_id;
3160  return 1;
3161  } else {
3162  dol_syslog(get_class($this).'::setWarehouse Error ', LOG_DEBUG);
3163  $this->error = $this->db->error();
3164  return 0;
3165  }
3166  }
3167 
3168 
3176  public function setDocModel($user, $modelpdf)
3177  {
3178  if (!$this->table_element) {
3179  dol_syslog(get_class($this)."::setDocModel was called on object with property table_element not defined", LOG_ERR);
3180  return -1;
3181  }
3182 
3183  $newmodelpdf = dol_trunc($modelpdf, 255);
3184 
3185  $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3186  $sql .= " SET model_pdf = '".$this->db->escape($newmodelpdf)."'";
3187  $sql .= " WHERE rowid = ".((int) $this->id);
3188 
3189  dol_syslog(get_class($this)."::setDocModel", LOG_DEBUG);
3190  $resql = $this->db->query($sql);
3191  if ($resql) {
3192  $this->model_pdf = $modelpdf;
3193  return 1;
3194  } else {
3195  dol_print_error($this->db);
3196  return 0;
3197  }
3198  }
3199 
3200 
3209  public function setBankAccount($fk_account, $notrigger = 0, $userused = null)
3210  {
3211  global $user;
3212 
3213  if (empty($userused)) {
3214  $userused = $user;
3215  }
3216 
3217  $error = 0;
3218 
3219  if (!$this->table_element) {
3220  dol_syslog(get_class($this)."::setBankAccount was called on object with property table_element not defined", LOG_ERR);
3221  return -1;
3222  }
3223  $this->db->begin();
3224 
3225  if ($fk_account < 0) {
3226  $fk_account = 'NULL';
3227  }
3228  dol_syslog(get_class($this).'::setBankAccount('.$fk_account.')');
3229 
3230  $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3231  $sql .= " SET fk_account = ".((int) $fk_account);
3232  $sql .= " WHERE rowid=".((int) $this->id);
3233 
3234  $resql = $this->db->query($sql);
3235  if (!$resql) {
3236  dol_syslog(get_class($this).'::setBankAccount Error '.$sql.' - '.$this->db->error());
3237  $this->error = $this->db->lasterror();
3238  $error++;
3239  } else {
3240  if (!$notrigger) {
3241  // Call trigger
3242  $this->context['bankaccountupdate'] = 1;
3243  $triggerName = strtoupper(get_class($this)).'_MODIFY';
3244  // Special cases
3245  if ($triggerName == 'FACTUREREC_MODIFY') {
3246  $triggerName = 'BILLREC_MODIFY';
3247  }
3248  $result = $this->call_trigger($triggerName, $userused);
3249  if ($result < 0) {
3250  $error++;
3251  }
3252  // End call trigger
3253  }
3254  }
3255  if ($error) {
3256  $this->db->rollback();
3257  return -1;
3258  } else {
3259  $this->fk_account = ($fk_account == 'NULL') ? null : $fk_account;
3260  $this->db->commit();
3261  return 1;
3262  }
3263  }
3264 
3265 
3266  // TODO: Move line related operations to CommonObjectLine?
3267 
3268  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3278  public function line_order($renum = false, $rowidorder = 'ASC', $fk_parent_line = true)
3279  {
3280  // phpcs:enable
3281  if (!$this->table_element_line) {
3282  dol_syslog(get_class($this)."::line_order was called on object with property table_element_line not defined", LOG_ERR);
3283  return -1;
3284  }
3285  if (!$this->fk_element) {
3286  dol_syslog(get_class($this)."::line_order was called on object with property fk_element not defined", LOG_ERR);
3287  return -1;
3288  }
3289 
3290  $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3291  if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3292  $fieldposition = 'position';
3293  }
3294 
3295  // Count number of lines to reorder (according to choice $renum)
3296  $nl = 0;
3297  $sql = "SELECT count(rowid) FROM ".$this->db->prefix().$this->table_element_line;
3298  $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3299  if (!$renum) {
3300  $sql .= " AND " . $fieldposition . " = 0";
3301  }
3302  if ($renum) {
3303  $sql .= " AND " . $fieldposition . " <> 0";
3304  }
3305 
3306  dol_syslog(get_class($this)."::line_order", LOG_DEBUG);
3307  $resql = $this->db->query($sql);
3308  if ($resql) {
3309  $row = $this->db->fetch_row($resql);
3310  $nl = $row[0];
3311  } else {
3312  dol_print_error($this->db);
3313  }
3314  if ($nl > 0) {
3315  // The goal of this part is to reorder all lines, with all children lines sharing the same counter that parents.
3316  $rows = array();
3317 
3318  // We first search all lines that are parent lines (for multilevel details lines)
3319  $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element_line;
3320  $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3321  if ($fk_parent_line) {
3322  $sql .= ' AND fk_parent_line IS NULL';
3323  }
3324  $sql .= " ORDER BY " . $fieldposition . " ASC, rowid " . $rowidorder;
3325 
3326  dol_syslog(get_class($this)."::line_order search all parent lines", LOG_DEBUG);
3327  $resql = $this->db->query($sql);
3328  if ($resql) {
3329  $i = 0;
3330  $num = $this->db->num_rows($resql);
3331  while ($i < $num) {
3332  $row = $this->db->fetch_row($resql);
3333  $rows[] = $row[0]; // Add parent line into array rows
3334  $children = $this->getChildrenOfLine($row[0]);
3335  if (!empty($children)) {
3336  foreach ($children as $child) {
3337  array_push($rows, $child);
3338  }
3339  }
3340  $i++;
3341  }
3342 
3343  // Now we set a new number for each lines (parent and children with children included into parent tree)
3344  if (!empty($rows)) {
3345  foreach ($rows as $key => $row) {
3346  $this->updateRangOfLine($row, ($key + 1));
3347  }
3348  }
3349  } else {
3350  dol_print_error($this->db);
3351  }
3352  }
3353  return 1;
3354  }
3355 
3363  public function getChildrenOfLine($id, $includealltree = 0)
3364  {
3365  $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3366  if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3367  $fieldposition = 'position';
3368  }
3369 
3370  $rows = array();
3371 
3372  $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element_line;
3373  $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3374  $sql .= ' AND fk_parent_line = '.((int) $id);
3375  $sql .= " ORDER BY " . $fieldposition . " ASC";
3376 
3377  dol_syslog(get_class($this)."::getChildrenOfLine search children lines for line ".$id, LOG_DEBUG);
3378  $resql = $this->db->query($sql);
3379  if ($resql) {
3380  if ($this->db->num_rows($resql) > 0) {
3381  while ($row = $this->db->fetch_row($resql)) {
3382  $rows[] = $row[0];
3383  if ($includealltree) {
3384  $rows = array_merge($rows, $this->getChildrenOfLine($row[0], $includealltree));
3385  }
3386  }
3387  }
3388  }
3389  return $rows;
3390  }
3391 
3392  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3400  public function line_up($rowid, $fk_parent_line = true)
3401  {
3402  // phpcs:enable
3403  $this->line_order(false, 'ASC', $fk_parent_line);
3404 
3405  // Get rang of line
3406  $rang = $this->getRangOfLine($rowid);
3407 
3408  // Update position of line
3409  $this->updateLineUp($rowid, $rang);
3410  }
3411 
3412  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3420  public function line_down($rowid, $fk_parent_line = true)
3421  {
3422  // phpcs:enable
3423  $this->line_order(false, 'ASC', $fk_parent_line);
3424 
3425  // Get rang of line
3426  $rang = $this->getRangOfLine($rowid);
3427 
3428  // Get max value for rang
3429  $max = $this->line_max();
3430 
3431  // Update position of line
3432  $this->updateLineDown($rowid, $rang, $max);
3433  }
3434 
3442  public function updateRangOfLine($rowid, $rang)
3443  {
3444  global $hookmanager;
3445  $fieldposition = 'rang'; // @todo Rename 'rang' into 'position'
3446  if (in_array($this->table_element_line, array('bom_bomline', 'ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3447  $fieldposition = 'position';
3448  }
3449 
3450  $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) $rang);
3451  $sql .= ' WHERE rowid = '.((int) $rowid);
3452 
3453  dol_syslog(get_class($this)."::updateRangOfLine", LOG_DEBUG);
3454  if (!$this->db->query($sql)) {
3455  dol_print_error($this->db);
3456  return -1;
3457  } else {
3458  $parameters = array('rowid' => $rowid, 'rang' => $rang, 'fieldposition' => $fieldposition);
3459  $action = '';
3460  $reshook = $hookmanager->executeHooks('afterRankOfLineUpdate', $parameters, $this, $action);
3461  return 1;
3462  }
3463  }
3464 
3465  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3472  public function line_ajaxorder($rows)
3473  {
3474  // phpcs:enable
3475  $num = count($rows);
3476  for ($i = 0; $i < $num; $i++) {
3477  $this->updateRangOfLine($rows[$i], ($i + 1));
3478  }
3479  }
3480 
3488  public function updateLineUp($rowid, $rang)
3489  {
3490  if ($rang > 1) {
3491  $fieldposition = 'rang';
3492  if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3493  $fieldposition = 'position';
3494  }
3495 
3496  $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) $rang);
3497  $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3498  $sql .= " AND " . $fieldposition . " = " . ((int) ($rang - 1));
3499  if ($this->db->query($sql)) {
3500  $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) ($rang - 1));
3501  $sql .= ' WHERE rowid = '.((int) $rowid);
3502  if (!$this->db->query($sql)) {
3503  dol_print_error($this->db);
3504  }
3505  } else {
3506  dol_print_error($this->db);
3507  }
3508  }
3509  }
3510 
3519  public function updateLineDown($rowid, $rang, $max)
3520  {
3521  if ($rang < $max) {
3522  $fieldposition = 'rang';
3523  if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3524  $fieldposition = 'position';
3525  }
3526 
3527  $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) $rang);
3528  $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3529  $sql .= " AND " . $fieldposition . " = " . ((int) ($rang + 1));
3530  if ($this->db->query($sql)) {
3531  $sql = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldposition." = ".((int) ($rang + 1));
3532  $sql .= ' WHERE rowid = '.((int) $rowid);
3533  if (!$this->db->query($sql)) {
3534  dol_print_error($this->db);
3535  }
3536  } else {
3537  dol_print_error($this->db);
3538  }
3539  }
3540  }
3541 
3548  public function getRangOfLine($rowid)
3549  {
3550  $fieldposition = 'rang';
3551  if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3552  $fieldposition = 'position';
3553  }
3554 
3555  $sql = "SELECT " . $fieldposition . " FROM ".$this->db->prefix().$this->table_element_line;
3556  $sql .= " WHERE rowid = ".((int) $rowid);
3557 
3558  dol_syslog(get_class($this)."::getRangOfLine", LOG_DEBUG);
3559  $resql = $this->db->query($sql);
3560  if ($resql) {
3561  $row = $this->db->fetch_row($resql);
3562  return $row[0];
3563  }
3564 
3565  return 0;
3566  }
3567 
3574  public function getIdOfLine($rang)
3575  {
3576  $fieldposition = 'rang';
3577  if (in_array($this->table_element_line, array('ecm_files', 'emailcollector_emailcollectoraction', 'product_attribute_value'))) {
3578  $fieldposition = 'position';
3579  }
3580 
3581  $sql = "SELECT rowid FROM ".$this->db->prefix().$this->table_element_line;
3582  $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3583  $sql .= " AND " . $fieldposition . " = ".((int) $rang);
3584  $resql = $this->db->query($sql);
3585  if ($resql) {
3586  $row = $this->db->fetch_row($resql);
3587  return $row[0];
3588  }
3589 
3590  return 0;
3591  }
3592 
3593  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3600  public function line_max($fk_parent_line = 0)
3601  {
3602  // phpcs:enable
3603  $positionfield = 'rang';
3604  if (in_array($this->table_element, array('bom_bom', 'product_attribute'))) {
3605  $positionfield = 'position';
3606  }
3607 
3608  // Search the last rang with fk_parent_line
3609  if ($fk_parent_line) {
3610  $sql = "SELECT max(".$positionfield.") FROM ".$this->db->prefix().$this->table_element_line;
3611  $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3612  $sql .= " AND fk_parent_line = ".((int) $fk_parent_line);
3613 
3614  dol_syslog(get_class($this)."::line_max", LOG_DEBUG);
3615  $resql = $this->db->query($sql);
3616  if ($resql) {
3617  $row = $this->db->fetch_row($resql);
3618  if (!empty($row[0])) {
3619  return $row[0];
3620  } else {
3621  return $this->getRangOfLine($fk_parent_line);
3622  }
3623  }
3624  } else {
3625  // If not, search the last rang of element
3626  $sql = "SELECT max(".$positionfield.") FROM ".$this->db->prefix().$this->table_element_line;
3627  $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3628 
3629  dol_syslog(get_class($this)."::line_max", LOG_DEBUG);
3630  $resql = $this->db->query($sql);
3631  if ($resql) {
3632  $row = $this->db->fetch_row($resql);
3633  return $row[0];
3634  }
3635  }
3636 
3637  return 0;
3638  }
3639 
3640  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3647  public function update_ref_ext($ref_ext)
3648  {
3649  // phpcs:enable
3650  if (!$this->table_element) {
3651  dol_syslog(get_class($this)."::update_ref_ext was called on object with property table_element not defined", LOG_ERR);
3652  return -1;
3653  }
3654 
3655  $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3656  $sql .= " SET ref_ext = '".$this->db->escape($ref_ext)."'";
3657  $sql .= " WHERE ".(isset($this->table_rowid) ? $this->table_rowid : 'rowid')." = ".((int) $this->id);
3658 
3659  dol_syslog(get_class($this)."::update_ref_ext", LOG_DEBUG);
3660  if ($this->db->query($sql)) {
3661  $this->ref_ext = $ref_ext;
3662  return 1;
3663  } else {
3664  $this->error = $this->db->error();
3665  return -1;
3666  }
3667  }
3668 
3669  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3678  public function update_note($note, $suffix = '', $notrigger = 0)
3679  {
3680  // phpcs:enable
3681  global $user;
3682 
3683  if (!$this->table_element) {
3684  $this->error = 'update_note was called on object with property table_element not defined';
3685  dol_syslog(get_class($this)."::update_note was called on object with property table_element not defined", LOG_ERR);
3686  return -1;
3687  }
3688  if (!in_array($suffix, array('', '_public', '_private'))) {
3689  $this->error = 'update_note Parameter suffix must be empty, \'_private\' or \'_public\'';
3690  dol_syslog(get_class($this)."::update_note Parameter suffix must be empty, '_private' or '_public'", LOG_ERR);
3691  return -2;
3692  }
3693 
3694  $newsuffix = $suffix;
3695 
3696  // Special case
3697  if ($this->table_element == 'product' && $newsuffix == '_private') {
3698  $newsuffix = '';
3699  }
3700  if (in_array($this->table_element, array('actioncomm', 'adherent', 'advtargetemailing', 'cronjob', 'establishment'))) {
3701  $fieldusermod = "fk_user_mod";
3702  } elseif ($this->table_element == 'ecm_files') {
3703  $fieldusermod = "fk_user_m";
3704  } else {
3705  $fieldusermod = "fk_user_modif";
3706  }
3707  $sql = "UPDATE ".$this->db->prefix().$this->table_element;
3708  $sql .= " SET note".$newsuffix." = ".(!empty($note) ? ("'".$this->db->escape($note)."'") : "NULL");
3709  $sql .= ", ".$fieldusermod." = ".((int) $user->id);
3710  $sql .= " WHERE rowid = ".((int) $this->id);
3711 
3712  dol_syslog(get_class($this)."::update_note", LOG_DEBUG);
3713  if ($this->db->query($sql)) {
3714  if ($suffix == '_public') {
3715  $this->note_public = $note;
3716  } elseif ($suffix == '_private') {
3717  $this->note_private = $note;
3718  } else {
3719  $this->note = $note; // deprecated
3720  $this->note_private = $note;
3721  }
3722  if (empty($notrigger)) {
3723  switch ($this->element) {
3724  case 'societe':
3725  $trigger_name = 'COMPANY_MODIFY';
3726  break;
3727  case 'commande':
3728  $trigger_name = 'ORDER_MODIFY';
3729  break;
3730  case 'facture':
3731  $trigger_name = 'BILL_MODIFY';
3732  break;
3733  case 'invoice_supplier':
3734  $trigger_name = 'BILL_SUPPLIER_MODIFY';
3735  break;
3736  case 'facturerec':
3737  $trigger_name = 'BILLREC_MODIFIY';
3738  break;
3739  case 'expensereport':
3740  $trigger_name = 'EXPENSE_REPORT_MODIFY';
3741  break;
3742  default:
3743  $trigger_name = strtoupper($this->element) . '_MODIFY';
3744  }
3745  $ret = $this->call_trigger($trigger_name, $user);
3746  if ($ret < 0) {
3747  return -1;
3748  }
3749  }
3750  return 1;
3751  } else {
3752  $this->error = $this->db->lasterror();
3753  return -1;
3754  }
3755  }
3756 
3757  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3766  public function update_note_public($note)
3767  {
3768  // phpcs:enable
3769  return $this->update_note($note, '_public');
3770  }
3771 
3772  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3783  public function update_price($exclspec = 0, $roundingadjust = 'auto', $nodatabaseupdate = 0, $seller = null)
3784  {
3785  // phpcs:enable
3786  global $conf, $hookmanager, $action;
3787 
3788  $parameters = array('exclspec' => $exclspec, 'roundingadjust' => $roundingadjust, 'nodatabaseupdate' => $nodatabaseupdate, 'seller' => $seller);
3789  $reshook = $hookmanager->executeHooks('updateTotalPrice', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3790  if ($reshook > 0) {
3791  return 1; // replacement code
3792  } elseif ($reshook < 0) {
3793  return -1; // failure
3794  } // reshook = 0 => execute normal code
3795 
3796  // Some external module want no update price after a trigger because they have another method to calculate the total (ex: with an extrafield)
3797  $isElementForSupplier = false;
3798  $roundTotalConstName = 'MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND'; // const for customer by default
3799  $MODULE = "";
3800  if ($this->element == 'propal') {
3801  $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_PROPOSAL";
3802  } elseif ($this->element == 'commande' || $this->element == 'order') {
3803  $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_ORDER";
3804  } elseif ($this->element == 'facture' || $this->element == 'invoice') {
3805  $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_INVOICE";
3806  } elseif ($this->element == 'facture_fourn' || $this->element == 'supplier_invoice' || $this->element == 'invoice_supplier' || $this->element == 'invoice_supplier_rec') {
3807  $isElementForSupplier = true;
3808  $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_INVOICE";
3809  } elseif ($this->element == 'order_supplier' || $this->element == 'supplier_order') {
3810  $isElementForSupplier = true;
3811  $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_ORDER";
3812  } elseif ($this->element == 'supplier_proposal') {
3813  $isElementForSupplier = true;
3814  $MODULE = "MODULE_DISALLOW_UPDATE_PRICE_SUPPLIER_PROPOSAL";
3815  }
3816  if ($isElementForSupplier) {
3817  $roundTotalConstName = 'MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND_SUPPLIER'; // const for supplier
3818  }
3819 
3820  if (!empty($MODULE)) {
3821  if (getDolGlobalString($MODULE)) {
3822  $modsactivated = explode(',', getDolGlobalString($MODULE));
3823  foreach ($modsactivated as $mod) {
3824  if (isModEnabled($mod)) {
3825  return 1; // update was disabled by specific setup
3826  }
3827  }
3828  }
3829  }
3830 
3831  include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
3832 
3833  $forcedroundingmode = $roundingadjust;
3834  if ($forcedroundingmode == 'auto' && isset($conf->global->{$roundTotalConstName})) {
3835  $forcedroundingmode = getDolGlobalString($roundTotalConstName);
3836  } elseif ($forcedroundingmode == 'auto') {
3837  $forcedroundingmode = '0';
3838  }
3839 
3840  $error = 0;
3841 
3842  $multicurrency_tx = !empty($this->multicurrency_tx) ? $this->multicurrency_tx : 1;
3843 
3844  // Define constants to find lines to sum (field name int the table_element_line not into table_element)
3845  $fieldtva = 'total_tva';
3846  $fieldlocaltax1 = 'total_localtax1';
3847  $fieldlocaltax2 = 'total_localtax2';
3848  $fieldup = 'subprice';
3849  $base_price_type = 'HT';
3850  if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
3851  $fieldtva = 'tva';
3852  $fieldup = 'pu_ht';
3853  }
3854  if ($this->element == 'invoice_supplier_rec') {
3855  $fieldup = 'pu_ht';
3856  }
3857  if ($this->element == 'expensereport') {
3858  // Force rounding mode to '0', otherwise when you set MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND to 1, you may have lines with different totals.
3859  // For example, if you have 2 lines with same TTC amounts (6,2 Unit price TTC and VAT rate 20%), on the first line you got 5,17 on HT total
3860  // and 5,16 on HT total and 1,04 on VAT total to get 6,20 on TTT total on second line (see #30051).
3861  $forcedroundingmode = '0';
3862  $fieldup = 'value_unit';
3863  $base_price_type = 'TTC';
3864  }
3865 
3866  $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,";
3867  $sql .= ' tva_tx as vatrate, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, info_bits, product_type';
3868  if ($this->table_element_line == 'facturedet') {
3869  $sql .= ', situation_percent';
3870  }
3871  $sql .= ', multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
3872  $sql .= " FROM ".$this->db->prefix().$this->table_element_line;
3873  $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
3874  if ($exclspec) {
3875  $product_field = 'product_type';
3876  if ($this->table_element_line == 'contratdet') {
3877  $product_field = ''; // contratdet table has no product_type field
3878  }
3879  if ($product_field) {
3880  $sql .= " AND ".$product_field." <> 9";
3881  }
3882  }
3883  $sql .= ' ORDER by rowid'; // We want to be certain to always use same order of line to not change lines differently when option MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND is used
3884 
3885  dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
3886 
3887  $resql = $this->db->query($sql);
3888  if ($resql) {
3889  $this->total_ht = 0;
3890  $this->total_tva = 0;
3891  $this->total_localtax1 = 0;
3892  $this->total_localtax2 = 0;
3893  $this->total_ttc = 0;
3894  $total_ht_by_vats = array();
3895  $total_tva_by_vats = array();
3896  $total_ttc_by_vats = array();
3897  $this->multicurrency_total_ht = 0;
3898  $this->multicurrency_total_tva = 0;
3899  $this->multicurrency_total_ttc = 0;
3900 
3901  $this->db->begin();
3902 
3903  $num = $this->db->num_rows($resql);
3904  $i = 0;
3905  while ($i < $num) {
3906  $obj = $this->db->fetch_object($resql);
3907 
3908  // Note: There is no check on detail line and no check on total, if $forcedroundingmode = '0'
3909  $parameters = array('fk_element' => $obj->rowid);
3910  $reshook = $hookmanager->executeHooks('changeRoundingMode', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3911 
3912  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'
3913  // This part of code is to fix data. We should not call it too often.
3914  $localtax_array = array($obj->localtax1_type, $obj->localtax1_tx, $obj->localtax2_type, $obj->localtax2_tx);
3915  $tmpcal = calcul_price_total($obj->qty, $obj->up, $obj->remise_percent, $obj->vatrate, $obj->localtax1_tx, $obj->localtax2_tx, 0, $base_price_type, $obj->info_bits, $obj->product_type, $seller, $localtax_array, (isset($obj->situation_percent) ? $obj->situation_percent : 100), $multicurrency_tx);
3916 
3917  $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.
3918  $diff_on_current_total = price2num($obj->total_ttc - $obj->total_ht - $obj->total_tva - $obj->total_localtax1 - $obj->total_localtax2, 'MT', 1);
3919  //var_dump($obj->total_ht.' '.$obj->total_tva.' '.$obj->total_localtax1.' '.$obj->total_localtax2.' => '.$obj->total_ttc);
3920  //var_dump($diff_when_using_price_ht.' '.$diff_on_current_total);
3921 
3922  if ($diff_on_current_total) {
3923  // This should not happen, we should always have in table: total_ttc = total_ht + total_vat + total_localtax1 + total_localtax2
3924  $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);
3925  dol_syslog('We found inconsistent 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);
3926  $resqlfix = $this->db->query($sqlfix);
3927  if (!$resqlfix) {
3928  dol_print_error($this->db, 'Failed to update line');
3929  }
3930  $obj->total_tva = $tmpcal[1];
3931  $obj->total_ttc = $tmpcal[2];
3932  } elseif ($diff_when_using_price_ht) {
3933  // After calculation from HT, total is consistent but we have found a difference between VAT part in calculation and into database and
3934  // we ask to force the use of rounding on line (like done on calculation) so we force update of line
3935  $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);
3936  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);
3937  $resqlfix = $this->db->query($sqlfix);
3938  if (!$resqlfix) {
3939  dol_print_error($this->db, 'Failed to update line');
3940  }
3941  $obj->total_tva = $tmpcal[1];
3942  $obj->total_ttc = $tmpcal[2];
3943  }
3944  }
3945 
3946  $this->total_ht += $obj->total_ht; // The field visible at end of line detail
3947  $this->total_tva += $obj->total_tva;
3948  $this->total_localtax1 += $obj->total_localtax1;
3949  $this->total_localtax2 += $obj->total_localtax2;
3950  $this->total_ttc += $obj->total_ttc;
3951  $this->multicurrency_total_ht += $obj->multicurrency_total_ht; // The field visible at end of line detail
3952  $this->multicurrency_total_tva += $obj->multicurrency_total_tva;
3953  $this->multicurrency_total_ttc += $obj->multicurrency_total_ttc;
3954 
3955  if (!isset($total_ht_by_vats[$obj->vatrate])) {
3956  $total_ht_by_vats[$obj->vatrate] = 0;
3957  }
3958  if (!isset($total_tva_by_vats[$obj->vatrate])) {
3959  $total_tva_by_vats[$obj->vatrate] = 0;
3960  }
3961  if (!isset($total_ttc_by_vats[$obj->vatrate])) {
3962  $total_ttc_by_vats[$obj->vatrate] = 0;
3963  }
3964  $total_ht_by_vats[$obj->vatrate] += $obj->total_ht;
3965  $total_tva_by_vats[$obj->vatrate] += $obj->total_tva;
3966  $total_ttc_by_vats[$obj->vatrate] += $obj->total_ttc;
3967 
3968  if ($forcedroundingmode == '1') { // Check if we need adjustment onto line for vat. TODO This works on the company currency but not on foreign currency
3969  if ($base_price_type == 'TTC') {
3970  $tmpvat = price2num($total_ttc_by_vats[$obj->vatrate] * $obj->vatrate / (100 + $obj->vatrate), 'MT', 1);
3971  } else {
3972  $tmpvat = price2num($total_ht_by_vats[$obj->vatrate] * $obj->vatrate / 100, 'MT', 1);
3973  }
3974  $diff = price2num($total_tva_by_vats[$obj->vatrate] - (float) $tmpvat, 'MT', 1);
3975  //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";
3976  if ($diff) {
3977  if (abs((float) $diff) > (10 * pow(10, -1 * getDolGlobalInt('MAIN_MAX_DECIMALS_TOT', 0)))) {
3978  // If error is more than 10 times the accuracy of rounding. This should not happen.
3979  $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.';
3980  dol_syslog($errmsg, LOG_WARNING);
3981  $this->error = $errmsg;
3982  $error++;
3983  break;
3984  }
3985  if ($base_price_type == 'TTC') {
3986  $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldtva." = ".price2num($obj->total_tva - (float) $diff).", total_ht = ".price2num($obj->total_ht + (float) $diff)." WHERE rowid = ".((int) $obj->rowid);
3987  dol_syslog('We found a difference of '.$diff.' for line rowid = '.$obj->rowid.". We fix the total_vat and total_ht of line by running sqlfix = ".$sqlfix);
3988  } else {
3989  $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldtva." = ".price2num($obj->total_tva - (float) $diff).", total_ttc = ".price2num($obj->total_ttc - (float) $diff)." WHERE rowid = ".((int) $obj->rowid);
3990  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);
3991  }
3992 
3993  $resqlfix = $this->db->query($sqlfix);
3994 
3995  if (!$resqlfix) {
3996  dol_print_error($this->db, 'Failed to update line');
3997  }
3998 
3999  $this->total_tva = (float) price2num($this->total_tva - (float) $diff, '', 1);
4000  $total_tva_by_vats[$obj->vatrate] = (float) price2num($total_tva_by_vats[$obj->vatrate] - (float) $diff, '', 1);
4001  if ($base_price_type == 'TTC') {
4002  $this->total_ht = (float) price2num($this->total_ht + (float) $diff, '', 1);
4003  $total_ht_by_vats[$obj->vatrate] = (float) price2num($total_ht_by_vats[$obj->vatrate] + (float) $diff, '', 1);
4004  } else {
4005  $this->total_ttc = (float) price2num($this->total_ttc - (float) $diff, '', 1);
4006  $total_ttc_by_vats[$obj->vatrate] = (float) price2num($total_ttc_by_vats[$obj->vatrate] - (float) $diff, '', 1);
4007  }
4008  }
4009  }
4010 
4011  $i++;
4012  }
4013 
4014  // Add revenue stamp to total
4015  $this->total_ttc += isset($this->revenuestamp) ? $this->revenuestamp : 0;
4016  $this->multicurrency_total_ttc += isset($this->revenuestamp) ? ($this->revenuestamp * $multicurrency_tx) : 0;
4017 
4018  // Situations totals
4019  if (!empty($this->situation_cycle_ref) && !empty($this->situation_counter) && $this->situation_counter > 1 && method_exists($this, 'get_prev_sits')) {
4020  include_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
4021  if ($this->type != Facture::TYPE_CREDIT_NOTE) { // @phpstan-ignore-line
4022  if (getDolGlobalInt('INVOICE_USE_SITUATION') != 2) {
4023  $prev_sits = $this->get_prev_sits();
4024 
4025  foreach ($prev_sits as $sit) { // $sit is an object Facture loaded with a fetch.
4026  $this->total_ht -= $sit->total_ht;
4027  $this->total_tva -= $sit->total_tva;
4028  $this->total_localtax1 -= $sit->total_localtax1;
4029  $this->total_localtax2 -= $sit->total_localtax2;
4030  $this->total_ttc -= $sit->total_ttc;
4031  $this->multicurrency_total_ht -= $sit->multicurrency_total_ht;
4032  $this->multicurrency_total_tva -= $sit->multicurrency_total_tva;
4033  $this->multicurrency_total_ttc -= $sit->multicurrency_total_ttc;
4034  }
4035  }
4036  }
4037  }
4038 
4039  // Clean total
4040  $this->total_ht = (float) price2num($this->total_ht);
4041  $this->total_tva = (float) price2num($this->total_tva);
4042  $this->total_localtax1 = (float) price2num($this->total_localtax1);
4043  $this->total_localtax2 = (float) price2num($this->total_localtax2);
4044  $this->total_ttc = (float) price2num($this->total_ttc);
4045 
4046  $this->db->free($resql);
4047 
4048  // Now update global fields total_ht, total_ttc, total_tva, total_localtax1, total_localtax2, multicurrency_total_* of main object
4049  $fieldht = 'total_ht';
4050  $fieldtva = 'tva';
4051  $fieldlocaltax1 = 'localtax1';
4052  $fieldlocaltax2 = 'localtax2';
4053  $fieldttc = 'total_ttc';
4054  // Specific code for backward compatibility with old field names
4055  if (in_array($this->element, array('propal', 'commande', 'facture', 'facturerec', 'supplier_proposal', 'order_supplier', 'facture_fourn', 'invoice_supplier', 'invoice_supplier_rec', 'expensereport'))) {
4056  $fieldtva = 'total_tva';
4057  }
4058 
4059  if (!$error && empty($nodatabaseupdate)) {
4060  $sql = "UPDATE ".$this->db->prefix().$this->table_element.' SET';
4061  $sql .= " ".$fieldht." = ".((float) price2num($this->total_ht, 'MT', 1)).",";
4062  $sql .= " ".$fieldtva." = ".((float) price2num($this->total_tva, 'MT', 1)).",";
4063  $sql .= " ".$fieldlocaltax1." = ".((float) price2num($this->total_localtax1, 'MT', 1)).",";
4064  $sql .= " ".$fieldlocaltax2." = ".((float) price2num($this->total_localtax2, 'MT', 1)).",";
4065  $sql .= " ".$fieldttc." = ".((float) price2num($this->total_ttc, 'MT', 1));
4066  $sql .= ", multicurrency_total_ht = ".((float) price2num($this->multicurrency_total_ht, 'MT', 1));
4067  $sql .= ", multicurrency_total_tva = ".((float) price2num($this->multicurrency_total_tva, 'MT', 1));
4068  $sql .= ", multicurrency_total_ttc = ".((float) price2num($this->multicurrency_total_ttc, 'MT', 1));
4069  $sql .= " WHERE rowid = ".((int) $this->id);
4070 
4071  dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
4072  $resql = $this->db->query($sql);
4073 
4074  if (!$resql) {
4075  $error++;
4076  $this->error = $this->db->lasterror();
4077  $this->errors[] = $this->db->lasterror();
4078  }
4079  }
4080 
4081  if (!$error) {
4082  $this->db->commit();
4083  return 1;
4084  } else {
4085  $this->db->rollback();
4086  return -1;
4087  }
4088  } else {
4089  dol_print_error($this->db, 'Bad request in update_price');
4090  return -1;
4091  }
4092  }
4093 
4094  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4105  public function add_object_linked($origin = null, $origin_id = null, $f_user = null, $notrigger = 0)
4106  {
4107  // phpcs:enable
4108  global $user, $hookmanager, $action;
4109  $origin = (!empty($origin) ? $origin : $this->origin);
4110  $origin_id = (!empty($origin_id) ? $origin_id : $this->origin_id);
4111  $f_user = isset($f_user) ? $f_user : $user;
4112 
4113  // Special case
4114  if ($origin == 'order') {
4115  $origin = 'commande';
4116  }
4117  if ($origin == 'invoice') {
4118  $origin = 'facture';
4119  }
4120  if ($origin == 'invoice_template') {
4121  $origin = 'facturerec';
4122  }
4123  if ($origin == 'supplierorder') {
4124  $origin = 'order_supplier';
4125  }
4126 
4127  // Add module part to target type
4128  $targettype = $this->getElementType();
4129 
4130  $parameters = array('targettype' => $targettype);
4131  // Hook for explicitly set the targettype if it must be different than $this->element
4132  $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4133  if ($reshook > 0) {
4134  if (!empty($hookmanager->resArray['targettype'])) {
4135  $targettype = $hookmanager->resArray['targettype'];
4136  }
4137  }
4138 
4139  $this->db->begin();
4140  $error = 0;
4141 
4142  $sql = "INSERT INTO " . $this->db->prefix() . "element_element (";
4143  $sql .= "fk_source";
4144  $sql .= ", sourcetype";
4145  $sql .= ", fk_target";
4146  $sql .= ", targettype";
4147  $sql .= ") VALUES (";
4148  $sql .= ((int) $origin_id);
4149  $sql .= ", '" . $this->db->escape($origin) . "'";
4150  $sql .= ", " . ((int) $this->id);
4151  $sql .= ", '" . $this->db->escape($targettype) . "'";
4152  $sql .= ")";
4153 
4154  dol_syslog(get_class($this) . "::add_object_linked", LOG_DEBUG);
4155  if ($this->db->query($sql)) {
4156  if (!$notrigger) {
4157  // Call trigger
4158  $this->context['link_origin'] = $origin;
4159  $this->context['link_origin_id'] = $origin_id;
4160  $result = $this->call_trigger('OBJECT_LINK_INSERT', $f_user);
4161  if ($result < 0) {
4162  $error++;
4163  }
4164  // End call triggers
4165  }
4166  } else {
4167  $this->error = $this->db->lasterror();
4168  $error++;
4169  }
4170 
4171  if (!$error) {
4172  $this->db->commit();
4173  return 1;
4174  } else {
4175  $this->db->rollback();
4176  return 0;
4177  }
4178  }
4179 
4185  public function getElementType()
4186  {
4187  // Elements of the core modules having a `$module` property but for which we may not want to prefix the element name with the module name for finding the linked object in llx_element_element.
4188  // It's because existing llx_element_element entries inserted prior to this modification (version <=14.2) may already use the element name alone in fk_source or fk_target (without the module name prefix).
4189  $coreModule = array('knowledgemanagement', 'partnership', 'workstation', 'ticket', 'recruitment', 'eventorganization', 'asset');
4190  // Add module part to target type if object has $module property and isn't in core modules.
4191  return ((!empty($this->module) && !in_array($this->module, $coreModule)) ? $this->module.'_' : '').$this->element;
4192  }
4193 
4194 
4217  public function fetchObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $clause = 'OR', $alsosametype = 1, $orderby = 'sourcetype', $loadalsoobjects = 1)
4218  {
4219  global $conf, $hookmanager, $action;
4220 
4221  // Important for pdf generation time reduction
4222  // This boolean is true if $this->linkedObjects has already been loaded with all objects linked without filter
4223  // If you need to force the reload, you can call clearObjectLinkedCache() before calling fetchObjectLinked()
4224  if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
4225  return 1;
4226  }
4227 
4228  $this->linkedObjectsIds = array();
4229  $this->linkedObjects = array();
4230 
4231  $justsource = false;
4232  $justtarget = false;
4233  $withtargettype = false;
4234  $withsourcetype = false;
4235 
4236  $parameters = array('sourcetype' => $sourcetype, 'sourceid' => $sourceid, 'targettype' => $targettype, 'targetid' => $targetid);
4237  // Hook for explicitly set the targettype if it must be differtent than $this->element
4238  $reshook = $hookmanager->executeHooks('setLinkedObjectSourceTargetType', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4239  if ($reshook > 0) {
4240  if (!empty($hookmanager->resArray['sourcetype'])) {
4241  $sourcetype = $hookmanager->resArray['sourcetype'];
4242  }
4243  if (!empty($hookmanager->resArray['sourceid'])) {
4244  $sourceid = $hookmanager->resArray['sourceid'];
4245  }
4246  if (!empty($hookmanager->resArray['targettype'])) {
4247  $targettype = $hookmanager->resArray['targettype'];
4248  }
4249  if (!empty($hookmanager->resArray['targetid'])) {
4250  $targetid = $hookmanager->resArray['targetid'];
4251  }
4252  }
4253 
4254  if (!empty($sourceid) && !empty($sourcetype) && empty($targetid)) {
4255  $justsource = true; // the source (id and type) is a search criteria
4256  if (!empty($targettype)) {
4257  $withtargettype = true;
4258  }
4259  }
4260  if (!empty($targetid) && !empty($targettype) && empty($sourceid)) {
4261  $justtarget = true; // the target (id and type) is a search criteria
4262  if (!empty($sourcetype)) {
4263  $withsourcetype = true;
4264  }
4265  }
4266 
4267  $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
4268  $targetid = (!empty($targetid) ? $targetid : $this->id);
4269  $sourcetype = (!empty($sourcetype) ? $sourcetype : $this->element);
4270  $targettype = (!empty($targettype) ? $targettype : $this->element);
4271 
4272  /*if (empty($sourceid) && empty($targetid))
4273  {
4274  dol_syslog('Bad usage of function. No source nor target id defined (nor as parameter nor as object id)', LOG_ERR);
4275  return -1;
4276  }*/
4277 
4278  // Links between objects are stored in table element_element
4279  $sql = "SELECT rowid, fk_source, sourcetype, fk_target, targettype";
4280  $sql .= " FROM ".$this->db->prefix()."element_element";
4281  $sql .= " WHERE ";
4282  if ($justsource || $justtarget) {
4283  if ($justsource) {
4284  $sql .= "fk_source = ".((int) $sourceid)." AND sourcetype = '".$this->db->escape($sourcetype)."'";
4285  if ($withtargettype) {
4286  $sql .= " AND targettype = '".$this->db->escape($targettype)."'";
4287  }
4288  } elseif ($justtarget) {
4289  $sql .= "fk_target = ".((int) $targetid)." AND targettype = '".$this->db->escape($targettype)."'";
4290  if ($withsourcetype) {
4291  $sql .= " AND sourcetype = '".$this->db->escape($sourcetype)."'";
4292  }
4293  }
4294  } else {
4295  $sql .= "(fk_source = ".((int) $sourceid)." AND sourcetype = '".$this->db->escape($sourcetype)."')";
4296  $sql .= " ".$clause." (fk_target = ".((int) $targetid)." AND targettype = '".$this->db->escape($targettype)."')";
4297  if ($loadalsoobjects && $this->id > 0 && $sourceid == $this->id && $sourcetype == $this->element && $targetid == $this->id && $targettype == $this->element && $clause == 'OR') {
4298  $this->linkedObjectsFullLoaded[$this->id] = true;
4299  }
4300  }
4301  $sql .= " ORDER BY ".$orderby;
4302 
4303  dol_syslog(get_class($this)."::fetchObjectLink", LOG_DEBUG);
4304  $resql = $this->db->query($sql);
4305  if ($resql) {
4306  $num = $this->db->num_rows($resql);
4307  $i = 0;
4308  while ($i < $num) {
4309  $obj = $this->db->fetch_object($resql);
4310  if ($justsource || $justtarget) {
4311  if ($justsource) {
4312  $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
4313  } elseif ($justtarget) {
4314  $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
4315  }
4316  } else {
4317  if ($obj->fk_source == $sourceid && $obj->sourcetype == $sourcetype) {
4318  $this->linkedObjectsIds[$obj->targettype][$obj->rowid] = $obj->fk_target;
4319  }
4320  if ($obj->fk_target == $targetid && $obj->targettype == $targettype) {
4321  $this->linkedObjectsIds[$obj->sourcetype][$obj->rowid] = $obj->fk_source;
4322  }
4323  }
4324  $i++;
4325  }
4326 
4327  if (!empty($this->linkedObjectsIds)) {
4328  $tmparray = $this->linkedObjectsIds;
4329  foreach ($tmparray as $objecttype => $objectids) { // $objecttype is a module name ('facture', 'mymodule', ...) or a module name with a suffix ('project_task', 'mymodule_myobj', ...)
4330  $element_properties = getElementProperties($objecttype);
4331  $element = $element_properties['element'];
4332  $classPath = $element_properties['classpath'];
4333  $classFile = $element_properties['classfile'];
4334  $className = $element_properties['classname'];
4335  $module = $element_properties['module'];
4336 
4337  // Here $module, $classFile and $className are set, we can use them.
4338  if (isModEnabled($module) && (($element != $this->element) || $alsosametype)) {
4339  if ($loadalsoobjects && (is_numeric($loadalsoobjects) || ($loadalsoobjects === $objecttype))) {
4340  dol_include_once('/'.$classPath.'/'.$classFile.'.class.php');
4341  if (class_exists($className)) {
4342  foreach ($objectids as $i => $objectid) { // $i is rowid into llx_element_element
4343  $object = new $className($this->db);
4344  $ret = $object->fetch($objectid);
4345  if ($ret >= 0) {
4346  $this->linkedObjects[$objecttype][$i] = $object;
4347  }
4348  }
4349  }
4350  }
4351  } else {
4352  unset($this->linkedObjectsIds[$objecttype]);
4353  }
4354  }
4355  }
4356  return 1;
4357  } else {
4358  dol_print_error($this->db);
4359  return -1;
4360  }
4361  }
4362 
4369  public function clearObjectLinkedCache()
4370  {
4371  if ($this->id > 0 && !empty($this->linkedObjectsFullLoaded[$this->id])) {
4372  unset($this->linkedObjectsFullLoaded[$this->id]);
4373  }
4374 
4375  return 1;
4376  }
4377 
4390  public function updateObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $f_user = null, $notrigger = 0)
4391  {
4392  global $user;
4393  $updatesource = false;
4394  $updatetarget = false;
4395  $f_user = isset($f_user) ? $f_user : $user;
4396 
4397  if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
4398  $updatesource = true;
4399  } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
4400  $updatetarget = true;
4401  }
4402 
4403  $this->db->begin();
4404  $error = 0;
4405 
4406  $sql = "UPDATE " . $this->db->prefix() . "element_element SET ";
4407  if ($updatesource) {
4408  $sql .= "fk_source = " . ((int) $sourceid);
4409  $sql .= ", sourcetype = '" . $this->db->escape($sourcetype) . "'";
4410  $sql .= " WHERE fk_target = " . ((int) $this->id);
4411  $sql .= " AND targettype = '" . $this->db->escape($this->element) . "'";
4412  } elseif ($updatetarget) {
4413  $sql .= "fk_target = " . ((int) $targetid);
4414  $sql .= ", targettype = '" . $this->db->escape($targettype) . "'";
4415  $sql .= " WHERE fk_source = " . ((int) $this->id);
4416  $sql .= " AND sourcetype = '" . $this->db->escape($this->element) . "'";
4417  }
4418 
4419  dol_syslog(get_class($this) . "::updateObjectLinked", LOG_DEBUG);
4420  if ($this->db->query($sql)) {
4421  if (!$notrigger) {
4422  // Call trigger
4423  $this->context['link_source_id'] = $sourceid;
4424  $this->context['link_source_type'] = $sourcetype;
4425  $this->context['link_target_id'] = $targetid;
4426  $this->context['link_target_type'] = $targettype;
4427  $result = $this->call_trigger('OBJECT_LINK_MODIFY', $f_user);
4428  if ($result < 0) {
4429  $error++;
4430  }
4431  // End call triggers
4432  }
4433  } else {
4434  $this->error = $this->db->lasterror();
4435  $error++;
4436  }
4437 
4438  if (!$error) {
4439  $this->db->commit();
4440  return 1;
4441  } else {
4442  $this->db->rollback();
4443  return -1;
4444  }
4445  }
4446 
4460  public function deleteObjectLinked($sourceid = null, $sourcetype = '', $targetid = null, $targettype = '', $rowid = 0, $f_user = null, $notrigger = 0)
4461  {
4462  global $user;
4463  $deletesource = false;
4464  $deletetarget = false;
4465  $f_user = isset($f_user) ? $f_user : $user;
4466 
4467  if (!empty($sourceid) && !empty($sourcetype) && empty($targetid) && empty($targettype)) {
4468  $deletesource = true;
4469  } elseif (empty($sourceid) && empty($sourcetype) && !empty($targetid) && !empty($targettype)) {
4470  $deletetarget = true;
4471  }
4472 
4473  $sourceid = (!empty($sourceid) ? $sourceid : $this->id);
4474  $sourcetype = (!empty($sourcetype) ? $sourcetype : $this->element);
4475  $targetid = (!empty($targetid) ? $targetid : $this->id);
4476  $targettype = (!empty($targettype) ? $targettype : $this->element);
4477  $this->db->begin();
4478  $error = 0;
4479 
4480  if (!$notrigger) {
4481  // Call trigger
4482  $this->context['link_id'] = $rowid;
4483  $this->context['link_source_id'] = $sourceid;
4484  $this->context['link_source_type'] = $sourcetype;
4485  $this->context['link_target_id'] = $targetid;
4486  $this->context['link_target_type'] = $targettype;
4487  $result = $this->call_trigger('OBJECT_LINK_DELETE', $f_user);
4488  if ($result < 0) {
4489  $error++;
4490  }
4491  // End call triggers
4492  }
4493 
4494  if (!$error) {
4495  $sql = "DELETE FROM " . $this->db->prefix() . "element_element";
4496  $sql .= " WHERE";
4497  if ($rowid > 0) {
4498  $sql .= " rowid = " . ((int) $rowid);
4499  } else {
4500  if ($deletesource) {
4501  $sql .= " fk_source = " . ((int) $sourceid) . " AND sourcetype = '" . $this->db->escape($sourcetype) . "'";
4502  $sql .= " AND fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($this->element) . "'";
4503  } elseif ($deletetarget) {
4504  $sql .= " fk_target = " . ((int) $targetid) . " AND targettype = '" . $this->db->escape($targettype) . "'";
4505  $sql .= " AND fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($this->element) . "'";
4506  } else {
4507  $sql .= " (fk_source = " . ((int) $this->id) . " AND sourcetype = '" . $this->db->escape($this->element) . "')";
4508  $sql .= " OR";
4509  $sql .= " (fk_target = " . ((int) $this->id) . " AND targettype = '" . $this->db->escape($this->element) . "')";
4510  }
4511  }
4512 
4513  dol_syslog(get_class($this) . "::deleteObjectLinked", LOG_DEBUG);
4514  if (!$this->db->query($sql)) {
4515  $this->error = $this->db->lasterror();
4516  $this->errors[] = $this->error;
4517  $error++;
4518  }
4519  }
4520 
4521  if (!$error) {
4522  $this->db->commit();
4523  return 1;
4524  } else {
4525  $this->db->rollback();
4526  return 0;
4527  }
4528  }
4529 
4539  public static function getAllItemsLinkedByObjectID($fk_object_where, $field_select, $field_where, $table_element)
4540  {
4541  if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4542  return -1;
4543  }
4544  if (!preg_match('/^[_a-zA-Z0-9]+$/', $field_select)) {
4545  dol_syslog('Invalid value $field_select for parameter '.$field_select.' in call to getAllItemsLinkedByObjectID(). Must be a single field name.', LOG_ERR);
4546  }
4547 
4548  global $db;
4549 
4550  $sql = "SELECT ".$field_select." FROM ".$db->prefix().$table_element." WHERE ".$field_where." = ".((int) $fk_object_where);
4551  $resql = $db->query($sql);
4552 
4553  $TRes = array();
4554  if (!empty($resql)) {
4555  while ($res = $db->fetch_object($resql)) {
4556  $TRes[] = $res->{$field_select};
4557  }
4558  }
4559 
4560  return $TRes;
4561  }
4562 
4571  public static function getCountOfItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
4572  {
4573  if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4574  return -1;
4575  }
4576 
4577  global $db;
4578 
4579  $sql = "SELECT COUNT(*) as nb FROM ".$db->prefix().$table_element." WHERE ".$field_where." = ".((int) $fk_object_where);
4580  $resql = $db->query($sql);
4581  $n = 0;
4582  if ($resql) {
4583  $res = $db->fetch_object($resql);
4584  if ($res) {
4585  $n = $res->nb;
4586  }
4587  }
4588 
4589  return $n;
4590  }
4591 
4600  public static function deleteAllItemsLinkedByObjectID($fk_object_where, $field_where, $table_element)
4601  {
4602  if (empty($fk_object_where) || empty($field_where) || empty($table_element)) {
4603  return -1;
4604  }
4605 
4606  global $db;
4607 
4608  $sql = "DELETE FROM ".$db->prefix().$table_element." WHERE ".$field_where." = ".((int) $fk_object_where);
4609  $resql = $db->query($sql);
4610 
4611  if (empty($resql)) {
4612  return 0;
4613  }
4614 
4615  return 1;
4616  }
4617 
4628  public function setStatut($status, $elementId = null, $elementType = '', $trigkey = '', $fieldstatus = 'fk_statut')
4629  {
4630  global $user;
4631 
4632  $savElementId = $elementId; // To be used later to know if we were using the method using the id of this or not.
4633 
4634  $elementId = (!empty($elementId) ? $elementId : $this->id);
4635  $elementTable = (!empty($elementType) ? $elementType : $this->table_element);
4636 
4637  $this->db->begin();
4638 
4639  if ($elementTable == 'facture_rec') {
4640  $fieldstatus = "suspended";
4641  }
4642  if ($elementTable == 'mailing') {
4643  $fieldstatus = "statut";
4644  }
4645  if ($elementTable == 'cronjob') {
4646  $fieldstatus = "status";
4647  }
4648  if ($elementTable == 'user') {
4649  $fieldstatus = "statut";
4650  }
4651  if ($elementTable == 'expensereport') {
4652  $fieldstatus = "fk_statut";
4653  }
4654  if ($elementTable == 'receptiondet_batch') {
4655  $fieldstatus = "status";
4656  }
4657  if ($elementTable == 'prelevement_bons') {
4658  $fieldstatus = "statut";
4659  }
4660  if (isset($this->fields) && is_array($this->fields) && array_key_exists('status', $this->fields)) {
4661  $fieldstatus = 'status';
4662  }
4663 
4664  $sql = "UPDATE ".$this->db->prefix().$elementTable;
4665  $sql .= " SET ".$fieldstatus." = ".((int) $status);
4666  // If status = 1 = validated, update also fk_user_valid
4667  // TODO Replace the test on $elementTable by doing a test on existence of the field in $this->fields
4668  if ($status == 1 && in_array($elementTable, array('expensereport', 'inventory'))) {
4669  $sql .= ", fk_user_valid = ".((int) $user->id);
4670  }
4671  if ($status == 1 && in_array($elementTable, array('expensereport'))) {
4672  $sql .= ", date_valid = '".$this->db->idate(dol_now())."'";
4673  }
4674  if ($status == 1 && in_array($elementTable, array('inventory'))) {
4675  $sql .= ", date_validation = '".$this->db->idate(dol_now())."'";
4676  }
4677  $sql .= " WHERE rowid = ".((int) $elementId);
4678  $sql .= " AND ".$fieldstatus." <> ".((int) $status); // We avoid update if status already correct
4679 
4680  dol_syslog(get_class($this)."::setStatut", LOG_DEBUG);
4681  $resql = $this->db->query($sql);
4682  if ($resql) {
4683  $error = 0;
4684 
4685  $nb_rows_affected = $this->db->affected_rows($resql); // should be 1 or 0 if status was already correct
4686 
4687  if ($nb_rows_affected > 0) {
4688  if (empty($trigkey)) {
4689  // Try to guess trigkey (for backward compatibility, now we should have trigkey defined into the call of setStatus)
4690  if ($this->element == 'supplier_proposal' && $status == 2) {
4691  $trigkey = 'SUPPLIER_PROPOSAL_SIGN'; // 2 = SupplierProposal::STATUS_SIGNED. Can't use constant into this generic class
4692  }
4693  if ($this->element == 'supplier_proposal' && $status == 3) {
4694  $trigkey = 'SUPPLIER_PROPOSAL_REFUSE'; // 3 = SupplierProposal::STATUS_REFUSED. Can't use constant into this generic class
4695  }
4696  if ($this->element == 'supplier_proposal' && $status == 4) {
4697  $trigkey = 'SUPPLIER_PROPOSAL_CLOSE'; // 4 = SupplierProposal::STATUS_CLOSED. Can't use constant into this generic class
4698  }
4699  if ($this->element == 'fichinter' && $status == 3) {
4700  $trigkey = 'FICHINTER_CLASSIFY_DONE';
4701  }
4702  if ($this->element == 'fichinter' && $status == 2) {
4703  $trigkey = 'FICHINTER_CLASSIFY_BILLED';
4704  }
4705  if ($this->element == 'fichinter' && $status == 1) {
4706  $trigkey = 'FICHINTER_CLASSIFY_UNBILLED';
4707  }
4708  }
4709 
4710  if ($trigkey) {
4711  // Call trigger
4712  $result = $this->call_trigger($trigkey, $user);
4713  if ($result < 0) {
4714  $error++;
4715  }
4716  // End call triggers
4717  }
4718  } else {
4719  // The status was probably already good. We do nothing more, no triggers.
4720  }
4721 
4722  if (!$error) {
4723  $this->db->commit();
4724 
4725  if (empty($savElementId)) {
4726  // If the element we update is $this (so $elementId was provided as null)
4727  if ($fieldstatus == 'tosell') {
4728  $this->status = $status;
4729  } elseif ($fieldstatus == 'tobuy') {
4730  $this->status_buy = $status; // @phpstan-ignore-line
4731  } else {
4732  $this->status = $status;
4733  }
4734  }
4735 
4736  return 1;
4737  } else {
4738  $this->db->rollback();
4739  dol_syslog(get_class($this)."::setStatut ".$this->error, LOG_ERR);
4740  return -1;
4741  }
4742  } else {
4743  $this->error = $this->db->lasterror();
4744  $this->db->rollback();
4745  return -1;
4746  }
4747  }
4748 
4749 
4757  public function getCanvas($id = 0, $ref = '')
4758  {
4759  global $conf;
4760 
4761  if (empty($id) && empty($ref)) {
4762  return 0;
4763  }
4764  if (getDolGlobalString('MAIN_DISABLE_CANVAS')) {
4765  return 0; // To increase speed. Not enabled by default.
4766  }
4767 
4768  // Clean parameters
4769  $ref = trim($ref);
4770 
4771  $sql = "SELECT rowid, canvas";
4772  $sql .= " FROM ".$this->db->prefix().$this->table_element;
4773  $sql .= " WHERE entity IN (".getEntity($this->element).")";
4774  if (!empty($id)) {
4775  $sql .= " AND rowid = ".((int) $id);
4776  }
4777  if (!empty($ref)) {
4778  $sql .= " AND ref = '".$this->db->escape($ref)."'";
4779  }
4780 
4781  $resql = $this->db->query($sql);
4782  if ($resql) {
4783  $obj = $this->db->fetch_object($resql);
4784  if ($obj) {
4785  $this->canvas = $obj->canvas;
4786  return 1;
4787  } else {
4788  return 0;
4789  }
4790  } else {
4791  dol_print_error($this->db);
4792  return -1;
4793  }
4794  }
4795 
4796 
4803  public function getSpecialCode($lineid)
4804  {
4805  $sql = "SELECT special_code FROM ".$this->db->prefix().$this->table_element_line;
4806  $sql .= " WHERE rowid = ".((int) $lineid);
4807  $resql = $this->db->query($sql);
4808  if ($resql) {
4809  $row = $this->db->fetch_row($resql);
4810  return (!empty($row[0]) ? $row[0] : 0);
4811  }
4812 
4813  return 0;
4814  }
4815 
4824  public function isObjectUsed($id = 0, $entity = 0)
4825  {
4826  global $langs;
4827 
4828  if (empty($id)) {
4829  $id = $this->id;
4830  }
4831 
4832  // Check parameters
4833  if (!isset($this->childtables) || !is_array($this->childtables) || count($this->childtables) == 0) {
4834  dol_print_error(null, 'Called isObjectUsed on a class with property this->childtables not defined');
4835  return -1;
4836  }
4837 
4838  $arraytoscan = $this->childtables; // array('tablename'=>array('fk_element'=>'parentfield'), ...) or array('tablename'=>array('parent'=>table_parent, 'parentkey'=>'nameoffieldforparentfkkey'), ...)
4839  // For backward compatibility, we check if array is old format array('tablename1', 'tablename2', ...)
4840  $tmparray = array_keys($this->childtables);
4841  if (is_numeric($tmparray[0])) {
4842  $arraytoscan = array_flip($this->childtables);
4843  }
4844 
4845  // Test if child exists
4846  $haschild = 0;
4847  foreach ($arraytoscan as $table => $element) {
4848  //print $id.'-'.$table.'-'.$elementname.'<br>';
4849  // Check if element can be deleted
4850  $sql = "SELECT COUNT(*) as nb";
4851  $sql .= " FROM ".$this->db->prefix().$table." as c";
4852  if (!empty($element['parent']) && !empty($element['parentkey'])) {
4853  $sql .= ", ".$this->db->prefix().$element['parent']." as p";
4854  }
4855  if (!empty($element['fk_element'])) {
4856  $sql .= " WHERE c.".$element['fk_element']." = ".((int) $id);
4857  } else {
4858  $sql .= " WHERE c.".$this->fk_element." = ".((int) $id);
4859  }
4860  if (!empty($element['parent']) && !empty($element['parentkey'])) {
4861  $sql .= " AND c.".$element['parentkey']." = p.rowid";
4862  }
4863  if (!empty($element['parent']) && !empty($element['parenttypefield']) && !empty($element['parenttypevalue'])) {
4864  $sql .= " AND c.".$element['parenttypefield']." = '".$this->db->escape($element['parenttypevalue'])."'";
4865  }
4866  if (!empty($entity)) {
4867  if (!empty($element['parent']) && !empty($element['parentkey'])) {
4868  $sql .= " AND p.entity = ".((int) $entity);
4869  } else {
4870  $sql .= " AND c.entity = ".((int) $entity);
4871  }
4872  }
4873 
4874  $resql = $this->db->query($sql);
4875  if ($resql) {
4876  $obj = $this->db->fetch_object($resql);
4877  if ($obj->nb > 0) {
4878  $langs->load("errors");
4879  //print 'Found into table '.$table.', type '.$langs->transnoentitiesnoconv($elementname).', haschild='.$haschild;
4880  $haschild += $obj->nb;
4881  if (is_numeric($element)) { // very old usage array('table1', 'table2', ...)
4882  $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $table);
4883  } elseif (is_string($element)) { // old usage array('table1' => 'TranslateKey1', 'table2' => 'TranslateKey2', ...)
4884  $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element));
4885  } else { // new usage: $element['name']=Translation key
4886  $this->errors[] = $langs->transnoentitiesnoconv("ErrorRecordHasAtLeastOneChildOfType", method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref, $langs->transnoentitiesnoconv($element['name']));
4887  }
4888  break; // We found at least one, we stop here
4889  }
4890  } else {
4891  $this->errors[] = $this->db->lasterror();
4892  return -1;
4893  }
4894  }
4895  if ($haschild > 0) {
4896  $this->errors[] = "ErrorRecordHasChildren";
4897  return $haschild;
4898  } else {
4899  return 0;
4900  }
4901  }
4902 
4909  public function hasProductsOrServices($predefined = -1)
4910  {
4911  $nb = 0;
4912 
4913  foreach ($this->lines as $key => $val) {
4914  $qualified = 0;
4915  if ($predefined == -1) {
4916  $qualified = 1;
4917  }
4918  if ($predefined == 1 && $val->fk_product > 0) {
4919  $qualified = 1;
4920  }
4921  if ($predefined == 0 && $val->fk_product <= 0) {
4922  $qualified = 1;
4923  }
4924  if ($predefined == 2 && $val->fk_product > 0 && $val->product_type == 0) {
4925  $qualified = 1;
4926  }
4927  if ($predefined == 3 && $val->fk_product > 0 && $val->product_type == 1) {
4928  $qualified = 1;
4929  }
4930  if ($qualified) {
4931  $nb++;
4932  }
4933  }
4934  dol_syslog(get_class($this).'::hasProductsOrServices we found '.$nb.' qualified lines of products/servcies');
4935  return $nb;
4936  }
4937 
4943  public function getTotalDiscount()
4944  {
4945  if (!empty($this->table_element_line)) {
4946  $total_discount = 0.00;
4947 
4948  $sql = "SELECT subprice as pu_ht, qty, remise_percent, total_ht";
4949  $sql .= " FROM ".$this->db->prefix().$this->table_element_line;
4950  $sql .= " WHERE ".$this->fk_element." = ".((int) $this->id);
4951 
4952  dol_syslog(get_class($this).'::getTotalDiscount', LOG_DEBUG);
4953  $resql = $this->db->query($sql);
4954  if ($resql) {
4955  $num = $this->db->num_rows($resql);
4956  $i = 0;
4957  while ($i < $num) {
4958  $obj = $this->db->fetch_object($resql);
4959 
4960  $pu_ht = $obj->pu_ht;
4961  $qty = $obj->qty;
4962  $total_ht = $obj->total_ht;
4963 
4964  $total_discount_line = (float) price2num(($pu_ht * $qty) - $total_ht, 'MT');
4965  $total_discount += $total_discount_line;
4966 
4967  $i++;
4968  }
4969  }
4970 
4971  //print $total_discount; exit;
4972  return (float) price2num($total_discount);
4973  }
4974 
4975  return null;
4976  }
4977 
4978 
4985  public function getTotalWeightVolume()
4986  {
4987  $totalWeight = 0;
4988  $totalVolume = 0;
4989  // defined for shipment only
4990  $totalOrdered = '';
4991  // defined for shipment only
4992  $totalToShip = '';
4993 
4994  if (empty($this->lines)) {
4995  return array();
4996  }
4997 
4998  foreach ($this->lines as $line) {
4999  if (isset($line->qty_asked)) {
5000  if (empty($totalOrdered)) {
5001  $totalOrdered = 0; // Avoid warning because $totalOrdered is ''
5002  }
5003  $totalOrdered += $line->qty_asked; // defined for shipment only
5004  }
5005  if (isset($line->qty_shipped)) {
5006  if (empty($totalToShip)) {
5007  $totalToShip = 0; // Avoid warning because $totalToShip is ''
5008  }
5009  $totalToShip += $line->qty_shipped; // defined for shipment only
5010  } elseif ($line->element == 'commandefournisseurdispatch' && isset($line->qty)) {
5011  if (empty($totalToShip)) {
5012  $totalToShip = 0;
5013  }
5014  $totalToShip += $line->qty; // defined for reception only
5015  }
5016 
5017  // Define qty, weight, volume, weight_units, volume_units
5018  if ($this->element == 'shipping') {
5019  // for shipments
5020  $qty = $line->qty_shipped ? $line->qty_shipped : 0;
5021  } else {
5022  $qty = $line->qty ? $line->qty : 0;
5023  }
5024 
5025  $weight = !empty($line->weight) ? $line->weight : 0;
5026  ($weight == 0 && !empty($line->product->weight)) ? $weight = $line->product->weight : 0;
5027  $volume = !empty($line->volume) ? $line->volume : 0;
5028  ($volume == 0 && !empty($line->product->volume)) ? $volume = $line->product->volume : 0;
5029 
5030  $weight_units = !empty($line->weight_units) ? $line->weight_units : 0;
5031  ($weight_units == 0 && !empty($line->product->weight_units)) ? $weight_units = $line->product->weight_units : 0;
5032  $volume_units = !empty($line->volume_units) ? $line->volume_units : 0;
5033  ($volume_units == 0 && !empty($line->product->volume_units)) ? $volume_units = $line->product->volume_units : 0;
5034 
5035  $weightUnit = 0;
5036  $volumeUnit = 0;
5037  if (!empty($weight_units)) {
5038  $weightUnit = $weight_units;
5039  }
5040  if (!empty($volume_units)) {
5041  $volumeUnit = $volume_units;
5042  }
5043 
5044  if (empty($totalWeight)) {
5045  $totalWeight = 0; // Avoid warning because $totalWeight is ''
5046  }
5047  if (empty($totalVolume)) {
5048  $totalVolume = 0; // Avoid warning because $totalVolume is ''
5049  }
5050 
5051  //var_dump($line->volume_units);
5052  if ($weight_units < 50) { // < 50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
5053  $trueWeightUnit = pow(10, $weightUnit);
5054  $totalWeight += $weight * $qty * $trueWeightUnit;
5055  } else {
5056  if ($weight_units == 99) {
5057  // conversion 1 Pound = 0.45359237 KG
5058  $trueWeightUnit = 0.45359237;
5059  $totalWeight += $weight * $qty * $trueWeightUnit;
5060  } elseif ($weight_units == 98) {
5061  // conversion 1 Ounce = 0.0283495 KG
5062  $trueWeightUnit = 0.0283495;
5063  $totalWeight += $weight * $qty * $trueWeightUnit;
5064  } else {
5065  $totalWeight += $weight * $qty; // This may be wrong if we mix different units
5066  }
5067  }
5068  if ($volume_units < 50) { // >50 means a standard unit (power of 10 of official unit), > 50 means an exotic unit (like inch)
5069  //print $line->volume."x".$line->volume_units."x".($line->volume_units < 50)."x".$volumeUnit;
5070  $trueVolumeUnit = pow(10, $volumeUnit);
5071  //print $line->volume;
5072  $totalVolume += $volume * $qty * $trueVolumeUnit;
5073  } else {
5074  $totalVolume += $volume * $qty; // This may be wrong if we mix different units
5075  }
5076  }
5077 
5078  return array('weight' => $totalWeight, 'volume' => $totalVolume, 'ordered' => $totalOrdered, 'toship' => $totalToShip);
5079  }
5080 
5081 
5087  public function setExtraParameters()
5088  {
5089  $this->db->begin();
5090 
5091  $extraparams = (!empty($this->extraparams) ? json_encode($this->extraparams) : null);
5092 
5093  $sql = "UPDATE ".$this->db->prefix().$this->table_element;
5094  $sql .= " SET extraparams = ".(!empty($extraparams) ? "'".$this->db->escape($extraparams)."'" : "null");
5095  $sql .= " WHERE rowid = ".((int) $this->id);
5096 
5097  dol_syslog(get_class($this)."::setExtraParameters", LOG_DEBUG);
5098  $resql = $this->db->query($sql);
5099  if (!$resql) {
5100  $this->error = $this->db->lasterror();
5101  $this->db->rollback();
5102  return -1;
5103  } else {
5104  $this->db->commit();
5105  return 1;
5106  }
5107  }
5108 
5109 
5110  // --------------------
5111  // TODO: All functions here must be redesigned and moved as they are not business functions but output functions
5112  // --------------------
5113 
5114  /* This is to show add lines */
5115 
5125  public function formAddObjectLine($dateSelector, $seller, $buyer, $defaulttpldir = '/core/tpl')
5126  {
5127  global $conf, $user, $langs, $object, $hookmanager, $extrafields, $form;
5128 
5129  // Line extrafield
5130  if (!is_object($extrafields)) {
5131  require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5132  $extrafields = new ExtraFields($this->db);
5133  }
5134  $extrafields->fetch_name_optionals_label($this->table_element_line);
5135 
5136  // Output template part (modules that overwrite templates must declare this into descriptor)
5137  // Use global variables + $dateSelector + $seller and $buyer
5138  // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook 'formAddObjectLine'.
5139  $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5140  foreach ($dirtpls as $module => $reldir) {
5141  if (!empty($module)) {
5142  $tpl = dol_buildpath($reldir.'/objectline_create.tpl.php');
5143  } else {
5144  $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_create.tpl.php';
5145  }
5146 
5147  if (empty($conf->file->strict_mode)) {
5148  $res = @include $tpl;
5149  } else {
5150  $res = include $tpl; // for debug
5151  }
5152  if ($res) {
5153  break;
5154  }
5155  }
5156  }
5157 
5158 
5159 
5160  /* This is to show array of line of details */
5161 
5162 
5177  public function printObjectLines($action, $seller, $buyer, $selected = 0, $dateSelector = 0, $defaulttpldir = '/core/tpl')
5178  {
5179  global $conf, $hookmanager, $langs, $user, $form, $extrafields, $object;
5180  // TODO We should not use global var for this
5181  global $inputalsopricewithtax, $usemargins, $disableedit, $disablemove, $disableremove, $outputalsopricetotalwithtax;
5182 
5183  // Define $usemargins (used by objectline_xxx.tpl.php files)
5184  $usemargins = 0;
5185  if (isModEnabled('margin') && !empty($this->element) && in_array($this->element, array('facture', 'facturerec', 'propal', 'commande'))) {
5186  $usemargins = 1;
5187  }
5188 
5189  $num = count($this->lines);
5190 
5191  // Line extrafield
5192  if (!is_object($extrafields)) {
5193  require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
5194  $extrafields = new ExtraFields($this->db);
5195  }
5196  $extrafields->fetch_name_optionals_label($this->table_element_line);
5197 
5198  $parameters = array('num' => $num, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $this->table_element_line);
5199  $reshook = $hookmanager->executeHooks('printObjectLineTitle', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5200  if (empty($reshook)) {
5201  // Output template part (modules that overwrite templates must declare this into descriptor)
5202  // Use global variables + $dateSelector + $seller and $buyer
5203  // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook.
5204  $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5205  foreach ($dirtpls as $module => $reldir) {
5206  $res = 0;
5207  if (!empty($module)) {
5208  $tpl = dol_buildpath($reldir.'/objectline_title.tpl.php');
5209  } else {
5210  $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_title.tpl.php';
5211  }
5212  if (file_exists($tpl)) {
5213  if (empty($conf->file->strict_mode)) {
5214  $res = @include $tpl;
5215  } else {
5216  $res = include $tpl; // for debug
5217  }
5218  }
5219  if ($res) {
5220  break;
5221  }
5222  }
5223  }
5224 
5225  $i = 0;
5226 
5227  print "<!-- begin printObjectLines() --><tbody>\n";
5228  foreach ($this->lines as $line) {
5229  //Line extrafield
5230  $line->fetch_optionals();
5231 
5232  //if (is_object($hookmanager) && (($line->product_type == 9 && !empty($line->special_code)) || !empty($line->fk_parent_line)))
5233  if (is_object($hookmanager)) { // Old code is commented on preceding line.
5234  if (empty($line->fk_parent_line)) {
5235  $parameters = array('line' => $line, 'num' => $num, 'i' => $i, 'dateSelector' => $dateSelector, 'seller' => $seller, 'buyer' => $buyer, 'selected' => $selected, 'table_element_line' => $line->table_element, 'defaulttpldir' => $defaulttpldir);
5236  $reshook = $hookmanager->executeHooks('printObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5237  } else {
5238  $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, 'defaulttpldir' => $defaulttpldir);
5239  $reshook = $hookmanager->executeHooks('printObjectSubLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5240  }
5241  }
5242  if (empty($reshook)) {
5243  $this->printObjectLine($action, $line, '', $num, $i, $dateSelector, $seller, $buyer, $selected, $extrafields, $defaulttpldir);
5244  }
5245 
5246  $i++;
5247  }
5248  print "</tbody><!-- end printObjectLines() -->\n";
5249  }
5250 
5268  public function printObjectLine($action, $line, $var, $num, $i, $dateSelector, $seller, $buyer, $selected = 0, $extrafields = null, $defaulttpldir = '/core/tpl')
5269  {
5270  global $conf, $langs, $user, $object, $hookmanager;
5271  global $form;
5272  global $object_rights, $disableedit, $disablemove, $disableremove; // TODO We should not use global var for this !
5273 
5274  $object_rights = $this->getRights();
5275 
5276  // var used into tpl
5277  $text = '';
5278  $description = '';
5279 
5280  // Line in view mode
5281  if ($action != 'editline' || $selected != $line->id) {
5282  // Product
5283  if (!empty($line->fk_product) && $line->fk_product > 0) {
5284  $product_static = new Product($this->db);
5285  $product_static->fetch($line->fk_product);
5286 
5287  $product_static->ref = $line->ref; //can change ref in hook
5288  $product_static->label = !empty($line->label) ? $line->label : ""; //can change label in hook
5289 
5290  $text = $product_static->getNomUrl(1);
5291 
5292  // Define output language and label
5293  if (getDolGlobalInt('MAIN_MULTILANGS')) {
5294  if (property_exists($this, 'socid') && !is_object($this->thirdparty)) {
5295  dol_print_error(null, 'Error: Method printObjectLine was called on an object and object->fetch_thirdparty was not done before');
5296  return;
5297  }
5298 
5299  $prod = new Product($this->db);
5300  $prod->fetch($line->fk_product);
5301 
5302  $outputlangs = $langs;
5303  $newlang = '';
5304  if (empty($newlang) && GETPOST('lang_id', 'aZ09')) {
5305  $newlang = GETPOST('lang_id', 'aZ09');
5306  }
5307  if (getDolGlobalString('PRODUIT_TEXTS_IN_THIRDPARTY_LANGUAGE') && empty($newlang) && is_object($this->thirdparty)) {
5308  $newlang = $this->thirdparty->default_lang; // To use language of customer
5309  }
5310  if (!empty($newlang)) {
5311  $outputlangs = new Translate("", $conf);
5312  $outputlangs->setDefaultLang($newlang);
5313  }
5314 
5315  $label = (!empty($prod->multilangs[$outputlangs->defaultlang]["label"])) ? $prod->multilangs[$outputlangs->defaultlang]["label"] : $line->product_label;
5316  } else {
5317  $label = $line->product_label;
5318  }
5319 
5320  $text .= ' - '.(!empty($line->label) ? $line->label : $label);
5321  $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.
5322  }
5323 
5324  $line->pu_ttc = price2num((!empty($line->subprice) ? $line->subprice : 0) * (1 + ((!empty($line->tva_tx) ? $line->tva_tx : 0) / 100)), 'MU');
5325 
5326  // Output template part (modules that overwrite templates must declare this into descriptor)
5327  // Use global variables + $dateSelector + $seller and $buyer
5328  // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5329  $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5330  foreach ($dirtpls as $module => $reldir) {
5331  $res = 0;
5332  if (!empty($module)) {
5333  $tpl = dol_buildpath($reldir.'/objectline_view.tpl.php');
5334  } else {
5335  $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_view.tpl.php';
5336  }
5337  //var_dump($tpl);
5338  if (file_exists($tpl)) {
5339  if (empty($conf->file->strict_mode)) {
5340  $res = @include $tpl;
5341  } else {
5342  $res = include $tpl; // for debug
5343  }
5344  }
5345  if ($res) {
5346  break;
5347  }
5348  }
5349  }
5350 
5351  // Line in update mode
5352  if ($this->status == 0 && $action == 'editline' && $selected == $line->id) {
5353  $label = (!empty($line->label) ? $line->label : (($line->fk_product > 0) ? $line->product_label : ''));
5354 
5355  $line->pu_ttc = price2num($line->subprice * (1 + ($line->tva_tx / 100)), 'MU');
5356 
5357  // Output template part (modules that overwrite templates must declare this into descriptor)
5358  // Use global variables + $dateSelector + $seller and $buyer
5359  // Note: This is deprecated. If you need to overwrite the tpl file, use instead the hook printObjectLine and printObjectSubLine.
5360  $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5361  foreach ($dirtpls as $module => $reldir) {
5362  if (!empty($module)) {
5363  $tpl = dol_buildpath($reldir.'/objectline_edit.tpl.php');
5364  } else {
5365  $tpl = DOL_DOCUMENT_ROOT.$reldir.'/objectline_edit.tpl.php';
5366  }
5367 
5368  if (empty($conf->file->strict_mode)) {
5369  $res = @include $tpl;
5370  } else {
5371  $res = include $tpl; // for debug
5372  }
5373  if ($res) {
5374  break;
5375  }
5376  }
5377  }
5378  }
5379 
5380 
5381  /* This is to show array of line of details of source object */
5382 
5383 
5394  public function printOriginLinesList($restrictlist = '', $selectedLines = array())
5395  {
5396  global $langs, $hookmanager, $form, $action;
5397 
5398  print '<tr class="liste_titre">';
5399  print '<td class="linecolref">'.$langs->trans('Ref').'</td>';
5400  print '<td class="linecoldescription">'.$langs->trans('Description').'</td>';
5401  print '<td class="linecolvat right">'.$langs->trans('VATRate').'</td>';
5402  print '<td class="linecoluht right">'.$langs->trans('PriceUHT').'</td>';
5403  if (isModEnabled("multicurrency")) {
5404  print '<td class="linecoluht_currency right">'.$langs->trans('PriceUHTCurrency').'</td>';
5405  }
5406  print '<td class="linecolqty right">'.$langs->trans('Qty').'</td>';
5407  if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
5408  print '<td class="linecoluseunit left">'.$langs->trans('Unit').'</td>';
5409  }
5410  print '<td class="linecoldiscount right">'.$langs->trans('ReductionShort').'</td>';
5411  print '<td class="linecolht right">'.$langs->trans('TotalHT').'</td>';
5412  print '<td class="center">'.$form->showCheckAddButtons('checkforselect', 1).'</td>';
5413  print '</tr>';
5414  $i = 0;
5415 
5416  if (!empty($this->lines)) {
5417  foreach ($this->lines as $line) {
5418  $reshook = 0;
5419  //if (is_object($hookmanager) && (($line->product_type == 9 && !empty($line->special_code)) || !empty($line->fk_parent_line))) {
5420  if (is_object($hookmanager)) { // Old code is commented on preceding line.
5421  $parameters = array('line' => $line, 'i' => $i, 'restrictlist' => $restrictlist, 'selectedLines' => $selectedLines);
5422  if (!empty($line->fk_parent_line)) {
5423  $parameters['fk_parent_line'] = $line->fk_parent_line;
5424  }
5425  $reshook = $hookmanager->executeHooks('printOriginObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5426  }
5427  if (empty($reshook)) {
5428  $this->printOriginLine($line, '', $restrictlist, '/core/tpl', $selectedLines);
5429  }
5430 
5431  $i++;
5432  }
5433  }
5434  }
5435 
5449  public function printOriginLine($line, $var, $restrictlist = '', $defaulttpldir = '/core/tpl', $selectedLines = array())
5450  {
5451  global $langs, $conf;
5452 
5453  //var_dump($line);
5454  if (!empty($line->date_start)) {
5455  $date_start = $line->date_start;
5456  } else {
5457  $date_start = $line->date_debut_prevue;
5458  if ($line->date_debut_reel) {
5459  $date_start = $line->date_debut_reel;
5460  }
5461  }
5462  if (!empty($line->date_end)) {
5463  $date_end = $line->date_end;
5464  } else {
5465  $date_end = $line->date_fin_prevue;
5466  if ($line->date_fin_reel) {
5467  $date_end = $line->date_fin_reel;
5468  }
5469  }
5470 
5471  $this->tpl['id'] = $line->id;
5472 
5473  $this->tpl['label'] = '';
5474  if (!empty($line->fk_parent_line)) {
5475  $this->tpl['label'] .= img_picto('', 'rightarrow');
5476  }
5477 
5478  if (($line->info_bits & 2) == 2) { // TODO Not sure this is used for source object
5479  $discount = new DiscountAbsolute($this->db);
5480  if (property_exists($this, 'socid')) {
5481  $discount->fk_soc = $this->socid;
5482  $discount->socid = $this->socid;
5483  }
5484  $this->tpl['label'] .= $discount->getNomUrl(0, 'discount');
5485  } elseif (!empty($line->fk_product)) {
5486  $productstatic = new Product($this->db);
5487  $productstatic->id = $line->fk_product;
5488  $productstatic->ref = $line->ref;
5489  $productstatic->type = $line->fk_product_type;
5490  if (empty($productstatic->ref)) {
5491  $line->fetch_product();
5492  $productstatic = $line->product;
5493  }
5494 
5495  $this->tpl['label'] .= $productstatic->getNomUrl(1);
5496  $this->tpl['label'] .= ' - '.(!empty($line->label) ? $line->label : $line->product_label);
5497  // Dates
5498  if ($line->product_type == 1 && ($date_start || $date_end)) {
5499  $this->tpl['label'] .= get_date_range($date_start, $date_end);
5500  }
5501  } else {
5502  $this->tpl['label'] .= ($line->product_type == -1 ? '&nbsp;' : ($line->product_type == 1 ? img_object($langs->trans(''), 'service') : img_object($langs->trans(''), 'product')));
5503  if (!empty($line->desc)) {
5504  $this->tpl['label'] .= $line->desc;
5505  } else {
5506  $this->tpl['label'] .= ($line->label ? '&nbsp;'.$line->label : '');
5507  }
5508 
5509  // Dates
5510  if ($line->product_type == 1 && ($date_start || $date_end)) {
5511  $this->tpl['label'] .= get_date_range($date_start, $date_end);
5512  }
5513  }
5514 
5515  if (!empty($line->desc)) {
5516  if ($line->desc == '(CREDIT_NOTE)') { // TODO Not sure this is used for source object
5517  $discount = new DiscountAbsolute($this->db);
5518  $discount->fetch($line->fk_remise_except);
5519  $this->tpl['description'] = $langs->transnoentities("DiscountFromCreditNote", $discount->getNomUrl(0));
5520  } elseif ($line->desc == '(DEPOSIT)') { // TODO Not sure this is used for source object
5521  $discount = new DiscountAbsolute($this->db);
5522  $discount->fetch($line->fk_remise_except);
5523  $this->tpl['description'] = $langs->transnoentities("DiscountFromDeposit", $discount->getNomUrl(0));
5524  } elseif ($line->desc == '(EXCESS RECEIVED)') {
5525  $discount = new DiscountAbsolute($this->db);
5526  $discount->fetch($line->fk_remise_except);
5527  $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessReceived", $discount->getNomUrl(0));
5528  } elseif ($line->desc == '(EXCESS PAID)') {
5529  $discount = new DiscountAbsolute($this->db);
5530  $discount->fetch($line->fk_remise_except);
5531  $this->tpl['description'] = $langs->transnoentities("DiscountFromExcessPaid", $discount->getNomUrl(0));
5532  } else {
5533  $this->tpl['description'] = dol_trunc($line->desc, 60);
5534  }
5535  } else {
5536  $this->tpl['description'] = '&nbsp;';
5537  }
5538 
5539  // VAT Rate
5540  $this->tpl['vat_rate'] = vatrate($line->tva_tx, true);
5541  $this->tpl['vat_rate'] .= (($line->info_bits & 1) == 1) ? '*' : '';
5542  if (!empty($line->vat_src_code) && !preg_match('/\‍(/', $this->tpl['vat_rate'])) {
5543  $this->tpl['vat_rate'] .= ' ('.$line->vat_src_code.')';
5544  }
5545 
5546  $this->tpl['price'] = price($line->subprice);
5547  $this->tpl['total_ht'] = price($line->total_ht);
5548  $this->tpl['multicurrency_price'] = price($line->multicurrency_subprice);
5549  $this->tpl['qty'] = (($line->info_bits & 2) != 2) ? $line->qty : '&nbsp;';
5550  if (getDolGlobalInt('PRODUCT_USE_UNITS')) {
5551  $this->tpl['unit'] = $langs->transnoentities($line->getLabelOfUnit('long'));
5552  }
5553  $this->tpl['remise_percent'] = (($line->info_bits & 2) != 2) ? vatrate($line->remise_percent, true) : '&nbsp;';
5554 
5555  // Is the line strike or not
5556  $this->tpl['strike'] = 0;
5557  if ($restrictlist == 'services' && $line->product_type != Product::TYPE_SERVICE) {
5558  $this->tpl['strike'] = 1;
5559  }
5560 
5561  // Output template part (modules that overwrite templates must declare this into descriptor)
5562  // Use global variables + $dateSelector + $seller and $buyer
5563  $dirtpls = array_merge($conf->modules_parts['tpl'], array($defaulttpldir));
5564  foreach ($dirtpls as $module => $reldir) {
5565  if (!empty($module)) {
5566  $tpl = dol_buildpath($reldir.'/originproductline.tpl.php');
5567  } else {
5568  $tpl = DOL_DOCUMENT_ROOT.$reldir.'/originproductline.tpl.php';
5569  }
5570 
5571  if (empty($conf->file->strict_mode)) {
5572  $res = @include $tpl;
5573  } else {
5574  $res = include $tpl; // for debug
5575  }
5576  if ($res) {
5577  break;
5578  }
5579  }
5580  }
5581 
5582 
5583  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5594  public function add_element_resource($resource_id, $resource_type, $busy = 0, $mandatory = 0)
5595  {
5596  // phpcs:enable
5597  $this->db->begin();
5598 
5599  $sql = "INSERT INTO ".$this->db->prefix()."element_resources (";
5600  $sql .= "resource_id";
5601  $sql .= ", resource_type";
5602  $sql .= ", element_id";
5603  $sql .= ", element_type";
5604  $sql .= ", busy";
5605  $sql .= ", mandatory";
5606  $sql .= ") VALUES (";
5607  $sql .= ((int) $resource_id);
5608  $sql .= ", '".$this->db->escape($resource_type)."'";
5609  $sql .= ", '".$this->db->escape($this->id)."'";
5610  $sql .= ", '".$this->db->escape($this->element)."'";
5611  $sql .= ", '".$this->db->escape($busy)."'";
5612  $sql .= ", '".$this->db->escape($mandatory)."'";
5613  $sql .= ")";
5614 
5615  dol_syslog(get_class($this)."::add_element_resource", LOG_DEBUG);
5616  if ($this->db->query($sql)) {
5617  $this->db->commit();
5618  return 1;
5619  } else {
5620  $this->error = $this->db->lasterror();
5621  $this->db->rollback();
5622  return 0;
5623  }
5624  }
5625 
5626  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5635  public function delete_resource($rowid, $element, $notrigger = 0)
5636  {
5637  // phpcs:enable
5638  global $user;
5639 
5640  $this->db->begin();
5641 
5642  $sql = "DELETE FROM ".$this->db->prefix()."element_resources";
5643  $sql .= " WHERE rowid = ".((int) $rowid);
5644 
5645  dol_syslog(get_class($this)."::delete_resource", LOG_DEBUG);
5646 
5647  $resql = $this->db->query($sql);
5648  if (!$resql) {
5649  $this->error = $this->db->lasterror();
5650  $this->db->rollback();
5651  return -1;
5652  } else {
5653  if (!$notrigger) {
5654  $result = $this->call_trigger(strtoupper($element).'_DELETE_RESOURCE', $user);
5655  if ($result < 0) {
5656  $this->db->rollback();
5657  return -1;
5658  }
5659  }
5660  $this->db->commit();
5661  return 1;
5662  }
5663  }
5664 
5665 
5671  public function __clone()
5672  {
5673  // Force a copy of this->lines, otherwise it will point to same object.
5674  if (isset($this->lines) && is_array($this->lines)) {
5675  $nboflines = count($this->lines);
5676  for ($i = 0; $i < $nboflines; $i++) {
5677  if (is_object($this->lines[$i])) {
5678  $this->lines[$i] = clone $this->lines[$i];
5679  }
5680  }
5681  }
5682  }
5683 
5697  protected function commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams = null)
5698  {
5699  global $conf, $langs, $user, $hookmanager, $action;
5700 
5701  $srctemplatepath = '';
5702 
5703  $parameters = array('modelspath' => $modelspath, 'modele' => $modele, 'outputlangs' => $outputlangs, 'hidedetails' => $hidedetails, 'hidedesc' => $hidedesc, 'hideref' => $hideref, 'moreparams' => $moreparams);
5704  $reshook = $hookmanager->executeHooks('commonGenerateDocument', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5705 
5706  if (!empty($reshook)) {
5707  return $reshook;
5708  }
5709 
5710  dol_syslog("commonGenerateDocument modele=".$modele." outputlangs->defaultlang=".(is_object($outputlangs) ? $outputlangs->defaultlang : 'null'));
5711 
5712  if (empty($modele)) {
5713  $this->error = 'BadValueForParameterModele';
5714  return -1;
5715  }
5716 
5717  // Increase limit for PDF build
5718  $err = error_reporting();
5719  error_reporting(0);
5720  @set_time_limit(120);
5721  error_reporting($err);
5722 
5723  // If selected model is a filename template (then $modele="modelname" or "modelname:filename")
5724  $tmp = explode(':', $modele, 2);
5725  $saved_model = $modele;
5726  if (!empty($tmp[1])) {
5727  $modele = $tmp[0];
5728  $srctemplatepath = $tmp[1];
5729  }
5730 
5731  // Search template files
5732  $file = '';
5733  $classname = '';
5734  $filefound = '';
5735  $dirmodels = array('/');
5736  if (is_array($conf->modules_parts['models'])) {
5737  $dirmodels = array_merge($dirmodels, $conf->modules_parts['models']);
5738  }
5739  foreach ($dirmodels as $reldir) {
5740  foreach (array('doc', 'pdf') as $prefix) {
5741  if (in_array(get_class($this), array('Adherent'))) {
5742  // Member module use prefix_modele.class.php
5743  $file = $prefix."_".$modele.".class.php";
5744  } else {
5745  // Other module use prefix_modele.modules.php
5746  $file = $prefix."_".$modele.".modules.php";
5747  }
5748 
5749  $file = dol_sanitizeFileName($file);
5750 
5751  // We check if the file exists
5752  $file = dol_buildpath($reldir.$modelspath.$file, 0);
5753  if (file_exists($file)) {
5754  $filefound = $file;
5755  $classname = $prefix.'_'.$modele;
5756  break;
5757  }
5758  }
5759  if ($filefound) {
5760  break;
5761  }
5762  }
5763 
5764  if ($filefound === '' || $classname === '') {
5765  $this->error = $langs->trans("Error").' Failed to load doc generator with modelpaths='.$modelspath.' - modele='.$modele;
5766  $this->errors[] = $this->error;
5767  dol_syslog($this->error, LOG_ERR);
5768  return -1;
5769  }
5770 
5771  // Sanitize $filefound
5772  $filefound = dol_sanitizePathName($filefound);
5773 
5774  // If generator was found
5775  global $db; // Required to solve a conception error making an include of some code that uses $db instead of $this->db just after.
5776 
5777  require_once $filefound;
5778 
5779  $obj = new $classname($this->db);
5780 
5781  // If generator is ODT, we must have srctemplatepath defined, if not we set it.
5782  if ($obj->type == 'odt' && empty($srctemplatepath)) {
5783  $varfortemplatedir = $obj->scandir;
5784  if ($varfortemplatedir && getDolGlobalString($varfortemplatedir)) {
5785  $dirtoscan = getDolGlobalString($varfortemplatedir);
5786 
5787  $listoffiles = array();
5788 
5789  // Now we add first model found in directories scanned
5790  $listofdir = explode(',', $dirtoscan);
5791  foreach ($listofdir as $key => $tmpdir) {
5792  $tmpdir = trim($tmpdir);
5793  $tmpdir = preg_replace('/DOL_DATA_ROOT/', DOL_DATA_ROOT, $tmpdir);
5794  if (!$tmpdir) {
5795  unset($listofdir[$key]);
5796  continue;
5797  }
5798  if (is_dir($tmpdir)) {
5799  $tmpfiles = dol_dir_list($tmpdir, 'files', 0, '\.od(s|t)$', '', 'name', SORT_ASC, 0);
5800  if (count($tmpfiles)) {
5801  $listoffiles = array_merge($listoffiles, $tmpfiles);
5802  }
5803  }
5804  }
5805 
5806  if (count($listoffiles)) {
5807  foreach ($listoffiles as $record) {
5808  $srctemplatepath = $record['fullname'];
5809  break;
5810  }
5811  }
5812  }
5813 
5814  if (empty($srctemplatepath)) {
5815  $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotDefined';
5816  return -1;
5817  }
5818  }
5819 
5820  if ($obj->type == 'odt' && !empty($srctemplatepath)) {
5821  if (!dol_is_file($srctemplatepath)) {
5822  dol_syslog("Failed to locate template file ".$srctemplatepath, LOG_WARNING);
5823  $this->error = 'ErrorGenerationAskedForOdtTemplateWithSrcFileNotFound';
5824  return -1;
5825  }
5826  }
5827 
5828  // We save charset_output to restore it because write_file can change it if needed for
5829  // output format that does not support UTF8.
5830  $sav_charset_output = empty($outputlangs->charset_output) ? '' : $outputlangs->charset_output;
5831 
5832  // update model_pdf in object
5833  $this->model_pdf = $saved_model;
5834 
5835  if (in_array(get_class($this), array('Adherent'))) {
5836  '@phan-var-force Adherent $this';
5837  $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, 'member', 1, 'tmp_cards', $moreparams);
5838  } else {
5839  $resultwritefile = $obj->write_file($this, $outputlangs, $srctemplatepath, $hidedetails, $hidedesc, $hideref, $moreparams);
5840  }
5841  // After call of write_file $obj->result['fullpath'] is set with generated file. It will be used to update the ECM database index.
5842 
5843  if ($resultwritefile > 0) {
5844  $outputlangs->charset_output = $sav_charset_output;
5845 
5846  // We delete old preview
5847  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
5848  dol_delete_preview($this);
5849 
5850  // Index file in database
5851  if (!empty($obj->result['fullpath'])) {
5852  $destfull = $obj->result['fullpath'];
5853 
5854  // Update the last_main_doc field into main object (if document generator has property ->update_main_doc_field set)
5855  $update_main_doc_field = 0;
5856  if (!empty($obj->update_main_doc_field)) {
5857  $update_main_doc_field = 1;
5858  }
5859 
5860  // Check that the file exists, before indexing it.
5861  // Hint: It does not exist, if we create a PDF and auto delete the ODT File
5862  if (dol_is_file($destfull)) {
5863  $this->indexFile($destfull, $update_main_doc_field);
5864  }
5865  } else {
5866  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);
5867  }
5868 
5869  // Success in building document. We build meta file.
5870  dol_meta_create($this);
5871 
5872  return 1;
5873  } else {
5874  $outputlangs->charset_output = $sav_charset_output;
5875  $this->error = $obj->error;
5876  $this->errors = $obj->errors;
5877  dol_syslog("Error generating document for ".__CLASS__.". Error: ".$obj->error, LOG_ERR);
5878  return -1;
5879  }
5880  }
5881 
5891  public function indexFile($destfull, $update_main_doc_field)
5892  {
5893  global $conf, $user;
5894 
5895  $upload_dir = dirname($destfull);
5896  $destfile = basename($destfull);
5897  $rel_dir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $upload_dir);
5898 
5899  if (!preg_match('/[\\/]temp[\\/]|[\\/]thumbs|\.meta$/', $rel_dir)) { // If not a tmp dir
5900  $filename = basename($destfile);
5901  $rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
5902  $rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
5903 
5904  include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
5905  $ecmfile = new EcmFiles($this->db);
5906  $result = $ecmfile->fetch(0, '', ($rel_dir ? $rel_dir.'/' : '').$filename);
5907 
5908  // Set the public "share" key
5909  $setsharekey = false;
5910  if ($this->element == 'propal' || $this->element == 'proposal') {
5911  if (getDolGlobalInt("PROPOSAL_ALLOW_ONLINESIGN")) {
5912  $setsharekey = true; // feature to make online signature is not set or set to on (default)
5913  }
5914  if (getDolGlobalInt("PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
5915  $setsharekey = true;
5916  }
5917  }
5918  if ($this->element == 'commande' && getDolGlobalInt("ORDER_ALLOW_EXTERNAL_DOWNLOAD")) {
5919  $setsharekey = true;
5920  }
5921  if ($this->element == 'facture' && getDolGlobalInt("INVOICE_ALLOW_EXTERNAL_DOWNLOAD")) {
5922  $setsharekey = true;
5923  }
5924  if ($this->element == 'bank_account' && getDolGlobalInt("BANK_ACCOUNT_ALLOW_EXTERNAL_DOWNLOAD")) {
5925  $setsharekey = true;
5926  }
5927  if ($this->element == 'product' && getDolGlobalInt("PRODUCT_ALLOW_EXTERNAL_DOWNLOAD")) {
5928  $setsharekey = true;
5929  }
5930  if ($this->element == 'contrat' && getDolGlobalInt("CONTRACT_ALLOW_EXTERNAL_DOWNLOAD")) {
5931  $setsharekey = true;
5932  }
5933  if ($this->element == 'fichinter' && getDolGlobalInt("FICHINTER_ALLOW_EXTERNAL_DOWNLOAD")) {
5934  $setsharekey = true;
5935  }
5936  if ($this->element == 'supplier_proposal' && getDolGlobalInt("SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD")) {
5937  $setsharekey = true;
5938  }
5939  if ($this->element == 'societe_rib' && getDolGlobalInt("SOCIETE_RIB_ALLOW_ONLINESIGN")) {
5940  $setsharekey = true;
5941  }
5942 
5943  if ($setsharekey) {
5944  if (empty($ecmfile->share)) { // Because object not found or share not set yet
5945  require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
5946  $ecmfile->share = getRandomPassword(true);
5947  }
5948  }
5949 
5950  if ($result > 0) {
5951  $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
5952  $ecmfile->fullpath_orig = '';
5953  $ecmfile->gen_or_uploaded = 'generated';
5954  $ecmfile->description = ''; // indexed content
5955  $ecmfile->keywords = ''; // keyword content
5956  $result = $ecmfile->update($user);
5957  if ($result < 0) {
5958  setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
5959  return -1;
5960  }
5961  } else {
5962  $ecmfile->entity = $conf->entity;
5963  $ecmfile->filepath = $rel_dir;
5964  $ecmfile->filename = $filename;
5965  $ecmfile->label = md5_file(dol_osencode($destfull)); // hash of file content
5966  $ecmfile->fullpath_orig = '';
5967  $ecmfile->gen_or_uploaded = 'generated';
5968  $ecmfile->description = ''; // indexed content
5969  $ecmfile->keywords = ''; // keyword content
5970  $ecmfile->src_object_type = $this->table_element; // $this->table_name is 'myobject' or 'mymodule_myobject'.
5971  $ecmfile->src_object_id = $this->id;
5972 
5973  $result = $ecmfile->create($user);
5974  if ($result < 0) {
5975  setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
5976  return -1;
5977  }
5978  }
5979 
5980  /*$this->result['fullname']=$destfull;
5981  $this->result['filepath']=$ecmfile->filepath;
5982  $this->result['filename']=$ecmfile->filename;*/
5983  //var_dump($obj->update_main_doc_field);exit;
5984 
5985  if ($update_main_doc_field && !empty($this->table_element)) {
5986  $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET last_main_doc = '".$this->db->escape($ecmfile->filepath."/".$ecmfile->filename)."'";
5987  $sql .= " WHERE rowid = ".((int) $this->id);
5988 
5989  $resql = $this->db->query($sql);
5990  if (!$resql) {
5991  dol_print_error($this->db);
5992  return -1;
5993  } else {
5994  $this->last_main_doc = $ecmfile->filepath.'/'.$ecmfile->filename;
5995  }
5996  }
5997  }
5998 
5999  return 1;
6000  }
6001 
6009  public function addThumbs($file)
6010  {
6011  $file_osencoded = dol_osencode($file);
6012 
6013  if (file_exists($file_osencoded)) {
6014  require_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6015 
6016  $tmparraysize = getDefaultImageSizes();
6017  $maxwidthsmall = $tmparraysize['maxwidthsmall'];
6018  $maxheightsmall = $tmparraysize['maxheightsmall'];
6019  $maxwidthmini = $tmparraysize['maxwidthmini'];
6020  $maxheightmini = $tmparraysize['maxheightmini'];
6021  //$quality = $tmparraysize['quality'];
6022  $quality = 50; // For thumbs, we force quality to 50
6023 
6024  // Create small thumbs for company (Ratio is near 16/9)
6025  // Used on logon for example
6026  vignette($file_osencoded, $maxwidthsmall, $maxheightsmall, '_small', $quality);
6027 
6028  // Create mini thumbs for company (Ratio is near 16/9)
6029  // Used on menu or for setup page for example
6030  vignette($file_osencoded, $maxwidthmini, $maxheightmini, '_mini', $quality);
6031  }
6032  }
6033 
6041  public function delThumbs($file)
6042  {
6043  $imgThumbName = getImageFileNameForSize($file, '_small'); // Full path of thumb file
6044  dol_delete_file($imgThumbName);
6045  $imgThumbName = getImageFileNameForSize($file, '_mini'); // Full path of thumb file
6046  dol_delete_file($imgThumbName);
6047  }
6048 
6049 
6050  /* Functions common to commonobject and commonobjectline */
6051 
6052  /* For default values */
6053 
6067  public function getDefaultCreateValueFor($fieldname, $alternatevalue = null, $type = 'alphanohtml')
6068  {
6069  // If param here has been posted, we use this value first.
6070  if (GETPOSTISSET($fieldname)) {
6071  return GETPOST($fieldname, $type, 3);
6072  }
6073 
6074  if (isset($alternatevalue)) {
6075  return $alternatevalue;
6076  }
6077 
6078  $newelement = $this->element;
6079  if ($newelement == 'facture') {
6080  $newelement = 'invoice';
6081  }
6082  if ($newelement == 'commande') {
6083  $newelement = 'order';
6084  }
6085  if (empty($newelement)) {
6086  dol_syslog("Ask a default value using common method getDefaultCreateValueForField on an object with no property ->element defined. Return empty string.", LOG_WARNING);
6087  return '';
6088  }
6089 
6090  $keyforfieldname = strtoupper($newelement.'_DEFAULT_'.$fieldname);
6091  //var_dump($keyforfieldname);
6092  if (getDolGlobalString($keyforfieldname)) {
6093  return getDolGlobalString($keyforfieldname);
6094  }
6095 
6096  // TODO Ad here a scan into table llx_overwrite_default with a filter on $this->element and $fieldname
6097  // store content into $conf->cache['overwrite_default']
6098 
6099  return '';
6100  }
6101 
6102 
6103  /* For triggers */
6104 
6105 
6106  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6117  public function call_trigger($triggerName, $user)
6118  {
6119  // phpcs:enable
6120  global $langs, $conf;
6121 
6122  if (!empty(self::TRIGGER_PREFIX) && strpos($triggerName, self::TRIGGER_PREFIX . '_') !== 0) {
6123  dol_print_error(null, 'The trigger "' . $triggerName . '" does not start with "' . self::TRIGGER_PREFIX . '_" as required.');
6124  exit;
6125  }
6126  if (!is_object($langs)) { // If lang was not defined, we set it. It is required by run_triggers().
6127  include_once DOL_DOCUMENT_ROOT.'/core/class/translate.class.php';
6128  $langs = new Translate('', $conf);
6129  }
6130 
6131  include_once DOL_DOCUMENT_ROOT.'/core/class/interfaces.class.php';
6132  $interface = new Interfaces($this->db);
6133  $result = $interface->run_triggers($triggerName, $this, $user, $langs, $conf);
6134 
6135  if ($result < 0) {
6136  if (!empty($this->errors)) {
6137  $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.
6138  } else {
6139  $this->errors = $interface->errors;
6140  }
6141  }
6142  return $result;
6143  }
6144 
6145 
6146  /* Functions for data in other language */
6147 
6148 
6157  {
6158  // To avoid SQL errors. Probably not the better solution though
6159  if (!$this->element) {
6160  return 0;
6161  }
6162  if (!($this->id > 0)) {
6163  return 0;
6164  }
6165  if (is_array($this->array_languages)) {
6166  return 1;
6167  }
6168 
6169  $this->array_languages = array();
6170 
6171  $element = $this->element;
6172  if ($element == 'categorie') {
6173  $element = 'categories'; // For compatibility
6174  }
6175 
6176  // Request to get translation values for object
6177  $sql = "SELECT rowid, property, lang , value";
6178  $sql .= " FROM ".$this->db->prefix()."object_lang";
6179  $sql .= " WHERE type_object = '".$this->db->escape($element)."'";
6180  $sql .= " AND fk_object = ".((int) $this->id);
6181 
6182  //dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG); // Too verbose
6183  $resql = $this->db->query($sql);
6184  if ($resql) {
6185  $numrows = $this->db->num_rows($resql);
6186  if ($numrows) {
6187  $i = 0;
6188  while ($i < $numrows) {
6189  $obj = $this->db->fetch_object($resql);
6190  $key = $obj->property;
6191  $value = $obj->value;
6192  $codelang = $obj->lang;
6193  $type = $this->fields[$key]['type'];
6194 
6195  // we can add this attribute to object
6196  if (preg_match('/date/', $type)) {
6197  $this->array_languages[$key][$codelang] = $this->db->jdate($value);
6198  } else {
6199  $this->array_languages[$key][$codelang] = $value;
6200  }
6201 
6202  $i++;
6203  }
6204  }
6205 
6206  $this->db->free($resql);
6207 
6208  if ($numrows) {
6209  return $numrows;
6210  } else {
6211  return 0;
6212  }
6213  } else {
6214  dol_print_error($this->db);
6215  return -1;
6216  }
6217  }
6218 
6225  public function setValuesForExtraLanguages($onlykey = '')
6226  {
6227  // Get extra fields
6228  foreach ($_POST as $postfieldkey => $postfieldvalue) {
6229  $tmparray = explode('-', $postfieldkey);
6230  if ($tmparray[0] != 'field') {
6231  continue;
6232  }
6233 
6234  $element = $tmparray[1];
6235  $key = $tmparray[2];
6236  $codelang = $tmparray[3];
6237  //var_dump("postfieldkey=".$postfieldkey." element=".$element." key=".$key." codelang=".$codelang);
6238 
6239  if (!empty($onlykey) && $key != $onlykey) {
6240  continue;
6241  }
6242  if ($element != $this->element) {
6243  continue;
6244  }
6245 
6246  $key_type = $this->fields[$key]['type'];
6247 
6248  $enabled = 1;
6249  if (isset($this->fields[$key]['enabled'])) {
6250  $enabled = (int) dol_eval($this->fields[$key]['enabled'], 1, 1, '1');
6251  }
6252  /*$perms = 1;
6253  if (isset($this->fields[$key]['perms']))
6254  {
6255  $perms = (int) dol_eval($this->fields[$key]['perms'], 1, 1, '1');
6256  }*/
6257  if (empty($enabled)) {
6258  continue;
6259  }
6260  //if (empty($perms)) continue;
6261 
6262  if (in_array($key_type, array('date'))) {
6263  // Clean parameters
6264  // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6265  $value_key = dol_mktime(0, 0, 0, GETPOSTINT($postfieldkey."month"), GETPOSTINT($postfieldkey."day"), GETPOSTINT($postfieldkey."year"));
6266  } elseif (in_array($key_type, array('datetime'))) {
6267  // Clean parameters
6268  // TODO GMT date in memory must be GMT so we should add gm=true in parameters
6269  $value_key = dol_mktime(GETPOSTINT($postfieldkey."hour"), GETPOSTINT($postfieldkey."min"), 0, GETPOSTINT($postfieldkey."month"), GETPOSTINT($postfieldkey."day"), GETPOSTINT($postfieldkey."year"));
6270  } elseif (in_array($key_type, array('checkbox', 'chkbxlst'))) {
6271  $value_arr = GETPOST($postfieldkey, 'array'); // check if an array
6272  if (!empty($value_arr)) {
6273  $value_key = implode(',', $value_arr);
6274  } else {
6275  $value_key = '';
6276  }
6277  } elseif (in_array($key_type, array('price', 'double'))) {
6278  $value_arr = GETPOST($postfieldkey, 'alpha');
6279  $value_key = price2num($value_arr);
6280  } else {
6281  $value_key = GETPOST($postfieldkey);
6282  if (in_array($key_type, array('link')) && $value_key == '-1') {
6283  $value_key = '';
6284  }
6285  }
6286 
6287  $this->array_languages[$key][$codelang] = $value_key;
6288 
6289  /*if ($nofillrequired) {
6290  $langs->load('errors');
6291  setEventMessages($langs->trans('ErrorFieldsRequired').' : '.implode(', ', $error_field_required), null, 'errors');
6292  return -1;
6293  }*/
6294  }
6295 
6296  return 1;
6297  }
6298 
6299 
6300  /* Functions for extrafields */
6301 
6308  public function fetchNoCompute($id)
6309  {
6310  global $conf;
6311 
6312  $savDisableCompute = $conf->disable_compute;
6313  $conf->disable_compute = 1;
6314 
6315  $ret = $this->fetch($id); /* @phpstan-ignore-line */
6316 
6317  $conf->disable_compute = $savDisableCompute;
6318 
6319  return $ret;
6320  }
6321 
6322  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6332  public function fetch_optionals($rowid = null, $optionsArray = null)
6333  {
6334  // phpcs:enable
6335  global $conf, $extrafields;
6336 
6337  if (empty($rowid)) {
6338  $rowid = $this->id;
6339  }
6340  if (empty($rowid) && isset($this->rowid)) {
6341  $rowid = $this->rowid; // deprecated
6342  }
6343 
6344  // To avoid SQL errors. Probably not the better solution though
6345  if (!$this->table_element) {
6346  return 0;
6347  }
6348 
6349  $this->array_options = array();
6350 
6351  if (!is_array($optionsArray)) {
6352  // If $extrafields is not a known object, we initialize it. Best practice is to have $extrafields defined into card.php or list.php page.
6353  if (!isset($extrafields) || !is_object($extrafields)) {
6354  require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6355  $extrafields = new ExtraFields($this->db);
6356  }
6357 
6358  // Load array of extrafields for elementype = $this->table_element
6359  if (empty($extrafields->attributes[$this->table_element]['loaded'])) {
6360  $extrafields->fetch_name_optionals_label($this->table_element);
6361  }
6362  $optionsArray = (!empty($extrafields->attributes[$this->table_element]['label']) ? $extrafields->attributes[$this->table_element]['label'] : null);
6363  } else {
6364  global $extrafields;
6365  dol_syslog("Warning: fetch_optionals was called with param optionsArray defined when you should pass null now", LOG_WARNING);
6366  }
6367 
6368  $table_element = $this->table_element;
6369  if ($table_element == 'categorie') {
6370  $table_element = 'categories'; // For compatibility
6371  }
6372 
6373  // Request to get complementary values
6374  if (is_array($optionsArray) && count($optionsArray) > 0) {
6375  $sql = "SELECT rowid";
6376  foreach ($optionsArray as $name => $label) {
6377  if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || (!in_array($extrafields->attributes[$this->table_element]['type'][$name], ['separate', 'point', 'multipts', 'linestrg','polygon']))) {
6378  $sql .= ", ".$name;
6379  }
6380  // use geo sql fonction to read as text
6381  if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || $extrafields->attributes[$this->table_element]['type'][$name] == 'point') {
6382  $sql .= ", ST_AsWKT(".$name.") as ".$name;
6383  }
6384  if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || $extrafields->attributes[$this->table_element]['type'][$name] == 'multipts') {
6385  $sql .= ", ST_AsWKT(".$name.") as ".$name;
6386  }
6387  if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || $extrafields->attributes[$this->table_element]['type'][$name] == 'linestrg') {
6388  $sql .= ", ST_AsWKT(".$name.") as ".$name;
6389  }
6390  if (empty($extrafields->attributes[$this->table_element]['type'][$name]) || $extrafields->attributes[$this->table_element]['type'][$name] == 'polygon') {
6391  $sql .= ", ST_AsWKT(".$name.") as ".$name;
6392  }
6393  }
6394  $sql .= " FROM ".$this->db->prefix().$table_element."_extrafields";
6395  $sql .= " WHERE fk_object = ".((int) $rowid);
6396 
6397  //dol_syslog(get_class($this)."::fetch_optionals get extrafields data for ".$this->table_element, LOG_DEBUG); // Too verbose
6398  $resql = $this->db->query($sql);
6399  if ($resql) {
6400  $numrows = $this->db->num_rows($resql);
6401  if ($numrows) {
6402  $tab = $this->db->fetch_array($resql);
6403 
6404  foreach ($tab as $key => $value) {
6405  // 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)
6406  if ($key != 'rowid' && $key != 'tms' && $key != 'fk_member' && !is_int($key)) {
6407  // we can add this attribute to object
6408  if (!empty($extrafields->attributes[$this->table_element]) && in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date', 'datetime'))) {
6409  //var_dump($extrafields->attributes[$this->table_element]['type'][$key]);
6410  $this->array_options["options_".$key] = $this->db->jdate($value);
6411  } else {
6412  $this->array_options["options_".$key] = $value;
6413  }
6414 
6415  //var_dump('key '.$key.' '.$value.' type='.$extrafields->attributes[$this->table_element]['type'][$key].' '.$this->array_options["options_".$key]);
6416  }
6417  if (!empty($extrafields->attributes[$this->table_element]['type'][$key]) && $extrafields->attributes[$this->table_element]['type'][$key] == 'password') {
6418  if (!empty($value) && preg_match('/^dolcrypt:/', $value)) {
6419  $this->array_options["options_".$key] = dolDecrypt($value);
6420  }
6421  }
6422  }
6423  } else {
6428  if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6429  foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6430  $this->array_options['options_' . $key] = null;
6431  }
6432  }
6433  }
6434 
6435  // If field is a computed field, value must become result of compute (regardless of whether a row exists
6436  // in the element's extrafields table)
6437  if (is_array($extrafields->attributes[$this->table_element]['label'])) {
6438  foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
6439  if (!empty($extrafields->attributes[$this->table_element]) && !empty($extrafields->attributes[$this->table_element]['computed'][$key])) {
6440  //var_dump($conf->disable_compute);
6441  if (empty($conf->disable_compute)) {
6442  global $objectoffield; // We set a global variable to $objectoffield so
6443  $objectoffield = $this; // we can use it inside computed formula
6444  $this->array_options['options_' . $key] = dol_eval($extrafields->attributes[$this->table_element]['computed'][$key], 1, 0, '2');
6445  }
6446  }
6447  }
6448  }
6449 
6450  $this->db->free($resql);
6451 
6452  if ($numrows) {
6453  return $numrows;
6454  } else {
6455  return 0;
6456  }
6457  } else {
6458  $this->errors[] = $this->db->lasterror;
6459  return -1;
6460  }
6461  }
6462  return 0;
6463  }
6464 
6471  public function deleteExtraFields()
6472  {
6473  global $conf;
6474 
6475  if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
6476  return 0;
6477  }
6478 
6479  $this->db->begin();
6480 
6481  $table_element = $this->table_element;
6482  if ($table_element == 'categorie') {
6483  $table_element = 'categories'; // For compatibility
6484  }
6485 
6486  dol_syslog(get_class($this)."::deleteExtraFields delete", LOG_DEBUG);
6487 
6488  $sql_del = "DELETE FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
6489 
6490  $resql = $this->db->query($sql_del);
6491  if (!$resql) {
6492  $this->error = $this->db->lasterror();
6493  $this->db->rollback();
6494  return -1;
6495  } else {
6496  $this->db->commit();
6497  return 1;
6498  }
6499  }
6500 
6511  public function insertExtraFields($trigger = '', $userused = null)
6512  {
6513  global $conf, $langs, $user;
6514 
6515  if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
6516  return 0;
6517  }
6518 
6519  if (empty($userused)) {
6520  $userused = $user;
6521  }
6522 
6523  $error = 0;
6524 
6525  if (!empty($this->array_options)) {
6526  // Check parameters
6527  $langs->load('admin');
6528  require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6529  $extrafields = new ExtraFields($this->db);
6530  $target_extrafields = $extrafields->fetch_name_optionals_label($this->table_element);
6531 
6532  // Eliminate copied source object extra fields that do not exist in target object
6533  $new_array_options = array();
6534  foreach ($this->array_options as $key => $value) {
6535  if (in_array(substr($key, 8), array_keys($target_extrafields))) { // We remove the 'options_' from $key for test
6536  $new_array_options[$key] = $value;
6537  } elseif (in_array($key, array_keys($target_extrafields))) { // We test on $key that does not contain the 'options_' prefix
6538  $new_array_options['options_'.$key] = $value;
6539  }
6540  }
6541 
6542  foreach ($new_array_options as $key => $value) {
6543  $attributeKey = substr($key, 8); // Remove 'options_' prefix
6544  $attributeType = $extrafields->attributes[$this->table_element]['type'][$attributeKey];
6545  $attributeLabel = $extrafields->attributes[$this->table_element]['label'][$attributeKey];
6546  $attributeParam = $extrafields->attributes[$this->table_element]['param'][$attributeKey];
6547  $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$attributeKey];
6548  $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
6549  $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$attributeKey];
6550 
6551  // If we clone, we have to clean unique extrafields to prevent duplicates.
6552  // This behaviour can be prevented by external code by changing $this->context['createfromclone'] value in createFrom hook
6553  if (!empty($this->context['createfromclone']) && $this->context['createfromclone'] == 'createfromclone' && !empty($attributeUnique)) {
6554  $new_array_options[$key] = null;
6555  }
6556 
6557  // Similar code than into insertExtraFields
6558  if ($attributeRequired) {
6559  $v = $this->array_options[$key];
6560  if (ExtraFields::isEmptyValue($v, $attributeType)) {
6561  $langs->load("errors");
6562  dol_syslog("Mandatory field '".$key."' is empty during create and set to required into definition of extrafields");
6563  $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
6564  return -1;
6565  }
6566  }
6567 
6568  //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
6569  //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
6570 
6571  if (!empty($attrfieldcomputed)) {
6572  if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
6573  $value = dol_eval($attrfieldcomputed, 1, 0, '2');
6574  dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
6575  $new_array_options[$key] = $value;
6576  } else {
6577  $new_array_options[$key] = null;
6578  }
6579  }
6580 
6581  switch ($attributeType) {
6582  case 'int':
6583  if (!is_numeric($value) && $value != '') {
6584  $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
6585  return -1;
6586  } elseif ($value == '') {
6587  $new_array_options[$key] = null;
6588  }
6589  break;
6590  case 'price':
6591  case 'double':
6592  $value = price2num($value);
6593  if (!is_numeric($value) && $value != '') {
6594  dol_syslog($langs->trans("ExtraFieldHasWrongValue")." for ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
6595  $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
6596  return -1;
6597  } elseif ($value == '') {
6598  $value = null;
6599  }
6600  //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
6601  $new_array_options[$key] = $value;
6602  break;
6603  /*case 'select': // Not required, we chose value='0' for undefined values
6604  if ($value=='-1')
6605  {
6606  $this->array_options[$key] = null;
6607  }
6608  break;*/
6609  case 'password':
6610  $algo = '';
6611  if ($this->array_options[$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
6612  // If there is an encryption choice, we use it to encrypt data before insert
6613  $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
6614  $algo = reset($tmparrays);
6615  if ($algo != '') {
6616  //global $action; // $action may be 'create', 'update', 'update_extras'...
6617  //var_dump($action);
6618  //var_dump($this->oldcopy);exit;
6619  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
6620  //var_dump('algo='.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
6621  if (isset($this->oldcopy->array_options[$key]) && $this->array_options[$key] == $this->oldcopy->array_options[$key]) {
6622  // If old value encrypted in database is same than submitted new value, it means we don't change it, so we don't update.
6623  if ($algo == 'dolcrypt') { // dolibarr reversible encryption
6624  if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
6625  $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
6626  } else {
6627  $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6628  }
6629  } else {
6630  $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6631  }
6632  } else {
6633  // If value has changed
6634  if ($algo == 'dolcrypt') { // dolibarr reversible encryption
6635  if (!preg_match('/^dolcrypt:/', $this->array_options[$key])) {
6636  $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
6637  } else {
6638  $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6639  }
6640  } else {
6641  $new_array_options[$key] = dol_hash($this->array_options[$key], $algo);
6642  }
6643  }
6644  } else {
6645  //var_dump('jjj'.$algo.' '.$this->oldcopy->array_options[$key].' -> '.$this->array_options[$key]);
6646  // If this->oldcopy is not defined, we can't know if we change attribute or not, so we must keep value
6647  if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options[$key])) { // dolibarr reversible encryption
6648  $new_array_options[$key] = dolEncrypt($this->array_options[$key]); // warning, must be called when on the master
6649  } else {
6650  $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6651  }
6652  }
6653  } else {
6654  // No encryption
6655  $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6656  }
6657  } else { // Common usage
6658  $new_array_options[$key] = $this->array_options[$key]; // Value is kept
6659  }
6660  break;
6661  case 'date':
6662  case 'datetime':
6663  // If data is a string instead of a timestamp, we convert it
6664  if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
6665  $this->array_options[$key] = strtotime($this->array_options[$key]);
6666  }
6667  $new_array_options[$key] = $this->db->idate($this->array_options[$key]);
6668  break;
6669  case 'datetimegmt':
6670  // If data is a string instead of a timestamp, we convert it
6671  if (!is_numeric($this->array_options[$key]) || $this->array_options[$key] != intval($this->array_options[$key])) {
6672  $this->array_options[$key] = strtotime($this->array_options[$key]);
6673  }
6674  $new_array_options[$key] = $this->db->idate($this->array_options[$key], 'gmt');
6675  break;
6676  case 'link':
6677  $param_list = array_keys($attributeParam['options']);
6678  // 0 : ObjectName
6679  // 1 : classPath
6680  $InfoFieldList = explode(":", $param_list[0]);
6681  dol_include_once($InfoFieldList[1]);
6682  if ($InfoFieldList[0] && class_exists($InfoFieldList[0])) {
6683  if ($value == '-1') { // -1 is key for no defined in combo list of objects
6684  $new_array_options[$key] = '';
6685  } elseif ($value) {
6686  $object = new $InfoFieldList[0]($this->db);
6687  if (is_numeric($value)) {
6688  $res = $object->fetch($value); // Common case
6689  } else {
6690  $res = $object->fetch('', $value); // For compatibility
6691  }
6692 
6693  if ($res > 0) {
6694  $new_array_options[$key] = $object->id;
6695  } else {
6696  $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
6697  return -1;
6698  }
6699  }
6700  } else {
6701  dol_syslog('Error bad setup of extrafield', LOG_WARNING);
6702  }
6703  break;
6704  case 'checkbox':
6705  case 'chkbxlst':
6706  if (is_array($this->array_options[$key])) {
6707  $new_array_options[$key] = implode(',', $this->array_options[$key]);
6708  } else {
6709  $new_array_options[$key] = $this->array_options[$key];
6710  }
6711  break;
6712  }
6713  }
6714 
6715  $this->db->begin();
6716 
6717  $table_element = $this->table_element;
6718  if ($table_element == 'categorie') {
6719  $table_element = 'categories'; // For compatibility
6720  }
6721 
6722  dol_syslog(get_class($this)."::insertExtraFields delete then insert", LOG_DEBUG);
6723 
6724  $sql_del = "DELETE FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
6725  $this->db->query($sql_del);
6726 
6727  $sql = "INSERT INTO ".$this->db->prefix().$table_element."_extrafields (fk_object";
6728  foreach ($new_array_options as $key => $value) {
6729  $attributeKey = substr($key, 8); // Remove 'options_' prefix
6730  // Add field of attribute
6731  if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] != 'separate') { // Only for other type than separator
6732  $sql .= ",".$attributeKey;
6733  }
6734  }
6735  // We must insert a default value for fields for other entities that are mandatory to avoid not null error
6736  if (!empty($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities']) && is_array($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'])) {
6737  foreach ($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'] as $tmpkey => $tmpval) {
6738  if (!isset($extrafields->attributes[$this->table_element]['type'][$tmpkey])) { // If field not already added previously
6739  $sql .= ",".$tmpkey;
6740  }
6741  }
6742  }
6743  $sql .= ") VALUES (".$this->id;
6744 
6745  foreach ($new_array_options as $key => $value) {
6746  $attributeKey = substr($key, 8); // Remove 'options_' prefix
6747  // Add field of attribute
6748  if (!in_array($extrafields->attributes[$this->table_element]['type'][$attributeKey], ['separate', 'point', 'multipts', 'linestrg', 'polygon'])) { // Only for other type than separator)
6749  if ($new_array_options[$key] != '' || $new_array_options[$key] == '0') {
6750  $sql .= ",'".$this->db->escape($new_array_options[$key])."'";
6751  } else {
6752  $sql .= ",null";
6753  }
6754  }
6755  if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] == 'point') { // for point type
6756  if (!empty($new_array_options[$key])) {
6757  $sql .= ",ST_PointFromText('".$this->db->escape($new_array_options[$key])."')";
6758  } else {
6759  $sql .= ",null";
6760  }
6761  }
6762  if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] == 'multipts') { // for point type
6763  if (!empty($new_array_options[$key])) {
6764  $sql .= ",ST_MultiPointFromText('".$this->db->escape($new_array_options[$key])."')";
6765  } else {
6766  $sql .= ",null";
6767  }
6768  }
6769  if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] == 'linestrg') { // for linestring type
6770  if (!empty($new_array_options[$key])) {
6771  $sql .= ",ST_LineFromText('".$this->db->escape($new_array_options[$key])."')";
6772  } else {
6773  $sql .= ",null";
6774  }
6775  }
6776  if ($extrafields->attributes[$this->table_element]['type'][$attributeKey] == 'polygon') { // for polygon type
6777  if (!empty($new_array_options[$key])) {
6778  $sql .= ",ST_PolyFromText('".$this->db->escape($new_array_options[$key])."')";
6779  } else {
6780  $sql .= ",null";
6781  }
6782  }
6783  }
6784  // We must insert a default value for fields for other entities that are mandatory to avoid not null error
6785  if (!empty($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities']) && is_array($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'])) {
6786  foreach ($extrafields->attributes[$this->table_element]['mandatoryfieldsofotherentities'] as $tmpkey => $tmpval) {
6787  if (!isset($extrafields->attributes[$this->table_element]['type'][$tmpkey])) { // If field not already added previously
6788  if (in_array($tmpval, array('int', 'double', 'price'))) {
6789  $sql .= ", 0";
6790  } else {
6791  $sql .= ", ''";
6792  }
6793  }
6794  }
6795  }
6796 
6797  $sql .= ")";
6798 
6799  $resql = $this->db->query($sql);
6800  if (!$resql) {
6801  $this->error = $this->db->lasterror();
6802  $error++;
6803  }
6804 
6805  if (!$error && $trigger) {
6806  // Call trigger
6807  $this->context = array('extrafieldaddupdate' => 1);
6808  $result = $this->call_trigger($trigger, $userused);
6809  if ($result < 0) {
6810  $error++;
6811  }
6812  // End call trigger
6813  }
6814 
6815  if ($error) {
6816  $this->db->rollback();
6817  return -1;
6818  } else {
6819  $this->db->commit();
6820  return 1;
6821  }
6822  } else {
6823  return 0;
6824  }
6825  }
6826 
6837  public function insertExtraLanguages($trigger = '', $userused = null)
6838  {
6839  global $conf, $langs, $user;
6840 
6841  if (empty($userused)) {
6842  $userused = $user;
6843  }
6844 
6845  $error = 0;
6846 
6847  if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
6848  return 0; // For avoid conflicts if trigger used
6849  }
6850 
6851  if (is_array($this->array_languages)) {
6852  $new_array_languages = $this->array_languages;
6853 
6854  foreach ($new_array_languages as $key => $value) {
6855  $attributeKey = $key;
6856  $attributeType = $this->fields[$attributeKey]['type'];
6857  $attributeLabel = $this->fields[$attributeKey]['label'];
6858 
6859  //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
6860  //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
6861 
6862  switch ($attributeType) {
6863  case 'int':
6864  if (is_array($value) || (!is_numeric($value) && $value != '')) {
6865  $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
6866  return -1;
6867  } elseif ($value == '') {
6868  $new_array_languages[$key] = null;
6869  }
6870  break;
6871  case 'double':
6872  $value = price2num($value);
6873  if (!is_numeric($value) && $value != '') {
6874  dol_syslog($langs->trans("ExtraLanguageHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
6875  $this->errors[] = $langs->trans("ExtraLanguageHasWrongValue", $attributeLabel);
6876  return -1;
6877  } elseif ($value == '') {
6878  $new_array_languages[$key] = null;
6879  } else {
6880  $new_array_languages[$key] = $value;
6881  }
6882  break;
6883  /*case 'select': // Not required, we chose value='0' for undefined values
6884  if ($value=='-1')
6885  {
6886  $this->array_options[$key] = null;
6887  }
6888  break;*/
6889  }
6890  }
6891 
6892  $this->db->begin();
6893 
6894  $table_element = $this->table_element;
6895  if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
6896  $table_element = 'categories'; // For compatibility
6897  }
6898 
6899  dol_syslog(get_class($this)."::insertExtraLanguages delete then insert", LOG_DEBUG);
6900 
6901  foreach ($new_array_languages as $key => $langcodearray) { // $key = 'name', 'town', ...
6902  foreach ($langcodearray as $langcode => $value) {
6903  $sql_del = "DELETE FROM ".$this->db->prefix()."object_lang";
6904  $sql_del .= " WHERE fk_object = ".((int) $this->id)." AND property = '".$this->db->escape($key)."' AND type_object = '".$this->db->escape($table_element)."'";
6905  $sql_del .= " AND lang = '".$this->db->escape($langcode)."'";
6906  $this->db->query($sql_del);
6907 
6908  if ($value !== '') {
6909  $sql = "INSERT INTO ".$this->db->prefix()."object_lang (fk_object, property, type_object, lang, value";
6910  $sql .= ") VALUES (".$this->id.", '".$this->db->escape($key)."', '".$this->db->escape($table_element)."', '".$this->db->escape($langcode)."', '".$this->db->escape($value)."'";
6911  $sql .= ")";
6912 
6913  $resql = $this->db->query($sql);
6914  if (!$resql) {
6915  $this->error = $this->db->lasterror();
6916  $error++;
6917  break;
6918  }
6919  }
6920  }
6921  }
6922 
6923  if (!$error && $trigger) {
6924  // Call trigger
6925  $this->context = array('extralanguagesaddupdate' => 1);
6926  $result = $this->call_trigger($trigger, $userused);
6927  if ($result < 0) {
6928  $error++;
6929  }
6930  // End call trigger
6931  }
6932 
6933  if ($error) {
6934  $this->db->rollback();
6935  return -1;
6936  } else {
6937  $this->db->commit();
6938  return 1;
6939  }
6940  } else {
6941  return 0;
6942  }
6943  }
6944 
6955  public function updateExtraField($key, $trigger = null, $userused = null)
6956  {
6957  global $conf, $langs, $user, $hookmanager;
6958 
6959  if (getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED')) {
6960  return 0;
6961  }
6962 
6963  if (empty($userused)) {
6964  $userused = $user;
6965  }
6966 
6967  $error = 0;
6968 
6969  if (!empty($this->array_options) && isset($this->array_options["options_".$key])) {
6970  // Check parameters
6971  $langs->load('admin');
6972  require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
6973  $extrafields = new ExtraFields($this->db);
6974  $extrafields->fetch_name_optionals_label($this->table_element);
6975 
6976  $value = $this->array_options["options_".$key];
6977 
6978  $attributeKey = $key;
6979  $attributeType = $extrafields->attributes[$this->table_element]['type'][$key];
6980  $attributeLabel = $extrafields->attributes[$this->table_element]['label'][$key];
6981  $attributeParam = $extrafields->attributes[$this->table_element]['param'][$key];
6982  $attributeRequired = $extrafields->attributes[$this->table_element]['required'][$key];
6983  $attributeUnique = $extrafields->attributes[$this->table_element]['unique'][$attributeKey];
6984  $attrfieldcomputed = $extrafields->attributes[$this->table_element]['computed'][$key];
6985 
6986  // Similar code than into insertExtraFields
6987  if ($attributeRequired) {
6988  $mandatorypb = false;
6989  if ($attributeType == 'link' && $this->array_options["options_".$key] == '-1') {
6990  $mandatorypb = true;
6991  }
6992  if ($this->array_options["options_".$key] === '') {
6993  $mandatorypb = true;
6994  }
6995  if ($mandatorypb) {
6996  $langs->load("errors");
6997  dol_syslog("Mandatory field 'options_".$key."' is empty during update and set to required into definition of extrafields");
6998  $this->errors[] = $langs->trans('ErrorFieldRequired', $attributeLabel);
6999  return -1;
7000  }
7001  }
7002 
7003  // $new_array_options will be used for direct update, so must contains formatted data for the UPDATE.
7004  $new_array_options = $this->array_options;
7005 
7006  //dol_syslog("attributeLabel=".$attributeLabel, LOG_DEBUG);
7007  //dol_syslog("attributeType=".$attributeType, LOG_DEBUG);
7008  if (!empty($attrfieldcomputed)) {
7009  if (getDolGlobalString('MAIN_STORE_COMPUTED_EXTRAFIELDS')) {
7010  $value = dol_eval($attrfieldcomputed, 1, 0, '2');
7011  dol_syslog($langs->trans("Extrafieldcomputed")." on ".$attributeLabel."(".$value.")", LOG_DEBUG);
7012 
7013  $new_array_options["options_".$key] = $value;
7014 
7015  $this->array_options["options_".$key] = $new_array_options["options_".$key];
7016  } else {
7017  $new_array_options["options_".$key] = null;
7018 
7019  $this->array_options["options_".$key] = $new_array_options["options_".$key];
7020  }
7021  }
7022 
7023  switch ($attributeType) {
7024  case 'int':
7025  if (!is_numeric($value) && $value != '') {
7026  $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7027  return -1;
7028  } elseif ($value === '') {
7029  $new_array_options["options_".$key] = null;
7030 
7031  $this->array_options["options_".$key] = $new_array_options["options_".$key];
7032  }
7033  break;
7034  case 'price':
7035  case 'double':
7036  $value = price2num($value);
7037  if (!is_numeric($value) && $value != '') {
7038  dol_syslog($langs->trans("ExtraFieldHasWrongValue")." on ".$attributeLabel."(".$value."is not '".$attributeType."')", LOG_DEBUG);
7039  $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel);
7040  return -1;
7041  } elseif ($value === '') {
7042  $value = null;
7043  }
7044  //dol_syslog("double value"." on ".$attributeLabel."(".$value." is '".$attributeType."')", LOG_DEBUG);
7045  $new_array_options["options_".$key] = $value;
7046 
7047  $this->array_options["options_".$key] = $new_array_options["options_".$key];
7048  break;
7049  /*case 'select': // Not required, we chose value='0' for undefined values
7050  if ($value=='-1')
7051  {
7052  $new_array_options["options_".$key] = $value;
7053 
7054  $this->array_options["options_".$key] = $new_array_options["options_".$key];
7055  }
7056  break;*/
7057  case 'password':
7058  $algo = '';
7059  if ($this->array_options["options_".$key] != '' && is_array($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options'])) {
7060  // If there is an encryption choice, we use it to encrypt data before insert
7061  $tmparrays = array_keys($extrafields->attributes[$this->table_element]['param'][$attributeKey]['options']);
7062  $algo = reset($tmparrays);
7063  if ($algo != '') {
7064  //global $action; // $action may be 'create', 'update', 'update_extras'...
7065  //var_dump($action);
7066  //var_dump($this->oldcopy);exit;
7067  //var_dump($key.' '.$this->array_options["options_".$key].' '.$algo);
7068  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
7069  //var_dump($this->oldcopy->array_options["options_".$key]); var_dump($this->array_options["options_".$key]);
7070  if (isset($this->oldcopy->array_options["options_".$key]) && $this->array_options["options_".$key] == $this->oldcopy->array_options["options_".$key]) { // If old value encrypted in database is same than submitted new value, it means we don't change it, so we don't update.
7071  if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7072  if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7073  $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7074  } else {
7075  $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7076  }
7077  } else {
7078  $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7079  }
7080  } else {
7081  if ($algo == 'dolcrypt') { // dolibarr reversible encryption
7082  if (!preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) {
7083  $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]);
7084  } else {
7085  $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7086  }
7087  } else {
7088  $new_array_options["options_".$key] = dol_hash($this->array_options["options_".$key], $algo);
7089  }
7090  }
7091  } else {
7092  if ($algo == 'dolcrypt' && !preg_match('/^dolcrypt:/', $this->array_options["options_".$key])) { // dolibarr reversible encryption
7093  $new_array_options["options_".$key] = dolEncrypt($this->array_options["options_".$key]); // warning, must be called when on the master
7094  } else {
7095  $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7096  }
7097  }
7098  } else {
7099  // No encryption
7100  $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7101  }
7102  } else { // Common usage
7103  $new_array_options["options_".$key] = $this->array_options["options_".$key]; // Value is kept
7104  }
7105 
7106  $this->array_options["options_".$key] = $new_array_options["options_".$key];
7107  break;
7108  case 'date':
7109  case 'datetime':
7110  if (empty($this->array_options["options_".$key])) {
7111  $new_array_options["options_".$key] = null;
7112 
7113  $this->array_options["options_".$key] = $new_array_options["options_".$key];
7114  } else {
7115  $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key]);
7116  }
7117  break;
7118  case 'datetimegmt':
7119  if (empty($this->array_options["options_".$key])) {
7120  $new_array_options["options_".$key] = null;
7121 
7122  $this->array_options["options_".$key] = $new_array_options["options_".$key];
7123  } else {
7124  $new_array_options["options_".$key] = $this->db->idate($this->array_options["options_".$key], 'gmt');
7125  }
7126  break;
7127  case 'boolean':
7128  if (empty($this->array_options["options_".$key])) {
7129  $new_array_options["options_".$key] = null;
7130 
7131  $this->array_options["options_".$key] = $new_array_options["options_".$key];
7132  }
7133  break;
7134  case 'link':
7135  if ($this->array_options["options_".$key] === '') {
7136  $new_array_options["options_".$key] = null;
7137 
7138  $this->array_options["options_".$key] = $new_array_options["options_".$key];
7139  }
7140  break;
7141  /*
7142  case 'link':
7143  $param_list = array_keys($attributeParam['options']);
7144  // 0 : ObjectName
7145  // 1 : classPath
7146  $InfoFieldList = explode(":", $param_list[0]);
7147  dol_include_once($InfoFieldList[1]);
7148  if ($InfoFieldList[0] && class_exists($InfoFieldList[0]))
7149  {
7150  if ($value == '-1') // -1 is key for no defined in combo list of objects
7151  {
7152  $new_array_options[$key] = '';
7153  } elseif ($value) {
7154  $object = new $InfoFieldList[0]($this->db);
7155  if (is_numeric($value)) $res = $object->fetch($value); // Common case
7156  else $res = $object->fetch('', $value); // For compatibility
7157 
7158  if ($res > 0) $new_array_options[$key] = $object->id;
7159  else {
7160  $this->error = "Id/Ref '".$value."' for object '".$object->element."' not found";
7161  $this->db->rollback();
7162  return -1;
7163  }
7164  }
7165  } else {
7166  dol_syslog('Error bad setup of extrafield', LOG_WARNING);
7167  }
7168  break;
7169  */
7170  case 'checkbox':
7171  case 'chkbxlst':
7172  $new_array_options = array();
7173  if (is_array($this->array_options["options_".$key])) {
7174  $new_array_options["options_".$key] = implode(',', $this->array_options["options_".$key]);
7175  } else {
7176  $new_array_options["options_".$key] = $this->array_options["options_".$key];
7177  }
7178 
7179  $this->array_options["options_".$key] = $new_array_options["options_".$key];
7180  break;
7181  }
7182 
7183  $this->db->begin();
7184 
7185  $linealreadyfound = 0;
7186 
7187  // 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)
7188  $table_element = $this->table_element;
7189  if ($table_element == 'categorie') { // TODO Rename table llx_categories_extrafields into llx_categorie_extrafields so we can remove this.
7190  $table_element = 'categories'; // For compatibility
7191  }
7192 
7193  $sql = "SELECT COUNT(rowid) as nb FROM ".$this->db->prefix().$table_element."_extrafields WHERE fk_object = ".((int) $this->id);
7194  $resql = $this->db->query($sql);
7195  if ($resql) {
7196  $tmpobj = $this->db->fetch_object($resql);
7197  if ($tmpobj) {
7198  $linealreadyfound = $tmpobj->nb;
7199  }
7200  }
7201 
7202  //var_dump('linealreadyfound='.$linealreadyfound.' sql='.$sql); exit;
7203  if ($linealreadyfound) {
7204  if ($this->array_options["options_".$key] === null) {
7205  $sql = "UPDATE ".$this->db->prefix().$this->table_element."_extrafields SET ".$key." = null";
7206  } else {
7207  $sql = "UPDATE ".$this->db->prefix().$this->table_element."_extrafields SET ".$key." = '".$this->db->escape($new_array_options["options_".$key])."'";
7208  }
7209  $sql .= " WHERE fk_object = ".((int) $this->id);
7210 
7211  $resql = $this->db->query($sql);
7212  if (!$resql) {
7213  $error++;
7214  $this->error = $this->db->lasterror();
7215  }
7216  } else {
7217  $result = $this->insertExtraFields('', $user);
7218  if ($result < 0) {
7219  $error++;
7220  }
7221  }
7222 
7223  if (!$error) {
7224  $parameters = array('key' => $key);
7225  global $action;
7226  $reshook = $hookmanager->executeHooks('updateExtraFieldBeforeCommit', $parameters, $this, $action);
7227  if ($reshook < 0) {
7228  setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
7229  }
7230  }
7231 
7232  if (!$error && $trigger) {
7233  // Call trigger
7234  $this->context = array('extrafieldupdate' => 1);
7235  $result = $this->call_trigger($trigger, $userused);
7236  if ($result < 0) {
7237  $error++;
7238  }
7239  // End call trigger
7240  }
7241 
7242  if ($error) {
7243  dol_syslog(__METHOD__.$this->error, LOG_ERR);
7244  $this->db->rollback();
7245  return -1;
7246  } else {
7247  $this->db->commit();
7248  return 1;
7249  }
7250  } else {
7251  return 0;
7252  }
7253  }
7254 
7261  public function getExtraField($key)
7262  {
7263  return $this->array_options['options_'.$key] ?? null;
7264  }
7265 
7273  public function setExtraField($key, $value)
7274  {
7275  $this->array_options['options_'.$key] = $value;
7276  }
7277 
7288  public function updateExtraLanguages($key, $trigger = null, $userused = null)
7289  {
7290  global $conf, $langs, $user;
7291 
7292  if (empty($userused)) {
7293  $userused = $user;
7294  }
7295 
7296  $error = 0;
7297 
7298  if (getDolGlobalString('MAIN_EXTRALANGUAGES_DISABLED')) {
7299  return 0; // For avoid conflicts if trigger used
7300  }
7301 
7302  return 0;
7303  }
7304 
7305 
7321  public function showInputField($val, $key, $value, $moreparam = '', $keysuffix = '', $keyprefix = '', $morecss = 0, $nonewbutton = 0)
7322  {
7323  global $conf, $langs, $form;
7324 
7325  if (!is_object($form)) {
7326  require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
7327  $form = new Form($this->db);
7328  }
7329 
7330  if (!empty($this->fields)) {
7331  $val = $this->fields[$key];
7332  }
7333 
7334  // Validation tests and output
7335  $fieldValidationErrorMsg = '';
7336  $validationClass = '';
7337  $fieldValidationErrorMsg = $this->getFieldError($key);
7338  if (!empty($fieldValidationErrorMsg)) {
7339  $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
7340  } else {
7341  $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
7342  }
7343 
7344  $valuemultiselectinput = array();
7345  $out = '';
7346  $type = '';
7347  $isDependList = 0;
7348  $param = array();
7349  $param['options'] = array();
7350  $reg = array();
7351  // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7352  $size = !empty($this->fields[$key]['size']) ? $this->fields[$key]['size'] : 0;
7353  // Because we work on extrafields
7354  if (preg_match('/^(integer|link):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7355  $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7356  $type = 'link';
7357  } elseif (preg_match('/^(integer|link):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7358  $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7359  $type = 'link';
7360  } elseif (preg_match('/^(integer|link):(.*):(.*)/i', $val['type'], $reg)) {
7361  $param['options'] = array($reg[2].':'.$reg[3] => 'N');
7362  $type = 'link';
7363  } elseif (preg_match('/^(sellist):(.*):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7364  $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4].':'.$reg[5] => 'N');
7365  $type = 'sellist';
7366  } elseif (preg_match('/^(sellist):(.*):(.*):(.*)/i', $val['type'], $reg)) {
7367  $param['options'] = array($reg[2].':'.$reg[3].':'.$reg[4] => 'N');
7368  $type = 'sellist';
7369  } elseif (preg_match('/^(sellist):(.*):(.*)/i', $val['type'], $reg)) {
7370  $param['options'] = array($reg[2].':'.$reg[3] => 'N');
7371  $type = 'sellist';
7372  } elseif (preg_match('/^chkbxlst:(.*)/i', $val['type'], $reg)) {
7373  $param['options'] = array($reg[1] => 'N');
7374  $type = 'chkbxlst';
7375  } elseif (preg_match('/varchar\‍((\d+)\‍)/', $val['type'], $reg)) {
7376  $param['options'] = array();
7377  $type = 'varchar';
7378  $size = $reg[1];
7379  } elseif (preg_match('/varchar/', $val['type'])) {
7380  $param['options'] = array();
7381  $type = 'varchar';
7382  } else {
7383  $param['options'] = array();
7384  $type = $this->fields[$key]['type'];
7385  }
7386  //var_dump($type); var_dump($param['options']);
7387 
7388  // Special case that force options and type ($type can be integer, varchar, ...)
7389  if (!empty($this->fields[$key]['arrayofkeyval']) && is_array($this->fields[$key]['arrayofkeyval'])) {
7390  $param['options'] = $this->fields[$key]['arrayofkeyval'];
7391  // Special case that prevent to force $type to have multiple input
7392  if (empty($this->fields[$key]['multiinput'])) {
7393  $type = (($this->fields[$key]['type'] == 'checkbox') ? $this->fields[$key]['type'] : 'select');
7394  }
7395  }
7396 
7397  $label = $this->fields[$key]['label'];
7398  //$elementtype=$this->fields[$key]['elementtype']; // Seems not used
7399  // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7400  $default = (!empty($this->fields[$key]['default']) ? $this->fields[$key]['default'] : '');
7401  // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7402  $computed = (!empty($this->fields[$key]['computed']) ? $this->fields[$key]['computed'] : '');
7403  // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7404  $unique = (!empty($this->fields[$key]['unique']) ? $this->fields[$key]['unique'] : 0);
7405  // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7406  $required = (!empty($this->fields[$key]['required']) ? $this->fields[$key]['required'] : 0);
7407  // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7408  $autofocusoncreate = (!empty($this->fields[$key]['autofocusoncreate']) ? $this->fields[$key]['autofocusoncreate'] : 0);
7409 
7410  // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7411  $langfile = (!empty($this->fields[$key]['langfile']) ? $this->fields[$key]['langfile'] : '');
7412  // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
7413  $list = (!empty($this->fields[$key]['list']) ? $this->fields[$key]['list'] : 0);
7414  $hidden = (in_array(abs($this->fields[$key]['visible']), array(0, 2)) ? 1 : 0);
7415 
7416  $objectid = $this->id;
7417 
7418  if ($computed) {
7419  if (!preg_match('/^search_/', $keyprefix)) {
7420  return '<span class="opacitymedium">'.$langs->trans("AutomaticallyCalculated").'</span>';
7421  } else {
7422  return '';
7423  }
7424  }
7425 
7426  // Set value of $morecss. For this, we use in priority showsize from parameters, then $val['css'] then autodefine
7427  if (empty($morecss) && !empty($val['css'])) {
7428  $morecss = $val['css'];
7429  } elseif (empty($morecss)) {
7430  if ($type == 'date') {
7431  $morecss = 'minwidth100imp';
7432  } elseif ($type == 'datetime' || $type == 'link') { // link means an foreign key to another primary id
7433  $morecss = 'minwidth200imp';
7434  } elseif (in_array($type, array('int', 'integer', 'price')) || preg_match('/^double(\‍([0-9],[0-9]\‍)){0,1}/', (string) $type)) {
7435  $morecss = 'maxwidth75';
7436  } elseif ($type == 'url') {
7437  $morecss = 'minwidth400';
7438  } elseif ($type == 'boolean') {
7439  $morecss = '';
7440  } else {
7441  if (is_numeric($size) && round((float) $size) < 12) {
7442  $morecss = 'minwidth100';
7443  } elseif (is_numeric($size) && round((float) $size) <= 48) {
7444  $morecss = 'minwidth200';
7445  } else {
7446  $morecss = 'minwidth400';
7447  }
7448  }
7449  }
7450 
7451  // Add validation state class
7452  if (!empty($validationClass)) {
7453  $morecss .= $validationClass;
7454  }
7455 
7456  if (in_array($type, array('date'))) {
7457  $tmp = explode(',', $size);
7458  $newsize = $tmp[0];
7459  $showtime = 0;
7460 
7461  // Do not show current date when field not required (see selectDate() method)
7462  if (!$required && $value == '') {
7463  $value = '-1';
7464  }
7465 
7466  // TODO Must also support $moreparam
7467  $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1);
7468  } elseif (in_array($type, array('datetime'))) {
7469  $tmp = explode(',', $size);
7470  $newsize = $tmp[0];
7471  $showtime = 1;
7472 
7473  // Do not show current date when field not required (see selectDate() method)
7474  if (!$required && $value == '') {
7475  $value = '-1';
7476  }
7477 
7478  // TODO Must also support $moreparam
7479  $out = $form->selectDate($value, $keyprefix.$key.$keysuffix, $showtime, $showtime, $required, '', 1, (($keyprefix != 'search_' && $keyprefix != 'search_options_') ? 1 : 0), 0, 1, '', '', '', 1, '', '', 'tzuserrel');
7480  } elseif (in_array($type, array('duration'))) {
7481  $out = $form->select_duration($keyprefix.$key.$keysuffix, $value, 0, 'text', 0, 1);
7482  } elseif (in_array($type, array('int', 'integer'))) {
7483  $tmp = explode(',', $size);
7484  $newsize = $tmp[0];
7485  $out = '<input type="text" class="flat '.$morecss.'" name="'.$keyprefix.$key.$keysuffix.'" id="'.$keyprefix.$key.$keysuffix.'"'.($newsize > 0 ? ' maxlength="'.$newsize.'"' : '').' value="'.